/*
** Copyright 2000-2005 Double Precision, Inc.  See COPYING for
** distribution information.
*/

#include	<stdio.h>
#include	<stdlib.h>
#include	<string.h>
#include	<unistd.h>
#include	<ctype.h>
#include	<sys/types.h>
#include	<sys/stat.h>
#include	<mysql.h>
#include	<time.h>

#include	"authmysql.h"
#include	"authmysqlrc.h"
#include	"auth.h"
#include	"courierauthdebug.h"

#define err courier_auth_err

/* siefca@pld.org.pl */
#define		MAX_SUBSTITUTION_LEN	32
#define		SV_BEGIN_MARK		"$("
#define		SV_END_MARK		")"
#define		SV_BEGIN_LEN		((sizeof(SV_BEGIN_MARK))-1)
#define		SV_END_LEN		((sizeof(SV_END_MARK))-1)

static const char rcsid[]="$Id: authmysqllib.c,v 1.45 2007/10/07 02:50:45 mrsam Exp $";

/* siefca@pld.org.pl */
struct var_data {			
	const char *name;
	const char *value;
	const size_t size;
	size_t value_length;
	} ;

/* siefca@pld.org.pl */
typedef int (*parsefunc)(const char *, size_t, void *);

static const char *read_env(const char *env)
{
static char *mysqlauth=0;
static size_t mysqlauth_size=0;
size_t	i;
char	*p=0;
int	l=strlen(env);

	if (!mysqlauth)
	{
	FILE	*f=fopen(AUTHMYSQLRC, "r");
	struct	stat	buf;

		if (!f)	return (0);
		if (fstat(fileno(f), &buf) ||
			(mysqlauth=malloc(buf.st_size+2)) == 0)
		{
			fclose(f);
			return (0);
		}
		if (fread(mysqlauth, buf.st_size, 1, f) != 1)
		{
			free(mysqlauth);
			mysqlauth=0;
			fclose(f);
			return (0);
		}
		mysqlauth[mysqlauth_size=buf.st_size]=0;

		for (i=0; i<mysqlauth_size; i++)
			if (mysqlauth[i] == '\n')
			{	/* siefca@pld.org.pl */
				if (!i || mysqlauth[i-1] != '\\')
				{
					mysqlauth[i]='\0';
				}
				else
				{
					mysqlauth[i]=mysqlauth[i-1]= ' ';
				}
			}
		fclose(f);
	}

	for (i=0; i<mysqlauth_size; )
	{
		p=mysqlauth+i;
		if (memcmp(p, env, l) == 0 &&
			isspace((int)(unsigned char)p[l]))
		{
			p += l;
			while (*p && *p != '\n' &&
				isspace((int)(unsigned char)*p))
				++p;
			break;
		}

		while (i < mysqlauth_size)
			if (mysqlauth[i++] == 0)	break;
	}

	if (i < mysqlauth_size)
		return (p);
	return (0);
}

static MYSQL mysql_buf;

static MYSQL *mysql=0;

