diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml
index 6442aa1efed565dc0915475328d9ec7d9cc78d17..e1d8c9503b56554333056a849b4f155752c7fe26 100644
--- a/doc/src/sgml/client-auth.sgml
+++ b/doc/src/sgml/client-auth.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/client-auth.sgml,v 1.124 2009/10/01 01:58:57 tgl Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/client-auth.sgml,v 1.125 2009/12/12 21:35:21 mha Exp $ -->
 
 <chapter id="client-authentication">
  <title>Client Authentication</title>
@@ -1202,7 +1202,8 @@ omicron       bryanh            guest1
    </para>
 
    <para>
-    The server will bind to the distinguished name constructed as
+    LDAP authentication can operate in two modes. In the first mode,
+    the server will bind to the distinguished name constructed as
     <replaceable>prefix</> <replaceable>username</> <replaceable>suffix</>.
     Typically, the <replaceable>prefix</> parameter is used to specify
     <literal>cn=</>, or <replaceable>DOMAIN</><literal>\</> in an Active
@@ -1210,6 +1211,23 @@ omicron       bryanh            guest1
     remaining part of the DN in a non-Active Directory environment.
    </para>
 
+   <para>
+    In the second mode, the server first binds to the LDAP directory with
+    a fixed username and password, specified with <replaceable>ldapbinduser</>
+    and <replaceable>ldapbinddn</>, and performs a search for the user trying
+    to log in to the database. If no user and password is configured, an
+    anonymous bind will be attempted to the directory. The search will be
+    performed over the subtree at <replaceable>ldapbasedn</>, and will try to
+    do an exact match of the attribute specified in
+    <replaceable>ldapsearchattribute</>. If no attribute is specified, the
+    <literal>uid</> attribute will be used. Once the user has been found in
+    this search, the server disconnects and re-binds to the directory as
+    this user, using the password specified by the client, to verify that the
+    login is correct. This method allows for significantly more flexibility
+    in where the user objects are located in the directory, but will cause
+    two separate connections to the LDAP server to be made.
+   </para>
+
    <para>
     The following configuration options are supported for LDAP:
     <variablelist>
@@ -1221,11 +1239,32 @@ omicron       bryanh            guest1
        </para>
       </listitem>
      </varlistentry>
+     <varlistentry>
+      <term><literal>ldapport</literal></term>
+      <listitem>
+       <para>
+        Port number on LDAP server to connect to. If no port is specified,
+        the default port in the LDAP library will be used.
+       </para>
+      </listitem>
+     </varlistentry>
+     <varlistentry>
+      <term><literal>ldaptls</literal></term>
+      <listitem>
+       <para>
+        Set to <literal>1</> to make the connection between PostgreSQL and the
+        LDAP server use TLS encryption. Note that this only encrypts
+        the traffic to the LDAP server &mdash; the connection to the client
+        will still be unencrypted unless SSL is used.
+       </para>
+      </listitem>
+     </varlistentry>
      <varlistentry>
       <term><literal>ldapprefix</literal></term>
       <listitem>
        <para>
-        String to prepend to the username when forming the DN to bind as.
+        String to prepend to the username when forming the DN to bind as,
+        when doing simple bind authentication.
        </para>
       </listitem>
      </varlistentry>
@@ -1233,30 +1272,47 @@ omicron       bryanh            guest1
       <term><literal>ldapsuffix</literal></term>
       <listitem>
        <para>
-        String to append to the username when forming the DN to bind as.
+        String to append to the username when forming the DN to bind as,
+        when doing simple bind authentication.
        </para>
       </listitem>
      </varlistentry>
      <varlistentry>
-      <term><literal>ldapport</literal></term>
+      <term><literal>ldapbasedn</literal></term>
       <listitem>
        <para>
-        Port number on LDAP server to connect to. If no port is specified,
-        the default port in the LDAP library will be used.
+        DN to root the search for the user in, when doing search+bind
+        authentication.
        </para>
       </listitem>
      </varlistentry>
      <varlistentry>
-      <term><literal>ldaptls</literal></term>
+      <term><literal>ldapbinddn</literal></term>
       <listitem>
        <para>
