diff --git a/configure b/configure
index 82f332f545..d88863e50c 100755
--- a/configure
+++ b/configure
@@ -10424,6 +10424,17 @@ fi
else
LDAP_LIBS_FE="-lldap $EXTRA_LDAP_LIBS"
fi
+ for ac_func in ldap_initialize
+do :
+ ac_fn_c_check_func "$LINENO" "ldap_initialize" "ac_cv_func_ldap_initialize"
+if test "x$ac_cv_func_ldap_initialize" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LDAP_INITIALIZE 1
+_ACEOF
+
+fi
+done
+
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ldap_bind in -lwldap32" >&5
$as_echo_n "checking for ldap_bind in -lwldap32... " >&6; }
diff --git a/configure.in b/configure.in
index 4a20c9da96..4968b67bf9 100644
--- a/configure.in
+++ b/configure.in
@@ -1106,6 +1106,7 @@ if test "$with_ldap" = yes ; then
else
LDAP_LIBS_FE="-lldap $EXTRA_LDAP_LIBS"
fi
+ AC_CHECK_FUNCS([ldap_initialize])
else
AC_CHECK_LIB(wldap32, ldap_bind, [], [AC_MSG_ERROR([library 'wldap32' is required for LDAP])])
LDAP_LIBS_FE="-lwldap32"
diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml
index c8a1bc79aa..53832d08e2 100644
--- a/doc/src/sgml/client-auth.sgml
+++ b/doc/src/sgml/client-auth.sgml
@@ -1502,19 +1502,40 @@ omicron bryanh guest1
+
+ ldapscheme
+
+
+ Set to ldaps to use LDAPS. This is a non-standard
+ way of using LDAP over SSL, supported by some LDAP server
+ implementations. See also the ldaptls option for
+ an alternative.
+
+
+
ldaptls
- Set to 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 — the connection to the client
- will still be unencrypted unless SSL is used.
+ Set to 1 to make the connection between PostgreSQL and the LDAP server
+ use TLS encryption. This uses the StartTLS
+ operation per RFC 4513. See also the ldapscheme
+ option for an alternative.
+
+
+ Note that using ldapscheme or
+ ldaptls only encrypts the traffic between the
+ PostgreSQL server and the LDAP server. The connection between the
+ PostgreSQL server and the PostgreSQL client will still be unencrypted
+ unless SSL is used there as well.
+
+
+
The following options are used in simple bind mode only:
@@ -1536,7 +1557,9 @@ omicron bryanh guest1
+
+
The following options are used in search+bind mode only:
@@ -1594,7 +1617,7 @@ 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][?[filter]]]]
+ldap[s]://host[:port]/basedn[?[attribute][?[scope][?[filter]]]]
scope must be one
of base, one, sub,
@@ -1608,16 +1631,19 @@ ldap://host[:port]/
- For non-anonymous binds, ldapbinddn
- and ldapbindpasswd must be specified as separate
- options.
+ The URL scheme ldaps chooses the LDAPS method for
+ making LDAP connections over SSL, equivalent to using
+ ldapscheme=ldaps. To use encrypted LDAP
+ connections using the StartTLS operation, use the
+ normal URL scheme ldap and specify the
+ ldaptls option in addition to
+ ldapurl.
- To use encrypted LDAP connections, the ldaptls
- option has to be used in addition to ldapurl.
- The ldaps URL scheme (direct SSL connection) is not
- supported.
+ For non-anonymous binds, ldapbinddn
+ and ldapbindpasswd must be specified as separate
+ options.
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 1d49ed784f..3560edc33a 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2355,22 +2355,61 @@ static int errdetail_for_ldap(LDAP *ldap);
static int
InitializeLDAPConnection(Port *port, LDAP **ldap)
{
+ const char *scheme;
int ldapversion = LDAP_VERSION3;
int r;
- *ldap = ldap_init(port->hba->ldapserver, port->hba->ldapport);
+ scheme = port->hba->ldapscheme;
+ if (scheme == NULL)
+ scheme = "ldap";
+#ifdef WIN32
+ *ldap = ldap_sslinit(port->hba->ldapserver,
+ port->hba->ldapport,
+ strcmp(scheme, "ldaps") == 0);
if (!*ldap)
{
-#ifndef WIN32
- ereport(LOG,
- (errmsg("could not initialize LDAP: %m")));
-#else
ereport(LOG,
(errmsg("could not initialize LDAP: error code %d",
(int) LdapGetLastError())));
-#endif
+
return STATUS_ERROR;
}
+#else
+#ifdef HAVE_LDAP_INITIALIZE
+ {
+ char *uri;
+
+ uri = psprintf("%s://%s:%d", scheme, port->hba->ldapserver,
+ port->hba->ldapport);
+ r = ldap_initialize(ldap, uri);
+ pfree(uri);
+ if (r != LDAP_SUCCESS)
+ {
+ ereport(LOG,
+ (errmsg("could not initialize LDAP: %s",
+ ldap_err2string(r))));
+
+ return STATUS_ERROR;
+ }
+ }
+#else
+ if (strcmp(scheme, "ldaps") == 0)
+ {
+ ereport(LOG,
+ (errmsg("ldaps not supported with this LDAP library")));
+
+ return STATUS_ERROR;
+ }
+ *ldap = ldap_init(port->hba->ldapserver, port->hba->ldapport);
+ if (!*ldap)
+ {
+ ereport(LOG,
+ (errmsg("could not initialize LDAP: %m")));
+
+ return STATUS_ERROR;
+ }
+#endif
+#endif
if ((r = ldap_set_option(*ldap, LDAP_OPT_PROTOCOL_VERSION, &ldapversion)) != LDAP_SUCCESS)
{
@@ -2493,7 +2532,13 @@ CheckLDAPAuth(Port *port)
}
if (port->hba->ldapport == 0)
- port->hba->ldapport = LDAP_PORT;
+ {
+ if (port->hba->ldapscheme != NULL &&
+ strcmp(port->hba->ldapscheme, "ldaps") == 0)
+ port->hba->ldapport = LDAPS_PORT;
+ else
+ port->hba->ldapport = LDAP_PORT;
+ }
sendAuthRequest(port, AUTH_REQ_PASSWORD, NULL, 0);
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index f760d24886..aa20f266b8 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -1728,7 +1728,8 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
return false;
}
- if (strcmp(urldata->lud_scheme, "ldap") != 0)
+ if (strcmp(urldata->lud_scheme, "ldap") != 0 &&
+ strcmp(urldata->lud_scheme, "ldaps") != 0)
{
ereport(elevel,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
@@ -1739,6 +1740,8 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
return false;
}
+ if (urldata->lud_scheme)
+ hbaline->ldapscheme = pstrdup(urldata->lud_scheme);
if (urldata->lud_host)
hbaline->ldapserver = pstrdup(urldata->lud_host);
hbaline->ldapport = urldata->lud_port;
@@ -1766,6 +1769,17 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
else
hbaline->ldaptls = false;
}
+ else if (strcmp(name, "ldapscheme") == 0)
+ {
+ REQUIRE_AUTH_OPTION(uaLDAP, "ldapscheme", "ldap");
+ if (strcmp(val, "ldap") != 0 && strcmp(val, "ldaps") != 0)
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("invalid ldapscheme value: \"%s\"", val),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, HbaFileName)));
+ hbaline->ldapscheme = pstrdup(val);
+ }
else if (strcmp(name, "ldapserver") == 0)
{
REQUIRE_AUTH_OPTION(uaLDAP, "ldapserver", "ldap");
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index e711bee8bf..5f68f4c666 100644
--- a/src/include/libpq/hba.h
+++ b/src/include/libpq/hba.h
@@ -75,6 +75,7 @@ typedef struct HbaLine
char *pamservice;
bool pam_use_hostname;
bool ldaptls;
+ char *ldapscheme;
char *ldapserver;
int ldapport;
char *ldapbinddn;
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 0aa6be4666..27b1368721 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -310,6 +310,9 @@
/* Define to 1 if you have the header file. */
#undef HAVE_LDAP_H
+/* Define to 1 if you have the `ldap_initialize' function. */
+#undef HAVE_LDAP_INITIALIZE
+
/* Define to 1 if you have the `crypto' library (-lcrypto). */
#undef HAVE_LIBCRYPTO
diff --git a/src/test/ldap/t/001_auth.pl b/src/test/ldap/t/001_auth.pl
index 38760ece61..5508da459f 100644
--- a/src/test/ldap/t/001_auth.pl
+++ b/src/test/ldap/t/001_auth.pl
@@ -2,7 +2,7 @@ use strict;
use warnings;
use TestLib;
use PostgresNode;
-use Test::More tests => 15;
+use Test::More tests => 19;
my ($slapd, $ldap_bin_dir, $ldap_schema_dir);
@@ -33,13 +33,16 @@ elsif ($^O eq 'freebsd')
$ENV{PATH} = "$ldap_bin_dir:$ENV{PATH}" if $ldap_bin_dir;
my $ldap_datadir = "${TestLib::tmp_check}/openldap-data";
+my $slapd_certs = "${TestLib::tmp_check}/slapd-certs";
my $slapd_conf = "${TestLib::tmp_check}/slapd.conf";
my $slapd_pidfile = "${TestLib::tmp_check}/slapd.pid";
my $slapd_logfile = "${TestLib::tmp_check}/slapd.log";
my $ldap_conf = "${TestLib::tmp_check}/ldap.conf";
my $ldap_server = 'localhost';
my $ldap_port = int(rand() * 16384) + 49152;
+my $ldaps_port = $ldap_port + 1;
my $ldap_url = "ldap://$ldap_server:$ldap_port";
+my $ldaps_url = "ldaps://$ldap_server:$ldaps_port";
my $ldap_basedn = 'dc=example,dc=net';
my $ldap_rootdn = 'cn=Manager,dc=example,dc=net';
my $ldap_rootpw = 'secret';
@@ -63,13 +66,27 @@ access to *
database ldif
directory $ldap_datadir
+TLSCACertificateFile $slapd_certs/ca.crt
+TLSCertificateFile $slapd_certs/server.crt
+TLSCertificateKeyFile $slapd_certs/server.key
+
suffix "dc=example,dc=net"
rootdn "$ldap_rootdn"
rootpw $ldap_rootpw});
-mkdir $ldap_datadir or die;
+# don't bother to check the server's cert (though perhaps we should)
+append_to_file($ldap_conf,
+qq{TLS_REQCERT never
+});
-system_or_bail $slapd, '-f', $slapd_conf, '-h', $ldap_url;
+mkdir $ldap_datadir or die;
+mkdir $slapd_certs or die;
+
+system_or_bail "openssl", "req", "-new", "-nodes", "-keyout", "$slapd_certs/ca.key", "-x509", "-out", "$slapd_certs/ca.crt", "-subj", "/cn=CA";
+system_or_bail "openssl", "req", "-new", "-nodes", "-keyout", "$slapd_certs/server.key", "-out", "$slapd_certs/server.csr", "-subj", "/cn=server";
+system_or_bail "openssl", "x509", "-req", "-in", "$slapd_certs/server.csr", "-CA", "$slapd_certs/ca.crt", "-CAkey", "$slapd_certs/ca.key", "-CAcreateserial", "-out", "$slapd_certs/server.crt";
+
+system_or_bail $slapd, '-f', $slapd_conf, '-h', "$ldap_url $ldaps_url";
END
{
@@ -81,6 +98,7 @@ chmod 0600, $ldap_pwfile or die;
$ENV{'LDAPURI'} = $ldap_url;
$ENV{'LDAPBINDDN'} = $ldap_rootdn;
+$ENV{'LDAPCONF'} = $ldap_conf;
note "loading LDAP data";
@@ -178,9 +196,44 @@ test_access($node, 'test1', 0, 'combined LDAP URL and search filter');
note "diagnostic message";
+# note bad ldapprefix with a question mark that triggers a diagnostic message
unlink($node->data_dir . '/pg_hba.conf');
-$node->append_conf('pg_hba.conf', qq{local all all ldap ldapserver=$ldap_server ldapport=$ldap_port ldapprefix="uid=" ldapsuffix=",dc=example,dc=net" ldaptls=1});
+$node->append_conf('pg_hba.conf', qq{local all all ldap ldapserver=$ldap_server ldapport=$ldap_port ldapprefix="?uid=" ldapsuffix=""});
$node->reload;
$ENV{"PGPASSWORD"} = 'secret1';
-test_access($node, 'test1', 2, 'any attempt fails due to unsupported TLS');
+test_access($node, 'test1', 2, 'any attempt fails due to bad search pattern');
+
+note "TLS";
+
+# request StartTLS with ldaptls=1
+unlink($node->data_dir . '/pg_hba.conf');
+$node->append_conf('pg_hba.conf', qq{local all all ldap ldapserver=$ldap_server ldapport=$ldap_port ldapbasedn="$ldap_basedn" ldapsearchfilter="(uid=\$username)" ldaptls=1});
+$node->reload;
+
+$ENV{"PGPASSWORD"} = 'secret1';
+test_access($node, 'test1', 0, 'StartTLS');
+
+# request LDAPS with ldapscheme=ldaps
+unlink($node->data_dir . '/pg_hba.conf');
+$node->append_conf('pg_hba.conf', qq{local all all ldap ldapserver=$ldap_server ldapscheme=ldaps ldapport=$ldaps_port ldapbasedn="$ldap_basedn" ldapsearchfilter="(uid=\$username)"});
+$node->reload;
+
+$ENV{"PGPASSWORD"} = 'secret1';
+test_access($node, 'test1', 0, 'LDAPS');
+
+# request LDAPS with ldapurl=ldaps://...
+unlink($node->data_dir . '/pg_hba.conf');
+$node->append_conf('pg_hba.conf', qq{local all all ldap ldapurl="$ldaps_url/$ldap_basedn??sub?(uid=\$username)"});
+$node->reload;
+
+$ENV{"PGPASSWORD"} = 'secret1';
+test_access($node, 'test1', 0, 'LDAPS with URL');
+
+# bad combination of LDAPS and StartTLS
+unlink($node->data_dir . '/pg_hba.conf');
+$node->append_conf('pg_hba.conf', qq{local all all ldap ldapurl="$ldaps_url/$ldap_basedn??sub?(uid=\$username)" ldaptls=1});
+$node->reload;
+
+$ENV{"PGPASSWORD"} = 'secret1';
+test_access($node, 'test1', 2, 'bad combination of LDAPS and StartTLS');