Index: doc/src/sgml/client-auth.sgml
===================================================================
RCS file: /cvsroot/pgsql/doc/src/sgml/client-auth.sgml,v
retrieving revision 1.124
diff -c -r1.124 client-auth.sgml
*** doc/src/sgml/client-auth.sgml 1 Oct 2009 01:58:57 -0000 1.124
--- doc/src/sgml/client-auth.sgml 12 Dec 2009 21:29:17 -0000
***************
*** 1202,1208 ****
! The server will bind to the distinguished name constructed as
prefix> username> suffix>.
Typically, the prefix> parameter is used to specify
cn=>, or DOMAIN>\> in an Active
--- 1202,1209 ----
! LDAP authentication can operate in two modes. In the first mode,
! the server will bind to the distinguished name constructed as
prefix> username> suffix>.
Typically, the prefix> parameter is used to specify
cn=>, or DOMAIN>\> in an Active
***************
*** 1211,1216 ****
--- 1212,1234 ----
+ In the second mode, the server first binds to the LDAP directory with
+ a fixed username and password, specified with ldapbinduser>
+ and 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 ldapbasedn>, and will try to
+ do an exact match of the attribute specified in
+ ldapsearchattribute>. If no attribute is specified, the
+ 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.
+
+
+
The following configuration options are supported for LDAP:
***************
*** 1222,1231 ****
ldapprefix
! String to prepend to the username when forming the DN to bind as.
--- 1240,1270 ----
+ ldapport
+
+
+ Port number on LDAP server to connect to. If no port is specified,
+ the default port in the LDAP library will be used.
+
+
+
+
+ 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.
+
+
+
+
ldapprefix
! String to prepend to the username when forming the DN to bind as,
! when doing simple bind authentication.
***************
*** 1233,1262 ****
ldapsuffix
! String to append to the username when forming the DN to bind as.
! ldapport
! Port number on LDAP server to connect to. If no port is specified,
! the default port in the LDAP library will be used.
! 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.
--- 1272,1318 ----
ldapsuffix
! String to append to the username when forming the DN to bind as,
! when doing simple bind authentication.
! ldapbasedn
! DN to root the search for the user in, when doing search+bind
! authentication.
! ldapbinddn
! DN of user to bind to the directory with to perform the search when
! doing search+bind authentication.
+
+ ldapbindpasswd
+
+
+ Password for user to bind to the directory with to perform the search
+ when doing search+bind authentication.
+
+
+
+
+ ldapsearchattribute
+
+
+ Attribute to match against the username in the search when doing
+ search+bind authentication.
+
+
+
Index: src/backend/libpq/auth.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/libpq/auth.c,v
retrieving revision 1.187
diff -c -r1.187 auth.c
*** src/backend/libpq/auth.c 16 Oct 2009 22:08:36 -0000 1.187
--- src/backend/libpq/auth.c 12 Dec 2009 21:29:17 -0000
***************
*** 2096,2135 ****
*/
#ifdef USE_LDAP
static int
! CheckLDAPAuth(Port *port)
{
- 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;
! }
! ldap = ldap_init(port->hba->ldapserver, port->hba->ldapport);
! if (!ldap)
{
#ifndef WIN32
ereport(LOG,
--- 2096,2113 ----
*/
#ifdef USE_LDAP
+ /*
+ * Initialize a connection to the LDAP server, including setting up
+ * TLS if requested.
+ */
static int
! InitializeLDAPConnection(Port *port, LDAP **ldap)
{
int ldapversion = LDAP_VERSION3;
! int r;
! *ldap = ldap_init(port->hba->ldapserver, port->hba->ldapport);
! if (!*ldap)
{
#ifndef WIN32
ereport(LOG,
***************
*** 2143,2151 ****
return STATUS_ERROR;
}
! if ((r = ldap_set_option(ldap, LDAP_OPT_PROTOCOL_VERSION, &ldapversion)) != LDAP_SUCCESS)
{
! ldap_unbind(ldap);
ereport(LOG,
(errmsg("could not set LDAP protocol version: error code %d", r)));
return STATUS_ERROR;
--- 2121,2129 ----
return STATUS_ERROR;
}
! if ((r = ldap_set_option(*ldap, LDAP_OPT_PROTOCOL_VERSION, &ldapversion)) != LDAP_SUCCESS)
{
! ldap_unbind(*ldap);
ereport(LOG,
(errmsg("could not set LDAP protocol version: error code %d", r)));
return STATUS_ERROR;
***************
*** 2154,2160 ****
if (port->hba->ldaptls)
{
#ifndef WIN32
! if ((r = ldap_start_tls_s(ldap, NULL, NULL)) != LDAP_SUCCESS)
#else
static __ldap_start_tls_sA _ldap_start_tls_sA = NULL;
--- 2132,2138 ----
if (port->hba->ldaptls)
{
#ifndef WIN32
! if ((r = ldap_start_tls_s(*ldap, NULL, NULL)) != LDAP_SUCCESS)
#else
static __ldap_start_tls_sA _ldap_start_tls_sA = NULL;
***************
*** 2174,2180 ****
* should never happen since we import other files from
* wldap32, but check anyway
*/
! ldap_unbind(ldap);
ereport(LOG,
(errmsg("could not load wldap32.dll")));
return STATUS_ERROR;
--- 2152,2158 ----
* should never happen since we import other files from
* wldap32, but check anyway
*/
! ldap_unbind(*ldap);
ereport(LOG,
(errmsg("could not load wldap32.dll")));
return STATUS_ERROR;
***************
*** 2182,2188 ****
_ldap_start_tls_sA = (__ldap_start_tls_sA) GetProcAddress(ldaphandle, "ldap_start_tls_sA");
if (_ldap_start_tls_sA == NULL)
{
! 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.")));
--- 2160,2166 ----
_ldap_start_tls_sA = (__ldap_start_tls_sA) GetProcAddress(ldaphandle, "ldap_start_tls_sA");
if (_ldap_start_tls_sA == NULL)
{
! 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,2215 ****
* per process and is automatically cleaned up on process exit.
*/
}
! if ((r = _ldap_start_tls_sA(ldap, NULL, NULL, NULL, NULL)) != LDAP_SUCCESS)
#endif
{
! 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';
r = ldap_simple_bind_s(ldap, fulluser, passwd);
ldap_unbind(ldap);
--- 2173,2374 ----
* per process and is automatically cleaned up on process exit.
*/
}
! if ((r = _ldap_start_tls_sA(*ldap, NULL, NULL, NULL, NULL)) != LDAP_SUCCESS)
#endif
{
! ldap_unbind(*ldap);
ereport(LOG,
(errmsg("could not start LDAP TLS session: error code %d", r)));
return STATUS_ERROR;
}
}
! 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,2227 ****
--- 2378,2389 ----
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 */
Index: src/backend/libpq/hba.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/libpq/hba.c,v
retrieving revision 1.192
diff -c -r1.192 hba.c
*** src/backend/libpq/hba.c 3 Oct 2009 20:04:39 -0000 1.192
--- src/backend/libpq/hba.c 12 Dec 2009 21:29:17 -0000
***************
*** 1103,1108 ****
--- 1103,1128 ----
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,1161 ****
--- 1176,1212 ----
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;
+ }
}
/*
Index: src/include/libpq/hba.h
===================================================================
RCS file: /cvsroot/pgsql/src/include/libpq/hba.h,v
retrieving revision 1.59
diff -c -r1.59 hba.h
*** src/include/libpq/hba.h 1 Oct 2009 01:58:58 -0000 1.59
--- src/include/libpq/hba.h 12 Dec 2009 21:29:17 -0000
***************
*** 61,66 ****
--- 61,70 ----
bool ldaptls;
char *ldapserver;
int ldapport;
+ char *ldapbinddn;
+ char *ldapbindpasswd;
+ char *ldapsearchattribute;
+ char *ldapbasedn;
char *ldapprefix;
char *ldapsuffix;
bool clientcert;