static int do_connect()
{
const	char *server;
const	char *userid;
const	char *password;
const	char *database;
const	char *server_socket=0;
unsigned int server_port=0;
unsigned int server_opt=0;
const	char *p;

const	char *sslkey;
const	char *sslcert;
const	char *sslcacert;
const	char *sslcapath;
const	char *sslcipher;
unsigned int  use_ssl=0;

/*
** Periodically detect dead connections.
*/
	if (mysql)
	{
		static time_t last_time=0;
		time_t t_check;

		time(&t_check);

		if (t_check < last_time)
			last_time=t_check;	/* System clock changed */

		if (t_check < last_time + 60)
			return (0);

		last_time=t_check;
			
		if (mysql_ping(mysql) == 0) return (0);

		DPRINTF("authmysqllib: mysql_ping failed, connection lost");
		mysql_close(mysql);
		mysql=0;
	}

	server=read_env("MYSQL_SERVER");
	userid=read_env("MYSQL_USERNAME");
	password=read_env("MYSQL_PASSWORD");
	database=read_env("MYSQL_DATABASE");

#if MYSQL_VERSION_ID >= 32200
	sslkey=read_env("MYSQL_SSL_KEY");
	sslcert=read_env("MYSQL_SSL_CERT");
	sslcacert=read_env("MYSQL_SSL_CACERT");
	sslcapath=read_env("MYSQL_SSL_CAPATH");
	sslcipher=read_env("MYSQL_SSL_CIPHER");

	if ((sslcert != NULL) && ((sslcacert != NULL) || (sslcapath != NULL)))
	{
		use_ssl=1;
	}
#endif

	server_socket=(char *) read_env("MYSQL_SOCKET");

	if ((p=read_env("MYSQL_PORT")) != 0)
	{
		server_port=(unsigned int) atoi(p);
	}

	if ((p=read_env("MYSQL_OPT")) != 0)
	{
		server_opt=(unsigned int) atol(p);
	}

	if (!server && !server_socket)
	{
		err("authmysql: MYSQL_SERVER nor MYSQL_SOCKET set in"
			AUTHMYSQLRC ".");
		return (-1);
	}

	if (!userid)
	{
		err("authmysql: MYSQL_USERNAME not set in "
			AUTHMYSQLRC ".");
		return (-1);
	}

	if (!database)
	{
		err("authmysql: MYSQL_DATABASE not set in "
			AUTHMYSQLRC ".");
		return (-1);
	}

#if MYSQL_VERSION_ID >= 32200
	mysql_init(&mysql_buf);
	if (use_ssl)
	{
		mysql_ssl_set(&mysql_buf, sslkey, sslcert, sslcacert,
			      sslcapath, sslcipher);
	}
	mysql=mysql_real_connect(&mysql_buf, server, userid, password,
				 NULL,
				 server_port,
				 server_socket,
				 server_opt);
#else
	mysql=mysql_connect(&mysql_buf, server, userid, password);
#endif
	if (!mysql)
	{
		err("failed to connect to mysql server (server=%s, userid=%s): %s",
			server ? server : "<null>",
			userid ? userid : "<null>",
			mysql_error(&mysql_buf));
		return (-1);
	}

	if (mysql_select_db(mysql, database))
	{
		err("authmysql: mysql_select_db(%s) error: %s",
			database, mysql_error(mysql));
		mysql_close(mysql);
		mysql=0;
		return (-1);
	}
	return (0);
}

void auth_mysql_cleanup()
{
	if (mysql)
	{
		mysql_close(mysql);
		mysql=0;
	}
}

static struct authmysqluserinfo ui={0, 0, 0, 0, 0, 0, 0, 0};

static void initui()
{

	if (ui.username)
		free(ui.username);
	if (ui.cryptpw)
		free(ui.cryptpw);
	if (ui.clearpw)
		free(ui.clearpw);
	if (ui.home)
		free(ui.home);
	if (ui.maildir)
		free(ui.maildir);
	if (ui.quota)
		free(ui.quota);
	if (ui.fullname)
		free(ui.fullname);
	if (ui.options)
		free(ui.options);
	memset(&ui, 0, sizeof(ui));
}

static void append_username(char *p, const char *username,
			    const char *defdomain)
{
	for (strcpy(p, username); *p; p++)
		if (*p == '\'' || *p == '"' || *p == '\\' ||
		    (int)(unsigned char)*p < ' ')
			*p=' ';	/* No funny business */
	if (strchr(username, '@') == 0 && defdomain && *defdomain)
		strcat(strcpy(p, "@"), defdomain);
}

/* siefca@pld.org.pl */
static struct var_data *get_variable (const char *begin, size_t len,
  		  	                   struct var_data *vdt)
{
struct var_data *vdp;

	if (!begin || !vdt) /* should never happend */
	{
		err("authmysql: critical error while "
				 "parsing substitution variable");
		return NULL;
	}
	if (len < 1)
	{
		err("authmysql: unknown empty substitution "
				 "variable - aborting");
		return NULL;
	}
	if (len > MAX_SUBSTITUTION_LEN)
	{
		err("authmysql: variable name too long "
				 "while parsing substitution. "
				 "name begins with "
				 SV_BEGIN_MARK
				 "%.*s...", MAX_SUBSTITUTION_LEN, begin);
		return NULL;
	}
	
	for (vdp=vdt; vdp->name; vdp++)
		if (vdp->size == len+1 &&
		    !strncmp(begin, vdp->name, len))
		{
			if (!vdp->value)
				vdp->value = "";
			if (!vdp->value_length)		/* length cache */
				vdp->value_length = strlen (vdp->value);
			return vdp;
		}
	
