/*
 * Copyright (c) 2001 by J. R. Westmoreland <jr@jrw.org>
 * Portions Copyright (c) 2002-2004 by Matthew Palmer <mpalmer@debian.org>
 *
 * Original module/version: mod_auth_mysql v2.20
 * Originally written and maintained by Zeev Suraski <bourbon@netvision.net.il>
 * A couple of fixes by Marschall Peter <Peter.Marschall@gedos.de>
 * and Brent Metz <bmetz@thor.tjhsst.edu>
 * MySQL/PHP style MD5 hashes, and an integration with the mod-auth-mysql
 * maintained by Bill Joned by Matthew Palmer <mpalmer@debian.org>
 *
 * This version maintained by Matthew Palmer <mpalmer@debian.org>
 *
 * Please read the INSTALL and USAGE files for further information.
 * 
 * 2004-02-01 MURAKAMI, takeshi <takeshi@softagency.co.jp>
 * add port, socket
 * 2004-02-07 MURAKAMI, takeshi <takeshi@softagency.co.jp>
 * apache2
 * 2004-09-20 Joseph Walton <joe@kafsemo.org>
 * SHA1 hash support
 */

#define AUTH_MYSQL_VERSION "4.3.9"

#include "config.h"

#ifdef APACHE2
#define PALLOC apr_palloc
#define PCALLOC apr_pcalloc
#define SNPRINTF apr_snprintf
#define PSTRDUP apr_pstrdup
#define PSTRCAT apr_pstrcat
#define APACHELOG(severity, handle, message...) ap_log_error(APLOG_MARK, APLOG_NOERRNO | severity, 0, handle->server, message)
#else
#define PALLOC ap_palloc
#define PCALLOC ap_pcalloc
#define SNPRINTF ap_snprintf
#define PSTRDUP ap_pstrdup
#define PSTRCAT ap_pstrcat
#define APACHELOG(severity, handle, message...) ap_log_error(APLOG_MARK, APLOG_NOERRNO | severity, handle->server, message)
#endif

#include <httpd.h>
#include <http_config.h>
#include <http_core.h>
#include <http_protocol.h>
#include <http_log.h>
#ifdef APACHE2
#include "http_request.h"   /* for ap_hook_(check_user_id | auth_checker)*/
#include <apr_md5.h>
#include <apr_sha1.h>
#else
#include <ap_md5.h>
#include <ap_sha1.h>
#endif

#include <mysql.h>
#include <errmsg.h>
#include <mysqld_error.h>

#ifdef HAVE_CRYPT_H
#include <crypt.h>
#endif

#ifndef TRUE
#define TRUE 1
#endif
#ifndef FALSE
#define FALSE 0
#endif

/* This are the system-wide config options; the more specific options live in
 * a mysql_auth_config_rec structure, one for each MySQL-configured directory.
 */
static char	*auth_db_host = NULL,
		*auth_db_name = NULL,
		*auth_db_user = NULL,
		*auth_db_pwd = NULL;

static int	auth_db_override = 1;

char *tmp_host = NULL;
char *auth_db_socket = NULL;
long auth_db_port = -1;
unsigned long auth_db_client_flag = 0;

/* Support for general-purpose encryption schemes.  Should be fairly straightforward.
 * We have a checking routine and a name for it (for AuthMySQL_Encryption_Types).
 */

#define PLAINTEXT_ENCRYPTION_FLAG	1<<0
#ifdef CRYPT_DES
#define CRYPT_DES_ENCRYPTION_FLAG	1<<1
#endif
#define MYSQL_ENCRYPTION_FLAG		1<<2
#ifdef CRYPT_MD5
#define CRYPT_MD5_ENCRYPTION_FLAG	1<<3
#endif
#define PHP_MD5_ENCRYPTION_FLAG		1<<4
#ifdef HAVE_CRYPT_H
#define CRYPT_ENCRYPTION_FLAG		1<<5
#endif
#define SHA1SUM_ENCRYPTION_FLAG		1<<6

static int check_no_encryption(const char *passwd, char *enc_passwd)
{
	return (!strcmp(passwd, enc_passwd));
}


#ifdef CRYPT_DES
static int check_crypt_des_encryption(const char *passwd, char *enc_passwd)
{
	/* Ensure that MD5 passwords aren't checked here */
	if (!strncmp(enc_passwd, "$1$", 3)) {
		return 0;
	}
	return (!strcmp(crypt(passwd, enc_passwd), enc_passwd));
}
#endif

#ifdef CRYPT_MD5
static int check_crypt_MD5_encryption(const char *passwd, char *enc_passwd)
{
	/* Make sure only MD5 passwords are checked */
	if (strncmp(enc_passwd, "$1$", 3)) {
		return 0;
	}
	return (!strcmp(crypt(passwd, enc_passwd), enc_passwd));
}
#endif

#ifdef HAVE_CRYPT_H
static int check_crypt_encryption(const char *passwd, char *enc_passwd)
{
	return (!strcmp(crypt(passwd, enc_passwd), enc_passwd));
}
#endif

char hex_digit(char c)
{
	if (c < 10) {
		return c+'0';
	} else {
		return c-10+'a';
	}
}

static char *md5_hex_hash(const char *pass)
{
	unsigned char hash[16];
	/* This makes this function *very* specialised.  Change this to
	 * use dynamic memory if you want to reuse it somewhere else */
	static char real_hash[33];
	int i;
#ifdef APACHE2
	apr_md5_ctx_t ct;

	apr_md5_init(&ct);
	apr_md5_update(&ct, pass, strlen(pass));
	apr_md5_final(hash, &ct);
#else
	AP_MD5_CTX ct;

	ap_MD5Init(&ct);
	ap_MD5Update(&ct, pass, strlen(pass));
	ap_MD5Final(hash, &ct);
#endif
	
	/* Now we convert the 16 octet hash to a 32 byte hex string */
	for (i = 0; i < 16; i++) {
		real_hash[2*i+1] = hash[i] & 0xF;
		real_hash[2*i] = (hash[i] & 0xF0) >> 4;
	}
	for (i = 0; i < 32; i++) {
		real_hash[i] = hex_digit(real_hash[i]);
	}
	real_hash[32] = '\0';

	return real_hash;
}

static int check_PHP_MD5_encryption(const char *passwd, char *enc_passwd)
{
	return (!strcmp(md5_hex_hash(passwd), enc_passwd));
}

static char *sha1_hex_hash(const char *passwd)
{
	int i;

#ifdef APACHE2
	apr_sha1_ctx_t ct;
	char hash[APR_SHA1_DIGESTSIZE];
	static char real_hash[APR_SHA1_DIGESTSIZE * 2 + 1];

	apr_sha1_init(&ct);
	apr_sha1_update(&ct, passwd, strlen(passwd));
	apr_sha1_final(hash, &ct);
#else
	AP_SHA1_CTX ct;
	char hash[SHA_DIGESTSIZE];
	static char real_hash[SHA_DIGESTSIZE * 2 + 1];

	ap_SHA1Init(&ct);
	ap_SHA1Update(&ct, passwd, strlen(passwd));
	ap_SHA1Final(hash, &ct);
#endif

	/* Now we convert the 20 octet hash to a 40 byte hex string */
	for (i = 0; i < sizeof(hash); i++) {
		real_hash[2*i+1] = hash[i] & 0xF;
		real_hash[2*i] = (hash[i] & 0xF0) >> 4;
	}
	for (i = 0; i < sizeof(real_hash); i++) {
		real_hash[i] = hex_digit(real_hash[i]);
	}
	real_hash[sizeof(real_hash)-1] = '\0';

	return real_hash;
}

static int check_SHA1Sum_encryption(const char *passwd, char *enc_passwd)
{
	return (!strcmp(sha1_hex_hash(passwd), enc_passwd));
}


static int check_mysql_encryption(const char *passwd, char *enc_passwd)
{
	char scrambled_passwd[32];
	
	make_scrambled_password(scrambled_passwd, passwd);
	return (!strcmp(scrambled_passwd, enc_passwd));
}

typedef struct {
	char *name;
	int (*check_function)(const char *passwd, char *enc_passwd);
	int flag;
} encryption_type_entry;

