SPDX: license tags (mostly by guesswork)
[exim.git] / src / src / lookups / ldap.c
index f77318626aabff64db75d9b46188f678c9c657a6..17c431e5c7e7d43270dff8cd81d2eeb99435268e 100644 (file)
@@ -2,8 +2,10 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
+/* Copyright (c) The Exim Maintainers 2020 - 2022 */
 /* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 /* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
+/* SPDX-License-Identifier: GPL-2.0-only */
 
 /* Many thanks to Stuart Lynne for contributing the original code for this
 driver. Further contributions from Michael Haardt, Brian Candler, Barry
 
 /* Many thanks to Stuart Lynne for contributing the original code for this
 driver. Further contributions from Michael Haardt, Brian Candler, Barry
@@ -163,8 +165,8 @@ int    rescount = 0;
 BOOL   attribute_found = FALSE;
 BOOL   ldapi = FALSE;
 
 BOOL   attribute_found = FALSE;
 BOOL   ldapi = FALSE;
 
-DEBUG(D_lookup)
-  debug_printf("perform_ldap_search: ldap%s URL = \"%s\" server=%s port=%d "
+DEBUG(D_lookup) debug_printf_indent("perform_ldap_search:"
+    " ldap%s URL = \"%s\" server=%s port=%d "
     "sizelimit=%d timelimit=%d tcplimit=%d\n",
     search_type == SEARCH_LDAP_MULTIPLE ? "m" :
     search_type == SEARCH_LDAP_DN       ? "dn" :
     "sizelimit=%d timelimit=%d tcplimit=%d\n",
     search_type == SEARCH_LDAP_MULTIPLE ? "m" :
     search_type == SEARCH_LDAP_DN       ? "dn" :
@@ -207,7 +209,7 @@ else
   port = ludp->lud_port;
   }
 
   port = ludp->lud_port;
   }
 
-DEBUG(D_lookup) debug_printf("after ldap_url_parse: host=%s port=%d\n",
+DEBUG(D_lookup) debug_printf_indent("after ldap_url_parse: host=%s port=%d\n",
   host, port);
 
 if (port == 0) port = LDAP_PORT;      /* Default if none given */
   host, port);
 
 if (port == 0) port = LDAP_PORT;      /* Default if none given */
@@ -301,24 +303,18 @@ if (!lcp)
   than the host name + "ldaps:///" plus : and a port number, say 20 + the
   length of the host name. What we get should accommodate both, easily. */
 
   than the host name + "ldaps:///" plus : and a port number, say 20 + the
   length of the host name. What we get should accommodate both, easily. */
 
-  uschar *shost = (host == NULL)? US"" : host;
-  uschar *init_url = store_get(20 + 3 * Ustrlen(shost));
-  uschar *init_ptr;
+  uschar * shost = host ? host : US"";
+  rmark reset_point = store_mark();
+  gstring * g;
 
   /* Handle connection via Unix socket ("ldapi"). We build a basic LDAP URI to
   contain the path name, with slashes escaped as %2F. */
 
   if (ldapi)
     {
 
   /* Handle connection via Unix socket ("ldapi"). We build a basic LDAP URI to
   contain the path name, with slashes escaped as %2F. */
 
   if (ldapi)
     {
-    int ch;
-    init_ptr = init_url + 8;
-    Ustrcpy(init_url, "ldapi://");
-    while ((ch = *shost++))
-      if (ch == '/')
-       { Ustrncpy(init_ptr, "%2F", 3); init_ptr += 3; }
-      else
-       *init_ptr++ = ch;
-    *init_ptr = 0;
+    g = string_catn(NULL, US"ldapi://", 8);
+    for (uschar ch; (ch = *shost); shost++)
+      g = ch == '/' ? string_catn(g, US"%2F", 3) : string_catn(g, shost, 1);
     }
 
   /* This is not an ldapi call. Just build a URI with the protocol type, host
     }
 
   /* This is not an ldapi call. Just build a URI with the protocol type, host
@@ -326,22 +322,22 @@ if (!lcp)
 
   else
     {
 
   else
     {
-    init_ptr = Ustrchr(ldap_url, '/');
-    Ustrncpy(init_url, ldap_url, init_ptr - ldap_url);
-    init_ptr = init_url + (init_ptr - ldap_url);
-    sprintf(CS init_ptr, "//%s:%d/", shost, port);
+    uschar * init_ptr = Ustrchr(ldap_url, '/');
+    g = string_catn(NULL, ldap_url, init_ptr - ldap_url);
+    g = string_fmt_append(g, "//%s:%d/", shost, port);
     }
     }
+  string_from_gstring(g);
 
   /* Call ldap_initialize() and check the result */
 
 
   /* Call ldap_initialize() and check the result */
 
-  DEBUG(D_lookup) debug_printf("ldap_initialize with URL %s\n", init_url);
-  if ((rc = ldap_initialize(&ld, CS init_url)) != LDAP_SUCCESS)
+  DEBUG(D_lookup) debug_printf_indent("ldap_initialize with URL %s\n", g->s);
+  if ((rc = ldap_initialize(&ld, CS g->s)) != LDAP_SUCCESS)
     {
     *errmsg = string_sprintf("ldap_initialize: (error %d) URL \"%s\"\n",
     {
     *errmsg = string_sprintf("ldap_initialize: (error %d) URL \"%s\"\n",
-      rc, init_url);
+      rc, g->s);
     goto RETURN_ERROR;
     }
     goto RETURN_ERROR;
     }
-  store_reset(init_url);   /* Might as well save memory when we can */
+  store_reset(reset_point);   /* Might as well save memory when we can */
 
 
   /* ------------------------- Not OpenLDAP ---------------------- */
 
 
   /* ------------------------- Not OpenLDAP ---------------------- */
@@ -409,7 +405,7 @@ if (!lcp)
   ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, (void *)&eldap_version);
 #endif
 
   ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, (void *)&eldap_version);
 #endif
 
