Index: configure.in
===================================================================
RCS file: /cvsroot/pgsql/configure.in,v
retrieving revision 1.469
diff -c -c -r1.469 configure.in
*** configure.in	24 Jul 2006 16:32:44 -0000	1.469
--- configure.in	26 Jul 2006 16:38:39 -0000
***************
*** 1106,1111 ****
--- 1106,1119 ----
  PGAC_FUNC_GETPWUID_R_5ARG
  PGAC_FUNC_STRERROR_R_INT
  
+ # this will link libpq against libldap_r
+ if test "$with_ldap" = yes ; then
+   if test "$PORTNAME" != "win32"; then
+     AC_CHECK_LIB(ldap_r,    ldap_simple_bind, [], [AC_MSG_ERROR([library 'ldap_r' is required for LDAP])])
+     PTHREAD_LIBS="$PTHREAD_LIBS -lldap_r"
+   fi
+ fi
+ 
  CFLAGS="$_CFLAGS"
  LIBS="$_LIBS"
  
Index: doc/src/sgml/libpq.sgml
===================================================================
RCS file: /cvsroot/pgsql/doc/src/sgml/libpq.sgml,v
retrieving revision 1.213
diff -c -c -r1.213 libpq.sgml
*** doc/src/sgml/libpq.sgml	4 Jul 2006 13:22:15 -0000	1.213
--- doc/src/sgml/libpq.sgml	26 Jul 2006 16:38:41 -0000
***************
*** 4126,4131 ****
--- 4126,4197 ----
  </sect1>
  
  
+ <sect1 id="libpq-ldap">
+  <title>LDAP Lookup of Connection Parameters</title>
+ 
+ <indexterm zone="libpq-ldap">
+  <primary>LDAP connection parameter lookup</primary>
+ </indexterm>
+ 
+ <para>
+ If <application>libpq</application> has been compiled with LDAP support (option
+ <literal><option>--with-ldap</option></literal> for <command>configure</command>)
+ it is possible to retrieve connection options like <literal>host</literal>
+ or <literal>dbname</literal> via LDAP from a central server.
+ The advantage is that if the connection parameters for a database change,
+ the connection information doesn't have to be updated on all client machines.
+ </para>
+ 
+ <para>
+ LDAP connection parameter lookup uses the connection service file
+ <filename>pg_service.conf</filename> (see <xref linkend="libpq-pgservice">).
+ A line in a <filename>pg_service.conf</filename> stanza that starts with
+ <literal>ldap://</literal> will be recognized as an LDAP URL and an LDAP
+ query will be performed. The result must be a list of <literal>keyword =
+ value</literal> pairs which will be used to set connection options.
+ The URL must conform to RFC 1959 and be of the form
+ <synopsis>
+ ldap://[<replaceable>hostname</replaceable>[:<replaceable>port</replaceable>]]/<replaceable>search_base</replaceable>?<replaceable>attribute</replaceable>?<replaceable>search_scope</replaceable>?<replaceable>filter</replaceable>
+ </synopsis>
+ where <replaceable>hostname</replaceable>
+ defaults to <literal>localhost</literal> and
+ <replaceable>port</replaceable> defaults to 389.
+ </para>
+ 
+ <para>
+ Processing of <filename>pg_service.conf</filename> is terminated after
+ a successful LDAP lookup, but is continued if the LDAP server cannot be
+ contacted.  This is to provide a fallback with
+ further LDAP URL lines that point to different LDAP
+ servers, classical <literal>keyword = value</literal> pairs, or
+ default connection options.
+ If you would rather get an error message in this case, add a
+ syntactically incorrect line after the LDAP URL.
+ </para>
+ 
+ <para>
+ A sample LDAP entry that has been created with the LDIF file
+ <synopsis>
+ version:1
+ dn:cn=mydatabase,dc=mycompany,dc=com
+ changetype:add
+ objectclass:top
+ objectclass:groupOfUniqueNames
+ cn:mydatabase
+ uniqueMember:host=dbserver.mycompany.com
+ uniqueMember:port=5439
+ uniqueMember:dbname=mydb
+ uniqueMember:user=mydb_user
+ uniqueMember:sslmode=require
+ </synopsis>
+ might be queried with the following LDAP URL:
+ <synopsis>
+ ldap://ldap.mycompany.com/dc=mycompany,dc=com?uniqueMember?one?(cn=mydatabase)
+ </synopsis>
+ </para>
+ </sect1>
+ 
+ 
  <sect1 id="libpq-ssl">
  <title>SSL Support</title>
  