-        Set to <literal>1</> to make the connection between PostgreSQL and the
-        LDAP server use TLS encryption. Note that this only encrypts
-        the traffic to the LDAP server &mdash; the connection to the client
-        will still be unencrypted unless SSL is used.
+        DN of user to bind to the directory with to perform the search when
+        doing search+bind authentication.
        </para>
       </listitem>
      </varlistentry>
+     <varlistentry>
+      <term><literal>ldapbindpasswd</literal></term>
+      <listitem>
+       <para>
+        Password for user to bind to the directory with to perform the search
+        when doing search+bind authentication.
+       </para>
+      </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term><literal>ldapsearchattribute</literal></term>
+       <listitem>
+        <para>
+         Attribute to match against the username in the search when doing
+         search+bind authentication.
+        </para>
+       </listitem>
+      </varlistentry>
     </variablelist>
    </para>
 
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 1e080465b675249bbf6b9b11488848bb5f3e4d79..7b6b5a5a071b72223b65a1ce2a88cc96c2e81e11 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/libpq/auth.c,v 1.187 2009/10/16 22:08:36 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/libpq/auth.c,v 1.188 2009/12/12 21:35:21 mha Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -2096,40 +2096,18 @@ CheckPAMAuth(Port *port, char *user, char *password)
  */
 #ifdef USE_LDAP
 
+/*
+ * Initialize a connection to the LDAP server, including setting up
+ * TLS if requested.
+ */
 static int