-  DEBUG(D_lookup) debug_printf("initialized for LDAP (v%d) server %s%s\n",
+  DEBUG(D_lookup) debug_printf_indent("initialized for LDAP (v%d) server %s%s\n",
     eldap_version, host, porttext);
 
   /* If not using ldapi and TLS is available, set appropriate TLS options: hard
     eldap_version, host, porttext);
 
   /* If not using ldapi and TLS is available, set appropriate TLS options: hard
@@ -429,9 +425,9 @@ if (!lcp)
        : Ustrcmp(eldap_require_cert, "try")    == 0 ? LDAP_OPT_X_TLS_TRY
        : LDAP_OPT_X_TLS_NEVER;
 
        : Ustrcmp(eldap_require_cert, "try")    == 0 ? LDAP_OPT_X_TLS_TRY
        : LDAP_OPT_X_TLS_NEVER;
 
-      DEBUG(D_lookup)
-        debug_printf("Require certificate overrides LDAP_OPT_X_TLS option (%d)\n",
-                     tls_option);
+      DEBUG(D_lookup) debug_printf_indent(
+       "Require certificate overrides LDAP_OPT_X_TLS option (%d)\n",
+       tls_option);
       }
     else
 # endif  /* LDAP_OPT_X_TLS_REQUIRE_CERT */
       }
     else
 # endif  /* LDAP_OPT_X_TLS_REQUIRE_CERT */
@@ -439,13 +435,13 @@ if (!lcp)
       {
       tls_option = LDAP_OPT_X_TLS_HARD;
       DEBUG(D_lookup)
       {
       tls_option = LDAP_OPT_X_TLS_HARD;
       DEBUG(D_lookup)
-        debug_printf("LDAP_OPT_X_TLS_HARD set due to ldaps:// URI\n");
+        debug_printf_indent("LDAP_OPT_X_TLS_HARD set due to ldaps:// URI\n");
       }
     else
       {
       tls_option = LDAP_OPT_X_TLS_TRY;
       DEBUG(D_lookup)
       }
     else
       {
       tls_option = LDAP_OPT_X_TLS_TRY;
       DEBUG(D_lookup)
-        debug_printf("LDAP_OPT_X_TLS_TRY set due to ldap:// URI\n");
+        debug_printf_indent("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);
     }
@@ -488,21 +484,21 @@ if (!lcp)
     rc = ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_REQUIRE_CERT, &cert_option);
     if (rc)
       DEBUG(D_lookup)
     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",
+        debug_printf_indent("Unable to set TLS require cert_option(%d) globally: %s\n",
           cert_option, ldap_err2string(rc));
     }
 #endif
 #ifdef LDAP_OPT_X_TLS_NEWCTX
   if ((rc = ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_NEWCTX, &am_server)))
     DEBUG(D_lookup)
           cert_option, ldap_err2string(rc));
     }
 #endif
 #ifdef LDAP_OPT_X_TLS_NEWCTX
   if ((rc = ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_NEWCTX, &am_server)))
     DEBUG(D_lookup)
-      debug_printf("Unable to reload TLS context %d: %s\n",
+      debug_printf_indent("Unable to reload TLS context %d: %s\n",
                    rc, ldap_err2string(rc));
   #endif
 
   /* Now add this connection to the chain of cached connections */
 
                    rc, ldap_err2string(rc));
   #endif
 
   /* Now add this connection to the chain of cached connections */
 
-  lcp = store_get(sizeof(LDAP_CONNECTION));
-  lcp->host = (host == NULL)? NULL : string_copy(host);
+  lcp = store_get(sizeof(LDAP_CONNECTION), GET_UNTAINTED);
+  lcp->host = host ? string_copy(host) : NULL;
   lcp->bound = FALSE;
   lcp->user = NULL;
   lcp->password = NULL;
   lcp->bound = FALSE;
   lcp->user = NULL;
   lcp->password = NULL;