	err("authmysql: unknown substitution variable "
			 SV_BEGIN_MARK
			 "%.*s"
			 SV_END_MARK
			 , (int)len, begin);
	
	return NULL;
}

/* siefca@pld.org.pl */
static int ParsePlugin_counter (const char *p, size_t length, void *vp)
{
	if (!p || !vp || length < 0)
	{
		err("authmysql: bad arguments while counting "
				 "query string");
		return -1;
	}
	
	*((size_t *)vp) += length;
   
	return 0;
}

/* siefca@pld.org.pl */
static int ParsePlugin_builder (const char *p, size_t length, void *vp)
{
char	**strptr = (char **) vp;

	if (!p || !vp || length < 0)
	{
		err("authmysql: bad arguments while building "
				 "query string");
		return -1;
	}
	
	if (!length) return 0;
	memcpy ((void *) *strptr, (void *) p, length);
	*strptr += length;
	
	return 0;
}

/* siefca@pld.org.pl */
static int parse_core  (const char *source, struct var_data *vdt,
			parsefunc outfn, void *result)
{
size_t	v_size		= 0,
	t_size		= 0;
const char	*p, *q, *e,
		*v_begin, *v_end,
		*t_begin, *t_end;
struct var_data	*v_ptr;

	if (!source)
		source = "";
	if (!result)
	{
		err("authmysql: no memory allocated for result "
				 "while parser core was invoked");
		return -1;
	}
	if (!vdt)
	{
		err("authmysql: no substitution table found "
				 "while parser core was invoked");
		return -1;
	}
	
	q = source;
	while ( (p=strstr(q, SV_BEGIN_MARK)) )
	{
		e = strstr (p, SV_END_MARK);
		if (!e)
		{
			err("authmysql: syntax error in "
					 "substitution "
					 "- no closing symbol found! "
					 "bad variable begins with:"
					 "%.*s...", MAX_SUBSTITUTION_LEN, p);
			return -1;
		}
		
		/*
		 **
		 **	     __________sometext$(variable_name)_________
		 **	 	       |      |  |    	     |	
		 **	        t_begin' t_end'  `v_begin    `v_end
		 **
		  */

		v_begin	= p+SV_BEGIN_LEN; /* variable field ptr		    */
		v_end	= e-SV_END_LEN;	  /* variable field last character  */
		v_size	= v_end-v_begin+1;/* variable field length	    */
		
		t_begin	= q;		  /* text field ptr		    */
		t_end	= p-1;		  /* text field last character	    */
		t_size	= t_end-t_begin+1;/* text field length		    */

		/* work on text */
		if ( (outfn (t_begin, t_size, result)) == -1 )
			return -1;
		
		/* work on variable */
		v_ptr = get_variable (v_begin, v_size, vdt);
		if (!v_ptr) return -1;
		
		if ( (outfn (v_ptr->value, v_ptr->value_length, result)) == -1 )
			return -1;
		
		q = e + 1;
	}

	/* work on last part of text if any */
	if (*q != '\0')
		if ( (outfn (q, strlen(q), result)) == -1 )
			return -1;

	return 0;
}

/* siefca@pld.org.pl */
static char *parse_string (const char *source, struct var_data *vdt)
{
struct var_data *vdp	= NULL;
char	*output_buf	= NULL,
	*pass_buf	= NULL;
size_t	buf_size	= 2;

	if (source == NULL || *source == '\0' || 
	    vdt == NULL    || vdt[0].name == NULL)
	{
		err("authmysql: source clause is empty "
		       "- this is critical error");
		return NULL;
	}

	/* zero var_data length cache - important! */
	for (vdp=vdt; vdp->name; vdp++)
		vdp->value_length = 0;


	/* phase 1 - count and validate string */
	if ( (parse_core (source, vdt, &ParsePlugin_counter, &buf_size)) != 0)
		return NULL;

	/* phase 2 - allocate memory */
	output_buf = malloc (buf_size);
	if (!output_buf)
	{
		perror ("malloc");
		return NULL;
	}
	pass_buf = output_buf;

	/* phase 3 - build the output string */
	if ( (parse_core (source, vdt, &ParsePlugin_builder, &pass_buf)) != 0)
	{
		free (output_buf);
		return NULL;
	}	
	*pass_buf = '\0';
	
	return output_buf;
}

