Fix issue with long lines with comments in lsearch. fixes: #894
[exim.git] / src / src / lookups / ldap.c
index aa2e7bfd8472d24c5cdec745c7993e6965f3120f..048066e25af711e772de6f4fce8b2a952e0268f0 100644 (file)
@@ -1,10 +1,10 @@
-/* $Cambridge: exim/src/src/lookups/ldap.c,v 1.1 2004/10/07 13:10:01 ph10 Exp $ */
+/* $Cambridge: exim/src/src/lookups/ldap.c,v 1.14 2007/01/08 10:50:19 ph10 Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2004 */
+/* Copyright (c) University of Cambridge 1995 - 2007 */
 /* 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
@@ -31,7 +31,12 @@ static void dummy(int x) { dummy(x-1); }
 #else
 
 
 #else
 
 
-/* Include LDAP headers */
+/* Include LDAP headers. The code below uses some "old" LDAP interfaces that
+are deprecated in OpenLDAP. I don't know their status in other LDAP
+implementations. LDAP_DEPRECATED causes their prototypes to be defined in
+ldap.h. */
+
+#define LDAP_DEPRECATED 1
 
 #include <lber.h>
 #include <ldap.h>
 
 #include <lber.h>
 #include <ldap.h>
@@ -72,13 +77,6 @@ LDAP_LIB_OPENLDAP1
 #endif
 
 
 #endif
 
 
-/* For libraries without TCP connect timeouts */
-
-#ifndef LDAP_X_IO_TIMEOUT_NO_TIMEOUT
-#define LDAP_X_IO_TIMEOUT_NO_TIMEOUT (-1)
-#endif
-
-
 /* Four types of LDAP search are implemented */
 
 #define SEARCH_LDAP_MULTIPLE 0       /* Get attributes from multiple entries */
 /* Four types of LDAP search are implemented */
 
 #define SEARCH_LDAP_MULTIPLE 0       /* Get attributes from multiple entries */
@@ -136,9 +134,10 @@ Arguments:
   password      password for authentication, or NULL
   sizelimit     max number of entries returned, or 0 for no limit
   timelimit     max time to wait, or 0 for no limit
   password      password for authentication, or NULL
   sizelimit     max number of entries returned, or 0 for no limit
   timelimit     max time to wait, or 0 for no limit
-  tcplimit      max time to connect, or NULL for OS default
+  tcplimit      max time for network activity, e.g. connect, or 0 for OS default
   deference     the dereference option, which is one of
                   LDAP_DEREF_{NEVER,SEARCHING,FINDING,ALWAYS}
   deference     the dereference option, which is one of
                   LDAP_DEREF_{NEVER,SEARCHING,FINDING,ALWAYS}
+  referrals     the referral option, which is LDAP_OPT_ON or LDAP_OPT_OFF
 
 Returns:        OK or FAIL or DEFER
                 FAIL is given only if a lookup was performed successfully, but
 
 Returns:        OK or FAIL or DEFER
                 FAIL is given only if a lookup was performed successfully, but
@@ -148,7 +147,7 @@ Returns:        OK or FAIL or DEFER
 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,
 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)
+  int sizelimit, int timelimit, int tcplimit, int dereference, void *referrals)
 {
 LDAPURLDesc     *ludp = NULL;
 LDAPMessage     *result = NULL;
 {
 LDAPURLDesc     *ludp = NULL;
 LDAPMessage     *result = NULL;
@@ -174,7 +173,7 @@ uschar *matched = NULL;  /* partially matched DN */
 int    attr_count = 0;
 int    error_yield = DEFER;
 int    msgid;
 int    attr_count = 0;
 int    error_yield = DEFER;
 int    msgid;
-int    rc;
+int    rc, ldap_rc, ldap_parse_rc;
 int    port;
 int    ptr = 0;
 int    rescount = 0;
 int    port;
 int    ptr = 0;
 int    rescount = 0;
@@ -278,6 +277,15 @@ for (lcp = ldap_connections; lcp != NULL; lcp = lcp->next)
   if (ldapi || port == lcp->port) break;
   }
 
   if (ldapi || port == lcp->port) break;
   }
 
