LDAP where DN does not include UID attribute
Following a discussion on the pgsql-admin list <
http://archives.postgresql.org/pgsql-admin/2009-09/msg00075.php>, I have
created a patch to (optionally) allow PostgreSQL to do a LDAP search to
determine the user's DN (as is done in Apache, MediaWiki, Bugzilla, et al.)
instead of building the DN from a prefix and suffix.
This is necessary for schemas where the login attribute is not in the DN,
such as is described here <
http://www.ldapman.org/articles/intro_to_ldap.html#individual> (look for
"name-based"). This patch is against PostgreSQL 8.4.0 from Debian
Lenny-backports. If this would be a welcome addition, I can port it forward
to the latest from postgresql.org.
Thanks in advance for your feedback.
Robert
Attachments:
pg-ldap.patchtext/x-patch; charset=US-ASCII; name=pg-ldap.patchDownload
diff -ru postgresql-8.4-8.4.0-original/src/backend/libpq/auth.c postgresql-8.4-8.4.0/src/backend/libpq/auth.c
--- postgresql-8.4-8.4.0-original/src/backend/libpq/auth.c 2009-06-25 04:30:08.000000000 -0700
+++ postgresql-8.4-8.4.0/src/backend/libpq/auth.c 2009-09-16 22:33:46.000000000 -0700
@@ -2150,10 +2150,75 @@
}
}
- snprintf(fulluser, sizeof(fulluser), "%s%s%s",
- port->hba->ldapprefix ? port->hba->ldapprefix : "",
- port->user_name,
- port->hba->ldapsuffix ? port->hba->ldapsuffix : "");
+ if (port->hba->ldapbasedn)
+ {
+ char filter[NAMEDATALEN + 10]; // FIXME: maybe there's a preferred way to pick this size?
+ LDAPMessage* search_message;
+ char* attributes[2];
+ LDAPMessage* entry;
+ char* dn;
+
+ /* it seems that you need to search for at least one attribute, else /all/ attributes are returned */
+ attributes[0] = "uid";
+ attributes[1] = NULL;
+
+ snprintf(filter, sizeof(filter), "(uid=%s)", port->user_name);
+ filter[sizeof(filter) - 1] = '\0';
+
+ r = ldap_search_s(ldap,
+ port->hba->ldapbasedn,
+ LDAP_SCOPE_SUBTREE,
+ filter,
+ attributes,
+ 0,
+ &search_message);
+
+ if (r != LDAP_SUCCESS)
+ {
+ ereport(LOG,
+ (errmsg("LDAP search failed for user \"%s\" on server \"%s\": error code %d",
+ fulluser, port->hba->ldapserver, r)));
+ 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 user \"%s\" on server \"%s\": no such user",
+ fulluser, port->hba->ldapserver)));
+ else
+ ereport(LOG,
+ (errmsg("LDAP search failed for user \"%s\" on server \"%s\": user is not unique (%d matches)",
+ fulluser, port->hba->ldapserver, ldap_count_entries(ldap, search_message))));
+
+ 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;
+ r = ldap_get_option(ldap, LDAP_OPT_RESULT_CODE, &error);
+ ereport(LOG,
+ (errmsg("LDAP search failed for user \"%s\" on server \"%s\": %s",
+ fulluser, port->hba->ldapserver, ldap_err2string(error))));
+ ldap_msgfree(search_message);
+ return STATUS_ERROR;
+ }
+ strncpy(fulluser, dn, sizeof(fulluser));
+
+ ldap_memfree(dn);
+ ldap_msgfree(search_message);
+ }
+ else
+ 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);
diff -ru postgresql-8.4-8.4.0-original/src/backend/libpq/hba.c postgresql-8.4-8.4.0/src/backend/libpq/hba.c
--- postgresql-8.4-8.4.0-original/src/backend/libpq/hba.c 2009-06-24 06:39:42.000000000 -0700
+++ postgresql-8.4-8.4.0/src/backend/libpq/hba.c 2009-09-16 22:19:59.000000000 -0700
@@ -1032,6 +1032,11 @@
return false;
}
}
+ 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");
diff -ru postgresql-8.4-8.4.0-original/src/include/libpq/hba.h postgresql-8.4-8.4.0/src/include/libpq/hba.h
--- postgresql-8.4-8.4.0-original/src/include/libpq/hba.h 2009-06-11 07:49:11.000000000 -0700
+++ postgresql-8.4-8.4.0/src/include/libpq/hba.h 2009-09-16 22:20:07.000000000 -0700
@@ -53,6 +53,7 @@
bool ldaptls;
char *ldapserver;
int ldapport;
+ char *ldapbasedn;
char *ldapprefix;
char *ldapsuffix;
bool clientcert;
On Thu, Sep 17, 2009 at 18:02, Robert Fleming <fleminra@gmail.com> wrote:
Following a discussion on the pgsql-admin list
<http://archives.postgresql.org/pgsql-admin/2009-09/msg00075.php>, I have
created a patch to (optionally) allow PostgreSQL to do a LDAP search to
determine the user's DN (as is done in Apache, MediaWiki, Bugzilla, et al.)
instead of building the DN from a prefix and suffix.
This is necessary for schemas where the login attribute is not in the DN,
such as is described here
<http://www.ldapman.org/articles/intro_to_ldap.html#individual> (look for
"name-based"). This patch is against PostgreSQL 8.4.0 from Debian
Lenny-backports. If this would be a welcome addition, I can port it forward
to the latest from postgresql.org.
Thanks in advance for your feedback.
This sounds like a very useful feature, and one that I can then remove
from my personal TODO list without having to do much work :-)
A couple of comments:
First of all, please read up on the PostgreSQL coding style, or at
least look at the code around yours. This doesn't look anything like
our standards.
Second, this appears to require an anonymous bind to the directory,
which is something we should not encourage people to enable on their
LDAP servers. I think we need to also take parameters with a DN and a
password to bind with in order to do the search, and then re-bind as
the user when found.
--
Magnus Hagander
Me: http://www.hagander.net/
Work: http://www.redpill-linpro.com/
On Thu, Sep 17, 2009 at 11:15 AM, Magnus Hagander <magnus@hagander.net>wrote:
On Thu, Sep 17, 2009 at 18:02, Robert Fleming <fleminra@gmail.com> wrote:
Following a discussion on the pgsql-admin list
<http://archives.postgresql.org/pgsql-admin/2009-09/msg00075.php>, Ihave
created a patch to (optionally) allow PostgreSQL to do a LDAP search to
determine the user's DN (as is done in Apache, MediaWiki, Bugzilla, etal.)
instead of building the DN from a prefix and suffix.
This is necessary for schemas where the login attribute is not in the DN,
such as is described here
<http://www.ldapman.org/articles/intro_to_ldap.html#individual> (lookfor
"name-based"). This patch is against PostgreSQL 8.4.0 from Debian
Lenny-backports. If this would be a welcome addition, I can port itforward
to the latest from postgresql.org.
Thanks in advance for your feedback.This sounds like a very useful feature, and one that I can then remove
from my personal TODO list without having to do much work :-)A couple of comments:
First of all, please read up on the PostgreSQL coding style, or at
least look at the code around yours. This doesn't look anything like
our standards.
Sorry, the 8.1 manual said all contributions go through pgindent so I didn't
spend too much time on that. I see that the 8.4 manual clarifies that
pgindent won't get run till release time. In any case, I've re-formatted
the patch using the Emacs settings from the 8.1 manual (why are they gone
from the 8.4 manual)? It seems like most of the rest of the Coding
Conventions have to do with error reporting. Do please let me know if
there's something else I can do.
Second, this appears to require an anonymous bind to the directory,
which is something we should not encourage people to enable on their
LDAP servers. I think we need to also take parameters with a DN and a
password to bind with in order to do the search, and then re-bind as
the user when found.
The new patch implements the initial bind using new configuration parameters
"ldapbinddn" and "ldapbindpasswd". I've also added a "ldapsearchattribute"
just to be complete.
Robert
Attachments:
pg84-ldap.patchtext/x-patch; charset=US-ASCII; name=pg84-ldap.patchDownload
diff -ru postgresql-8.4-8.4.0-original/src/backend/libpq/auth.c postgresql-8.4-8.4.0/src/backend/libpq/auth.c
--- postgresql-8.4-8.4.0-original/src/backend/libpq/auth.c 2009-06-25 04:30:08.000000000 -0700
+++ postgresql-8.4-8.4.0/src/backend/libpq/auth.c 2009-09-17 18:12:57.000000000 -0700
@@ -2150,10 +2150,110 @@
}
}
- snprintf(fulluser, sizeof(fulluser), "%s%s%s",
- port->hba->ldapprefix ? port->hba->ldapprefix : "",
- port->user_name,
- port->hba->ldapsuffix ? port->hba->ldapsuffix : "");
+ if (port->hba->ldapbasedn)
+ {
+ char filter[NAMEDATALEN + 10]; // FIXME: maybe there's a preferred way to pick this size?
+ LDAPMessage* search_message;
+ char* attributes[2];
+ LDAPMessage* entry;
+ char* dn;
+
+ /* bind for searching */
+ 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("LDAP initial bind failed 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] = "uid";
+ attributes[1] = NULL;
+
+ snprintf(filter, sizeof(filter), "(%s=%s)",
+ port->hba->ldapsearchattribute ? port->hba->ldapsearchattribute : "uid",
+ port->user_name); /* FIXME: sanitize user_name? */
+ filter[sizeof(filter) - 1] = '\0';
+
+ r = ldap_search_s(ldap,
+ port->hba->ldapbasedn,
+ LDAP_SCOPE_SUBTREE,
+ filter,
+ attributes,
+ 0,
+ &search_message);
+
+ if (r != LDAP_SUCCESS)
+ {
+ ereport(LOG,
+ (errmsg("LDAP search failed for filter \"%s\" on server \"%s\": error code %d",
+ filter, port->hba->ldapserver, r)));
+ 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))));
+
+ 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_RESULT_CODE, &error);
+ ereport(LOG,
+ (errmsg("LDAP get_dn() for the first entry matching \"%s\" on server \"%s\": %s",
+ filter, port->hba->ldapserver, ldap_err2string(error))));
+ ldap_msgfree(search_message);
+ return STATUS_ERROR;
+ }
+ strncpy(fulluser, dn, sizeof(fulluser));
+
+ ldap_memfree(dn);
+ ldap_msgfree(search_message);
+
+ /* unbind */
+ r = ldap_unbind_s(ldap);
+ if (r != LDAP_SUCCESS)
+ {
+ int error;
+ (void)ldap_get_option(ldap, LDAP_OPT_RESULT_CODE, &error);
+ ereport(LOG,
+ (errmsg("LDAP unbind failed for user \"%s\" on server \"%s\": %s",
+ fulluser, port->hba->ldapserver, ldap_err2string(error))));
+ return STATUS_ERROR;
+ }
+
+ /* re-init */
+ ldap = ldap_init(port->hba->ldapserver, port->hba->ldapport);
+ if (!ldap)
+ {
+ printf("could not initialize LDAP: error code %d",
+ errno);
+ return STATUS_ERROR;
+ }
+ }
+ else
+ 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);
diff -ru postgresql-8.4-8.4.0-original/src/backend/libpq/hba.c postgresql-8.4-8.4.0/src/backend/libpq/hba.c
--- postgresql-8.4-8.4.0-original/src/backend/libpq/hba.c 2009-06-24 06:39:42.000000000 -0700
+++ postgresql-8.4-8.4.0/src/backend/libpq/hba.c 2009-09-17 17:46:21.000000000 -0700
@@ -1032,6 +1032,26 @@
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");
diff -ru postgresql-8.4-8.4.0-original/src/include/libpq/hba.h postgresql-8.4-8.4.0/src/include/libpq/hba.h
--- postgresql-8.4-8.4.0-original/src/include/libpq/hba.h 2009-06-11 07:49:11.000000000 -0700
+++ postgresql-8.4-8.4.0/src/include/libpq/hba.h 2009-09-17 17:46:00.000000000 -0700
@@ -53,6 +53,10 @@
bool ldaptls;
char *ldapserver;
int ldapport;
+ char *ldapbinddn;
+ char *ldapbindpasswd;
+ char *ldapsearchattribute;
+ char *ldapbasedn;
char *ldapprefix;
char *ldapsuffix;
bool clientcert;
On Thu, Sep 17, 2009 at 6:24 PM, Robert Fleming <fleminra@gmail.com> wrote:
The new patch ...
s/printf/ereport(LOG,/
On Fri, Sep 18, 2009 at 02:24, Robert Fleming <fleminra@gmail.com> wrote:
On Thu, Sep 17, 2009 at 11:15 AM, Magnus Hagander <magnus@hagander.net>
wrote:On Thu, Sep 17, 2009 at 18:02, Robert Fleming <fleminra@gmail.com> wrote:
Following a discussion on the pgsql-admin list
<http://archives.postgresql.org/pgsql-admin/2009-09/msg00075.php>, I
have
created a patch to (optionally) allow PostgreSQL to do a LDAP search to
determine the user's DN (as is done in Apache, MediaWiki, Bugzilla, et
al.)
instead of building the DN from a prefix and suffix.
This is necessary for schemas where the login attribute is not in the
DN,
such as is described here
<http://www.ldapman.org/articles/intro_to_ldap.html#individual> (look
for
"name-based"). This patch is against PostgreSQL 8.4.0 from Debian
Lenny-backports. If this would be a welcome addition, I can port it
forward
to the latest from postgresql.org.
Thanks in advance for your feedback.This sounds like a very useful feature, and one that I can then remove
from my personal TODO list without having to do much work :-)A couple of comments:
First of all, please read up on the PostgreSQL coding style, or at
least look at the code around yours. This doesn't look anything like
our standards.Sorry, the 8.1 manual said all contributions go through pgindent so I didn't
spend too much time on that. I see that the 8.4 manual clarifies that
pgindent won't get run till release time. In any case, I've re-formatted
the patch using the Emacs settings from the 8.1 manual (why are they gone
from the 8.4 manual)? It seems like most of the rest of the Coding
Conventions have to do with error reporting. Do please let me know if
there's something else I can do.Second, this appears to require an anonymous bind to the directory,
which is something we should not encourage people to enable on their
LDAP servers. I think we need to also take parameters with a DN and a
password to bind with in order to do the search, and then re-bind as
the user when found.The new patch implements the initial bind using new configuration parameters
"ldapbinddn" and "ldapbindpasswd". I've also added a "ldapsearchattribute"
just to be complete.
I've finally had the time to look at this patch some more, for the
current commitfest - sorry about the delay.
Other than minor style changes (make the indentation be the same as
the code around it), I noticed one fairly large issue.
The way that LDAP is re-initialized both breaks Windows (in the error
reporting part) and not as obvious but even more importantly, it
breaks TLS. If you enable TLS for LDAP it will only be used for the
search, not for the actual password check - which really removes the
point of it :-)
I assume we need the second ldap_init() because we want to do a new
ldap_simple_bind()? In that case, we realliy need to re-initialize the
whole connection, including TLS.
I also notice that it's hardcoded to retrieve the "uid" attribute,
while the one being searched for can be configured. ISTM it's better
to set both of these to the hba->ldapsearchattribute value - so we
won't fail if "uid" does not exist.
Also, as I'm sure you're aware there are no documentation updates included.
I'll be happy to work on this to get it ready for commit, or do you
want to do the updates?
--
Magnus Hagander
Me: http://www.hagander.net/
Work: http://www.redpill-linpro.com/
On Sun, Nov 29, 2009 at 13:05, Magnus Hagander <magnus@hagander.net> wrote:
On Fri, Sep 18, 2009 at 02:24, Robert Fleming <fleminra@gmail.com> wrote:
On Thu, Sep 17, 2009 at 11:15 AM, Magnus Hagander <magnus@hagander.net>
wrote:On Thu, Sep 17, 2009 at 18:02, Robert Fleming <fleminra@gmail.com> wrote:
Following a discussion on the pgsql-admin list
<http://archives.postgresql.org/pgsql-admin/2009-09/msg00075.php>, I
have
created a patch to (optionally) allow PostgreSQL to do a LDAP search to
determine the user's DN (as is done in Apache, MediaWiki, Bugzilla, et
al.)
instead of building the DN from a prefix and suffix.
This is necessary for schemas where the login attribute is not in the
DN,
such as is described here
<http://www.ldapman.org/articles/intro_to_ldap.html#individual> (look
for
"name-based"). This patch is against PostgreSQL 8.4.0 from Debian
Lenny-backports. If this would be a welcome addition, I can port it
forward
to the latest from postgresql.org.
Thanks in advance for your feedback.This sounds like a very useful feature, and one that I can then remove
from my personal TODO list without having to do much work :-)A couple of comments:
First of all, please read up on the PostgreSQL coding style, or at
least look at the code around yours. This doesn't look anything like
our standards.Sorry, the 8.1 manual said all contributions go through pgindent so I didn't
spend too much time on that. I see that the 8.4 manual clarifies that
pgindent won't get run till release time. In any case, I've re-formatted
the patch using the Emacs settings from the 8.1 manual (why are they gone
from the 8.4 manual)? It seems like most of the rest of the Coding
Conventions have to do with error reporting. Do please let me know if
there's something else I can do.Second, this appears to require an anonymous bind to the directory,
which is something we should not encourage people to enable on their
LDAP servers. I think we need to also take parameters with a DN and a
password to bind with in order to do the search, and then re-bind as
the user when found.The new patch implements the initial bind using new configuration parameters
"ldapbinddn" and "ldapbindpasswd". I've also added a "ldapsearchattribute"
just to be complete.I've finally had the time to look at this patch some more, for the
current commitfest - sorry about the delay.Other than minor style changes (make the indentation be the same as
the code around it), I noticed one fairly large issue.The way that LDAP is re-initialized both breaks Windows (in the error
reporting part) and not as obvious but even more importantly, it
breaks TLS. If you enable TLS for LDAP it will only be used for the
search, not for the actual password check - which really removes the
point of it :-)I assume we need the second ldap_init() because we want to do a new
ldap_simple_bind()? In that case, we realliy need to re-initialize the
whole connection, including TLS.I also notice that it's hardcoded to retrieve the "uid" attribute,
while the one being searched for can be configured. ISTM it's better
to set both of these to the hba->ldapsearchattribute value - so we
won't fail if "uid" does not exist.Also, as I'm sure you're aware there are no documentation updates included.
I'll be happy to work on this to get it ready for commit, or do you
want to do the updates?
Here's a patch with my work so far. Major points missing is the
sanitizing of the username and the docs.
--
Magnus Hagander
Me: http://www.hagander.net/
Work: http://www.redpill-linpro.com/
Attachments:
ldap_search.patchapplication/octet-stream; name=ldap_search.patchDownload
*** a/src/backend/libpq/auth.c
--- b/src/backend/libpq/auth.c
***************
*** 2096,2135 **** CheckPAMAuth(Port *port, char *user, char *password)
*/
#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 **** CheckLDAPAuth(Port *port)
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 **** CheckLDAPAuth(Port *port)
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;
***************
*** 2195,2215 **** 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)
#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,2354 ----
* 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;
!
! /*
! * 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); /* FIXME: sanitize 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_RESULT_CODE, &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 **** CheckLDAPAuth(Port *port)
--- 2358,2369 ----
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 */
*** a/src/backend/libpq/hba.c
--- b/src/backend/libpq/hba.c
***************
*** 1103,1108 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline)
--- 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");
*** a/src/include/libpq/hba.h
--- b/src/include/libpq/hba.h
***************
*** 61,66 **** typedef struct
--- 61,70 ----
bool ldaptls;
char *ldapserver;
int ldapport;
+ char *ldapbinddn;
+ char *ldapbindpasswd;
+ char *ldapsearchattribute;
+ char *ldapbasedn;
char *ldapprefix;
char *ldapsuffix;
bool clientcert;
Magnus Hagander wrote:
On Sun, Nov 29, 2009 at 13:05, Magnus Hagander <magnus@hagander.net> wrote:
I'll be happy to work on this to get it ready for commit, or do you
want to do the updates?Here's a patch with my work so far. Major points missing is the
sanitizing of the username and the docs.
It looks like you did an initial review here already. Since we haven't
heard from Robert in a while and you seem interested in the patch, I
just updated this one to list you as the committer and marked it "ready
for committer". You can commit it or bounce it from there, but it's
obvious none of our other reviewers are going to be able to do anything
with it.
--
Greg Smith 2ndQuadrant Baltimore, MD
PostgreSQL Training, Services and Support
greg@2ndQuadrant.com www.2ndQuadrant.com
On Sun, Dec 6, 2009 at 11:29 PM, Greg Smith <greg@2ndquadrant.com> wrote:
Magnus Hagander wrote:
On Sun, Nov 29, 2009 at 13:05, Magnus Hagander <magnus@hagander.net> wrote:
I'll be happy to work on this to get it ready for commit, or do you
want to do the updates?Here's a patch with my work so far. Major points missing is the
sanitizing of the username and the docs.It looks like you did an initial review here already. Since we haven't
heard from Robert in a while and you seem interested in the patch, I just
updated this one to list you as the committer and marked it "ready for
committer". You can commit it or bounce it from there, but it's obvious
none of our other reviewers are going to be able to do anything with it.
I think we should mark this returned with feedback, since it sounds
like there are still open items.
...Robert
On Sat, Dec 12, 2009 at 02:41, Robert Haas <robertmhaas@gmail.com> wrote:
On Sun, Dec 6, 2009 at 11:29 PM, Greg Smith <greg@2ndquadrant.com> wrote:
Magnus Hagander wrote:
On Sun, Nov 29, 2009 at 13:05, Magnus Hagander <magnus@hagander.net> wrote:
I'll be happy to work on this to get it ready for commit, or do you
want to do the updates?Here's a patch with my work so far. Major points missing is the
sanitizing of the username and the docs.It looks like you did an initial review here already. Since we haven't
heard from Robert in a while and you seem interested in the patch, I just
updated this one to list you as the committer and marked it "ready for
committer". You can commit it or bounce it from there, but it's obvious
none of our other reviewers are going to be able to do anything with it.I think we should mark this returned with feedback, since it sounds
like there are still open items.
I plan to clean up those open item smyself since there has been no
further feedback from the OP. Hopefully in time before the CF ends. So
please leave it for a few days more :-)
--
Magnus Hagander
Me: http://www.hagander.net/
Work: http://www.redpill-linpro.com/
On Sat, Dec 12, 2009 at 12:24, Magnus Hagander <magnus@hagander.net> wrote:
On Sat, Dec 12, 2009 at 02:41, Robert Haas <robertmhaas@gmail.com> wrote:
On Sun, Dec 6, 2009 at 11:29 PM, Greg Smith <greg@2ndquadrant.com> wrote:
Magnus Hagander wrote:
On Sun, Nov 29, 2009 at 13:05, Magnus Hagander <magnus@hagander.net> wrote:
I'll be happy to work on this to get it ready for commit, or do you
want to do the updates?Here's a patch with my work so far. Major points missing is the
sanitizing of the username and the docs.It looks like you did an initial review here already. Since we haven't
heard from Robert in a while and you seem interested in the patch, I just
updated this one to list you as the committer and marked it "ready for
committer". You can commit it or bounce it from there, but it's obvious
none of our other reviewers are going to be able to do anything with it.I think we should mark this returned with feedback, since it sounds
like there are still open items.I plan to clean up those open item smyself since there has been no
further feedback from the OP. Hopefully in time before the CF ends. So
please leave it for a few days more :-)
I have applied the attached, rather heavily reworked, patch. It also
includes the required documentation.
--
Magnus Hagander
Me: http://www.hagander.net/
Work: http://www.redpill-linpro.com/
Attachments:
ldap.patchapplication/octet-stream; name=ldap.patchDownload
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 ****
</para>
<para>
! 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
--- 1202,1209 ----
</para>
<para>
! 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,1216 ****
--- 1212,1234 ----
</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,1231 ****
</listitem>
</varlistentry>
<varlistentry>
<term><literal>ldapprefix</literal></term>
<listitem>
<para>
! String to prepend to the username when forming the DN to bind as.
</para>
</listitem>
</varlistentry>
--- 1240,1270 ----
</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 — 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,
! when doing simple bind authentication.
</para>
</listitem>
</varlistentry>
***************
*** 1233,1262 ****
<term><literal>ldapsuffix</literal></term>
<listitem>
<para>
! String to append to the username when forming the DN to bind as.
</para>
</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 — the connection to the client
! will still be unencrypted unless SSL is used.
</para>
</listitem>
</varlistentry>
</variablelist>
</para>
--- 1272,1318 ----
<term><literal>ldapsuffix</literal></term>
<listitem>
<para>
! String to append to the username when forming the DN to bind as,
! when doing simple bind authentication.
</para>
</listitem>
</varlistentry>
<varlistentry>
! <term><literal>ldapbasedn</literal></term>
<listitem>
<para>
! DN to root the search for the user in, when doing search+bind
! authentication.
</para>
</listitem>
</varlistentry>
<varlistentry>
! <term><literal>ldapbinddn</literal></term>
<listitem>
<para>
! 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>
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;