Index: src/interfaces/libpq/Makefile
===================================================================
RCS file: /cvsroot/pgsql/src/interfaces/libpq/Makefile,v
retrieving revision 1.146
diff -c -c -r1.146 Makefile
*** src/interfaces/libpq/Makefile	18 Jul 2006 22:18:08 -0000	1.146
--- src/interfaces/libpq/Makefile	26 Jul 2006 16:38:49 -0000
***************
*** 62,68 ****
  SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lssl -lsocket -lnsl -lresolv -lintl $(PTHREAD_LIBS), $(LIBS))
  endif
  ifeq ($(PORTNAME), win32)
! SHLIB_LINK += -lshfolder -lwsock32 -lws2_32 $(filter -leay32 -lssleay32 -lcomerr32 -lkrb5_32, $(LIBS))
  endif
  
  
--- 62,68 ----
  SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lssl -lsocket -lnsl -lresolv -lintl $(PTHREAD_LIBS), $(LIBS))
  endif
  ifeq ($(PORTNAME), win32)
! SHLIB_LINK += -lshfolder -lwsock32 -lws2_32 $(filter -leay32 -lssleay32 -lcomerr32 -lkrb5_32 -lwldap32, $(LIBS))
  endif
  
  
Index: src/interfaces/libpq/fe-connect.c
===================================================================
RCS file: /cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v
retrieving revision 1.333
diff -c -c -r1.333 fe-connect.c
*** src/interfaces/libpq/fe-connect.c	7 Jun 2006 22:24:46 -0000	1.333
--- src/interfaces/libpq/fe-connect.c	26 Jul 2006 16:38:58 -0000
***************
*** 60,65 ****
--- 60,78 ----
  #endif
  #endif
  
+ #ifdef USE_LDAP
+ #ifdef WIN32
+ #include <winldap.h>
+ #else
+ /* OpenLDAP deprecates RFC 1823, but we want standard conformance */
+ #define LDAP_DEPRECATED 1
+ #include <ldap.h>
+ typedef struct timeval LDAP_TIMEVAL;
+ #endif
+ static int ldapServiceLookup(const char *purl, PQconninfoOption *options,
+ 							 PQExpBuffer errorMessage);
+ #endif
+ 
  #include "libpq/ip.h"
  #include "mb/pg_wchar.h"
  
***************
*** 2343,2349 ****
--- 2356,2765 ----
  	return STATUS_OK;
  }
  
