/*
 Copyright (c) 2002 Aaron Stone, aaron@serendipity.cx
 Copyright (c) 2005 Paul Stevens, paul@nfg.nl

 This program is free software; you can redistribute it and/or 
 modify it under the terms of the GNU General Public License 
 as published by the Free Software Foundation; either 
 version 2 of the License, or (at your option) any later 
 version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

/*
 * $Id: authldap.c 2187 2006-06-24 14:57:02Z paul $
 * * User authentication functions for LDAP.
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "dbmail.h"

#define AUTH_QUERY_SIZE 1024
#define LDAP_RES_SIZE 1024

extern char *configFile;

static LDAP *_ldap_conn = NULL;
LDAPMod **_ldap_mod;
LDAPMessage *_ldap_res;
LDAPMessage *_ldap_msg;
int _ldap_err;
int _ldap_attrsonly = 0;
char *_ldap_dn;
char **_ldap_vals;
char **_ldap_attrs = NULL;
char _ldap_query[AUTH_QUERY_SIZE];

typedef struct _ldap_cfg {
	field_t bind_dn, bind_pw, base_dn, port, version, scope, hostname;
	field_t user_objectclass, forw_objectclass;
	field_t cn_string;
	field_t field_uid, field_cid, min_cid, max_cid, field_nid, min_nid, max_nid;
	field_t field_mail, field_mailalt, mailaltprefix;
	field_t field_maxmail, field_passwd;
	field_t field_fwd, field_fwdsave, field_fwdtarget, fwdtargetprefix;
	field_t field_members;
	int scope_int, port_int, version_int;
} _ldap_cfg_t;

_ldap_cfg_t _ldap_cfg;


static GList * __auth_get_every_match(const char *q, char **retfields);

static int dm_ldap_user_shadow_rename(u64_t user_idnr, const char *new_name);
static int auth_reconnect(void);
static int auth_search(const gchar *query);

static void __auth_get_config(void)
{
	static int beenhere=0;
	if (beenhere)
		return;
	
	GETCONFIGVALUE("BIND_DN",		"LDAP", _ldap_cfg.bind_dn);
	GETCONFIGVALUE("BIND_PW",		"LDAP", _ldap_cfg.bind_pw);
	GETCONFIGVALUE("BASE_DN",		"LDAP", _ldap_cfg.base_dn);
	GETCONFIGVALUE("PORT",			"LDAP", _ldap_cfg.port);
	GETCONFIGVALUE("VERSION",		"LDAP", _ldap_cfg.version);
	GETCONFIGVALUE("HOSTNAME",		"LDAP", _ldap_cfg.hostname);
	GETCONFIGVALUE("USER_OBJECTCLASS",	"LDAP", _ldap_cfg.user_objectclass);
	GETCONFIGVALUE("FORW_OBJECTCLASS",	"LDAP", _ldap_cfg.forw_objectclass);
	GETCONFIGVALUE("CN_STRING",		"LDAP", _ldap_cfg.cn_string);
	GETCONFIGVALUE("FIELD_UID",		"LDAP", _ldap_cfg.field_uid);
	GETCONFIGVALUE("FIELD_CID",		"LDAP", _ldap_cfg.field_cid);
	GETCONFIGVALUE("MIN_CID",		"LDAP", _ldap_cfg.min_cid);
	GETCONFIGVALUE("MAX_CID",		"LDAP", _ldap_cfg.max_cid);
	GETCONFIGVALUE("FIELD_NID",		"LDAP", _ldap_cfg.field_nid);
	GETCONFIGVALUE("MIN_NID",		"LDAP", _ldap_cfg.min_nid);
	GETCONFIGVALUE("MAX_NID",		"LDAP", _ldap_cfg.max_nid);
	GETCONFIGVALUE("FIELD_MAIL",		"LDAP", _ldap_cfg.field_mail);
	GETCONFIGVALUE("FIELD_QUOTA",		"LDAP", _ldap_cfg.field_maxmail);
	GETCONFIGVALUE("FIELD_PASSWD",		"LDAP", _ldap_cfg.field_passwd);
	GETCONFIGVALUE("FIELD_FWDTARGET",	"LDAP", _ldap_cfg.field_fwdtarget);
	GETCONFIGVALUE("SCOPE",			"LDAP", _ldap_cfg.scope);

	/* Store the port as an integer for later use. */
	_ldap_cfg.port_int = atoi(_ldap_cfg.port);

	/* Store the version as an integer for later use. */
	_ldap_cfg.version_int = atoi(_ldap_cfg.version);
	/* defaults to version 3 */
	if (!_ldap_cfg.version_int)
		_ldap_cfg.version_int=3;

	/* Compare the input string with the possible options,
	 * making sure not to exceeed the length of the given string */
	{
		int len = (strlen(_ldap_cfg.scope) < 3 ? strlen(_ldap_cfg.scope) : 3);

		if (strncasecmp(_ldap_cfg.scope, "one", len) == 0)
			_ldap_cfg.scope_int = LDAP_SCOPE_ONELEVEL;
		else if (strncasecmp(_ldap_cfg.scope, "bas", len) == 0)
			_ldap_cfg.scope_int = LDAP_SCOPE_BASE;
		else if (strncasecmp(_ldap_cfg.scope, "sub", len) == 0)
			_ldap_cfg.scope_int = LDAP_SCOPE_SUBTREE;
		else
			_ldap_cfg.scope_int = LDAP_SCOPE_SUBTREE;
	}
	trace(TRACE_DEBUG,
	      "%s,%s: integer ldap scope is [%d]",__FILE__,__func__,
	      _ldap_cfg.scope_int);
	
	beenhere++;
}

static int auth_ldap_bind(void)
{
	trace(TRACE_DEBUG, "%s,%s: binding to ldap server as [%s] / [xxxxxxxx]",
			__FILE__,__func__, 
			_ldap_cfg.bind_dn);
	
	/* 
	 * TODO: support tls connects
	 */
	
	if ((_ldap_err = ldap_bind_s(_ldap_conn, 
					_ldap_cfg.bind_dn, 
					_ldap_cfg.bind_pw, 
					LDAP_AUTH_SIMPLE))) {
		trace(TRACE_ERROR, "%s,%s: ldap_bind_s failed: %s",
				__FILE__,__func__, 
				ldap_err2string(_ldap_err));
		return -1;
	}
	trace(TRACE_DEBUG, "%s,%s: successfully bound to ldap server",
			__FILE__,__func__);

	return 0;

}

/*
 * auth_connect()
 *
 * initializes the connection for authentication.
 * 
 * returns 0 on success, -1 on failure
 */
int auth_connect(void)
{
	int version;

	if (_ldap_conn != NULL) 
		return 0;
	
	__auth_get_config();
	
	trace(TRACE_DEBUG, "%s,%s: connecting to ldap server on [%s] : [%d] version [%d]",
			__FILE__,__func__, 
			_ldap_cfg.hostname, 
			_ldap_cfg.port_int,
			_ldap_cfg.version_int);
	
	_ldap_conn = ldap_init(
			_ldap_cfg.hostname, 
			_ldap_cfg.port_int);

	switch (_ldap_cfg.version_int) {
	case 2:
		version = LDAP_VERSION2;
		break;
	case 3:
		version = LDAP_VERSION3;
		break;
	default:
		trace(TRACE_ERROR, "%s, %s: Unsupported LDAP version requested [%d]. "
				"Defaulting to LDAP version 3.",
				__FILE__, __func__, _ldap_cfg.version_int);
		version = LDAP_VERSION3;
		break;
	}

	ldap_set_option(_ldap_conn, LDAP_OPT_PROTOCOL_VERSION, &version);

	return auth_ldap_bind();	
}

