DKIM: verify using separate pool-pair, reset per message
[exim.git] / src / src / smtp_in.c
index 39416679b28438c0f5f065098b48e8b3640f2d6e..6d6370ffd9d3f2e06e4d67f34be84bf8b8606ede 100644 (file)
@@ -347,7 +347,7 @@ wouldblock_reading(void)
 {
 int fd, rc;
 fd_set fds;
-struct timeval tzero;
+struct timeval tzero = {.tv_sec = 0, .tv_usec = 0};
 
 #ifndef DISABLE_TLS
 if (tls_in.active.sock >= 0)
@@ -360,8 +360,6 @@ if (smtp_inptr < smtp_inend)
 fd = fileno(smtp_in);
 FD_ZERO(&fds);
 FD_SET(fd, &fds);
-tzero.tv_sec = 0;
-tzero.tv_usec = 0;
 rc = select(fd + 1, (SELECT_ARG2_TYPE *)&fds, NULL, NULL, &tzero);
 
 if (rc <= 0) return TRUE;     /* Not ready to read */
@@ -587,6 +585,8 @@ smtp_get_cache(void)
 {
 #ifndef DISABLE_DKIM
 int n = smtp_inend - smtp_inptr;
+if (chunking_state == CHUNKING_LAST && chunking_data_left < n)
+  n = chunking_data_left;
 if (n > 0)
   dkim_exim_verify_feed(smtp_inptr, n);
 #endif
@@ -1033,25 +1033,6 @@ had_command_sigterm = sig;
 
 
 #ifdef SUPPORT_PROXY
-/*************************************************
-*     Restore socket timeout to previous value   *
-*************************************************/
-/* If the previous value was successfully retrieved, restore
-it before returning control to the non-proxy routines
-
-Arguments: fd     - File descriptor for input
-           get_ok - Successfully retrieved previous values
-           tvtmp  - Time struct with previous values
-           vslen  - Length of time struct
-Returns:   none
-*/
-static void
-restore_socket_timeout(int fd, int get_ok, struct timeval * tvtmp, socklen_t vslen)
-{
-if (get_ok == 0)
-  (void) setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, CS tvtmp, vslen);
-}
-
 /*************************************************
 *       Check if host is required proxy host     *
 *************************************************/
@@ -1128,7 +1109,7 @@ if (cr != NULL)
 
 while (capacity > 0)
   {
-  do { ret = read(fd, to, 1); } while (ret == -1 && errno == EINTR);
+  do { ret = read(fd, to, 1); } while (ret == -1 && errno == EINTR && !had_command_timeout);
   if (ret == -1)
     return -1;
   have++;
@@ -1232,20 +1213,11 @@ int size, ret;
 int fd = fileno(smtp_in);
 const char v2sig[12] = "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A";
 uschar * iptype;  /* To display debug info */
-struct timeval tv;
-struct timeval tvtmp;
 socklen_t vslen = sizeof(struct timeval);
 BOOL yield = FALSE;
 
-/* Save current socket timeout values */
-get_ok = getsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, CS &tvtmp, &vslen);
-
-/* Proxy Protocol host must send header within a short time
-(default 3 seconds) or it's considered invalid */
-tv.tv_sec  = PROXY_NEGOTIATION_TIMEOUT_SEC;
-tv.tv_usec = PROXY_NEGOTIATION_TIMEOUT_USEC;
-if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, CS &tv, sizeof(tv)) < 0)
-  goto bad;
+os_non_restarting_signal(SIGALRM, command_timeout_handler);
+ALARM(proxy_protocol_timeout);
 
 do
   {
@@ -1255,7 +1227,7 @@ do
   immediately with TLS handshake. */
   ret = read(fd, &hdr, PROXY_INITIAL_READ);
   }
-  while (ret == -1 && errno == EINTR);
+  while (ret == -1 && errno == EINTR && !had_command_timeout);
 
 if (ret == -1)
   goto proxyfail;
@@ -1270,7 +1242,7 @@ if ((ret == PROXY_INITIAL_READ) && (memcmp(&hdr.v2, v2sig, sizeof(v2sig)) == 0))
   do
     {
     retmore = read(fd, (uschar*)&hdr + ret, PROXY_V2_HEADER_SIZE - PROXY_INITIAL_READ);
-    } while (retmore == -1 && errno == EINTR);
+    } while (retmore == -1 && errno == EINTR && !had_command_timeout);
   if (retmore == -1)
     goto proxyfail;
   ret += retmore;
