Merge tag 'exim-4_82_1'
[exim.git] / src / src / lookups / ldap.c
index ddf803e21047da98a3ce5141fa9171d15b5fa812..f77229ded320ba5b6923f7f916d7d26ee0c0cc89 100644 (file)
@@ -1,10 +1,8 @@
-/* $Cambridge: exim/src/src/lookups/ldap.c,v 1.15 2009/11/16 19:50:38 nm4 Exp $ */
-
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2014 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Many thanks to Stuart Lynne for contributing the original code for this
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Many thanks to Stuart Lynne for contributing the original code for this
@@ -83,6 +81,7 @@ typedef struct ldap_connection {
   uschar *password;
   BOOL  bound;
   int   port;
   uschar *password;
   BOOL  bound;
   int   port;
+  BOOL  is_start_tls_called;
   LDAP *ld;
 } LDAP_CONNECTION;
 
   LDAP *ld;
 } LDAP_CONNECTION;
 
@@ -281,6 +280,13 @@ if (lcp == NULL)
   {
   LDAP *ld;
 
   {
   LDAP *ld;
 
+  #ifdef LDAP_OPT_X_TLS_NEWCTX
+  int  am_server = 0;
+  LDAP *ldsetctx;
+  #else
+  LDAP *ldsetctx = NULL;
+  #endif
+
 
   /* --------------------------- OpenLDAP ------------------------ */
 
 
   /* --------------------------- OpenLDAP ------------------------ */
 
@@ -366,6 +372,10 @@ if (lcp == NULL)
     goto RETURN_ERROR;
     }
 
     goto RETURN_ERROR;
     }
 
+  #ifdef LDAP_OPT_X_TLS_NEWCTX
+  ldsetctx = ld;
+  #endif
+
   /* Set the TCP connect time limit if available. This is something that is
   in Netscape SDK v4.1; I don't know about other libraries. */
 
   /* Set the TCP connect time limit if available. This is something that is
   in Netscape SDK v4.1; I don't know about other libraries. */
 
@@ -417,15 +427,43 @@ if (lcp == NULL)
   if (!ldapi)
     {
     int tls_option;
   if (!ldapi)
     {
     int tls_option;
+    #ifdef LDAP_OPT_X_TLS_REQUIRE_CERT
+    if (eldap_require_cert != NULL)
+      {
+      tls_option = LDAP_OPT_X_TLS_NEVER;
+      if (Ustrcmp(eldap_require_cert, "hard") == 0)
+        {
+        tls_option = LDAP_OPT_X_TLS_HARD;
+        }
+      else if (Ustrcmp(eldap_require_cert, "demand") == 0)
+        {
+        tls_option = LDAP_OPT_X_TLS_DEMAND;
+        }
+      else if (Ustrcmp(eldap_require_cert, "allow") == 0)
+        {
+        tls_option = LDAP_OPT_X_TLS_ALLOW;
+        }
+      else if (Ustrcmp(eldap_require_cert, "try") == 0)
+        {
+        tls_option = LDAP_OPT_X_TLS_TRY;
+        }
+      DEBUG(D_lookup)
+        debug_printf("Require certificate overrides LDAP_OPT_X_TLS option (%d)\n",
+                     tls_option);
+      }
+    else
+    #endif  /* LDAP_OPT_X_TLS_REQUIRE_CERT */
     if (strncmp(ludp->lud_scheme, "ldaps", 5) == 0)
       {
       tls_option = LDAP_OPT_X_TLS_HARD;
     if (strncmp(ludp->lud_scheme, "ldaps", 5) == 0)
       {
       tls_option = LDAP_OPT_X_TLS_HARD;
-      DEBUG(D_lookup) debug_printf("LDAP_OPT_X_TLS_HARD set\n");
+      DEBUG(D_lookup)
+        debug_printf("LDAP_OPT_X_TLS_HARD set due to ldaps:// URI\n");
       }
     else
       {
       tls_option = LDAP_OPT_X_TLS_TRY;
       }
     else
       {
       tls_option = LDAP_OPT_X_TLS_TRY;
-      DEBUG(D_lookup) debug_printf("LDAP_OPT_X_TLS_TRY set\n");
+      DEBUG(D_lookup)
+        debug_printf("LDAP_OPT_X_TLS_TRY set due to ldap:// URI\n");
       }
     ldap_set_option(ld, LDAP_OPT_X_TLS, (void *)&tls_option);
     }
       }
     ldap_set_option(ld, LDAP_OPT_X_TLS, (void *)&tls_option);
     }