-CheckLDAPAuth(Port *port)
+InitializeLDAPConnection(Port *port, LDAP **ldap)
 {
-	char	   *passwd;
-	LDAP	   *ldap;
-	int			r;
 	int			ldapversion = LDAP_VERSION3;
-	char		fulluser[NAMEDATALEN + 256 + 1];
-
-	if (!port->hba->ldapserver || port->hba->ldapserver[0] == '\0')
-	{
-		ereport(LOG,
-				(errmsg("LDAP server not specified")));
-		return STATUS_ERROR;
-	}
-
-	if (port->hba->ldapport == 0)
-		port->hba->ldapport = LDAP_PORT;
-
-	sendAuthRequest(port, AUTH_REQ_PASSWORD);
-
-	passwd = recv_password_packet(port);
-	if (passwd == NULL)
-		return STATUS_EOF;		/* client wouldn't send password */
-
-	if (strlen(passwd) == 0)
-	{
-		ereport(LOG,
-				(errmsg("empty password returned by client")));
-		return STATUS_ERROR;
-	}
+	int			r;
 
-	ldap = ldap_init(port->hba->ldapserver, port->hba->ldapport);
-	if (!ldap)
+	*ldap = ldap_init(port->hba->ldapserver, port->hba->ldapport);
+	if (!*ldap)
 	{
 #ifndef WIN32
 		ereport(LOG,
@@ -2143,9 +2121,9 @@ CheckLDAPAuth(Port *port)
 		return STATUS_ERROR;
 	}
 
-	if ((r = ldap_set_option(ldap, LDAP_OPT_PROTOCOL_VERSION, &ldapversion)) != LDAP_SUCCESS)
+	if ((r = ldap_set_option(*ldap, LDAP_OPT_PROTOCOL_VERSION, &ldapversion)) != LDAP_SUCCESS)
 	{
-		ldap_unbind(ldap);
+		ldap_unbind(*ldap);
 		ereport(LOG,
 		  (errmsg("could not set LDAP protocol version: error code %d", r)));
 		return STATUS_ERROR;
@@ -2154,7 +2132,7 @@ CheckLDAPAuth(Port *port)
 	if (port->hba->ldaptls)
 	{
 #ifndef WIN32
-		if ((r = ldap_start_tls_s(ldap, NULL, NULL)) != LDAP_SUCCESS)
+		if ((r = ldap_start_tls_s(*ldap, NULL, NULL)) != LDAP_SUCCESS)
 #else
 		static __ldap_start_tls_sA _ldap_start_tls_sA = NULL;
 
@@ -2174,7 +2152,7 @@ CheckLDAPAuth(Port *port)
 				 * should never happen since we import other files from
 				 * wldap32, but check anyway
 				 */
-				ldap_unbind(ldap);
+				ldap_unbind(*ldap);
 				ereport(LOG,
 						(errmsg("could not load wldap32.dll")));
 				return STATUS_ERROR;
@@ -2182,7 +2160,7 @@ CheckLDAPAuth(Port *port)
 			_ldap_start_tls_sA = (__ldap_start_tls_sA) GetProcAddress(ldaphandle, "ldap_start_tls_sA");
 			if (_ldap_start_tls_sA == NULL)
 			{
-				ldap_unbind(ldap);
+				ldap_unbind(*ldap);
 				ereport(LOG,
 						(errmsg("could not load function _ldap_start_tls_sA in wldap32.dll"),
 						 errdetail("LDAP over SSL is not supported on this platform.")));
@@ -2195,21 +2173,202 @@ CheckLDAPAuth(Port *port)
 			 * per process and is automatically cleaned up on process exit.
 			 */
 		}
-		if ((r = _ldap_start_tls_sA(ldap, NULL, NULL, NULL, NULL)) != LDAP_SUCCESS)
+		if ((r = _ldap_start_tls_sA(*ldap, NULL, NULL, NULL, NULL)) != LDAP_SUCCESS)
 #endif
 		{
-			ldap_unbind(ldap);
+			ldap_unbind(*ldap);
 			ereport(LOG,
 			 (errmsg("could not start LDAP TLS session: error code %d", r)));
 			return STATUS_ERROR;
 		}
 	}
 
-	snprintf(fulluser, sizeof(fulluser), "%s%s%s",
-			 port->hba->ldapprefix ? port->hba->ldapprefix : "",
-			 port->user_name,
-			 port->hba->ldapsuffix ? port->hba->ldapsuffix : "");
-	fulluser[sizeof(fulluser) - 1] = '\0';
+	return STATUS_OK;
+}
+
+/*
+ * Perform LDAP authentication
+ */
+static int
+CheckLDAPAuth(Port *port)
+{
+	char	   *passwd;
+	LDAP	   *ldap;
+	int			r;
+	char	   *fulluser;
+
+	if (!port->hba->ldapserver || port->hba->ldapserver[0] == '\0')
+	{
+		ereport(LOG,
+				(errmsg("LDAP server not specified")));
+		return STATUS_ERROR;
+	}
+
+	if (port->hba->ldapport == 0)
+		port->hba->ldapport = LDAP_PORT;
+
+	sendAuthRequest(port, AUTH_REQ_PASSWORD);
+
+	passwd = recv_password_packet(port);
+	if (passwd == NULL)
+		return STATUS_EOF;		/* client wouldn't send password */
+
+	if (strlen(passwd) == 0)
+	{
+		ereport(LOG,
+				(errmsg("empty password returned by client")));
+		return STATUS_ERROR;
+	}
+
+	if (InitializeLDAPConnection(port, &ldap) == STATUS_ERROR)
+		/* Error message already sent */
+		return STATUS_ERROR;
+
+	if (port->hba->ldapbasedn)
+	{
+		/*
+		 * First perform an LDAP search to find the DN for the user we are trying to log
+		 * in as.
+		 */
+		char		   *filter;
+		LDAPMessage	   *search_message;
+		LDAPMessage	   *entry;
+		char		   *attributes[2];
+		char		   *dn;
+		char		   *c;
+
+		/*
+		 * Disallow any characters that we would otherwise need to escape, since they
+		 * aren't really reasonable in a username anyway. Allowing them would make it
+		 * possible to inject any kind of custom filters in the LDAP filter.
+		 */
+		for (c = port->user_name; *c; c++)
+		{
+			if (*c == '*' ||
+				*c == '(' ||
+				*c == ')' ||
+				*c == '\\' ||
+				*c == '/')
+			{
+				ereport(LOG,
+						(errmsg("invalid character in username for LDAP authentication")));
+				return STATUS_ERROR;
+			}
+		}
+
+		/*
+		 * Bind with a pre-defined username/password (if available) for searching. If
+		 * none is specified, this turns into an anonymous bind.
+		 */
+		r = ldap_simple_bind_s(ldap,
+							   port->hba->ldapbinddn ? port->hba->ldapbinddn : "",
+							   port->hba->ldapbindpasswd ? port->hba->ldapbindpasswd : "");
+		if (r != LDAP_SUCCESS)
+		{
+			ereport(LOG,
+					(errmsg("could not perform initial LDAP bind for ldapbinddn \"%s\" on server \"%s\": error code %d",
+							port->hba->ldapbinddn, port->hba->ldapserver, r)));
+			return STATUS_ERROR;
+		}
+
+		/* Fetch just one attribute, else *all* attributes are returned */
+		attributes[0] = port->hba->ldapsearchattribute ? port->hba->ldapsearchattribute : "uid";
+		attributes[1] = NULL;
+
+		filter = palloc(strlen(attributes[0])+strlen(port->user_name)+4);
+		sprintf(filter, "(%s=%s)",
+				 attributes[0],
+				 port->user_name);
+
+		r = ldap_search_s(ldap,
+						  port->hba->ldapbasedn,
+						  LDAP_SCOPE_SUBTREE,
+						  filter,
+						  attributes,
+						  0,
+						  &search_message);
+
+		if (r != LDAP_SUCCESS)
+		{
+			ereport(LOG,
+					(errmsg("could not search LDAP for filter \"%s\" on server \"%s\": error code %d",
+							filter, port->hba->ldapserver, r)));
+			pfree(filter);
+			return STATUS_ERROR;
+		}
+
+		if (ldap_count_entries(ldap, search_message) != 1)
+		{
+			if (ldap_count_entries(ldap, search_message) == 0)
+				ereport(LOG,
+						(errmsg("LDAP search failed for filter \"%s\" on server \"%s\": no such user",
+								filter, port->hba->ldapserver)));
+			else
+				ereport(LOG,
+						(errmsg("LDAP search failed for filter \"%s\" on server \"%s\": user is not unique (%d matches)",
+								filter, port->hba->ldapserver, ldap_count_entries(ldap, search_message))));
+
+			pfree(filter);
+			ldap_msgfree(search_message);
+			return STATUS_ERROR;
+		}
+
+		entry = ldap_first_entry(ldap, search_message);
+		dn = ldap_get_dn(ldap, entry);
+		if (dn == NULL)
+		{
+			int error;
+			(void)ldap_get_option(ldap, LDAP_OPT_ERROR_NUMBER, &error);
+			ereport(LOG,
+					(errmsg("could not get dn for the first entry matching \"%s\" on server \"%s\": %s",
+							filter, port->hba->ldapserver, ldap_err2string(error))));
+			pfree(filter);
+			ldap_msgfree(search_message);
+			return STATUS_ERROR;
+		}
+		fulluser = pstrdup(dn);
+
+		pfree(filter);
+		ldap_memfree(dn);
+		ldap_msgfree(search_message);
+
+		/* Unbind and disconnect from the LDAP server */
+		r = ldap_unbind_s(ldap);
+		if (r != LDAP_SUCCESS)
+		{
+			int error;
+			(void)ldap_get_option(ldap, LDAP_OPT_ERROR_NUMBER, &error);
+			ereport(LOG,
+					(errmsg("could not unbind after searching for user \"%s\" on server \"%s\": %s",
+							fulluser, port->hba->ldapserver, ldap_err2string(error))));
+			pfree(fulluser);
+			return STATUS_ERROR;
+		}
+
+		/*
+		 * Need to re-initialize the LDAP connection, so that we can bind
+		 * to it with a different username.
+		 */
+		if (InitializeLDAPConnection(port, &ldap) == STATUS_ERROR)
+		{
+			pfree(fulluser);
+
+			/* Error message already sent */
+			return STATUS_ERROR;
+		}
+	}
+	else
+	{
+		fulluser = palloc((port->hba->ldapprefix ? strlen(port->hba->ldapprefix) : 0) +
+						  strlen(port->user_name) +
+						  (port->hba->ldapsuffix ? strlen(port->hba->ldapsuffix) : 0) +
+						  1);
+
+		sprintf(fulluser, "%s%s%s",
+				 port->hba->ldapprefix ? port->hba->ldapprefix : "",
+				 port->user_name,
+				 port->hba->ldapsuffix ? port->hba->ldapsuffix : "");
+	}
 
 	r = ldap_simple_bind_s(ldap, fulluser, passwd);
 	ldap_unbind(ldap);
@@ -2219,9 +2378,12 @@ CheckLDAPAuth(Port *port)
 		ereport(LOG,
 				(errmsg("LDAP login failed for user \"%s\" on server \"%s\": error code %d",
 						fulluser, port->hba->ldapserver, r)));
+		pfree(fulluser);
 		return STATUS_ERROR;
 	}
 
+	pfree(fulluser);
+
 	return STATUS_OK;
 }
 #endif   /* USE_LDAP */
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index a51ea53082b423606dfd15297a223b0bcdc7acf4..65d5beadd3f905dac5dedf5b55aa55089b6148c7 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -10,7 +10,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/libpq/hba.c,v 1.192 2009/10/03 20:04:39 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/libpq/hba.c,v 1.193 2009/12/12 21:35:21 mha Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1103,6 +1103,26 @@ parse_hba_line(List *line, int line_num, HbaLine *parsedline)
 					return false;
 				}
 			}