int auth_disconnect(void)
{
	/* Destroy the connection */
	if (_ldap_conn != NULL) {
		ldap_unbind(_ldap_conn);
		_ldap_conn = NULL;
	}
	return 0;
}

static int auth_search(const gchar *query)
{
	int c=0;

	g_return_val_if_fail(query!=NULL, DM_EQUERY);
	
	while (c++ < 5) {
		trace(TRACE_DEBUG, "%s,%s: [%s]",__FILE__,__func__, query);
		_ldap_err = ldap_search_s(_ldap_conn, _ldap_cfg.base_dn, _ldap_cfg.scope_int, 
				query, _ldap_attrs, _ldap_attrsonly, &_ldap_res);
		
		if (! _ldap_err)
			return 0;
		
		switch (_ldap_err) {
			case LDAP_SERVER_DOWN:
				trace(TRACE_ERROR, "%s,%s: %s", __FILE__, __func__, ldap_err2string(_ldap_err));
				if (auth_reconnect())
					sleep(2); // reconnect failed. wait before trying again
				break;
			default:
				trace(TRACE_ERROR, "%s,%s: %s", __FILE__, __func__, ldap_err2string(_ldap_err));
				return _ldap_err;
				break;
		}
	}
	
	trace(TRACE_FATAL,"%s,%s: unrecoverable error while talking to ldap server", __FILE__, __func__);
	return -1;
}


static int auth_reconnect(void)
{
	auth_disconnect();
	return auth_connect();
}

void dm_ldap_freeresult(GList *entlist)
{
	GList *fldlist, *attlist;
	entlist = g_list_first(entlist);
	while (entlist) {
		fldlist = entlist->data;
		while(fldlist) {
			attlist = fldlist->data;
			g_list_foreach(attlist,(GFunc)g_free,NULL);
			g_list_free(attlist);
			if (! g_list_next(fldlist))
				break;
			fldlist = g_list_next(fldlist);
		}
		if (! g_list_next(entlist))
			break;
		entlist = g_list_next(entlist);
	}
}

GList * dm_ldap_entdm_list_get_values(GList *entlist)
{
	GList *fldlist, *attlist;
	GList *values = NULL;
	gchar *tmp;
	entlist = g_list_first(entlist);
	while (entlist) {
		fldlist = g_list_first(entlist->data);
		while (fldlist) {
			attlist = g_list_first(fldlist->data);
			while (attlist) {
				tmp = (gchar *)attlist->data;
				trace(TRACE_DEBUG,"%s,%s: value [%s]",
						__FILE__, __func__,
						tmp);
				values = g_list_append_printf(values,"%s", tmp);
				if (! g_list_next(attlist))
					break;
				attlist = g_list_next(attlist);
			}
			if (! g_list_next(fldlist))
				break;
			fldlist = g_list_next(fldlist);
		}
		if (! g_list_next(entlist))
			break;
		entlist = g_list_next(entlist);
	}
	return values;
}

static char *dm_ldap_get_filter(const gchar boolean, const gchar *attribute, GList *values) 
{
	/* build user filter from objectclasses */
	gchar *s;
	GString *t = g_string_new("");
	GString *q = g_string_new("");
	GList *l = NULL;

	values = g_list_first(values);
	while (values) {
		g_string_printf(t,"%s=%s", attribute, (char *)values->data);
		l = g_list_append(l,g_strdup(t->str));
		if (! g_list_next(values))
			break;
		values = g_list_next(values);
	}
	t = g_list_join(l,")(");
	g_string_printf(q,"(%c(%s))", boolean, t->str);
	s = q->str;

	g_string_free(t,TRUE);
	g_string_free(q,FALSE);
	g_list_foreach(l,(GFunc)g_free,NULL);
	g_list_free(l);

	return s;
}
	
static u64_t dm_ldap_get_freeid(const gchar *attribute)
{
	/* get the first available uidNumber/gidNumber */
	u64_t id = 0, t;
	GList *ids, *entlist;
	u64_t min = 0, max = 0;
	char *attrs[2] = { (char *)attribute, NULL };
	GString *q = g_string_new("");
	u64_t *key;
	
	g_string_printf(q,"(%s=*)", attribute);
	entlist = __auth_get_every_match(q->str, attrs);
	
	ids = dm_ldap_entdm_list_get_values(entlist);
	
	/* get the valid range */
	if (strcmp(attribute,_ldap_cfg.field_nid)==0) {
		min = strtoull(_ldap_cfg.min_nid,NULL,10);
		max = strtoull(_ldap_cfg.max_nid,NULL,10);
	} 
	if (strcmp(attribute,_ldap_cfg.field_cid)==0) {
		min = strtoull(_ldap_cfg.min_cid,NULL,10);
		max = strtoull(_ldap_cfg.max_cid,NULL,10);
	} 
	g_assert(min < max);
	
	/* allocate the key array */
	key = g_new0(u64_t, 1 + max - min );
	
	/* get all used ids */
	ids = g_list_first(ids);
	while (ids) {
		t = strtoull(ids->data,NULL,10);
		if ( (t >= min) && (t <= max) ) 
			key[t-min] = t;
		if (! g_list_next(ids))
			break;
		ids = g_list_next(ids);
	}

	/* find the first unused id */
	for (t = min; t <= max; t++) {
		if (! (key[t-min])) 
			break;
	}

	g_assert( (t >= min) && (t <= max) );
	
	/* cleanup */
	g_free(key);
	g_list_foreach(ids,(GFunc)g_free,NULL);
	g_list_free(ids);
	
	id=t;
	trace(TRACE_DEBUG,"%s,%s: return free id [%llu]\n", 
			__FILE__, __func__,
			id);
	return id;
}

static char * dm_ldap_user_getdn(u64_t user_idnr) 
{
	GString *t = g_string_new("");
	char *dn;
	
	g_string_printf(t, "(%s=%llu)", _ldap_cfg.field_nid, user_idnr);
	trace(TRACE_DEBUG, "%s,%s: searching with query [%s]", 
			__FILE__,__func__, 
			t->str);
	
	
	if (auth_search(t->str)) {
		g_string_free(t,TRUE);
		return NULL;
	}
		
	if (ldap_count_entries(_ldap_conn, _ldap_res) < 1) {
		trace(TRACE_DEBUG, "%s,%s: no entries found",__FILE__,__func__);
		g_string_free(t,TRUE);
		ldap_msgfree(_ldap_res);
		return NULL;
	}

	if (! (_ldap_msg = ldap_first_entry(_ldap_conn, _ldap_res))) {
		ldap_get_option(_ldap_conn, LDAP_OPT_ERROR_NUMBER, &_ldap_err);
		trace(TRACE_ERROR, "%s,%s: ldap_first_entry failed: %s",
				__FILE__,__func__, 
				ldap_err2string(_ldap_err));
		ldap_msgfree(_ldap_res);
		return NULL;
	}

	if (! (dn = ldap_get_dn(_ldap_conn, _ldap_msg))) {
		ldap_get_option(_ldap_conn, LDAP_OPT_ERROR_NUMBER, &_ldap_err);
		trace(TRACE_ERROR, "%s,%s: ldap_get_dn failed: %s",
				__FILE__,__func__,
				ldap_err2string(_ldap_err));
		ldap_msgfree(_ldap_res);
		return NULL;
	}

	ldap_msgfree(_ldap_res);
	return dn;
}

