Logging: ensure that an error for a mistyped IPv6 address in a search
[exim.git] / src / src / verify.c
index 6e07566a7edc78fa0611b02c1cf422cffbc983f7..194e9a76a8896e27c1daeceb60fcdeed32fa0b1c 100644 (file)
@@ -2,9 +2,10 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2018 */
-/* Copyright (c) The Exim Maintainers 2020 */
+/* Copyright (c) The Exim Maintainers 2020 - 2023 */
+/* Copyright (c) University of Cambridge 1995 - 2023 */
 /* See the file NOTICE for conditions of use and distribution. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 
 /* Functions concerned with verifying things. The original code for callout
 caching was contributed by Kevin Fleming (but I hacked it around a bit). */
@@ -78,7 +79,7 @@ if (type[0] == 'd' && cache_record->result != ccache_reject)
   {
   if (length == sizeof(dbdata_callout_cache_obs))
     {
-    dbdata_callout_cache *new = store_get(sizeof(dbdata_callout_cache), FALSE);
+    dbdata_callout_cache * new = store_get(sizeof(dbdata_callout_cache), GET_UNTAINTED);
     memcpy(new, cache_record, length);
     new->postmaster_stamp = new->random_stamp = new->time_stamp;
     cache_record = new;
@@ -104,8 +105,8 @@ Return: TRUE if result found
 */
 
 static BOOL
-cached_callout_lookup(address_item * addr, uschar * address_key,
-  uschar * from_address, int * opt_ptr, uschar ** pm_ptr,
+cached_callout_lookup(address_item * addr, const uschar * address_key,
+  const uschar * from_address, int * opt_ptr, uschar ** pm_ptr,
   int * yield, uschar ** failure_ptr,
   dbdata_callout_cache * new_domain_record, int * old_domain_res)
 {
@@ -277,10 +278,10 @@ return FALSE;
 */
 static void
 cache_callout_write(dbdata_callout_cache * dom_rec, const uschar * domain,
-  int done, dbdata_callout_cache_address * addr_rec, uschar * address_key)
+  int done, dbdata_callout_cache_address * addr_rec, const uschar * address_key)
 {
 open_db dbblock;
-open_db *dbm_file = NULL;
+open_db * dbm_file = NULL;
 
 /* If we get here with done == TRUE, a successful callout happened, and yield
 will be set OK or FAIL according to the response to the RCPT command.
@@ -400,7 +401,7 @@ if (addr->transport == cutthrough.addr.transport)
 
        if (done)
          {
-         address_item * na = store_get(sizeof(address_item), FALSE);
+         address_item * na = store_get(sizeof(address_item), GET_UNTAINTED);
          *na = cutthrough.addr;
          cutthrough.addr = *addr;
          cutthrough.addr.host_used = &cutthrough.host;
@@ -445,6 +446,21 @@ return done;
 }
 
 
+
+
+/* A rcpt callout, or cached record of one, verified the address.
+Set $domain_data and $local_part_data to detainted versions.
+*/
+static void
+callout_verified_rcpt(const address_item * addr)
+{
+address_item a = {.address = addr->address};
+if (deliver_split_address(&a) != OK) return;
+deliver_localpart_data = string_copy_taint(a.local_part, GET_UNTAINTED);
+deliver_domain_data =    string_copy_taint(a.domain,     GET_UNTAINTED);
+}
+
+
 /*************************************************
 *      Do callout verification for an address    *
 *************************************************/
@@ -486,11 +502,11 @@ do_callout(address_item *addr, host_item *host_list, transport_feedback *tf,
 int yield = OK;
 int old_domain_cache_result = ccache_accept;
 BOOL done = FALSE;
-uschar *address_key;
-uschar *from_address;
-uschar *random_local_part = NULL;
-const uschar *save_deliver_domain = deliver_domain;
-uschar **failure_ptr = options & vopt_is_recipient
+const uschar * address_key;
+const uschar * from_address;
+uschar * random_local_part = NULL;
+const uschar * save_deliver_domain = deliver_domain;
+uschar ** failure_ptr = options & vopt_is_recipient
   ? &recipient_verify_failure : &sender_verify_failure;
 dbdata_callout_cache new_domain_record;
 dbdata_callout_cache_address new_address_record;
@@ -651,7 +667,7 @@ coding means skipping this whole loop and doing the append separately.  */
       log_write(0, LOG_MAIN|LOG_PANIC, "<%s>: %s", addr->address,
         addr->message);
 
-    if (!sx) sx = store_get(sizeof(*sx), TRUE);        /* tainted buffers */
+    if (!sx) sx = store_get(sizeof(*sx), GET_TAINTED); /* tainted buffers */
     memset(sx, 0, sizeof(*sx));
 
     sx->addrlist = sx->first_addr = addr;
@@ -661,6 +677,7 @@ coding means skipping this whole loop and doing the append separately.  */
     sx->conn_args.interface = interface;
     sx->helo_data = tf->helo_data;
     sx->conn_args.tblock = addr->transport;
+    sx->cctx.sock = sx->conn_args.sock = -1;
     sx->verify = TRUE;
 
 tls_retry_connection:
@@ -692,6 +709,30 @@ tls_retry_connection:
     if (yield != OK)
       {
       errno = addr->basic_errno;
+
+      /* For certain errors we want specifically to log the transport name,
+      for ease of fixing config errors. Slightly ugly doing it here, but we want
+      to not leak that also in the SMTP response. */
+      switch (errno)
+       {
+       case EPROTOTYPE:
+       case ENOPROTOOPT:
+       case EPROTONOSUPPORT:
+       case ESOCKTNOSUPPORT:
+       case EOPNOTSUPP:
+       case EPFNOSUPPORT:
+       case EAFNOSUPPORT:
+       case EADDRINUSE:
+       case EADDRNOTAVAIL:
+       case ENETDOWN:
+       case ENETUNREACH:
+         log_write(0, LOG_MAIN|LOG_PANIC,
+           "%s verify %s (making calloout connection): T=%s %s",
+           options & vopt_is_recipient ? "sender" : "recipient",
+           yield == FAIL ? "fail" : "defer",
+           transport_name, strerror(errno));
+       }
+
       transport_name = NULL;
       deliver_host = deliver_host_address = NULL;
       deliver_domain = save_deliver_domain;
@@ -730,7 +771,7 @@ tls_retry_connection:
 
     if (random_local_part)
       {
-      uschar * main_address = addr->address;
+      const uschar * main_address = addr->address;
       const uschar * rcpt_domain = addr->domain;
 
 #ifdef SUPPORT_I18N
@@ -815,7 +856,7 @@ tls_retry_connection:
            sx->cctx.sock = -1;
 #ifndef DISABLE_EVENT
            (void) event_raise(addr->transport->event_action,
-                             US"tcp:close", NULL);
+                             US"tcp:close", NULL, NULL);
 #endif
            addr->address = main_address;
            addr->transport_return = PENDING_DEFER;
@@ -901,7 +942,7 @@ tls_retry_connection:
 
       if (done)
        {
-       uschar * main_address = addr->address;
+       const uschar * main_address = addr->address;
 
        /*XXX oops, affixes */
        addr->address = string_sprintf("postmaster@%.1000s", addr->domain);
@@ -1039,6 +1080,8 @@ no_conn:
         HDEBUG(D_acl|D_v) debug_printf("Cutthrough cancelled by presence of transport filter\n");
         }
 #ifndef DISABLE_DKIM
+      /* DKIM signing needs to add a header after seeing the whole body, so we cannot just copy
+      body bytes to the outbound as they are received, which is the intent of cutthrough. */
       if (ob->dkim.dkim_domain)
         {
         cutthrough.delivery= FALSE;
@@ -1096,7 +1139,7 @@ no_conn:
       for (address_item * caddr = &cutthrough.addr, * parent = addr->parent;
           parent;
           caddr = caddr->parent, parent = parent->parent)
-        *(caddr->parent = store_get(sizeof(address_item), FALSE)) = *parent;
+        *(caddr->parent = store_get(sizeof(address_item), GET_UNTAINTED)) = *parent;
 
       ctctx.outblock.buffer = ctbuffer;
       ctctx.outblock.buffersize = sizeof(ctbuffer);
@@ -1109,7 +1152,7 @@ no_conn:
       /* Ensure no cutthrough on multiple verifies that were incompatible */
       if (options & vopt_callout_recipsender)
         cancel_cutthrough_connection(TRUE, US"not usable for cutthrough");
-      if (sx->send_quit)
+      if (sx->send_quit && sx->cctx.sock >= 0)
        if (smtp_write_command(sx, SCMD_FLUSH, "QUIT\r\n") != -1)
          /* Wait a short time for response, and discard it */
          smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), '2', 1);
@@ -1126,8 +1169,9 @@ no_conn:
        HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP(close)>>\n");
        (void)close(sx->cctx.sock);
        sx->cctx.sock = -1;
+       smtp_debug_cmd_report();
 #ifndef DISABLE_EVENT
-       (void) event_raise(addr->transport->event_action, US"tcp:close", NULL);
+       (void) event_raise(addr->transport->event_action, US"tcp:close", NULL, NULL);
 #endif
        }
       }
@@ -1242,7 +1286,7 @@ return FALSE;
 
 
 static BOOL
-_cutthrough_puts(uschar * cp, int n)
+_cutthrough_puts(const uschar * cp, int n)
 {
 while(n--)
  {
@@ -1257,7 +1301,7 @@ return TRUE;
 
 /* Buffered output of counted data block.   Return boolean success */
 static BOOL
-cutthrough_puts(uschar * cp, int n)
+cutthrough_puts(const uschar * cp, int n)
 {
 if (cutthrough.cctx.sock < 0) return TRUE;
 if (_cutthrough_puts(cp, n))  return TRUE;
@@ -1309,7 +1353,13 @@ cutthrough_data_puts(US"\r\n", 2);
 }
 
 
-/* Get and check response from cutthrough target */
+/* Get and check response from cutthrough target.
+Used for
+- nonfirst RCPT
+- predata
+- data finaldot
+- cutthrough conn close
+*/
 static uschar
 cutthrough_response(client_conn_ctx * cctx, char expect, uschar ** copy, int timeout)
 {
@@ -1323,7 +1373,7 @@ sx.inblock.ptr = inbuffer;
 sx.inblock.ptrend = inbuffer;
 sx.inblock.cctx = cctx;
 if(!smtp_read_response(&sx, responsebuffer, sizeof(responsebuffer), expect, timeout))
-  cancel_cutthrough_connection(TRUE, US"target timeout on read");
+  cancel_cutthrough_connection(TRUE, US"unexpected response to smtp command");
 
 if(copy)
   {
@@ -1346,7 +1396,7 @@ cutthrough_predata(void)
 if(cutthrough.cctx.sock < 0 || cutthrough.callout_hold_only)
   return FALSE;
 
-HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP>> DATA\n");
+smtp_debug_cmd(US"DATA", 0);
 cutthrough_puts(US"DATA\r\n", 6);
 cutthrough_flush_send();
 
@@ -1357,9 +1407,9 @@ return cutthrough_response(&cutthrough.cctx, '3', NULL, CUTTHROUGH_DATA_TIMEOUT)
 
 /* tctx arg only to match write_chunk() */
 static BOOL
-cutthrough_write_chunk(transport_ctx * tctx, uschar * s, int len)
+cutthrough_write_chunk(transport_ctx * tctx, const uschar * s, int len)
 {
-uschar * s2;
+const uschar * s2;
 while(s && (s2 = Ustrchr(s, '\n')))
  {
  if(!cutthrough_puts(s, s2-s) || !cutthrough_put_nl())
@@ -1414,7 +1464,7 @@ if(fd >= 0)
   */
   client_conn_ctx tmp_ctx = cutthrough.cctx;
   ctctx.outblock.ptr = ctbuffer;
-  HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP>> QUIT\n");
+  smtp_debug_cmd(US"QUIT", 0);
   _cutthrough_puts(US"QUIT\r\n", 6);   /* avoid recursion */
   _cutthrough_flush_send();
   cutthrough.cctx.sock = -1;           /* avoid recursion via read timeout */
@@ -1433,6 +1483,7 @@ if(fd >= 0)
 #endif
   HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP(close)>>\n");
   (void)close(fd);
+  smtp_debug_cmd_report();
   HDEBUG(D_acl) debug_printf_indent("----------- cutthrough shutdown (%s) ------------\n", why);
   }
 ctctx.outblock.ptr = ctbuffer;
@@ -1646,16 +1697,16 @@ int yield = OK;
 int verify_type = expn ? v_expn :
    f.address_test_mode ? v_none :
           options & vopt_is_recipient ? v_recipient : v_sender;
-address_item *addr_list;
-address_item *addr_new = NULL;
-address_item *addr_remote = NULL;
-address_item *addr_local = NULL;
-address_item *addr_succeed = NULL;
-uschar **failure_ptr = options & vopt_is_recipient
+address_item * addr_list;
+address_item * addr_new = NULL;
+address_item * addr_remote = NULL;
+address_item * addr_local = NULL;
+address_item * addr_succeed = NULL;
+uschar ** failure_ptr = options & vopt_is_recipient
   ? &recipient_verify_failure : &sender_verify_failure;
-uschar *ko_prefix, *cr;
-uschar *address = vaddr->address;
-uschar *save_sender;
+uschar * ko_prefix, * cr;
+const uschar * address = vaddr->address;
+const uschar * save_sender;
 uschar null_sender[] = { 0 };             /* Ensure writeable memory */
 
 /* Clear, just in case */
@@ -1700,9 +1751,8 @@ may have been set by domains and local part tests during an ACL. */
 
 if (global_rewrite_rules)
   {
-  uschar *old = address;
-  /* deconst ok as address was not const */
-  address = US rewrite_address(address, options & vopt_is_recipient, FALSE,
+  const uschar * old = address;
+  address = rewrite_address(address, options & vopt_is_recipient, FALSE,
     global_rewrite_rules, rewrite_existflags);
   if (address != old)
     {
@@ -1868,8 +1918,8 @@ while (addr_new)
          if (tf.hosts && (!host_list || tf.hosts_override))
            {
            uschar *s;
-           const uschar *save_deliver_domain = deliver_domain;
-           uschar *save_deliver_localpart = deliver_localpart;
+           const uschar * save_deliver_domain = deliver_domain;
+           const uschar * save_deliver_localpart = deliver_localpart;
 
            host_list = NULL;    /* Ignore the router's hosts */
 
@@ -1949,6 +1999,12 @@ while (addr_new)
 #ifndef DISABLE_TLS
          deliver_set_expansions(NULL);
 #endif
+         if (  options & vopt_is_recipient
+            && rc == OK
+                        /* set to "random", with OK, for an accepted random */
+            && !recipient_verify_failure
+            )
+           callout_verified_rcpt(addr);
           }
         }
       else if (local_verify)
@@ -2150,10 +2206,6 @@ for (addr_list = addr_local, i = 0; i < 2; addr_list = addr_remote, i++)
     addr_list = addr->next;
 
     fprintf(fp, "%s", CS addr->address);
-#ifdef EXPERIMENTAL_SRS_ALT
-    if(addr->prop.srs_sender)
-      fprintf(fp, "    [srs = %s]", addr->prop.srs_sender);
-#endif
 
     /* If the address is a duplicate, show something about it. */
 
@@ -2402,11 +2454,11 @@ verify_check_notblind(BOOL case_sensitive)
 for (int i = 0; i < recipients_count; i++)
   {
   BOOL found = FALSE;
-  uschar *address = recipients_list[i].address;
+  const uschar * address = recipients_list[i].address;
 
   for (header_line * h = header_list; !found && h; h = h->next)
     {
-    uschar *colon, *s;
+    uschar * colon, * s;
 
     if (h->type != htype_to && h->type != htype_cc) continue;
 
@@ -2480,7 +2532,7 @@ Returns:     pointer to an address item, or NULL
 */
 
 address_item *
-verify_checked_sender(uschar *sender)
+verify_checked_sender(const uschar * sender)
 {
 for (address_item * addr = sender_verified_list; addr; addr = addr->next)
   if (Ustrcmp(sender, addr->address) == 0) return addr;
@@ -2876,28 +2928,27 @@ Returns:         OK      matched
 */
 
 int
-check_host(void *arg, const uschar *ss, const uschar **valueptr, uschar **error)
+check_host(void * arg, const uschar * ss, const uschar ** valueptr, uschar ** error)
 {
-check_host_block *cb = (check_host_block *)arg;
+check_host_block * cb = (check_host_block *)arg;
 int mlen = -1;
 int maskoffset;
-BOOL iplookup = FALSE;
-BOOL isquery = FALSE;
-BOOL isiponly = cb->host_name != NULL && cb->host_name[0] == 0;
-const uschar *t;
+BOOL iplookup = FALSE, isquery = FALSE;
+BOOL isiponly = cb->host_name && !cb->host_name[0];
+const uschar * t;
 uschar * semicolon, * endname, * opts;
-uschar **aliases;
+uschar ** aliases;
 
 /* Optimize for the special case when the pattern is "*". */
 
-if (*ss == '*' && ss[1] == 0) return OK;
+if (*ss == '*' && !ss[1]) return OK;
 
 /* If the pattern is empty, it matches only in the case when there is no host -
 this can occur in ACL checking for SMTP input using the -bs option. In this
 situation, the host address is the empty string. */
 
-if (cb->host_address[0] == 0) return (*ss == 0)? OK : FAIL;
-if (*ss == 0) return FAIL;
+if (!cb->host_address[0]) return *ss ? FAIL : OK;
+if (!*ss) return FAIL;
 
 /* If the pattern is precisely "@" then match against the primary host name,
 provided that host name matching is permitted; if it's "@[]" match against the
@@ -2920,7 +2971,7 @@ if (*ss == '@')
 a (possibly masked) comparison with the current IP address. */
 
 if (string_is_ip_address(ss, &maskoffset) != 0)
-  return (host_is_in_net(cb->host_address, ss, maskoffset)? OK : FAIL);
+  return host_is_in_net(cb->host_address, ss, maskoffset) ? OK : FAIL;
 
 /* The pattern is not an IP address. A common error that people make is to omit
 one component of an IPv4 address, either by accident, or believing that, for
@@ -2931,13 +2982,25 @@ ancient specification.) To aid in debugging these cases, we give a specific
 error if the pattern contains only digits and dots or contains a slash preceded
 only by digits and dots (a slash at the start indicates a file name and of
 course slashes may be present in lookups, but not preceded only by digits and
-dots). */
+dots).  Then the equivalent for IPv6 (roughly). */
 
-for (t = ss; isdigit(*t) || *t == '.'; ) t++;
-if (*t == 0 || (*t == '/' && t != ss))
+if (Ustrchr(ss, ':'))
   {
-  *error = US"malformed IPv4 address or address mask";
-  return ERROR;
+  for (t = ss; isxdigit(*t) || *t == ':' || *t == '.'; ) t++;
+  if (!*t  ||  (*t == '/' || *t == '%') && t != ss)
+    {
+    *error = string_sprintf("malformed IPv6 address or address mask: %.*s", (int)(t - ss), ss);
+    return ERROR;
+    }
+  }
+else
+  {
+  for (t = ss; isdigit(*t) || *t == '.'; ) t++;
+  if (!*t  || (*t == '/' && t != ss))
+    {
+    *error = string_sprintf("malformed IPv4 address or address mask: %.*s", (int)(t - ss), ss);
+    return ERROR;
+    }
   }
 
 /* See if there is a semicolon in the pattern, separating a searchtype
@@ -2954,6 +3017,8 @@ if ((semicolon = Ustrchr(ss, ';')))
     endname = semicolon;
     opts = NULL;
     }
+else
+  opts = NULL;
 
 /* If we are doing an IP address only match, then all lookups must be IP
 address lookups, even if there is no "net-". */
@@ -3055,7 +3120,7 @@ digits, full stops, and hyphens (the constituents of domain names). Allow
 underscores, as they are all too commonly found. Sigh. Also, if
 allow_utf8_domains is set, allow top-bit characters. */
 
-for (t = ss; *t != 0; t++)
+for (t = ss; *t; t++)
   if (!isalnum(*t) && *t != '.' && *t != '-' && *t != '_' &&
       (!allow_utf8_domains || *t < 128)) break;
 
@@ -3063,7 +3128,7 @@ for (t = ss; *t != 0; t++)
 its IP address and match against that. Note that a multi-homed host will add
 items to the chain. */
 
-if (*t == 0)
+if (!*t)
   {
   int rc;
   host_item h;
@@ -3094,8 +3159,8 @@ outgoing hosts, the name is always given explicitly. If it is NULL, it means we
 must use sender_host_name and its aliases, looking them up if necessary. */
 
 if (cb->host_name)   /* Explicit host name given */
-  return match_check_string(cb->host_name, ss, -1, TRUE, TRUE, TRUE,
-    valueptr);
+  return match_check_string(cb->host_name, ss, -1,
+    MCS_PARTIAL | MCS_CASELESS | MCS_AT_SPECIAL | cb->flags, valueptr);
 
 /* Host name not given; in principle we need the sender host name and its
 aliases. However, for query-style lookups, we do not need the name if the
@@ -3124,7 +3189,9 @@ if ((semicolon = Ustrchr(ss, ';')))
 
 if (isquery)
   {
-  switch(match_check_string(US"", ss, -1, TRUE, TRUE, TRUE, valueptr))
+  switch(match_check_string(US"", ss, -1,
+      MCS_PARTIAL| MCS_CASELESS| MCS_AT_SPECIAL | (cb->flags & MCS_CACHEABLE),
+      valueptr))
     {
     case OK:    return OK;
     case DEFER: return DEFER;
@@ -3150,7 +3217,9 @@ if (!sender_host_name)
 
 /* Match on the sender host name, using the general matching function */
 
-switch(match_check_string(sender_host_name, ss, -1, TRUE, TRUE, TRUE, valueptr))
+switch(match_check_string(sender_host_name, ss, -1,
+      MCS_PARTIAL| MCS_CASELESS| MCS_AT_SPECIAL | (cb->flags & MCS_CACHEABLE),
+      valueptr))
   {
   case OK:    return OK;
   case DEFER: return DEFER;
@@ -3160,7 +3229,9 @@ switch(match_check_string(sender_host_name, ss, -1, TRUE, TRUE, TRUE, valueptr))
 
 aliases = sender_host_aliases;
 while (*aliases)
-  switch(match_check_string(*aliases++, ss, -1, TRUE, TRUE, TRUE, valueptr))
+  switch(match_check_string(*aliases++, ss, -1,
+      MCS_PARTIAL| MCS_CASELESS| MCS_AT_SPECIAL | (cb->flags & MCS_CACHEABLE),
+      valueptr))
     {
     case OK:    return OK;
     case DEFER: return DEFER;
@@ -3236,8 +3307,8 @@ rc = match_check_list(
        check_host,                             /* function for testing */
        &cb,                                    /* argument for function */
        MCL_HOST,                               /* type of check */
-       (host_address == sender_host_address)?
-         US"host" : host_address,              /* text for debugging */
+       host_address == sender_host_address
+         ? US"host" : host_address,           /* text for debugging */
        valueptr);                              /* where to pass back data */
 deliver_host_address = save_host_address;
 return rc;
@@ -3549,13 +3620,13 @@ else
     if (n > 4)
       save_errno = (buf[1] << 24) | (buf[2] << 16) | (buf[3] << 8) | buf[4];
     if ((recipient_verify_failure = n > 5
-       ? string_copyn_taint(buf+5, n-5, FALSE) : NULL))
+       ? string_copyn_taint(buf+5, n-5, GET_UNTAINTED) : NULL))
       {
       int m;
       s = buf + 5 + Ustrlen(recipient_verify_failure) + 1;
       m = n - (s - buf);
       acl_verify_message = *msg =
-       m > 0 ? string_copyn_taint(s, m, FALSE) : NULL;
+       m > 0 ? string_copyn_taint(s, m, GET_UNTAINTED) : NULL;
       }
 
     DEBUG(D_verify) debug_printf_indent("verify call response:"
@@ -3576,10 +3647,12 @@ else
   }
 
 close(pfd[pipe_read]);
+signal(SIGCHLD, oldsignal);
 errno = save_errno;
+return yield;
 
 fail:
-
+DEBUG(D_verify) debug_printf_indent("verify_quota_call fail in %s\n", where);
 return yield;
 }