/* ==================================================================== * 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_MSQLhost localhost * Cookie_MSQLdatabase EWSE * Cookie_MSQL_table cookies * Cookie_MSQLcookie_namefield name * Cookie_MSQLcookie_valuefield value * Cookie_MSQLuid_field userid * Cookie_Authorative off * Cookie_MustGive off * Cookie_EncryptedCookies on * * * require user name... * or * require valid-user * * * Person to contact/blame * Dirk.vanGulik@jrc.it * * Revision History * 0.0 First version * 0.2 second attempt :-) * 0.3 Added file/msql verification * */ #include "httpd.h" #include "http_config.h" #include "http_protocol.h" #include "http_log.h" #include #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 LENGHT_WARNING /* Compain & block if things are too many hits ?! */ #define AMBIGIOUS_MULTIPLE_HITS /* Do you really want to keep the connection open ?? */ #undef KEEP_MSQL_CONNECTION_OPEN /* Length query and lenth mSQL 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_msql_access_module; typedef struct { int cookie_msql_active; /* on/off flag */ char *cookie_msql_auth_host; /* mSQL database host, or null for local */ char *cookie_msql_auth_database; /* mSQL datbase name */ char *cookie_msql_auth_table; /* mSQL table with username/cookie fields */ char *cookie_msql_auth_uid_field; /* field names for the username */ char *cookie_msql_auth_name_field; /* and the actual cookie name */ char *cookie_msql_auth_value_field; /* and the actual cookie value */ char *cookie_msql_auth_anonymous; /* Anonymous access, resulting username */ char *cookie_msql_auth_anonymous_cookie; /* actual cookie name */ int cookie_msql_auth_authorative; /* do we have a final say ? */ int cookie_msql_auth_encrypted; /* security ? */ int cookie_msql_auth_must; /* Enforce cookie eating ? */ } cookie_msql_auth_config_rec; void * create_cookie_msql_access_config (pool * p, char *d) { cookie_msql_auth_config_rec *sec = (cookie_msql_auth_config_rec *) pcalloc (p, sizeof (cookie_msql_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_msql_active = 0; sec->cookie_msql_auth_host = NULL; /* mSQL database host, or null for local */ sec->cookie_msql_auth_database = NULL; /* mSQL datbase name */ sec->cookie_msql_auth_table = NULL; /* mSQL table with username/cookie fields */ sec->cookie_msql_auth_uid_field = NULL; /* field names for the username */ sec->cookie_msql_auth_name_field = NULL; /* and the actual cookie, name */ sec->cookie_msql_auth_value_field = NULL; /* and the actual cooki, value */ sec->cookie_msql_auth_anonymous = NULL; /* a generic cookie */ sec->cookie_msql_auth_anonymous_cookie = NULL; /* a generic cookie */ sec->cookie_msql_auth_authorative = 0; /* do we have a final say ? */ sec->cookie_msql_auth_encrypted = 1; /* security ? */ sec->cookie_msql_auth_must = 0; /* Forcefull cooky eating */ return sec; } char * cookie_msql_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_msql_set_flag_slot (cmd_parms * cmd, char *struct_ptr, int arg) { (int) *(char **) (struct_ptr + ((int) cmd->info)) = arg; return NULL; } command_rec cookie_msql_access_cmds[] = { {"Cookie_Access", cookie_msql_set_flag_slot, (void *) XtOffsetOf (cookie_msql_auth_config_rec, cookie_msql_active), OR_AUTHCFG, FLAG, "Switch cookie access on/off"}, {"Cookie_MSQLhost", cookie_msql_set_string_slot, (void *) XtOffsetOf (cookie_msql_auth_config_rec, cookie_msql_auth_host), OR_AUTHCFG, TAKE1, "Host on which the mSQL database engine resides (defaults to localhost)"}, {"Cookie_MSQLcookie_database", cookie_msql_set_string_slot, (void *) XtOffsetOf (cookie_msql_auth_config_rec, cookie_msql_auth_database), OR_AUTHCFG, TAKE1, "Name of the mSQL database which contains the username/cookie table. "}, {"Cookie_MSQLcookie_table", cookie_msql_set_string_slot, (void *) XtOffsetOf (cookie_msql_auth_config_rec, cookie_msql_auth_table), OR_AUTHCFG, TAKE1, "Name of the mSQL table containing the cookie/user-name combination"}, {"Cookie_AnonymousUserID", cookie_msql_set_string_slot, (void *) XtOffsetOf (cookie_msql_auth_config_rec, cookie_msql_auth_anonymous), OR_AUTHCFG, TAKE1, "Anonymous UserID set upon Anonymous Cookie"}, {"Cookie_Anonymous", cookie_msql_set_string_slot, (void *) XtOffsetOf (cookie_msql_auth_config_rec, cookie_msql_auth_anonymous_cookie), OR_AUTHCFG, TAKE1, "Anonymous Cookie Name; the Value is logged"}, {"Cookie_MSQLcookie_namefield", cookie_msql_set_string_slot, (void *) XtOffsetOf (cookie_msql_auth_config_rec, cookie_msql_auth_name_field), OR_AUTHCFG, TAKE1, "The name of the cookie NAME field in the mSQL cookie/user-name table"}, {"Cookie_MSQLcookie_valuefield", cookie_msql_set_string_slot, (void *) XtOffsetOf (cookie_msql_auth_config_rec, cookie_msql_auth_value_field), OR_AUTHCFG, TAKE1, "The name of the cookie VALUE field in the mSQL cookie/user-name table"}, {"Cookie_MSQLcookie_uidfield", cookie_msql_set_string_slot, (void *) XtOffsetOf (cookie_msql_auth_config_rec, cookie_msql_auth_uid_field), OR_AUTHCFG, TAKE1, "The name of the user-name field in the mSQL cookie/user-name table(s)."}, {"Cookie_Authorative", cookie_msql_set_flag_slot, (void *) XtOffsetOf (cookie_msql_auth_config_rec, cookie_msql_auth_authorative), OR_AUTHCFG, FLAG, "When 'on' the Cookie is taken to be authorative and access control is not passed."}, {"Cookie_MustGive", cookie_msql_set_flag_slot, (void *) XtOffsetOf (cookie_msql_auth_config_rec, cookie_msql_auth_must), OR_AUTHCFG, FLAG, "When 'on' the client must present a cookie."}, {"Cookie_EncryptedCookies", cookie_msql_set_flag_slot, (void *) XtOffsetOf (cookie_msql_auth_config_rec, cookie_msql_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 mSQL FAQ for more information :-) on * this very popular subject in the msql-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_msql_data ( request_rec * r, cookie_msql_auth_config_rec * sec, char *cookie, char *uname, char *value, char *msql_errstr ) { char query[MAX_QUERY_LEN]; char esc_cookie[MAX_FIELD_LEN]; static int sock = -1; m_result *results; m_row currow; char *host = sec->cookie_msql_auth_host; /* null the output, just in case. */ *value = NULL; *uname = NULL; /* do we have enough information to build a query */ if ( (!sec->cookie_msql_auth_table) || (!sec->cookie_msql_auth_name_field) || (!sec->cookie_msql_auth_value_field) || (!sec->cookie_msql_auth_uid_field) ) { sprintf (msql_errstr, "CookieAuth: Missing parameters for cookie->username/value lookup: %s%s%s%s", (sec->cookie_msql_auth_table ? "" : "'Cookie table' "), (sec->cookie_msql_auth_name_field ? "" : "'Cookie name field' "), (sec->cookie_msql_auth_value_field ? "" : "'Cookie value field' "), (sec->cookie_msql_auth_uid_field ? "" : "'Username field' ") ); return NULL; }; if (!(escape (esc_cookie, cookie))) { sprintf (msql_errstr, "mSQL: Could not cope/escape the '%s' cookie value", cookie); return NULL; }; sprintf (query, "select %s,%s from %s where %s='%s'", sec->cookie_msql_auth_uid_field, sec->cookie_msql_auth_value_field, sec->cookie_msql_auth_table, sec->cookie_msql_auth_name_field, esc_cookie ); #ifndef KEEP_MSQL_CONNECTION_OPEN sock = -1; #endif /* force fast access over /dev/msql */ if ((host) && (!(strcasecmp (host, "localhost")))) host = NULL; /* (re) open if nessecary */ if (sock == -1) if ((sock = msqlConnect (host)) == -1) { sprintf (msql_errstr, "mSQL: Could not connect to Msql DB %s (%s)", (sec->cookie_msql_auth_host ? sec->cookie_msql_auth_host : "assuming localhost"), msqlErrMsg); return NULL; }; /* we always do this, as it avoids book-keeping * and is quite cheap anyway */ if (msqlSelectDB (sock, sec->cookie_msql_auth_database) == -1) { sprintf (msql_errstr, "mSQL: Could not select Msql Table <%s> on host <%s>(%s)", (sec->cookie_msql_auth_database ? sec->cookie_msql_auth_database : ""), (sec->cookie_msql_auth_host ? sec->cookie_msql_auth_host : ""), msqlErrMsg); msqlClose (sock); sock = -1; return NULL; } if (msqlQuery (sock, query) == -1) { sprintf (msql_errstr, "mSQL: Could not Query database '%s' on host '%s' (%s) with query [%s]", (sec->cookie_msql_auth_database ? sec->cookie_msql_auth_database : ""), (sec->cookie_msql_auth_host ? sec->cookie_msql_auth_host : ""), msqlErrMsg, (query ? query : "")); msqlClose (sock); sock = -1; return NULL; } if (!(results = msqlStoreResult ())) { sprintf (msql_errstr, "mSQL: Could not get the results from mSQL database <%s< on <%s< (%s) with query [%s]", (sec->cookie_msql_auth_database ? sec->cookie_msql_auth_database : ""), (sec->cookie_msql_auth_host ? sec->cookie_msql_auth_host : ""), msqlErrMsg, (query ? query : "")); msqlClose (sock); sock = -1; return NULL; }; #ifdef AMBIGIOUS_MULTIPLE_HITS if (msqlNumRows (results) > 1) { sprintf (msql_errstr, "mSQL: Ambigious Cookie Name; There are %d matches with [%s]", msqlNumRows (results), (query ? query : "")); } else if ((msqlNumRows (results) == 1) && (currow = msqlFetchRow (results))) { #else if ((msqlNumRows (results)) && (currow = msqlFetchRow (results))) { #endif #ifdef LENGHT_WARNING if (strlen (currow[0]) + 1 > MAX_USER_NAME_LEN) { sprintf (msql_errstr, "mSQL: Username is longer than %d with [%s]", MAX_USER_NAME_LEN, (query ? query : "")); } else if (strlen (currow[0]) + 1 > MAX_COOKIE_VALUE_LEN) { sprintf (msql_errstr, "mSQL: Cookie Value is longer than %d with [%s]", MAX_COOKIE_VALUE_LEN, (query ? query : "")); } else #endif { /* copy the first matching field value */ strcpy (uname, currow[0]); strcpy (value, currow[1]); }; }; /* ignore errors, functions are voids anyway. */ msqlFreeResult (results); #ifndef KEEP_MSQL_CONNECTION_OPEN /* close the connection, unless explicitly told not to. Do note that * we do not have a decent closing option of child termination due * the lack of hooks in the API (or my understanding thereof) */ msqlClose (sock); sock = -1; #endif return 1; } int cookie_msql_authenticate (request_rec * r) { cookie_msql_auth_config_rec *sec = (cookie_msql_auth_config_rec *) get_module_config (r->per_dir_config, &cookie_msql_access_module); conn_rec *c = r->connection; char *cookie, *sent_pw, *ptr, *anon = NULL; char *value, real_value[MAX_USER_NAME_LEN], real_uname[MAX_COOKIE_VALUE_LEN]; char msql_errstr[MAX_STRING_LEN]; msql_errstr[0] = '\0'; /* are we configured ? */ if (!(sec->cookie_msql_active)) return DECLINED; /* Is there a cookie we can act on ? */ if (!(ptr = table_get (r->headers_in, "Cookie"))) { if (sec->cookie_msql_auth_must) return AUTH_REQUIRED; return DECLINED; }; /* 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)) { 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); 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_msql_name and cookie_msql_value * fields of the table. */ while ((!(value = strchr (cookie, (int) '='))) && (cookie)) { /* 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_msql_auth_anonymous_cookie) && (sec->cookie_msql_auth_anonymous)) if (!(strcmp (cookie, sec->cookie_msql_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_msql_data (r, sec, cookie, real_uname, real_value,msql_errstr); if (msql_errstr[0]) { log_reason (msql_errstr, r->filename, r); 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_msql_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 = real_uname; c->auth_type ="COOKIE"; 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_msql_auth_anonymous; c->auth_type ="COOKIE"; return OK; }; if (sec->cookie_msql_auth_authorative) { sprintf (msql_errstr, "CookieAuth: No valid Cookie(s)"); note_basic_auth_failure (r); log_reason (msql_errstr, r->filename, r); return AUTH_REQUIRED; }; return DECLINED; } module cookie_msql_access_module = { STANDARD_MODULE_STUFF, NULL, /* initializer */ create_cookie_msql_access_config, /* dir config creater */ NULL, /* dir merger */ NULL, /* server config */ NULL, /* merge server configs */ cookie_msql_access_cmds, /* command table */ NULL, /* handlers */ NULL, /* filename translation */ cookie_msql_authenticate, /* check_user_id */ NULL, /* check auth */ NULL, /* check access */ NULL, /* type_checker */ NULL, /* fixups */ NULL, /* logger */ };