static int dm_ldap_mod_field(u64_t user_idnr, const char *fieldname, const char *newvalue)
{

	LDAPMod *mods[2], modField; 
	char *newvalues[2];
	
	
	if (! user_idnr) {
		trace(TRACE_ERROR, "%s,%s: no user_idnr specified",
				__FILE__,__func__);
		return -1;
	}
	if (! fieldname) {
		trace(TRACE_ERROR, "%s,%s: no fieldname specified",
				__FILE__,__func__);
		return -1;
	}
	if (! newvalue) {
		trace(TRACE_ERROR, "%s,%s: no new value specified",
				__FILE__,__func__);
		return -1;
	}
		
	if (! (_ldap_dn = dm_ldap_user_getdn(user_idnr)))
		return -1;

	newvalues[0] = (char *)newvalue;
	newvalues[1] = NULL;

	modField.mod_op = LDAP_MOD_REPLACE;
	modField.mod_type = (char *)fieldname;
	modField.mod_values = newvalues;

	mods[0] = &modField;
	mods[1] = NULL;

	_ldap_err = ldap_modify_s(_ldap_conn, _ldap_dn, mods);
	if (_ldap_err) {
		trace(TRACE_ERROR,"%s,%s: error changing field [%s] to value [%s]: %s",
				__FILE__, __func__,
				fieldname, newvalue,
				ldap_err2string(_ldap_err));
		ldap_memfree(_ldap_dn);
		return -1;
	}
	ldap_memfree(_ldap_dn);
	return 0;
}


/*  OLD-SCHOOL:
 *
 * Each node of retlist contains a data field
 * which is a pointer to another list, "fieldlist".
 *
 * Each node of fieldlist contains a data field
 * which is a pointer to another list, "datalist".
 *
 * Each node of datalist contains a data field
 * which is a (char *) pointer to some actual data.
 *
 * Here's a visualization:
 *
 * retlist
 *  has the "rows" that matched
 *   {
 *     (struct dm_list *)data
 *       has the fields you requested
 *       {
 *         (struct dm_list *)data
 *           has the values for the field
 *           {
 *             (char *)data
 *             (char *)data
 *             (char *)data
 *           }
 *       }
 *   }
 *
 *  TODO: GLIB-STYLIE:
 *
 *  ghashtable *ldap_entities
 *  {
 *    gchar *dn;
 *    ghashtable *ldap_attributes {
 *      gchar *attribute;
 *      glist *values;
 *    }
 *  }
 *  
 */


/* returns the number of matches found */
static GList * __auth_get_every_match(const char *q, char **retfields)
{
	LDAPMessage *ldap_msg;
	int ldap_err;
	char **ldap_vals = NULL;
	char *dn;
	int j = 0, k = 0, m = 0;
	GList *attlist,*fldlist,*entlist;
	
	attlist = fldlist = entlist = NULL;

	if (auth_search(q))
		return NULL;

	if ((j = ldap_count_entries(_ldap_conn, _ldap_res)) < 1) {
		trace(TRACE_DEBUG, "%s,%s: nothing found",__FILE__,__func__);
		if (_ldap_res)
			ldap_msgfree(_ldap_res);
		return NULL;
	}

	/* do the first entry here */
	if ((ldap_msg = ldap_first_entry(_ldap_conn, _ldap_res)) == NULL) {
		ldap_get_option(_ldap_conn, LDAP_OPT_ERROR_NUMBER, &_ldap_err);
		trace(TRACE_ERROR, "%s,%s: ldap_first_entry failed: [%s]",__FILE__,__func__,
		      ldap_err2string(_ldap_err));
		if (_ldap_res)
			ldap_msgfree(_ldap_res);
		return NULL;
	}

	while (ldap_msg) {
		
		dn = ldap_get_dn(_ldap_conn, ldap_msg);
		trace(TRACE_DEBUG,"%s,%s: scan results for DN: [%s]", __FILE__, __func__, dn);
		
		for (k = 0; retfields[k] != NULL; k++) {
			trace(TRACE_DEBUG,"%s,%s: ldap_get_values [%s]",
					__FILE__, __func__, retfields[k]);
			if (! (ldap_vals = ldap_get_values(_ldap_conn, ldap_msg, retfields[k]))) {
				ldap_get_option(_ldap_conn, LDAP_OPT_ERROR_NUMBER, &ldap_err);
				trace(TRACE_WARNING, "%s,%s: ldap_get_values failed: [%s] %s",
						__FILE__,__func__, 
						retfields[k], 
						ldap_err2string(ldap_err));
			} else {
				m = 0;
				while (ldap_vals[m]) { 
					trace(TRACE_DEBUG,"%s,%s: got value [%s]\n", 
							__FILE__, __func__,
							ldap_vals[m]);
					attlist = g_list_append(attlist,g_strdup(ldap_vals[m]));
					m++;
				}
			}
			fldlist = g_list_append(fldlist, attlist);
			attlist = NULL;
			
			ldap_value_free(ldap_vals);
		}
		entlist = g_list_append(entlist, fldlist);
		fldlist = NULL;
		
		ldap_memfree(dn);
		
		ldap_msg = ldap_next_entry(_ldap_conn, ldap_msg);
	}

	if (_ldap_res)
		ldap_msgfree(_ldap_res);
	if (ldap_msg)
		ldap_msgfree(ldap_msg);

	return entlist;
}

static char *__auth_get_first_match(const char *q, char **retfields)
{
	LDAPMessage *ldap_msg;
	char *returnid = NULL;
	char *ldap_dn = NULL;
	char **ldap_vals = NULL;
	int k = 0;

	if (auth_search(q))
		return NULL;
	
	if (ldap_count_entries(_ldap_conn, _ldap_res) < 1) {
		trace(TRACE_DEBUG, "%s,%s: none found",__FILE__,__func__);
		goto endfree;
	}

	ldap_msg = ldap_first_entry(_ldap_conn, _ldap_res);
	if (ldap_msg == NULL) {
		ldap_get_option(_ldap_conn, LDAP_OPT_ERROR_NUMBER, &_ldap_err);
		trace(TRACE_ERROR, "%s,%s: ldap_first_entry failed: %s",__FILE__,__func__,
		      ldap_err2string(_ldap_err));
		goto endfree;
	}
	
	for (k = 0; retfields[k] != NULL; k++) {
		if (0 == strcasecmp(retfields[k], "dn")) {
			ldap_dn = ldap_get_dn(_ldap_conn, ldap_msg);
			if (ldap_dn)
				returnid = g_strdup(ldap_dn);
			break;
		} else {
			ldap_vals = ldap_get_values(_ldap_conn, ldap_msg, retfields[k]);
			if (ldap_vals) 
				returnid = g_strdup(ldap_vals[0]);
			break;
		}
	}
	
      endfree:
	if (ldap_dn)
		ldap_memfree(ldap_dn);
	if (ldap_vals)
		ldap_value_free(ldap_vals);
	if (_ldap_res)
		ldap_msgfree(_ldap_res);

	trace(TRACE_DEBUG,"%s,%s: returnid [%s]", __FILE__,__func__,returnid);
	return returnid;
}