+/* Use this network timeout in any requests. */
+
+if (tcplimit > 0)
+  {
+  timeout.tv_sec = tcplimit;
+  timeout.tv_usec = 0;
+  timeoutptr = &timeout;
+  }
+
 /* If no cached connection found, we must open a connection to the server. If
 the server name is actually an absolute path, we set ldapi=TRUE above. This
 requests connection via a Unix socket. However, as far as I know, only OpenLDAP
 /* If no cached connection found, we must open a connection to the server. If
 the server name is actually an absolute path, we set ldapi=TRUE above. This
 requests connection via a Unix socket. However, as far as I know, only OpenLDAP
@@ -376,7 +384,23 @@ if (lcp == NULL)
   in Netscape SDK v4.1; I don't know about other libraries. */
 
   #ifdef LDAP_X_OPT_CONNECT_TIMEOUT
   in Netscape SDK v4.1; I don't know about other libraries. */
 
   #ifdef LDAP_X_OPT_CONNECT_TIMEOUT
-  ldap_set_option(ld, LDAP_X_OPT_CONNECT_TIMEOUT, (void *)&tcplimit);
+  if (tcplimit > 0)
+    {
+    int timeout1000 = tcplimit*1000;
+    ldap_set_option(ld, LDAP_X_OPT_CONNECT_TIMEOUT, (void *)&timeout1000);
+    }
+  else
+    {
+    int notimeout = LDAP_X_IO_TIMEOUT_NO_TIMEOUT;
+    ldap_set_option(ld, LDAP_X_OPT_CONNECT_TIMEOUT, (void *)&notimeout);
+    }
+  #endif
+
+  /* Set the TCP connect timeout. This works with OpenLDAP 2.2.14. */
+
+  #ifdef LDAP_OPT_NETWORK_TIMEOUT
+  if (tcplimit > 0)
+    ldap_set_option(ld, LDAP_OPT_NETWORK_TIMEOUT, (void *)timeoutptr);
   #endif
 
   /* I could not get TLS to work until I set the version to 3. That version
   #endif
 
   /* I could not get TLS to work until I set the version to 3. That version
@@ -457,23 +481,41 @@ 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 ((rc = ldap_bind_s(lcp->ld, CS user, CS password, LDAP_AUTH_SIMPLE))
-       != LDAP_SUCCESS)
+  if ((msgid = ldap_bind(lcp->ld, CS user, CS password, LDAP_AUTH_SIMPLE))
+       == -1)
     {
     {
-    /* Invalid credentials when just checking credentials returns FAIL. This
-    stops any further servers being tried. */
+    *errmsg = string_sprintf("failed to bind the LDAP connection to server "
+      "%s%s - ldap_bind() returned -1", host, porttext);
+    goto RETURN_ERROR;
+    }
 
 
-    if (search_type == SEARCH_LDAP_AUTH && rc == LDAP_INVALID_CREDENTIALS)
-      {
-      DEBUG(D_lookup)
-        debug_printf("Invalid credentials: ldapauth returns FAIL\n");
-      error_yield = FAIL;
-      goto RETURN_ERROR_NOMSG;
-      }
+  if ((rc = ldap_result( lcp->ld, msgid, 1, timeoutptr, &result )) <= 0)
+    {
+    *errmsg = string_sprintf("failed to bind the LDAP connection to server "
+      "%s%s - LDAP error: %s", host, porttext,
+      rc == -1 ? "result retrieval failed" : "timeout" );
+    result = NULL;
+    goto RETURN_ERROR;
+    }
+
+  rc = ldap_result2error( lcp->ld, result, 0 );
 
 
-    /* Otherwise we have a problem that doesn't stop further servers from being
-    tried. */
+  /* Invalid credentials when just checking credentials returns FAIL. This
+  stops any further servers being tried. */
 
 
+  if (search_type == SEARCH_LDAP_AUTH && rc == LDAP_INVALID_CREDENTIALS)
+    {
+    DEBUG(D_lookup)
+      debug_printf("Invalid credentials: ldapauth returns FAIL\n");
+    error_yield = FAIL;
+    goto RETURN_ERROR_NOMSG;
+    }
+
+  /* Otherwise we have a problem that doesn't stop further servers from being
+  tried. */
+
+  if (rc != LDAP_SUCCESS)
+    {
     *errmsg = string_sprintf("failed to bind the LDAP connection to server "
       "%s%s - LDAP error %d: %s", host, porttext, rc, ldap_err2string(rc));
     goto RETURN_ERROR;
     *errmsg = string_sprintf("failed to bind the LDAP connection to server "
       "%s%s - LDAP error %d: %s", host, porttext, rc, ldap_err2string(rc));
     goto RETURN_ERROR;
@@ -484,6 +526,9 @@ if (!lcp->bound ||
   lcp->bound = TRUE;
   lcp->user = (user == NULL)? NULL : string_copy(user);
   lcp->password = (password == NULL)? NULL : string_copy(password);
   lcp->bound = TRUE;
   lcp->user = (user == NULL)? NULL : string_copy(user);
   lcp->password = (password == NULL)? NULL : string_copy(password);
+
+  ldap_msgfree(result);
+  result = NULL;
   }
 
 /* If we are just checking credentials, return OK. */
   }
 
 /* If we are just checking credentials, return OK. */
