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