int auth_user_exists(const char *username, u64_t * user_idnr)
{
	char *id_char;
	char query[AUTH_QUERY_SIZE];
	char *fields[] = { _ldap_cfg.field_nid, NULL };

	assert(user_idnr != NULL);
	*user_idnr = 0;

	if (!username) {
		trace(TRACE_ERROR, "%s,%s: got NULL as username",
				__FILE__,__func__);
		return 0;
	}

	/* fall back to db-user for DBMAIL_DELIVERY_USERNAME */
	if (strcmp(username,DBMAIL_DELIVERY_USERNAME)==0)
		return db_user_exists(DBMAIL_DELIVERY_USERNAME, user_idnr);
	
	snprintf(query, AUTH_QUERY_SIZE, "(%s=%s)", _ldap_cfg.field_uid,
		 username);
	
	id_char = __auth_get_first_match(query, fields);
	*user_idnr = (id_char) ? strtoull(id_char, NULL, 0) : 0;
	if (id_char != NULL)
		g_free(id_char);

	trace(TRACE_DEBUG, "%s,%s: returned value is [%llu]",__FILE__,__func__,
	      *user_idnr);

	if (*user_idnr != 0)
		return 1;

	return 0;
}

/* Given a useridnr, find the account/login name
 * return 0 if not found, NULL on error
 */
char *auth_get_userid(u64_t user_idnr)
{
	char *returnid = NULL;
	char query[AUTH_QUERY_SIZE];
	char *fields[] = { _ldap_cfg.field_uid, NULL };
	
	snprintf(query, AUTH_QUERY_SIZE, "(%s=%llu)", _ldap_cfg.field_nid, user_idnr);
	returnid = __auth_get_first_match(query, fields);
	trace(TRACE_DEBUG, "%s,%s: returned value is [%s]",__FILE__,__func__,
	      returnid);

	return returnid;
}

/* We'd like to have -1 return on failure, but
 * the internal ldap api here won't tell us. */
int auth_check_userid(u64_t user_idnr)
{
	char *returnid = NULL;
	char query[AUTH_QUERY_SIZE];
	char *fields[] = { _ldap_cfg.field_nid, NULL };
	int ret;
	
	snprintf(query, AUTH_QUERY_SIZE, "(%s=%llu)", _ldap_cfg.field_nid, user_idnr);
	returnid = __auth_get_first_match(query, fields);

	if (returnid) {
		ret = 0;
		trace(TRACE_DEBUG, "%s,%s: found user_idnr [%llu]",
			__FILE__, __func__, user_idnr);
	} else {
		ret = 1;
		trace(TRACE_DEBUG, "%s,%s: didn't find user_idnr [%llu]",
			__FILE__, __func__, user_idnr);
	}

	dm_free(returnid);

	return ret;
}


/*
 * Get the Client ID number
 * Return 0 on successful failure
 * Return -1 on really big failures
 */
int auth_getclientid(u64_t user_idnr, u64_t * client_idnr)
{
	char *cid_char = NULL;
	char query[AUTH_QUERY_SIZE];
	char *fields[] = { _ldap_cfg.field_cid, NULL };

	assert(client_idnr != NULL);
	*client_idnr = 0;

	if (!user_idnr) {
		trace(TRACE_ERROR,
		      "%s,%s: got NULL as useridnr",__FILE__,__func__);
		return -1;
	}

	snprintf(query, AUTH_QUERY_SIZE, "(%s=%llu)", _ldap_cfg.field_nid,
		 user_idnr);
	cid_char = __auth_get_first_match(query, fields);
	*client_idnr = (cid_char) ? strtoull(cid_char, NULL, 0) : 0;
	if (cid_char != NULL)
		g_free(cid_char);

	trace(TRACE_DEBUG, "%s,%s: found client_idnr [%llu]",__FILE__,__func__,
	      *client_idnr);

	return 1;
}


int auth_getmaxmailsize(u64_t user_idnr, u64_t * maxmail_size)
{
	char *max_char;
	char query[AUTH_QUERY_SIZE];
	char *fields[] = { _ldap_cfg.field_maxmail, NULL };

	assert(maxmail_size != NULL);
	*maxmail_size = 0;

	if (!user_idnr) {
		trace(TRACE_ERROR,
		      "%s,%s: got NULL as useridnr",__FILE__,__func__);
		return 0;
	}

	snprintf(query, AUTH_QUERY_SIZE, "(%s=%llu)", _ldap_cfg.field_nid,
		 user_idnr);
	max_char = __auth_get_first_match(query, fields);
	*maxmail_size = (max_char) ? strtoull(max_char, 0, 10) : 0;
	if (max_char != NULL)
		g_free(max_char);

	trace(TRACE_DEBUG,
	      "%s,%s: returned value is [%llu]",__FILE__,__func__,
	      *maxmail_size);

	return 1;
}


/*
 * auth_getencryption()
 *
 * returns a string describing the encryption used for the passwd storage
 * for this user.
 * The string is valid until the next function call; in absence of any 
 * encryption the string will be empty (not null).
 *
 * If the specified user does not exist an empty string will be returned.
 */
char *auth_getencryption(u64_t user_idnr UNUSED)
{
	/* ldap does not support fancy passwords, but return 
	 * something valid for the sql shadow */
	return "md5";
}
		


/* Fills the users list with all existing users
 * return -2 on mem error, -1 on db-error, 0 on success */
GList * auth_get_known_users(void)
{
	char *query;
	char *fields[] = { _ldap_cfg.field_uid, NULL };
	GList *users;
	GList *entlist;
	
	GString *t = g_string_new(_ldap_cfg.user_objectclass);
	GList *l = g_string_split(t,",");
	g_string_free(t,TRUE);
	
	query =  dm_ldap_get_filter('&',"objectClass",l);
	entlist = __auth_get_every_match(query, fields);
	g_free(query);
	
	trace(TRACE_INFO, "%s,%s: found %d users",
			__FILE__,__func__, 
			g_list_length(entlist));

	users = dm_ldap_entdm_list_get_values(entlist);
	
	dm_ldap_freeresult(entlist);
	return users;
}

/*
 * auth_check_user_ext()
 * 
 * As auth_check_user() but adds the numeric ID of the user found
 * to userids or the forward to the fwds.
 * 
 * returns the number of occurences. 
 */


	
int auth_check_user_ext(const char *address, struct dm_list *userids,
			struct dm_list *fwds, int checks)
{
	int occurences = 0;
	u64_t id;
	char *endptr = NULL;
	char query[AUTH_QUERY_SIZE];
	char *fields[] = { _ldap_cfg.field_nid, _ldap_cfg.field_fwdtarget, NULL };
	unsigned c2;
	char *attrvalue;
	GList *entlist, *fldlist, *attlist;

	if (checks > 20) {
		trace(TRACE_ERROR, "%s,%s: too many checks. Possible loop detected.",
				__FILE__, __func__);
		return 0;
	}

	trace(TRACE_DEBUG,
	      "%s,%s: checking user [%s] in alias table",__FILE__,__func__,
	      address);

	/* This is my private line for sending a DN rather than a search */
	snprintf(query, AUTH_QUERY_SIZE, "(%s=%s)", _ldap_cfg.field_mail, address);
	
	trace(TRACE_DEBUG, "%s,%s: searching with query [%s], checks [%d]",
			__FILE__,__func__, query, checks);

	entlist = __auth_get_every_match(query, fields);

