/*==================================================================== * Copyright (c) 1995-1997 The Apache Group. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. All advertising materials mentioning features or use of this * software must display the following acknowledgment: * "This product includes software developed by the Apache Group * for use in the Apache HTTP server project (http://www.apache.org/)." * * 4. The names "Apache Server" and "Apache Group" must not be used to * endorse or promote products derived from this software without * prior written permission. * * 5. Redistributions of any form whatsoever must retain the following * acknowledgment: * "This product includes software developed by the Apache Group * for use in the Apache HTTP server project (http://www.apache.org/)." * * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE APACHE GROUP OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Group and was originally based * on public domain software written at the National Center for * Supercomputing Applications, University of Illinois, Urbana-Champaign. * For more information on the Apache Group and the Apache HTTP server * project, please see . */ static char const *rcsid = "$Id: mod_auth_sqlACL.c,v 1.1 2001/10/23 02:03:18 eric Exp $"; #define IFACE_META_PACK 1 /* * mod_auth_sqlACL: W3C's GDBM ACL module for W3C's main sites * * Eric Prud'hommeaux * * April/97: Adapted the Jose Kahan's mod_auth_cern.c Apache module to read * W3C's ACL syntax. Merged code from mod_auth_mysql.c and abstracted database * calls to mysql. */ #include "apr_strings.h" #include "apr_lib.h" #include "ap_config.h" #include "httpd.h" #include "http_request.h" #include "http_config.h" #include "http_core.h" #include "http_log.h" #include "http_protocol.h" #if IFACE_META_PACK #include "meta_package.h" #endif IFACE_META_PACK #if defined(HAVE_CRYPT_H) #include #else char *crypt (const char *__key, const char *__salt); #endif /* ** database includes */ #include #define ACCESS_CHACL 0x001 #define ACCESS_RACL 0x002 #define ACCESS_HEAD 0x010 #define ACCESS_GET 0x020 #define ACCESS_PUT 0x040 #define ACCESS_POST 0x080 #define ACCESS_DELETE 0x100 #define ACCESS_CONNECT 0x200 #define ACCESS_OPTIONS 0x400 #define ACCESS_TRACE 0x800 #define ACCESS_FULL 0xFF3 /* ENABLE_DEBUG_LOGGING - compile in a small amount of extra code to handle * the logALot directive and, surprisingly, log a lot when it's set. * * 1 - add the code * 0 - don't add the code */ #define ENABLE_DEBUG_LOGGING 1 #if ENABLE_DEBUG_LOGGING #define LOGRETURN(WHAT, REQ, WHERE, WHY) return _logReturn(WHAT, REQ, WHERE, WHY) #else #define LOGRETURN(WHAT, REQ, WHERE, WHY) return (WHAT) #endif /* select count (*) ... where ... and access & aclBits[r->method_number] */ static char * aclBits[] = { /* M_HEAD is M_GET + r->header_only */ "0x020", /* M_GET */ "0x040", /* M_PUT */ "0x080", /* M_POST */ "0x100", /* M_DELETE */ "0x200", /* M_CONNECT */ "0x400", /* M_OPTIONS */ "0x800" /* M_TRACE */ }; typedef struct auth_directory_struct { /* msyql */ char *mysql_db_user; char *mysql_db_pwd; char *mysql_db_name; /* override DB lookup URI */ char *overrideServerHost; int overrideServerPort; /* password field data formats */ int useEncryptedPasswords; int useScrambledPasswords; /* auth */ int auth_authoritative; int allow_empty_passwords; } sqlACL_directory_rec; typedef struct auth_server_struct { /* connection info */ char *dbHost; char *dbUser; char *dbPassword; char *dbTable; /* override DB lookup URI */ char *overrideServerHost; int overrideServerPort; /* check the requires for a directory, sqlACL means use sqlACL */ char *checkRequires; #if ENABLE_DEBUG_LOGGING int logALot; #endif apr_array_header_t *directoryIndex; } sqlACL_server_rec; static void sqlACL_init(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s); static void * sqlACL_dir_config (apr_pool_t *p, char *d); static void * sqlACL_dir_merge(apr_pool_t *p, void *basev, void *overridesv); static void * sqlACL_server_config (apr_pool_t *p, server_rec *s); static void * sqlACL_server_merge(apr_pool_t *p, void *basev, void *overridesv); static const command_rec sqlACL_cmds[]; static int sqlACL_authenticate_basic_user (request_rec *r); static int sqlACL_check_user_access (request_rec *r); #if IFACE_META_PACK static metaPack_ACLquery_t _metaPack_ap_querySqlACLData; #endif IFACE_META_PACK static apr_pool_t *pconf; static void sqlACL_register_hooks(apr_pool_t *p) { #if IFACE_META_PACK static const char * const listOfOne[]={ "mod_meta_package.c", NULL }; ap_hook_post_config(sqlACL_init, listOfOne, NULL, APR_HOOK_MIDDLE); #else IFACE_META_PACK ap_hook_post_config(sqlACL_init, NULL, NULL, APR_HOOK_MIDDLE); #endif !IFACE_META_PACK ap_hook_check_user_id(sqlACL_authenticate_basic_user,NULL,NULL,APR_HOOK_MIDDLE); ap_hook_auth_checker(sqlACL_check_user_access,NULL,NULL,APR_HOOK_MIDDLE); } module AP_MODULE_DECLARE_DATA auth_sqlACL_module = { STANDARD20_MODULE_STUFF, sqlACL_dir_config, /* dir config creater */ sqlACL_dir_merge, /* dir merger --- default is to override */ sqlACL_server_config, /* server config */ sqlACL_server_merge, /* merge server config */ sqlACL_cmds, /* command apr_table_t */ sqlACL_register_hooks /* register hooks */ }; /*------------------- mysql ----------------------*/ #define MYSQL_MAX_SCRAMBLED_PASSWORD_LENGTH 32 #define MYSQL_ERROR(mysql) ((mysql)?(mysql_error(mysql)):"mysql server has gone away") static MYSQL auth_sql_server, *mysql_auth = NULL; #if ENABLE_DEBUG_LOGGING static int _logReturn (int what, request_rec *r, const char * const where, const char * const why) { sqlACL_server_rec *srvRec = (sqlACL_server_rec *)ap_get_module_config(r->server->module_config, &auth_sqlACL_module); if (srvRec->logALot > 0) { ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, 999, r->server, "%s returning %d (%s) on %s %s", where, what, why, r->connection->remote_ip, r->the_request); } return what; } #endif static void sqlACL_init(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) { pconf = p; #if IFACE_META_PACK metaPack_register_query(p, "ACLs", &_metaPack_ap_querySqlACLData); #endif IFACE_META_PACK } static const char *cmd_dbTable(cmd_parms *cmd, void *what, char *arg) { sqlACL_server_rec *srvRec = (sqlACL_server_rec *)ap_get_module_config(cmd->server->module_config, &auth_sqlACL_module); srvRec->dbTable = arg; return NULL;} static const char *cmd_server_set_string_slot(cmd_parms *cmd, void *what, const char *arg) { sqlACL_server_rec *srvRec = (sqlACL_server_rec *)ap_get_module_config(cmd->server->module_config, &auth_sqlACL_module); return ap_set_string_slot(cmd, (char*)srvRec, arg); } static const char *cmd_server_set_flag_slot(cmd_parms *cmd, void *what, int arg) { sqlACL_server_rec *srvRec = (sqlACL_server_rec *)ap_get_module_config(cmd->server->module_config, &auth_sqlACL_module); return ap_set_flag_slot(cmd, (char*)srvRec, arg); } static const char *cmd_overrideServerPort(cmd_parms *cmd, void *dummy, const char *arg) { int port; sqlACL_server_rec *srvRec = (sqlACL_server_rec *)ap_get_module_config(cmd->server->module_config, &auth_sqlACL_module); port = atoi(arg); if (port <= 0 || port >= 65536) { /* 65536 == 1<<16 */ return apr_pstrcat(cmd->temp_pool, "The port number \"", arg, "\" is outside the appropriate range " "(i.e., 1..65535).", NULL); } srvRec->overrideServerPort = port; return NULL; } static const char *cmd_add_index(cmd_parms *cmd, void *dummy, const char *arg) { sqlACL_server_rec *srvRec = (sqlACL_server_rec *)ap_get_module_config(cmd->server->module_config, &auth_sqlACL_module); if (!srvRec->directoryIndex) { srvRec->directoryIndex = apr_array_make(cmd->pool, 2, sizeof(char *)); } *(char **)apr_array_push(srvRec->directoryIndex) = apr_pstrdup(cmd->pool, arg); return NULL; } static const char *my_set_passwd_flag(cmd_parms * cmd, void *v, int arg) { sqlACL_directory_rec * dirRec = (sqlACL_directory_rec *)v; dirRec->allow_empty_passwords = arg; return NULL; } static const char *my_set_crypted_password_flag(cmd_parms * cmd, void *v, int arg) { sqlACL_directory_rec * dirRec = (sqlACL_directory_rec *)v; dirRec->useEncryptedPasswords = arg; dirRec->useScrambledPasswords = 0; return NULL; } static const char *my_set_scrambled_password_flag(cmd_parms * cmd, void *v, int arg) { sqlACL_directory_rec * dirRec = (sqlACL_directory_rec *)v; dirRec->useScrambledPasswords = arg; dirRec->useEncryptedPasswords = 0; return NULL; } static const char *cmd_dbConnection(cmd_parms * cmd, void *dummy, const char *host, const char *user, const char *pwd) { sqlACL_server_rec *srvRec = (sqlACL_server_rec *)ap_get_module_config(cmd->server->module_config, &auth_sqlACL_module); if (*host != '.') { srvRec->dbHost = apr_pstrdup(cmd->pool, host); } if (*user != '.') { srvRec->dbUser = apr_pstrdup(cmd->pool, user); } if (*pwd != '.') { srvRec->dbPassword = apr_pstrdup(cmd->pool, pwd); } return NULL; } static char *mysql_escape(char *str, apr_pool_t * p) { int need_to_escape = 0; register char *source; if (!str) { return NULL; } source = str; /* first find out if we need to escape */ while (*source) { if (*source == '\'' || *source == '\\' || *source == '\"') { need_to_escape = 1; break; } source++; } if (need_to_escape) { int length = strlen(str); char *tmp_str; register char *target; source = str; /* worst case situation, which wouldn't be a pretty sight :) */ tmp_str = target = (char *) apr_palloc(p, length * 2 + 1); if (!target) { return str; } while (*source) { switch (*source) { case '\'': case '\"': case '\\': *target++ = '\\'; /* break missing intentionally */ default: *target++ = *source; break; } } *target = 0; return tmp_str; } else { return str; } } static apr_status_t auth_mysql_cleanup(void *mysql) { mysql_close((MYSQL *) mysql); return APR_SUCCESS; } static void note_cleanups_for_mysql_auth(apr_pool_t * p, MYSQL * mysql) { apr_pool_cleanup_register(p, (void *) mysql, auth_mysql_cleanup, apr_pool_cleanup_null); } static apr_status_t auth_mysql_result_cleanup(void *result) { mysql_free_result((MYSQL_RES *) result); return APR_SUCCESS; } static void note_cleanups_for_mysql_auth_result(apr_pool_t * p, MYSQL_RES * result) { apr_pool_cleanup_register(p, (void *) result, auth_mysql_result_cleanup, auth_mysql_result_cleanup); } static void open_auth_dblink(apr_pool_t * p, sqlACL_directory_rec * dirRec, sqlACL_server_rec *srvRec,request_rec *r) { char *name = srvRec->dbTable, *user = srvRec->dbUser, *pwd = srvRec->dbPassword; if (mysql_auth != NULL) { /* link already opened */ return; } if (!user) { user = dirRec->mysql_db_user; } if (!pwd) { pwd = dirRec->mysql_db_pwd; } if (!name) { name = dirRec->mysql_db_name; } if (name != NULL) { /* open an SQL link */ /* link to the MySQL database and register its cleanup!@$ */ mysql_auth = mysql_connect(&auth_sql_server, srvRec->dbHost, user, pwd); if (mysql_auth) { /* link opened */ /* ap_block_alarms(); !!! */ note_cleanups_for_mysql_auth(p, mysql_auth); /* ap_unblock_alarms(); !!! */ ap_log_error(APLOG_MARK,APLOG_NOERRNO|APLOG_INFO,999,r->server, "sqlACL: SQL connection established"); } } } static int safe_auth_mysql_query(request_rec * r, char *query, sqlACL_directory_rec * dirRec) { int error = 1; int was_connected = 0; sqlACL_server_rec *srvRec = (sqlACL_server_rec *)ap_get_module_config(r->server->module_config, &auth_sqlACL_module); #ifdef SIGPIPE void (*handler) (int); handler = signal(SIGPIPE, SIG_IGN); #endif if (mysql_auth) { mysql_select_db(mysql_auth, (dirRec->mysql_db_name ? dirRec->mysql_db_name : srvRec->dbTable)); } if (!mysql_auth || ((error = mysql_query(mysql_auth, query)) && !strcasecmp(mysql_error(mysql_auth), "mysql server has gone away"))) { /* we need to restart the server link */ if (mysql_auth) { ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 999, r->server, "sqlACL: SQL connection lost, attempting reconnect"); was_connected = 1; } mysql_auth = NULL; open_auth_dblink(pconf, dirRec, srvRec,r); if (mysql_auth == NULL) { /* unable to link */ #ifdef SIGPIPE signal(SIGPIPE, handler); #endif ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 999, r->server, "sqlACL: SQL connect failed."); return error; } if (was_connected) { ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, was_connected, r->server, "sqlACL: SQL reconnect successful."); } error = mysql_select_db(mysql_auth, (dirRec->mysql_db_name ? dirRec->mysql_db_name : srvRec->dbTable)) || mysql_query(mysql_auth, query); } #ifdef SIGPIPE signal(SIGPIPE, handler); #endif if (error) { ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, error, r->server, "sqlACL: SQL query failed: \"%s\"", query); ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, error, r->server, "sqlACL: SQL failure reason: \"%s\"", MYSQL_ERROR(mysql_auth)); } return error; } static MYSQL_RES *safe_mysql_store_result(apr_pool_t * p) { MYSQL_RES *result = mysql_store_result(mysql_auth); if (result) { /* ap_block_alarms(); !!! */ note_cleanups_for_mysql_auth_result(p, result); /* ap_unblock_alarms(); !!! */ } return result; } static char *checkUserPassword(request_rec *r, char *user, sqlACL_directory_rec *dirRec) { /* char *auth_apr_table_t = "users", *auth_mysql_user_field = "user", *auth_mysql_password_field = "passwd"; */ char *query; char *esc_user = mysql_escape(user, r->pool); MYSQL_RES *result; MYSQL_ROW sql_row; query = (char *) apr_pstrcat(r->pool, "select passwd from ids,userDetails where ids.type='U' and ids.value=\'", esc_user, "\' and ids.id=userDetails.id", NULL); if (!query) { return NULL; /* HTTP_INTERNAL_SERVER_ERROR */ } if (safe_auth_mysql_query(r, query, dirRec)) { return NULL; /* HTTP_INTERNAL_SERVER_ERROR */ } result = safe_mysql_store_result(r->pool); if (!result) { return NULL; /* HTTP_INTERNAL_SERVER_ERROR */ } switch (mysql_num_rows(result)) { case 0: return NULL; break; case 1: #ifndef ONLY_ONCE default: #endif sql_row = mysql_fetch_row(result); if (sql_row && sql_row[0]) { /* can't be too careful :) */ return sql_row[0]; } break; #ifdef ONLY_ONCE default: return NULL; /* more than one entry for user */ #endif } return NULL; /* HTTP_INTERNAL_SERVER_ERROR */ } /* ** check user,passwd,ip against ACL database */ static int sqlACL_getIntResult (request_rec *r, sqlACL_directory_rec *dirRec, char* queryStr) { MYSQL_RES *result; MYSQL_ROW row; if (!queryStr || safe_auth_mysql_query(r, queryStr, dirRec) || (result = safe_mysql_store_result(r->pool))==NULL || mysql_num_rows(result) != 1) { return -1; } row = mysql_fetch_row(result); return atoi(row[0]); } static char * fullUri (request_rec *r, char * path) { sqlACL_server_rec *srvRec = (sqlACL_server_rec *)ap_get_module_config(r->server->module_config, &auth_sqlACL_module); sqlACL_directory_rec *dirRec = (sqlACL_directory_rec *)ap_get_module_config (r->per_dir_config, &auth_sqlACL_module); char * uri; /* use host/port from [virtual]host configs */ const char * host = srvRec->overrideServerHost ? srvRec->overrideServerHost : ap_get_server_name(r); int port = srvRec->overrideServerPort ? srvRec->overrideServerPort : ap_get_server_port(r); /* but override them with directory configs */ if (dirRec->overrideServerHost != NULL) host = dirRec->overrideServerHost; if (dirRec->overrideServerPort != 0) port = dirRec->overrideServerPort; uri = apr_pstrcat(r->pool, "http://", host, NULL); if (port != 80) { char portStr[10]; sprintf(portStr, "%d", port); uri = apr_pstrcat(r->pool, uri, ":", portStr, NULL); } return apr_pstrcat(r->pool, uri, path, NULL); } /*-------------------- auth ----------------------*/ static void * sqlACL_dir_config (apr_pool_t *p, char *d) { sqlACL_directory_rec *dirRec = (sqlACL_directory_rec *) apr_pcalloc (p, sizeof(sqlACL_directory_rec)); /* mysql */ dirRec->mysql_db_name = dirRec->mysql_db_user = dirRec->mysql_db_pwd = NULL; dirRec->useEncryptedPasswords = 1; dirRec->useScrambledPasswords = 0; /* auth */ dirRec->auth_authoritative = -1; /* keep the fortress secure by default */ dirRec->allow_empty_passwords = 1; return dirRec; } static void *sqlACL_dir_merge(apr_pool_t *p, void *basev, void *overridesv) { sqlACL_directory_rec *new, *base, *overrides; new = (sqlACL_directory_rec *) apr_pcalloc (p, sizeof(sqlACL_directory_rec)); new = (sqlACL_directory_rec *)apr_pcalloc(p, sizeof(sqlACL_directory_rec)); base = (sqlACL_directory_rec *)basev; overrides = (sqlACL_directory_rec *)overridesv; /* msyql */ new->mysql_db_user = overrides->mysql_db_user == 0 ? base->mysql_db_user : overrides->mysql_db_user; new->mysql_db_pwd = overrides->mysql_db_pwd == 0 ? base->mysql_db_pwd : overrides->mysql_db_pwd; new->mysql_db_name = overrides->mysql_db_name == 0 ? base->mysql_db_name : overrides->mysql_db_name; /* override DB lookup URI */ new->overrideServerHost = overrides->overrideServerHost == 0 ? base->overrideServerHost : overrides->overrideServerHost; new->overrideServerPort = overrides->overrideServerPort == 0 ? base->overrideServerPort : overrides->overrideServerPort; new->useEncryptedPasswords = overrides->useEncryptedPasswords == 0 ? base->useEncryptedPasswords : overrides->useEncryptedPasswords; new->useScrambledPasswords = overrides->useScrambledPasswords == 0 ? base->useScrambledPasswords : overrides->useScrambledPasswords; /* auth */ new->auth_authoritative = overrides->auth_authoritative == -1 ? base->auth_authoritative : overrides->auth_authoritative; new->allow_empty_passwords = overrides->allow_empty_passwords == -1 ? base->allow_empty_passwords : overrides->allow_empty_passwords; return new; } static void * sqlACL_server_config (apr_pool_t *p, server_rec *s) { sqlACL_server_rec *srvRec = (sqlACL_server_rec *)apr_pcalloc(p, sizeof(sqlACL_server_rec)); /* connection info */ srvRec->dbHost = NULL; srvRec->dbUser = NULL; srvRec->dbPassword = NULL; srvRec->dbTable = NULL; /* override DB lookup URI */ srvRec->overrideServerHost = NULL; srvRec->overrideServerPort = 0; /* check the requires for a directory, sqlACL means use sqlACL */ srvRec->checkRequires = NULL; #if ENABLE_DEBUG_LOGGING srvRec->logALot = -1; #endif srvRec->directoryIndex = 0; return srvRec; } static void *sqlACL_server_merge(apr_pool_t *p, void *basev, void *overridesv) { sqlACL_server_rec *new, *base, *overrides; new = (sqlACL_server_rec *)apr_pcalloc(p, sizeof(sqlACL_server_rec)); base = (sqlACL_server_rec *)basev; overrides = (sqlACL_server_rec *)overridesv; /* connection info */ new->dbHost = overrides->dbHost == NULL ? base->dbHost : overrides->dbHost; new->dbUser = overrides->dbUser == NULL ? base->dbUser : overrides->dbUser; new->dbPassword = overrides->dbPassword == NULL ? base->dbPassword : overrides->dbPassword; new->dbTable = overrides->dbTable == NULL ? base->dbTable : overrides->dbTable; /* override DB lookup URI */ new->overrideServerHost = overrides->overrideServerHost == NULL ? base->overrideServerHost : overrides->overrideServerHost; new->overrideServerPort = overrides->overrideServerPort == 0 ? base->overrideServerPort : overrides->overrideServerPort; /* check the requires for a directory, sqlACL means use sqlACL */ new->checkRequires = overrides->checkRequires == NULL ? base->checkRequires : overrides->checkRequires; #if ENABLE_DEBUG_LOGGING new->logALot = overrides->logALot == -1 ? base->logALot : overrides->logALot; #endif new->directoryIndex = overrides->directoryIndex == 0 ? base->directoryIndex : overrides->directoryIndex; return new; } /* ** Command handler definitions. ** We only allow these options to be declared on the resource configuration ** files */ static const command_rec sqlACL_cmds[] = { /* msyql */ AP_INIT_TAKE3("sqlACL_dbConnection", cmd_dbConnection, NULL, RSRC_CONF, "host, user and password of the MySQL database"), AP_INIT_TAKE1("sqlACL_dbTable", cmd_server_set_string_slot, (void *) XtOffsetOf(sqlACL_server_rec, dbTable), RSRC_CONF, "default database for MySQL authentication"), AP_INIT_TAKE1("sqlACL_overrideServerHost", cmd_server_set_string_slot, (void *) XtOffsetOf(sqlACL_server_rec, overrideServerHost), OR_FILEINFO, "server name"), AP_INIT_TAKE1("sqlACL_overrideServerPort", cmd_overrideServerPort, NULL, RSRC_CONF, "server port"), AP_INIT_TAKE1("sqlACL_checkRequires", cmd_server_set_string_slot, (void *) XtOffsetOf(sqlACL_server_rec, checkRequires), RSRC_CONF, "check for sqlACL in requires"), #if ENABLE_DEBUG_LOGGING AP_INIT_FLAG("sqlACL_logALot", cmd_server_set_flag_slot, (void *) XtOffsetOf(sqlACL_server_rec, logALot), RSRC_CONF, "check for sqlACL in requires"), #endif AP_INIT_TAKE1("sqlACL_username", ap_set_string_slot, (void *) XtOffsetOf(sqlACL_directory_rec, mysql_db_user), OR_AUTHCFG, "database user"), AP_INIT_TAKE1("certAcl_password", ap_set_string_slot, (void *) XtOffsetOf(sqlACL_directory_rec, mysql_db_pwd), OR_AUTHCFG, "database password"), AP_INIT_TAKE1("sqlACL_datatabase", ap_set_string_slot, (void *) XtOffsetOf(sqlACL_directory_rec, mysql_db_name), OR_AUTHCFG, "database name"), AP_INIT_TAKE1("sqlACL_dir_overrideServerHost", ap_set_string_slot, (void *) XtOffsetOf(sqlACL_directory_rec, overrideServerHost), OR_AUTHCFG, "directory dependent server host"), AP_INIT_ITERATE("sqlACL_directoryIndex", cmd_add_index, (void *) XtOffsetOf(sqlACL_server_rec, directoryIndex), RSRC_CONF, "tell sqlACL which files serve as directory indexes"), AP_INIT_FLAG("sqlACL_encryptedPasswords", my_set_crypted_password_flag, NULL, OR_AUTHCFG, "When 'on' the password in the password apr_table_t are taken to be crypt()ed using your machines crypt() function."), AP_INIT_FLAG("sqlACL_scrambledPasswords", my_set_scrambled_password_flag, NULL, OR_AUTHCFG, "When 'on' the password in the password apr_table_t are taken to be scramble()d using mySQL's password() function."), /* auth */ AP_INIT_FLAG( "AuthACLAuthoritative", ap_set_flag_slot, (void*)XtOffsetOf(sqlACL_directory_rec,auth_authoritative), OR_AUTHCFG, "Set to 'no' to allow access control to be passed along to lower modules if the UserID is not known to this module" ), AP_INIT_FLAG("sqlACL__nopasswd", my_set_passwd_flag, NULL, OR_AUTHCFG, "Enable (on) or disable (off) empty password strings; in which case any user password is accepted."), { NULL } }; module AP_MODULE_DECLARE_DATA auth_sqlACL_module; static int requiresCertACL (char *reqFlag, request_rec *r) { int m = r->method_number; register int x; const apr_array_header_t *reqs_arr = ap_requires(r); require_line *reqs; int ret = 0; if (!reqs_arr) return 0; reqs = (require_line *) reqs_arr->elts; for (x = 0; x < reqs_arr->nelts; x++) { const char *t, *w; if (!(reqs[x].method_mask & (1 << m))) continue; t = reqs[x].requirement; w = ap_getword(r->pool, &t, ' '); if (!strcmp(w, reqFlag)) { ret = 1; } else if (*w == '!' && !strcmp(w+1, reqFlag)) { ret = 0; } else if (!strcmp(w, "sqlACL_dbConnection")) { char * host = t[0] ? ap_getword_conf(r->pool, &t) : "."; char * user = t[0] ? ap_getword_conf(r->pool, &t) : "."; char * password = t[0] ? ap_getword_conf(r->pool, &t) : "."; cmd_dbConnection(NULL, NULL, host, user, password); } else if (!strcmp(w, "sqlACL_dbTable")) { char * dbTable = t[0] ? ap_getword_conf(r->pool, &t) : NULL; cmd_dbTable(NULL, NULL, dbTable); } } return ret; } /* get the ACLs for the URI */ static char * getCheckUris (request_rec * r, char ** pUri, sqlACL_server_rec *srvRec) { char * checkUris; if (r->finfo.filetype == APR_DIR && (*pUri)[strlen(*pUri)-1] != '/') *pUri = apr_pstrcat(r->pool, *pUri, "/", NULL); if ((*pUri)[strlen(*pUri)-1] == '/' && srvRec->directoryIndex != NULL) { /* look for all the index URIs */ int i; char * escUri = mysql_escape(fullUri(r, *pUri), r->pool); checkUris = apr_pstrcat(r->pool, "uris.uri in (\'", escUri, "\'", NULL); for (i = 0; i < srvRec->directoryIndex->nelts; i++) { char * name = ((char **)(srvRec->directoryIndex->elts))[i]; char * indexFile = apr_pstrcat(r->pool, r->filename, "/", name, NULL); apr_finfo_t finfo; int rv; rv = apr_stat(&finfo, indexFile, APR_FINFO_MIN, r->pool); if (rv == APR_SUCCESS || rv == APR_INCOMPLETE) { char * indexUri = apr_pstrcat(r->pool, r->uri, name, NULL); escUri = mysql_escape(fullUri(r, indexUri), r->pool); checkUris = apr_pstrcat(r->pool, checkUris, ",\'", escUri, "\'", NULL); } } checkUris = apr_pstrcat(r->pool, checkUris, ")", NULL); } else { /* look for a single URI */ char * escUri = mysql_escape(fullUri(r, *pUri), r->pool); checkUris = apr_pstrcat(r->pool, "uris.uri=\'", escUri, "\'", NULL); } return checkUris; } /* get the uri of the base resource */ static char * _sqlAcl_baseUri (request_rec * r) { char * uri = apr_pstrdup(r->pool, r->uri); if (r->path_info) { uri[strlen(r->uri)-strlen(r->path_info)] = 0; } uri = ap_os_escape_path(r->pool, uri, 1); return uri; } static int sqlAcl_checkAccess (request_rec * r, sqlACL_directory_rec *dirRec, sqlACL_server_rec *srvRec, char * uri, const char * const requiredAccess) { char ipList[60] = ""; /* '123.456.789.012','123.456.789.*','123.456.*.*','123.*.*.*' */ #if ENABLE_DEBUG_LOGGING const char * const funcName = "sqlACL_check_user_access"; #endif /* prepare supernets of the user's IP-address */ { static const char * stars = ".*.*.*"; char * const userIp = r->connection->remote_ip; int offsets[3]; int i, j, start; /* get supernets */ for (start = i = 0; i < 3; i++) { for (j = 1; userIp[start+j] != '.'; j++) if (j > 2) return HTTP_INTERNAL_SERVER_ERROR; /* ip segment too long */ start += j; offsets[i] = start++; } /* check remaining length */ for (j = 0; userIp[start+j] != '\0'; j++) if (j > 2) return HTTP_INTERNAL_SERVER_ERROR; /* ip segment too long */ /* put it all together */ strcat(ipList, "'"); strcat(ipList, userIp); strcat(ipList, "'"); for (i = 2; i >= 0; i--) { strcat(ipList, ",'"); strncat(ipList, userIp, offsets[i]); strncat(ipList, stars, (3-i)*2); strcat(ipList, "'"); } } /* Execute select to find number of rules that allow this user to see */ /* the requested resrouce. */ { char * optionalUser = ""; char * checkUris = getCheckUris(r, &uri, srvRec); char * const user = r->user; int rules; /* check user access if user defined */ if (user != NULL) { char *escUser = mysql_escape(user, r->pool); optionalUser = apr_pstrcat(r->pool, "(ids.type=\'U\' and ids.value=\'", escUser, "\') or ", NULL); } /* and count the matching rules */ rules = sqlACL_getIntResult(r, dirRec, apr_pstrcat(r->pool, "select count(*) from" /* count matching rules */ " uris,acls,idInclusions,ids" /* tables to cross */ " where ", checkUris, /* uri(s) to check */ " and acls.acl=uris.acl" /* and its corresponding acls */ " and acls.access&", requiredAccess, /* at the sought level of access */ " and (" /* group checks for */ "(ids.type=\'A\' and ids.value=\'all\') or ", /* keywords, */ optionalUser, /* user and */ "(ids.type=\'I\' and ids.value in (", ipList, "))" /* ip */ ")" /* close group */ " and ids.id=idInclusions.id" /* above matched id is a member of a group */ " and idInclusions.groupId=acls.id", /* included in the matched acls */ NULL)); if (rules < 0) { return HTTP_INTERNAL_SERVER_ERROR; } if (rules > 0) { LOGRETURN(OK, r, funcName, "access reasons > 0"); } if (!(dirRec->auth_authoritative)) { LOGRETURN(DECLINED, r, funcName, "no access but not authoritative"); } /* see whether the requestor has a chance of seeing the document */ /* there's no sense encouraging them otherwise */ rules = sqlACL_getIntResult(r, dirRec, apr_pstrcat(r->pool, "select count(*) from" /* count matching rules */ " uris,acls" /* tables to cross */ " where ", checkUris, /* uri(s) to check */ " and acls.acl=uris.acl" /* and its corresponding acls */ " and acls.access&", requiredAccess, /* at the sought level of access */ NULL)); if (rules < 0 ) { return HTTP_INTERNAL_SERVER_ERROR; } ap_note_basic_auth_failure (r); if (rules == 0) { LOGRETURN(HTTP_FORBIDDEN, r, funcName, "no access rules"); } else { LOGRETURN(HTTP_UNAUTHORIZED, r, funcName, "no access rules for user or ip"); } } } /* API functions for the module. * These functions return 0 if client is OK or the proper error status * code. * HTTP_UNAUTHORIZED if there's an authorization or authentication failure, * and if the Authoritative flag is off. * If they return DECLINED, and all other modules also decline, that's * treated by the server core as a configuration error, logged and * reported as such. */ /* ** Determine user ID, and check if it really is that user, for HTTP ** basic authentication (using passwords) ... ** If the user did not send a password, then we'll later use his IP ** address as his uid */ static int sqlACL_authenticate_basic_user (request_rec *r) { sqlACL_directory_rec *dirRec = (sqlACL_directory_rec *)ap_get_module_config (r->per_dir_config, &auth_sqlACL_module); sqlACL_server_rec *srvRec = (sqlACL_server_rec *)ap_get_module_config(r->server->module_config, &auth_sqlACL_module); const char *sent_pw, *real_pw; #if ENABLE_DEBUG_LOGGING const char * const funcName = "sqlACL_authenticate_basic_user"; #endif int res; /* ** if the user did not send a password, then go ahead with the access ** control phase (we'll use the user's IP address as the user id) */ if (srvRec->checkRequires != NULL && !requiresCertACL(srvRec->checkRequires, r)) LOGRETURN(OK, r, funcName, "not required"); if ((res = ap_get_basic_auth_pw (r, &sent_pw))) /* see if a user id is needed in next function */ LOGRETURN(OK, r, funcName, "no password sent"); if (!r->user || !sent_pw) /* protection against badly formed requests */ LOGRETURN(HTTP_BAD_REQUEST, r, funcName, "!r->user || !sent_pw"); if (!(real_pw = checkUserPassword(r, r->user, dirRec))) { if (!(dirRec->auth_authoritative)) /* pass control on to the next authorization module. */ LOGRETURN(DECLINED, r, funcName, "bad password but not authoritative"); ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 999, r->server, "ACL user %s not found", r->user); ap_note_basic_auth_failure (r); LOGRETURN(HTTP_UNAUTHORIZED, r, funcName, "bad password and required"); } /* some good ideas from mod_auth_mysql */ if (dirRec->allow_empty_passwords && !strlen(real_pw)) { LOGRETURN(DECLINED, r, funcName, "empty password"); } if (dirRec->useEncryptedPasswords) { /* anyone know where the prototype for crypt is? */ sent_pw = (char*)crypt(sent_pw, real_pw); } else if (dirRec->useScrambledPasswords) { char scrambled_password[MYSQL_MAX_SCRAMBLED_PASSWORD_LENGTH]; make_scrambled_password(scrambled_password, sent_pw); sent_pw = scrambled_password; } if(strcmp(real_pw, sent_pw)) { ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 999, r->server, "user %s: password mismatch",r->user); ap_note_basic_auth_failure (r); LOGRETURN(HTTP_UNAUTHORIZED, r, funcName, "bad password"); } LOGRETURN(OK, r, funcName, r->user); } /* ** This function verifies if the uid has been explicitly specified using either ** a require valid-user, a require group, or require user directives. If ** the user did not send a password, we consider that his IP address is his ** uid. */ static int sqlACL_check_user_access (request_rec *r) { sqlACL_directory_rec *dirRec = (sqlACL_directory_rec *)ap_get_module_config (r->per_dir_config, &auth_sqlACL_module); sqlACL_server_rec *srvRec = (sqlACL_server_rec *)ap_get_module_config(r->server->module_config, &auth_sqlACL_module); #if ENABLE_DEBUG_LOGGING const char * const funcName = "sqlACL_check_user_access"; #endif char * uri = _sqlAcl_baseUri(r); char * requiredAccess; if (srvRec->checkRequires != NULL && !requiresCertACL(srvRec->checkRequires, r)) LOGRETURN(OK, r, funcName, "not required"); /* find access required for this request */ if (r->method_number > sizeof(aclBits)/sizeof(aclBits[0])) { ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 999, r->server, "mod_auth_sqlACL can't handle method number:\"%d\".", r->method_number); return HTTP_INTERNAL_SERVER_ERROR; } requiredAccess = (r->method_number == M_GET && r->header_only) ? "0x010" : aclBits[r->method_number]; /* ** check access granted to this user/ip */ /* don't check rules on non-existent resources */ if (r->finfo.inode == 0) { LOGRETURN(OK, r, funcName, "non-existent resource"); } return sqlAcl_checkAccess(r, dirRec, srvRec, uri, requiredAccess); } /* ============== metaPack interface ============= */ #if IFACE_META_PACK static int _metaPack_ap_querySqlACLData(apr_bucket_brigade *b, apr_off_t *pLength, request_rec * r, void * context, metaPack_generator_t * generator) { sqlACL_directory_rec *dirRec = (sqlACL_directory_rec *)ap_get_module_config (r->per_dir_config, &auth_sqlACL_module); sqlACL_server_rec *srvRec = (sqlACL_server_rec *)ap_get_module_config(r->server->module_config, &auth_sqlACL_module); #if ENABLE_DEBUG_LOGGING const char * const funcName = "sqlACL_check_user_access"; #endif char * uri = _sqlAcl_baseUri(r); int ret; char * checkUris = getCheckUris(r, &uri, srvRec); char * queryStr; MYSQL_RES *result; int rowCount; int i; if (srvRec->checkRequires != NULL && !requiresCertACL(srvRec->checkRequires, r)) LOGRETURN(DECLINED, r, funcName, "not required"); if ((ret = sqlAcl_checkAccess(r, dirRec, srvRec, uri, "0x020")) != OK) return ret; queryStr = apr_pstrcat(r->pool, "select ids.type,ids.value,acls.access,uris.uri from" " uris,acls,ids" /* tables to cross */ " where ", checkUris, /* uri(s) to check */ " and acls.acl=uris.acl" /* and its corresponding acls */ " and acls.id=ids.id", /* and the ids mentioned */ NULL); if (safe_auth_mysql_query(r, queryStr, dirRec) || (result = safe_mysql_store_result(r->pool))==NULL || (rowCount = mysql_num_rows(result)) < 1) { return HTTP_INTERNAL_SERVER_ERROR; } for (i = 0; i < rowCount; i++) { MYSQL_ROW row = mysql_fetch_row(result); char * typeStr = row[0]; char * name = row[1]; int access = atoi(row[2]); char * aclUri = row[3]; aclType_t type; switch (typeStr[0]) { case 'A': type = aclType_all; break; case 'N': type = aclType_none; break; case 'U': type = aclType_user; break; case 'I': type = aclType_ip; break; case 'G': type = aclType_group; break; default: ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 999, r->server, "mod_auth_sqlACL unknown type:\"%s\".", typeStr); continue; } if ((ret = (*generator)(b, pLength, r, aclType_user, name, access, aclUri)) != OK) return ret; } return OK; } #endif IFACE_META_PACK