encryption_type_entry supported_encryption_types[] = {
	{ "Plaintext",		check_no_encryption,			PLAINTEXT_ENCRYPTION_FLAG },
#if CRYPT_DES
	{ "Crypt_DES",		check_crypt_des_encryption,		CRYPT_DES_ENCRYPTION_FLAG },
#endif
	{ "MySQL",		check_mysql_encryption,			MYSQL_ENCRYPTION_FLAG },
#if CRYPT_MD5
	{ "Crypt_MD5",		check_crypt_MD5_encryption,		CRYPT_MD5_ENCRYPTION_FLAG },
#endif
	{ "Crypt",		check_crypt_encryption,			CRYPT_ENCRYPTION_FLAG },
	{ "PHP_MD5",		check_PHP_MD5_encryption,		PHP_MD5_ENCRYPTION_FLAG	},
	{ "SHA1Sum",	check_SHA1Sum_encryption, SHA1SUM_ENCRYPTION_FLAG},
	/* add additional encryption types below */
	{ NULL,			NULL,					0 }
};

static int get_encryption_flag(const char *name)
{
	register encryption_type_entry *ete=supported_encryption_types;
	
	while (ete->name) {
		if (!strcmp(ete->name, name)) {
			return ete->flag;
		}
		ete++;
	}
	return 0;
}

/* end of support for general-purpose encryption schemes */

/* Per-directory configuration structure.  One of these is created for each
 * <Directory>...</Directory> and .htaccess file which requests authentication
 */
typedef struct {
	char *dir;

	char *db_host;
	char *db_socket;
	unsigned int db_port;
	char *db_user;
	char *db_pwd;
	char *db_name;
	
	MYSQL *dbh;

	/* Boolean options */
	unsigned char persistent;
	unsigned char enable_mysql_auth;

	/* Some MySQL errors are retryable; if we retry the operation
	 * by recursing into the same function, we set this so we don't
	 * recurse indefinitely if it's a permanent error.
	 */
	unsigned char dbh_error_lastchance;

	char *user_table;
	char *group_table;

	char *user_field;
	char *password_field;
	char *group_field;
	char *group_user_field;
	char *group_where_clause;
	char *password_where_clause;
		
	int encryption_types;
	unsigned char using_encryption_types;

	unsigned char allow_empty_passwords;
	unsigned char authoritative;

	/* You're not going to believe this, but, near as I can tell, apache
	 * doesn't respect the last part of the config_rec.  May be an
	 * underflow in some code somewhere, but I'm not taking no chances
	 * with *my* config variables...
	 */
	char sacrificial_lamb[15];

} mysql_auth_config_rec;

module auth_mysql_module;

#ifdef APACHE2
static apr_status_t
#else
static void
#endif
auth_mysql_cleanup(void *ptr)
{
	mysql_auth_config_rec *sec = ptr;

	if (sec->dbh) {
#ifdef DEBUG
		syslog(LOG_DEBUG, "MAMDEBUG: Closing MySQL connection");
#endif
		mysql_close(sec->dbh);
		sec->dbh = NULL;
	}
}

/* Do the magic required when the module is first loaded.
 */
#ifdef APACHE2
void mysql_auth_init_handler(server_rec *s, apr_pool_t *p)
#else
void mysql_auth_init_handler(server_rec *s, pool *p)
#endif
{
#ifdef APACHE2
#else
#if MODULE_MAGIC_NUMBER >= 19980527
    ap_add_version_component("AuthMySQL/" AUTH_MYSQL_VERSION);
#endif
#endif
}

/* Called each and every time a new per-directory configuration is
 * created.  We just initialise variables and set defaults.  This is
 * run *before* actual config takes place.
 */
#ifdef APACHE2
void *create_mysql_auth_dir_config(apr_pool_t *p, char *d)
#else
void *create_mysql_auth_dir_config(pool *p, char *d)
#endif
{
#ifdef DEBUG
	int i;
#endif

	mysql_auth_config_rec *sec = (mysql_auth_config_rec *) PCALLOC(p, sizeof(mysql_auth_config_rec));
	
#ifdef DEBUG
	syslog(LOG_DEBUG, "MAMDEBUG: Now configuring server config for %s", d);
	syslog(LOG_DEBUG, "MAMDEBUG: sizeof(mysql_auth_config_rec) = %i",
				sizeof(mysql_auth_config_rec));
#endif

	sec->db_name = sec->db_socket = sec->db_user = sec->db_pwd = NULL;

	sec->dbh = NULL;
	/* When the memory for this connection record is cleaned, we must
	 * be sure to close the DB connection, if it exists.  If this does
	 * not happen, we are in a world of pain.
	 */
#ifdef APACHE2
	apr_pool_cleanup_register(p, sec, auth_mysql_cleanup, apr_pool_cleanup_null);
#else
	ap_register_cleanup(p, sec, auth_mysql_cleanup, ap_null_cleanup);
#endif

	sec->dir = d;
	
	sec->user_table = sec->group_table = NULL;
	sec->user_field = sec->password_field = sec->group_field = NULL;
	sec->group_where_clause = sec->password_where_clause = NULL;
	sec->group_user_field = NULL;
	
	sec->authoritative = 1;
	sec->allow_empty_passwords = 1;

	sec->dbh_error_lastchance = 0;

#ifdef DEBUG
	syslog(LOG_DEBUG, "MAMDEBUG: Enabling MySQL auth by default");
#endif
	sec->enable_mysql_auth = 1;

#ifdef CRYPT_DES
	sec->encryption_types = CRYPT_DES_ENCRYPTION_FLAG;
	sec->using_encryption_types = 0;
#else
	sec->encryption_types = 0;
	sec->using_encryption_types = 0;
#endif

	sec->db_port = -1;
	
#ifdef DEBUG
	syslog(LOG_DEBUG, "MAMDEBUG: Persistent is now ON");
#endif
	sec->persistent = 1;

#ifdef DEBUG
	for (i = 0; i < 15; i++)
	{
		sec->sacrificial_lamb[i] = i % 10 + '0';
	}
#endif
	
	return sec;
}

/* Helper function to make some decisions about whether to use crypted
 * passwords in response to "AuthMySQL_Encrypted_Passwords on" in a config
 * file.
 * XXX DEPRECATED XXX
 */
static const char *set_crypted_password_flag(cmd_parms *cmd, void *sconf, int arg)
{
	mysql_auth_config_rec *sec = (mysql_auth_config_rec *) sconf;
	
	if (sec->using_encryption_types) {
		/* This setting is ignored if we're using Encryption_Types */
		return NULL;
	}
#ifdef CRYPT_DES
	if (arg) {
		sec->encryption_types |= CRYPT_DES_ENCRYPTION_FLAG;
	} else {
		sec->encryption_types &= ~CRYPT_DES_ENCRYPTION_FLAG;
		if (!sec->encryption_types) {
			sec->encryption_types = PLAINTEXT_ENCRYPTION_FLAG;
		}
	}
#endif

	return NULL;
}

/* Equivalent to set_crypted_password_flag above, except that this time we're
 * talking about MySQL-style scrambled passwords instead.
 * XXX DEPRECATED XXX
 */
static const char *set_scrambled_password_flag(cmd_parms *cmd, void *sconf, int arg)
{
	mysql_auth_config_rec *sec = (mysql_auth_config_rec *) sconf;

	if (sec->using_encryption_types) {
		/* This setting is ignored if we're using Encryption_Types */
		return NULL;
	}
	if (arg) {
		sec->encryption_types |= MYSQL_ENCRYPTION_FLAG;
	} else {
		sec->encryption_types &= ~MYSQL_ENCRYPTION_FLAG;
		if (!sec->encryption_types) {
			sec->encryption_types = PLAINTEXT_ENCRYPTION_FLAG;
		}
	}
	return NULL;
}

/* Ensure that any string passed through us won't unduly upset the MySQL
 * server when passed in as part of a query.
 */
#ifdef APACHE2
static char *mysql_escape(char *str, apr_pool_t *p)
#else
static char *mysql_escape(char *str, pool *p)
#endif
{
	char *dest;
	
	if (!str) {
		return NULL;
	}

	dest = (char *) PALLOC(p, strlen(str) * 2 + 1);
	if (!dest) {
		return str;
	}
	
	mysql_escape_string(dest, str, strlen(str));
	
	return dest;
}

/* Config helper to set the server-wide default database name.
 */
static const char *set_auth_mysql_db(cmd_parms * parms, void *dummy, const char *db)
{
	auth_db_name = (char *)db;
	return NULL;
}

/* Config helper to set the server-wide default database host.
 */
static const char *set_auth_mysql_host(cmd_parms *parms, void *dummy, const char *host)
{
	auth_db_host = (char *) host;
	return NULL;
}