+			else if (strcmp(token, "ldapbinddn") == 0)
+			{
+				REQUIRE_AUTH_OPTION(uaLDAP, "ldapbinddn", "ldap");
+				parsedline->ldapbinddn = pstrdup(c);
+			}
+			else if (strcmp(token, "ldapbindpasswd") == 0)
+			{
+				REQUIRE_AUTH_OPTION(uaLDAP, "ldapbindpasswd", "ldap");
+				parsedline->ldapbindpasswd = pstrdup(c);
+			}
+			else if (strcmp(token, "ldapsearchattribute") == 0)
+			{
+				REQUIRE_AUTH_OPTION(uaLDAP, "ldapsearchattribute", "ldap");
+				parsedline->ldapsearchattribute = pstrdup(c);
+			}
+			else if (strcmp(token, "ldapbasedn") == 0)
+			{
+				REQUIRE_AUTH_OPTION(uaLDAP, "ldapbasedn", "ldap");
+				parsedline->ldapbasedn = pstrdup(c);
+			}
 			else if (strcmp(token, "ldapprefix") == 0)
 			{
 				REQUIRE_AUTH_OPTION(uaLDAP, "ldapprefix", "ldap");
@@ -1156,6 +1176,37 @@ parse_hba_line(List *line, int line_num, HbaLine *parsedline)
 	if (parsedline->auth_method == uaLDAP)
 	{
 		MANDATORY_AUTH_ARG(parsedline->ldapserver, "ldapserver", "ldap");
+
+		/*
+		 * LDAP can operate in two modes: either with a direct bind, using
+		 * ldapprefix and ldapsuffix, or using a search+bind,
+		 * using ldapbasedn, ldapbinddn, ldapbindpasswd and ldapsearchattribute.
+		 * Disallow mixing these parameters.
+		 */
+		if (parsedline->ldapprefix || parsedline->ldapsuffix)
+		{
+			if (parsedline->ldapbasedn ||
+				parsedline->ldapbinddn ||
+				parsedline->ldapbindpasswd ||
+				parsedline->ldapsearchattribute)
+			{
+				ereport(LOG,
+						(errcode(ERRCODE_CONFIG_FILE_ERROR),
+						 errmsg("cannot use ldapbasedn, ldapbinddn, ldapbindpasswd or ldapsearchattribute together with ldapprefix"),
+						 errcontext("line %d of configuration file \"%s\"",
+									line_num, HbaFileName)));
+				return false;
+			}
+		}
+		else if (!parsedline->ldapbasedn)
+		{
+			ereport(LOG,
+					(errcode(ERRCODE_CONFIG_FILE_ERROR),
+					 errmsg("authentication method \"ldap\" requires argument \"ldapbasedn\", \"ldapprefix\" or \"ldapsuffix\" to be set"),
+					 errcontext("line %d of configuration file \"%s\"",
+								line_num, HbaFileName)));
+			return false;
+		}
 	}
 
 	/*
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index b444e09ecf201e97000734782308db9bc37ec0a6..8ee71a7e093647b84047e71374edce20ef5c3510 100644
--- a/src/include/libpq/hba.h
+++ b/src/include/libpq/hba.h
@@ -4,7 +4,7 @@
  *	  Interface to hba.c
  *
  *
- * $PostgreSQL: pgsql/src/include/libpq/hba.h,v 1.59 2009/10/01 01:58:58 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/libpq/hba.h,v 1.60 2009/12/12 21:35:21 mha Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -61,6 +61,10 @@ typedef struct
 	bool		ldaptls;
 	char	   *ldapserver;
 	int			ldapport;
+	char	   *ldapbinddn;
+	char	   *ldapbindpasswd;
+	char	   *ldapsearchattribute;
+	char	   *ldapbasedn;
 	char	   *ldapprefix;
 	char	   *ldapsuffix;
 	bool		clientcert;