/* siefca@pld.org.pl */
static const char *get_localpart (const char *username)
{
size_t		lbuf	= 0;
const char	*l_end, *p;
char		*q;
static char	localpart_buf[130];
	
	if (!username || *username == '\0')	return NULL;
	
	p = strchr(username,'@');
	if (p)
	{
		if ((p-username) > 128)
			return NULL;
		l_end = p;
	}
	else
	{
		if ((lbuf = strlen(username)) > 128)
			return NULL;
		l_end = username + lbuf;
	}

	p=username;
	q=localpart_buf;
	
	while (*p && p != l_end)
		if (*p == '\"' || *p == '\\' ||
		    *p == '\'' || (int)(unsigned char)*p < ' ')
			p++;
		else
			*q++ = *p++;

	*q = '\0';
	return localpart_buf;
}

/* siefca@pld.org.pl */
static const char *get_domain (const char *username, const char *defdomain)
{
static char	domain_buf[260];
const char	*p;
char		*q;
	
	if (!username || *username == '\0')	return NULL;
	p = strchr(username,'@');
	
	if (!p || *(p+1) == '\0')
	{
		if (defdomain && *defdomain)
			return defdomain;
		else
			return NULL;
	}

	p++;
	if ((strlen(p)) > 256)
		return NULL;
	
	q = domain_buf;
	while (*p)
		if (*p == '\"' || *p == '\\' ||
	    	    *p == '\'' || (int)(unsigned char)*p < ' ')
			p++;
		else
			*q++ = *p++;

	*q = '\0';
	return domain_buf;
}

/* siefca@pld.org.pl */

static const char *validateMyPassword (const char *password)
{
static char pass_buf[2][540]; /* Use two buffers, see parse_chpass_clause */
static int next_pass=0;
const char	*p;
char		*q, *endq;
	
	if (!password || *password == '\0' || (strlen(password)) > 256)
		return NULL;
	
	next_pass= 1-next_pass;

	p = password;
	q = pass_buf[next_pass];
	endq = q + sizeof pass_buf[next_pass];
	
	while (*p && q < endq)
	{
		if (*p == '\"' || *p == '\\' || *p == '\'')
			*q++ = '\\';
		*q++ = *p++;
	}
	
	if (q >= endq)
		return NULL;
	
	*q = '\0';
	return pass_buf[next_pass];
}

/* siefca@pld.org.pl */
static char *parse_select_clause (const char *clause, const char *username,
				  const char *defdomain,
				  const char *service)
{
static struct var_data vd[]={
	    {"local_part",	NULL,	sizeof("local_part"),	0},
	    {"domain",		NULL,	sizeof("domain"),	0},
	    {"service",		NULL,	sizeof("service"),	0},
	    {NULL,		NULL,	0,			0}};

	if (clause == NULL || *clause == '\0' ||
	    !username || *username == '\0')
		return NULL;
	
	vd[0].value	= get_localpart (username);
	vd[1].value	= get_domain (username, defdomain);
	if (!vd[0].value || !vd[1].value)
		return NULL;
	vd[2].value     = service;

	return (parse_string (clause, vd));
}

/* siefca@pld.org.pl */
static char *parse_chpass_clause (const char *clause, const char *username,
				  const char *defdomain, const char *newpass,
				  const char *newpass_crypt)
{
static struct var_data vd[]={
	    {"local_part",	NULL,	sizeof("local_part"),		0},
	    {"domain",		NULL,	sizeof("domain"),		0},
	    {"newpass",		NULL, 	sizeof("newpass"),		0},
	    {"newpass_crypt",	NULL,	sizeof("newpass_crypt"),	0},
	    {NULL,		NULL,	0,				0}};

	if (clause == NULL || *clause == '\0'		||
	    !username || *username == '\0'		||
	    !newpass || *newpass == '\0'		||
	    !newpass_crypt || *newpass_crypt == '\0')	return NULL;

	vd[0].value	= get_localpart (username);
	vd[1].value	= get_domain (username, defdomain);
	vd[2].value	= validateMyPassword (newpass);
	vd[3].value	= validateMyPassword (newpass_crypt);
	
	if (!vd[0].value || !vd[1].value ||
	    !vd[2].value || !vd[3].value)	return NULL;

	return (parse_string (clause, vd));
}

