/* $Id: ldap.cpp,v 1.15 2004/01/02 03:54:01 fesnel Exp $ */
/*******************************************************************************
 *   This program is part of a library used by the Archimedes email client     * 
 *                                                                             *
 *   Copyright : (C) 1995-1998 Gennady B. Sorokopud (gena@NetVision.net.il)    *
 *               (C) 1995 Ugen. J. S. Antsilevich (ugen@latte.worldbank.org)   *
 *               (C) 1998-2004 by the Archimedes Project                       *
 *                   http://sourceforge.net/projects/archimedes                *
 *                                                                             *
 *             --------------------------------------------                    *
 *                                                                             *
 *   This program is free software; you can redistribute it and/or modify      *
 *   it under the terms of the GNU Library 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 Library General Public License for more details.                      *
 *                                                                             *
 *   You should have received a copy of the GNU Library General Public License *
 *   along with this program; if not, write to the Free Software               *
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307, USA.  *
 *                                                                             *
 ******************************************************************************/


#include "fldap.h"

#ifdef WITH_LDAP

LDAP * ld = NULL;
int ldap_bound = 0;

/*
 * Copy name into its own storage. If name contains characters not
 * permitted by RFC822 (i.e. <>[];:,), enclose name in quotes if it
 * is not already.
 */
char *copy_and_quote_name(const char *name)
{
	int bad_char;
	int namelen;
	const char *p;
	static const char BAD_ADDR_CHARS[] = "<>[];:,";

	bad_char = FALSE;
	namelen = strlen(name);
	
	for ( p = BAD_ADDR_CHARS; *p; p++ )
	if ( strchr(name, *p) != NULL )
	{
		bad_char = TRUE;
		break;
	}

	if ( bad_char && ( name[0] != '"' || name[namelen] != '"' ) )
	{
	char *res = (char *)malloc(namelen + 2 + 1);

	if ( res != NULL )
	{
		strcpy(res, "\"");
		strcat(res, name);
		strcat(res, "\"");
	}
	else
	{
		errno = 0;
		display_msg(MSG_WARN, "malloc", "malloc failed in copy_and_quote_name");
	}

	return res;
	}
	else
	return strdup(name);
}

/*
 * Make a search filter string. We try to be a little cunning here, to
 * ensure that 'ben waine' will succeed in databases where the cn is
 * hed as 'Waine, Ben' as well as 'Ben Waine'. Split the search string into
 * tokens delimited by space or comma, and combine those into a ANDed
 * search of the cn.
 *
 * The caller needs to free() the result.
 */
char *make_filter(const char *str)
{
	int no_components;
	size_t component_len;
	size_t tot_component_len;
	const char *p;
	char *res;
	static const char DELIMS[] = " .\t";

	no_components = tot_component_len = 0;
	p = str;
	while ( ( component_len = strcspn(p, DELIMS) ) > 0 )
	{
	tot_component_len += component_len;
	p+= component_len;
	p += strspn(p, DELIMS);
	no_components++;
	}

	if ( no_components == 0 )
	return NULL;

	/*
	 * Each component is wrapped in (cn=*%s*), and if more than one
	 * the entire has to be wrapped in (&%s).
	 */
	tot_component_len += no_components * (sizeof("(cn=**)") - 1);
	if ( no_components > 1 )
	tot_component_len += sizeof("(&)") - 1;

	res = (char *) malloc(tot_component_len + 1);
	if ( res == NULL )
	{
	errno = 0;
		display_msg(MSG_WARN, "malloc", "malloc failed in make_filter");
	return NULL;
	}

	if ( no_components > 1 )
	strcpy(res, "(&");
	else
	*res = '\0';
	
	p = str;
	while ( ( component_len = strcspn(p, DELIMS) ) > 0 )
	{
	strcat(res, "(cn=*");
	strncat(res, p, component_len);
	strcat(res, "*)");
	p += component_len;
	p += strspn(p, DELIMS);
	}

	if ( no_components > 1 )
	strcat(res, ")");

	return res;
}

int init_LDAP(void) 
{
	//cerr << "init_LDAP called ";
	if (ld == NULL) {
        string sHostPort;
        string sHost;
        string sPort;

		if(!Config.exist("LDAPserver"))
            return 0;

		sHost = Config.get("LDAPserver", "");

		if(Config.exist("LDAPport")) {
		    sPort = Config.get("LDAPport", "");
            sHost.append(":" + sPort);
        }

		ld = ldap_init(sHost.c_str(), LDAP_PORT);
		if (ld == NULL) {
			//cerr << "ldap_init failure" << endl;
			display_msg(MSG_WARN, "LDAP", "Failure in ldap_init! Bad options?");
			return -1;
		}
	}

	if (!ldap_bound) {
		if (ldap_simple_bind_s(ld, NULL, NULL) != LDAP_SUCCESS) {
			//cerr << "ldap_simple_bind_s failure" << endl;
			return -1;
		}
		ldap_bound = 1;
		//cerr << "ldap_simple_bind_s: ldap is now bound" << endl;
	}
	
	return (ldap_bound);
}

