/* 
**  mod_log_sdb.c -- Log to sdb database 
**
**  Ulric Eriksson <ulric@siag.nu>
**  Tatsuhiko Miyagawa <miyagawa@bulklnews.net>
**  IKEBE Tomohiro <ikechin@0xfa.com>
**
*/ 

#include "httpd.h"
#include "http_config.h"
#include "http_protocol.h"
#include "http_log.h"
#include "http_core.h"
#include "ap_config.h"
#include "sdb.h"

#ifdef STANDARD20_MODULE_STUFF /* Apache 2.x compatible */
#define APACHE2
#include "ap_compat.h"
#include "apr_pools.h"
#include "apr_strings.h"
#include "apr_tables.h"
#include "unixd.h"

typedef apr_pool_t pool;
typedef apr_array_header_t array_header;
typedef apr_table_t table;
typedef apr_table_entry_t table_entry;
#endif

#ifdef APACHE2
#define LOG_ERROR_ERR(s, args...) \
ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, s, args)
#define LOG_ERROR_INFO(s, args...) \
ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, 0, s, args)
#define LOG_RERROR_INFO(r, args...) \
ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, 0, r, args)
#else
#define LOG_ERROR_ERR(s, args...) \
ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, s, args)
#define LOG_ERROR_INFO(s, args...) \
ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, s, args)
#define LOG_RERROR_INFO(r, args...) \
ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, r, args)
#endif

#define LOG_SDB_VERSION 0.08
#define SQL_TIMEOUT 30000

#define WATCHPOINT printf("WATCHPOINT %s %d\n", __FILE__, __LINE__)

#define LOG_SDB_CREATE "CREATE TABLE %s (" \
"    remote_host       varchar(50)," \
"    remote_user       varchar(50)," \
"    request_uri       varchar(50)," \
"    virtual_host      varchar(50)," \
"    time_stamp        integer unsigned not null," \
"    status            smallint(6)," \
"    bytes_sent        integer," \
"    referer           varchar(255)," \
"    agent             varchar(255)," \
"    request_method    varchar(6)," \
"    request_protocol  varchar(10)" \
")"

typedef struct {
  char *url;
  char *table;
  char *db;	/* will be populated by sdb_open, I think */
} log_sdb_config_rec;

module MODULE_VAR_EXPORT log_sdb_module;

/* utility: NULL to "NULL", others escaped and quoted */
static char *quote(request_rec *r, const char *str)
{
  char *new;
  int  i, j, n;

  if (str == 0) {
    return "NULL";
  }

  for (i=0, n=0; str[i]; i++) {
    if (str[i] == '\'') n++;
  }
  n += i + 1;
  new = ap_palloc(r->pool, n + 2);

  new[0] = '\'';
  for (i=0, j=1; str[i]; i++) {
    new[j++] = str[i];
    if (str[i] == '\'') new[j++] = str[i];
  }
  new[i+1] = '\'';
  new[i+2] = 0;
  return new;
}

/* create config */
static void *create_log_sdb_config(pool *p, server_rec *s)
{
  log_sdb_config_rec *conf = (log_sdb_config_rec *) ap_pcalloc(p, sizeof(log_sdb_config_rec));
  conf->url = NULL;
  conf->table   = "access_log";
  conf->db      = 0;
  return (void *)conf;
}

/* set logdb file path */
static const char *set_sdb_url(cmd_parms *cmd, void *mconfig, char *param)
{
  log_sdb_config_rec *conf = (log_sdb_config_rec *) ap_get_module_config(cmd->server->module_config, &log_sdb_module);
  conf->url = param;

  return NULL;
}

/* set table name */
static const char *set_sdb_table(cmd_parms *cmd, void *mconfig, char *param)
{
  log_sdb_config_rec *conf = (log_sdb_config_rec *) ap_get_module_config(cmd->server->module_config, &log_sdb_module);
  conf->table = param;
  return NULL;
}

static int null_cb(int n, char **p, void *closure)
{
	return 0;
}

static void log_sdb_open(server_rec *s, pool *p)
{
    log_sdb_config_rec *conf = (log_sdb_config_rec *) ap_get_module_config(s->module_config, &log_sdb_module);
    int ret;

    if (!conf->url) {
	return;
    }

    conf->db = sdb_open(conf->url);
    LOG_ERROR_INFO(s, "sdb open for %s", s->server_hostname);
    if (conf->db == 0) {
	LOG_ERROR_ERR(s, "sdb open error");
    }
    else {
	char query[1024];

	snprintf(query, sizeof query, LOG_SDB_CREATE, conf->table);
	ret = sdb_query(conf->db, query, null_cb, NULL);
	if (ret < 0) {
	    LOG_ERROR_INFO(s, "sdb exec error creating table: %s", conf->table);
	}
    }
    return;
}

/* open logdb file */
#ifdef APACHE2
static int init_log_sdb(pool *pc, pool *p, pool *pt, server_rec *s)
#else
static void init_log_sdb(server_rec* s, pool *p)
#endif
{
    log_sdb_open(s, p);
    for (s = s->next; s; s = s->next){
	log_sdb_open(s, p);
    }
#ifdef APACHE2
    return OK;
#else
    return;
#endif
}

