From 43c79378c85cb0ac51c1a445655c2262dac7d46a Mon Sep 17 00:00:00 2001
From: Bruce Momjian <bruce@momjian.us>
Date: Thu, 27 Jul 2006 13:20:24 +0000
Subject: [PATCH] Allow LDAP lookups from pg_service.conf.

Albe Laurenz
---
 configure                         |  85 ++++++
 configure.in                      |  10 +-
 doc/src/sgml/libpq.sgml           |  68 ++++-
 src/interfaces/libpq/Makefile     |   4 +-
 src/interfaces/libpq/fe-connect.c | 438 +++++++++++++++++++++++++++++-
 5 files changed, 600 insertions(+), 5 deletions(-)

diff --git a/configure b/configure
index 20084080c5a..973c9ebf554 100755
--- a/configure
+++ b/configure
@@ -17314,6 +17314,91 @@ _ACEOF
 fi
 
 
+# this will link libpq against libldap_r
+if test "$with_ldap" = yes ; then
+  if test "$PORTNAME" != "win32"; then
+
+echo "$as_me:$LINENO: checking for ldap_simple_bind in -lldap_r" >&5
+echo $ECHO_N "checking for ldap_simple_bind in -lldap_r... $ECHO_C" >&6
+if test "${ac_cv_lib_ldap_r_ldap_simple_bind+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-lldap_r  $LIBS"
+cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+
+/* Override any gcc2 internal prototype to avoid an error.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+/* We use char because int might match the return type of a gcc2
+   builtin and then its argument prototype would still apply.  */
+char ldap_simple_bind ();
+int
+main ()
+{
+ldap_simple_bind ();
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext conftest$ac_exeext
+if { (eval echo "$as_me:$LINENO: \"$ac_link\"") >&5
+  (eval $ac_link) 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } &&
+	 { ac_try='test -z "$ac_c_werror_flag"
+			 || test ! -s conftest.err'
+  { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; } &&
+	 { ac_try='test -s conftest$ac_exeext'
+  { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; }; then
+  ac_cv_lib_ldap_r_ldap_simple_bind=yes
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ac_cv_lib_ldap_r_ldap_simple_bind=no
+fi
+rm -f conftest.err conftest.$ac_objext \
+      conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+echo "$as_me:$LINENO: result: $ac_cv_lib_ldap_r_ldap_simple_bind" >&5
+echo "${ECHO_T}$ac_cv_lib_ldap_r_ldap_simple_bind" >&6
+if test $ac_cv_lib_ldap_r_ldap_simple_bind = yes; then
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBLDAP_R 1
+_ACEOF
+
+  LIBS="-lldap_r $LIBS"
+
+else
+  { { echo "$as_me:$LINENO: error: library 'ldap_r' is required for LDAP" >&5
+echo "$as_me: error: library 'ldap_r' is required for LDAP" >&2;}
+   { (exit 1); exit 1; }; }
+fi
+
+    PTHREAD_LIBS="$PTHREAD_LIBS -lldap_r"
+  fi
+fi
+
 CFLAGS="$_CFLAGS"
 LIBS="$_LIBS"
 
diff --git a/configure.in b/configure.in
index fe3cde3f6a7..7031f7d380a 100644
--- a/configure.in
+++ b/configure.in
@@ -1,5 +1,5 @@
 dnl Process this file with autoconf to produce a configure script.
-dnl $PostgreSQL: pgsql/configure.in,v 1.469 2006/07/24 16:32:44 petere Exp $
+dnl $PostgreSQL: pgsql/configure.in,v 1.470 2006/07/27 13:20:24 momjian Exp $
 dnl
 dnl Developers, please strive to achieve this order:
 dnl
@@ -1106,6 +1106,14 @@ AC_CHECK_FUNCS([strerror_r getpwuid_r gethostbyname_r])
 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"
 
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 4d8b29de726..7ffd15a0388 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/libpq.sgml,v 1.213 2006/07/04 13:22:15 momjian Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/libpq.sgml,v 1.214 2006/07/27 13:20:24 momjian Exp $ -->
 
  <chapter id="libpq">
   <title><application>libpq</application> - C Library</title>
@@ -4126,6 +4126,72 @@ installs too. The file's location can also be specified by the
 </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>
 
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 24218d12b70..ea395c8834a 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -5,7 +5,7 @@
 # Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
 # Portions Copyright (c) 1994, Regents of the University of California
 #
-# $PostgreSQL: pgsql/src/interfaces/libpq/Makefile,v 1.146 2006/07/18 22:18:08 momjian Exp $
+# $PostgreSQL: pgsql/src/interfaces/libpq/Makefile,v 1.147 2006/07/27 13:20:24 momjian Exp $
 #
 #-------------------------------------------------------------------------
 
@@ -62,7 +62,7 @@ else
 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))
+SHLIB_LINK += -lshfolder -lwsock32 -lws2_32 $(filter -leay32 -lssleay32 -lcomerr32 -lkrb5_32 -lwldap32, $(LIBS))
 endif
 
 
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 74dfe3a9cf5..00235fb0bad 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/interfaces/libpq/fe-connect.c,v 1.333 2006/06/07 22:24:46 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/interfaces/libpq/fe-connect.c,v 1.334 2006/07/27 13:20:24 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -60,6 +60,19 @@
 #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,7 +2356,410 @@ pqPacketSend(PGconn *conn, char pack_type,
 	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,6 +2855,26 @@ parseServiceInfo(PQconninfoOption *options, PQExpBuffer errorMessage)
 							   *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)
-- 
GitLab