From 0b86ecf4c3b5aa5559b36a201bc91d9bdc8b7e78 Mon Sep 17 00:00:00 2001 From: Thomas Munro Date: Mon, 17 Jul 2017 10:11:28 +1200 Subject: [PATCH] Allow custom search filters to be configured for LDAP auth. Before, only filters of the form "(=)" could be used to search an LDAP server. Introduce ldapsearchfilter so that more general filters can be configured using patterns like "(|(uid=%u)(mail=%u))" and "(&(uid=%u)(objectClass=posixAccount))". --- doc/src/sgml/client-auth.sgml | 32 ++++++++++++++++++++++++ src/backend/libpq/auth.c | 57 ++++++++++++++++++++++++++++++++++++++++--- src/backend/libpq/hba.c | 38 +++++++++++++++++++++++++---- src/include/libpq/hba.h | 1 + 4 files changed, 120 insertions(+), 8 deletions(-) diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml index 819db811b2..e599357ffa 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -1508,6 +1508,17 @@ omicron bryanh guest1 + ldapsearchfilter + + + The search filter to use when doing search+bind authentication. + Occurences of %u will be replaced with the + user name. This allows for more flexible search filters than + ldapsearchattribute. + + + + ldapurl @@ -1550,6 +1561,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=%u)". If neither + option is specified the default is + ldapsearchattribute=uid. + + + Here is an example for a simple-bind LDAP configuration: host ... ldap ldapserver=ldap.example.net ldapprefix="cn=" ldapsuffix=", dc=example, dc=net" @@ -1584,6 +1606,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=%u)(mail=%u))" + + + 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 dd7de7c3a4..d062ebfcba 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -2381,6 +2381,55 @@ InitializeLDAPConnection(Port *port, LDAP **ldap) } /* + * Return a newly allocated C string copied from "pattern" with all occurences + * of "%u" replaced with "user_name". + */ +static char * +FormatSearchFilter(const char *pattern, const char *user_name) +{ + Size user_name_size = strlen(user_name); + Size buffer_size = 0; + const char *in; + char *out; + char *result; + + /* Compute the size of the output buffer. */ + in = pattern; + while (*in != '\0') + { + if (in[0] == '%' && in[1] == 'u') + { + buffer_size += user_name_size; + in += 2; + } + else + { + buffer_size++; + in++; + } + } + buffer_size++; /* terminator */ + + /* Build the output string. */ + out = result = palloc(buffer_size); + in = pattern; + while (*in != '\0') + { + if (in[0] == '%' && in[1] == 'u') + { + memcpy(out, user_name, user_name_size); + out += user_name_size; + in += 2; + } + else + *out++ = *in++; + } + *out = '\0'; /* terminator */ + + return result; +} + +/* * Perform LDAP authentication */ static int @@ -2471,9 +2520,11 @@ CheckLDAPAuth(Port *port) 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 != NULL) + filter = FormatSearchFilter(port->hba->ldapsearchfilter, port->user_name); + else + filter = psprintf("(%s=%s)", attributes[0], 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 42afead9fd..4ae10f6d02 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) @@ -1788,6 +1806,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 +2289,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; -- 2.13.2