Fix server_set_id for SPA/NTLM auth.
[exim.git] / src / src / lookups / ldap.c
index aa2e7bfd8472d24c5cdec745c7993e6965f3120f..244d6756120694623efbcf7bfa72eaa4924636bd 100644 (file)
@@ -1,10 +1,8 @@
-/* $Cambridge: exim/src/src/lookups/ldap.c,v 1.1 2004/10/07 13:10:01 ph10 Exp $ */
-
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2004 */
+/* Copyright (c) University of Cambridge 1995 - 2012 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Many thanks to Stuart Lynne for contributing the original code for this
@@ -15,23 +13,14 @@ researching how to handle the different kinds of error. */
 
 #include "../exim.h"
 #include "lf_functions.h"
-#include "ldap.h"
-
 
-/* We can't just compile this code and allow the library mechanism to omit the
-functions if they are not wanted, because we need to have the LDAP headers
-available for compiling. Therefore, compile these functions only if LOOKUP_LDAP
-is defined. However, some compilers don't like compiling empty modules, so keep
-them happy with a dummy when skipping the rest. Make it reference itself to
-stop picky compilers complaining that it is unused, and put in a dummy argument
-to stop even pickier compilers complaining about infinite loops. */
 
-#ifndef LOOKUP_LDAP
-static void dummy(int x) { dummy(x-1); }
-#else
+/* 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. */
 
-
-/* Include LDAP headers */
+#define LDAP_DEPRECATED 1
 
 #include <lber.h>
 #include <ldap.h>
@@ -72,13 +61,6 @@ LDAP_LIB_OPENLDAP1
 #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 */
@@ -136,9 +118,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
-  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}
+  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
@@ -148,7 +131,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,
-  int sizelimit, int timelimit, int tcplimit, int dereference)
+  int sizelimit, int timelimit, int tcplimit, int dereference, void *referrals)
 {
 LDAPURLDesc     *ludp = NULL;
 LDAPMessage     *result = NULL;
@@ -174,7 +157,7 @@ uschar *matched = NULL;  /* partially matched DN */
 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;
@@ -278,6 +261,15 @@ for (lcp = ldap_connections; lcp != NULL; lcp = lcp->next)
   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
@@ -376,7 +368,23 @@ if (lcp == NULL)
   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
@@ -421,6 +429,60 @@ if (lcp == NULL)
     }
   #endif  /* LDAP_OPT_X_TLS */
 
+  #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);
+    }
+  #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);
+    }
+  #endif
+  #ifdef LDAP_OPT_X_TLS_CERTFILE
+  if (eldap_cert_file != NULL)
+    {
+    ldap_set_option(ld, LDAP_OPT_X_TLS_CERTFILE, eldap_cert_file);
+    }
+  #endif
+  #ifdef LDAP_OPT_X_TLS_KEYFILE
+  if (eldap_cert_key != NULL)
+    {
+    ldap_set_option(ld, LDAP_OPT_X_TLS_KEYFILE, eldap_cert_key);
+    }
+  #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);
+    }
+  #endif
+  #ifdef LDAP_OPT_X_TLS_REQUIRE_CERT
+  if (eldap_require_cert != NULL)
+    {
+    int cert_option = LDAP_OPT_X_TLS_NEVER;
+    if (Ustrcmp(eldap_require_cert, "hard") == 0)
+      {
+      cert_option = LDAP_OPT_X_TLS_HARD;
+      }
+    else if (Ustrcmp(eldap_require_cert, "demand") == 0)
+      {
+      cert_option = LDAP_OPT_X_TLS_DEMAND;
+      }
+    else if (Ustrcmp(eldap_require_cert, "allow") == 0)
+      {
+      cert_option = LDAP_OPT_X_TLS_ALLOW;
+      }
+    else if (Ustrcmp(eldap_require_cert, "try") == 0)
+      {
+      cert_option = LDAP_OPT_X_TLS_TRY;
+      }
+    ldap_set_option(ld, LDAP_OPT_X_TLS_REQUIRE_CERT, &cert_option);
+    }
+  #endif
+
   /* Now add this connection to the chain of cached connections */
 
   lcp = store_get(sizeof(LDAP_CONNECTION));
@@ -457,23 +519,53 @@ if (!lcp->bound ||
   {
   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)
+#ifdef LDAP_OPT_X_TLS
+  /* The Oracle LDAP libraries (LDAP_LIB_TYPE=SOLARIS) don't support this: */
+  if (eldap_start_tls)
     {
-    /* Invalid credentials when just checking credentials returns FAIL. This
-    stops any further servers being tried. */
+        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;
+        }
+    }
+#endif
+  if ((msgid = ldap_bind(lcp->ld, CS user, CS password, LDAP_AUTH_SIMPLE))
+       == -1)
+    {
+    *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;
@@ -484,6 +576,9 @@ if (!lcp->bound ||
   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. */
@@ -512,6 +607,14 @@ an LDAP library without LDAP_OPT_DEREF. */
 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");
@@ -521,20 +624,22 @@ msgid = ldap_search(lcp->ld, ludp->lud_dn, ludp->lud_scope, ludp->lud_filter,
 
 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. */
 
-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)
   {
@@ -625,10 +730,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)
-              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
-            internal quotes, backslashes, newlines. */
+            internal quotes, backslashes, newlines, and must double commas. */
 
             if (attr_count != 1)
               {
@@ -637,6 +742,8 @@ while ((rc = ldap_result(lcp->ld, msgid, 0, timeoutptr, &result)) ==
                 {
                 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] == '\\')
@@ -646,9 +753,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 */
 
@@ -747,10 +865,16 @@ if (rc == -1 || result == NULL)
   }
 
 /* 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;
@@ -759,11 +883,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. */
 
-  #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);
@@ -784,18 +916,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.
 
-  (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.
 
-  (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));
 
-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,
@@ -916,9 +1057,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;
-int tcplimit = LDAP_X_IO_TIMEOUT_NO_TIMEOUT;
-int dereference = LDAP_DEREF_NEVER;
+int tcplimit = 0;
 int sep = 0;
+int dereference = LDAP_DEREF_NEVER;
+void* referrals = LDAP_OPT_ON;
 uschar *url = ldap_url;
 uschar *p;
 uschar *user = NULL;
@@ -949,7 +1091,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"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 */
 
@@ -971,7 +1114,29 @@ while (strncmpic(url, US"ldap", 4) != 0)
         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
@@ -1020,8 +1185,8 @@ if (user != NULL)
 
 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
@@ -1058,7 +1223,8 @@ if (Ustrncmp(p, "://", 3) != 0)
 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 */
@@ -1075,7 +1241,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,
-    &defer_break, user, password, sizelimit, timelimit, tcplimit, dereference);
+    &defer_break, user, password, sizelimit, timelimit, tcplimit, dereference,
+    referrals);
   if (rc != DEFER || defer_break) return rc;
   }
 
