Update version number and copyright year.
[exim.git] / src / src / lookups / ldap.c
index 7a21e8e014ceb7e5affa32f92636d9a280081786..048066e25af711e772de6f4fce8b2a952e0268f0 100644 (file)
@@ -1,10 +1,10 @@
-/* $Cambridge: exim/src/src/lookups/ldap.c,v 1.4 2004/11/17 16:31:45 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>
@@ -132,6 +137,7 @@ Arguments:
   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}
   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}
+  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
@@ -141,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;
@@ -167,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;
@@ -395,7 +401,7 @@ if (lcp == NULL)
   #ifdef LDAP_OPT_NETWORK_TIMEOUT
   if (tcplimit > 0)
     ldap_set_option(ld, LDAP_OPT_NETWORK_TIMEOUT, (void *)timeoutptr);
   #ifdef LDAP_OPT_NETWORK_TIMEOUT
   if (tcplimit > 0)
     ldap_set_option(ld, LDAP_OPT_NETWORK_TIMEOUT, (void *)timeoutptr);
-  #endif 
+  #endif
 
   /* I could not get TLS to work until I set the version to 3. That version
   seems to be the default nowadays. The RFC is dated 1997, so I would hope
 
   /* I could not get TLS to work until I set the version to 3. That version
   seems to be the default nowadays. The RFC is dated 1997, so I would hope
@@ -486,7 +492,7 @@ if (!lcp->bound ||
   if ((rc = ldap_result( lcp->ld, msgid, 1, timeoutptr, &result )) <= 0)
     {
     *errmsg = string_sprintf("failed to bind the LDAP connection to server "
   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, 
+      "%s%s - LDAP error: %s", host, porttext,
       rc == -1 ? "result retrieval failed" : "timeout" );
     result = NULL;
     goto RETURN_ERROR;
       rc == -1 ? "result retrieval failed" : "timeout" );
     result = NULL;
     goto RETURN_ERROR;
@@ -551,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");
@@ -560,7 +574,16 @@ 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;
   }
 
   goto RETURN_ERROR;
   }
 
@@ -779,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;
@@ -791,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);
@@ -816,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,
@@ -949,8 +995,9 @@ BOOL defer_break = FALSE;
 int timelimit = LDAP_NO_LIMIT;
 int sizelimit = LDAP_NO_LIMIT;
 int tcplimit = 0;
 int timelimit = LDAP_NO_LIMIT;
 int sizelimit = LDAP_NO_LIMIT;
 int tcplimit = 0;
-int dereference = LDAP_DEREF_NEVER;
 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;
@@ -1004,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
@@ -1053,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
@@ -1091,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 */
@@ -1108,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;
   }