summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/src/sgml/client-auth.sgml80
-rw-r--r--src/backend/libpq/auth.c248
-rw-r--r--src/backend/libpq/hba.c53
-rw-r--r--src/include/libpq/hba.h6
4 files changed, 330 insertions, 57 deletions
diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml
index 6442aa1efe..e1d8c9503b 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
@@ -1211,6 +1212,23 @@ omicron bryanh guest1
</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>
<varlistentry>
@@ -1222,10 +1240,31 @@ omicron bryanh guest1
</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 1e080465b6..7b6b5a5a07 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 a51ea53082..65d5beadd3 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 b444e09ecf..8ee71a7e09 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;