diff options
author | Thomas Munro <tmunro@postgresql.org> | 2019-03-21 15:19:03 +1300 |
---|---|---|
committer | Thomas Munro <tmunro@postgresql.org> | 2019-03-21 15:28:17 +1300 |
commit | 0f086f84ad9041888b789af5871c7432f0e19c5b (patch) | |
tree | e5423d372a6b2ee5077235f656eb00d3d8f39337 /src | |
parent | 8aa9dd74b36757342b6208fbfebb5b35c2d67c53 (diff) | |
download | postgresql-0f086f84ad9041888b789af5871c7432f0e19c5b.tar.gz postgresql-0f086f84ad9041888b789af5871c7432f0e19c5b.zip |
Add DNS SRV support for LDAP server discovery.
LDAP servers can be advertised on a network with RFC 2782 DNS SRV
records. The OpenLDAP command-line tools automatically try to find
servers that way, if no server name is provided by the user. Teach
PostgreSQL to do the same using OpenLDAP's support functions, when
building with OpenLDAP.
For now, we assume that HAVE_LDAP_INITIALIZE (an OpenLDAP extension
available since OpenLDAP 2.0 and also present in Apple LDAP) implies
that you also have ldap_domain2hostlist() (which arrived in the same
OpenLDAP version and is also present in Apple LDAP).
Author: Thomas Munro
Reviewed-by: Daniel Gustafsson
Discussion: https://postgr.es/m/CAEepm=2hAnSfhdsd6vXsM6VZVN0br-FbAZ-O+Swk18S5HkCP=A@mail.gmail.com
Diffstat (limited to 'src')
-rw-r--r-- | src/backend/libpq/auth.c | 152 | ||||
-rw-r--r-- | src/backend/libpq/hba.c | 3 |
2 files changed, 115 insertions, 40 deletions
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index fb86e9e9d41..6f03c7c2a5e 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -2368,45 +2368,95 @@ InitializeLDAPConnection(Port *port, LDAP **ldap) } #else #ifdef HAVE_LDAP_INITIALIZE + + /* + * OpenLDAP provides a non-standard extension ldap_initialize() that takes + * a list of URIs, allowing us to request "ldaps" instead of "ldap". It + * also provides ldap_domain2hostlist() to find LDAP servers automatically + * using DNS SRV. They were introduced in the same version, so for now we + * don't have an extra configure check for the latter. + */ { - const char *hostnames = port->hba->ldapserver; - char *uris = NULL; + StringInfoData uris; + char *hostlist = NULL; + char *p; + bool append_port; + + /* We'll build a space-separated scheme://hostname:port list here */ + initStringInfo(&uris); /* - * We have a space-separated list of hostnames. Convert it - * to a space-separated list of URIs. + * If pg_hba.conf provided no hostnames, we can ask OpenLDAP to try to + * find some by extracting a domain name from the base DN and looking + * up DSN SRV records for _ldap._tcp.<domain>. */ + if (!port->hba->ldapserver || port->hba->ldapserver[0] == '\0') + { + char *domain; + + /* ou=blah,dc=foo,dc=bar -> foo.bar */ + if (ldap_dn2domain(port->hba->ldapbasedn, &domain)) + { + ereport(LOG, + (errmsg("could not extract domain name from ldapbasedn"))); + return STATUS_ERROR; + } + + /* Look up a list of LDAP server hosts and port numbers */ + if (ldap_domain2hostlist(domain, &hostlist)) + { + ereport(LOG, + (errmsg("LDAP authentication could not find DNS SRV records for \"%s\"", + domain), + (errhint("Set an LDAP server name explicitly.")))); + ldap_memfree(domain); + return STATUS_ERROR; + } + ldap_memfree(domain); + + /* We have a space-separated list of host:port entries */ + p = hostlist; + append_port = false; + } + else + { + /* We have a space-separated list of hosts from pg_hba.conf */ + p = port->hba->ldapserver; + append_port = true; + } + + /* Convert the list of host[:port] entries to full URIs */ do { - char *hostname; - size_t hostname_size; - char *new_uris; - - /* Find the leading hostname. */ - hostname_size = strcspn(hostnames, " "); - hostname = pnstrdup(hostnames, hostname_size); - - /* Append a URI for this hostname. */ - new_uris = psprintf("%s%s%s://%s:%d", - uris ? uris : "", - uris ? " " : "", - scheme, - hostname, - port->hba->ldapport); - - pfree(hostname); - if (uris) - pfree(uris); - uris = new_uris; - - /* Step over this hostname and any spaces. */ - hostnames += hostname_size; - while (*hostnames == ' ') - ++hostnames; - } while (*hostnames); - - r = ldap_initialize(ldap, uris); - pfree(uris); + size_t size; + + /* Find the span of the next entry */ + size = strcspn(p, " "); + + /* Append a space separator if this isn't the first URI */ + if (uris.len > 0) + appendStringInfoChar(&uris, ' '); + + /* Append scheme://host:port */ + appendStringInfoString(&uris, scheme); + appendStringInfoString(&uris, "://"); + appendBinaryStringInfo(&uris, p, size); + if (append_port) + appendStringInfo(&uris, ":%d", port->hba->ldapport); + + /* Step over this entry and any number of trailing spaces */ + p += size; + while (*p == ' ') + ++p; + } while (*p); + + /* Free memory from OpenLDAP if we looked up SRV records */ + if (hostlist) + ldap_memfree(hostlist); + + /* Finally, try to connect using the URI list */ + r = ldap_initialize(ldap, uris.data); + pfree(uris.data); if (r != LDAP_SUCCESS) { ereport(LOG, @@ -2552,13 +2602,35 @@ CheckLDAPAuth(Port *port) LDAP *ldap; int r; char *fulluser; + const char *server_name; +#ifdef HAVE_LDAP_INITIALIZE + + /* + * For OpenLDAP, allow empty hostname if we have a basedn. We'll look for + * servers with DNS SRV records via OpenLDAP library facilities. + */ + if ((!port->hba->ldapserver || port->hba->ldapserver[0] == '\0') && + (!port->hba->ldapbasedn || port->hba->ldapbasedn[0] == '\0')) + { + ereport(LOG, + (errmsg("LDAP server not specified, and no ldapbasedn"))); + return STATUS_ERROR; + } +#else if (!port->hba->ldapserver || port->hba->ldapserver[0] == '\0') { ereport(LOG, (errmsg("LDAP server not specified"))); return STATUS_ERROR; } +#endif + + /* + * If we're using SRV records, we don't have a server name so we'll just + * show an empty string in error messages. + */ + server_name = port->hba->ldapserver ? port->hba->ldapserver : ""; if (port->hba->ldapport == 0) { @@ -2630,7 +2702,7 @@ CheckLDAPAuth(Port *port) ereport(LOG, (errmsg("could not perform initial LDAP bind for ldapbinddn \"%s\" on server \"%s\": %s", port->hba->ldapbinddn ? port->hba->ldapbinddn : "", - port->hba->ldapserver, + server_name, ldap_err2string(r)), errdetail_for_ldap(ldap))); ldap_unbind(ldap); @@ -2658,7 +2730,7 @@ CheckLDAPAuth(Port *port) { ereport(LOG, (errmsg("could not search LDAP for filter \"%s\" on server \"%s\": %s", - filter, port->hba->ldapserver, ldap_err2string(r)), + filter, server_name, ldap_err2string(r)), errdetail_for_ldap(ldap))); ldap_unbind(ldap); pfree(passwd); @@ -2673,14 +2745,14 @@ CheckLDAPAuth(Port *port) ereport(LOG, (errmsg("LDAP user \"%s\" does not exist", port->user_name), errdetail("LDAP search for filter \"%s\" on server \"%s\" returned no entries.", - filter, port->hba->ldapserver))); + filter, server_name))); else ereport(LOG, (errmsg("LDAP user \"%s\" is not unique", port->user_name), errdetail_plural("LDAP search for filter \"%s\" on server \"%s\" returned %d entry.", "LDAP search for filter \"%s\" on server \"%s\" returned %d entries.", count, - filter, port->hba->ldapserver, count))); + filter, server_name, count))); ldap_unbind(ldap); pfree(passwd); @@ -2698,7 +2770,7 @@ CheckLDAPAuth(Port *port) (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, + filter, server_name, ldap_err2string(error)), errdetail_for_ldap(ldap))); ldap_unbind(ldap); @@ -2719,7 +2791,7 @@ CheckLDAPAuth(Port *port) { ereport(LOG, (errmsg("could not unbind after searching for user \"%s\" on server \"%s\"", - fulluser, port->hba->ldapserver))); + fulluser, server_name))); pfree(passwd); pfree(fulluser); return STATUS_ERROR; @@ -2750,7 +2822,7 @@ CheckLDAPAuth(Port *port) { ereport(LOG, (errmsg("LDAP login failed for user \"%s\" on server \"%s\": %s", - fulluser, port->hba->ldapserver, ldap_err2string(r)), + fulluser, server_name, ldap_err2string(r)), errdetail_for_ldap(ldap))); ldap_unbind(ldap); pfree(passwd); diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index 59de1b76395..ce9bca868cc 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -1500,7 +1500,10 @@ parse_hba_line(TokenizedLine *tok_line, int elevel) */ if (parsedline->auth_method == uaLDAP) { +#ifndef HAVE_LDAP_INITIALIZE + /* Not mandatory for OpenLDAP, because it can use DNS SRV records */ MANDATORY_AUTH_ARG(parsedline->ldapserver, "ldapserver", "ldap"); +#endif /* * LDAP can operate in two modes: either with a direct bind, using |