+ #ifdef USE_LDAP
+ 
+ #define LDAP_URL	"ldap://"
+ #define LDAP_DEF_PORT	389
+ #define PGLDAP_TIMEOUT 2
+ 
+ #define ld_is_sp_tab(x) ((x) == ' ' || (x) == '\t')
+ #define ld_is_nl_cr(x) ((x) == '\r' || (x) == '\n')
+ 
+ 
+ /*
+  *		ldapServiceLookup
+  *
+  * Search the LDAP URL passed as first argument, treat the result as a
+  * string of connection options that are parsed and added to the array of
+  * options passed as second argument.
+  *
+  * LDAP URLs must conform to RFC 1959 without escape sequences.
+  *	ldap://host:port/dn?attributes?scope?filter?extensions
+  *
+  * Returns
+  *	0 if the lookup was successful,
+  *	1 if the connection to the LDAP server could be established but
+  *	  the search was unsuccessful,
+  *	2 if a connection could not be established, and
+  *	3 if a fatal error occurred.
+  *
+  * An error message is returned in the third argument for return codes 1 and 3.
+  */
+ static int
+ ldapServiceLookup(const char *purl, PQconninfoOption *options,
+ 				  PQExpBuffer errorMessage)
+ {
+ 	int			port = LDAP_DEF_PORT, scope, rc, msgid, size, state, oldstate, i;
+ 	bool		found_keyword;
+ 	char	   *url, *hostname, *portstr, *endptr, *dn, *scopestr, *filter,
+ 			   *result, *p, *p1 = NULL, *optname = NULL, *optval = NULL;
+ 	char	   *attrs[2] = {NULL, NULL};
+ 	LDAP	   *ld = NULL;
+ 	LDAPMessage *res, *entry;
+ 	struct berval **values;
+ 	LDAP_TIMEVAL time = {PGLDAP_TIMEOUT, 0};
+ 
+ 	if ((url = strdup(purl)) == NULL)
+ 	{
+ 		printfPQExpBuffer(errorMessage, libpq_gettext("out of memory\n"));
+ 		return 3;
+ 	}
+ 
+ 	/*
+ 	 *	Parse URL components, check for correctness.  Basically, url has
+ 	 *	'\0' placed at component boundaries and variables are pointed
+ 	 *	at each component.
+ 	 */
+ 
+ 	if (strncasecmp(url, LDAP_URL, strlen(LDAP_URL)) != 0)
+ 	{
+ 		printfPQExpBuffer(errorMessage,
+ 		libpq_gettext("bad LDAP URL \"%s\": scheme must be ldap://\n"), purl);
+ 		free(url);
+ 		return 3;
+ 	}
+ 
+ 	/* hostname */
+ 	hostname = url + strlen(LDAP_URL);
+ 	if (*hostname == '/')	/* no hostname? */
+ 		hostname = "localhost";	/* the default */
+ 
+ 	/* dn, "distinguished name" */
+ 	p = strchr(url +  strlen(LDAP_URL), '/');
+ 	if (p == NULL || *(p + 1) == '\0' || *(p + 1) == '?')
+ 	{
+ 		printfPQExpBuffer(errorMessage, libpq_gettext(
+ 						"bad LDAP URL \"%s\": missing distinguished name\n"), purl);
+ 		free(url);
+ 		return 3;
+ 	}
+ 	*p = '\0';	/* terminate hostname */
+ 	dn = p + 1;
+ 
+ 	/* attribute */
+ 	if ((p = strchr(dn, '?')) == NULL || *(p + 1) == '\0' || *(p + 1) == '?')
+ 	{
+ 		printfPQExpBuffer(errorMessage, libpq_gettext(
+ 							"bad LDAP URL \"%s\": must have exactly one attribute\n"), purl);
+ 		free(url);
+ 		return 3;
+ 	}
+ 	*p = '\0';
+ 	attrs[0] = p + 1;
+ 
+ 	/* scope */
+ 	if ((p = strchr(attrs[0], '?')) == NULL || *(p + 1) == '\0' || *(p + 1) == '?')
+ 	{
+ 		printfPQExpBuffer(errorMessage, libpq_gettext(
+ 							"bad LDAP URL \"%s\": must have search scope (base/one/sub)\n"), purl);
+ 		free(url);
+ 		return 3;
+ 	}
+ 	*p = '\0';
+ 	scopestr = p + 1;
+ 
+ 	/* filter */
+ 	if ((p = strchr(scopestr, '?')) == NULL || *(p + 1) == '\0' || *(p + 1) == '?')
+ 	{
+ 		printfPQExpBuffer(errorMessage,
+ 					libpq_gettext("bad LDAP URL \"%s\": no filter\n"), purl);
+ 		free(url);
+ 		return 3;
+ 	}
+ 	*p = '\0';
+ 	filter = p + 1;
+ 	if ((p = strchr(filter, '?')) != NULL)
+ 		*p = '\0';
+ 
+ 	/* port number? */
+ 	if ((p1 = strchr(hostname, ':')) != NULL)
+ 	{
+ 		long		lport;
+ 
+ 		*p1 = '\0';
+ 		portstr = p1 + 1;
+ 		errno = 0;
+ 		lport = strtol(portstr, &endptr, 10);
+ 		if (*portstr == '\0' || *endptr != '\0' || errno || lport < 0 || lport > 65535)
+ 		{
+ 			printfPQExpBuffer(errorMessage, libpq_gettext(
+ 							"bad LDAP URL \"%s\": invalid port number\n"), purl);
+ 			free(url);
+ 			return 3;
+ 		}
+ 		port = (int) lport;
+ 	}
+ 
+ 	/* Allow only one attribute */
+ 	if (strchr(attrs[0], ',') != NULL)
+ 	{
+ 		printfPQExpBuffer(errorMessage, libpq_gettext(
+ 							"bad LDAP URL \"%s\": must have exactly one attribute\n"), purl);
+ 		free(url);
+ 		return 3;
+ 	}
+ 
+ 	/* set scope */
+ 	if (strcasecmp(scopestr, "base") == 0)
+ 		scope = LDAP_SCOPE_BASE;
+ 	else if (strcasecmp(scopestr, "one") == 0)
+ 		scope = LDAP_SCOPE_ONELEVEL;
+ 	else if (strcasecmp(scopestr, "sub") == 0)
+ 		scope = LDAP_SCOPE_SUBTREE;
+ 	else
+ 	{
+ 		printfPQExpBuffer(errorMessage, libpq_gettext(
+ 					"bad LDAP URL \"%s\": must have search scope (base/one/sub)\n"), purl);
+ 		free(url);
+ 		return 3;
+ 	}
+ 
+ 	/* initialize LDAP structure */
+ 	if ((ld = ldap_init(hostname, port)) == NULL)
+ 	{
+ 		printfPQExpBuffer(errorMessage,
+ 						  libpq_gettext("error creating LDAP structure\n"));
+ 		free(url);
+ 		return 3;
+ 	}
  
+ 	/*
+ 	 *	Initialize connection to the server.  We do an explicit bind because
+ 	 *	we want to return 2 if the bind fails.
+ 	 */
+ 	if ((msgid = ldap_simple_bind(ld, NULL, NULL)) == -1)
+ 	{
+ 		/* error in ldap_simple_bind() */
+ 		free(url);
+ 		ldap_unbind(ld);
+ 		return 2;
+ 	}
+ 
+ 	/* wait some time for the connection to succeed */
+ 	res = NULL;
+ 	if ((rc = ldap_result(ld, msgid, LDAP_MSG_ALL, &time, &res)) == -1 ||
+ 		res == NULL)
+ 	{
+ 		if (res != NULL)
+ 		{
+ 			/* timeout */
+ 			ldap_msgfree(res);
+ 		}
+ 		/* error in ldap_result() */
+ 		free(url);
+ 		ldap_unbind(ld);
+ 		return 2;
+ 	}
+ 	ldap_msgfree(res);
+ 
+ 	/* search */
+ 	res = NULL;
+ 	if ((rc = ldap_search_st(ld, dn, scope, filter, attrs, 0, &time, &res))
+ 		!= LDAP_SUCCESS)
+ 	{
+ 		if (res != NULL)
+ 			ldap_msgfree(res);
+ 		printfPQExpBuffer(errorMessage,
+ 						  libpq_gettext("lookup on LDAP server failed: %s\n"),
+ 						  ldap_err2string(rc));
+ 		ldap_unbind(ld);
+ 		free(url);
+ 		return 1;
+ 	}
+ 
+ 	/* complain if there was not exactly one result */
+ 	if ((rc = ldap_count_entries(ld, res)) != 1)
+ 	{
+ 		printfPQExpBuffer(errorMessage,
+ 			 rc ? libpq_gettext("more than one entry found on LDAP lookup\n")
+ 						  : libpq_gettext("no entry found on LDAP lookup\n"));
+ 		ldap_msgfree(res);
+ 		ldap_unbind(ld);
+ 		free(url);
+ 		return 1;
+ 	}
+ 
+ 	/* get entry */
+ 	if ((entry = ldap_first_entry(ld, res)) == NULL)
+ 	{
+ 		/* should never happen */
+ 		printfPQExpBuffer(errorMessage,
+ 						  libpq_gettext("no entry found on LDAP lookup\n"));
+ 		ldap_msgfree(res);
+ 		ldap_unbind(ld);
+ 		free(url);
+ 		return 1;
+ 	}
+ 
+ 	/* get values */
+ 	if ((values = ldap_get_values_len(ld, entry, attrs[0])) == NULL)
+ 	{
+ 		printfPQExpBuffer(errorMessage,
+ 				  libpq_gettext("attribute has no values on LDAP lookup\n"));
+ 		ldap_msgfree(res);
+ 		ldap_unbind(ld);
+ 		free(url);
+ 		return 1;
+ 	}
+ 
+ 	ldap_msgfree(res);
+ 	free(url);
+ 
+ 	if (values[0] == NULL)
+ 	{
+ 		printfPQExpBuffer(errorMessage,
+ 				  libpq_gettext("attribute has no values on LDAP lookup\n"));
+ 		ldap_value_free_len(values);
+ 		ldap_unbind(ld);
+ 		return 1;
+ 	}
+ 
+ 	/* concatenate values to a single string */
+ 	for (size = 0, i = 0; values[i] != NULL; ++i)
+ 		size += values[i]->bv_len + 1;
+ 	if ((result = malloc(size + 1)) == NULL)
+ 	{
+ 		printfPQExpBuffer(errorMessage,
+ 						  libpq_gettext("out of memory\n"));
+ 		ldap_value_free_len(values);
+ 		ldap_unbind(ld);
+ 		return 3;
+ 	}
+ 	for (p = result, i = 0; values[i] != NULL; ++i)
+ 	{
+ 		strncpy(p, values[i]->bv_val, values[i]->bv_len);
+ 		p += values[i]->bv_len;
+ 		*(p++) = '\n';
+ 		if (values[i + 1] == NULL)
+ 			*(p + 1) = '\0';
+ 	}
+ 
+ 	ldap_value_free_len(values);
+ 	ldap_unbind(ld);
+ 
+ 	/* parse result string */
+ 	oldstate = state = 0;
+ 	for (p = result; *p != '\0'; ++p)
+ 	{
+ 		switch (state)
+ 		{
+ 			case 0:				/* between entries */
+ 				if (!ld_is_sp_tab(*p) && !ld_is_nl_cr(*p))
+ 				{
+ 					optname = p;
+ 					state = 1;
+ 				}
+ 				break;
+ 			case 1:				/* in option name */
+ 				if (ld_is_sp_tab(*p))
+ 				{
+ 					*p = '\0';
+ 					state = 2;
+ 				}
+ 				else if (ld_is_nl_cr(*p))
+ 				{
+ 					printfPQExpBuffer(errorMessage, libpq_gettext(
+ 								"missing \"=\" after \"%s\" in connection info string\n"),
+ 								  optname);
+ 					return 3;
+ 				}
+ 				else if (*p == '=')
+ 				{
+ 					*p = '\0';
+ 					state = 3;
+ 				}
+ 				break;
+ 			case 2:				/* after option name */
+ 				if (*p == '=')
+ 				{
+ 					state = 3;
+ 				}
+ 				else if (!ld_is_sp_tab(*p))
+ 				{
+ 					printfPQExpBuffer(errorMessage, libpq_gettext(
+ 								"missing \"=\" after \"%s\" in connection info string\n"),
+ 								  optname);
+ 					return 3;
+ 				}
+ 				break;
+ 			case 3:				/* before option value */
+ 				if (*p == '\'')
+ 				{
+ 					optval = p + 1;
+ 					p1 = p + 1;
+ 					state = 5;
+ 				}
+ 				else if (ld_is_nl_cr(*p))
+ 				{
+ 					optval = optname + strlen(optname); /* empty */
+ 					state = 0;
+ 				}
+ 				else if (!ld_is_sp_tab(*p))
+ 				{
+ 					optval = p;
+ 					state = 4;
+ 				}
+ 				break;
+ 			case 4:				/* in unquoted option value */
+ 				if (ld_is_sp_tab(*p) || ld_is_nl_cr(*p))
+ 				{
+ 					*p = '\0';
+ 					state = 0;
+ 				}
+ 				break;
+ 			case 5:				/* in quoted option value */
+ 				if (*p == '\'')
+ 				{
+ 					*p1 = '\0';
+ 					state = 0;
+ 				}
+ 				else if (*p == '\\')
+ 					state = 6;
+ 				else
+ 					*(p1++) = *p;
+ 				break;
+ 			case 6:				/* in quoted option value after escape */
+ 				*(p1++) = *p;
+ 				state = 5;
+ 				break;
+ 		}
+ 
+ 		if (state == 0 && oldstate != 0)
+ 		{
+ 			found_keyword = false;
+ 			for (i = 0; options[i].keyword; i++)
+ 			{
+ 				if (strcmp(options[i].keyword, optname) == 0)
+ 				{
+ 					if (options[i].val == NULL)
+ 						options[i].val = strdup(optval);
+ 					found_keyword = true;
+ 					break;
+ 				}
+ 			}
+ 			if (!found_keyword)
+ 			{
+ 				printfPQExpBuffer(errorMessage,
+ 						 libpq_gettext("invalid connection option \"%s\"\n"),
+ 						  optname);
+ 				return 1;
+ 			}
+ 			optname = NULL;
+ 			optval = NULL;
+ 		}
+ 		oldstate = state;
+ 	}
+ 
+ 	if (state == 5 || state == 6)
+ 	{
+ 		printfPQExpBuffer(errorMessage, libpq_gettext(
+ 						"unterminated quoted string in connection info string\n"));
+ 		return 3;
+ 	}
+ 
+ 	return 0;
+ }
+ #endif
  
  #define MAXBUFSIZE 256
  
***************
*** 2439,2444 ****
--- 2855,2880 ----
  							   *val;
  					bool		found_keyword;
  
+ #ifdef USE_LDAP
+ 					if (strncmp(line, "ldap", 4) == 0)
+ 					{
+ 						int rc = ldapServiceLookup(line, options, errorMessage);
+ 						/* if rc = 2, go on reading for fallback */
+ 						switch (rc)
+ 						{
+ 							case 0:
+ 								fclose(f);
+ 								return 0;
+ 							case 1:
+ 							case 3:
+ 								fclose(f);
+ 								return 3;
+ 							case 2:
+ 								continue;
+ 						}
+ 					}
+ #endif
+ 
  					key = line;
  					val = strchr(line, '=');
  					if (val == NULL)