/* Config helper to set server-wide defaults for database parameters.
 */
static const char *set_auth_mysql_info(cmd_parms * parms, void *dummy, const char *host, const char *user, const char *pwd)
{
	if (*host != '.') {
		auth_db_host = (char *) host;
	}

	if (*user != '.') {
		auth_db_user = (char *)user;
	}

	if (*pwd != '.') {
		auth_db_pwd = (char *)pwd;
	}

	return NULL;
}

/* Config helper to set the server-wide default database username.
 */
static const char *set_auth_mysql_user(cmd_parms *parms, void *dummy, const char *user)
{
	auth_db_user = (char *)user;
	return NULL;
}

/* Config helper to set the server-wide default database password (coupled to
 * the user specified above).
 */
static const char *set_auth_mysql_pwd(cmd_parms *parms, void *dummy, const char *pwd)
{
	auth_db_pwd = (char *)pwd;
	return NULL;
}

/* Set the server-wide database server socket.
 */
static const char *set_auth_mysql_socket(cmd_parms *parms, void *dummy, const char *sock)
{
	auth_db_socket = (char *)socket;
	return NULL;
}

/* Set the server-wide database server port.
 */
static const char *set_auth_mysql_port(cmd_parms *parms, void *dummy, const char *port)
{
	auth_db_port = (unsigned int) atoi(port);
	return NULL;
}

/* Config helper to judge whether to allow per-directory configs to override
 * the server-wide defaults for database parameters.  The only reason this
 * exists (instead of using an ap_set_flag_slot) is because this isn't part
 * of a config structure, and I'm not sure how to set globals from the Apache
 * config thing.
 */
static const char *set_auth_mysql_override(cmd_parms *parms, void *dummy, int arg)
{
	auth_db_override = arg;
	return NULL;
}

/* Config helper to set a selected encryption type.
 */
static const char *set_encryption_types(cmd_parms *cmd, void *sconf, const char *arg)
{
	mysql_auth_config_rec *sec = (mysql_auth_config_rec *) sconf;

	int new_encryption_flag = get_encryption_flag(arg);

	if (!new_encryption_flag) {
		APACHELOG(APLOG_ERR, cmd, "Unsupported encryption type: %s", arg);
		return NULL;
	}

	if (!sec->using_encryption_types) {
		sec->encryption_types = 0;
		sec->using_encryption_types = 1;
	}
	
	sec->encryption_types |= new_encryption_flag;
	
	return NULL;
}

/* This pair of config helpers exist only because of varying semantics
 * in the two versions of mod_auth_mysql I merged.  As soon as we have a
 * consistent set of configuration primitives, these are going.
 */
static const char *set_non_persistent(cmd_parms *cmd, void *sconf, int arg)
{
	mysql_auth_config_rec *sec = (mysql_auth_config_rec *) sconf;

	sec->persistent = !arg;
	APACHELOG(APLOG_DEBUG, cmd, "set_non_persistent: Setting persistent in %s to %i", sec->dir, sec->persistent);
	return NULL;
}

static const char *set_persistent(cmd_parms *cmd, void *sconf, int arg)
{
	mysql_auth_config_rec *sec = (mysql_auth_config_rec *) sconf;

	sec->persistent = arg;
	APACHELOG(APLOG_DEBUG, cmd, "set_persistent: Setting persistent in %s to %i", sec->dir, sec->persistent);
	return NULL;
}

static const char *enable_mysql(cmd_parms *cmd, void *sconf, int arg)
{
	mysql_auth_config_rec *sec = (mysql_auth_config_rec *) sconf;

	sec->enable_mysql_auth = arg;
	APACHELOG(APLOG_DEBUG, cmd, "enable_mysql: Setting enable_mysql_auth in %s to %i", sec->dir, sec->enable_mysql_auth);
	return NULL;
}

/* The command list.  What it's called, when it's legal to use it, and
 * what to do when we find it.  Pretty cool, IMHO.
 */