	if (g_list_length(entlist) < 1) {
		if (checks > 0) {
			/* found the last one, this is the deliver to
			 * but checks needs to be bigger then 0 because
			 * else it could be the first query failure */

			id = strtoull(address, &endptr, 10);
			if (*endptr == 0) {
				/* numeric deliver-to --> this is a userid */
				trace(TRACE_DEBUG, "%s,%s: adding [%llu] to userids",__FILE__,__func__, id);
				dm_list_nodeadd(userids, &id, sizeof(id));
			} else {
				trace(TRACE_DEBUG, "%s,%s: adding [%s] to forwards",__FILE__,__func__, address);
				dm_list_nodeadd(fwds, address, strlen(address) + 1);
			}
			return 1;
		} else {
			trace(TRACE_DEBUG, "%s,%s: user [%s] not in aliases table",__FILE__,__func__, address);
			dm_ldap_freeresult(entlist);
			return 0;
		}
	}

	trace(TRACE_DEBUG, "%s,%s: into checking loop",__FILE__,__func__);
	entlist = g_list_first(entlist);
	while (entlist) {
		fldlist = g_list_first(entlist->data);
		c2 = 0;
		while(fldlist) {
			attlist = g_list_first(fldlist->data);
			while(attlist) {
				attrvalue = (char *)attlist->data;
				
				occurences += auth_check_user_ext(attrvalue, userids, fwds, checks+1);
				
				if (! g_list_next(attlist))
					break;
				attlist = g_list_next(attlist);
			}
			if (! g_list_next(fldlist))
				break;
			fldlist = g_list_next(fldlist);
			c2++;
		}
		if (! g_list_next(entlist))
			break;
		entlist = g_list_next(entlist);
	}
	dm_ldap_freeresult(entlist);
	return occurences;
}


/* 
 * auth_adduser()
 *
 * adds a new user to the database 
 * and adds a INBOX.. 
 * returns a 1 on succes, -1 on failure 
 */
int auth_adduser(const char *username, const char *password, 
		 const char *enctype UNUSED, u64_t clientid, 
		 u64_t maxmail, u64_t * user_idnr)
{
	int i, j, result;
	/*int ret; unused variable */
	int NUM_MODS = 9;
	GString *nid = g_string_new("");
	GString *cid = g_string_new("");
	GString *maxm = g_string_new("");
	
	u64_t newidnr = dm_ldap_get_freeid(_ldap_cfg.field_nid);

	
	g_string_printf(nid,"%llu", newidnr);
	g_string_printf(cid,"%llu",clientid);
	g_string_printf(maxm,"%llu",maxmail);
	
	char **obj_values = g_strsplit(_ldap_cfg.user_objectclass,",",0);
	char *pw_values[] = { (char *)password, NULL };
	char *uid_values[] = { (char *)username, NULL };
	char *nid_values[] = { nid->str, NULL };
	char *cid_values[] = { cid->str, NULL };
	char *max_values[] = { maxm->str, NULL };
	
	field_t mail_type = "mail";
	field_t obj_type = "objectClass";
	
	GString *t=g_string_new("");
	
	assert(user_idnr != NULL);
	*user_idnr = 0;

	/* Construct the array of LDAPMod structures representing the attributes */ 
	if (! (_ldap_mod = (LDAPMod **) dm_malloc((NUM_MODS + 1) * sizeof(LDAPMod *)))) {
		trace(TRACE_ERROR, "%s,%s: Cannot allocate memory for mods array", 
				__FILE__, __func__);
		return -1;
	}

	for (i = 0; i < NUM_MODS; i++) {
		if (! (_ldap_mod[i] = (LDAPMod *) dm_malloc(sizeof(LDAPMod)))) {
			trace(TRACE_ERROR, "%s,%s: Cannot allocate memory for mods element %d",
					__FILE__, __func__, i);
			for (j = 0; j < (i - 1); j++)
				dm_free(_ldap_mod[j]);
			dm_free(_ldap_mod);
			return -1;
		}
	}

	g_string_printf(t,"%s=%s,%s", _ldap_cfg.cn_string, username, _ldap_cfg.base_dn);
	_ldap_dn=g_strdup(t->str);
	g_string_free(t,TRUE);
	
	trace(TRACE_DEBUG, "%s,%s: Adding user with DN of [%s]", __FILE__, __func__, _ldap_dn);
	
	i = 0;
	_ldap_mod[i]->mod_op = LDAP_MOD_ADD;
	_ldap_mod[i]->mod_type = obj_type;
	_ldap_mod[i]->mod_values = obj_values;
	
	if (strlen(_ldap_cfg.field_passwd) > 0) {
		i++;
		_ldap_mod[i]->mod_op = LDAP_MOD_ADD;
		_ldap_mod[i]->mod_type = _ldap_cfg.field_passwd;
		_ldap_mod[i]->mod_values = pw_values;
	}

	i++;
	_ldap_mod[i]->mod_op = LDAP_MOD_ADD;
	_ldap_mod[i]->mod_type = mail_type;
	_ldap_mod[i]->mod_values = uid_values;

	i++;
	_ldap_mod[i]->mod_op = LDAP_MOD_ADD;
	_ldap_mod[i]->mod_type = _ldap_cfg.field_uid;
	_ldap_mod[i]->mod_values = uid_values;
	
	i++;
	_ldap_mod[i]->mod_op = LDAP_MOD_ADD;
	_ldap_mod[i]->mod_type = _ldap_cfg.field_cid;
	_ldap_mod[i]->mod_values = cid_values;

	i++;
	_ldap_mod[i]->mod_op = LDAP_MOD_ADD;
	_ldap_mod[i]->mod_type = _ldap_cfg.field_maxmail;
	_ldap_mod[i]->mod_values = max_values;

	i++;
	_ldap_mod[i]->mod_op = LDAP_MOD_ADD;
	_ldap_mod[i]->mod_type = _ldap_cfg.field_nid;
	_ldap_mod[i]->mod_values = nid_values;

	i++;
	_ldap_mod[i] = NULL;

	_ldap_err = ldap_add_s(_ldap_conn, _ldap_dn, _ldap_mod);

	g_strfreev(obj_values);
	for (i = 0; i < NUM_MODS; i++)
		dm_free(_ldap_mod[i]);
	dm_free(_ldap_mod);
	ldap_memfree(_ldap_dn);

	if (_ldap_err) {
		trace(TRACE_ERROR, "%s,%s: could not add user: %s",
				__FILE__,__func__, 
				ldap_err2string(_ldap_err));
		return -1;
	}

	*user_idnr = newidnr;
	result = db_user_create_shadow(username, user_idnr);
	
	if (result != 1) {
		trace(TRACE_ERROR, "%s,%s: sql shadow account creation failed",
				__FILE__, __func__);
		auth_delete_user(username);
		*user_idnr=0;
		return result;
	}
	
	
	return 1;
}