@@ -434,31 +472,31 @@ if (lcp == NULL)
   #ifdef LDAP_OPT_X_TLS_CACERTFILE
   if (eldap_ca_cert_file != NULL)
     {
   #ifdef LDAP_OPT_X_TLS_CACERTFILE
   if (eldap_ca_cert_file != NULL)
     {
-    ldap_set_option(ld, LDAP_OPT_X_TLS_CACERTFILE, eldap_ca_cert_file);
+    ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_CACERTFILE, eldap_ca_cert_file);
     }
   #endif
   #ifdef LDAP_OPT_X_TLS_CACERTDIR
   if (eldap_ca_cert_dir != NULL)
     {
     }
   #endif
   #ifdef LDAP_OPT_X_TLS_CACERTDIR
   if (eldap_ca_cert_dir != NULL)
     {
-    ldap_set_option(ld, LDAP_OPT_X_TLS_CACERTDIR, eldap_ca_cert_dir);
+    ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_CACERTDIR, eldap_ca_cert_dir);
     }
   #endif
   #ifdef LDAP_OPT_X_TLS_CERTFILE
   if (eldap_cert_file != NULL)
     {
     }
   #endif
   #ifdef LDAP_OPT_X_TLS_CERTFILE
   if (eldap_cert_file != NULL)
     {
-    ldap_set_option(ld, LDAP_OPT_X_TLS_CERTFILE, eldap_cert_file);
+    ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_CERTFILE, eldap_cert_file);
     }
   #endif
   #ifdef LDAP_OPT_X_TLS_KEYFILE
   if (eldap_cert_key != NULL)
     {
     }
   #endif
   #ifdef LDAP_OPT_X_TLS_KEYFILE
   if (eldap_cert_key != NULL)
     {
-    ldap_set_option(ld, LDAP_OPT_X_TLS_KEYFILE, eldap_cert_key);
+    ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_KEYFILE, eldap_cert_key);
     }
   #endif
   #ifdef LDAP_OPT_X_TLS_CIPHER_SUITE
   if (eldap_cipher_suite != NULL)
     {
     }
   #endif
   #ifdef LDAP_OPT_X_TLS_CIPHER_SUITE
   if (eldap_cipher_suite != NULL)
     {
-    ldap_set_option(ld, LDAP_OPT_X_TLS_CIPHER_SUITE, eldap_cipher_suite);
+    ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_CIPHER_SUITE, eldap_cipher_suite);
     }
   #endif
   #ifdef LDAP_OPT_X_TLS_REQUIRE_CERT
     }
   #endif
   #ifdef LDAP_OPT_X_TLS_REQUIRE_CERT
@@ -481,7 +519,26 @@ if (lcp == NULL)
       {
       cert_option = LDAP_OPT_X_TLS_TRY;
       }
       {
       cert_option = LDAP_OPT_X_TLS_TRY;
       }
-    ldap_set_option(ld, LDAP_OPT_X_TLS_REQUIRE_CERT, cert_option);
+    /* This ldap handle is set at compile time based on client libs. Older
+     * versions want it to be global and newer versions can force a reload
+     * of the TLS context (to reload these settings we are changing from the
+     * default that loaded at instantiation). */
+    rc = ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_REQUIRE_CERT, &cert_option);
+    if (rc)
+      {
+      DEBUG(D_lookup)
+        debug_printf("Unable to set TLS require cert_option(%d) globally: %s\n",
+          cert_option, ldap_err2string(rc));
+      }
+    }
+  #endif
+  #ifdef LDAP_OPT_X_TLS_NEWCTX
+  rc = ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_NEWCTX, &am_server);
+  if (rc)
+    {
+    DEBUG(D_lookup)
+      debug_printf("Unable to reload TLS context %d: %s\n",
+                   rc, ldap_err2string(rc));
     }
   #endif
 
     }
   #endif
 