void close_LDAP(void) {
	if (ld != NULL && ldap_bound) {
		ldap_unbind(ld);
		ldap_bound = 0;
		ld = NULL;
	}
}

struct _mail_addr * find_ldap_expansion(char * name) {
	struct _mail_addr * res = NULL;
    string sBase;
	char *filter;
	char *attributes[] = { "cn", "mail", NULL };
	int searchRes;
	int rc;
	LDAPMessage *msg;
	int noResults, maxResults;
	int initRes;
	LDAPMessage * entry;

	//cerr << "find_ldap_expansion: searching for " << name << endl;

	initRes = init_LDAP();
	if(initRes <= 0) {
		if (initRes < 0) {
			display_msg(MSG_WARN, "LDAP", "Can't initialise LDAP");
		}
		//cerr << "find_ldap_expansion: init_LDAP returned bad result" << endl;
		return NULL;
	}

    if(!Config.exist("LDAPbase")) {
		//cerr << "find_ldap_expansion: LDAPbase was empty" << endl;
		return NULL;
	}

	sBase = Config.get("LDAPbase", "");
	
	filter = make_filter(name);
	if ( filter == NULL ) {
		//cerr << "find_ldap_expansion: unable to create filter" << endl;
		return NULL;
	}
	
	searchRes = ldap_search(ld, sBase.c_str(), LDAP_SCOPE_SUBTREE, filter, attributes, 0);
	free(filter);
	if ( searchRes == -1 ) {
		//cerr << "find_ldap_expansion: ldap_search returned -1" << endl;
		return NULL;
	}

	noResults = 0;
	maxResults = Config.getInt("LDAPmaxResults", DEFAULT_MAX_RESULTS);

	//cerr << "find_ldap_expansion: search returned " << searchRes 
		//<< " results. Only returning " << maxResults << "." <<  endl;

	while ((noResults++ < maxResults) &&
	   ((rc = ldap_result(ld, LDAP_RES_ANY, 0, NULL, &msg)) == LDAP_RES_SEARCH_ENTRY)) {

		//cerr << "\tsearching result " << noResults << endl;

		for (entry = ldap_first_entry(ld, msg);
			entry != NULL;
			entry = ldap_next_entry(ld, entry)) {
			struct _mail_addr * ma;
			BerElement * ber;
			char * attrib;

			//cerr << "\t\tsearching entry " << noResults << endl;

			ma = (struct _mail_addr *)malloc(sizeof(struct _mail_addr));
			if (ma == NULL) {
				errno = 0;
				display_msg(MSG_WARN, "malloc", "malloc failed in find_ldap_extension");
				break;
			}

			memset(ma, 0, sizeof(struct _mail_addr));

			for (attrib = ldap_first_attribute(ld, entry, &ber);
				attrib != NULL;
				attrib = ldap_next_attribute(ld, entry, ber)) {
				char ** values;

				//cerr << "\t\t\tsearching attrib " << noResults << endl;

				values = ldap_get_values(ld, entry, attrib);
				if ( values != NULL) {
					char * last_val = values[ldap_count_values(values) - 1];

					if (strcmp(attrib, "mail") == 0)
						ma->addr = strdup(last_val);
					else if (strcmp(attrib, "cn") == 0)
						ma->name = copy_and_quote_name(last_val);
				}

				ldap_value_free(values);
			}

		  	/*
		   	* Ensure that the entry has both name and address.
		   	* Ignore the entry if it doesn't - addresses with one
		   	* or other missing will be problematic later on.
		   	*/
		  	if ( ma->name == NULL || ma->addr == NULL ) {
				//cerr << "find_ldap_expansion: ignoring problematic result" << endl;
				free(ma);
				continue;
		  	}
		  
		  	/* Got one. Add the new name to the front of the list. */
			if (res != NULL)
				ma->num = res->num + 1;

			ma->next_addr = res;
			res = ma;
			//cerr << "find_ldap_expansion: Result found!" << endl;
		}

		ldap_msgfree(msg);

	}

	if(res == NULL) {
		display_msg(MSG_WARN, "LDAP", "No entry found in LDAP Server.");
	}

	close_LDAP();

	return res;
}

#endif /* WITH_LDAP */
