/*
** Copyright 2006, S. Varshavchik.
**
** See COPYING for distribution information.
*/

#include "libldapsearch.h"
#include <stdlib.h>
#include <errno.h>
#include <time.h>
#if HAVE_SYS_TIME_H
#include <sys/time.h>
#endif

/*
** Allocate and deallocate the ldapsearch struct.
*/

struct ldapsearch *l_search_alloc(const char *host,
				  int port,
				  const char *userid,
				  const char *password,
				  const char *base)
{
	char *buf;

	struct ldapsearch *p=(struct ldapsearch *)
		malloc(sizeof(struct ldapsearch));

	if (!p)
		return NULL;

	if ((p->base=strdup(base)) == NULL)
	{
		free(p);
		return NULL;
	}

	if ((buf=malloc(strlen(host)+100)) == NULL)
	{
		free(p->base);
		free(p);
		return NULL;
	}

	sprintf(buf, "ldap://%s:%d", host, port);

	if (ldap_initialize(&p->handle, buf) != LDAP_SUCCESS)
	{
		free(buf);
		free(p->base);
		free(p);
		return NULL;
	}
	free(buf);

	if (userid && *userid)
	{
		struct berval cred;

		cred.bv_len=password && *password ? strlen(password):0;
		cred.bv_val=password && *password ? (char *)password:NULL;

		if (ldap_sasl_bind_s(p->handle, userid, NULL, &cred, NULL,
				     NULL, NULL))
		{
			l_search_free(p);
			errno=EPERM;
			return NULL;
		}
	}

	return p;
}

void l_search_free(struct ldapsearch *s)
{
	if (s->handle)
		ldap_unbind_ext(s->handle, NULL, NULL);
	free(s->base);
	free(s);
}

/*
** See RFC 2254 section 4.
*/

static char *encode_key(const char *lookupkey)
{
	const char *cp;

	char *p=NULL, *q;
	int pass;
	size_t l=0;

	for (pass=0; pass<2; pass++)
	{
		if (pass)
		{
			p=malloc(l);
			if (!p)
				return NULL;
		}
		l=1;
		q=p;
		for (cp=lookupkey; *cp; cp++)
		{
			const char *h;

			switch (*cp) {
			case '*':
				h="\\2a";
				break;
			case '(':
				h="\\28";
				break;
			case ')':
				h="\\29";
				break;
			case '\\':
				h="\\5c";
				break;
			default:
				if (pass)
					*q++= *cp;
				++l;
				continue;
			}

			if (pass)
				while ((*q++ = *h++) != 0)
					;
			l += 3;
		}
		if (pass)
			*q=0;
	}
	return p;
}

/*
** Insert lookup key into the search filter.
*/

static char *make_search_key(const char *filter, const char *lookupkey)
{
	size_t l=strlen(filter)+1;
	char *p, *q;
	const char *cp;

	for (cp=filter; *cp; cp++)
		if (*cp == '@')
			l += strlen(lookupkey);

	p=malloc(l);
	if (!p)
		return NULL;

	for (q=p, cp=filter; *cp; cp++)
	{
		if (*cp == '@')
		{
			const char *k=lookupkey;

			while ( (*q++ = *k++ ) != 0)
				;
			--q;
			continue;
		}
		*q++ = *cp;
	}
	*q=0;
	return p;
}

static int l_search_do_filter(struct ldapsearch *s,

			      int (*callback_func)(const char *utf8_name,
						   const char *address,
						   void *callback_arg),
			      void *callback_arg,

			      const char *filter,
			      const char *lookup_key,
			      int *found);


int l_search_do(struct ldapsearch *s,
		const char *lookupkey,

		int (*callback_func)(const char *utf8_name,
				     const char *address,
				     void *callback_arg),
		void *callback_arg)
{
	char *k;
	const char *filter;
	int rc_code;
	int found;

	k=encode_key(lookupkey);
	if (!k)
		return -1;

	filter=getenv("LDAP_SEARCH_FILTER_EXACT");
	if (!filter)
		filter="(|(uid=@)(sn=@)(cn=@))";

	rc_code=l_search_do_filter(s, callback_func, callback_arg,
				   filter, k, &found);

	if (rc_code == 0 && !found)
	{
		filter=getenv("LDAP_SEARCH_FILTER_APPROX");
		if (!filter)
			filter="(|(uid=@*)(sn=@*)(mail=@*)(cn=@*))";

		rc_code=l_search_do_filter(s, callback_func, callback_arg,
					   filter, k, &found);
	}
	free(k);
	return rc_code;
}

