Index: configure.in
===================================================================
RCS file: /cvsroot/pgsql/configure.in,v
retrieving revision 1.469
diff -c -c -r1.469 configure.in
*** configure.in 24 Jul 2006 16:32:44 -0000 1.469
--- configure.in 26 Jul 2006 16:38:39 -0000
***************
*** 1106,1111 ****
--- 1106,1119 ----
PGAC_FUNC_GETPWUID_R_5ARG
PGAC_FUNC_STRERROR_R_INT
+ # this will link libpq against libldap_r
+ if test "$with_ldap" = yes ; then
+ if test "$PORTNAME" != "win32"; then
+ AC_CHECK_LIB(ldap_r, ldap_simple_bind, [], [AC_MSG_ERROR([library 'ldap_r' is required for LDAP])])
+ PTHREAD_LIBS="$PTHREAD_LIBS -lldap_r"
+ fi
+ fi
+
CFLAGS="$_CFLAGS"
LIBS="$_LIBS"
Index: doc/src/sgml/libpq.sgml
===================================================================
RCS file: /cvsroot/pgsql/doc/src/sgml/libpq.sgml,v
retrieving revision 1.213
diff -c -c -r1.213 libpq.sgml
*** doc/src/sgml/libpq.sgml 4 Jul 2006 13:22:15 -0000 1.213
--- doc/src/sgml/libpq.sgml 26 Jul 2006 16:38:41 -0000
***************
*** 4126,4131 ****
--- 4126,4197 ----
+
+ LDAP Lookup of Connection Parameters
+
+
+ LDAP connection parameter lookup
+
+
+
+ If libpq has been compiled with LDAP support (option
+ for configure)
+ it is possible to retrieve connection options like host
+ or dbname via LDAP from a central server.
+ The advantage is that if the connection parameters for a database change,
+ the connection information doesn't have to be updated on all client machines.
+
+
+
+ LDAP connection parameter lookup uses the connection service file
+ pg_service.conf (see ).
+ A line in a pg_service.conf stanza that starts with
+ ldap:// will be recognized as an LDAP URL and an LDAP
+ query will be performed. The result must be a list of keyword =
+ value pairs which will be used to set connection options.
+ The URL must conform to RFC 1959 and be of the form
+
+ ldap://[hostname[:port]]/search_base?attribute?search_scope?filter
+
+ where hostname
+ defaults to localhost and
+ port defaults to 389.
+
+
+
+ Processing of pg_service.conf is terminated after
+ a successful LDAP lookup, but is continued if the LDAP server cannot be
+ contacted. This is to provide a fallback with
+ further LDAP URL lines that point to different LDAP
+ servers, classical keyword = value pairs, or
+ default connection options.
+ If you would rather get an error message in this case, add a
+ syntactically incorrect line after the LDAP URL.
+
+
+
+ A sample LDAP entry that has been created with the LDIF file
+
+ version:1
+ dn:cn=mydatabase,dc=mycompany,dc=com
+ changetype:add
+ objectclass:top
+ objectclass:groupOfUniqueNames
+ cn:mydatabase
+ uniqueMember:host=dbserver.mycompany.com
+ uniqueMember:port=5439
+ uniqueMember:dbname=mydb
+ uniqueMember:user=mydb_user
+ uniqueMember:sslmode=require
+
+ might be queried with the following LDAP URL:
+
+ ldap://ldap.mycompany.com/dc=mycompany,dc=com?uniqueMember?one?(cn=mydatabase)
+
+
+
+
+
SSL Support
Index: src/interfaces/libpq/Makefile
===================================================================
RCS file: /cvsroot/pgsql/src/interfaces/libpq/Makefile,v
retrieving revision 1.146
diff -c -c -r1.146 Makefile
*** src/interfaces/libpq/Makefile 18 Jul 2006 22:18:08 -0000 1.146
--- src/interfaces/libpq/Makefile 26 Jul 2006 16:38:49 -0000
***************
*** 62,68 ****
SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lssl -lsocket -lnsl -lresolv -lintl $(PTHREAD_LIBS), $(LIBS))
endif
ifeq ($(PORTNAME), win32)
! SHLIB_LINK += -lshfolder -lwsock32 -lws2_32 $(filter -leay32 -lssleay32 -lcomerr32 -lkrb5_32, $(LIBS))
endif
--- 62,68 ----
SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lssl -lsocket -lnsl -lresolv -lintl $(PTHREAD_LIBS), $(LIBS))
endif
ifeq ($(PORTNAME), win32)
! SHLIB_LINK += -lshfolder -lwsock32 -lws2_32 $(filter -leay32 -lssleay32 -lcomerr32 -lkrb5_32 -lwldap32, $(LIBS))
endif
Index: src/interfaces/libpq/fe-connect.c
===================================================================
RCS file: /cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v
retrieving revision 1.333
diff -c -c -r1.333 fe-connect.c
*** src/interfaces/libpq/fe-connect.c 7 Jun 2006 22:24:46 -0000 1.333
--- src/interfaces/libpq/fe-connect.c 26 Jul 2006 16:38:58 -0000
***************
*** 60,65 ****
--- 60,78 ----
#endif
#endif
+ #ifdef USE_LDAP
+ #ifdef WIN32
+ #include
+ #else
+ /* OpenLDAP deprecates RFC 1823, but we want standard conformance */
+ #define LDAP_DEPRECATED 1
+ #include
+ typedef struct timeval LDAP_TIMEVAL;
+ #endif
+ static int ldapServiceLookup(const char *purl, PQconninfoOption *options,
+ PQExpBuffer errorMessage);
+ #endif
+
#include "libpq/ip.h"
#include "mb/pg_wchar.h"
***************
*** 2343,2349 ****
--- 2356,2765 ----
return STATUS_OK;
}
+ #ifdef USE_LDAP
+
+ #define LDAP_URL "ldap://"
+ #define LDAP_DEF_PORT 389
+ #define PGLDAP_TIMEOUT 2
+
+ #define ld_is_sp_tab(x) ((x) == ' ' || (x) == '\t')
+ #define ld_is_nl_cr(x) ((x) == '\r' || (x) == '\n')
+
+
+ /*
+ * ldapServiceLookup
+ *
+ * Search the LDAP URL passed as first argument, treat the result as a
+ * string of connection options that are parsed and added to the array of
+ * options passed as second argument.
+ *
+ * LDAP URLs must conform to RFC 1959 without escape sequences.
+ * ldap://host:port/dn?attributes?scope?filter?extensions
+ *
+ * Returns
+ * 0 if the lookup was successful,
+ * 1 if the connection to the LDAP server could be established but
+ * the search was unsuccessful,
+ * 2 if a connection could not be established, and
+ * 3 if a fatal error occurred.
+ *
+ * An error message is returned in the third argument for return codes 1 and 3.
+ */
+ static int
+ ldapServiceLookup(const char *purl, PQconninfoOption *options,
+ PQExpBuffer errorMessage)
+ {
+ int port = LDAP_DEF_PORT, scope, rc, msgid, size, state, oldstate, i;
+ bool found_keyword;
+ char *url, *hostname, *portstr, *endptr, *dn, *scopestr, *filter,
+ *result, *p, *p1 = NULL, *optname = NULL, *optval = NULL;
+ char *attrs[2] = {NULL, NULL};
+ LDAP *ld = NULL;
+ LDAPMessage *res, *entry;
+ struct berval **values;
+ LDAP_TIMEVAL time = {PGLDAP_TIMEOUT, 0};
+
+ if ((url = strdup(purl)) == NULL)
+ {
+ printfPQExpBuffer(errorMessage, libpq_gettext("out of memory\n"));
+ return 3;
+ }
+
+ /*
+ * Parse URL components, check for correctness. Basically, url has
+ * '\0' placed at component boundaries and variables are pointed
+ * at each component.
+ */
+
+ if (strncasecmp(url, LDAP_URL, strlen(LDAP_URL)) != 0)
+ {
+ printfPQExpBuffer(errorMessage,
+ libpq_gettext("bad LDAP URL \"%s\": scheme must be ldap://\n"), purl);
+ free(url);
+ return 3;
+ }
+
+ /* hostname */
+ hostname = url + strlen(LDAP_URL);
+ if (*hostname == '/') /* no hostname? */
+ hostname = "localhost"; /* the default */
+
+ /* dn, "distinguished name" */
+ p = strchr(url + strlen(LDAP_URL), '/');
+ if (p == NULL || *(p + 1) == '\0' || *(p + 1) == '?')
+ {
+ printfPQExpBuffer(errorMessage, libpq_gettext(
+ "bad LDAP URL \"%s\": missing distinguished name\n"), purl);
+ free(url);
+ return 3;
+ }
+ *p = '\0'; /* terminate hostname */
+ dn = p + 1;
+
+ /* attribute */
+ if ((p = strchr(dn, '?')) == NULL || *(p + 1) == '\0' || *(p + 1) == '?')
+ {
+ printfPQExpBuffer(errorMessage, libpq_gettext(
+ "bad LDAP URL \"%s\": must have exactly one attribute\n"), purl);
+ free(url);
+ return 3;
+ }
+ *p = '\0';
+ attrs[0] = p + 1;
+
+ /* scope */
+ if ((p = strchr(attrs[0], '?')) == NULL || *(p + 1) == '\0' || *(p + 1) == '?')
+ {
+ printfPQExpBuffer(errorMessage, libpq_gettext(
+ "bad LDAP URL \"%s\": must have search scope (base/one/sub)\n"), purl);
+ free(url);
+ return 3;
+ }
+ *p = '\0';
+ scopestr = p + 1;
+
+ /* filter */
+ if ((p = strchr(scopestr, '?')) == NULL || *(p + 1) == '\0' || *(p + 1) == '?')
+ {
+ printfPQExpBuffer(errorMessage,
+ libpq_gettext("bad LDAP URL \"%s\": no filter\n"), purl);
+ free(url);
+ return 3;
+ }
+ *p = '\0';
+ filter = p + 1;
+ if ((p = strchr(filter, '?')) != NULL)
+ *p = '\0';
+
+ /* port number? */
+ if ((p1 = strchr(hostname, ':')) != NULL)
+ {
+ long lport;
+
+ *p1 = '\0';
+ portstr = p1 + 1;
+ errno = 0;
+ lport = strtol(portstr, &endptr, 10);
+ if (*portstr == '\0' || *endptr != '\0' || errno || lport < 0 || lport > 65535)
+ {
+ printfPQExpBuffer(errorMessage, libpq_gettext(
+ "bad LDAP URL \"%s\": invalid port number\n"), purl);
+ free(url);
+ return 3;
+ }
+ port = (int) lport;
+ }
+
+ /* Allow only one attribute */
+ if (strchr(attrs[0], ',') != NULL)
+ {
+ printfPQExpBuffer(errorMessage, libpq_gettext(
+ "bad LDAP URL \"%s\": must have exactly one attribute\n"), purl);
+ free(url);
+ return 3;
+ }
+
+ /* set scope */
+ if (strcasecmp(scopestr, "base") == 0)
+ scope = LDAP_SCOPE_BASE;
+ else if (strcasecmp(scopestr, "one") == 0)
+ scope = LDAP_SCOPE_ONELEVEL;
+ else if (strcasecmp(scopestr, "sub") == 0)
+ scope = LDAP_SCOPE_SUBTREE;
+ else
+ {
+ printfPQExpBuffer(errorMessage, libpq_gettext(
+ "bad LDAP URL \"%s\": must have search scope (base/one/sub)\n"), purl);
+ free(url);
+ return 3;
+ }
+
+ /* initialize LDAP structure */
+ if ((ld = ldap_init(hostname, port)) == NULL)
+ {
+ printfPQExpBuffer(errorMessage,
+ libpq_gettext("error creating LDAP structure\n"));
+ free(url);
+ return 3;
+ }
+ /*
+ * Initialize connection to the server. We do an explicit bind because
+ * we want to return 2 if the bind fails.
+ */
+ if ((msgid = ldap_simple_bind(ld, NULL, NULL)) == -1)
+ {
+ /* error in ldap_simple_bind() */
+ free(url);
+ ldap_unbind(ld);
+ return 2;
+ }
+
+ /* wait some time for the connection to succeed */
+ res = NULL;
+ if ((rc = ldap_result(ld, msgid, LDAP_MSG_ALL, &time, &res)) == -1 ||
+ res == NULL)
+ {
+ if (res != NULL)
+ {
+ /* timeout */
+ ldap_msgfree(res);
+ }
+ /* error in ldap_result() */
+ free(url);
+ ldap_unbind(ld);
+ return 2;
+ }
+ ldap_msgfree(res);
+
+ /* search */
+ res = NULL;
+ if ((rc = ldap_search_st(ld, dn, scope, filter, attrs, 0, &time, &res))
+ != LDAP_SUCCESS)
+ {
+ if (res != NULL)
+ ldap_msgfree(res);
+ printfPQExpBuffer(errorMessage,
+ libpq_gettext("lookup on LDAP server failed: %s\n"),
+ ldap_err2string(rc));
+ ldap_unbind(ld);
+ free(url);
+ return 1;
+ }
+
+ /* complain if there was not exactly one result */
+ if ((rc = ldap_count_entries(ld, res)) != 1)
+ {
+ printfPQExpBuffer(errorMessage,
+ rc ? libpq_gettext("more than one entry found on LDAP lookup\n")
+ : libpq_gettext("no entry found on LDAP lookup\n"));
+ ldap_msgfree(res);
+ ldap_unbind(ld);
+ free(url);
+ return 1;
+ }
+
+ /* get entry */
+ if ((entry = ldap_first_entry(ld, res)) == NULL)
+ {
+ /* should never happen */
+ printfPQExpBuffer(errorMessage,
+ libpq_gettext("no entry found on LDAP lookup\n"));
+ ldap_msgfree(res);
+ ldap_unbind(ld);
+ free(url);
+ return 1;
+ }
+
+ /* get values */
+ if ((values = ldap_get_values_len(ld, entry, attrs[0])) == NULL)
+ {
+ printfPQExpBuffer(errorMessage,
+ libpq_gettext("attribute has no values on LDAP lookup\n"));
+ ldap_msgfree(res);
+ ldap_unbind(ld);
+ free(url);
+ return 1;
+ }
+
+ ldap_msgfree(res);
+ free(url);
+
+ if (values[0] == NULL)
+ {
+ printfPQExpBuffer(errorMessage,
+ libpq_gettext("attribute has no values on LDAP lookup\n"));
+ ldap_value_free_len(values);
+ ldap_unbind(ld);
+ return 1;
+ }
+
+ /* concatenate values to a single string */
+ for (size = 0, i = 0; values[i] != NULL; ++i)
+ size += values[i]->bv_len + 1;
+ if ((result = malloc(size + 1)) == NULL)
+ {
+ printfPQExpBuffer(errorMessage,
+ libpq_gettext("out of memory\n"));
+ ldap_value_free_len(values);
+ ldap_unbind(ld);
+ return 3;
+ }
+ for (p = result, i = 0; values[i] != NULL; ++i)
+ {
+ strncpy(p, values[i]->bv_val, values[i]->bv_len);
+ p += values[i]->bv_len;
+ *(p++) = '\n';
+ if (values[i + 1] == NULL)
+ *(p + 1) = '\0';
+ }
+
+ ldap_value_free_len(values);
+ ldap_unbind(ld);
+
+ /* parse result string */
+ oldstate = state = 0;
+ for (p = result; *p != '\0'; ++p)
+ {
+ switch (state)
+ {
+ case 0: /* between entries */
+ if (!ld_is_sp_tab(*p) && !ld_is_nl_cr(*p))
+ {
+ optname = p;
+ state = 1;
+ }
+ break;
+ case 1: /* in option name */
+ if (ld_is_sp_tab(*p))
+ {
+ *p = '\0';
+ state = 2;
+ }
+ else if (ld_is_nl_cr(*p))
+ {
+ printfPQExpBuffer(errorMessage, libpq_gettext(
+ "missing \"=\" after \"%s\" in connection info string\n"),
+ optname);
+ return 3;
+ }
+ else if (*p == '=')
+ {
+ *p = '\0';
+ state = 3;
+ }
+ break;
+ case 2: /* after option name */
+ if (*p == '=')
+ {
+ state = 3;
+ }
+ else if (!ld_is_sp_tab(*p))
+ {
+ printfPQExpBuffer(errorMessage, libpq_gettext(
+ "missing \"=\" after \"%s\" in connection info string\n"),
+ optname);
+ return 3;
+ }
+ break;
+ case 3: /* before option value */
+ if (*p == '\'')
+ {
+ optval = p + 1;
+ p1 = p + 1;
+ state = 5;
+ }
+ else if (ld_is_nl_cr(*p))
+ {
+ optval = optname + strlen(optname); /* empty */
+ state = 0;
+ }
+ else if (!ld_is_sp_tab(*p))
+ {
+ optval = p;
+ state = 4;
+ }
+ break;
+ case 4: /* in unquoted option value */
+ if (ld_is_sp_tab(*p) || ld_is_nl_cr(*p))
+ {
+ *p = '\0';
+ state = 0;
+ }
+ break;
+ case 5: /* in quoted option value */
+ if (*p == '\'')
+ {
+ *p1 = '\0';
+ state = 0;
+ }
+ else if (*p == '\\')
+ state = 6;
+ else
+ *(p1++) = *p;
+ break;
+ case 6: /* in quoted option value after escape */
+ *(p1++) = *p;
+ state = 5;
+ break;
+ }
+
+ if (state == 0 && oldstate != 0)
+ {
+ found_keyword = false;
+ for (i = 0; options[i].keyword; i++)
+ {
+ if (strcmp(options[i].keyword, optname) == 0)
+ {
+ if (options[i].val == NULL)
+ options[i].val = strdup(optval);
+ found_keyword = true;
+ break;
+ }
+ }
+ if (!found_keyword)
+ {
+ printfPQExpBuffer(errorMessage,
+ libpq_gettext("invalid connection option \"%s\"\n"),
+ optname);
+ return 1;
+ }
+ optname = NULL;
+ optval = NULL;
+ }
+ oldstate = state;
+ }
+
+ if (state == 5 || state == 6)
+ {
+ printfPQExpBuffer(errorMessage, libpq_gettext(
+ "unterminated quoted string in connection info string\n"));
+ return 3;
+ }
+
+ return 0;
+ }
+ #endif
#define MAXBUFSIZE 256
***************
*** 2439,2444 ****
--- 2855,2880 ----
*val;
bool found_keyword;
+ #ifdef USE_LDAP
+ if (strncmp(line, "ldap", 4) == 0)
+ {
+ int rc = ldapServiceLookup(line, options, errorMessage);
+ /* if rc = 2, go on reading for fallback */
+ switch (rc)
+ {
+ case 0:
+ fclose(f);
+ return 0;
+ case 1:
+ case 3:
+ fclose(f);
+ return 3;
+ case 2:
+ continue;
+ }
+ }
+ #endif
+
key = line;
val = strchr(line, '=');
if (val == NULL)