@@ -1307,7 +1279,7 @@ if ((ret == PROXY_INITIAL_READ) && (memcmp(&hdr.v2, v2sig, sizeof(v2sig)) == 0))
     do
       {
       retmore = read(fd, (uschar*)&hdr + ret, size-ret);
-      } while (retmore == -1 && errno == EINTR);
+      } while (retmore == -1 && errno == EINTR && !had_command_timeout);
     if (retmore == -1)
       goto proxyfail;
     ret += retmore;
@@ -1535,7 +1507,8 @@ done:
 should cause a synchronization failure */
 
 proxyfail:
-  restore_socket_timeout(fd, get_ok, &tvtmp, vslen);
+  DEBUG(D_receive) if (had_command_timeout)
+    debug_printf("Timeout while reading proxy header\n");
 
 bad:
   if (yield)
@@ -1551,6 +1524,7 @@ bad:
       debug_printf("Failure to extract proxied host, only QUIT allowed\n");
     }
 
+ALARM(0);
 return;
 }
 #endif
@@ -2135,7 +2109,11 @@ while (acl_warn_logged)
   acl_warn_logged = acl_warn_logged->next;
   store_free(this);
   }
+
+message_tidyup();
 store_reset(reset_point);
+
+message_start();
 return store_mark();
 }
 
@@ -2212,7 +2190,7 @@ while (done <= 0)
 
     case MAIL_CMD:
       smtp_mailcmd_count++;              /* Count for no-mail log */
-      if (sender_address != NULL)
+      if (sender_address)
        /* The function moan_smtp_batch() does not return. */
        moan_smtp_batch(smtp_cmd_buffer, "503 Sender already given");
 