static int l_search_do_filter(struct ldapsearch *s,
			      int (*callback_func)(const char *utf8_name,
						   const char *address,
						   void *callback_arg),
			      void *callback_arg,
			      const char *filter,
			      const char *lookup_key,
			      int *found)
{
	char *kk;
	struct timeval tv;
	LDAPMessage *result;
	char *attrs[3];
	int rc_code=0;
	int msgidp;

	*found=0;

	kk=make_search_key(filter, lookup_key);

	if (!kk)
		return -1;

	if (s->handle == NULL)
	{
		errno=ETIMEDOUT;  /* Timeout previously */
		return -1;
	}


	attrs[0]="cn";
	attrs[1]="mail";
	attrs[2]=NULL;

	tv.tv_sec=60*60;
	tv.tv_usec=0;

	if (ldap_search_ext(s->handle, s->base, LDAP_SCOPE_SUBTREE,
			    kk, attrs, 0, NULL, NULL, &tv, 1000000, &msgidp)
	    != LDAP_SUCCESS)
		return -1;

	do
	{
		int rc;
		LDAPMessage *msg;

		const char *timeout=getenv("LDAP_SEARCH_TIMEOUT");

		tv.tv_sec=atoi(timeout ? timeout:"30");
		tv.tv_usec=0;

		rc=ldap_result(s->handle, msgidp, 0, &tv, &result);

		if (rc <= 0)
		{
			if (rc == 0)
				errno=ETIMEDOUT;

			ldap_unbind_ext(s->handle, NULL, NULL);
			s->handle=NULL;
			rc_code= -1;
			break;
		}

		if (rc == LDAP_RES_SEARCH_RESULT)
		{
			ldap_msgfree(result);
			break; /* End of search */
		}

		if (rc != LDAP_RES_SEARCH_ENTRY)
		{
			ldap_msgfree(result);
			continue;
		}

		for (msg=ldap_first_message(s->handle, result); msg;
		     msg=ldap_next_message(s->handle, msg))
		{
			struct berval **n_val=
				ldap_get_values_len(s->handle, msg, "cn");
			struct berval **a_val=
				ldap_get_values_len(s->handle, msg, "mail");

			if (n_val && a_val)
			{
				size_t i, j;

				for (i=0; n_val[i]; i++)
					for (j=0; a_val[j]; j++)
					{
						char *p=malloc(n_val[i]->bv_len
							       +1);
						char *q=malloc(a_val[j]->bv_len
							       +1);

						if (!p || !q)
						{
							if (p) free(p);
							if (q) free(q);
							rc_code= -1;
							break;
						}

						memcpy(p, n_val[i]->bv_val,
						       n_val[i]->bv_len);
						p[n_val[i]->bv_len]=0;

						memcpy(q, a_val[j]->bv_val,
						       a_val[j]->bv_len);
						q[a_val[j]->bv_len]=0;

						rc_code=(*callback_func)
							(p, q, callback_arg);
						free(p);
						free(q);
						if (rc_code)
							break;
						*found=1;
					}
			}
			if (n_val)
				ldap_value_free_len(n_val);
			if (a_val)
				ldap_value_free_len(a_val);
		}

		ldap_msgfree(result);
	} while (rc_code == 0);
	return rc_code;
}

int l_search_ping(struct ldapsearch *s)
{
	char *attrs[2];
	struct timeval tv;
	LDAPMessage *result;
	int rc;
	int msgid;

	if (s->handle == NULL)
	{
		errno=ETIMEDOUT;  /* Timeout previously */
		return -1;
	}

	attrs[0]="objectClass";
	attrs[1]=NULL;

	tv.tv_sec=60*60;
	tv.tv_usec=0;

	if (ldap_search_ext(s->handle, s->base, LDAP_SCOPE_BASE,
			    "objectClass=*", attrs, 0, NULL, NULL, &tv,
			    1000000, &msgid) < 0)
		return -1;

	do
	{
		const char *timeout=getenv("LDAP_SEARCH_TIMEOUT");

		tv.tv_sec=atoi(timeout ? timeout:"30");
		tv.tv_usec=0;

		rc=ldap_result(s->handle, msgid, 0, &tv, &result);

		if (rc <= 0)
		{
			if (rc == 0)
				errno=ETIMEDOUT;

			ldap_unbind_ext(s->handle, NULL, NULL);
			s->handle=NULL;

			return -1;
		}

		ldap_msgfree(result);
	} while (rc != LDAP_RES_SEARCH_RESULT);
	return 0;
}

#if 0

#include <stdio.h>

static int cb(const char *utf8_name,
	      const char *address,
	      void *callback_arg)
{
	printf("\"%s\" <%s>\n", utf8_name, address);
	return 0;
}

int main(int argc, char **argv)
{
	struct ldapsearch *s=l_search_alloc("localhost", 389,
					    "dc=courier-mta,dc=com");
	int n;

	if (!s)
	{
		perror("l_search_alloc");
		exit(1);
	}

	for (n=1; n<argc; n++)
	{
		if (l_search_do(s, argv[n], cb, NULL) != 0)
		{
			printf("l_search_do: error\n");
		}
	}
	l_search_free(s);
	return 0;
}
#endif
