Experimental: ESMTP LIMITS extension
[exim.git] / src / src / smtp_in.c
index cf6271c6033c2510821bacd2b26299ab10c36f39..d60e7d5c5eec87acf701fa8e3809056c616bcc97 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
@@ -1213,8 +1213,6 @@ 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;
 
@@ -2111,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();
 }
 
@@ -2188,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");
 
@@ -2895,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;
@@ -2904,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 */
@@ -3281,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;
 
@@ -3308,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
@@ -3821,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)
@@ -3830,12 +3847,35 @@ if (*user_msgp)
 else
   smtp_printf("221 %s closing connection\r\n", FALSE, smtp_active_hostname);
 
-#ifndef DISABLE_TLS
+#ifdef SERVERSIDE_CLOSE_NOWAIT
+# ifndef DISABLE_TLS
 tls_close(NULL, TLS_SHUTDOWN_NOWAIT);
-#endif
+# endif
+
+log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT",
+  smtp_get_connection_info());
+#else
+
+# ifndef DISABLE_TLS
+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);
+  }
+#endif /*!DAEMON_CLOSE_NOWAIT*/
 }
 
 
@@ -3849,6 +3889,13 @@ cmd_list[CMD_LIST_RSET].is_mail_cmd = FALSE;
 }
 
 
+static int
+expand_mailmax(const uschar * s)
+{
+if (!(s = expand_cstring(s)))
+  log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand smtp_accept_max_per_connection");
+return *s ? Uatoi(s) : 0;
+}
 
 /*************************************************
 *       Initialize for SMTP incoming message     *
@@ -3879,6 +3926,7 @@ int
 smtp_setup_msg(void)
 {
 int done = 0;
+int mailmax = -1;
 BOOL toomany = FALSE;
 BOOL discarded = FALSE;
 BOOL last_was_rej_mail = FALSE;
@@ -3914,6 +3962,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. */
 
@@ -3971,12 +4025,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,
@@ -4037,21 +4085,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++;
@@ -4239,6 +4284,9 @@ while (done <= 0)
       fl.smtputf8_advertised = FALSE;
 #endif
 
+      /* Expand the per-connection message count limit option */
+      mailmax = expand_mailmax(smtp_accept_max_per_connection);
+
       smtp_code = US"250 ";        /* Default response code plus space*/
       if (!user_msg)
        {
@@ -4298,6 +4346,19 @@ while (done <= 0)
          g = string_catn(g, US"-SIZE\r\n", 7);
          }
 
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+       if (  (mailmax > 0 || recipients_max)
+          && verify_check_host(&limits_advertise_hosts) == OK)
+         {
+         g = string_fmt_append(g, "%.3s-LIMITS", smtp_code);
+         if (mailmax > 0)
+           g = string_fmt_append(g, " MAILMAX=%d", mailmax);
+         if (recipients_max)
+           g = string_fmt_append(g, " RCPTMAX=%d", recipients_max);
+         g = string_catn(g, US"\r\n", 2);
+         }
+#endif
+
        /* Exim does not do protocol conversion or data conversion. It is 8-bit
        clean; if it has an 8-bit character in its hand, it just sends it. It
        cannot therefore specify 8BITMIME and remain consistent with the RFCs.
@@ -4510,16 +4571,20 @@ 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 */
 
-      if (fl.helo_required && !fl.helo_seen)
-       {
-       smtp_printf("503 HELO or EHLO required\r\n", FALSE);
-       log_write(0, LOG_MAIN|LOG_REJECT, "rejected MAIL from %s: no "
-         "HELO/EHLO given", host_and_ident(FALSE));
-       break;
-       }
+      if (!fl.helo_seen)
+       if (fl.helo_required)
+         {
+         smtp_printf("503 HELO or EHLO required\r\n", FALSE);
+         log_write(0, LOG_MAIN|LOG_REJECT, "rejected MAIL from %s: no "
+           "HELO/EHLO given", host_and_ident(FALSE));
+         break;
+         }
+       else if (mailmax < 0)
+         mailmax = expand_mailmax(smtp_accept_max_per_connection);
 
       if (sender_address)
        {
@@ -4538,8 +4603,7 @@ while (done <= 0)
       /* Check to see if the limit for messages per connection would be
       exceeded by accepting further messages. */
 
-      if (smtp_accept_max_per_connection > 0 &&
-         smtp_mailcmd_count > smtp_accept_max_per_connection)
+      if (mailmax > 0 && smtp_mailcmd_count > mailmax)
        {
        smtp_printf("421 too many messages in this connection\r\n", FALSE);
        log_write(0, LOG_MAIN|LOG_REJECT, "rejected MAIL command %s: too many "
@@ -5894,12 +5958,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_cat(g, US") reason=x509.auth");
+  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);