@@ -495,6 +552,7 @@ if (lcp == NULL)
   lcp->port = port;
   lcp->ld = ld;
   lcp->next = ldap_connections;
   lcp->port = port;
   lcp->ld = ld;
   lcp->next = ldap_connections;
+  lcp->is_start_tls_called = FALSE;
   ldap_connections = lcp;
   }
 
   ldap_connections = lcp;
   }
 
@@ -521,9 +579,25 @@ if (!lcp->bound ||
   {
   DEBUG(D_lookup) debug_printf("%sbinding with user=%s password=%s\n",
     (lcp->bound)? "re-" : "", user, password);
   {
   DEBUG(D_lookup) debug_printf("%sbinding with user=%s password=%s\n",
     (lcp->bound)? "re-" : "", user, password);
-  if (eldap_start_tls)
+  if (eldap_start_tls && !lcp->is_start_tls_called)
     {
     {
-    ldap_start_tls_s(lcp->ld, NULL, NULL);
+#if defined(LDAP_OPT_X_TLS) && !defined(LDAP_LIB_SOLARIS)
+    /* The Oracle LDAP libraries (LDAP_LIB_TYPE=SOLARIS) don't support this.
+     * Note: moreover, they appear to now define LDAP_OPT_X_TLS and still not
+     *       export an ldap_start_tls_s symbol.
+     */
+    if ( (rc = ldap_start_tls_s(lcp->ld, NULL, NULL)) != LDAP_SUCCESS)
+      {
+      *errmsg = string_sprintf("failed to initiate TLS processing on an "
+          "LDAP session to server %s%s - ldap_start_tls_s() returned %d:"
+          " %s", host, porttext, rc, ldap_err2string(rc));
+      goto RETURN_ERROR;
+      }
+    lcp->is_start_tls_called = TRUE;
+#else
+    DEBUG(D_lookup)
+      debug_printf("TLS initiation not supported with this Exim and your LDAP library.\n");
+#endif
     }
   if ((msgid = ldap_bind(lcp->ld, CS user, CS password, LDAP_AUTH_SIMPLE))
        == -1)
     }
   if ((msgid = ldap_bind(lcp->ld, CS user, CS password, LDAP_AUTH_SIMPLE))
        == -1)
@@ -724,10 +798,10 @@ while ((rc = ldap_result(lcp->ld, msgid, 0, timeoutptr, &result)) ==
             DEBUG(D_lookup) debug_printf("LDAP attr loop %s:%s\n", attr, value);
 
             if (values != firstval)
             DEBUG(D_lookup) debug_printf("LDAP attr loop %s:%s\n", attr, value);
 
             if (values != firstval)
-              data = string_cat(data, &size, &ptr, US", ", 2);
+              data = string_cat(data, &size, &ptr, US",", 1);
 
             /* For multiple attributes, the data is in quotes. We must escape
 
             /* For multiple attributes, the data is in quotes. We must escape
-            internal quotes, backslashes, newlines. */
+            internal quotes, backslashes, newlines, and must double commas. */
 
             if (attr_count != 1)
               {
 
             if (attr_count != 1)
               {
@@ -736,6 +810,8 @@ while ((rc = ldap_result(lcp->ld, msgid, 0, timeoutptr, &result)) ==
                 {
                 if (value[j] == '\n')
                   data = string_cat(data, &size, &ptr, US"\\n", 2);
                 {
                 if (value[j] == '\n')
                   data = string_cat(data, &size, &ptr, US"\\n", 2);
+                else if (value[j] == ',')
+                  data = string_cat(data, &size, &ptr, US",,", 2);
                 else
                   {
                   if (value[j] == '\"' || value[j] == '\\')
                 else
                   {
                   if (value[j] == '\"' || value[j] == '\\')
@@ -745,9 +821,20 @@ while ((rc = ldap_result(lcp->ld, msgid, 0, timeoutptr, &result)) ==
                 }
               }
 
                 }
               }
 
-            /* For single attributes, copy the value verbatim */
+            /* For single attributes, just double commas */
+
+           else
+             {
+             int j;
+             for (j = 0; j < len; j++)
+               {
+               if (value[j] == ',')
+                 data = string_cat(data, &size, &ptr, US",,", 2);
+               else
+                 data = string_cat(data, &size, &ptr, value+j, 1);
+               }
+             }
 
 
-            else data = string_cat(data, &size, &ptr, value, len);
 
             /* Move on to the next value */
 
 
             /* Move on to the next value */
 
@@ -1046,6 +1133,7 @@ uschar *url = ldap_url;
 uschar *p;
 uschar *user = NULL;
 uschar *password = NULL;
 uschar *p;
 uschar *user = NULL;
 uschar *password = NULL;
+uschar *local_servers = NULL;
 uschar *server, *list;
 uschar buffer[512];
 
 uschar *server, *list;
 uschar buffer[512];
 
@@ -1074,6 +1162,7 @@ while (strncmpic(url, US"ldap", 4) != 0)
       else if (strncmpic(name, US"TIME=", namelen) == 0) timelimit = Uatoi(value);
       else if (strncmpic(name, US"CONNECT=", namelen) == 0) tcplimit = Uatoi(value);
       else if (strncmpic(name, US"NETTIME=", namelen) == 0) tcplimit = Uatoi(value);
       else if (strncmpic(name, US"TIME=", namelen) == 0) timelimit = Uatoi(value);
       else if (strncmpic(name, US"CONNECT=", namelen) == 0) tcplimit = Uatoi(value);
       else if (strncmpic(name, US"NETTIME=", namelen) == 0) tcplimit = Uatoi(value);
+      else if (strncmpic(name, US"SERVERS=", namelen) == 0) local_servers = value;
 
       /* Don't know if all LDAP libraries have LDAP_OPT_DEREF */
 
 
       /* Don't know if all LDAP libraries have LDAP_OPT_DEREF */
 
@@ -1201,16 +1290,16 @@ if (Ustrncmp(p, "://", 3) != 0)
 
 /* No default servers, or URL contains a server name: just one attempt */
 
 
 /* No default servers, or URL contains a server name: just one attempt */
 
-if (eldap_default_servers == NULL || p[3] != '/')
+if ((eldap_default_servers == NULL && local_servers == NULL) || p[3] != '/')
   {
   return perform_ldap_search(url, NULL, 0, search_type, res, errmsg,
     &defer_break, user, password, sizelimit, timelimit, tcplimit, dereference,
     referrals);
   }
 
   {
   return perform_ldap_search(url, NULL, 0, search_type, res, errmsg,
     &defer_break, user, password, sizelimit, timelimit, tcplimit, dereference,
     referrals);
   }
 
-/* Loop through the default servers until OK or FAIL */
-
-list = eldap_default_servers;
+/* Loop through the default servers until OK or FAIL. Use local_servers list
+ * if defined in the lookup, otherwise use the global default list */
+list = (local_servers == NULL) ? eldap_default_servers : local_servers;
 while ((server = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
   {
   int rc;
 while ((server = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
   {
   int rc;
@@ -1309,7 +1398,8 @@ while ((lcp = ldap_connections) != NULL)
   {
   DEBUG(D_lookup) debug_printf("unbind LDAP connection to %s:%d\n", lcp->host,
     lcp->port);
   {
   DEBUG(D_lookup) debug_printf("unbind LDAP connection to %s:%d\n", lcp->host,
     lcp->port);
-  ldap_unbind(lcp->ld);
+  if(lcp->bound == TRUE)
+    ldap_unbind(lcp->ld);
   ldap_connections = lcp->next;
   }
 }
   ldap_connections = lcp->next;
   }
 }