#ifdef APACHE2
static
command_rec mysql_auth_cmds[] = {
   AP_INIT_TAKE3( "Auth_MySQL_Info",	set_auth_mysql_info,
   		  NULL,
   		  RSRC_CONF,	"host, user and password of the MySQL database" ),

   AP_INIT_TAKE1( "AuthMySQL_DefaultHost",	set_auth_mysql_host,
		  NULL,
		  RSRC_CONF,	"Default MySQL host" ),

   AP_INIT_TAKE1( "AuthMySQL_DefaultUser",	set_auth_mysql_user,
		  NULL,
		  RSRC_CONF,	"Default MySQL user" ),

   AP_INIT_TAKE1( "AuthMySQL_DefaultPassword",	set_auth_mysql_pwd,
		  NULL,
		  RSRC_CONF,	"Default MySQL password" ),

   AP_INIT_TAKE1( "Auth_MySQL_DefaultPort",	set_auth_mysql_port,
		  NULL,
		  RSRC_CONF,	"Default MySQL server port" ),
	
   AP_INIT_TAKE1( "Auth_MySQL_DefaultSocket",	set_auth_mysql_socket,
		  NULL,
		  RSRC_CONF,	"Default MySQL server socket" ),
	  	
   AP_INIT_TAKE1( "Auth_MySQL_General_DB",	set_auth_mysql_db,
		  NULL,
		  RSRC_CONF,	"default database for MySQL authentication" ),

   AP_INIT_TAKE1( "AuthMySQL_DefaultDB",	set_auth_mysql_db,
		  NULL,
		  RSRC_CONF,	"default database for MySQL authentication" ),

   AP_INIT_TAKE1( "AuthMySQL_Host",	ap_set_string_slot,
		  (void*)APR_XtOffsetOf(mysql_auth_config_rec, db_host),
		  OR_AUTHCFG,	"database host" ),

   AP_INIT_TAKE1( "Auth_MySQL_Host",	ap_set_string_slot,
		  (void*)APR_XtOffsetOf(mysql_auth_config_rec, db_host),
		  OR_AUTHCFG,	"database host" ),

   AP_INIT_TAKE1( "Auth_MySQL_Socket",	ap_set_string_slot,
		  (void*)APR_XtOffsetOf(mysql_auth_config_rec, db_socket),
		  OR_AUTHCFG,	"database host socket" ),

   AP_INIT_TAKE1( "AuthMySQL_Socket",	ap_set_string_slot,
		  (void*)APR_XtOffsetOf(mysql_auth_config_rec, db_socket),
		  OR_AUTHCFG,	"database host socket" ),

   AP_INIT_TAKE1( "Auth_MySQL_Port",	ap_set_string_slot,
		  (void*)APR_XtOffsetOf(mysql_auth_config_rec, db_port),
		  OR_AUTHCFG,	"database host port" ),

   AP_INIT_TAKE1( "AuthMySQL_Port",	ap_set_string_slot,
		  (void*)APR_XtOffsetOf(mysql_auth_config_rec, db_port),
		  OR_AUTHCFG,	"database host port" ),

   AP_INIT_TAKE1( "Auth_MySQL_Username",	ap_set_string_slot,
		  (void*)APR_XtOffsetOf(mysql_auth_config_rec, db_user),
		  OR_AUTHCFG,	"database user" ),

   AP_INIT_TAKE1( "AuthMySQL_User",	ap_set_string_slot,
		  (void*)APR_XtOffsetOf(mysql_auth_config_rec, db_user),
		  OR_AUTHCFG,	"database user" ),

   AP_INIT_TAKE1( "Auth_MySQL_Password",	ap_set_string_slot,
		(void*)APR_XtOffsetOf(mysql_auth_config_rec, db_pwd),
		  OR_AUTHCFG,	"database password" ),

   AP_INIT_TAKE1( "AuthMySQL_Password",			ap_set_string_slot,
		  (void*)APR_XtOffsetOf(mysql_auth_config_rec, db_pwd),
		  OR_AUTHCFG,	"database password" ),

   AP_INIT_TAKE1( "Auth_MySQL_DB",		ap_set_string_slot,
		  (void*)APR_XtOffsetOf(mysql_auth_config_rec, db_name),
		  OR_AUTHCFG,	"database name" ),

   AP_INIT_TAKE1( "AuthMySQL_DB",	ap_set_string_slot,
		  (void*)APR_XtOffsetOf(mysql_auth_config_rec, db_name),
		  OR_AUTHCFG,	"database name" ),

   AP_INIT_TAKE1( "Auth_MySQL_Password_Table",		ap_set_string_slot,
		  (void*)APR_XtOffsetOf(mysql_auth_config_rec, user_table),
		  OR_AUTHCFG,	"Name of the MySQL table containing the password/user-name combination" ),

   AP_INIT_TAKE1( "AuthMySQL_Password_Table",		ap_set_string_slot,
		  (void*)APR_XtOffsetOf(mysql_auth_config_rec, user_table),
		  OR_AUTHCFG,	"Name of the MySQL table containing the password/user-name combination" ),

   AP_INIT_TAKE1( "Auth_MySQL_Group_Table",		ap_set_string_slot,
		  (void*)APR_XtOffsetOf(mysql_auth_config_rec, group_table),
		  OR_AUTHCFG,	"Name of the MySQL table containing the group-name/user-name combination; can be the same as the password-table." ),

   AP_INIT_TAKE1( "Auth_MySQL_Group_Clause",		ap_set_string_slot,
		  (void*)APR_XtOffsetOf(mysql_auth_config_rec, group_where_clause),
		  OR_AUTHCFG,	"Additional WHERE clause for group/user-name lookup" ),

   AP_INIT_TAKE1( "AuthMySQL_Group_Table",		ap_set_string_slot,
		  (void*)APR_XtOffsetOf(mysql_auth_config_rec, group_table),
		  OR_AUTHCFG,	"Name of the MySQL table containing the group-name/user-name combination; can be the same as the password-table." ),

   AP_INIT_TAKE1( "Auth_MySQL_Password_Field",		ap_set_string_slot,
		  (void*)APR_XtOffsetOf(mysql_auth_config_rec, password_field),
		  OR_AUTHCFG,	"The name of the field in the MySQL password table" ),

   AP_INIT_TAKE1( "AuthMySQL_Password_Field",		ap_set_string_slot,
		  (void*)APR_XtOffsetOf(mysql_auth_config_rec, password_field),
		  OR_AUTHCFG,	"The name of the field in the MySQL password table" ),

   AP_INIT_TAKE1( "Auth_MySQL_Password_Clause",		ap_set_string_slot,
		  (void*)APR_XtOffsetOf(mysql_auth_config_rec, password_where_clause),
		  OR_AUTHCFG,	"Additional WHERE clause for group password/user-name lookup" ),

   AP_INIT_TAKE1( "Auth_MySQL_Username_Field",		ap_set_string_slot,
		  (void*)APR_XtOffsetOf(mysql_auth_config_rec, user_field),
		  OR_AUTHCFG,	"The name of the user-name field in the MySQL password (and possibly group) table(s)." ),

   AP_INIT_TAKE1( "AuthMySQL_Username_Field",		ap_set_string_slot,
		  (void*)APR_XtOffsetOf(mysql_auth_config_rec, user_field),
		  OR_AUTHCFG,	"The name of the user-name field in the MySQL password (and possibly group) table(s)." ),

   AP_INIT_TAKE1( "Auth_MySQL_Group_Field",		ap_set_string_slot,
		  (void*)APR_XtOffsetOf(mysql_auth_config_rec, group_field),
		  OR_AUTHCFG,	"The name of the group field in the MySQL group table; must be set if you want to use groups." ),

   AP_INIT_TAKE1( "AuthMySQL_Group_Field",		ap_set_string_slot,
		  (void*)APR_XtOffsetOf(mysql_auth_config_rec, group_field),
		  OR_AUTHCFG,	"The name of the group field in the MySQL group table; must be set if you want to use groups." ),

   AP_INIT_TAKE1( "Auth_MySQL_Group_User_Field",	ap_set_string_slot,
		  (void*)APR_XtOffsetOf(mysql_auth_config_rec, group_user_field),
		  OR_AUTHCFG,	"The name of the user-name field in the MySQL group table; defaults to the same as the username field for the password table." ),

   AP_INIT_TAKE1( "AuthMySQL_Group_User_Field",		ap_set_string_slot,
		  (void*)APR_XtOffsetOf(mysql_auth_config_rec, group_user_field),
		  OR_AUTHCFG,	"The name of the user-name field in the MySQL group table; defaults to the same as the username field for the password table." ),

   AP_INIT_FLAG( "Auth_MySQL_Empty_Passwords",		ap_set_flag_slot,
		 (void*)APR_XtOffsetOf(mysql_auth_config_rec, allow_empty_passwords),
		 OR_AUTHCFG,	"Enable (on) or disable (off) empty password strings; in which case any user password is accepted." ),

   AP_INIT_FLAG( "AuthMySQL_Empty_Passwords",		ap_set_flag_slot,
		 (void*)APR_XtOffsetOf(mysql_auth_config_rec, allow_empty_passwords),
		 OR_AUTHCFG,	"Enable (on) or disable (off) empty password strings; in which case any user password is accepted." ),

   AP_INIT_FLAG( "Auth_MySQL_Authoritative",		ap_set_flag_slot,
		 (void*)APR_XtOffsetOf(mysql_auth_config_rec, authoritative),
		 OR_AUTHCFG,	"When 'on' the MySQL database is taken to be authoritative and access control is not passed along to other db or access modules." ),

   AP_INIT_FLAG( "AuthMySQL_Authoritative",		ap_set_flag_slot,
		 (void*)APR_XtOffsetOf(mysql_auth_config_rec, authoritative),
		 OR_AUTHCFG,	"When 'on' the MySQL database is taken to be authoritative and access control is not passed along to other db or access modules." ),

   AP_INIT_FLAG( "AuthMySQL_AllowOverride",		set_auth_mysql_override,
		 NULL,
		 RSRC_CONF,	"Allow directory overrides of configuration" ),

   AP_INIT_FLAG( "Auth_MySQL_Encrypted_Passwords",	set_crypted_password_flag,
		 NULL,
		 OR_AUTHCFG,	"When 'on' the password in the password table are taken to be crypt()ed using your machines crypt() function." ),

   AP_INIT_FLAG( "AuthMySQL_Encrypted_Passwords",	set_crypted_password_flag,
		  NULL,
		  OR_AUTHCFG,	"When 'on' the password in the password table are taken to be crypt()ed using your machines crypt() function." ),

   AP_INIT_FLAG( "Auth_MySQL_Scrambled_Passwords",	set_scrambled_password_flag,
		 NULL,
		 OR_AUTHCFG,	"When 'on' the password in the password table are taken to be scramble()d using mySQL's password() function." ),

   AP_INIT_FLAG( "AuthMySQL_Scrambled_Passwords",	set_scrambled_password_flag,
		 NULL,
		 OR_AUTHCFG,	"When 'on' the password in the password table are taken to be scramble()d using mySQL's password() function." ),

   AP_INIT_ITERATE( "Auth_MySQL_Encryption_Types",	set_encryption_types,
		  NULL,
		  OR_AUTHCFG,	"Encryption types to use" ),

   AP_INIT_ITERATE( "AuthMySQL_Encryption_Types",		set_encryption_types,
		  NULL,
		  OR_AUTHCFG,	"Encryption types to use" ),

   AP_INIT_FLAG( "Auth_MySQL_Non_Persistent",		set_non_persistent,
		 NULL,
		 OR_AUTHCFG,	"Use non-persistent MySQL links" ),

   AP_INIT_FLAG( "AuthMySQL_Persistent",		set_persistent,
		 NULL,
		 OR_AUTHCFG,	"Use non-persistent MySQL links" ),

   AP_INIT_FLAG( "Auth_MySQL",		enable_mysql,
		 NULL,
		 OR_AUTHCFG,	"Enable MySQL authentication" ),

   AP_INIT_FLAG( "AuthMySQL",		enable_mysql,
		 NULL,
		 OR_AUTHCFG,	"Enable MySQL authentication" ),

   AP_INIT_TAKE1( "Auth_MySQL_Where",		ap_set_string_slot,
		  (void*)APR_XtOffsetOf(mysql_auth_config_rec, password_where_clause),
		  OR_AUTHCFG,	"Additional WHERE clause for group password/user-name lookup" ),

  { NULL }
};
#else
command_rec mysql_auth_cmds[] = {
	{ "Auth_MySQL_Info",			set_auth_mysql_info,
	  NULL,
	  RSRC_CONF,	TAKE3,	"host, user and password of the MySQL database" },

	{ "AuthMySQL_DefaultHost",		set_auth_mysql_host,
	  NULL,
	  RSRC_CONF,	TAKE1,	"Default MySQL host" },

	{ "AuthMySQL_DefaultUser",		set_auth_mysql_user,
	  NULL,
	  RSRC_CONF,	TAKE1,	"Default MySQL user" },

	{ "AuthMySQL_DefaultPassword",		set_auth_mysql_pwd,
	  NULL,
	  RSRC_CONF,	TAKE1,	"Default MySQL password" },

	{ "Auth_MySQL_DefaultPort",		set_auth_mysql_port,
	  NULL,
	  RSRC_CONF,	TAKE1,  "Default MySQL server port" },
	
	{ "Auth_MySQL_DefaultSocket",		set_auth_mysql_socket,
	  NULL,
	  RSRC_CONF,    TAKE1,  "Default MySQL server socket" },
	  	
	{ "Auth_MySQL_General_DB",		set_auth_mysql_db,
	  NULL,
	  RSRC_CONF,	TAKE1,	"default database for MySQL authentication" },
	  
	{ "AuthMySQL_DefaultDB",		set_auth_mysql_db,
	  NULL,
	  RSRC_CONF,	TAKE1,	"default database for MySQL authentication" },

	{ "AuthMySQL_Host",			ap_set_string_slot,
	  (void *) XtOffsetOf(mysql_auth_config_rec, db_host),
	  OR_AUTHCFG,	TAKE1,	"database host" },

	{ "Auth_MySQL_Host",			ap_set_string_slot,
	  (void *) XtOffsetOf(mysql_auth_config_rec, db_host),
	  OR_AUTHCFG,	TAKE1,	"database host" },

	{ "Auth_MySQL_Socket",			ap_set_string_slot,
	  (void *) XtOffsetOf(mysql_auth_config_rec, db_socket),
	  OR_AUTHCFG,	TAKE1,	"database host socket" },

	{ "Auth_MySQL_Port",			ap_set_string_slot,
	  (void *) XtOffsetOf(mysql_auth_config_rec, db_port),
	  OR_AUTHCFG,	TAKE1,	"database host socket" },

	{ "Auth_MySQL_Username",		ap_set_string_slot,
	  (void *) XtOffsetOf(mysql_auth_config_rec, db_user),
	  OR_AUTHCFG,	TAKE1,	"database user" },
	  
	{ "AuthMySQL_User",			ap_set_string_slot,
	  (void *) XtOffsetOf(mysql_auth_config_rec, db_user),
	  OR_AUTHCFG,	TAKE1,	"database user" },
	  
	{ "Auth_MySQL_Password",		ap_set_string_slot,
	  (void *) XtOffsetOf(mysql_auth_config_rec, db_pwd),
	  OR_AUTHCFG,	TAKE1,	"database password" },
	  
	{ "AuthMySQL_Password",			ap_set_string_slot,
	  (void *) XtOffsetOf(mysql_auth_config_rec, db_pwd),
	  OR_AUTHCFG,	TAKE1,	"database password" },
	  
	{ "Auth_MySQL_DB",			ap_set_string_slot,
	  (void *) XtOffsetOf(mysql_auth_config_rec, db_name),
	  OR_AUTHCFG,	TAKE1,	"database name" },
	  
	{ "AuthMySQL_DB",			ap_set_string_slot,
	  (void *) XtOffsetOf(mysql_auth_config_rec, db_name),
	  OR_AUTHCFG,	TAKE1,	"database name" },
	  
	{ "Auth_MySQL_Password_Table",		ap_set_string_slot,
	  (void *) XtOffsetOf(mysql_auth_config_rec, user_table),
	  OR_AUTHCFG,	TAKE1,	"Name of the MySQL table containing the password/user-name combination" },
	  
	{ "AuthMySQL_Password_Table",		ap_set_string_slot,
	  (void *) XtOffsetOf(mysql_auth_config_rec, user_table),
	  OR_AUTHCFG,	TAKE1,	"Name of the MySQL table containing the password/user-name combination" },
	  
	{ "Auth_MySQL_Group_Table",		ap_set_string_slot,
	  (void *) XtOffsetOf(mysql_auth_config_rec, group_table),
	  OR_AUTHCFG,	TAKE1,	"Name of the MySQL table containing the group-name/user-name combination; can be the same as the password-table." },
	  
	{ "Auth_MySQL_Group_Clause",		ap_set_string_slot,
	  (void *) XtOffsetOf(mysql_auth_config_rec, group_where_clause),
	  OR_AUTHCFG,	TAKE1,  "Additional WHERE clause for group/user-name lookup" },
	  
	{ "AuthMySQL_Group_Table",		ap_set_string_slot,
	  (void *) XtOffsetOf(mysql_auth_config_rec, group_table),
	  OR_AUTHCFG,	TAKE1,	"Name of the MySQL table containing the group-name/user-name combination; can be the same as the password-table." },

	{ "Auth_MySQL_Password_Field",		ap_set_string_slot,
	  (void *) XtOffsetOf(mysql_auth_config_rec, password_field),
	  OR_AUTHCFG,	TAKE1,	"The name of the field in the MySQL password table" },

	{ "AuthMySQL_Password_Field",		ap_set_string_slot,
	  (void *) XtOffsetOf(mysql_auth_config_rec, password_field),
	  OR_AUTHCFG,	TAKE1,	"The name of the field in the MySQL password table" },

	{ "Auth_MySQL_Password_Clause",		ap_set_string_slot,
	  (void *) XtOffsetOf(mysql_auth_config_rec, password_where_clause),
	  OR_AUTHCFG,	TAKE1,  "Additional WHERE clause for group password/user-name lookup" },

	{ "Auth_MySQL_Username_Field",		ap_set_string_slot,
	  (void *) XtOffsetOf(mysql_auth_config_rec, user_field),
	  OR_AUTHCFG,	TAKE1,	"The name of the user-name field in the MySQL password (and possibly group) table(s)." },
	  
	{ "AuthMySQL_Username_Field",		ap_set_string_slot,
	  (void *) XtOffsetOf(mysql_auth_config_rec, user_field),
	  OR_AUTHCFG,	TAKE1,	"The name of the user-name field in the MySQL password (and possibly group) table(s)." },
	  
	{ "Auth_MySQL_Group_Field",		ap_set_string_slot,
	  (void *) XtOffsetOf(mysql_auth_config_rec, group_field),
	  OR_AUTHCFG,	TAKE1,	"The name of the group field in the MySQL group table; must be set if you want to use groups." },

	{ "AuthMySQL_Group_Field",		ap_set_string_slot,
	  (void *) XtOffsetOf(mysql_auth_config_rec, group_field),
	  OR_AUTHCFG,	TAKE1,	"The name of the group field in the MySQL group table; must be set if you want to use groups." },

	{ "Auth_MySQL_Group_User_Field",	ap_set_string_slot,
	  (void *) XtOffsetOf(mysql_auth_config_rec, group_user_field),
	  OR_AUTHCFG,	TAKE1,	"The name of the user-name field in the MySQL group table; defaults to the same as the username field for the password table." },

	{ "AuthMySQL_Group_User_Field",		ap_set_string_slot,
	  (void *) XtOffsetOf(mysql_auth_config_rec, group_user_field),
	  OR_AUTHCFG,	TAKE1,	"The name of the user-name field in the MySQL group table; defaults to the same as the username field for the password table." },

	{ "Auth_MySQL_Empty_Passwords",		ap_set_flag_slot,
	  (void *) XtOffsetOf(mysql_auth_config_rec, allow_empty_passwords),
	  OR_AUTHCFG,	FLAG,	"Enable (on) or disable (off) empty password strings; in which case any user password is accepted." },

	{ "AuthMySQL_Empty_Passwords",		ap_set_flag_slot,
	  (void *) XtOffsetOf(mysql_auth_config_rec, allow_empty_passwords),
	  OR_AUTHCFG,	FLAG,	"Enable (on) or disable (off) empty password strings; in which case any user password is accepted." },

	{ "Auth_MySQL_Authoritative",		ap_set_flag_slot,
	  (void *) XtOffsetOf(mysql_auth_config_rec, authoritative),
	  OR_AUTHCFG,	FLAG,	"When 'on' the MySQL database is taken to be authoritative and access control is not passed along to other db or access modules." },

	{ "AuthMySQL_Authoritative",		ap_set_flag_slot,
	  (void *) XtOffsetOf(mysql_auth_config_rec, authoritative),
	  OR_AUTHCFG,	FLAG,	"When 'on' the MySQL database is taken to be authoritative and access control is not passed along to other db or access modules." },

	{ "AuthMySQL_AllowOverride",		set_auth_mysql_override,
	  NULL,
	  RSRC_CONF,	FLAG,	"Allow directory overrides of configuration" },

	{ "Auth_MySQL_Encrypted_Passwords",	set_crypted_password_flag,
	  NULL,
	  OR_AUTHCFG,	FLAG,	"When 'on' the password in the password table are taken to be crypt()ed using your machines crypt() function." },

	{ "AuthMySQL_Encrypted_Passwords",	set_crypted_password_flag,
	  NULL,
	  OR_AUTHCFG,	FLAG,	"When 'on' the password in the password table are taken to be crypt()ed using your machines crypt() function." },

	{ "Auth_MySQL_Scrambled_Passwords",	set_scrambled_password_flag,
	  NULL,
	  OR_AUTHCFG,	FLAG,	"When 'on' the password in the password table are taken to be scramble()d using mySQL's password() function." },

	{ "AuthMySQL_Scrambled_Passwords",	set_scrambled_password_flag,
	  NULL,
	  OR_AUTHCFG,	FLAG,	"When 'on' the password in the password table are taken to be scramble()d using mySQL's password() function." },

	{ "Auth_MySQL_Encryption_Types",	set_encryption_types,
	  NULL,
	  OR_AUTHCFG,	ITERATE,"Encryption types to use" },

	{ "AuthMySQL_Encryption_Types",		set_encryption_types,
	  NULL,
	  OR_AUTHCFG,	ITERATE,"Encryption types to use" },

	{ "Auth_MySQL_Non_Persistent",		set_non_persistent,
	  NULL,
	  OR_AUTHCFG,	FLAG,	"Use non-persistent MySQL links" },

	{ "AuthMySQL_Persistent",		set_persistent,
	  NULL,
	  OR_AUTHCFG,	FLAG,	"Use non-persistent MySQL links" },

	{ "Auth_MySQL",				enable_mysql,
	  NULL,
	  OR_AUTHCFG,	FLAG,	"Enable MySQL authentication" },

	{ "AuthMySQL",				enable_mysql,
	  NULL,
	  OR_AUTHCFG,	FLAG,	"Enable MySQL authentication" },

	{ "Auth_MySQL_Where",			ap_set_string_slot,
	  (void *) XtOffsetOf(mysql_auth_config_rec, password_where_clause),
	  OR_AUTHCFG,	TAKE1,  "Additional WHERE clause for group password/user-name lookup" },

	{ NULL }
};