struct authmysqluserinfo *auth_mysql_getuserinfo(const char *username,
						 const char *service)
{
const char *defdomain	=NULL;
char	*querybuf, *p;
MYSQL_ROW	row;
MYSQL_RES	*result;
int		num_fields;
char		*endp;

const char	*select_clause; /* siefca@pld.org.pl */

static const char query[]=
	"SELECT %s, %s, %s, %s, %s, %s, %s, %s, %s, %s FROM %s WHERE %s = \"";

	if (do_connect())	return (0);

	initui();

	select_clause=read_env("MYSQL_SELECT_CLAUSE");
	defdomain=read_env("DEFAULT_DOMAIN");	
	if (!defdomain)	defdomain="";
	
	if (!select_clause) /* siefca@pld.org.pl */
	{
		const char	*user_table,
				*crypt_field,
				*clear_field,
				*name_field,
				*uid_field,
				*gid_field,
				*login_field,
				*home_field,
				*maildir_field,
				*quota_field,
				*options_field,
				*where_clause;

		user_table=read_env("MYSQL_USER_TABLE");

		if (!user_table)
		{
			err("authmysql: MYSQL_USER_TABLE not set in "
				AUTHMYSQLRC ".");
			return (0);
		}

		crypt_field=read_env("MYSQL_CRYPT_PWFIELD");
		clear_field=read_env("MYSQL_CLEAR_PWFIELD");
		name_field=read_env("MYSQL_NAME_FIELD");

		if (!crypt_field && !clear_field)
		{
			err("authmysql: MYSQL_CRYPT_PWFIELD and "
				"MYSQL_CLEAR_PWFIELD not set in " AUTHMYSQLRC ".");
			return (0);
		}
		if (!crypt_field) crypt_field="\"\"";
		if (!clear_field) clear_field="\"\"";
		if (!name_field) name_field="\"\"";

		uid_field = read_env("MYSQL_UID_FIELD");
		if (!uid_field) uid_field = "uid";

		gid_field = read_env("MYSQL_GID_FIELD");
		if (!gid_field) gid_field = "gid";

		login_field = read_env("MYSQL_LOGIN_FIELD");
		if (!login_field) login_field = "id";

		home_field = read_env("MYSQL_HOME_FIELD");
		if (!home_field) home_field = "home";

		maildir_field=read_env(service && strcmp(service, "courier")==0
				       ? "MYSQL_DEFAULTDELIVERY"
				       : "MYSQL_MAILDIR_FIELD");
		if (!maildir_field) maildir_field="\"\"";

		quota_field=read_env("MYSQL_QUOTA_FIELD");
		if (!quota_field) quota_field="\"\""; 

		options_field=read_env("MYSQL_AUXOPTIONS_FIELD");
		if (!options_field) options_field="\"\"";

		where_clause=read_env("MYSQL_WHERE_CLAUSE");
		if (!where_clause) where_clause = "";

		querybuf=malloc(sizeof(query) + 100
			+ 2 * strlen(login_field)
			+ strlen(crypt_field)
			+ strlen(clear_field)
			+ strlen(uid_field) + strlen(gid_field)
			+ strlen(home_field)
			+ strlen(maildir_field)
			+ strlen(quota_field)
			+ strlen(name_field)
			+ strlen(options_field)
			+ strlen(user_table)
			+ strlen(username)
			+ strlen(defdomain)
			+ strlen(where_clause));

		if (!querybuf)
		{
			perror("malloc");
			return (0);
		}

		sprintf(querybuf, query, login_field, crypt_field, clear_field, 
			uid_field, gid_field, home_field, maildir_field, quota_field,
			name_field, options_field, user_table, login_field);

		p=querybuf+strlen(querybuf);

		append_username(p, username, defdomain);
		strcat(p, "\"");
	
		if (strcmp(where_clause, "")) {
			strcat(p, " AND (");
			strcat(p, where_clause);
			strcat(p, ")");
		}
	}
	else
	{
		/* siefca@pld.org.pl */
		querybuf=parse_select_clause (select_clause, username,
					      defdomain, service);
		if (!querybuf)
		{
			DPRINTF("parse_select_clause failed (DEFAULT_DOMAIN not set?)");
			return 0;
		}
	}

/* Anton Dobkin <anton@viansib.ru>, VIAN, Ltd. */
#if MYSQL_VERSION_ID >= 41000    
	const char *character_set=read_env("MYSQL_CHARACTER_SET");
    
        if(character_set){
            
    	    char *character_set_buf;
        	
            character_set_buf=malloc(strlen(character_set)+11);
        	        
    	    if (!character_set_buf)
            {
		perror("malloc");
        	return (0);
    	    }
        	    				    
    	    strcpy(character_set_buf, "SET NAMES ");
           strcat(character_set_buf, character_set);
        	    						
            DPRINTF("Install of a character set for MySQL. SQL query: SET NAMES %s", character_set);	
        	    							
            if(mysql_query (mysql, character_set_buf))
    	     {    
                err("Install of a character set for MySQL is failed: %s MYSQL_CHARACTER_SET: may be invalid character set", mysql_error(mysql));
    	        auth_mysql_cleanup();
        		    
    		if (do_connect())
        	{
        	    free(character_set_buf);
        	    return (0);
        	}
    	     }
    	    
    	    free(character_set_buf);
        }
#endif	

	DPRINTF("SQL query: %s", querybuf);
	if (mysql_query (mysql, querybuf))
	{
		/* <o.blasnik@nextra.de> */

		DPRINTF("mysql_query failed, reconnecting: %s", mysql_error(mysql));
		auth_mysql_cleanup();

		if (do_connect())
		{
			free(querybuf);
			return (0);
		}

		if (mysql_query (mysql, querybuf))
		{
			DPRINTF("mysql_query failed second time, giving up: %s", mysql_error(mysql));
			free(querybuf);
			auth_mysql_cleanup();
			/* Server went down, that's OK,
			** try again next time.
			*/
			return (0);
		}
	}
	free(querybuf);

	result = mysql_store_result (mysql);       
	if (result)
	{
		if (mysql_num_rows(result))
		{
			row = mysql_fetch_row (result);
			num_fields = mysql_num_fields (result);

			if (num_fields < 6)
			{
				DPRINTF("incomplete row, only %d fields returned",
					num_fields);
				mysql_free_result(result);
				return(0);
			}

			if (row[0] && row[0][0])
				ui.username=strdup(row[0]);
			if (row[1] && row[1][0])
				ui.cryptpw=strdup(row[1]);
			if (row[2] && row[2][0])
				ui.clearpw=strdup(row[2]);
			/* perhaps authmysql needs a glob_uid/glob_gid feature
			   like authldap? */
			if (!row[3] || !row[3][0] ||
			   (ui.uid=strtol(row[3], &endp, 10), endp[0] != '\0'))
			{
				DPRINTF("invalid value for uid: '%s'",
					row[3] ? row[3] : "<null>");
				mysql_free_result(result);
				return 0;
			}
			if (!row[4] || !row[4][0] ||
			   (ui.gid=strtol(row[4], &endp, 10), endp[0] != '\0'))
			{
				DPRINTF("invalid value for gid: '%s'",
					row[4] ? row[4] : "<null>");
				mysql_free_result(result);
				return 0;
			}
			if (row[5] && row[5][0])
				ui.home=strdup(row[5]);
			else
			{
				DPRINTF("required value for 'home' (column 6) is missing");
				mysql_free_result(result);
				return(0);
			}
			if (num_fields > 6 && row[6] && row[6][0])
				ui.maildir=strdup(row[6]);
			if (num_fields > 7 && row[7] && row[7][0])
				ui.quota=strdup(row[7]);
			if (num_fields > 8 && row[8] && row[8][0])
				ui.fullname=strdup(row[8]);
			if (num_fields > 9 && row[9] && row[9][0])
				ui.options=strdup(row[9]);
		}
		else
		{
			DPRINTF("zero rows returned");
			mysql_free_result(result);
			return (&ui); /* User not found */
		}
	}
	else
	{
		DPRINTF("mysql_store_result failed");
		return (0);
	}
	mysql_free_result(result);
	return (&ui);
}