@@ -2412,7 +2390,7 @@ TCP_SYN_RCV (as of 12.1) so no idea about data-use. */
 
 if (getsockopt(fileno(smtp_out), IPPROTO_TCP, TCP_FASTOPEN, &is_fastopen, &len) == 0)
   {
-  if (is_fastopen) 
+  if (is_fastopen)
     {
     DEBUG(D_receive)
       debug_printf("TFO mode connection (TCP_FASTOPEN getsockopt)\n");
@@ -2919,7 +2897,7 @@ if (!f.sender_host_unknown)
 if (smtp_batched_input) return TRUE;
 
 /* If valid Proxy Protocol source is connecting, set up session.
- * Failure will not allow any SMTP function other than QUIT. */
+Failure will not allow any SMTP function other than QUIT. */
 
 #ifdef SUPPORT_PROXY
 proxy_session = FALSE;
@@ -2928,16 +2906,16 @@ if (check_proxy_protocol_host())
   setup_proxy_protocol_host();
 #endif
 
-  /* Start up TLS if tls_on_connect is set. This is for supporting the legacy
-  smtps port for use with older style SSL MTAs. */
+/* Start up TLS if tls_on_connect is set. This is for supporting the legacy
+smtps port for use with older style SSL MTAs. */
 
 #ifndef DISABLE_TLS
-  if (tls_in.on_connect)
-    {
-    if (tls_server_start(&user_msg) != OK)
-      return smtp_log_tls_fail(user_msg);
-    cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = TRUE;
-    }
+if (tls_in.on_connect)
+  {
+  if (tls_server_start(&user_msg) != OK)
+    return smtp_log_tls_fail(user_msg);
+  cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = TRUE;
+  }
 #endif
 
 /* Run the connect ACL if it exists */
@@ -3305,18 +3283,7 @@ int codelen = 3;
 uschar *smtp_code;
 uschar *lognl;
 uschar *sender_info = US"";
-uschar *what =
-#ifdef WITH_CONTENT_SCAN
-  where == ACL_WHERE_MIME ? US"during MIME ACL checks" :
-#endif
-  where == ACL_WHERE_PREDATA ? US"DATA" :
-  where == ACL_WHERE_DATA ? US"after DATA" :
-#ifndef DISABLE_PRDR
-  where == ACL_WHERE_PRDR ? US"after DATA PRDR" :
-#endif
-  smtp_cmd_data ?
-    string_sprintf("%s %s", acl_wherenames[where], smtp_cmd_data) :
-    string_sprintf("%s in \"connect\" ACL", acl_wherenames[where]);
+uschar *what;
 
 if (drop) rc = FAIL;
 
@@ -3332,19 +3299,45 @@ fixed, sender_address at this point became the rewritten address. I'm not sure
 this is what should be logged, so I've changed to logging the unrewritten
 address to retain backward compatibility. */
 
-#ifndef WITH_CONTENT_SCAN
-if (where == ACL_WHERE_RCPT || where == ACL_WHERE_DATA)
-#else
-if (where == ACL_WHERE_RCPT || where == ACL_WHERE_DATA || where == ACL_WHERE_MIME)
+switch (where)
+  {
+#ifdef WITH_CONTENT_SCAN
+  case ACL_WHERE_MIME:         what = US"during MIME ACL checks";      break;
+#endif
+  case ACL_WHERE_PREDATA:      what = US"DATA";                        break;
+  case ACL_WHERE_DATA:         what = US"after DATA";                  break;
+#ifndef DISABLE_PRDR
+  case ACL_WHERE_PRDR:         what = US"after DATA PRDR";             break;
 #endif
+  default:
+    {
+    uschar * place = smtp_cmd_data ? smtp_cmd_data : US"in \"connect\" ACL";
+    int lim = 100;
+
+    if (where == ACL_WHERE_AUTH)       /* avoid logging auth creds */
+      {
+      uschar * s;
+      for (s = smtp_cmd_data; *s && !isspace(*s); ) s++;
+      lim = s - smtp_cmd_data; /* atop after method */
+      }
+    what = string_sprintf("%s %.*s", acl_wherenames[where], lim, place);
+    }
+  }
+switch (where)
   {
-  sender_info = string_sprintf("F=<%s>%s%s%s%s ",
-    sender_address_unrewritten ? sender_address_unrewritten : sender_address,
-    sender_host_authenticated ? US" A="                                    : US"",
-    sender_host_authenticated ? sender_host_authenticated                  : US"",
-    sender_host_authenticated && authenticated_id ? US":"                  : US"",
-    sender_host_authenticated && authenticated_id ? authenticated_id       : US""
-    );
+  case ACL_WHERE_RCPT:
+  case ACL_WHERE_DATA:
+#ifdef WITH_CONTENT_SCAN
+  case ACL_WHERE_MIME:
+#endif
+    sender_info = string_sprintf("F=<%s>%s%s%s%s ",
+      sender_address_unrewritten ? sender_address_unrewritten : sender_address,
+      sender_host_authenticated ? US" A="                                    : US"",
+      sender_host_authenticated ? sender_host_authenticated                  : US"",
+      sender_host_authenticated && authenticated_id ? US":"                  : US"",
+      sender_host_authenticated && authenticated_id ? authenticated_id       : US""
+      );
+  break;
   }
 
 /* If there's been a sender verification failure with a specific message, and
@@ -3845,8 +3838,8 @@ if (  acl_smtp_quit
     log_write(0, LOG_MAIN|LOG_PANIC, "ACL for QUIT returned ERROR: %s",
       *log_msgp);
 
-#ifdef TCP_CORK
-(void) setsockopt(fileno(smtp_out), IPPROTO_TCP, TCP_CORK, US &on, sizeof(on));
+#ifdef EXIM_TCP_CORK
+(void) setsockopt(fileno(smtp_out), IPPROTO_TCP, EXIM_TCP_CORK, US &on, sizeof(on));
 #endif
 
 if (*user_msgp)
@@ -3855,11 +3848,24 @@ else
   smtp_printf("221 %s closing connection\r\n", FALSE, smtp_active_hostname);
 
 #ifndef DISABLE_TLS
-tls_close(NULL, TLS_SHUTDOWN_NOWAIT);
+tls_close(NULL, TLS_SHUTDOWN_WAIT);
 #endif
 
 log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT",
   smtp_get_connection_info());
+
+/* Pause, hoping client will FIN first so that they get the TIME_WAIT.
+The socket should become readble (though with no data) */
+
+  {
+  int fd = fileno(smtp_in);
+  fd_set fds;
+  struct timeval t_limit = {.tv_sec = 0, .tv_usec = 200*1000};
+
+  FD_ZERO(&fds);
+  FD_SET(fd, &fds);
+  (void) select(fd + 1, (SELECT_ARG2_TYPE *)&fds, NULL, NULL, &t_limit);
+  }
 }
 
 
@@ -3938,6 +3944,12 @@ os_non_restarting_signal(SIGTERM, command_sigterm_handler);
 
 if (smtp_batched_input) return smtp_setup_batch_msg();
 
+#ifdef TCP_QUICKACK
+if (smtp_in)           /* Avoid pure-ACKs while in cmd pingpong phase */
+  (void) setsockopt(fileno(smtp_in), IPPROTO_TCP, TCP_QUICKACK,
+         US &off, sizeof(off));
+#endif
+
 /* Deal with SMTP commands. This loop is exited by setting done to a POSITIVE
 value. The values are 2 larger than the required yield of the function. */
 
@@ -3995,12 +4007,6 @@ while (done <= 0)
     }
 #endif
 