/*	{ "Auth_MySQL",				ap_set_flag_slot,		(void *) XtOffsetOf(mysql_auth_config_rec, enable_mysql_auth),		OR_AUTHCFG,	FLAG,	"Enable (on) or disable (off) MySQL authentication." },
	{ "AuthMySQL",				ap_set_flag_slot,		(void *) XtOffsetOf(mysql_auth_config_rec, enable_mysql_auth),		OR_AUTHCFG,	FLAG,	"Enable (on) or disable (off) MySQL authentication." },
*/


#endif

#ifdef APACHE2
static apr_status_t
#else
static void
#endif
auth_mysql_result_cleanup(void *result)
{
	mysql_free_result((MYSQL_RES *) result);
}

#ifdef APACHE2
static void note_cleanups_for_mysql_auth_result(apr_pool_t *p, MYSQL_RES * result)
#else
static void note_cleanups_for_mysql_auth_result(pool *p, MYSQL_RES * result)
#endif
{
#ifdef APACHE2
	apr_pool_cleanup_register(p, (void *) result, auth_mysql_result_cleanup, auth_mysql_result_cleanup);
#else
	ap_register_cleanup(p, (void *) result, auth_mysql_result_cleanup, auth_mysql_result_cleanup);
#endif

}

/* Make a MySQL database link open and ready for business.  Returns 0 on
 * success, or the MySQL error number which caused the failure if there was
 * some sort of problem.
 */