int auth_mysql_setpass(const char *user, const char *pass,
		       const char *oldpass)
{
	char *newpass_crypt;
	const char *p;
	int l;
	char *sql_buf;
	const char *comma;
	int rc=0;

	const char  *clear_field	=NULL,
		    *crypt_field	=NULL,
		    *defdomain		=NULL,
		    *where_clause	=NULL,
		    *user_table		=NULL,
		    *login_field	=NULL,
		    *chpass_clause	=NULL; /* siefca@pld.org.pl */

	if (!mysql)
		return (-1);


	if (!(newpass_crypt=authcryptpasswd(pass, oldpass)))
		return (-1);

	for (l=0, p=pass; *p; p++)
	{
		if ((int)(unsigned char)*p < ' ')
		{
			free(newpass_crypt);
			return (-1);
		}
		if (*p == '"' || *p == '\\')
			++l;
		++l;
	}

	/* siefca@pld.org.pl */
	chpass_clause=read_env("MYSQL_CHPASS_CLAUSE");
	defdomain=read_env("DEFAULT_DOMAIN");
	user_table=read_env("MYSQL_USER_TABLE");
	if (!chpass_clause)
	{
		login_field = read_env("MYSQL_LOGIN_FIELD");
		if (!login_field) login_field = "id";
		crypt_field=read_env("MYSQL_CRYPT_PWFIELD");
		clear_field=read_env("MYSQL_CLEAR_PWFIELD");
		where_clause=read_env("MYSQL_WHERE_CLAUSE");
		sql_buf=malloc(strlen(crypt_field ? crypt_field:"")
				+ strlen(clear_field ? clear_field:"")
				+ strlen(defdomain ? defdomain:"")
				+ strlen(login_field) + l + strlen(newpass_crypt)
				+ strlen(user_table)
				+ strlen(where_clause ? where_clause:"")
				+ 200);
	}
	else
	{
		sql_buf=parse_chpass_clause(chpass_clause,
					    user,
					    defdomain,
					    pass,
					    newpass_crypt);
	}
	

	if (!sql_buf)
	{
		free(newpass_crypt);
		return (-1);
	}

	if (!chpass_clause) /*siefca@pld.org.pl */
	{
		sprintf(sql_buf, "UPDATE %s SET", user_table);

		comma="";

		if (clear_field && *clear_field)
		{
			char *q;

			strcat(strcat(strcat(sql_buf, " "), clear_field),
		    		"=\"");

			q=sql_buf+strlen(sql_buf);
			while (*pass)
			{
				if (*pass == '"' || *pass == '\\')
					*q++= '\\';
				*q++ = *pass++;
			}
			strcpy(q, "\"");
			comma=", ";
		}

		if (crypt_field && *crypt_field)
		{
			strcat(strcat(strcat(strcat(strcat(strcat(sql_buf, comma),
							   " "),
						    crypt_field),
					     "=\""),
			    	newpass_crypt),
		    	     "\"");
		}
		free(newpass_crypt);

		strcat(strcat(strcat(sql_buf, " WHERE "),
			      login_field),
	    	    "=\"");

		append_username(sql_buf+strlen(sql_buf), user, defdomain);

		strcat(sql_buf, "\"");

		if (where_clause && *where_clause)
		{
			strcat(sql_buf, " AND (");
			strcat(sql_buf, where_clause);
			strcat(sql_buf, ")");
		}
		
	}
	else
	{
		free(newpass_crypt);
	}


	if (courier_authdebug_login_level >= 2)
	{
		DPRINTF("setpass SQL: %s", sql_buf);
	}
	if (mysql_query (mysql, sql_buf))
	{
		DPRINTF("setpass SQL failed");
		rc= -1;
		auth_mysql_cleanup();
	}
	free(sql_buf);
	return (rc);
}

