/* 
**  mod_auth_sdb.c -- Apache sample auth_sdb module
**  [Autogenerated via ``apxs -n auth_sdb -g'']
*/ 

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

#define AUTH_SDB_CRYPT 0
#define AUTH_SDB_PLAIN 1

#ifdef STANDARD20_MODULE_STUFF /* Apache 2.x */

#define APACHE2

#include "ap_compat.h"
#include "apr_pools.h"
#include "apr_strings.h"
#include "apr_tables.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;

#define AUTH_REQUIRED HTTP_UNAUTHORIZED
#define XtOffsetOf(dir_conf, member) APR_XtOffsetOf(dir_conf, member)

#endif

#ifdef APACHE2
#define LOG_RERROR_ERR(r, args...) ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, r, args);
#else
#define LOG_RERROR_ERR(r, args...) ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, r, args);
#endif

typedef struct {
    char *url;
    char *auth_user_table;
    char *auth_user_field;
    char *auth_passwd_field;
    char *auth_group_table;
    char *auth_group_field;
    char *auth_groupuser_field;
    char *auth_where_clause;
    int auth_passwd_type;
    int authoritative;
} auth_sdb_config_rec;

static char *
quote_sql(pool *p, char *str)
{
    char *quoted;
    int i, j;
    for (i = 0,j = 0; str[i]; i++,j++){
	if (str[i] == '\'')
	    j++;
    }
    quoted = ap_pcalloc(p, j + 1);
    for (i = 0,j = 0; str[i]; i++,j++){
	quoted[j] = str[i];
	if (str[i] == '\'')
	    quoted[++j] = '\'';
    }
    quoted[j] = '\0';
    return quoted;
}

static void *
create_auth_sdb_dir_config(pool *p, char *path)
{
    auth_sdb_config_rec *conf = 
	(auth_sdb_config_rec *) ap_pcalloc(p, sizeof(auth_sdb_config_rec));
    conf->url = NULL;
    conf->auth_user_table = "htpasswd";
    conf->auth_user_field = "user";
    conf->auth_passwd_field = "passwd";
    conf->auth_group_table = "htgroup";
    conf->auth_group_field = "groupname";
    conf->auth_groupuser_field = "user";
    conf->auth_passwd_type = AUTH_SDB_CRYPT;
    conf->auth_where_clause = NULL;
    conf->authoritative = 1;
    return (void *)conf;
}

static const command_rec auth_sdb_cmds[] =
{
    {"AuthSDBURL", (void *)ap_set_string_slot,
     (void *)XtOffsetOf(auth_sdb_config_rec, url),
     OR_AUTHCFG, TAKE1, 
     "set sdb url string"},
    {"AuthSDBUserTable", (void *)ap_set_string_slot,
     (void *) XtOffsetOf(auth_sdb_config_rec, auth_user_table),
     OR_AUTHCFG, TAKE1, 
     "set sdb user table"},
    {"AuthSDBUserField", (void *)ap_set_string_slot,
     (void *) XtOffsetOf(auth_sdb_config_rec, auth_user_field),
     OR_AUTHCFG, TAKE1, 
     "set sdb username field"},
    {"AuthSDBPasswdField", (void *)ap_set_string_slot,
     (void *) XtOffsetOf(auth_sdb_config_rec, auth_passwd_field),
     OR_AUTHCFG, TAKE1, 
     "set sdb passwd field"},
    {"AuthSDBGroupTable", (void *)ap_set_string_slot,
     (void *) XtOffsetOf(auth_sdb_config_rec, auth_group_table),
     OR_AUTHCFG, TAKE1, 
     "set sdb group table"},
    {"AuthSDBGroupField", (void *)ap_set_string_slot,
     (void *) XtOffsetOf(auth_sdb_config_rec, auth_group_field),
     OR_AUTHCFG, TAKE1, 
     "set sdb group field"},
    {"AuthSDBGroupUserField", (void *)ap_set_string_slot,
     (void *) XtOffsetOf(auth_sdb_config_rec, auth_groupuser_field),
     OR_AUTHCFG, TAKE1, 
     "set sdb group user field"},
    {"AuthSDBWhereClause", (void *)ap_set_string_slot,
     (void *) XtOffsetOf(auth_sdb_config_rec, auth_where_clause),
     OR_AUTHCFG, TAKE1, 
     "set sdb optional WHERE clause"},
    {"AuthSDBPlainText", (void *)ap_set_flag_slot,
     (void *) XtOffsetOf(auth_sdb_config_rec, auth_passwd_type),
     OR_AUTHCFG, FLAG, 
     "use plain text or crypted passwd"},
    {"AuthSDBAuthoritative", (void *)ap_set_flag_slot,
     (void *) XtOffsetOf(auth_sdb_config_rec, authoritative),
     OR_AUTHCFG, FLAG, 
     "AuthSDB is authoritative or not"},
    {NULL}
};