int auth_delete_user(const char *username)
{
	/* look up who's got that username, get their dn, and delete it! */
	if (!username) {
		trace(TRACE_ERROR, "%s,%s: got NULL as useridnr",__FILE__,__func__);
		return 0;
	}

	snprintf(_ldap_query, AUTH_QUERY_SIZE, "(%s=%s)", _ldap_cfg.field_uid, username);

	if (auth_search(_ldap_query))
		return -1;
	
	if (ldap_count_entries(_ldap_conn, _ldap_res) < 1) {
		trace(TRACE_DEBUG, "%s,%s: no entries found",__FILE__,__func__);
		ldap_msgfree(_ldap_res);
		return 0;
	}

	_ldap_msg = ldap_first_entry(_ldap_conn, _ldap_res);
	if (_ldap_msg == NULL) {
		ldap_get_option(_ldap_conn, LDAP_OPT_ERROR_NUMBER, &_ldap_err);
		trace(TRACE_ERROR, "%s,%s: ldap_first_entry failed: %s",
				__FILE__,__func__, ldap_err2string(_ldap_err));
		ldap_msgfree(_ldap_res);
		return -1;
	}

	_ldap_dn = ldap_get_dn(_ldap_conn, _ldap_msg);

	if (_ldap_dn) {
		trace(TRACE_DEBUG,
		      "%s,%s: deleting user at dn [%s]",__FILE__,__func__,
		      _ldap_dn);
		_ldap_err = ldap_delete_s(_ldap_conn, _ldap_dn);
		if (_ldap_err) {
			trace(TRACE_ERROR,
			      "%s,%s: could not delete dn: %s",__FILE__,__func__,
			      ldap_err2string(_ldap_err));
			ldap_memfree(_ldap_dn);
			ldap_msgfree(_ldap_res);
			return -1;
		}
	}

	ldap_memfree(_ldap_dn);
	ldap_msgfree(_ldap_res);
	
	if (db_user_delete(username)) {
		trace(TRACE_ERROR, "%s,%s: sql shadow account deletion failed",
				__FILE__, __func__);
	}
	
	return 0;
}

static int dm_ldap_user_shadow_rename(u64_t user_idnr, const char *new_name)
{
	char *oldname;
	u64_t dbidnr;
	oldname = auth_get_userid(user_idnr);
	db_user_exists(oldname,&dbidnr);
	if (dbidnr) {
		trace(TRACE_DEBUG, "%s,%s: call db_user_rename ([%llu],[%s])\n", 
				__FILE__, __func__, 
				dbidnr, new_name);
	}
	if ((! dbidnr) || (db_user_rename(dbidnr, new_name))) {
		trace(TRACE_ERROR, "%s,%s: renaming shadow account in db failed for [%llu]->[%s]",
				__FILE__, __func__, user_idnr, new_name);
		return -1;
	}
	return 0;
}

int auth_change_username(u64_t user_idnr, const char *new_name)
{
	GString *newrdn;
	
	if (!user_idnr) {
		trace(TRACE_ERROR, "%s,%s: got NULL as useridnr",
				__FILE__,__func__);
		return -1;
	}

	if (!new_name) {
		trace(TRACE_ERROR, "%s,%s: got NULL as new_name",
				__FILE__,__func__);
		return -1;
	}
	
	if (! (_ldap_dn = dm_ldap_user_getdn(user_idnr)))
		return -1;
	
	trace(TRACE_DEBUG, "%s,%s: got DN [%s]",
			__FILE__,__func__, 
			_ldap_dn);

	
	db_begin_transaction();
	dm_ldap_user_shadow_rename(user_idnr, new_name);
	
	/* perhaps we have to rename the dn */
	if (strcmp(_ldap_cfg.field_uid,_ldap_cfg.cn_string)==0) {
		newrdn = g_string_new("");
		g_string_printf(newrdn,"%s=%s", _ldap_cfg.cn_string,new_name);
		
		_ldap_err = ldap_modrdn_s(_ldap_conn, _ldap_dn, newrdn->str);
		
		ldap_memfree(_ldap_dn);
		g_string_free(newrdn,TRUE);
		
		if (_ldap_err) {
			trace(TRACE_ERROR, "%s,%s: error calling ldap_modrdn_s [%s]",
				__FILE__,__func__,
				ldap_err2string(_ldap_err));
			db_rollback_transaction();
			return -1;
		}
		db_commit_transaction();
		return 0;
	}
	/* else we need to modify an attribute */
	
	ldap_memfree(_ldap_dn);
	
	if (dm_ldap_mod_field(user_idnr, _ldap_cfg.field_uid, new_name)) {
		db_rollback_transaction();
		return -1;
	}
	db_commit_transaction();
	
	return 0;
	
}

int auth_change_password(u64_t user_idnr, const char *new_pass, const char *enctype UNUSED)
{
	return dm_ldap_mod_field(user_idnr, _ldap_cfg.field_passwd, new_pass);
}


int auth_change_clientid(u64_t user_idnr, u64_t newcid)
{
	char newcid_str[16];
	snprintf(newcid_str, 16, "%llu", newcid);
	return dm_ldap_mod_field(user_idnr, _ldap_cfg.field_cid, newcid_str);
}

int auth_change_mailboxsize(u64_t user_idnr, u64_t new_size)
{
	int result;
	char newsize_str[16];
	snprintf(newsize_str, 16, "%llu", new_size);
	if ((result = db_change_mailboxsize(user_idnr, new_size)))
		return result;
	return dm_ldap_mod_field(user_idnr, _ldap_cfg.field_maxmail, newsize_str);
}


/* 
 * auth_validate()
 *
 * tries to validate user 'user'
 *
 * returns useridnr on OK, 0 on validation failed, -1 on error 
 */
int auth_validate(clientinfo_t *ci, char *username, char *password, u64_t * user_idnr)
{
	timestring_t timestring;
	char real_username[DM_USERNAME_LEN];
	int result;
	u64_t mailbox_idnr;
	int ldap_err;
	char *ldap_dn = NULL;

	assert(user_idnr != NULL);
	*user_idnr = 0;

	if (username == NULL || password == NULL) {
		trace(TRACE_DEBUG, "%s,%s: username or password is NULL",__FILE__,__func__);
		return 0;
	}

	memset(real_username,'\0', sizeof(real_username));
	
	create_current_timestring(&timestring);
	
	strncpy(real_username, username, DM_USERNAME_LEN);
	if (db_use_usermap()) {  /* use usermap */
		result = db_usermap_resolve(ci, username, real_username);
		if (result == DM_EGENERAL)
			return 0;
		if (result == DM_EQUERY)
			return DM_EQUERY;
	}

	if (auth_user_exists(real_username, user_idnr) == -1) {
		return -1;
	}
	
	if (! (ldap_dn = dm_ldap_user_getdn(*user_idnr))) {
		trace(TRACE_ERROR,"%s,%s: unable to determine DN for user",
				__FILE__, __func__);
		return -1;
	}

	/* now, try to rebind as the given DN using the supplied password */
	trace(TRACE_DEBUG, "%s,%s: rebinding as [%s] to validate password",
			__FILE__,__func__, 
			ldap_dn);

	ldap_err = ldap_bind_s(_ldap_conn, ldap_dn, password, LDAP_AUTH_SIMPLE);

	if (ldap_err) {
		trace(TRACE_ERROR, "%s,%s: ldap_bind_s failed: %s",
				__FILE__,__func__,
				ldap_err2string(ldap_err));
		*user_idnr = 0;
	} else {

		/* FIXME: implement this in LDAP...  log login in the database
		   snprintf(__auth_query_data, AUTH_QUERY_SIZE, "UPDATE users SET last_login = '%s' "
		   "WHERE user_idnr = '%llu'", timestring, id);

		   if (__auth_query(__auth_query_data)==-1)
		   trace(TRACE_ERROR, "%s,%s: could not update user login time",__FILE__,__func__);
		 */
	}
	
	/* rebind as admin */
	auth_ldap_bind();
	
	if (ldap_dn)
		ldap_memfree(ldap_dn);

	if (*user_idnr == 0)
		return 0;
	
	db_find_create_mailbox("INBOX", BOX_DEFAULT, *user_idnr, &mailbox_idnr);

	return 1;
}