static int open_auth_dblink(request_rec *r, mysql_auth_config_rec *sec)
{
	char *host = "localhost", *socket = NULL;
	unsigned int port = 3306;
	char *dbname = auth_db_name, *user = auth_db_user, *pwd = auth_db_pwd;
	void (*sigpipe_handler)();
	unsigned long client_flag = 0;

	APACHELOG(APLOG_DEBUG, r, "Opening DB connection for %s", sec->dir);
	
	if (auth_db_host) {
		host = auth_db_host;
	}

	if (auth_db_socket)
	{
		socket = auth_db_socket;
	}

	if (auth_db_port != -1)
	{
		port = auth_db_port;
	}
	
	if (auth_db_override)
	{
		if (sec->db_socket)
		{
			socket = sec->db_socket;
		}
	
		if (sec->db_port != -1)
		{
			port = sec->db_port;
		}
		
		if (sec->db_host)
		{
			host = sec->db_host;
		}

		if (sec->db_user) {
			user = sec->db_user;
		}

		if (sec->db_pwd) {
			pwd = sec->db_pwd;
		}

		if (sec->db_name) {
			dbname = sec->db_name;
		}
	}

	if (!dbname || !dbname[0]) {
		/* It would be preferred if we had somewhere to connect to... */
		APACHELOG(APLOG_CRIT, r,
			"No database given - rather a problem.  Bailing out.");
		return CR_WRONG_HOST_INFO;
	}

	/* MySQL likes to throw the odd SIGPIPE now and then - ignore it for now */
	sigpipe_handler = signal(SIGPIPE, SIG_IGN);
		
	sec->dbh = mysql_init(NULL);
	
	if (!mysql_real_connect(sec->dbh, host, user, pwd, dbname, port, socket, client_flag)) {
		APACHELOG(APLOG_ERR, r,
			 "Connection error: %s", mysql_error(sec->dbh));
		errno = mysql_errno(sec->dbh);
		mysql_close(sec->dbh);
		sec->dbh = NULL;
		return errno;
	}

	signal(SIGPIPE, sigpipe_handler);
	
	APACHELOG(APLOG_DEBUG, r, "Persistent in %s is %i", sec->dir, sec->persistent);

	if (!sec->persistent) {
		APACHELOG(APLOG_DEBUG, r, "Registering non-persistent for %s", sec->dir);
#ifdef APACHE2
		apr_pool_cleanup_register(r->pool, sec, auth_mysql_cleanup, apr_pool_cleanup_null);
#else
		ap_block_alarms();
		ap_register_cleanup(r->pool, sec, auth_mysql_cleanup, ap_null_cleanup);
		ap_unblock_alarms();
#endif
	}

	/* W00t!  We made it! */
	return 0;
}

/* Run a query against the database.  Doesn't assume nearly anything about
 * the state of affairs regarding the database connection.
 * Returns 0 on a successful query run, or the MySQL error number on
 * error.  It is the responsibility of the calling function to retrieve any
 * data which may have been obtained through the running of this function.
 */
static int safe_mysql_query(request_rec *r, char *query, mysql_auth_config_rec *sec)
{
	int error = CR_UNKNOWN_ERROR;

	APACHELOG(APLOG_DEBUG, r, "sec->dbh in %s is %p", sec->dir, sec->dbh);
	if (sec->dbh_error_lastchance)
	{
		APACHELOG(APLOG_DEBUG, r, "Last chance, bub");
	}
	else
	{
		APACHELOG(APLOG_DEBUG, r, "Ordinary query");
	}
	
	if (!sec->dbh) {
		APACHELOG(APLOG_DEBUG, r,
			"No DB connection open - firing one up");
		if ((error = open_auth_dblink(r, sec))) {
			APACHELOG(APLOG_DEBUG, r,
				"open_auth_dblink returned %i", error);
			return error;
		}

		APACHELOG(APLOG_DEBUG, r,
			"Correctly opened a new DB connection");
	}

	APACHELOG(APLOG_DEBUG, r,
		"Running query: [%s]", query);

	if (mysql_query(sec->dbh, query)) {
		error = mysql_errno(sec->dbh);
		
		APACHELOG(APLOG_DEBUG, r, 
			"Query maybe-failed: %s (%i), lastchance=%i", mysql_error(sec->dbh), error, sec->dbh_error_lastchance);
		APACHELOG(APLOG_DEBUG, r,
			"Error numbers of interest are %i (SG) and %i (SL)",
			CR_SERVER_GONE_ERROR, CR_SERVER_LOST);
		if (sec->dbh_error_lastchance)
		{
			/* No matter what error, we're moving out */
			return error;
		}
		else if (error == CR_SERVER_LOST || error == CR_SERVER_GONE_ERROR)
		{
			/* Try again, once more only */
			sec->dbh_error_lastchance = 1;
			sec->dbh = NULL;
			APACHELOG(APLOG_DEBUG, r, "Retrying query");
			return safe_mysql_query(r, query, sec);
		}
		else
		{
			return error;
		}
	}

	return 0;
}

