Allow LDAP lookups from pg_service.conf.
authorBruce Momjian
Thu, 27 Jul 2006 13:20:24 +0000 (13:20 +0000)
committerBruce Momjian
Thu, 27 Jul 2006 13:20:24 +0000 (13:20 +0000)
Albe Laurenz

configure
configure.in
doc/src/sgml/libpq.sgml
src/interfaces/libpq/Makefile
src/interfaces/libpq/fe-connect.c

index 20084080c5abe813b9150953ad57b92f77452a39..973c9ebf5540616e6d581c4a0137f5a09396c79d 100755 (executable)
--- a/configure
+++ b/configure
@@ -17314,6 +17314,91 @@ _ACEOF
 fi
 
 
+# this will link libpq against libldap_r
+if test "$with_ldap" = yes ; then
+  if test "$PORTNAME" != "win32"; then
+
+echo "$as_me:$LINENO: checking for ldap_simple_bind in -lldap_r" >&5
+echo $ECHO_N "checking for ldap_simple_bind in -lldap_r... $ECHO_C" >&6
+if test "${ac_cv_lib_ldap_r_ldap_simple_bind+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-lldap_r  $LIBS"
+cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+
+/* Override any gcc2 internal prototype to avoid an error.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+/* We use char because int might match the return type of a gcc2
+   builtin and then its argument prototype would still apply.  */
+char ldap_simple_bind ();
+int
+main ()
+{
+ldap_simple_bind ();
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext conftest$ac_exeext
+if { (eval echo "$as_me:$LINENO: \"$ac_link\"") >&5
+  (eval $ac_link) 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } &&
+    { ac_try='test -z "$ac_c_werror_flag"
+            || test ! -s conftest.err'
+  { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; } &&
+    { ac_try='test -s conftest$ac_exeext'
+  { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; }; then
+  ac_cv_lib_ldap_r_ldap_simple_bind=yes
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ac_cv_lib_ldap_r_ldap_simple_bind=no
+fi
+rm -f conftest.err conftest.$ac_objext \
+      conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+echo "$as_me:$LINENO: result: $ac_cv_lib_ldap_r_ldap_simple_bind" >&5
+echo "${ECHO_T}$ac_cv_lib_ldap_r_ldap_simple_bind" >&6
+if test $ac_cv_lib_ldap_r_ldap_simple_bind = yes; then
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBLDAP_R 1
+_ACEOF
+
+  LIBS="-lldap_r $LIBS"
+
+else
+  { { echo "$as_me:$LINENO: error: library 'ldap_r' is required for LDAP" >&5
+echo "$as_me: error: library 'ldap_r' is required for LDAP" >&2;}
+   { (exit 1); exit 1; }; }
+fi
+
+    PTHREAD_LIBS="$PTHREAD_LIBS -lldap_r"
+  fi
+fi
+
 CFLAGS="$_CFLAGS"
 LIBS="$_LIBS"
 
index fe3cde3f6a7ab36267fdb3cef10086aa35c0c511..7031f7d380aef75ad8c966a54219aa63a60eedb1 100644 (file)
@@ -1,5 +1,5 @@
 dnl Process this file with autoconf to produce a configure script.
-dnl $PostgreSQL: pgsql/configure.in,v 1.469 2006/07/24 16:32:44 petere Exp $
+dnl $PostgreSQL: pgsql/configure.in,v 1.470 2006/07/27 13:20:24 momjian Exp $
 dnl
 dnl Developers, please strive to achieve this order:
 dnl
@@ -1106,6 +1106,14 @@ AC_CHECK_FUNCS([strerror_r getpwuid_r gethostbyname_r])
 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 4d8b29de726ef6349c448d6901c173f7ddf467d2..7ffd15a0388d3d957eed028b434b905e5bd58c67 100644 (file)
@@ -1,4 +1,4 @@
-
+
 
  
   <application>libpq</application> - C Library
@@ -4126,6 +4126,72 @@ installs too. The file's location can also be specified by the
 
 
 
+
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 24218d12b70507866b1f8ca8796d1fe000144570..ea395c8834adfee6620b76e9a0659fad87756e0f 100644 (file)
@@ -5,7 +5,7 @@
 # Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
 # Portions Copyright (c) 1994, Regents of the University of California
 #
-# $PostgreSQL: pgsql/src/interfaces/libpq/Makefile,v 1.146 2006/07/18 22:18:08 momjian Exp $
+# $PostgreSQL: pgsql/src/interfaces/libpq/Makefile,v 1.147 2006/07/27 13:20:24 momjian Exp $
 #
 #-------------------------------------------------------------------------
 
@@ -62,7 +62,7 @@ else
 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))
+SHLIB_LINK += -lshfolder -lwsock32 -lws2_32 $(filter -leay32 -lssleay32 -lcomerr32 -lkrb5_32 -lwldap32, $(LIBS))
 endif
 
 
index 74dfe3a9cf581a4a44d55d89eba9ef009bbb794f..00235fb0badc0950e106feee66270c8620c6a240 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/interfaces/libpq/fe-connect.c,v 1.333 2006/06/07 22:24:46 momjian Exp $
+ *   $PostgreSQL: pgsql/src/interfaces/libpq/fe-connect.c,v 1.334 2006/07/27 13:20:24 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
 #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,7 +2356,410 @@ pqPacketSend(PGconn *conn, char pack_type,
    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,6 +2855,26 @@ parseServiceInfo(PQconninfoOption *options, PQExpBuffer errorMessage)
                               *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)