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;