/* returns useridnr on OK, 0 on validation failed, -1 on error */
u64_t auth_md5_validate(clientinfo_t *ci UNUSED, char *username UNUSED,
			unsigned char *md5_apop_he UNUSED,
			char *apop_stamp UNUSED)
{

	return -1;
}

/**
 * \brief get user ids belonging to a client id
 * \param client_id 
 * \param user_ids
 * \param num_users
 * \return 
 *      - -2 on memory error
 *      - -1 on database error
 *      -  1 on success
 */
int auth_get_users_from_clientid(u64_t client_id UNUSED, 
			       /*@out@*/ u64_t ** user_ids UNUSED,
			       /*@out@*/ unsigned *num_users UNUSED)
{

	return 1;
}

/**
 * \brief get a list of aliases associated with a user's user_idnr
 * \param user_idnr idnr of user
 * \param aliases list of aliases
 * \return
 * 		- -2 on memory failure
 * 		- -1 on database failure
 * 		- 0 on success
 * \attention aliases list needs to be empty. Method calls dm_list_init()
 *            which sets list->start to NULL.
 */



GList * auth_get_user_aliases(u64_t user_idnr)
{
	char *fields[] = { _ldap_cfg.field_mail, NULL };
	GString *t = g_string_new("");
	GList *aliases = NULL;
	GList *entlist, *fldlist, *attlist;
	
	g_string_printf(t,"%s=%llu", _ldap_cfg.field_nid, user_idnr);
	if ((entlist = __auth_get_every_match(t->str, fields))) {
		entlist = g_list_first(entlist);
		fldlist = g_list_first(entlist->data);
		attlist = g_list_first(fldlist->data);
		while (attlist) {
			aliases = g_list_append(aliases, g_strdup(attlist->data));
			if (! g_list_next(attlist))
				break;
			attlist = g_list_next(attlist);
		}
		dm_ldap_freeresult(entlist);
	}
	g_string_free(t,TRUE);
	return aliases;
}

/**
 * \brief get a list of aliases associated with a user's user_idnr
 * \param user_idnr idnr of user
 * \param aliases list of aliases
 * \return
 * 		- -2 on memory failure
 * 		- -1 on database failure
 * 		- 0 on success
 * \attention aliases list needs to be empty. Method calls dm_list_init()
 *            which sets list->start to NULL.
 */

GList * auth_get_aliases_ext(const char *alias)
{
	char *fields[] = { _ldap_cfg.field_fwdtarget, NULL };
	GString *t = g_string_new("");
	GList *aliases = NULL;
	GList *entlist, *fldlist, *attlist;
	
	g_string_printf(t,"%s=%s", _ldap_cfg.field_mail, alias);
	if ((entlist = __auth_get_every_match(t->str, fields))) {
		entlist = g_list_first(entlist);
		fldlist = g_list_first(entlist->data);
		attlist = g_list_first(fldlist->data);
		while (attlist) {
			aliases = g_list_append(aliases, g_strdup(attlist->data));
			if (! g_list_next(attlist))
				break;
			attlist = g_list_next(attlist);
		}
		dm_ldap_freeresult(entlist);
	}
	g_string_free(t,TRUE);
	return aliases;
}


/**
 * \brief add an alias for a user
 * \param user_idnr user's id
 * \param alias new alias
 * \param clientid client id
 * \return 
 *        - -1 on failure
 *        -  0 on success
 *        -  1 if alias already exists for given user
 */
int auth_addalias(u64_t user_idnr, const char *alias, u64_t clientid UNUSED)
{
	char *userid = NULL;
	char **mailValues = NULL;
	LDAPMod *modify[2], addMail;
	GList *aliases;

	if (! (userid = auth_get_userid(user_idnr)))
		return -1;
	
	/* check the alias newval against the known aliases for this user */
	aliases = auth_get_user_aliases(user_idnr);
	aliases = g_list_first(aliases);
	while (aliases) {
		if (strcmp(alias,(char *)aliases->data)==0) {
			g_list_foreach(aliases,(GFunc)g_free,NULL);
			g_list_free(aliases);
			return 1;
		}
		if (! g_list_next(aliases))
			break;
		aliases = g_list_next(aliases);
	}
	g_list_foreach(aliases,(GFunc)g_free,NULL);
	g_list_free(aliases);

	/* get the DN for this user */
	if (! (_ldap_dn = dm_ldap_user_getdn(user_idnr)))
		return -1;

	/* construct and apply the changes */
	mailValues = g_strsplit(alias,",",1);
	
	addMail.mod_op = LDAP_MOD_ADD;
	addMail.mod_type = _ldap_cfg.field_mail;
	addMail.mod_values = mailValues;
	
	modify[0] = &addMail;
	modify[1] = NULL;
			
	
	_ldap_err = ldap_modify_s(_ldap_conn, _ldap_dn, modify);
	
	g_strfreev(mailValues);
	ldap_memfree(_ldap_dn);
	
	if (_ldap_err) {
		trace(TRACE_ERROR, "%s,%s: update failed: %s",
				__FILE__,__func__, 
				ldap_err2string(_ldap_err));
		return -1;
	}
	
	return 0;
}


/**
 * \brief add an alias to deliver to an extern address
 * \param alias the alias
 * \param deliver_to extern address to deliver to
 * \param clientid client idnr
 * \return 
 *        - -1 on failure
 *        - 0 on success
 *        - 1 if deliver_to already exists for given alias
 */

/* find forwarding alias
 * \return
 * 	- -1 error
 * 	-  0 success
 * 	-  1 dn exists but no such deliver_to
 */

static int forward_exists(const char *alias, const char *deliver_to)
{
	char *objectfilter, *dn;
	char *fields[] = { "dn", _ldap_cfg.field_fwdtarget, NULL };
	int result = 0;
	
	GString *t = g_string_new(_ldap_cfg.forw_objectclass);
	GList *l = g_string_split(t,",");
	
	objectfilter = dm_ldap_get_filter('&',"objectClass", l);
	
	g_string_printf(t,"(&%s(%s=%s)(%s=%s))", objectfilter, _ldap_cfg.cn_string, alias, _ldap_cfg.field_fwdtarget, deliver_to);
	dn = __auth_get_first_match(t->str, fields);
	
	if (! dn) {
		result = -1; // assume total failure;
		
		g_string_printf(t,"(&%s(%s=%s))", objectfilter, _ldap_cfg.cn_string, alias);
		dn = __auth_get_first_match(t->str, fields);
		if (dn)
			result = 1; // dn does exist, just this forward is missing
		
	}
		
	
	g_free(objectfilter);
	dm_free(dn);
	g_string_free(t,TRUE);
	g_list_foreach(l,(GFunc)g_free,NULL);
	
	trace(TRACE_DEBUG, "%s,%s: result [%d]", __FILE__, __func__, result);

	return result;
}

