/* ==================================================================== * Copyright (c) 1995 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 * IT'S 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 . * */ /* Netscape Cookies Access control * This module allows access, and pretends to the rest of the * system that the user used BasicAuth to get in. In the very * near future we will have to use some encryption to make * second-guess spoofing harder. * * (c) Dirk-Willem van Gulik, June 1996, for * http://ewse.ceo.org/. and http://enrm.ceo.org/. * a http://www.ceo.org/ CEO Programme Project. * * In short, there is a file/msql table which contains user-name * and cookie pairs. If the cookie is found in that table, this * module will pretend that the user used normal auth methods, * set the 'user' field and allow access. * * Example: * Cookie_Access on * Cookie_MySQLhost localhost * Cookie_MySQLcookie_database EWSE * Cookie_MySQLcookie_table cookies * Cookie_MySQLcookie_namefield name * Cookie_MySQLcookie_valuefield value * Cookie_MySQLcookie_uidfield userid * Cookie_Authorative off * Cookie_MustGive off * Cookie_EncryptedCookies on * * * require user name... * or * require valid-user * * * Persons to contact/blame * Dirk.vanGulik@jrc.it - original module * mjd@pobox.com - bug fixes, conversion from mSQL to MySQL * * Revision History * 0.0 First version * 0.2 second attempt :-) * 0.3 Added file/msql verification * 0.4 Fixed many serious bugs - MJD * 0.5 Conversion from mSQL to mySQL - MJD * * URL: http://med.jrc.it/~dirkx/mod_auth_cookie_msql.c */ /* #define HAS_NO_CRYPT_H */ /* #define MJD_DEBUG 1 */ #include "httpd.h" #include "http_core.h" #include "http_config.h" #include "http_protocol.h" #include "http_log.h" #include "http_request.h" #include #if MJD_DEBUG FILE *MJDdebug; # define MJD_PUT_ERROR(x) fputs(x, MJDdebug) #else # define MJD_PUT_ERROR(x) #endif #ifndef HAS_NO_CRYPT_H /* #include */ #endif /* Arbitrary limit for the length of the * value of the cookie name and the cookie * value */ #define MAX_USER_NAME_LEN (32) #define MAX_COOKIE_VALUE_LEN (32) #define MAX_COOKIE_NAME_LEN (32) /* Compain if things are too long ?! */ #define LENGTH_WARNING /* Compain & block if there is more than one entry * in the database for the user's cookie. */ #define AMBIGIOUS_MULTIPLE_HITS /* Do you really want to keep the connection open ?? */ #undef KEEP_MYSQL_CONNECTION_OPEN /* I threw this away, although I probably shouldn't have. * the code would have had to change a little too much. * Maybe I'll put it back in the next version. MJD */ /* Length query and lenth MySQL field/value names * this is in msql_private.h, so we cannot get it * from there. */ #define MAX_FIELD_LEN (50) #define MAX_QUERY_LEN (MAX_COOKIE_NAME_LEN+3*MAX_FIELD_LEN+50) module cookie_mysql_access_module; typedef struct { int cookie_mysql_active; /* on/off flag */ char *cookie_mysql_auth_host; /* MySQL database host, or null for local */ char *cookie_mysql_auth_dbuser; /* MySQL database user, or null for anonymous ser */ char *cookie_mysql_auth_dbpassword; /* MySQL database password, or null for no password */ char *cookie_mysql_auth_database; /* MySQL database name */ char *cookie_mysql_auth_table; /* MySQL table with username/cookie fields */ char *cookie_mysql_auth_uid_field; /* field names for the username */ char *cookie_mysql_auth_name_field; /* and the actual cookie name */ char *cookie_mysql_auth_value_field; /* and the actual cookie value */ char *cookie_mysql_auth_anonymous; /* Anonymous access, resulting username */ char *cookie_mysql_auth_anonymous_cookie; /* actual cookie name */ int cookie_mysql_auth_authorative; /* do we have a final say ? */ int cookie_mysql_auth_encrypted; /* security ? */ int cookie_mysql_auth_must; /* Enforce cookie eating ? */ } cookie_mysql_auth_config_rec; void * create_cookie_mysql_access_config (pool * p, char *d) { cookie_mysql_auth_config_rec *sec = (cookie_mysql_auth_config_rec *) pcalloc (p, sizeof (cookie_mysql_auth_config_rec)); if (!sec) { fprintf (stderr, "Could not claim memory for access control block"); return NULL; /* no memory... */ }; /* Just to illustrate the defaults forcefully */ sec->cookie_mysql_active = 0; sec->cookie_mysql_auth_host = NULL; /* MySQL database host, or null for local */ sec->cookie_mysql_auth_dbuser = NULL; /* MySQL database user, or null for anonymous ser */ sec->cookie_mysql_auth_dbpassword = NULL; /* MySQL database password, or null for no password */ sec->cookie_mysql_auth_database = NULL; /* MySQL datbase name */ sec->cookie_mysql_auth_table = NULL; /* MySQL table with username/cookie fields */ sec->cookie_mysql_auth_uid_field = NULL; /* field names for the username */ sec->cookie_mysql_auth_name_field = NULL; /* and the actual cookie, name */ sec->cookie_mysql_auth_value_field = NULL; /* and the actual cooki, value */ sec->cookie_mysql_auth_anonymous = NULL; /* a generic cookie */ sec->cookie_mysql_auth_anonymous_cookie = NULL; /* a generic cookie */ sec->cookie_mysql_auth_authorative = 0; /* do we have a final say ? */ sec->cookie_mysql_auth_encrypted = 1; /* security ? */ sec->cookie_mysql_auth_must = 0; /* Forcefull cooky eating */ return sec; } char * cookie_mysql_set_string_slot (cmd_parms * cmd, char *struct_ptr, char *arg) { *(char **) (struct_ptr + ((int) cmd->info)) = pstrdup (cmd->pool, arg); return NULL; } char * cookie_mysql_set_flag_slot (cmd_parms * cmd, char *struct_ptr, int arg) { (int) *(char **) (struct_ptr + ((int) cmd->info)) = arg; return NULL; } command_rec cookie_mysql_access_cmds[] = { {"Cookie_Access", cookie_mysql_set_flag_slot, (void *) XtOffsetOf (cookie_mysql_auth_config_rec, cookie_mysql_active), OR_AUTHCFG, FLAG, "Switch cookie access on/off"}, {"Cookie_MySQLhost", cookie_mysql_set_string_slot, (void *) XtOffsetOf (cookie_mysql_auth_config_rec, cookie_mysql_auth_host), OR_AUTHCFG, TAKE1, "Host on which the MySQL database engine resides (defaults to localhost)"}, {"Cookie_MySQL_dbuser", cookie_mysql_set_string_slot, (void *) XtOffsetOf (cookie_mysql_auth_config_rec, cookie_mysql_auth_dbuser), OR_AUTHCFG, TAKE1, "Identity of user with permissions to connect to MySQL database. (defaults to attempt anonymous access)"}, {"Cookie_MySQL_dbpassword", cookie_mysql_set_string_slot, (void *) XtOffsetOf (cookie_mysql_auth_config_rec, cookie_mysql_auth_dbpassword), OR_AUTHCFG, TAKE1, "Password for user to connect to MySQL database. (defaults to attempt access without password)"}, {"Cookie_MySQLcookie_database", cookie_mysql_set_string_slot, (void *) XtOffsetOf (cookie_mysql_auth_config_rec, cookie_mysql_auth_database), OR_AUTHCFG, TAKE1, "Name of the MySQL database which contains the username/cookie table. "}, {"Cookie_MySQLcookie_table", cookie_mysql_set_string_slot, (void *) XtOffsetOf (cookie_mysql_auth_config_rec, cookie_mysql_auth_table), OR_AUTHCFG, TAKE1, "Name of the MySQL table containing the cookie/user-name combination"}, {"Cookie_AnonymousUserID", cookie_mysql_set_string_slot, (void *) XtOffsetOf (cookie_mysql_auth_config_rec, cookie_mysql_auth_anonymous), OR_AUTHCFG, TAKE1, "Anonymous UserID set upon Anonymous Cookie"}, {"Cookie_Anonymous", cookie_mysql_set_string_slot, (void *) XtOffsetOf (cookie_mysql_auth_config_rec, cookie_mysql_auth_anonymous_cookie), OR_AUTHCFG, TAKE1, "Anonymous Cookie Name; the Value is logged"}, {"Cookie_MySQLcookie_namefield", cookie_mysql_set_string_slot, (void *) XtOffsetOf (cookie_mysql_auth_config_rec, cookie_mysql_auth_name_field), OR_AUTHCFG, TAKE1, "The name of the cookie NAME field in the MySQL cookie/user-name table"}, {"Cookie_MySQLcookie_valuefield", cookie_mysql_set_string_slot, (void *) XtOffsetOf (cookie_mysql_auth_config_rec, cookie_mysql_auth_value_field), OR_AUTHCFG, TAKE1, "The name of the cookie VALUE field in the MySQL cookie/user-name table"}, {"Cookie_MySQLcookie_uidfield", cookie_mysql_set_string_slot, (void *) XtOffsetOf (cookie_mysql_auth_config_rec, cookie_mysql_auth_uid_field), OR_AUTHCFG, TAKE1, "The name of the user-name field in the MySQL cookie/user-name table(s)."}, {"Cookie_Authorative", cookie_mysql_set_flag_slot, (void *) XtOffsetOf (cookie_mysql_auth_config_rec, cookie_mysql_auth_authorative), OR_AUTHCFG, FLAG, "When 'on' the Cookie is taken to be authorative and access control is not passed."}, {"Cookie_MustGive", cookie_mysql_set_flag_slot, (void *) XtOffsetOf (cookie_mysql_auth_config_rec, cookie_mysql_auth_must), OR_AUTHCFG, FLAG, "When 'on' the client must present a cookie."}, {"Cookie_EncryptedCookies", cookie_mysql_set_flag_slot, (void *) XtOffsetOf (cookie_mysql_auth_config_rec, cookie_mysql_auth_encrypted), OR_AUTHCFG, FLAG, "When 'on' the cookie values in the table are taken to be crypt()ed using your machines crypt() function."}, {NULL} }; /* boring little routine which escapes the ' and \ in the * SQL query. See the MySQL FAQ for more information :-) on * this very popular subject in the mysql-mailing list. */ char *escape(char *out, char *in) { register int i=0,j=0; do { /* do we need to escape */ if ( (in[i] == '\'') || (in[i] == '\\')) { /* does this fit ? */ if (j >= (MAX_FIELD_LEN-1)) { return NULL; }; out[j++] = '\\'; /* insert that escaping slash for good measure */ }; /* Do things still fit ? */ if (j >= MAX_FIELD_LEN) return NULL; } while ( ( out[j++] = in[i++]) != '\0' ); return out; } int get_mysql_data ( request_rec * r, cookie_mysql_auth_config_rec * sec, char *cookie, char *uname, char *value, char *mysql_errstr ) { char query[MAX_QUERY_LEN]; char esc_cookie[MAX_FIELD_LEN]; MYSQL connection_object; MYSQL *connection = &connection_object; MYSQL_RES *results; MYSQL_ROW currow; char *host = sec->cookie_mysql_auth_host; char *username = sec->cookie_mysql_auth_dbuser; char *password = sec->cookie_mysql_auth_dbpassword; /* null the output, just in case. */ *value = NULL; *uname = NULL; /* do we have enough information to build a query */ if ( (!sec->cookie_mysql_auth_table) || (!sec->cookie_mysql_auth_name_field) || (!sec->cookie_mysql_auth_value_field) || (!sec->cookie_mysql_auth_uid_field) ) { sprintf (mysql_errstr, "CookieAuth: Missing parameters for cookie->username/value lookup: %s%s%s%s", (sec->cookie_mysql_auth_table ? "" : "'Cookie table' "), (sec->cookie_mysql_auth_name_field ? "" : "'Cookie name field' "), (sec->cookie_mysql_auth_value_field ? "" : "'Cookie value field' "), (sec->cookie_mysql_auth_uid_field ? "" : "'Username field' ") ); MJD_PUT_ERROR(mysql_errstr); return NULL; }; if (!(escape (esc_cookie, cookie))) { sprintf (mysql_errstr, "MySQL: Could not cope/escape the '%s' cookie value", cookie); MJD_PUT_ERROR(mysql_errstr); return NULL; }; sprintf (query, "select %s,%s from %s where %s='%s'", sec->cookie_mysql_auth_uid_field, sec->cookie_mysql_auth_value_field, sec->cookie_mysql_auth_table, sec->cookie_mysql_auth_name_field, esc_cookie ); /* force fast access over /dev/msql */ if (host && (!(strcasecmp (host, "localhost")))) host = ""; /* open connection to database */ if (0 == mysql_connect (connection,host,username,password)) { sprintf (mysql_errstr, "MySQL: Could not connect to MySQL DB %s (%s)", (sec->cookie_mysql_auth_host ? sec->cookie_mysql_auth_host : "assuming localhost"), mysql_error(connection)); MJD_PUT_ERROR(mysql_errstr); return NULL; } /* we always do this, as it avoids book-keeping * and is quite cheap anyway */ if (mysql_select_db (connection, sec->cookie_mysql_auth_database) != 0) { sprintf (mysql_errstr, "MySQL: Could not select MySQL Table <%s> on host <%s>(%s)", (sec->cookie_mysql_auth_database ? sec->cookie_mysql_auth_database : ""), (sec->cookie_mysql_auth_host ? sec->cookie_mysql_auth_host : ""), mysql_error(connection)); MJD_PUT_ERROR(mysql_errstr); mysql_close (connection); return NULL; } if (mysql_query (connection, query) != 0) { sprintf (mysql_errstr, "MySQL: Could not Query database '%s' on host '%s' (%s) with query [%s]", (sec->cookie_mysql_auth_database ? sec->cookie_mysql_auth_database : ""), (sec->cookie_mysql_auth_host ? sec->cookie_mysql_auth_host : ""), mysql_error(connection), (query ? query : "")); MJD_PUT_ERROR(mysql_errstr); mysql_close (connection); return NULL; } if (!(results = mysql_store_result (connection))) { sprintf (mysql_errstr, "MySQL: Could not get the results from MySQL database <%s< on <%s< (%s) with query [%s]", (sec->cookie_mysql_auth_database ? sec->cookie_mysql_auth_database : ""), (sec->cookie_mysql_auth_host ? sec->cookie_mysql_auth_host : ""), mysql_error(connection), (query ? query : "")); MJD_PUT_ERROR(mysql_errstr); mysql_close (connection); return NULL; }; #ifdef AMBIGIOUS_MULTIPLE_HITS /* Holy cow. Was this really the best way to write this? -MJD */ if (mysql_num_rows(results) > 1) { sprintf (mysql_errstr, "MySQL: Ambigious Cookie Name; There are %d matches with [%s]", mysql_num_rows (results), (query ? query : "")); MJD_PUT_ERROR(mysql_errstr); } else if ((mysql_num_rows(results) == 1) && (currow = mysql_fetch_row (results))) { #else if ((mysql_num_rows (results)) && (currow = mysql_fetch_row (results))) { #endif /* AMBIGUOUS_MULTIPLE_HITS */ #ifdef LENGTH_WARNING if (strlen (currow[0]) + 1 > MAX_USER_NAME_LEN) { sprintf (mysql_errstr, "MySQL: Username is longer than %d with [%s]", MAX_USER_NAME_LEN, (query ? query : "")); MJD_PUT_ERROR(mysql_errstr); } else if (strlen (currow[0]) + 1 > MAX_COOKIE_VALUE_LEN) { sprintf (mysql_errstr, "MySQL: Cookie Value is longer than %d with [%s]", MAX_COOKIE_VALUE_LEN, (query ? query : "")); MJD_PUT_ERROR(mysql_errstr); } else #endif /* LENGTH_WARNING */ { /* copy the first matching field value */ strcpy (uname, currow[0]); strcpy (value, currow[1]); }; }; /* ignore errors, functions are voids anyway. */ mysql_free_result (results); /* close the connection */ mysql_close (connection); return 1; } int cookie_mysql_authenticate (request_rec * r) { cookie_mysql_auth_config_rec *sec; conn_rec *c; char *cookie, *sent_pw, *ptr, *anon = NULL; char *value, real_value[MAX_USER_NAME_LEN], real_uname[MAX_COOKIE_VALUE_LEN]; char mysql_errstr[MAX_STRING_LEN]; MJD_PUT_ERROR("-- cookie_mysql_authenticate\n"); sec = (cookie_mysql_auth_config_rec *) get_module_config (r->per_dir_config, &cookie_mysql_access_module); c = r->connection; mysql_errstr[0] = '\0'; /* are we configured ? */ if (!(sec->cookie_mysql_active)) { MJD_PUT_ERROR("DECLINED - not active\n"); return DECLINED; } /* Is there a cookie we can act on ? */ if (!(ptr = table_get (r->headers_in, "Cookie"))) { if (sec->cookie_mysql_auth_must) { MJD_PUT_ERROR("AUTH_REQUIRED - no cookie, auth_must is on\n"); return AUTH_REQUIRED; } MJD_PUT_ERROR("DECLINED - no cookie, auth_must is off\n"); return DECLINED; }; #ifdef MJD_DEBUG fprintf(MJDdebug, "Cookie: %s\n", ptr); #endif /* We do NOT have to use cookie access, the client * already gave us that stuff. */ if ((OK == get_basic_auth_pw (r, &sent_pw)) && (sent_pw)) { MJD_PUT_ERROR("DECLINED - already have basic_auth password\n"); return DECLINED; }; /* make a copy which we can destroy, keep room for the \0 and ; */ if (!(cookie = palloc (r->pool, 2 + strlen (ptr)))) { log_reason ("CookieAuth: Could not claim memory for a cookie", r->uri, r); MJD_PUT_ERROR("SERVER_ERROR - couldn't malloc cookie memory\n"); return SERVER_ERROR; }; strcpy (cookie, ptr); /* Place the elephant in egypt. */ cookie[0 + strlen (ptr)] = ';'; cookie[1 + strlen (ptr)] = '\0'; /* Run Through The Cookies, (White)Space Or ; Separated ? */ for (cookie = strtok (cookie, " ;\n\r\t\f"); (cookie); cookie = strtok (NULL, " ;\n\r\t\f") ) { /* The cookie looks something like 'Blah=Bloh' where * Blah & Bloh are in the cookie_mysql_name and cookie_mysql_value * fields of the table. */ while (cookie && !(value = strchr (cookie, (int) '='))) { /* Misbaked cookie, should we log this, ignore or just give up ? */ cookie = strtok (NULL, " ;\n\r\t\f"); }; if (!(cookie)) break; *value = '\0'; value++; /* special magic for the anonymous cookie, only check the name; the * value is for the actual tracking etc. */ if ((sec->cookie_mysql_auth_anonymous_cookie) && (sec->cookie_mysql_auth_anonymous)) { if (!(strcmp (cookie, sec->cookie_mysql_auth_anonymous_cookie))) { anon = value; } } else { /* now look up, for the cookie name, which is pointed * to by 'cookie', the username and the real_value * of the cookie as stored in the db/file. */ real_value[0] = '\0'; real_uname[0] = '\0'; get_mysql_data (r, sec, cookie, real_uname, real_value,mysql_errstr); if (mysql_errstr[0]) { log_reason (mysql_errstr, r->filename, r); MJD_PUT_ERROR("SERVER_ERROR - mysql error; see logfile\n"); return SERVER_ERROR; }; if ((real_uname[0]) && (real_value[0])) { /* Now do we have to crypt the incoming cookie * (for the second time!) to avoid having * usable cookies in the database. */ if (sec->cookie_mysql_auth_encrypted) { /* anyone know where the prototype for crypt is? * PLEASE NOTE: * The crypt function (at least under FreeBSD 2.0.5) returns * a ptr to a *static* array (max 120 chars) and does *not* * modify the string pointed at by sent_pw ! */ value = (char *) crypt (value, real_value); }; if (!(strcmp (value, real_value))) { /* Jup, this looks good */ c->user = strdup(real_uname); c->auth_type ="COOKIE"; MJD_PUT_ERROR("OK!\n"); return OK; }; /* we could log illegal cookies here ?!*/ }; /* we could log unfound cookies here ?!*/ }; }; if (anon) { /* Hmm, let him/her in on an empty uname. */ c->user = sec->cookie_mysql_auth_anonymous; c->auth_type ="COOKIE"; MJD_PUT_ERROR("OK - empty username (anonumous access)\n"); return OK; }; if (sec->cookie_mysql_auth_authorative) { sprintf (mysql_errstr, "CookieAuth: No valid Cookie(s)"); MJD_PUT_ERROR(mysql_errstr); note_basic_auth_failure (r); log_reason (mysql_errstr, r->filename, r); MJD_PUT_ERROR("AUTH_REQUIRED - line 699\n"); return AUTH_REQUIRED; }; MJD_PUT_ERROR("DECLINED - line 704\n"); return DECLINED; } #ifdef MJD_DEBUG void MJD_mysqlcookie_init(void) { MJDdebug = fopen("/tmp/cookie.err", "w"); setbuf(MJDdebug, NULL ); /* Make unbufered */ fprintf(MJDdebug, "Process %d begins...\n", getpid()); } #endif module cookie_mysql_access_module = { STANDARD_MODULE_STUFF, #ifdef MJD_DEBUG MJD_mysqlcookie_init, /* initializer */ #else NULL, /* initializer */ #endif create_cookie_mysql_access_config, /* dir config creater */ NULL, /* dir merger */ NULL, /* server config */ NULL, /* merge server configs */ cookie_mysql_access_cmds, /* command table */ NULL, /* handlers */ NULL, /* filename translation */ cookie_mysql_authenticate, /* check_user_id */ NULL, /* check auth */ NULL, /* check access */ NULL, /* type_checker */ NULL, /* fixups */ NULL, /* logger */ };