void auth_mysql_enumerate( void(*cb_func)(const char *name,
					  uid_t uid,
					  gid_t gid,
					  const char *homedir,
					  const char *maildir,
					  const char *options,
					  void *void_arg),
			   void *void_arg)
{
	const char *defdomain, *select_clause;
	char	*querybuf, *p;
	MYSQL_ROW	row;
	MYSQL_RES	*result;

	static const char query[]=
		"SELECT %s, %s, %s, %s, %s, %s FROM %s WHERE 1=1";

	if (do_connect())	return;

	initui();

	select_clause=read_env("MYSQL_ENUMERATE_CLAUSE");
	defdomain=read_env("DEFAULT_DOMAIN");
	if (!defdomain || !defdomain[0])
		defdomain="*"; /* otherwise parse_select_clause fails */
	
	if (!select_clause)
	{
		const char	*user_table,
				*uid_field,
				*gid_field,
				*login_field,
				*home_field,
				*maildir_field,
				*options_field,
				*where_clause;

		user_table=read_env("MYSQL_USER_TABLE");

		if (!user_table)
		{
			err("authmysql: MYSQL_USER_TABLE not set in "
				AUTHMYSQLRC ".");
			return;
		}

		uid_field = read_env("MYSQL_UID_FIELD");
		if (!uid_field) uid_field = "uid";

		gid_field = read_env("MYSQL_GID_FIELD");
		if (!gid_field) gid_field = "gid";

		login_field = read_env("MYSQL_LOGIN_FIELD");
		if (!login_field) login_field = "id";

		home_field = read_env("MYSQL_HOME_FIELD");
		if (!home_field) home_field = "home";

		maildir_field=read_env("MYSQL_MAILDIR_FIELD");
		if (!maildir_field) maildir_field="\"\"";

		options_field=read_env("MYSQL_AUXOPTIONS_FIELD");
		if (!options_field) options_field="\"\"";

		where_clause=read_env("MYSQL_WHERE_CLAUSE");
		if (!where_clause) where_clause = "";

		querybuf=malloc(sizeof(query) + 100
				+ strlen(login_field)
				+ strlen(uid_field) + strlen(gid_field)
				+ strlen(home_field)
				+ strlen(maildir_field)
				+ strlen(options_field)
				+ strlen(user_table)
				+ strlen(where_clause));

		if (!querybuf)
		{
			perror("malloc");
			return;
		}

		sprintf(querybuf, query, login_field, 
			uid_field, gid_field, home_field, maildir_field,
			options_field, user_table);

		p=querybuf+strlen(querybuf);
	
		if (strcmp(where_clause, "")) {
			strcat(p, " AND (");
			strcat(p, where_clause);
			strcat(p, ")");
		}
	}
	else
	{
		/* siefca@pld.org.pl */
		querybuf=parse_select_clause (select_clause, "*",
					      defdomain, "enumerate");
		if (!querybuf)
		{
			DPRINTF("authmysql: parse_select_clause failed");
			return;
		}
	}
	DPRINTF("authmysql: enumerate query: %s", querybuf);

	if (mysql_query (mysql, querybuf))
	{
		DPRINTF("mysql_query failed, reconnecting: %s", mysql_error(mysql));
		/* <o.blasnik@nextra.de> */

		auth_mysql_cleanup();

		if (do_connect())
		{
			free(querybuf);
			return;
		}

		if (mysql_query (mysql, querybuf))
		{
			DPRINTF("mysql_query failed second time, giving up: %s", mysql_error(mysql));
			free(querybuf);
			auth_mysql_cleanup();
			return;
		}
	}
	free(querybuf);

	result = mysql_use_result (mysql);
	if (result)
	{
		const char *username;
		uid_t uid;
		gid_t gid;
		const char *homedir;
		const char *maildir;
		const char *options;

		while ((row = mysql_fetch_row (result)) != NULL)
		{
			if(!row[0] || !row[0][0]
			|| !row[1] || !row[1][0]
			|| !row[2] || !row[2][0]
			|| !row[3] || !row[3][0])
			{
				continue;
			}

			username=row[0];
			uid=atol(row[1]); /* FIXME use strtol to validate */
			gid=atol(row[2]);
			homedir=row[3];
			maildir=row[4];
			options=row[5];

			if (maildir && !*maildir)
				maildir=NULL;

			(*cb_func)(username, uid, gid, homedir,
				   maildir, options, void_arg);
		}
	}
	/* NULL row could indicate end of result or an error */
	if (mysql_errno(mysql))
	{
		DPRINTF("mysql error during enumeration: %s", mysql_error(mysql));
	}
	else
		(*cb_func)(NULL, 0, 0, NULL, NULL, NULL, void_arg);

	if (result) mysql_free_result(result);
}


syntax highlighted by Code2HTML, v. 0.9.1