/*====================================================================
* 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