static int forward_create(const char *alias, const char *deliver_to)
{
	LDAPMod *mods[5], objectClass, cnField, mailField, forwField;
	
	char **obj_values = g_strsplit(_ldap_cfg.forw_objectclass,",",0);
	char *cn_values[] = { (char *)alias, NULL };
	char *mail_values[] = { (char *)alias, NULL };
	char *forw_values[] = { (char *)deliver_to, NULL };
	
	GString *t=g_string_new("");
	
	g_string_printf(t,"%s=%s,%s", _ldap_cfg.cn_string, alias, _ldap_cfg.base_dn);
	_ldap_dn=g_strdup(t->str);
	g_string_free(t,TRUE);
	
	trace(TRACE_DEBUG, "%s,%s: Adding forwardingAddress with DN of [%s]",
			__FILE__, __func__, 
			_ldap_dn);
	
	objectClass.mod_op = LDAP_MOD_ADD;
	objectClass.mod_type = "objectClass";
	objectClass.mod_values = obj_values;
	
	cnField.mod_op = LDAP_MOD_ADD;
	cnField.mod_type = _ldap_cfg.cn_string;
	cnField.mod_values = cn_values;
	
	mailField.mod_op = LDAP_MOD_ADD;
	mailField.mod_type = _ldap_cfg.field_mail;
	mailField.mod_values = mail_values;

	forwField.mod_op = LDAP_MOD_ADD;
	forwField.mod_type = _ldap_cfg.field_fwdtarget;
	forwField.mod_values = forw_values;
	
	mods[0] = &objectClass;
	mods[1] = &cnField;
	mods[2] = &mailField;
	mods[3] = &forwField;
	mods[4] = NULL;
	
	trace(TRACE_DEBUG, "%s,%s: creating new forward [%s] -> [%s]", __FILE__, __func__, alias, deliver_to);
	
	_ldap_err = ldap_add_s(_ldap_conn, _ldap_dn, mods);

	g_strfreev(obj_values);
	ldap_memfree(_ldap_dn);

	if (_ldap_err) {
		trace(TRACE_ERROR, "%s,%s: could not add forwardingAddress: %s",
				__FILE__,__func__, 
				ldap_err2string(_ldap_err));
		return -1;
	}

	return 0;

}

static int forward_add(const char *alias,const char *deliver_to) 
{
	char **mailValues = NULL;
	LDAPMod *modify[2], addForw;
	GString *t=g_string_new("");

	g_string_printf(t,"%s=%s,%s", _ldap_cfg.cn_string, alias, _ldap_cfg.base_dn);
	_ldap_dn=g_strdup(t->str);
	g_string_free(t,TRUE);

	/* construct and apply the changes */
	mailValues = g_strsplit(deliver_to,",",1);
	
	addForw.mod_op = LDAP_MOD_ADD;
	addForw.mod_type = _ldap_cfg.field_fwdtarget;
	addForw.mod_values = mailValues;
	
	modify[0] = &addForw;
	modify[1] = NULL;
			
	trace(TRACE_DEBUG, "%s,%s: creating additional forward [%s] -> [%s]", __FILE__, __func__, alias, deliver_to);
	
	_ldap_err = ldap_modify_s(_ldap_conn, _ldap_dn, modify);
	
	g_strfreev(mailValues);
	ldap_memfree(_ldap_dn);
	
	if (_ldap_err) {
		trace(TRACE_ERROR, "%s,%s: update failed: %s",
				__FILE__,__func__, 
				ldap_err2string(_ldap_err));
		return -1;
	}
	
	return 0;
}	

static int forward_delete(const char *alias, const char *deliver_to)
{
	char **mailValues = NULL;
	LDAPMod *modify[2], delForw;
	GString *t=g_string_new("");

	g_string_printf(t,"%s=%s,%s", _ldap_cfg.cn_string, alias, _ldap_cfg.base_dn);
	_ldap_dn=g_strdup(t->str);
	g_string_free(t,TRUE);

	/* construct and apply the changes */
	mailValues = g_strsplit(deliver_to,",",1);
	
	delForw.mod_op = LDAP_MOD_DELETE;
	delForw.mod_type = _ldap_cfg.field_fwdtarget;
	delForw.mod_values = mailValues;
	
	modify[0] = &delForw;
	modify[1] = NULL;
			
	trace(TRACE_DEBUG, "%s,%s: delete additional forward [%s] -> [%s]", 
			__FILE__, __func__, alias, deliver_to);
	_ldap_err = ldap_modify_s(_ldap_conn, _ldap_dn, modify);
	
	g_strfreev(mailValues);
	
	if (_ldap_err) {
		trace(TRACE_ERROR, "%s,%s: update failed: [%d] %s",
				__FILE__,__func__, _ldap_err,
				ldap_err2string(_ldap_err));
		
		trace(TRACE_DEBUG, "%s,%s: delete additional forward failed, removing dn [%s]", 
				__FILE__, __func__, _ldap_dn);

		_ldap_err = ldap_delete_s(_ldap_conn, _ldap_dn);
		if (_ldap_err) {
			trace(TRACE_ERROR, "%s,%s: deletion failed [%s]",
					__FILE__, __func__, 
					ldap_err2string(_ldap_err));
		}
	}
	
	ldap_memfree(_ldap_dn);
	return 0;

}
int auth_addalias_ext(const char *alias, const char *deliver_to, u64_t clientid UNUSED)
{
	switch(forward_exists(alias,deliver_to)) {
		case -1:
			return forward_create(alias,deliver_to);
		case 1:
			return forward_add(alias,deliver_to);
	}
	return 0;
}


/**
 * \brief remove alias for user
 * \param user_idnr user id
 * \param alias the alias
 * \return
 *         - -1 on failure
 *         - 0 on success
 */
int auth_removealias(u64_t user_idnr, const char *alias)
{
	char *userid = NULL;
	char **mailValues = NULL;
	LDAPMod *modify[2], delMail;
	GList *aliases;

	if (! (userid = auth_get_userid(user_idnr)))
		return -1;
	
	/* check the alias against the known aliases for this user */
	aliases = auth_get_user_aliases(user_idnr);
	aliases = g_list_first(aliases);
	while (aliases) {
		if (strcmp(alias,(char *)aliases->data)==0)
			break;
		
		if (! g_list_next(aliases))
			break;
		aliases = g_list_next(aliases);
		
	}
	if (!aliases) {
		trace(TRACE_DEBUG,"%s,%s: alias [%s] for user [%s] not found",
				__FILE__, __func__,
				alias, userid);
				
		g_list_foreach(aliases,(GFunc)g_free,NULL);
		g_list_free(aliases);
		return 1;
	}
	
	g_list_foreach(aliases,(GFunc)g_free,NULL);
	g_list_free(aliases);

	/* get the DN for this user */
	if (! (_ldap_dn = dm_ldap_user_getdn(user_idnr)))
		return -1;

	/* construct and apply the changes */
	mailValues = g_strsplit(alias,",",1);
	
	delMail.mod_op = LDAP_MOD_DELETE;
	delMail.mod_type = _ldap_cfg.field_mail;
	delMail.mod_values = mailValues;
	
	modify[0] = &delMail;
	modify[1] = NULL;
			
	
	_ldap_err = ldap_modify_s(_ldap_conn, _ldap_dn, modify);
	if (_ldap_err) {
		trace(TRACE_ERROR, "%s,%s: update failed: %s",
				__FILE__,__func__, 
				ldap_err2string(_ldap_err));
		g_strfreev(mailValues);
		ldap_memfree(_ldap_dn);
		return -1;
	}
	g_strfreev(mailValues);
	ldap_memfree(_ldap_dn);
	
	return 0;
}

/**
 * \brief remove external delivery address for an alias
 * \param alias the alias
 * \param deliver_to the deliver to address the alias is
 *        pointing to now
 * \return
 *        - -1 on failure
 *        - 0 on success
 */
int auth_removealias_ext(const char *alias, const char *deliver_to)
{
	
	int check;

	check = forward_exists(alias,deliver_to);
	if (check)
		return 0;
	
	return forward_delete(alias,deliver_to);
}

gboolean auth_requires_shadow_user(void)
{
	return TRUE;
}



syntax highlighted by Code2HTML, v. 0.9.1