tidying
[users/jgh/exim.git] / src / src / lookups / ldap.c
index 6129b4bfeea6b1bf022126ab5bdecd0840fa8f15..b52ef2221ee1293c1254f9fd990788cdb5007f99 100644 (file)
@@ -2,11 +2,11 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
 /* 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
-driver. Further contibutions from Michael Haardt, Brian Candler, Barry
+driver. Further contributions from Michael Haardt, Brian Candler, Barry
 Pederson, Peter Savitch and Christian Kellner. Particular thanks to Brian for
 researching how to handle the different kinds of error. */
 
 Pederson, Peter Savitch and Christian Kellner. Particular thanks to Brian for
 researching how to handle the different kinds of error. */
 
@@ -130,9 +130,10 @@ Returns:        OK or FAIL or DEFER
 */
 
 static int
 */
 
 static int
-perform_ldap_search(uschar *ldap_url, uschar *server, int s_port, int search_type,
-  uschar **res, uschar **errmsg, BOOL *defer_break, uschar *user, uschar *password,
-  int sizelimit, int timelimit, int tcplimit, int dereference, void *referrals)
+perform_ldap_search(const uschar *ldap_url, uschar *server, int s_port,
+  int search_type, uschar **res, uschar **errmsg, BOOL *defer_break,
+  uschar *user, uschar *password, int sizelimit, int timelimit, int tcplimit,
+  int dereference, void *referrals)
 {
 LDAPURLDesc     *ludp = NULL;
 LDAPMessage     *result = NULL;
 {
 LDAPURLDesc     *ludp = NULL;
 LDAPMessage     *result = NULL;
@@ -155,7 +156,7 @@ uschar *error1 = NULL;   /* string representation of errcode (static) */
 uschar *error2 = NULL;   /* error message from the server */
 uschar *matched = NULL;  /* partially matched DN */
 
 uschar *error2 = NULL;   /* error message from the server */
 uschar *matched = NULL;  /* partially matched DN */
 
-int    attr_count = 0;
+int    attrs_requested = 0;
 int    error_yield = DEFER;
 int    msgid;
 int    rc, ldap_rc, ldap_parse_rc;
 int    error_yield = DEFER;
 int    msgid;
 int    rc, ldap_rc, ldap_parse_rc;
@@ -247,7 +248,7 @@ if (host != NULL)
 /* Count the attributes; we need this later to tell us how to format results */
 
 for (attrp = USS ludp->lud_attrs; attrp != NULL && *attrp != NULL; attrp++)
 /* Count the attributes; we need this later to tell us how to format results */
 
 for (attrp = USS ludp->lud_attrs; attrp != NULL && *attrp != NULL; attrp++)
-  attr_count++;
+  attrs_requested++;
 
 /* See if we can find a cached connection to this host. The port is not
 relevant for ldapi. The host name pointer is set to NULL if no host was given
 
 /* See if we can find a cached connection to this host. The port is not
 relevant for ldapi. The host name pointer is set to NULL if no host was given
@@ -280,6 +281,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 ------------------------ */
 
@@ -365,6 +373,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. */
 
@@ -461,31 +473,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
@@ -508,8 +520,26 @@ if (lcp == NULL)
       {
       cert_option = LDAP_OPT_X_TLS_TRY;
       }
       {
       cert_option = LDAP_OPT_X_TLS_TRY;
       }
-    /* Use NULL ldap handle because is a global option */
-    ldap_set_option(NULL, 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
 
@@ -550,7 +580,7 @@ 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 && !lcp->is_start_tls_called)
+  if (eldap_start_tls && !lcp->is_start_tls_called && !ldapi)
     {
 #if defined(LDAP_OPT_X_TLS) && !defined(LDAP_LIB_SOLARIS)
     /* The Oracle LDAP libraries (LDAP_LIB_TYPE=SOLARIS) don't support this.
     {
 #if defined(LDAP_OPT_X_TLS) && !defined(LDAP_LIB_SOLARIS)
     /* The Oracle LDAP libraries (LDAP_LIB_TYPE=SOLARIS) don't support this.
@@ -683,10 +713,15 @@ while ((rc = ldap_result(lcp->ld, msgid, 0, timeoutptr, &result)) ==
         LDAP_RES_SEARCH_ENTRY)
   {
   LDAPMessage  *e;
         LDAP_RES_SEARCH_ENTRY)
   {
   LDAPMessage  *e;
+  int valuecount;   /* We can see an attr spread across several
+                    entries. If B is derived from A and we request
+                    A and the directory contains both, A and B,
+                    then we get two entries, one for A and one for B.
+                    Here we just count the values per entry */
 
 
-  DEBUG(D_lookup) debug_printf("ldap_result loop\n");
+  DEBUG(D_lookup) debug_printf("LDAP result loop\n");
 
 
-  for(e = ldap_first_entry(lcp->ld, result);
+  for(e = ldap_first_entry(lcp->ld, result), valuecount = 0;
       e != NULL;
       e = ldap_next_entry(lcp->ld, e))
     {
       e != NULL;
       e = ldap_next_entry(lcp->ld, e))
     {
@@ -699,7 +734,7 @@ while ((rc = ldap_result(lcp->ld, msgid, 0, timeoutptr, &result)) ==
 
     /* Results for multiple entries values are separated by newlines. */
 
 
     /* Results for multiple entries values are separated by newlines. */
 
-    if (data != NULL) data = string_cat(data, &size, &ptr, US"\n", 1);
+    if (data != NULL) data = string_catn(data, &size, &ptr, US"\n", 1);
 
     /* Get the DN from the last result. */
 
 
     /* Get the DN from the last result. */
 
@@ -727,7 +762,7 @@ while ((rc = ldap_result(lcp->ld, msgid, 0, timeoutptr, &result)) ==
       {                                  /* condition, because of the else */
       if (new_dn != NULL)                /* below, that's for the first only */
         {
       {                                  /* condition, because of the else */
       if (new_dn != NULL)                /* below, that's for the first only */
         {
-        data = string_cat(data, &size, &ptr, new_dn, Ustrlen(new_dn));
+        data = string_cat(data, &size, &ptr, new_dn);
         data[ptr] = 0;
         attribute_found = TRUE;
         }
         data[ptr] = 0;
         attribute_found = TRUE;
         }
@@ -735,15 +770,21 @@ while ((rc = ldap_result(lcp->ld, msgid, 0, timeoutptr, &result)) ==
 
     /* Otherwise, loop through the entry, grabbing attribute values. If there's
     only one attribute being retrieved, no attribute name is given, and the
 
     /* Otherwise, loop through the entry, grabbing attribute values. If there's
     only one attribute being retrieved, no attribute name is given, and the
-    result is not quoted. Multiple values are separated by (comma, space).
+    result is not quoted. Multiple values are separated by (comma).
     If more than one attribute is being retrieved, the data is given as a
     If more than one attribute is being retrieved, the data is given as a
-    sequence of name=value pairs, with the value always in quotes. If there are
-    multiple values, they are given within the quotes, comma separated. */
+    sequence of name=value pairs, separated by (space), with the value always in quotes.
+    If there are multiple values, they are given within the quotes, comma separated. */
 
     else for (attr = US ldap_first_attribute(lcp->ld, e, &ber);
               attr != NULL;
               attr = US ldap_next_attribute(lcp->ld, e, ber))
       {
 
     else for (attr = US ldap_first_attribute(lcp->ld, e, &ber);
               attr != NULL;
               attr = US ldap_next_attribute(lcp->ld, e, ber))
       {
+      DEBUG(D_lookup) debug_printf("LDAP attr loop\n");
+
+      /* In case of attrs_requested == 1 we just count the values, in all other cases
+      (0, >1) we count the values per attribute */
+      if (attrs_requested != 1) valuecount = 0;
+
       if (attr[0] != 0)
         {
         /* Get array of values for this attribute. */
       if (attr[0] != 0)
         {
         /* Get array of values for this attribute. */
@@ -751,43 +792,51 @@ while ((rc = ldap_result(lcp->ld, msgid, 0, timeoutptr, &result)) ==
         if ((firstval = values = USS ldap_get_values(lcp->ld, e, CS attr))
              != NULL)
           {
         if ((firstval = values = USS ldap_get_values(lcp->ld, e, CS attr))
              != NULL)
           {
-          if (attr_count != 1)
+
+          if (attrs_requested != 1)
             {
             if (insert_space)
             {
             if (insert_space)
-              data = string_cat(data, &size, &ptr, US" ", 1);
+              data = string_catn(data, &size, &ptr, US" ", 1);
             else
               insert_space = TRUE;
             else
               insert_space = TRUE;
-            data = string_cat(data, &size, &ptr, attr, Ustrlen(attr));
-            data = string_cat(data, &size, &ptr, US"=\"", 2);
+            data = string_cat(data, &size, &ptr, attr);
+            data = string_catn(data, &size, &ptr, US"=\"", 2);
             }
 
           while (*values != NULL)
             {
             uschar *value = *values;
             int len = Ustrlen(value);
             }
 
           while (*values != NULL)
             {
             uschar *value = *values;
             int len = Ustrlen(value);
+            ++valuecount;
 
 
-            DEBUG(D_lookup) debug_printf("LDAP attr loop %s:%s\n", attr, value);
+            DEBUG(D_lookup) debug_printf("LDAP value loop %s:%s\n", attr, value);
 
 
-            if (values != firstval)
-              data = string_cat(data, &size, &ptr, US",", 1);
+            /* In case we requested one attribute only but got several times
+            into that attr loop, we need to append the additional values.
+            (This may happen if you derive attributeTypes B and C from A and
+            then query for A.) In all other cases we detect the different
+            attribute and append only every non first value. */
+
+            if (data && valuecount > 1)
+              data = string_catn(data, &size, &ptr, US",", 1);
 
             /* For multiple attributes, the data is in quotes. We must escape
             internal quotes, backslashes, newlines, and must double commas. */
 
 
             /* For multiple attributes, the data is in quotes. We must escape
             internal quotes, backslashes, newlines, and must double commas. */
 
-            if (attr_count != 1)
+            if (attrs_requested != 1)
               {
               int j;
               for (j = 0; j < len; j++)
                 {
                 if (value[j] == '\n')
               {
               int j;
               for (j = 0; j < len; j++)
                 {
                 if (value[j] == '\n')
-                  data = string_cat(data, &size, &ptr, US"\\n", 2);
+                  data = string_catn(data, &size, &ptr, US"\\n", 2);
                 else if (value[j] == ',')
                 else if (value[j] == ',')
-                  data = string_cat(data, &size, &ptr, US",,", 2);
+                  data = string_catn(data, &size, &ptr, US",,", 2);
                 else
                   {
                   if (value[j] == '\"' || value[j] == '\\')
                 else
                   {
                   if (value[j] == '\"' || value[j] == '\\')
-                    data = string_cat(data, &size, &ptr, US"\\", 1);
-                  data = string_cat(data, &size, &ptr, value+j, 1);
+                    data = string_catn(data, &size, &ptr, US"\\", 1);
+                  data = string_catn(data, &size, &ptr, value+j, 1);
                   }
                 }
               }
                   }
                 }
               }
@@ -798,12 +847,10 @@ while ((rc = ldap_result(lcp->ld, msgid, 0, timeoutptr, &result)) ==
              {
              int j;
              for (j = 0; j < len; j++)
              {
              int j;
              for (j = 0; j < len; j++)
-               {
                if (value[j] == ',')
                if (value[j] == ',')
-                 data = string_cat(data, &size, &ptr, US",,", 2);
+                 data = string_catn(data, &size, &ptr, US",,", 2);
                else
                else
-                 data = string_cat(data, &size, &ptr, value+j, 1);
-               }
+                 data = string_catn(data, &size, &ptr, value+j, 1);
              }
 
 
              }
 
 
@@ -815,8 +862,8 @@ while ((rc = ldap_result(lcp->ld, msgid, 0, timeoutptr, &result)) ==
 
           /* Closing quote at the end of the data for a named attribute. */
 
 
           /* Closing quote at the end of the data for a named attribute. */
 
-          if (attr_count != 1)
-            data = string_cat(data, &size, &ptr, US"\"", 1);
+          if (attrs_requested != 1)
+            data = string_catn(data, &size, &ptr, US"\"", 1);
 
           /* Free the values */
 
 
           /* Free the values */
 
@@ -1090,7 +1137,7 @@ Returns:        OK or FAIL or DEFER
 */
 
 static int
 */
 
 static int
-control_ldap_search(uschar *ldap_url, int search_type, uschar **res,
+control_ldap_search(const uschar *ldap_url, int search_type, uschar **res,
   uschar **errmsg)
 {
 BOOL defer_break = FALSE;
   uschar **errmsg)
 {
 BOOL defer_break = FALSE;
@@ -1100,11 +1147,13 @@ int tcplimit = 0;
 int sep = 0;
 int dereference = LDAP_DEREF_NEVER;
 void* referrals = LDAP_OPT_ON;
 int sep = 0;
 int dereference = LDAP_DEREF_NEVER;
 void* referrals = LDAP_OPT_ON;
-uschar *url = ldap_url;
-uschar *p;
+const uschar *url = ldap_url;
+const uschar *p;
 uschar *user = NULL;
 uschar *password = NULL;
 uschar *user = NULL;
 uschar *password = NULL;
-uschar *server, *list;
+uschar *local_servers = NULL;
+uschar *server;
+const uschar *list;
 uschar buffer[512];
 
 while (isspace(*url)) url++;
 uschar buffer[512];
 
 while (isspace(*url)) url++;
@@ -1116,7 +1165,7 @@ NAME has the value "ldap". */
 
 while (strncmpic(url, US"ldap", 4) != 0)
   {
 
 while (strncmpic(url, US"ldap", 4) != 0)
   {
-  uschar *name = url;
+  const uschar *name = url;
   while (*url != 0 && *url != '=') url++;
   if (*url == '=')
     {
   while (*url != 0 && *url != '=') url++;
   if (*url == '=')
     {
@@ -1132,6 +1181,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 */
 
@@ -1259,16 +1309,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;
@@ -1299,8 +1349,8 @@ are handled by a common function, with a flag to differentiate between them.
 The handle and filename arguments are not used. */
 
 static int
 The handle and filename arguments are not used. */
 
 static int
-eldap_find(void *handle, uschar *filename, uschar *ldap_url, int length,
-  uschar **result, uschar **errmsg, BOOL *do_cache)
+eldap_find(void *handle, uschar *filename, const uschar *ldap_url, int length,
+  uschar **result, uschar **errmsg, uint *do_cache)
 {
 /* Keep picky compilers happy */
 do_cache = do_cache;
 {
 /* Keep picky compilers happy */
 do_cache = do_cache;
@@ -1308,8 +1358,8 @@ return(control_ldap_search(ldap_url, SEARCH_LDAP_SINGLE, result, errmsg));
 }
 
 static int
 }
 
 static int
-eldapm_find(void *handle, uschar *filename, uschar *ldap_url, int length,
-  uschar **result, uschar **errmsg, BOOL *do_cache)
+eldapm_find(void *handle, uschar *filename, const uschar *ldap_url, int length,
+  uschar **result, uschar **errmsg, uint *do_cache)
 {
 /* Keep picky compilers happy */
 do_cache = do_cache;
 {
 /* Keep picky compilers happy */
 do_cache = do_cache;
@@ -1317,8 +1367,8 @@ return(control_ldap_search(ldap_url, SEARCH_LDAP_MULTIPLE, result, errmsg));
 }
 
 static int
 }
 
 static int
-eldapdn_find(void *handle, uschar *filename, uschar *ldap_url, int length,
-  uschar **result, uschar **errmsg, BOOL *do_cache)
+eldapdn_find(void *handle, uschar *filename, const uschar *ldap_url, int length,
+  uschar **result, uschar **errmsg, uint *do_cache)
 {
 /* Keep picky compilers happy */
 do_cache = do_cache;
 {
 /* Keep picky compilers happy */
 do_cache = do_cache;
@@ -1326,8 +1376,8 @@ return(control_ldap_search(ldap_url, SEARCH_LDAP_DN, result, errmsg));
 }
 
 int
 }
 
 int
-eldapauth_find(void *handle, uschar *filename, uschar *ldap_url, int length,
-  uschar **result, uschar **errmsg, BOOL *do_cache)
+eldapauth_find(void *handle, uschar *filename, const uschar *ldap_url, int length,
+  uschar **result, uschar **errmsg, uint *do_cache)
 {
 /* Keep picky compilers happy */
 do_cache = do_cache;
 {
 /* Keep picky compilers happy */
 do_cache = do_cache;