@@ -1092,7 +1259,7 @@ return DEFER;
 are handled by a common function, with a flag to differentiate between them.
 The handle and filename arguments are not used. */
 
-int
+static int
 eldap_find(void *handle, uschar *filename, uschar *ldap_url, int length,
   uschar **result, uschar **errmsg, BOOL *do_cache)
 {
@@ -1101,7 +1268,7 @@ do_cache = do_cache;
 return(control_ldap_search(ldap_url, SEARCH_LDAP_SINGLE, result, errmsg));
 }
 
-int
+static int
 eldapm_find(void *handle, uschar *filename, uschar *ldap_url, int length,
   uschar **result, uschar **errmsg, BOOL *do_cache)
 {
@@ -1110,7 +1277,7 @@ do_cache = do_cache;
 return(control_ldap_search(ldap_url, SEARCH_LDAP_MULTIPLE, result, errmsg));
 }
 
-int
+static int
 eldapdn_find(void *handle, uschar *filename, uschar *ldap_url, int length,
   uschar **result, uschar **errmsg, BOOL *do_cache)
 {
@@ -1136,7 +1303,7 @@ return(control_ldap_search(ldap_url, SEARCH_LDAP_AUTH, result, errmsg));
 
 /* See local README for interface description. */
 
-void *
+static void *
 eldap_open(uschar *filename, uschar **errmsg)
 {
 return (void *)(1);    /* Just return something non-null */
@@ -1151,7 +1318,7 @@ return (void *)(1);    /* Just return something non-null */
 /* See local README for interface description.
 Make sure that eldap_dn does not refer to reclaimed or worse, freed store */
 
-void
+static void
 eldap_tidy(void)
 {
 LDAP_CONNECTION *lcp = NULL;
@@ -1247,7 +1414,7 @@ quote_ldap_dn, respectively. */
 
 
 
-uschar *
+static uschar *
 eldap_quote(uschar *s, uschar *opt)
 {
 register int c;
@@ -1366,6 +1533,66 @@ else
 return quoted;
 }
 
-#endif  /* LOOKUP_LDAP */
+
+
+/*************************************************
+*         Version reporting entry point          *
+*************************************************/
+
+/* See local README for interface description. */
+
+#include "../version.h"
+
+void
+ldap_version_report(FILE *f)
+{
+#ifdef DYNLOOKUP
+fprintf(f, "Library version: LDAP: Exim version %s\n", EXIM_VERSION_STR);
+#endif
+}
+
+
+static lookup_info ldap_lookup_info = {
+  US"ldap",                      /* lookup name */
+  lookup_querystyle,             /* query-style lookup */
+  eldap_open,                    /* open function */
+  NULL,                          /* check function */
+  eldap_find,                    /* find function */
+  NULL,                          /* no close function */
+  eldap_tidy,                    /* tidy function */
+  eldap_quote,                   /* quoting function */
+  ldap_version_report            /* version reporting */
+};
+
+static lookup_info ldapdn_lookup_info = {
+  US"ldapdn",                     /* lookup name */
+  lookup_querystyle,             /* query-style lookup */
+  eldap_open,       /* sic */    /* open function */
+  NULL,                          /* check function */
+  eldapdn_find,                  /* find function */
+  NULL,                          /* no close function */
+  eldap_tidy,       /* sic */    /* tidy function */
+  eldap_quote,      /* sic */    /* quoting function */
+  NULL                           /* no version reporting (redundant) */
+};
+
+static lookup_info ldapm_lookup_info = {
+  US"ldapm",                     /* lookup name */
+  lookup_querystyle,             /* query-style lookup */
+  eldap_open,       /* sic */    /* open function */
+  NULL,                          /* check function */
+  eldapm_find,                   /* find function */
+  NULL,                          /* no close function */
+  eldap_tidy,       /* sic */    /* tidy function */
+  eldap_quote,      /* sic */    /* quoting function */
+  NULL                           /* no version reporting (redundant) */
+};
+
+#ifdef DYNLOOKUP
+#define ldap_lookup_module_info _lookup_module_info
+#endif
+
+static lookup_info *_lookup_list[] = { &ldap_lookup_info, &ldapdn_lookup_info, &ldapm_lookup_info };
+lookup_module_info ldap_lookup_module_info = { LOOKUP_MODULE_INFO_MAGIC, _lookup_list, 3 };
 
 /* End of lookups/ldap.c */