Logging: ensure that an error for a mistyped IPv6 address in a search
[exim.git] / src / src / verify.c
index 0ca09613078b9b1a6d0705e03ae9011bc56c11e2..194e9a76a8896e27c1daeceb60fcdeed32fa0b1c 100644 (file)
@@ -2,8 +2,8 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) The Exim Maintainers 2020 - 2022 */
-/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* 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 */
 
@@ -105,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)
 {
@@ -278,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.
@@ -502,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;
@@ -677,7 +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->conn_args.sock = -1;
+    sx->cctx.sock = sx->conn_args.sock = -1;
     sx->verify = TRUE;
 
 tls_retry_connection:
@@ -709,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;
@@ -747,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
@@ -918,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);
@@ -1056,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;
@@ -1126,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);
@@ -1260,7 +1286,7 @@ return FALSE;
 
 
 static BOOL
-_cutthrough_puts(uschar * cp, int n)
+_cutthrough_puts(const uschar * cp, int n)
 {
 while(n--)
  {
@@ -1275,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;
@@ -1327,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)
 {
@@ -1341,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)
   {
@@ -1375,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())
@@ -1665,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 */
@@ -1719,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)
     {
@@ -1887,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 */
 
@@ -2423,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;
 
@@ -2501,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;
@@ -2940,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
@@ -2951,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  || (*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
@@ -2974,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-". */