module MODULE_VAR_EXPORT auth_sdb_module;

/* SDB callback function, store passwd into r->notes */
static int
get_pw_callback(int n, char **row, void *closure)
{
    request_rec *r = (request_rec *)closure;
    if (!n) {
	ap_table_set(r->notes, "AuthSDBRealPW", NULL);
	return 0;
    }
    ap_table_set(r->notes, "AuthSDBRealPW", ap_pstrdup(r->pool, row[0]));
    return 0;
}

/* get password from RDBMS */
static char *
get_pw(request_rec *r, auth_sdb_config_rec *conf)
{
    int ret;
    char *SQL;
    char *real_pw;
#ifdef APACHE2
    char *user = r->user;
#else
    char *user = r->connection->user;
#endif

    if(conf->auth_where_clause != NULL) {
	SQL = ap_psprintf(r->pool, "SELECT %s FROM %s WHERE %s = '%s' AND (%s)",
			  conf->auth_passwd_field,
			  conf->auth_user_table,
			  conf->auth_user_field,
			  quote_sql(r->pool, user),
			  conf->auth_where_clause);
    }
    else {
	SQL = ap_psprintf(r->pool, "SELECT %s FROM %s WHERE %s = '%s'",
			  conf->auth_passwd_field,
			  conf->auth_user_table,
			  conf->auth_user_field,
			  quote_sql(r->pool, user));
    }
    sdb_init();
    ret = sdb_query(conf->url, SQL, get_pw_callback, (void *)r);
    if(ret == -1) {
	LOG_RERROR_ERR(r, "sdb query failed");
	return NULL;
    }
    /* get result via r->notes */
    real_pw = ap_pstrdup(r->pool, ap_table_get(r->notes, "AuthSDBRealPW"));
    return real_pw;
}

/* auth_sdb authen handler */
static int
auth_sdb_authen (request_rec *r)
{
    auth_sdb_config_rec *conf =
	(auth_sdb_config_rec *) ap_get_module_config(r->per_dir_config, &auth_sdb_module);
    int ret;
    const char *sent_pw;
    char *real_pw;
    char *user;

    if(conf->url == NULL)
	return DECLINED;
    ret = ap_get_basic_auth_pw(r, &sent_pw);
    if (ret != OK)
	return ret;
#ifdef APACHE2
    user = r->user;
#else
    user = r->connection->user;
#endif
    if(!(real_pw = get_pw(r, conf))) {
	if (!(conf->authoritative))
	    return DECLINED;
	LOG_RERROR_ERR(r, "user %s not found for %s", user, r->uri);
	ap_note_basic_auth_failure(r);
	return AUTH_REQUIRED;
    }
    if(conf->auth_passwd_type == AUTH_SDB_PLAIN){
	if (strcmp(sent_pw, real_pw) == 0) {
	    return OK;
	} else {
	    LOG_RERROR_ERR(r, "user %s: auth failure for %s", user, r->uri);
	    ap_note_basic_auth_failure(r);
	    return AUTH_REQUIRED;
	}
    }
    else {
	if (ap_validate_password(sent_pw, real_pw) != NULL){
	    LOG_RERROR_ERR(r, "user %s: auth failure for %s", user, r->uri);
	    ap_note_basic_auth_failure(r);
	    return AUTH_REQUIRED;
	}
    }
    return OK;
}

/* SDB callback function, store groups into table */
static int
find_groups_callback(int n, char **row, void *closure)
{
    int i;
    table *groups = (table *)closure;
    if (!n)
	return 0;
    for(i = 0; i < n; i++){
	/* XXX DEBUG print for httpd -X */
	/* printf("%s\n", row[i]); */
	ap_table_set(groups, row[i], "On");
    }
    return 0;
}

