Fix identd connections on FreeBSD under TCP Fast Open
[exim.git] / src / src / verify.c
index 794c76c4eb37a2517cdeb138fd0ba6b4ec789225..5c093bf31ca087f611454755327df700ae186058 100644 (file)
@@ -68,9 +68,7 @@ int length, expire;
 time_t now;
 dbdata_callout_cache *cache_record;
 
-cache_record = dbfn_read_with_length(dbm_file, key, &length);
-
-if (cache_record == NULL)
+if (!(cache_record = dbfn_read_with_length(dbm_file, key, &length)))
   {
   HDEBUG(D_verify) debug_printf("callout cache: no %s record found for %s\n", type, key);
   return NULL;
@@ -408,7 +406,7 @@ if (addr->transport == cutthrough.addr.transport)
 
        /* Match!  Send the RCPT TO, set done from the response */
        done =
-         smtp_write_command(&ctblock, FALSE, "RCPT TO:<%.1000s>\r\n",
+         smtp_write_command(&ctblock, SCMD_FLUSH, "RCPT TO:<%.1000s>\r\n",
            transport_rcpt_address(addr,
               addr->transport->rcpt_include_affixes)) >= 0 &&
          cutthrough_response(cutthrough.fd, '2', &resp, CUTTHROUGH_DATA_TIMEOUT) == '2';
@@ -785,26 +783,33 @@ tls_retry_connection:
       postmaster-verify.
       The sync_responses() would need to be taught about it and we'd
       need another return code filtering out to here.
+
+      Avoid using a SIZE option on the MAIL for all randon-rcpt checks.
       */
 
+      sx.avoid_option = OPTION_SIZE;
+
       /* Remember when we last did a random test */
       new_domain_record.random_stamp = time(NULL);
 
       if (smtp_write_mail_and_rcpt_cmds(&sx, &yield) == 0)
        switch(addr->transport_return)
          {
-         case PENDING_OK:
+         case PENDING_OK:      /* random was accepted, unfortunately */
            new_domain_record.random_result = ccache_accept;
-           break;
-         case FAIL:
+           yield = OK;         /* Only usable verify result we can return */
+           done = TRUE;
+           goto no_conn;
+         case FAIL:            /* rejected: the preferred result */
            new_domain_record.random_result = ccache_reject;
+           sx.avoid_option = 0;
 
            /* Between each check, issue RSET, because some servers accept only
            one recipient after MAIL FROM:<>.
            XXX We don't care about that for postmaster_full.  Should we? */
 
            if ((done =
-             smtp_write_command(&sx.outblock, FALSE, "RSET\r\n") >= 0 &&
+             smtp_write_command(&sx.outblock, SCMD_FLUSH, "RSET\r\n") >= 0 &&
              smtp_read_response(&sx.inblock, sx.buffer, sizeof(sx.buffer),
                '2', callout)))
              break;
@@ -829,6 +834,8 @@ tls_retry_connection:
            sx.send_rset = TRUE;
            sx.completed_addr = FALSE;
            goto tls_retry_connection;
+         case DEFER:           /* 4xx response to random */
+           break;              /* Just to be clear. ccache_unknown, !done. */
          }
 
       /* Re-setup for main verify, or for the error message when failing */
@@ -842,12 +849,14 @@ tls_retry_connection:
     else
       done = TRUE;
 
-    /* Main verify. If the host is accepting all local parts, as determined
-    by the "random" check, we don't need to waste time doing any further
-    checking. */
+    /* Main verify.  For rcpt-verify use SIZE if we know it and we're not cacheing;
+    for sndr-verify never use it. */
 
     if (done)
       {
+      if (!(options & vopt_is_recipient  &&  options & vopt_callout_no_cache))
+       sx.avoid_option = OPTION_SIZE;
+
       done = FALSE;
       switch(smtp_write_mail_and_rcpt_cmds(&sx, &yield))
        {
@@ -856,12 +865,12 @@ tls_retry_connection:
                    case PENDING_OK:  done = TRUE;
                                      new_address_record.result = ccache_accept;
                                      break;
-                   case FAIL:        done = TRUE;
+                   case FAIL:      done = TRUE;
                                      yield = FAIL;
                                      *failure_ptr = US"recipient";
                                      new_address_record.result = ccache_reject;
                                      break;
-                   default:          break;
+                   default:        break;
                    }
                  break;
 
@@ -897,7 +906,7 @@ tls_retry_connection:
       cancel_cutthrough_connection(TRUE, US"postmaster verify");
       HDEBUG(D_acl|D_v) debug_printf_indent("Cutthrough cancelled by presence of postmaster verify\n");
 
-      done = smtp_write_command(&sx.outblock, FALSE, "RSET\r\n") >= 0
+      done = smtp_write_command(&sx.outblock, SCMD_FLUSH, "RSET\r\n") >= 0
           && smtp_read_response(&sx.inblock, sx.buffer,
                                sizeof(sx.buffer), '2', callout);
 
@@ -914,6 +923,7 @@ tls_retry_connection:
        sx.ok = FALSE;
        sx.send_rset = TRUE;
        sx.completed_addr = FALSE;
+       sx.avoid_option = OPTION_SIZE;
 
        if(  smtp_write_mail_and_rcpt_cmds(&sx, &yield) == 0
          && addr->transport_return == PENDING_OK
@@ -921,7 +931,7 @@ tls_retry_connection:
          done = TRUE;
        else
          done = (options & vopt_callout_fullpm) != 0
-             && smtp_write_command(&sx.outblock, FALSE,
+             && smtp_write_command(&sx.outblock, SCMD_FLUSH,
                            "RCPT TO:<postmaster>\r\n") >= 0
              && smtp_read_response(&sx.inblock, sx.buffer,
                            sizeof(sx.buffer), '2', callout);
@@ -983,7 +993,7 @@ no_conn:
        if (*sx.buffer == 0) Ustrcpy(sx.buffer, US"connection dropped");
 
        /*XXX test here is ugly; seem to have a split of responsibility for
-       building this message.  Need to reationalise.  Where is it done
+       building this message.  Need to rationalise.  Where is it done
        before here, and when not?
        Not == 5xx resp to MAIL on main-verify
        */
@@ -1065,7 +1075,7 @@ no_conn:
         cancel_cutthrough_connection(TRUE, US"not usable for cutthrough");
       if (sx.send_quit)
        {
-       (void) smtp_write_command(&sx.outblock, FALSE, "QUIT\r\n");
+       (void) smtp_write_command(&sx.outblock, SCMD_FLUSH, "QUIT\r\n");
 
        /* Wait a short time for response, and discard it */
        smtp_read_response(&sx.inblock, sx.buffer, sizeof(sx.buffer),
@@ -1177,7 +1187,7 @@ if(cutthrough.fd < 0)
 
 if(
 #ifdef SUPPORT_TLS
-   (tls_out.active == cutthrough.fd) ? tls_write(FALSE, ctblock.buffer, n) :
+   tls_out.active == cutthrough.fd ? tls_write(FALSE, ctblock.buffer, n, FALSE) :
 #endif
    send(cutthrough.fd, ctblock.buffer, n, 0) > 0
   )
@@ -1308,9 +1318,9 @@ return cutthrough_response(cutthrough.fd, '3', NULL, CUTTHROUGH_DATA_TIMEOUT) ==
 }
 
 
-/* fd and tctx args only to match write_chunk() */
+/* tctx arg only to match write_chunk() */
 static BOOL
-cutthrough_write_chunk(int fd, transport_ctx * tctx, uschar * s, int len)
+cutthrough_write_chunk(transport_ctx * tctx, uschar * s, int len)
 {
 uschar * s2;
 while(s && (s2 = Ustrchr(s, '\n')))
@@ -1339,13 +1349,15 @@ if(cutthrough.fd < 0 || cutthrough.callout_hold_only)
 */
 HDEBUG(D_acl) debug_printf_indent("----------- start cutthrough headers send -----------\n");
 
+tctx.u.fd = cutthrough.fd;
 tctx.tblock = cutthrough.addr.transport;
 tctx.addr = &cutthrough.addr;
 tctx.check_string = US".";
 tctx.escape_string = US"..";
+/*XXX check under spool_files_wireformat.  Might be irrelevant */
 tctx.options = topt_use_crlf;
 
-if (!transport_headers_send(cutthrough.fd, &tctx, &cutthrough_write_chunk))
+if (!transport_headers_send(&tctx, &cutthrough_write_chunk))
   return FALSE;
 
 HDEBUG(D_acl) debug_printf_indent("----------- done cutthrough headers send ------------\n");
@@ -1394,6 +1406,7 @@ cutthrough.delivery = cutthrough.callout_hold_only = FALSE;
 void
 release_cutthrough_connection(const uschar * why)
 {
+if (cutthrough.fd < 0) return;
 HDEBUG(D_acl) debug_printf_indent("release cutthrough conn: %s\n", why);
 cutthrough.fd = -1;
 cutthrough.delivery = cutthrough.callout_hold_only = FALSE;
@@ -1514,7 +1527,7 @@ va_list ap;
 
 va_start(ap, format);
 if (smtp_out && (f == smtp_out))
-  smtp_vprintf(format, ap);
+  smtp_vprintf(format, FALSE, ap);
 else
   vfprintf(f, format, ap);
 va_end(ap);
@@ -1776,16 +1789,16 @@ while (addr_new)
       transport. */
 
       transport_feedback tf = {
-        NULL,                       /* interface (=> any) */
-        US"smtp",                   /* port */
-        US"smtp",                   /* protocol */
-        NULL,                       /* hosts */
-        US"$smtp_active_hostname",  /* helo_data */
-        FALSE,                      /* hosts_override */
-        FALSE,                      /* hosts_randomize */
-        FALSE,                      /* gethostbyname */
-        TRUE,                       /* qualify_single */
-        FALSE                       /* search_parents */
+        .interface =           NULL,                       /* interface (=> any) */
+        .port =                        US"smtp",
+        .protocol =            US"smtp",
+        .hosts =               NULL,
+        .helo_data =           US"$smtp_active_hostname",
+        .hosts_override =      FALSE,
+        .hosts_randomize =     FALSE,
+        .gethostbyname =       FALSE,
+        .qualify_single =      TRUE,
+        .search_parents =      FALSE
         };
 
       /* If verification yielded a remote transport, we want to use that
@@ -2669,8 +2682,11 @@ if (ip_bind(sock, host_af, interface_address, 0) < 0)
   goto END_OFF;
   }
 
+/*XXX could take advantage of TFO early-data.  Hmm, what are the
+error returns; can we differentiate connect from data fails?
+Do we need to? */
 if (ip_connect(sock, host_af, sender_host_address, port,
-               rfc1413_query_timeout, TRUE) < 0)
+               rfc1413_query_timeout, &tcp_fastopen_nodata) < 0)
   {
   if (errno == ETIMEDOUT && LOGGING(ident_timeout))
     log_write(0, LOG_MAIN, "ident connection to %s timed out",
@@ -2686,10 +2702,24 @@ if (ip_connect(sock, host_af, sender_host_address, port,
 sprintf(CS buffer, "%d , %d\r\n", sender_host_port, interface_port);
 qlen = Ustrlen(buffer);
 if (send(sock, buffer, qlen, 0) < 0)
-  {
-  DEBUG(D_ident) debug_printf("ident send failed: %s\n", strerror(errno));
-  goto END_OFF;
-  }
+  if (errno == ENOTCONN)       /* seen for TFO on FreeBSD */
+    {
+    struct timeval tv = { .tv_sec = 0, .tv_usec = 500*1000 };
+    fd_set s;
+
+    FD_ZERO(&s); FD_SET(sock, &s);
+    (void) select(sock+1, NULL,  (SELECT_ARG2_TYPE *)&s, (SELECT_ARG2_TYPE *)&s, &tv);
+    if (send(sock, buffer, qlen, 0) < 0)
+      {
+      DEBUG(D_ident) debug_printf("ident re-send failed: %s\n", strerror(errno));
+      goto END_OFF;
+      }
+    }
+  else
+    {
+    DEBUG(D_ident) debug_printf("ident send failed: %s\n", strerror(errno));
+    goto END_OFF;
+    }
 
 /* Read a response line. We put it into the rest of the buffer, using several
 recv() calls if necessary. */
@@ -3141,18 +3171,16 @@ verify_check_this_host(const uschar **listptr, unsigned int *cache_bits,
 int rc;
 unsigned int *local_cache_bits = cache_bits;
 const uschar *save_host_address = deliver_host_address;
-check_host_block cb;
-cb.host_name = host_name;
-cb.host_address = host_address;
+check_host_block cb = { .host_name = host_name, .host_address = host_address };
 
-if (valueptr != NULL) *valueptr = NULL;
+if (valueptr) *valueptr = NULL;
 
 /* If the host address starts off ::ffff: it is an IPv6 address in
 IPv4-compatible mode. Find the IPv4 part for checking against IPv4
 addresses. */
 
-cb.host_ipv4 = (Ustrncmp(host_address, "::ffff:", 7) == 0)?
-  host_address + 7 : host_address;
+cb.host_ipv4 = Ustrncmp(host_address, "::ffff:", 7) == 0
+  host_address + 7 : host_address;
 
 /* During the running of the check, put the IP address into $host_address. In
 the case of calls from the smtp transport, it will already be there. However,