/* Store the result of a query in a result structure, and return it.  It's
 * "safe" in the fact that a cleanup function is registered for the structure
 * so it will be tidied up after the request.
 * Returns the result data on success, or NULL if there was no data to retrieve.
 */
#ifdef APACHE2
static MYSQL_RES *safe_mysql_store_result(apr_pool_t *p, mysql_auth_config_rec *sec)
#else
static MYSQL_RES *safe_mysql_store_result(pool *p, mysql_auth_config_rec *sec)
#endif
{
	MYSQL_RES *result;
#ifdef APACHE2
#else	
	ap_block_alarms();
#endif

	result = mysql_store_result(sec->dbh);
#ifdef DEBUG
	syslog(LOG_DEBUG, "MAMDEBUG: Got %p for result", result);
#endif

	if (result) {
		note_cleanups_for_mysql_auth_result(p, result);
	}
#ifdef APACHE2
#else
	ap_unblock_alarms();
#endif

	return result;
}

/* Check the plaintext password given against the hashed version.  Go
 * through all configured encryption types looking for a match.
 * Returns 1 on a match, 0 on no match, and -1 on error.
 */
static int check_password(const char *plaintext, char *hashed, request_rec *r, mysql_auth_config_rec *sec)
{
	encryption_type_entry *ete;
	
	/* empty password support */
	if (sec->allow_empty_passwords && !strlen(hashed)) {
		APACHELOG(APLOG_INFO, r, "User successful on empty password");
		return 1;
	}
			
	for (ete=supported_encryption_types; ete->name; ete++) {
		if (sec->encryption_types & ete->flag) {
			APACHELOG(APLOG_DEBUG, r,
				"Checking with %s", ete->name);
			if (ete->check_function(plaintext, hashed)) {
				APACHELOG(APLOG_DEBUG, r, "Auth succeeded");
				return 1;
			}
		}
	}
	APACHELOG(APLOG_DEBUG, r, "User failed all encryption types");
	return 0;
}

/* Checks whether the username and plaintext password match the user data
 * stored in the database, against all configured encryption schemes.
 * Returns 1 on successful match, 0 unsuccessful match, -1 on error.
 */
static int mysql_check_user_password(request_rec *r, char *user, const char *password, mysql_auth_config_rec *sec)
{
	char *auth_table = "mysql_auth", *auth_user_field = "username",
		*auth_password_field = "passwd", *auth_password_clause = "";
	char *query;
	char *esc_user = mysql_escape(user, r->pool);
	MYSQL_RES *result;
	MYSQL_ROW sql_row;
	int rv;
		
	if (sec->user_table) {
		auth_table = sec->user_table;
	}
	if (sec->user_field) {
		auth_user_field = sec->user_field;
	}
	if (sec->password_field) {
		auth_password_field = sec->password_field;
	}
	if (sec->password_where_clause) {
		auth_password_clause = sec->password_where_clause;
	}
	APACHELOG(APLOG_DEBUG, r,
		"Constructing password collection query with "
		"passfield=[%s], table=[%s], userfield=[%s], where_clause=[%s]", auth_password_field
							, auth_table, esc_user,auth_password_clause);

	query = (char *) PSTRCAT(r->pool, "SELECT ", auth_password_field,
					" FROM ", auth_table, " WHERE ",
					auth_user_field, "='", esc_user, "'",
					auth_password_clause, NULL);
	if (!query) {
		APACHELOG(APLOG_ERR, r,
			"Failed to create query string - we're in deep poopy");
		return -1;
	}

	if ((rv = safe_mysql_query(r, query, sec))) {
		if (sec->dbh)
		{
			APACHELOG(APLOG_ERR, r,
				"Query call failed: %s (%i)", mysql_error(sec->dbh), rv);
		}

		APACHELOG(APLOG_DEBUG, r, "Failed query was: [%s]", query);
		return -1;
	}

	result = safe_mysql_store_result(r->pool, sec);
	if (!result) {
		APACHELOG(APLOG_ERR, r,
			"Failed to get MySQL result structure : %s", mysql_error(sec->dbh));
		return -1;
	}
	switch (mysql_num_rows(result)) {
		case 0:
			APACHELOG(APLOG_INFO, r, "User not found");
			return 0;
			break;
		case 1:
			sql_row = mysql_fetch_row(result);
			/* ensure we have a row, and non NULL value */
			if (!sql_row || !sql_row[0]) {
				APACHELOG(APLOG_INFO, r,
					"No row returned or NULL value: %s", mysql_error(sec->dbh));
				return -1;
			}
			
			rv = check_password(password, sql_row[0], r, sec);
			if (rv == 0)
			{
				APACHELOG(APLOG_INFO, r,
					"Authentication failed for user %s", user);
			}
			return rv;
			break;

		default:
			APACHELOG(APLOG_ERR, r,
				"Multiple password rows returned - this is what is known, in the industry, as a Bad Thing");
			return -1;
			break;
	}

	APACHELOG(APLOG_CRIT, r, "Can't happen - dropped out of switch!");
	return -1;
}

/* Has a look to see if the given user is a member of the named group.
 * Returns 0 if user is not a part of the group, 1 if he is, -1 on error.
 */
static int mysql_check_group(request_rec *r, char *user, char *group, mysql_auth_config_rec *sec)
{
	char *auth_table = "mysql_auth", *auth_group_field="groups", *auth_group_clause="";
	char *query;
	char *esc_user = mysql_escape(user, r->pool);
	char *esc_group = mysql_escape(group, r->pool);
	MYSQL_RES *result;
	MYSQL_ROW row;
	char *auth_user_field = "username";

	if (!group) {
		APACHELOG(APLOG_ERR, r, "No group specified");
		return 0;
	}
	
	if (sec->group_table) {
		auth_table = sec->group_table;
	}

	if (sec->user_field)
	{
		auth_user_field = sec->user_field;
	}

	if (sec->group_user_field) {
		auth_user_field = sec->group_user_field;
	}
		
	if (sec->group_field) {
		auth_group_field = sec->group_field;
	}
	if (sec->group_where_clause) {
		auth_group_clause = sec->group_where_clause;
	}

	APACHELOG(APLOG_DEBUG, r,
		"Making group query with auth_table=[%s], auth_user_field=[%s], "
		"esc_user=[%s], esc_group=[%s], auth_group_field=[%s], where_clause=[%s]",
		auth_table, auth_user_field, esc_user, esc_group, auth_group_field,auth_group_clause);

	query = (char *) PSTRCAT(r->pool, "SELECT count(*) FROM ", auth_table,
		" WHERE ", auth_user_field, "='", esc_user, "'",
		" and FIND_IN_SET('", esc_group, "',", auth_group_field, ")",
		auth_group_clause, NULL);

	APACHELOG(APLOG_DEBUG, r, "Group query created; [%s]", query);
		
	if (!query) {
		APACHELOG(APLOG_CRIT, r,
			"Failed to create group-check query - ran out of memory!");
		return -1;
	}
	if (safe_mysql_query(r, query, sec)) {
		APACHELOG(APLOG_CRIT, r, "Group query failed!");
		return -1;
	}
	result = safe_mysql_store_result(r->pool, sec);
	if (!result || (row=mysql_fetch_row(result))==NULL || !row[0]) {
		APACHELOG(APLOG_CRIT, r, "Store result failed - erp!");
		return -1;
	}

	return atoi(row[0]);
}

/* The apache-called function.  Note that this function says nothing about
 * what the user should be allowed to do - merely that they have proved they
 * are who they say they are.  Return OK if the user has proved their
 * identity, DECLINED if we are not taking any responsibility for them, or
 * some Apache error if there was a problem.
 */
