diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml
index 1b568683a4..405bf26832 100644
--- a/doc/src/sgml/client-auth.sgml
+++ b/doc/src/sgml/client-auth.sgml
@@ -1507,6 +1507,17 @@ omicron bryanh guest1
+
+ ldapsearchfilter
+
+
+ The search filter to use when doing search+bind authentication.
+ Occurrences of $username will be replaced with the
+ user name. This allows for more flexible search filters than
+ ldapsearchattribute.
+
+
+
ldapurl
@@ -1514,13 +1525,16 @@ omicron bryanh guest1
An RFC 4516 LDAP URL. This is an alternative way to write some of the
other LDAP options in a more compact and standard form. The format is
-ldap://host[:port]/basedn[?[attribute][?[scope]]]
+ldap://host[:port]/basedn[?[attribute][?[scope][?[filter]]]]
scope must be one
of base, one, sub,
- typically the latter. Only one attribute is used, and some other
- components of standard LDAP URLs such as filters and extensions are
- not supported.
+ typically the last. attribute can
+ nominate a single attribute, in which case it is used as a value for
+ ldapsearchattribute. If
+ attribute is empty then
+ filter can be used as a value for
+ ldapsearchfilter.
@@ -1549,6 +1563,17 @@ ldap://host[:port]/
+
+ When using search+bind mode, the search can be performed using a single
+ attribute specified with ldapsearchattribute, or using
+ a custom search filter specified with
+ ldapsearchfilter.
+ Specifying ldapsearchattribute=foo is equivalent to
+ specifying ldapsearchfilter="(foo=$username)". If neither
+ option is specified the default is
+ ldapsearchattribute=uid.
+
+
Here is an example for a simple-bind LDAP configuration:
@@ -1584,6 +1609,16 @@ host ... ldap ldapurl="ldap://ldap.example.net/dc=example,dc=net?uid?sub"
same URL format, so it will be easier to share the configuration.
+
+ Here is an example for a search+bind configuration that uses
+ ldapsearchfilter instead of
+ ldapsearchattribute to allow authentication by
+ user ID or email address:
+
+host ... ldap ldapserver=ldap.example.net ldapbasedn="dc=example, dc=net" ldapsearchfilter="(|(uid=$username)(mail=$username))"
+
+
+
Since LDAP often uses commas and spaces to separate the different
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index cb30fc7b71..62ff624dbd 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2394,6 +2394,34 @@ InitializeLDAPConnection(Port *port, LDAP **ldap)
return STATUS_OK;
}
+/* Placeholders recognized by FormatSearchFilter. For now just one. */
+#define LPH_USERNAME "$username"
+#define LPH_USERNAME_LEN (sizeof(LPH_USERNAME) - 1)
+
+/*
+ * Return a newly allocated C string copied from "pattern" with all
+ * occurrences of the placeholder "$username" replaced with "user_name".
+ */
+static char *
+FormatSearchFilter(const char *pattern, const char *user_name)
+{
+ StringInfoData output;
+
+ initStringInfo(&output);
+ while (*pattern != '\0')
+ {
+ if (strncmp(pattern, LPH_USERNAME, LPH_USERNAME_LEN) == 0)
+ {
+ appendStringInfoString(&output, user_name);
+ pattern += LPH_USERNAME_LEN;
+ }
+ else
+ appendStringInfoChar(&output, *pattern++);
+ }
+
+ return output.data;
+}
+
/*
* Perform LDAP authentication
*/
@@ -2437,7 +2465,7 @@ CheckLDAPAuth(Port *port)
char *filter;
LDAPMessage *search_message;
LDAPMessage *entry;
- char *attributes[2];
+ char *attributes[] = { LDAP_NO_ATTRS, NULL };
char *dn;
char *c;
int count;
@@ -2479,13 +2507,13 @@ CheckLDAPAuth(Port *port)
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 = psprintf("(%s=%s)",
- attributes[0],
- port->user_name);
+ /* Build a custom filter or a single attribute filter? */
+ if (port->hba->ldapsearchfilter)
+ filter = FormatSearchFilter(port->hba->ldapsearchfilter, port->user_name);
+ else if (port->hba->ldapsearchattribute)
+ filter = psprintf("(%s=%s)", port->hba->ldapsearchattribute, port->user_name);
+ else
+ filter = psprintf("(uid=%s)", port->user_name);
r = ldap_search_s(ldap,
port->hba->ldapbasedn,
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index ba011b6d61..b2c487a8e8 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -1505,22 +1505,24 @@ parse_hba_line(TokenizedLine *tok_line, int elevel)
/*
* 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.
+ * ldapbasedn, ldapbinddn, ldapbindpasswd and one of
+ * ldapsearchattribute or ldapsearchfilter. Disallow mixing these
+ * parameters.
*/
if (parsedline->ldapprefix || parsedline->ldapsuffix)
{
if (parsedline->ldapbasedn ||
parsedline->ldapbinddn ||
parsedline->ldapbindpasswd ||
- parsedline->ldapsearchattribute)
+ parsedline->ldapsearchattribute ||
+ parsedline->ldapsearchfilter)
{
ereport(elevel,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
- errmsg("cannot use ldapbasedn, ldapbinddn, ldapbindpasswd, ldapsearchattribute, or ldapurl together with ldapprefix"),
+ errmsg("cannot use ldapbasedn, ldapbinddn, ldapbindpasswd, ldapsearchattribute, ldapsearchfilter or ldapurl together with ldapprefix"),
errcontext("line %d of configuration file \"%s\"",
line_num, HbaFileName)));
- *err_msg = "cannot use ldapbasedn, ldapbinddn, ldapbindpasswd, ldapsearchattribute, or ldapurl together with ldapprefix";
+ *err_msg = "cannot use ldapbasedn, ldapbinddn, ldapbindpasswd, ldapsearchattribute, ldapsearchfilter or ldapurl together with ldapprefix";
return NULL;
}
}
@@ -1534,6 +1536,22 @@ parse_hba_line(TokenizedLine *tok_line, int elevel)
*err_msg = "authentication method \"ldap\" requires argument \"ldapbasedn\", \"ldapprefix\", or \"ldapsuffix\" to be set";
return NULL;
}
+
+ /*
+ * When using search+bind, you can either use a simple attribute
+ * (defaulting to "uid") or a fully custom search filter. You can't
+ * do both.
+ */
+ if (parsedline->ldapsearchattribute && parsedline->ldapsearchfilter)
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("cannot use ldapsearchattribute together with ldapsearchfilter"),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, HbaFileName)));
+ *err_msg = "cannot use ldapsearchattribute together with ldapsearchfilter";
+ return NULL;
+ }
}
if (parsedline->auth_method == uaRADIUS)
@@ -1729,14 +1747,7 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
hbaline->ldapsearchattribute = pstrdup(urldata->lud_attrs[0]); /* only use first one */
hbaline->ldapscope = urldata->lud_scope;
if (urldata->lud_filter)
- {
- ereport(elevel,
- (errcode(ERRCODE_CONFIG_FILE_ERROR),
- errmsg("filters not supported in LDAP URLs")));
- *err_msg = "filters not supported in LDAP URLs";
- ldap_free_urldesc(urldata);
- return false;
- }
+ hbaline->ldapsearchfilter = pstrdup(urldata->lud_filter);
ldap_free_urldesc(urldata);
#else /* not OpenLDAP */
ereport(elevel,
@@ -1788,6 +1799,11 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
REQUIRE_AUTH_OPTION(uaLDAP, "ldapsearchattribute", "ldap");
hbaline->ldapsearchattribute = pstrdup(val);
}
+ else if (strcmp(name, "ldapsearchfilter") == 0)
+ {
+ REQUIRE_AUTH_OPTION(uaLDAP, "ldapsearchfilter", "ldap");
+ hbaline->ldapsearchfilter = pstrdup(val);
+ }
else if (strcmp(name, "ldapbasedn") == 0)
{
REQUIRE_AUTH_OPTION(uaLDAP, "ldapbasedn", "ldap");
@@ -2266,6 +2282,11 @@ gethba_options(HbaLine *hba)
CStringGetTextDatum(psprintf("ldapsearchattribute=%s",
hba->ldapsearchattribute));
+ if (hba->ldapsearchfilter)
+ options[noptions++] =
+ CStringGetTextDatum(psprintf("ldapsearchfilter=%s",
+ hba->ldapsearchfilter));
+
if (hba->ldapscope)
options[noptions++] =
CStringGetTextDatum(psprintf("ldapscope=%d", hba->ldapscope));
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index 07d92d4f9f..e711bee8bf 100644
--- a/src/include/libpq/hba.h
+++ b/src/include/libpq/hba.h
@@ -80,6 +80,7 @@ typedef struct HbaLine
char *ldapbinddn;
char *ldapbindpasswd;
char *ldapsearchattribute;
+ char *ldapsearchfilter;
char *ldapbasedn;
int ldapscope;
char *ldapprefix;