SPDX: license tags (mostly by guesswork)
[exim.git] / src / src / lookups / ldap.c
index 97ee188d273999da1585d99c7ff7fb9b96ababee..17c431e5c7e7d43270dff8cd81d2eeb99435268e 100644 (file)
@@ -2,8 +2,10 @@
 *     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. */
+/* 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
@@ -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. */
 
-  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)
     {
-    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
@@ -326,22 +322,22 @@ if (!lcp)
 
   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 */
 
-  DEBUG(D_lookup) debug_printf_indent("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",
-      rc, init_url);
+      rc, g->s);
     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 ---------------------- */
@@ -501,8 +497,8 @@ if (!lcp)
 
   /* 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;
@@ -1004,7 +1000,7 @@ if (search_type != SEARCH_LDAP_MULTIPLE && 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;
   }
@@ -1096,9 +1092,7 @@ const uschar *p;
 uschar *user = NULL;
 uschar *password = NULL;
 uschar *local_servers = NULL;
-uschar *server;
 const uschar *list;
-uschar buffer[512];
 
 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 (*url != 0 && *url != '=') url++;
+  while (*url && *url != '=') url++;
   if (*url == '=')
     {
     int namelen;
@@ -1156,6 +1150,7 @@ while (strncmpic(url, US"ldap", 4) != 0)
         else if (strcmpic(value, US"nofollow") == 0) referrals = LDAP_OPT_OFF;
         else
           {
+          *errmsg = US"LDAP option REFERRALS is not \"follow\" or \"nofollow\"";
           DEBUG(D_lookup) debug_printf_indent("%s\n", *errmsg);
           return DEFER;
           }
@@ -1193,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. */
 
-if (user != NULL)
+if (user)
   {
   uschar *t = user;
   for (uschar * s = user; *s != 0; s++)
@@ -1216,7 +1211,7 @@ if (user != NULL)
 DEBUG(D_lookup)
   debug_printf_indent("LDAP parameters: user=%s pass=%s size=%d time=%d connect=%d "
     "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
@@ -1224,12 +1219,12 @@ password are considered anonymous, and will succeed on most installations. */
 
 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;
     }
-  if (password[0] == 0)
+  if (!*password)
     {
     DEBUG(D_lookup) debug_printf_indent("Empty password: ldapauth returns FAIL\n");
     return FAIL;
@@ -1250,22 +1245,20 @@ if (Ustrncmp(p, "://", 3) != 0)
 
 /* 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);
-  }
 
-/* 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, ':');
-  if (colon != NULL)
+  if (colon)
     {
     *colon = 0;
     port = Uatoi(colon+1);
@@ -1290,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
-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
-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
-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
-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));
 }
 
@@ -1334,7 +1322,7 @@ return(control_ldap_search(ldap_url, SEARCH_LDAP_AUTH, result, errmsg));
 /* 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 */
 }
@@ -1351,16 +1339,13 @@ Make sure that eldap_dn does not refer to reclaimed or worse, freed store */
 static void
 eldap_tidy(void)
 {
-LDAP_CONNECTION *lcp = 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_indent("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);
   }
 }
 
@@ -1421,6 +1406,7 @@ Arguments:
   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
 */
@@ -1446,18 +1432,15 @@ quote_ldap_dn, respectively. */
 
 
 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;
-uschar *t = s;
-uschar *quoted;
+uschar * t = s, * quoted;
 
 /* Test for a DN quotation. */
 
-if (opt != NULL)
+if (opt)
   {
   if (Ustrcmp(opt, "dn") != 0) return NULL;    /* No others recognized */
   dn = TRUE;
@@ -1470,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
-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;
   }
-if (count == 0) return s;
+/*if (count == 0) return s;*/
 
 /* 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)
   {
-  while ((c = *s++) != 0)
+  while ((c = *s++))
     {
     if (!isalnum(c))
       {
@@ -1512,7 +1496,7 @@ if (!dn)
 
 else
   {
-  uschar *ss = s + len;
+  uschar * ss = s + len;
 
   /* Find the last char before any trailing spaces */
 
@@ -1536,7 +1520,7 @@ else
       {
       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 */
@@ -1553,7 +1537,7 @@ else
 
   while (*ss++ != 0)
     {
-    Ustrncpy(t, "%5C%20", 6);
+    Ustrncpy(t, US"%5C%20", 6);
     t += 6;
     }
   }
@@ -1574,49 +1558,50 @@ return quoted;
 
 #include "../version.h"
 
-void
-ldap_version_report(FILE *f)
+gstring *
+ldap_version_report(gstring * g)
 {
 #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
+return g;
 }
 
 
 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 = {
-  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 = {
-  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