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