/* log_sdb handler */
static int log_sdb_handler(request_rec *r)
{
  char query[10240];

  log_sdb_config_rec *conf = (log_sdb_config_rec *) ap_get_module_config(r->server->module_config, &log_sdb_module);
  int  ret;
#ifdef APACHE2
  char *user = ap_pstrdup(r->pool, r->user);
  time_t time_stamp = atoi(ap_ht_time(r->pool, r->request_time, "%s", 0));
  char *remote_host = ap_pstrdup(r->pool, ap_get_remote_host(r->connection, r->per_dir_config, REMOTE_NAME, NULL));
#else
  char *user = ap_pstrdup(r->pool, r->connection->user);
  time_t time_stamp = r->request_time;
  char *remote_host = ap_pstrdup(r->pool, ap_get_remote_host(r->connection, r->per_dir_config, REMOTE_NAME));
#endif

  if (conf->db == 0) {
    return DECLINED;
  }

#if 0	/* can't do this */
  /* sets timeout handler */
  sdb_busy_timeout(conf->db, SQL_TIMEOUT);
#endif

  /* using '%q' would lead to '(NULL)', thus I use my own quote() function */
  snprintf(query, sizeof query,
	   "INSERT INTO %s "
	   "(remote_host, remote_user, request_uri, virtual_host, time_stamp, status, bytes_sent, referer, agent, request_method, request_protocol) "
	   "VALUES (%s,%s,%s,%s,%i,%i,%i,%s,%s,%s,%s)",
	   conf->table,
	   quote(r, remote_host),
	   quote(r, user),
	   quote(r, r->uri),
	   quote(r, r->hostname),
	   time_stamp,
	   r->status,
	   r->bytes_sent,
	   quote(r, ap_table_get(r->headers_in, "Referer")),
	   quote(r, ap_table_get(r->headers_in, "User-Agent")),
	   quote(r, r->method),
	   quote(r, r->protocol));
  ret = sdb_query(conf->db, query, null_cb, NULL);
  
  if (ret < 0) {
	LOG_RERROR_INFO(r, "sdb exec error");
  }

  return OK;
}

static void log_sdb_close(server_rec *s, pool *p)
{
    log_sdb_config_rec *conf = (log_sdb_config_rec *) ap_get_module_config(s->module_config, &log_sdb_module);
    LOG_ERROR_INFO(s, "close db for %s", s->server_hostname);
    if (conf->db != 0)
	sdb_close(conf->db);
    return;
}

/* close logdb on child exit */
#ifdef APACHE2
static apr_status_t cleanup_log_sdb(void *data)
#else
static void cleanup_log_sdb(server_rec *s, pool *p)
#endif
{
#ifdef APACHE2
    server_rec *s = (server_rec *)data;
    pool *p = s->process->pool;
#endif
    log_sdb_close(s, p);
    for (s = s->next; s; s = s->next){
	log_sdb_close(s, p);
    }
#ifdef APACHE2
    return APR_SUCCESS;
#else
    return;
#endif
}

/* setup commands */
static const command_rec log_sdb_cmds[] = {
  {"LogSdbUrl", (void *)set_sdb_url, 
   NULL, RSRC_CONF, TAKE1, "sdb log database url"},
  {"LogSdbTable", (void *)set_sdb_table, 
   NULL, RSRC_CONF, TAKE1, "sdb log table name"},
  {NULL},
};

#ifdef APACHE2

static void log_sdb_init_child(pool *p, server_rec *s)
{
    apr_pool_cleanup_register(p, s, cleanup_log_sdb, cleanup_log_sdb);
}

static void log_sdb_register_hooks(pool *p)
{
    ap_hook_open_logs(init_log_sdb, NULL, NULL, APR_HOOK_MIDDLE);
    ap_hook_log_transaction(log_sdb_handler, NULL, NULL, APR_HOOK_MIDDLE);
    ap_hook_child_init(log_sdb_init_child, NULL, NULL, APR_HOOK_MIDDLE);
}

module AP_MODULE_DECLARE_DATA log_sdb_module =
{
    STANDARD20_MODULE_STUFF,
    NULL,     /* dir config creater */
    NULL,      /* dir merger --- default is to override */
    create_log_sdb_config,  /* create per-server config structure */
    NULL,                       /* merge server config */
    log_sdb_cmds,                  /* command apr_table_t */
    log_sdb_register_hooks         /* register hooks */
};

#else
/* Dispatch list for API hooks */
module MODULE_VAR_EXPORT log_sdb_module = {
    STANDARD_MODULE_STUFF, 
    init_log_sdb,       /* module initializer                  */
    NULL,                  /* create per-dir    config structures */
    NULL,                  /* merge  per-dir    config structures */
    create_log_sdb_config,         /* create per-server config structures */
    NULL,                  /* merge  per-server config structures */
    log_sdb_cmds,       /* table of config file commands       */
    NULL,                  /* [#8] MIME-typed-dispatched handlers */
    NULL,                  /* [#1] URI to filename translation    */
    NULL,                  /* [#4] validate user id from request  */
    NULL,                  /* [#5] check if the user is ok _here_ */
    NULL,                  /* [#3] check access by host address   */
    NULL,                  /* [#6] determine MIME type            */
    NULL,                  /* [#7] pre-run fixups                 */
    log_sdb_handler,    /* [#9] log a transaction              */
    NULL,                  /* [#2] header parser                  */
    NULL,                  /* child_init                          */
    cleanup_log_sdb,    /* child_exit                          */
    NULL                   /* [#0] post read-request              */
};
#endif