@@ -512,6 +557,14 @@ an LDAP library without LDAP_OPT_DEREF. */
 ldap_set_option(lcp->ld, LDAP_OPT_DEREF, (void *)&dereference);
 #endif
 
 ldap_set_option(lcp->ld, LDAP_OPT_DEREF, (void *)&dereference);
 #endif
 
+/* Similarly for the referral setting; should the library follow referrals that
+the LDAP server returns? The conditional is just in case someone uses a library
+without it. */
+
+#if defined(LDAP_OPT_REFERRALS)
+ldap_set_option(lcp->ld, LDAP_OPT_REFERRALS, referrals);
+#endif
+
 /* Start the search on the server. */
 
 DEBUG(D_lookup) debug_printf("Start search\n");
 /* Start the search on the server. */
 
 DEBUG(D_lookup) debug_printf("Start search\n");
@@ -521,20 +574,22 @@ msgid = ldap_search(lcp->ld, ludp->lud_dn, ludp->lud_scope, ludp->lud_filter,
 
 if (msgid == -1)
   {
 
 if (msgid == -1)
   {
-  *errmsg = string_sprintf("ldap search initiation failed");
+  #if defined LDAP_LIB_SOLARIS || defined LDAP_LIB_OPENLDAP2
+  int err;
+  ldap_get_option(lcp->ld, LDAP_OPT_ERROR_NUMBER, &err);
+  *errmsg = string_sprintf("ldap_search failed: %d, %s", err,
+    ldap_err2string(err));
+
+  #else
+  *errmsg = string_sprintf("ldap_search failed");
+  #endif
+
   goto RETURN_ERROR;
   }
 
 /* Loop to pick up results as they come in, setting a timeout if one was
 given. */
 
   goto RETURN_ERROR;
   }
 
 /* Loop to pick up results as they come in, setting a timeout if one was
 given. */
 
-if (timelimit > 0)
-  {
-  timeout.tv_sec = timelimit;
-  timeout.tv_usec = 0;
-  timeoutptr = &timeout;
-  }
-
 while ((rc = ldap_result(lcp->ld, msgid, 0, timeoutptr, &result)) ==
         LDAP_RES_SEARCH_ENTRY)
   {
 while ((rc = ldap_result(lcp->ld, msgid, 0, timeoutptr, &result)) ==
         LDAP_RES_SEARCH_ENTRY)
   {
@@ -747,10 +802,16 @@ if (rc == -1 || result == NULL)
   }
 
 /* A return code that isn't -1 doesn't necessarily mean there were no problems
   }
 
 /* A return code that isn't -1 doesn't necessarily mean there were no problems
-with the search. The message must be an LDAP_RES_SEARCH_RESULT or else it's
-something we can't handle. */
-
-if (rc != LDAP_RES_SEARCH_RESULT)
+with the search. The message must be an LDAP_RES_SEARCH_RESULT or
+LDAP_RES_SEARCH_REFERENCE or else it's something we can't handle. Some versions
+of LDAP do not define LDAP_RES_SEARCH_REFERENCE (LDAP v1 is one, it seems). So
+we don't provide that functionality when we can't. :-) */
+
+if (rc != LDAP_RES_SEARCH_RESULT
+#ifdef LDAP_RES_SEARCH_REFERENCE
+    && rc != LDAP_RES_SEARCH_REFERENCE
+#endif
+   )
   {
   *errmsg = string_sprintf("ldap_result returned unexpected code %d", rc);
   goto RETURN_ERROR;
   {
   *errmsg = string_sprintf("ldap_result returned unexpected code %d", rc);
   goto RETURN_ERROR;
@@ -759,11 +820,19 @@ if (rc != LDAP_RES_SEARCH_RESULT)
 /* We have a result message from the server. This doesn't yet mean all is well.
 We need to parse the message to find out exactly what's happened. */
 
 /* We have a result message from the server. This doesn't yet mean all is well.
 We need to parse the message to find out exactly what's happened. */
 
-  #if defined LDAP_LIB_SOLARIS || defined LDAP_LIB_OPENLDAP2
-  if (ldap_parse_result(lcp->ld, result, &rc, CSS &matched, CSS &error2, NULL,
-      NULL, 0) < 0)
+#if defined LDAP_LIB_SOLARIS || defined LDAP_LIB_OPENLDAP2
+  ldap_rc = rc;
+  ldap_parse_rc = ldap_parse_result(lcp->ld, result, &rc, CSS &matched,
+    CSS &error2, NULL, NULL, 0);
+  DEBUG(D_lookup) debug_printf("ldap_parse_result: %d\n", ldap_parse_rc);
+  if (ldap_parse_rc < 0 &&
+      (ldap_parse_rc != LDAP_NO_RESULTS_RETURNED
+      #ifdef LDAP_RES_SEARCH_REFERENCE
+      || ldap_rc != LDAP_RES_SEARCH_REFERENCE
+      #endif
+     ))
     {
     {
-    *errmsg = US"ldap_parse_result failed";
+    *errmsg = string_sprintf("ldap_parse_result failed %d", ldap_parse_rc);
     goto RETURN_ERROR;
     }
   error1 = US ldap_err2string(rc);
     goto RETURN_ERROR;
     }
   error1 = US ldap_err2string(rc);
@@ -784,18 +853,27 @@ We need to parse the message to find out exactly what's happened. */
   (1) If we get LDAP_SIZELIMIT_EXCEEDED, just carry on, to return the
       truncated result list.
 
   (1) If we get LDAP_SIZELIMIT_EXCEEDED, just carry on, to return the
       truncated result list.
 
-  (2) The range of errors defined by LDAP_NAME_ERROR generally mean "that
+  (2) If we get LDAP_RES_SEARCH_REFERENCE, also just carry on. This was a
+      submitted patch that is reported to "do the right thing" with Solaris
+      LDAP libraries. (The problem it addresses apparently does not occur with
+      Open LDAP.)
+
+  (3) The range of errors defined by LDAP_NAME_ERROR generally mean "that
       object does not, or cannot, exist in the database". For those cases we
       fail the lookup.
 
       object does not, or cannot, exist in the database". For those cases we
       fail the lookup.
 
-  (3) All other non-successes here are treated as some kind of problem with
+  (4) All other non-successes here are treated as some kind of problem with
       the lookup, so return DEFER (which is the default in error_yield).
 */
 
 DEBUG(D_lookup) debug_printf("ldap_parse_result yielded %d: %s\n",
   rc, ldap_err2string(rc));
 
       the lookup, so return DEFER (which is the default in error_yield).
 */
 
 DEBUG(D_lookup) debug_printf("ldap_parse_result yielded %d: %s\n",
   rc, ldap_err2string(rc));
 
-if (rc != LDAP_SUCCESS && rc != LDAP_SIZELIMIT_EXCEEDED)
+if (rc != LDAP_SUCCESS && rc != LDAP_SIZELIMIT_EXCEEDED
+    #ifdef LDAP_RES_SEARCH_REFERENCE
+    && rc != LDAP_RES_SEARCH_REFERENCE
+    #endif
+    )
   {
   *errmsg = string_sprintf("LDAP search failed - error %d: %s%s%s%s%s",
     rc,
   {
   *errmsg = string_sprintf("LDAP search failed - error %d: %s%s%s%s%s",
     rc,
@@ -916,9 +994,10 @@ control_ldap_search(uschar *ldap_url, int search_type, uschar **res,
 BOOL defer_break = FALSE;
 int timelimit = LDAP_NO_LIMIT;
 int sizelimit = LDAP_NO_LIMIT;
 BOOL defer_break = FALSE;
 int timelimit = LDAP_NO_LIMIT;
 int sizelimit = LDAP_NO_LIMIT;
-int tcplimit = LDAP_X_IO_TIMEOUT_NO_TIMEOUT;
-int dereference = LDAP_DEREF_NEVER;
+int tcplimit = 0;
 int sep = 0;
 int sep = 0;
+int dereference = LDAP_DEREF_NEVER;
+void* referrals = LDAP_OPT_ON;
 uschar *url = ldap_url;
 uschar *p;
 uschar *user = NULL;
 uschar *url = ldap_url;
 uschar *p;
 uschar *user = NULL;
@@ -949,7 +1028,8 @@ while (strncmpic(url, US"ldap", 4) != 0)
       else if (strncmpic(name, US"PASS=", namelen) == 0) password = value;
       else if (strncmpic(name, US"SIZE=", namelen) == 0) sizelimit = Uatoi(value);
       else if (strncmpic(name, US"TIME=", namelen) == 0) timelimit = Uatoi(value);
       else if (strncmpic(name, US"PASS=", namelen) == 0) password = value;
       else if (strncmpic(name, US"SIZE=", namelen) == 0) sizelimit = Uatoi(value);
       else if (strncmpic(name, US"TIME=", namelen) == 0) timelimit = Uatoi(value);
-      else if (strncmpic(name, US"CONNECT=", namelen) == 0) tcplimit = Uatoi(value) * 1000;
+      else if (strncmpic(name, US"CONNECT=", namelen) == 0) tcplimit = Uatoi(value);
+      else if (strncmpic(name, US"NETTIME=", namelen) == 0) tcplimit = Uatoi(value);
 
       /* Don't know if all LDAP libraries have LDAP_OPT_DEREF */
 
 
       /* Don't know if all LDAP libraries have LDAP_OPT_DEREF */
 
@@ -971,7 +1051,29 @@ while (strncmpic(url, US"ldap", 4) != 0)
         DEBUG(D_lookup) debug_printf("%s\n", *errmsg);
         return DEFER;
         }
         DEBUG(D_lookup) debug_printf("%s\n", *errmsg);
         return DEFER;
         }
+      #endif
 
 
+      #ifdef LDAP_OPT_REFERRALS
+      else if (strncmpic(name, US"REFERRALS=", namelen) == 0)
+        {
+        if (strcmpic(value, US"follow") == 0) referrals = LDAP_OPT_ON;
+        else if (strcmpic(value, US"nofollow") == 0) referrals = LDAP_OPT_OFF;
+        else
+          {
+          *errmsg = string_sprintf("LDAP option REFERRALS is not \"follow\" "
+            "or \"nofollow\"");
+          DEBUG(D_lookup) debug_printf("%s\n", *errmsg);
+          return DEFER;
+          }
+        }
+      #else
+      else if (strncmpic(name, US"REFERRALS=", namelen) == 0)
+        {
+        *errmsg = string_sprintf("LDAP_OP_REFERRALS not defined in this LDAP "
+          "library - cannot use \"referrals\"");
+        DEBUG(D_lookup) debug_printf("%s\n", *errmsg);
+        return DEFER;
+        }
       #endif
 
       else
       #endif
 
       else
@@ -1020,8 +1122,8 @@ if (user != NULL)
 
 DEBUG(D_lookup)
   debug_printf("LDAP parameters: user=%s pass=%s size=%d time=%d connect=%d "
 
 DEBUG(D_lookup)
   debug_printf("LDAP parameters: user=%s pass=%s size=%d time=%d connect=%d "
-    "dereference=%d\n", user, password, sizelimit, timelimit, tcplimit,
-    dereference);
+    "dereference=%d referrals=%s\n", user, password, sizelimit, timelimit,
+    tcplimit, dereference, (referrals == LDAP_OPT_ON)? "on" : "off");
 
 /* If the request is just to check authentication, some credentials must
 be given. The password must not be empty because LDAP binds with an empty
 
 /* If the request is just to check authentication, some credentials must
 be given. The password must not be empty because LDAP binds with an empty
@@ -1058,7 +1160,8 @@ if (Ustrncmp(p, "://", 3) != 0)
 if (eldap_default_servers == NULL || p[3] != '/')
   {
   return perform_ldap_search(url, NULL, 0, search_type, res, errmsg,
 if (eldap_default_servers == NULL || p[3] != '/')
   {
   return perform_ldap_search(url, NULL, 0, search_type, res, errmsg,
-    &defer_break, user, password, sizelimit, timelimit, tcplimit, dereference);
+    &defer_break, user, password, sizelimit, timelimit, tcplimit, dereference,
+    referrals);
   }
 
 /* Loop through the default servers until OK or FAIL */
   }
 
 /* Loop through the default servers until OK or FAIL */
@@ -1075,7 +1178,8 @@ while ((server = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL
     port = Uatoi(colon+1);
     }
   rc = perform_ldap_search(url, server, port, search_type, res, errmsg,
     port = Uatoi(colon+1);
     }
   rc = perform_ldap_search(url, server, port, search_type, res, errmsg,
-    &defer_break, user, password, sizelimit, timelimit, tcplimit, dereference);
+    &defer_break, user, password, sizelimit, timelimit, tcplimit, dereference,
+    referrals);
   if (rc != DEFER || defer_break) return rc;
   }
 
   if (rc != DEFER || defer_break) return rc;
   }