@@ -517,7 +513,7 @@ if (!lcp)
 
 else
   DEBUG(D_lookup)
 
 else
   DEBUG(D_lookup)
-    debug_printf("re-using cached connection to LDAP server %s%s\n",
+    debug_printf_indent("re-using cached connection to LDAP server %s%s\n",
       host, porttext);
 
 /* Bind with the user/password supplied, or an anonymous bind if these values
       host, porttext);
 
 /* Bind with the user/password supplied, or an anonymous bind if these values
@@ -532,7 +528,7 @@ if (  !lcp->bound
    || lcp->password && password && Ustrcmp(lcp->password, password) != 0
    )
   {
    || lcp->password && password && Ustrcmp(lcp->password, password) != 0
    )
   {
-  DEBUG(D_lookup) debug_printf("%sbinding with user=%s password=%s\n",
+  DEBUG(D_lookup) debug_printf_indent("%sbinding with user=%s password=%s\n",
     lcp->bound ? "re-" : "", user, password);
 
   if (eldap_start_tls && !lcp->is_start_tls_called && !ldapi)
     lcp->bound ? "re-" : "", user, password);
 
   if (eldap_start_tls && !lcp->is_start_tls_called && !ldapi)
@@ -551,7 +547,7 @@ if (  !lcp->bound
       }
     lcp->is_start_tls_called = TRUE;
 #else
       }
     lcp->is_start_tls_called = TRUE;
 #else
-    DEBUG(D_lookup) debug_printf("TLS initiation not supported with this Exim"
+    DEBUG(D_lookup) debug_printf_indent("TLS initiation not supported with this Exim"
       " and your LDAP library.\n");
 #endif
     }
       " and your LDAP library.\n");
 #endif
     }
@@ -580,7 +576,7 @@ if (  !lcp->bound
   if (search_type == SEARCH_LDAP_AUTH && rc == LDAP_INVALID_CREDENTIALS)
     {
     DEBUG(D_lookup)
   if (search_type == SEARCH_LDAP_AUTH && rc == LDAP_INVALID_CREDENTIALS)
     {
     DEBUG(D_lookup)
-      debug_printf("Invalid credentials: ldapauth returns FAIL\n");
+      debug_printf_indent("Invalid credentials: ldapauth returns FAIL\n");
     error_yield = FAIL;
     goto RETURN_ERROR_NOMSG;
     }
     error_yield = FAIL;
     goto RETURN_ERROR_NOMSG;
     }
@@ -609,7 +605,7 @@ if (  !lcp->bound
 
 if (search_type == SEARCH_LDAP_AUTH)
   {
 
 if (search_type == SEARCH_LDAP_AUTH)
   {
-  DEBUG(D_lookup) debug_printf("Bind succeeded: ldapauth returns OK\n");
+  DEBUG(D_lookup) debug_printf_indent("Bind succeeded: ldapauth returns OK\n");
   goto RETURN_OK;
   }
 
   goto RETURN_OK;
   }
 
@@ -641,7 +637,7 @@ ldap_set_option(lcp->ld, LDAP_OPT_REFERRALS, referrals);
 
 /* Start the search on the server. */
 
 
 /* Start the search on the server. */
 
-DEBUG(D_lookup) debug_printf("Start search\n");
+DEBUG(D_lookup) debug_printf_indent("Start search\n");
 
 msgid = ldap_search(lcp->ld, ludp->lud_dn, ludp->lud_scope, ludp->lud_filter,
   ludp->lud_attrs, 0);
 
 msgid = ldap_search(lcp->ld, ludp->lud_dn, ludp->lud_scope, ludp->lud_filter,
   ludp->lud_attrs, 0);
@@ -673,7 +669,7 @@ while ((rc = ldap_result(lcp->ld, msgid, 0, timeoutptr, &result)) ==
                     then we get two entries, one for A and one for B.
                     Here we just count the values per entry */
 
                     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_indent("LDAP result loop\n");
 
   for(e = ldap_first_entry(lcp->ld, result), valuecount = 0;
       e;
 
   for(e = ldap_first_entry(lcp->ld, result), valuecount = 0;
       e;
@@ -682,7 +678,7 @@ while ((rc = ldap_result(lcp->ld, msgid, 0, timeoutptr, &result)) ==
     uschar *new_dn;
     BOOL insert_space = FALSE;
 
     uschar *new_dn;
     BOOL insert_space = FALSE;
 
-    DEBUG(D_lookup) debug_printf("LDAP entry loop\n");
+    DEBUG(D_lookup) debug_printf_indent("LDAP entry loop\n");
 
     rescount++;   /* Count results */
 
 
     rescount++;   /* Count results */
 
@@ -731,7 +727,7 @@ while ((rc = ldap_result(lcp->ld, msgid, 0, timeoutptr, &result)) ==
     else for (uschar * attr = US ldap_first_attribute(lcp->ld, e, &ber);
               attr; attr = US ldap_next_attribute(lcp->ld, e, ber))
       {
     else for (uschar * attr = US ldap_first_attribute(lcp->ld, e, &ber);
               attr; attr = US ldap_next_attribute(lcp->ld, e, ber))
       {
-      DEBUG(D_lookup) debug_printf("LDAP attr loop\n");
+      DEBUG(D_lookup) debug_printf_indent("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 */
 
       /* In case of attrs_requested == 1 we just count the values, in all other cases
       (0, >1) we count the values per attribute */
@@ -759,7 +755,7 @@ while ((rc = ldap_result(lcp->ld, msgid, 0, timeoutptr, &result)) ==
             int len = Ustrlen(value);
             ++valuecount;
 
             int len = Ustrlen(value);
             ++valuecount;
 
-            DEBUG(D_lookup) debug_printf("LDAP value loop %s:%s\n", attr, value);
+            DEBUG(D_lookup) debug_printf_indent("LDAP value loop %s:%s\n", attr, value);
 
             /* In case we requested one attribute only but got several times
             into that attr loop, we need to append the additional values.
 
             /* In case we requested one attribute only but got several times
             into that attr loop, we need to append the additional values.
@@ -838,7 +834,7 @@ an empty string. */
 
 if (!data) data = string_get(1);
 (void) string_from_gstring(data);
 
 if (!data) data = string_get(1);
 (void) string_from_gstring(data);
-gstring_reset_unused(data);
+gstring_release_unused(data);
 
 /* Copy the last dn into eldap_dn */
 
 
 /* Copy the last dn into eldap_dn */
 
@@ -852,7 +848,7 @@ if (dn)
 #endif
   }
 
 #endif
   }
 
-DEBUG(D_lookup) debug_printf("search ended by ldap_result yielding %d\n",rc);
+DEBUG(D_lookup) debug_printf_indent("search ended by ldap_result yielding %d\n",rc);
 
 if (rc == 0)
   {
 
 if (rc == 0)
   {
@@ -874,7 +870,7 @@ methods of handling error codes and generating error messages. */
 if (rc == -1 || !result)
   {
   int err;
 if (rc == -1 || !result)
   {
   int err;
-  DEBUG(D_lookup) debug_printf("ldap_result failed\n");
+  DEBUG(D_lookup) debug_printf_indent("ldap_result failed\n");
 
 #if defined LDAP_LIB_SOLARIS || defined LDAP_LIB_OPENLDAP2
     ldap_get_option(lcp->ld, LDAP_OPT_ERROR_NUMBER, &err);
 
 #if defined LDAP_LIB_SOLARIS || defined LDAP_LIB_OPENLDAP2
     ldap_get_option(lcp->ld, LDAP_OPT_ERROR_NUMBER, &err);
@@ -917,7 +913,7 @@ We need to parse the message to find out exactly what's happened. */
   ldap_rc = rc;
   ldap_parse_rc = ldap_parse_result(lcp->ld, result, &rc, CSS &matched,
     CSS &error2, NULL, NULL, 0);
   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);
+  DEBUG(D_lookup) debug_printf_indent("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
   if (ldap_parse_rc < 0 &&
       (ldap_parse_rc != LDAP_NO_RESULTS_RETURNED
       #ifdef LDAP_RES_SEARCH_REFERENCE
@@ -959,7 +955,7 @@ We need to parse the message to find out exactly what's happened. */
       the lookup, so return DEFER (which is the default in error_yield).
 */
 
       the lookup, so return DEFER (which is the default in error_yield).
 */
 
-DEBUG(D_lookup) debug_printf("ldap_parse_result yielded %d: %s\n",
+DEBUG(D_lookup) debug_printf_indent("ldap_parse_result yielded %d: %s\n",
   rc, ldap_err2string(rc));
 
 if (rc != LDAP_SUCCESS && rc != LDAP_SIZELIMIT_EXCEEDED
   rc, ldap_err2string(rc));
 
 if (rc != LDAP_SUCCESS && rc != LDAP_SIZELIMIT_EXCEEDED
@@ -985,7 +981,7 @@ if (rc != LDAP_SUCCESS && rc != LDAP_SIZELIMIT_EXCEEDED
 #endif
 
     {
 #endif
 
     {
-    DEBUG(D_lookup) debug_printf("lookup failure forced\n");
+    DEBUG(D_lookup) debug_printf_indent("lookup failure forced\n");
     error_yield = FAIL;
     }
   goto RETURN_ERROR;
     error_yield = FAIL;
     }
   goto RETURN_ERROR;
@@ -1004,7 +1000,7 @@ if (search_type != SEARCH_LDAP_MULTIPLE && rescount > 1)
 
 if (rescount < 1)
   {
 
 if (rescount < 1)
   {
-  *errmsg = string_sprintf("LDAP search: no results");
+  *errmsg = US"LDAP search: no results";
   error_yield = FAIL;
   goto RETURN_ERROR_BREAK;
   }
   error_yield = FAIL;
   goto RETURN_ERROR_BREAK;
   }
@@ -1021,7 +1017,7 @@ if (!attribute_found)
 
 /* Otherwise, it's all worked */
 
 
 /* Otherwise, it's all worked */
 
-DEBUG(D_lookup) debug_printf("LDAP search: returning: %s\n", data->s);
+DEBUG(D_lookup) debug_printf_indent("LDAP search: returning: %s\n", data->s);
 *res = data->s;
 
 RETURN_OK:
 *res = data->s;
 
 RETURN_OK:
@@ -1035,7 +1031,7 @@ RETURN_ERROR_BREAK:
 *defer_break = TRUE;
 
 RETURN_ERROR:
 *defer_break = TRUE;
 
 RETURN_ERROR:
-DEBUG(D_lookup) debug_printf("%s\n", *errmsg);
+DEBUG(D_lookup) debug_printf_indent("%s\n", *errmsg);
 
 RETURN_ERROR_NOMSG:
 if (result) ldap_msgfree(result);
 
 RETURN_ERROR_NOMSG:
 if (result) ldap_msgfree(result);
@@ -1096,9 +1092,7 @@ const uschar *p;
 uschar *user = NULL;
 uschar *password = NULL;
 uschar *local_servers = NULL;
 uschar *user = NULL;
 uschar *password = NULL;
 uschar *local_servers = NULL;
-uschar *server;
 const uschar *list;
 const uschar *list;
-uschar buffer[512];
 
 while (isspace(*url)) url++;
 
 
 while (isspace(*url)) url++;
 
@@ -1110,7 +1104,7 @@ NAME has the value "ldap". */
 while (strncmpic(url, US"ldap", 4) != 0)
   {
   const uschar *name = url;
 while (strncmpic(url, US"ldap", 4) != 0)
   {
   const uschar *name = url;
-  while (*url != 0 && *url != '=') url++;
+  while (*url && *url != '=') url++;
   if (*url == '=')
     {
     int namelen;
   if (*url == '=')
     {
     int namelen;
@@ -1144,7 +1138,7 @@ while (strncmpic(url, US"ldap", 4) != 0)
         {
         *errmsg = string_sprintf("LDAP_OP_DEREF not defined in this LDAP "
           "library - cannot use \"dereference\"");
         {
         *errmsg = string_sprintf("LDAP_OP_DEREF not defined in this LDAP "
           "library - cannot use \"dereference\"");
-        DEBUG(D_lookup) debug_printf("%s\n", *errmsg);
+        DEBUG(D_lookup) debug_printf_indent("%s\n", *errmsg);
         return DEFER;
         }
       #endif
         return DEFER;
         }
       #endif
@@ -1156,9 +1150,8 @@ while (strncmpic(url, US"ldap", 4) != 0)
         else if (strcmpic(value, US"nofollow") == 0) referrals = LDAP_OPT_OFF;
         else
           {
         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);
+          *errmsg = US"LDAP option REFERRALS is not \"follow\" or \"nofollow\"";
+          DEBUG(D_lookup) debug_printf_indent("%s\n", *errmsg);
           return DEFER;
           }
         }
           return DEFER;
           }
         }
@@ -1167,7 +1160,7 @@ while (strncmpic(url, US"ldap", 4) != 0)
         {
         *errmsg = string_sprintf("LDAP_OP_REFERRALS not defined in this LDAP "
           "library - cannot use \"referrals\"");
         {
         *errmsg = string_sprintf("LDAP_OP_REFERRALS not defined in this LDAP "
           "library - cannot use \"referrals\"");
-        DEBUG(D_lookup) debug_printf("%s\n", *errmsg);
+        DEBUG(D_lookup) debug_printf_indent("%s\n", *errmsg);
         return DEFER;
         }
       #endif
         return DEFER;
         }
       #endif
@@ -1177,7 +1170,7 @@ while (strncmpic(url, US"ldap", 4) != 0)
         *errmsg =
           string_sprintf("unknown parameter \"%.*s\" precedes LDAP URL",
             namelen, name);
         *errmsg =
           string_sprintf("unknown parameter \"%.*s\" precedes LDAP URL",
             namelen, name);
-        DEBUG(D_lookup) debug_printf("LDAP query error: %s\n", *errmsg);
+        DEBUG(D_lookup) debug_printf_indent("LDAP query error: %s\n", *errmsg);
         return DEFER;
         }
       while (isspace(*url)) url++;
         return DEFER;
         }
       while (isspace(*url)) url++;
@@ -1185,7 +1178,7 @@ while (strncmpic(url, US"ldap", 4) != 0)
       }
     }
   *errmsg = US"malformed parameter setting precedes LDAP URL";
       }
     }
   *errmsg = US"malformed parameter setting precedes LDAP URL";
-  DEBUG(D_lookup) debug_printf("LDAP query error: %s\n", *errmsg);
+  DEBUG(D_lookup) debug_printf_indent("LDAP query error: %s\n", *errmsg);
   return DEFER;
   }
 
   return DEFER;
   }
 
@@ -1195,7 +1188,7 @@ result of ${quote_ldap_dn:...} quoting, which does apply URL quoting, because
 that is needed when the DN is used as a base DN in a query. Sigh. This is all
 far too complicated. */
 
 that is needed when the DN is used as a base DN in a query. Sigh. This is all
 far too complicated. */
 
-if (user != NULL)
+if (user)
   {
   uschar *t = user;
   for (uschar * s = user; *s != 0; s++)
   {
   uschar *t = user;
   for (uschar * s = user; *s != 0; s++)
@@ -1216,9 +1209,9 @@ if (user != NULL)
   }
 
 DEBUG(D_lookup)
   }
 
 DEBUG(D_lookup)
-  debug_printf("LDAP parameters: user=%s pass=%s size=%d time=%d connect=%d "
+  debug_printf_indent("LDAP parameters: user=%s pass=%s size=%d time=%d connect=%d "
     "dereference=%d referrals=%s\n", user, password, sizelimit, timelimit,
     "dereference=%d referrals=%s\n", user, password, sizelimit, timelimit,
-    tcplimit, dereference, (referrals == LDAP_OPT_ON)? "on" : "off");
+    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
@@ -1226,14 +1219,14 @@ password are considered anonymous, and will succeed on most installations. */
 
 if (search_type == SEARCH_LDAP_AUTH)
   {
 
 if (search_type == SEARCH_LDAP_AUTH)
   {
-  if (user == NULL || password == NULL)
+  if (!user || !password)
     {
     *errmsg = US"ldapauth lookups must specify the username and password";
     return DEFER;
     }
     {
     *errmsg = US"ldapauth lookups must specify the username and password";
     return DEFER;
     }
-  if (password[0] == 0)
+  if (!*password)
     {
     {
-    DEBUG(D_lookup) debug_printf("Empty password: ldapauth returns FAIL\n");
+    DEBUG(D_lookup) debug_printf_indent("Empty password: ldapauth returns FAIL\n");
     return FAIL;
     }
   }
     return FAIL;
     }
   }
@@ -1246,28 +1239,26 @@ if (Ustrncmp(p, "://", 3) != 0)
   {
   *errmsg = string_sprintf("LDAP URL does not start with \"ldap://\", "
     "\"ldaps://\", or \"ldapi://\" (it starts with \"%.16s...\")", url);
   {
   *errmsg = string_sprintf("LDAP URL does not start with \"ldap://\", "
     "\"ldaps://\", or \"ldapi://\" (it starts with \"%.16s...\")", url);
-  DEBUG(D_lookup) debug_printf("LDAP query error: %s\n", *errmsg);
+  DEBUG(D_lookup) debug_printf_indent("LDAP query error: %s\n", *errmsg);
   return DEFER;
   }
 
 /* No default servers, or URL contains a server name: just one attempt */
 
   return DEFER;
   }
 
 /* No default servers, or URL contains a server name: just one attempt */
 
-if ((eldap_default_servers == NULL && local_servers == NULL) || p[3] != '/')
-  {
+if (!eldap_default_servers && !local_servers  || 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. 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)
+/* Loop through the servers until OK or FAIL. Use local_servers list
+if defined in the lookup, otherwise use the global default list */
+
+list = local_servers ? local_servers : eldap_default_servers;
+for (uschar * server; server = string_nextinlist(&list, &sep, NULL, 0); )
   {
   {
-  int rc;
-  int port = 0;
+  int rc, port = 0;
   uschar *colon = Ustrchr(server, ':');
   uschar *colon = Ustrchr(server, ':');
-  if (colon != NULL)
+  if (colon)
     {
     *colon = 0;
     port = Uatoi(colon+1);
     {
     *colon = 0;
     port = Uatoi(colon+1);
@@ -1292,38 +1283,33 @@ 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, const uschar *ldap_url, int length,
-  uschar **result, uschar **errmsg, uint *do_cache)
+eldap_find(void * handle, const uschar * filename, const uschar * ldap_url,
+  int length, uschar ** result, uschar ** errmsg, uint * do_cache,
+  const uschar * opts)
 {
 {
-/* Keep picky compilers happy */
-do_cache = do_cache;
 return(control_ldap_search(ldap_url, SEARCH_LDAP_SINGLE, result, errmsg));
 }
 
 static int
 return(control_ldap_search(ldap_url, SEARCH_LDAP_SINGLE, result, errmsg));
 }
 
 static int
-eldapm_find(void *handle, uschar *filename, const uschar *ldap_url, int length,
-  uschar **result, uschar **errmsg, uint *do_cache)
+eldapm_find(void * handle, const uschar * filename, const uschar * ldap_url,
+  int length, uschar ** result, uschar ** errmsg, uint * do_cache,
+  const uschar * opts)
 {
 {
-/* Keep picky compilers happy */
-do_cache = do_cache;
 return(control_ldap_search(ldap_url, SEARCH_LDAP_MULTIPLE, result, errmsg));
 }
 
 static int
 return(control_ldap_search(ldap_url, SEARCH_LDAP_MULTIPLE, result, errmsg));
 }
 
 static int
-eldapdn_find(void *handle, uschar *filename, const uschar *ldap_url, int length,
-  uschar **result, uschar **errmsg, uint *do_cache)
+eldapdn_find(void * handle, const uschar * filename, const uschar * ldap_url,
+  int length, uschar ** result, uschar ** errmsg, uint * do_cache,
+  const uschar * opts)
 {
 {
-/* Keep picky compilers happy */
-do_cache = do_cache;
 return(control_ldap_search(ldap_url, SEARCH_LDAP_DN, result, errmsg));
 }
 
 int
 return(control_ldap_search(ldap_url, SEARCH_LDAP_DN, result, errmsg));
 }
 
 int
-eldapauth_find(void *handle, uschar *filename, const uschar *ldap_url, int length,
-  uschar **result, uschar **errmsg, uint *do_cache)
+eldapauth_find(void * handle, const uschar * filename, const uschar * ldap_url,
+  int length, uschar ** result, uschar ** errmsg, uint * do_cache)
 {
 {
-/* Keep picky compilers happy */
-do_cache = do_cache;
 return(control_ldap_search(ldap_url, SEARCH_LDAP_AUTH, result, errmsg));
 }
 
 return(control_ldap_search(ldap_url, SEARCH_LDAP_AUTH, result, errmsg));
 }
 
@@ -1336,7 +1322,7 @@ return(control_ldap_search(ldap_url, SEARCH_LDAP_AUTH, result, errmsg));
 /* See local README for interface description. */
 
 static void *
 /* See local README for interface description. */
 
 static void *
-eldap_open(uschar *filename, uschar **errmsg)
+eldap_open(const uschar * filename, uschar ** errmsg)
 {
 return (void *)(1);    /* Just return something non-null */
 }
 {
 return (void *)(1);    /* Just return something non-null */
 }
@@ -1353,16 +1339,13 @@ Make sure that eldap_dn does not refer to reclaimed or worse, freed store */
 static void
 eldap_tidy(void)
 {
 static void
 eldap_tidy(void)
 {
-LDAP_CONNECTION *lcp = NULL;
 eldap_dn = NULL;
 
 eldap_dn = NULL;
 
-while ((lcp = ldap_connections) != NULL)
+for (LDAP_CONNECTION *lcp; lcp = ldap_connections; ldap_connections = lcp->next)
   {
   {
-  DEBUG(D_lookup) debug_printf("unbind LDAP connection to %s:%d\n", lcp->host,
-    lcp->port);
-  if(lcp->bound == TRUE)
-    ldap_unbind(lcp->ld);
-  ldap_connections = lcp->next;
+  DEBUG(D_lookup) debug_printf_indent("unbind LDAP connection to %s:%d\n",
+    lcp->host, lcp->port);
+  if(lcp->bound) ldap_unbind(lcp->ld);
   }
 }
 
   }
 }
 
@@ -1423,6 +1406,7 @@ Arguments:
   s          the string to be quoted
   opt        additional option text or NULL if none
              only "dn" is recognized
   s          the string to be quoted
   opt        additional option text or NULL if none
              only "dn" is recognized
+  idx       lookup type index
 
 Returns:     the processed string or NULL for a bad option
 */
 
 Returns:     the processed string or NULL for a bad option
 */
@@ -1448,18 +1432,15 @@ quote_ldap_dn, respectively. */
 
 
 static uschar *
 
 
 static uschar *
-eldap_quote(uschar *s, uschar *opt)
+eldap_quote(uschar * s, uschar * opt, unsigned idx)
 {
 {
-register int c;
-int count = 0;
-int len = 0;
+int c, count = 0, len = 0;
 BOOL dn = FALSE;
 BOOL dn = FALSE;
-uschar *t = s;
-uschar *quoted;
+uschar * t = s, * quoted;
 
 /* Test for a DN quotation. */
 
 
 /* Test for a DN quotation. */
 
-if (opt != NULL)
+if (opt)
   {
   if (Ustrcmp(opt, "dn") != 0) return NULL;    /* No others recognized */
   dn = TRUE;
   {
   if (Ustrcmp(opt, "dn") != 0) return NULL;    /* No others recognized */
   dn = TRUE;
@@ -1472,24 +1453,25 @@ where, for example, < turns into %5C%3C. For simplicity, we just add 5 for each
 possibly escaped character. The really fast way would be just to test for
 non-alphanumerics, but it is probably better to spot a few others that are
 never escaped, because if there are no specials at all, we can avoid copying
 possibly escaped character. The really fast way would be just to test for
 non-alphanumerics, but it is probably better to spot a few others that are
 never escaped, because if there are no specials at all, we can avoid copying
-the string. */
+the string.
+XXX No longer true; we always copy, to support quoted-enforcement */
 
 
-while ((c = *t++) != 0)
+while ((c = *t++))
   {
   len++;
   if (!isalnum(c) && Ustrchr(ALWAYS_LITERAL, c) == NULL) count += 5;
   }
   {
   len++;
   if (!isalnum(c) && Ustrchr(ALWAYS_LITERAL, c) == NULL) count += 5;
   }
-if (count == 0) return s;
+/*if (count == 0) return s;*/
 
 /* Get sufficient store to hold the quoted string */
 
 
 /* Get sufficient store to hold the quoted string */
 
-t = quoted = store_get(len + count + 1);
+t = quoted = store_get_quoted(len + count + 1, s, idx);
 
 /* Handle plain quote_ldap */
 
 if (!dn)
   {
 
 /* Handle plain quote_ldap */
 
 if (!dn)
   {
-  while ((c = *s++) != 0)
+  while ((c = *s++))
     {
     if (!isalnum(c))
       {
     {
     if (!isalnum(c))
       {
@@ -1514,7 +1496,7 @@ if (!dn)
 
 else
   {
 
 else
   {
-  uschar *ss = s + len;
+  uschar * ss = s + len;
 
   /* Find the last char before any trailing spaces */
 
 
   /* Find the last char before any trailing spaces */
 
@@ -1538,7 +1520,7 @@ else
       {
       if (Ustrchr(LDAP_DN_QUOTE, c) != NULL)
         {
       {
       if (Ustrchr(LDAP_DN_QUOTE, c) != NULL)
         {
-        Ustrncpy(t, "%5C", 3);               /* insert \ where needed */
+        Ustrncpy(t, US"%5C", 3);               /* insert \ where needed */
         t += 3;                              /* fall through to check URL */
         }
       if (Ustrchr(URL_NONQUOTE, c) == NULL)  /* e.g. ] => %5D */
         t += 3;                              /* fall through to check URL */
         }
       if (Ustrchr(URL_NONQUOTE, c) == NULL)  /* e.g. ] => %5D */
@@ -1555,7 +1537,7 @@ else
 
   while (*ss++ != 0)
     {
 
   while (*ss++ != 0)
     {
-    Ustrncpy(t, "%5C%20", 6);
+    Ustrncpy(t, US"%5C%20", 6);
     t += 6;
     }
   }
     t += 6;
     }
   }
@@ -1576,49 +1558,50 @@ return quoted;
 
 #include "../version.h"
 
 
 #include "../version.h"
 
-void
-ldap_version_report(FILE *f)
+gstring *
+ldap_version_report(gstring * g)
 {
 #ifdef DYNLOOKUP
 {
 #ifdef DYNLOOKUP
-fprintf(f, "Library version: LDAP: Exim version %s\n", EXIM_VERSION_STR);
+g = string_fmt_append(g, "Library version: LDAP: Exim version %s\n", EXIM_VERSION_STR);
 #endif
 #endif
+return g;
 }
 
 
 static lookup_info ldap_lookup_info = {
 }
 
 
 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 */
+  .name = US"ldap",                    /* lookup name */
+  .type = lookup_querystyle,           /* query-style lookup */
+  .open = eldap_open,                  /* open function */
+  .check = NULL,                       /* check function */
+  .find = eldap_find,                  /* find function */
+  .close = NULL,                       /* no close function */
+  .tidy = eldap_tidy,                  /* tidy function */
+  .quote = eldap_quote,                        /* quoting function */
+  .version_report = ldap_version_report            /* version reporting */
 };
 
 static lookup_info ldapdn_lookup_info = {
 };
 
 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) */
+  .name = US"ldapdn",                  /* lookup name */
+  .type = lookup_querystyle,           /* query-style lookup */
+  .open = eldap_open,                  /* sic */    /* open function */
+  .check = NULL,                       /* check function */
+  .find = eldapdn_find,                        /* find function */
+  .close = NULL,                       /* no close function */
+  .tidy = eldap_tidy,                  /* sic */    /* tidy function */
+  .quote = eldap_quote,                        /* sic */    /* quoting function */
+  .version_report = NULL                           /* no version reporting (redundant) */
 };
 
 static lookup_info ldapm_lookup_info = {
 };
 
 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) */
+  .name = US"ldapm",                   /* lookup name */
+  .type = lookup_querystyle,           /* query-style lookup */
+  .open = eldap_open,                  /* sic */    /* open function */
+  .check = NULL,                       /* check function */
+  .find = eldapm_find,                 /* find function */
+  .close = NULL,                       /* no close function */
+  .tidy = eldap_tidy,                  /* sic */    /* tidy function */
+  .quote = eldap_quote,                        /* sic */    /* quoting function */
+  .version_report = NULL                           /* no version reporting (redundant) */
 };
 
 #ifdef DYNLOOKUP
 };
 
 #ifdef DYNLOOKUP