/* find groups */
static table *
find_groups(request_rec *r, auth_sdb_config_rec *conf)
{
    table *groups = ap_make_table(r->pool, 15);
    char *SQL;
    int ret;
#ifdef APACHE2
    char *user = r->user;
#else
    char *user = r->connection->user;
#endif
    
    SQL = ap_psprintf(r->pool, 
		      "SELECT %s FROM %s WHERE %s = '%s'",
		      conf->auth_group_field, 
		      conf->auth_group_table,
		      conf->auth_groupuser_field,
		      quote_sql(r->pool, user));
    sdb_init();
    ret = sdb_query(conf->url, SQL, find_groups_callback, (void *)groups);
    if(ret == -1){
	LOG_RERROR_ERR(r, "sdb query failed");
	return NULL;
    }
    return groups;
}

/* auth_sdb autz handler */
static int
auth_sdb_authz (request_rec *r)
{
    auth_sdb_config_rec *conf = 
	(auth_sdb_config_rec *)ap_get_module_config(r->per_dir_config, &auth_sdb_module);
    int method_restricted = 0;
    table *grpstatus;
    const array_header *reqs_arr = ap_requires(r);
    require_line *reqs;
    int x;
    const char *t, *w;
#ifdef APACHE2
    char *user = r->user;
#else
    char *user = r->connection->user;
#endif

    if (reqs_arr == NULL) {
	return OK;
    }
    reqs = (require_line *) reqs_arr->elts;
    for (x = 0; x < reqs_arr->nelts; x++) {
        if (! (reqs[x].method_mask & (1 << r->method_number))) {
            continue;
        }
	method_restricted = 1;
	t = reqs[x].requirement;
	w = ap_getword_white(r->pool, &t);
	if (strcmp(w, "valid-user") == 0) {
	    return OK;
        }
	if (strcmp(w, "user") == 0) {
	    while (t[0] != '\0') {
		w = ap_getword_conf(r->pool, &t);
		if (strcmp(user, w) == 0) {
		    return OK;
                }
	    }
	}
	else if (strcmp(w, "group") == 0) {
	    if(conf->auth_group_table) {
		grpstatus = find_groups(r, conf);
	    }
	    else {
		grpstatus = NULL;
	    }
	    if (grpstatus == NULL) {
		return DECLINED;	
            }
	    while (t[0]) {
		w = ap_getword_conf(r->pool, &t);
		if (ap_table_get(grpstatus, w)) {
		    return OK;
                }
	    }
	}
        else if (conf->authoritative) {
	    LOG_RERROR_ERR(r,
			   "access to %s failed, "
			   "unknown require directive: %s", r->uri, reqs[x].requirement);
	}
    }
    if (!method_restricted) {
	return OK;
    }
    if (!conf->authoritative) {
	return DECLINED;
    }
    LOG_RERROR_ERR(r, "user %s, access to %s failed", user, r->uri);
    ap_note_basic_auth_failure(r);
    return AUTH_REQUIRED;
}

#ifdef APACHE2 /* for Apache 2.x */
static void
auth_sdb_register_hooks(pool *p)
{
    ap_hook_check_user_id(auth_sdb_authen, NULL, NULL, APR_HOOK_MIDDLE);
    ap_hook_auth_checker(auth_sdb_authz, NULL, NULL, APR_HOOK_MIDDLE);
}

module AP_MODULE_DECLARE_DATA auth_sdb_module =
{
    STANDARD20_MODULE_STUFF,
    create_auth_sdb_dir_config,     /* dir config creater */
    NULL,      /* dir merger --- default is to override */
    NULL,  /* create per-server config structure */
    NULL,                       /* merge server config */
    auth_sdb_cmds,                  /* command apr_table_t */
    auth_sdb_register_hooks         /* register hooks */
};

#else
/* Dispatch list for API hooks */
module MODULE_VAR_EXPORT auth_sdb_module = {
    STANDARD_MODULE_STUFF, 
    NULL,                  /* module initializer                  */
    create_auth_sdb_dir_config,/* create per-dir    config structures */
    NULL,                  /* merge  per-dir    config structures */
    NULL,                  /* create per-server config structures */
    NULL,                  /* merge  per-server config structures */
    auth_sdb_cmds,         /* table of config file commands       */
    NULL,                  /* [#8] MIME-typed-dispatched handlers */
    NULL,                  /* [#1] URI to filename translation    */
    auth_sdb_authen,       /* [#4] validate user id from request  */
    auth_sdb_authz,        /* [#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                 */
    NULL,                  /* [#9] log a transaction              */
    NULL,                  /* [#2] header parser                  */
    NULL,                  /* child_init                          */
    NULL,                  /* child_exit                          */
    NULL                   /* [#0] post read-request              */
};
#endif