-#ifdef TCP_QUICKACK
-  if (smtp_in)         /* Avoid pure-ACKs while in cmd pingpong phase */
-    (void) setsockopt(fileno(smtp_in), IPPROTO_TCP, TCP_QUICKACK,
-           US &off, sizeof(off));
-#endif
-
   switch(smtp_read_command(
 #ifndef DISABLE_PIPE_CONNECT
          !fl.pipe_connect_acceptable,
@@ -4061,21 +4067,18 @@ while (done <= 0)
       /* Find the name of the requested authentication mechanism. */
 
       s = smtp_cmd_data;
-      while ((c = *smtp_cmd_data) != 0 && !isspace(c))
-       {
+      for (; (c = *smtp_cmd_data) && !isspace(c); smtp_cmd_data++)
        if (!isalnum(c) && c != '-' && c != '_')
          {
          done = synprot_error(L_smtp_syntax_error, 501, NULL,
            US"invalid character in authentication mechanism name");
          goto COMMAND_LOOP;
          }
-       smtp_cmd_data++;
-       }
 
       /* If not at the end of the line, we must be at white space. Terminate the
       name and move the pointer on to any data that may be present. */
 
-      if (*smtp_cmd_data != 0)
+      if (*smtp_cmd_data)
        {
        *smtp_cmd_data++ = 0;
        while (isspace(*smtp_cmd_data)) smtp_cmd_data++;
@@ -4534,6 +4537,7 @@ while (done <= 0)
     case MAIL_CMD:
       HAD(SCH_MAIL);
       smtp_mailcmd_count++;              /* Count for limit and ratelimit */
+      message_start();
       was_rej_mail = TRUE;               /* Reset if accepted */
       env_mail_type_t * mail_args;       /* Sanity check & validate args */
 
@@ -5769,7 +5773,7 @@ while (done <= 0)
        /* If not serializing, do the exec right away. Otherwise, fork down
        into another process. */
 
-       if (  !smtp_etrn_serialize 
+       if (  !smtp_etrn_serialize
           || (pid = exim_fork(US"etrn-serialised-command")) == 0)
          {
          DEBUG(D_exec) debug_print_argv(argv);
@@ -5918,12 +5922,14 @@ if (!sender_host_authenticated)
 
 g = string_append(g, 2, US";\n\tauth=pass (", sender_host_auth_pubname);
 
-if (Ustrcmp(sender_host_auth_pubname, "tls") != 0)
-  g = string_append(g, 2, US") smtp.auth=", authenticated_id);
-else if (authenticated_id)
-  g = string_append(g, 2, US") x509.auth=", authenticated_id);
+if (Ustrcmp(sender_host_auth_pubname, "tls") == 0)
+  g = authenticated_id
+    ? string_append(g, 2, US") x509.auth=", authenticated_id)
+    : string_cat(g, US") reason=x509.auth");
 else
-  g = string_catn(g, US") reason=x509.auth", 17);
+  g = authenticated_id
+    ? string_append(g, 2, US") smtp.auth=", authenticated_id)
+    : string_cat(g, US", no id saved)");
 
 if (authenticated_sender)
   g = string_append(g, 2, US" smtp.mailfrom=", authenticated_sender);