int mysql_authenticate_basic_user(request_rec *r)
{
	mysql_auth_config_rec *sec = (mysql_auth_config_rec *) ap_get_module_config(r->per_dir_config, &auth_mysql_module);
	conn_rec *c = r->connection;
	const char *sent_pw;
	int res;

	APACHELOG(APLOG_DEBUG, r, "Handling an authentication request for section %s", sec->dir);

#ifdef DEBUG
	for (res = 0; res < 512; res++)
	{
		if (sec->sacrificial_lamb[res] == '\0')
		{
			sec->sacrificial_lamb[res] = 'n';
		}
		if (!isgraph(sec->sacrificial_lamb[res]))
		{
			sec->sacrificial_lamb[res] = ' ';
		}
	}
	sec->sacrificial_lamb[511] = '\0';
	
	syslog(LOG_DEBUG, "The contents of the lamb are %s", sec->sacrificial_lamb);
#endif

	if (!sec->enable_mysql_auth) {
		APACHELOG(APLOG_DEBUG, r,
			"Not running mod-auth-mysql for %s - disabled", r->unparsed_uri);
		return DECLINED;
	}

	/* use MySQL auth only if we have a database */
	if (!auth_db_name && !sec->db_name) {
		APACHELOG(APLOG_ERR, r,
			"Failed to run mod-auth-mysql for %s: No database name specified", r->unparsed_uri);
		return DECLINED;
	}

	/* obtain sent password */
	if ((res = ap_get_basic_auth_pw(r, &sent_pw))) {
		return res;
	}

#ifdef APACHE2
	APACHELOG(APLOG_DEBUG, r,
		"Starting basic user auth for [%s] in %s, child pid %i",
		r->user,
		sec->dir, getpid());
#else
	APACHELOG(APLOG_DEBUG, r,
		"Starting basic user auth for [%s] in %s, child pid %i",
		c->user,
		sec->dir, getpid());
#endif

#ifdef APACHE2
	switch (mysql_check_user_password(r, r->user, sent_pw, sec)) {
#else
	switch (mysql_check_user_password(r, c->user, sent_pw, sec)) {
#endif
		case 0:
			ap_note_basic_auth_failure(r);
			return HTTP_UNAUTHORIZED;
			break;
		case 1:
			return OK;
			break;
		case -1:
		default:
			APACHELOG(APLOG_DEBUG, r,
				"mysql_check_user_password returned error");
			return HTTP_INTERNAL_SERVER_ERROR;
			break;
	}
}

/* Go through a 'requires' line configured for the module, and return OK
 * if the user satisfies the line, or some sort of failure return code
 * otherwise.
 */
int check_mysql_auth_require(char *user, const char *t, request_rec *r)
{
	mysql_auth_config_rec *sec = (mysql_auth_config_rec *) ap_get_module_config(r->per_dir_config, &auth_mysql_module);
	const char *w;
	int rv;
	
	w = ap_getword(r->pool, &t, ' ');
	/* If they're letting any old authenticated user, we're off the
	 * hook!
	 */
	if (!strcmp(w, "valid-user")) {
		return OK;
	}

	/* Checking a list of usernames */
	if (!strcmp(w, "user")) {
		while (t[0]) {
			w = ap_getword_conf(r->pool, &t);
			if (!strcmp(user, w)) {
				return OK;
			}
		}
		/* Not found */
		return HTTP_UNAUTHORIZED;
	} else if (!strcmp(w, "group")) {
		/* This is the prickly one; checking whether the
		 * user is a member of a listed group.
		 */
		while (t[0])
		{
			w = ap_getword_conf(r->pool, &t);
			rv = mysql_check_group(r, user, (char *)w, sec);
			
			if (rv == 1)
			{
				/* Yep, we're all good */
				return OK;
			}
			else if (rv == -1)
			{
				return HTTP_INTERNAL_SERVER_ERROR;
			}
		}
		/* Distinct lack of foundage */
		return HTTP_UNAUTHORIZED;
	}
	else
	{
		APACHELOG(APLOG_ERR, r, "Invalid argument to require: %s", w);
		return HTTP_INTERNAL_SERVER_ERROR;
	}

	APACHELOG(APLOG_ERR, r, "CAN'T HAPPEN: Dropped out of the bottom of check_mysql_auth_require!");
	return HTTP_INTERNAL_SERVER_ERROR;
}

/* This is the authorization step.  We're presuming that the user has
 * successfully negotiated the step of "I am who I say I am", now we're
 * checking to see if the user has permission to access this particular
 * resource.  As with mysql_authenticate_basic_user, above, we return OK if
 * the user is fit to proceed, DECLINED if we don't want to make a decision
 * either way, HTTP_UNAUTHORIZED if the user is not allowed, or some apache
 * error if there was a major problem.
 */
int mysql_check_auth(request_rec *r)
{
	mysql_auth_config_rec *sec = (mysql_auth_config_rec *) ap_get_module_config(r->per_dir_config, &auth_mysql_module);
#ifdef APACHE2
	char *user = r->user;
#else
	char *user = r->connection->user;
#endif
	int m = r->method_number;
	int rv;
	register int x;
	const char *t;
#ifdef APACHE2
	const apr_array_header_t *reqs_arr = ap_requires(r);
#else
	const array_header *reqs_arr = ap_requires(r);
#endif
	require_line *reqs;

	/* use MySQL auth only if we have a database */
	if (!auth_db_name && !sec->db_name) {
		return DECLINED;
	}

	/* What do we do if there's no requires line available?  Either say
	 * "bad puppy" if we're king shit, or say "not my problem" otherwise.
	 */
	if (!reqs_arr) {
		if (sec->authoritative) {
			APACHELOG(APLOG_ERR, r, "No requires line available");
			return HTTP_UNAUTHORIZED;
		} else {
			return DECLINED;
		}
	}

	/* This is an array of all the requires lines which apply to us.
	 * There may be several, as in the case of something like:
	 * require user foo bar
	 * require group wombat
	 * That is, the user either has to belong to the group 'wombat' or
	 * be 'foo' or 'bar'.
	 * We have to check them all.  Yuck.
	 */
	reqs = (require_line *) reqs_arr->elts;

	for (x = 0; x < reqs_arr->nelts; x++) {
		/* mjp: WTF is this? */
		if (!(reqs[x].method_mask & (1 << m))) {
			continue;
		}

		t = reqs[x].requirement;

		/* OK, this might seem a little weird.  The logic is that,
		 * if the user is approved, that's sufficient, so we can
		 * return OK straight away.  Alternately, if there's an
		 * error, we bomb the check and die.  The only circumstance
		 * where we continue looping is when the user didn't pass this
		 * check, but might pass a future one, so keep looking.
		 */
		if ((rv = check_mysql_auth_require(user, t, r))
			!= HTTP_UNAUTHORIZED)
		{
			return rv;
		}
	}

	/* We don't know, and we don't really care */
	if (!(sec->authoritative)) {
		return DECLINED;
	}

	ap_note_basic_auth_failure(r);
	return HTTP_UNAUTHORIZED;
}



#ifdef APACHE2
static void register_hooks(apr_pool_t *p)
{
	ap_hook_check_user_id(mysql_authenticate_basic_user, NULL, NULL, APR_HOOK_MIDDLE);
	ap_hook_auth_checker(mysql_check_auth, NULL, NULL, APR_HOOK_MIDDLE);
}
#endif

#ifdef APACHE2
module AP_MODULE_DECLARE_DATA auth_mysql_module =
{
STANDARD20_MODULE_STUFF,
create_mysql_auth_dir_config, /* dir config creater */
NULL,                       /* dir merger --- default is to override */
NULL,                       /* server config */
NULL,                       /* merge server config */
mysql_auth_cmds,              /* command apr_table_t */
register_hooks              /* register hooks */
};
#else
module auth_mysql_module =
{
	STANDARD_MODULE_STUFF,
	mysql_auth_init_handler,	/* initializer */
	create_mysql_auth_dir_config,	/* dir config creater */
	NULL,				/* dir merger --- default is to override */
	NULL,				/* server config */
	NULL,				/* merge server config */
	mysql_auth_cmds,		/* command table */
	NULL,				/* handlers */
	NULL,				/* filename translation */
	mysql_authenticate_basic_user,	/* check_user_id */
	mysql_check_auth,		/* check auth */
	NULL,				/* check access */
	NULL,				/* type_checker */
	NULL,				/* pre-run fixups */
	NULL				/* logger */
#if MODULE_MAGIC_NUMBER >= 19970103
	,NULL				/* header parser */
#endif
#if MODULE_MAGIC_NUMBER >= 19970719
	,NULL				/* child_init */
#endif
#if MODULE_MAGIC_NUMBER >= 19970728
	,NULL				/* child_exit */
#endif
#if MODULE_MAGIC_NUMBER >= 19970902
	,NULL				/* post read-request */
#endif
};
#endif
