Added log selector +smtp_no_mail to log when no MAIL is issued (for
[exim.git] / src / src / smtp_in.c
index 97b721e550530a8e978b7dab72bb9a86aae9d84b..b1a1eba3dd8de80fa7ae84cc1009a841e803b8b4 100644 (file)
@@ -1,10 +1,10 @@
-/* $Cambridge: exim/src/src/smtp_in.c,v 1.47 2006/11/14 16:40:36 ph10 Exp $ */
+/* $Cambridge: exim/src/src/smtp_in.c,v 1.50 2007/01/15 15:59:22 ph10 Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2006 */
+/* Copyright (c) University of Cambridge 1995 - 2007 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for handling an incoming SMTP call. */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for handling an incoming SMTP call. */
@@ -96,6 +96,13 @@ enum {
   TOO_MANY_NONMAIL_CMD };
 
 
   TOO_MANY_NONMAIL_CMD };
 
 
+/* This is a convenience macro for adding the identity of an SMTP command
+to the circular buffer that holds a list of the last n received. */
+
+#define HAD(n) \
+    smtp_connection_had[smtp_ch_index++] = n; \
+    if (smtp_ch_index >= SMTP_HBUFF_SIZE) smtp_ch_index = 0
+
 
 /*************************************************
 *                Local static variables          *
 
 /*************************************************
 *                Local static variables          *
@@ -165,6 +172,15 @@ static smtp_cmd_list *cmd_list_end =
 #define CMD_LIST_AUTH      3
 #define CMD_LIST_STARTTLS  4
 
 #define CMD_LIST_AUTH      3
 #define CMD_LIST_STARTTLS  4
 
+/* This list of names is used for performing the smtp_no_mail logging action.
+It must be kept in step with the SCH_xxx enumerations. */
+
+static uschar *smtp_names[] =
+  {
+  US"NONE", US"AUTH", US"DATA", US"EHLO", US"ETRN", US"EXPN", US"HELO",
+  US"HELP", US"MAIL", US"NOOP", US"QUIT", US"RCPT", US"RSET", US"STARTTLS",
+  US"VRFY" };
+
 static uschar *protocols[] = {
   US"local-smtp",        /* HELO */
   US"local-smtps",       /* The rare case EHLO->STARTTLS->HELO */
 static uschar *protocols[] = {
   US"local-smtp",        /* HELO */
   US"local-smtps",       /* The rare case EHLO->STARTTLS->HELO */
@@ -666,6 +682,74 @@ return string_sprintf("SMTP connection from %s", hostname);
 
 
 
 
 
 
+/*************************************************
+*      Log lack of MAIL if so configured         *
+*************************************************/
+
+/* This function is called when an SMTP session ends. If the log selector
+smtp_no_mail is set, write a log line giving some details of what has happened
+in the SMTP session.
+
+Arguments:   none
+Returns:     nothing
+*/
+
+void
+smtp_log_no_mail(void)
+{
+int size, ptr, i;
+uschar *s, *sep;
+
+if (smtp_mailcmd_count > 0 || (log_extra_selector & LX_smtp_no_mail) == 0)
+  return;
+
+s = NULL;
+size = ptr = 0;
+
+if (sender_host_authenticated != NULL)
+  {
+  s = string_append(s, &size, &ptr, 2, US" A=", sender_host_authenticated);
+  if (authenticated_id != NULL)
+    s = string_append(s, &size, &ptr, 2, US":", authenticated_id);
+  }
+
+#ifdef SUPPORT_TLS
+if ((log_extra_selector & LX_tls_cipher) != 0 && tls_cipher != NULL)
+  s = string_append(s, &size, &ptr, 2, US" X=", tls_cipher);
+if ((log_extra_selector & LX_tls_certificate_verified) != 0 &&
+     tls_cipher != NULL)
+  s = string_append(s, &size, &ptr, 2, US" CV=",
+    tls_certificate_verified? "yes":"no");
+if ((log_extra_selector & LX_tls_peerdn) != 0 && tls_peerdn != NULL)
+  s = string_append(s, &size, &ptr, 3, US" DN=\"", tls_peerdn, US"\"");
+#endif
+
+sep = (smtp_connection_had[SMTP_HBUFF_SIZE-1] != SCH_NONE)?
+  US" C=..." : US" C=";
+for (i = smtp_ch_index; i < SMTP_HBUFF_SIZE; i++)
+  {
+  if (smtp_connection_had[i] != SCH_NONE)
+    {
+    s = string_append(s, &size, &ptr, 2, sep,
+      smtp_names[smtp_connection_had[i]]);
+    sep = US",";
+    }
+  }
+
+for (i = 0; i < smtp_ch_index; i++)
+  {
+  s = string_append(s, &size, &ptr, 2, sep, smtp_names[smtp_connection_had[i]]);
+  sep = US",";
+  }
+
+if (s != NULL) s[ptr] = 0; else s = US"";
+log_write(0, LOG_MAIN, "no MAIL in SMTP connection from %s D=%s%s",
+  host_and_ident(FALSE),
+  readconf_printtime(time(NULL) - smtp_connection_start), s);
+}
+
+
+
 /*************************************************
 *   Check HELO line and set sender_helo_name     *
 *************************************************/
 /*************************************************
 *   Check HELO line and set sender_helo_name     *
 *************************************************/
@@ -1146,9 +1230,15 @@ uschar *user_msg, *log_msg;
 uschar *code, *esc;
 uschar *p, *s, *ss;
 
 uschar *code, *esc;
 uschar *p, *s, *ss;
 
+smtp_connection_start = time(NULL);
+for (smtp_ch_index = 0; smtp_ch_index < SMTP_HBUFF_SIZE; smtp_ch_index++)
+  smtp_connection_had[smtp_ch_index] = SCH_NONE;
+smtp_ch_index = 0;
+
 /* Default values for certain variables */
 
 helo_seen = esmtp = helo_accept_junk = FALSE;
 /* Default values for certain variables */
 
 helo_seen = esmtp = helo_accept_junk = FALSE;
+smtp_mailcmd_count = 0;
 count_nonmail = TRUE_UNSET;
 synprot_error_count = unknown_command_count = nonmail_command_count = 0;
 smtp_delay_mail = smtp_rlm_base;
 count_nonmail = TRUE_UNSET;
 synprot_error_count = unknown_command_count = nonmail_command_count = 0;
 smtp_delay_mail = smtp_rlm_base;
@@ -1605,7 +1695,7 @@ else
   int codelen = 3;
   s = user_msg;
   smtp_message_code(&code, &codelen, &s, NULL);
   int codelen = 3;
   s = user_msg;
   smtp_message_code(&code, &codelen, &s, NULL);
-  if (codelen > 3)
+  if (codelen > 4)
     {
     esc = code + 4;
     esclen = codelen - 4;
     {
     esc = code + 4;
     esclen = codelen - 4;
@@ -1793,7 +1883,7 @@ output nothing for non-final calls, and only the first line for anything else.
 
 Arguments:
   code          SMTP code, may involve extended status codes
 
 Arguments:
   code          SMTP code, may involve extended status codes
-  codelen       length of smtp code; if > 3 there's an ESC
+  codelen       length of smtp code; if > 4 there's an ESC
   final         FALSE if the last line isn't the final line
   msg           message text, possibly containing newlines
 
   final         FALSE if the last line isn't the final line
   msg           message text, possibly containing newlines
 
@@ -1808,7 +1898,7 @@ uschar *esc = US"";
 
 if (!final && no_multiline_responses) return;
 
 
 if (!final && no_multiline_responses) return;
 
-if (codelen > 3)
+if (codelen > 4)
   {
   esc = code + 4;
   esclen = codelen - 4;
   {
   esc = code + 4;
   esclen = codelen - 4;
@@ -1856,9 +1946,12 @@ is actually going to be used (the original one).
 This function is global because it is called from receive.c as well as within
 this module.
 
 This function is global because it is called from receive.c as well as within
 this module.
 
+Note that the code length returned includes the terminating whitespace
+character, which is always included in the regex match.
+
 Arguments:
   code          SMTP code, may involve extended status codes
 Arguments:
   code          SMTP code, may involve extended status codes
-  codelen       length of smtp code; if > 3 there's an ESC
+  codelen       length of smtp code; if > 4 there's an ESC
   msg           message text
   log_msg       optional log message, to be adjusted with the new SMTP code
 
   msg           message text
   log_msg       optional log message, to be adjusted with the new SMTP code
 
@@ -2332,6 +2425,7 @@ while (done <= 0)
     AUTHS will eventually hit the nonmail threshold. */
 
     case AUTH_CMD:
     AUTHS will eventually hit the nonmail threshold. */
 
     case AUTH_CMD:
+    HAD(SCH_AUTH);
     authentication_failed = TRUE;
     cmd_list[CMD_LIST_AUTH].is_mail_cmd = FALSE;
 
     authentication_failed = TRUE;
     cmd_list[CMD_LIST_AUTH].is_mail_cmd = FALSE;
 
@@ -2524,11 +2618,13 @@ while (done <= 0)
     it did the reset first. */
 
     case HELO_CMD:
     it did the reset first. */
 
     case HELO_CMD:
+    HAD(SCH_HELO);
     hello = US"HELO";
     esmtp = FALSE;
     goto HELO_EHLO;
 
     case EHLO_CMD:
     hello = US"HELO";
     esmtp = FALSE;
     goto HELO_EHLO;
 
     case EHLO_CMD:
+    HAD(SCH_EHLO);
     hello = US"EHLO";
     esmtp = TRUE;
 
     hello = US"EHLO";
     esmtp = TRUE;
 
@@ -2650,7 +2746,7 @@ while (done <= 0)
     tls_advertised = FALSE;
     #endif
 
     tls_advertised = FALSE;
     #endif
 
-    smtp_code = US"250";        /* Default response code */
+    smtp_code = US"250 ";        /* Default response code plus space*/
     if (user_msg == NULL)
       {
       s = string_sprintf("%.3s %s Hello %s%s%s",
     if (user_msg == NULL)
       {
       s = string_sprintf("%.3s %s Hello %s%s%s",
@@ -2672,14 +2768,16 @@ while (done <= 0)
         }
       }
 
         }
       }
 
-    /* A user-supplied EHLO greeting may not contain more than one line */
+    /* A user-supplied EHLO greeting may not contain more than one line. Note
+    that the code returned by smtp_message_code() includes the terminating
+    whitespace character. */
 
     else
       {
       char *ss;
 
     else
       {
       char *ss;
-      int codelen = 3;
+      int codelen = 4;
       smtp_message_code(&smtp_code, &codelen, &user_msg, NULL);
       smtp_message_code(&smtp_code, &codelen, &user_msg, NULL);
-      s = string_sprintf("%.*s %s", codelen, smtp_code, user_msg);
+      s = string_sprintf("%.*s%s", codelen, smtp_code, user_msg);
       if ((ss = strpbrk(CS s, "\r\n")) != NULL)
         {
         log_write(0, LOG_MAIN|LOG_PANIC, "EHLO/HELO response must not contain "
       if ((ss = strpbrk(CS s, "\r\n")) != NULL)
         {
         log_write(0, LOG_MAIN|LOG_PANIC, "EHLO/HELO response must not contain "
@@ -2865,6 +2963,7 @@ while (done <= 0)
     it is the canonical extracted address which is all that is kept. */
 
     case MAIL_CMD:
     it is the canonical extracted address which is all that is kept. */
 
     case MAIL_CMD:
+    HAD(SCH_MAIL);
     smtp_mailcmd_count++;              /* Count for limit and ratelimit */
     was_rej_mail = TRUE;               /* Reset if accepted */
 
     smtp_mailcmd_count++;              /* Count for limit and ratelimit */
     was_rej_mail = TRUE;               /* Reset if accepted */
 
@@ -3154,6 +3253,7 @@ while (done <= 0)
     extracted address. */
 
     case RCPT_CMD:
     extracted address. */
 
     case RCPT_CMD:
+    HAD(SCH_RCPT);
     rcpt_count++;
     was_rcpt = TRUE;
 
     rcpt_count++;
     was_rcpt = TRUE;
 
@@ -3341,6 +3441,7 @@ while (done <= 0)
     because it is the same whether pipelining is in use or not. */
 
     case DATA_CMD:
     because it is the same whether pipelining is in use or not. */
 
     case DATA_CMD:
+    HAD(SCH_DATA);
     if (!discarded && recipients_count <= 0)
       {
       if (pipelining_advertised && last_was_rcpt)
     if (!discarded && recipients_count <= 0)
       {
       if (pipelining_advertised && last_was_rcpt)
@@ -3385,6 +3486,7 @@ while (done <= 0)
 
 
     case VRFY_CMD:
 
 
     case VRFY_CMD:
+    HAD(SCH_VRFY);
     rc = acl_check(ACL_WHERE_VRFY, NULL, acl_smtp_vrfy, &user_msg, &log_msg);
     if (rc != OK)
       done = smtp_handle_acl_fail(ACL_WHERE_VRFY, rc, user_msg, log_msg);
     rc = acl_check(ACL_WHERE_VRFY, NULL, acl_smtp_vrfy, &user_msg, &log_msg);
     if (rc != OK)
       done = smtp_handle_acl_fail(ACL_WHERE_VRFY, rc, user_msg, log_msg);
@@ -3432,6 +3534,7 @@ while (done <= 0)
 
 
     case EXPN_CMD:
 
 
     case EXPN_CMD:
+    HAD(SCH_EXPN);
     rc = acl_check(ACL_WHERE_EXPN, NULL, acl_smtp_expn, &user_msg, &log_msg);
     if (rc != OK)
       done = smtp_handle_acl_fail(ACL_WHERE_EXPN, rc, user_msg, log_msg);
     rc = acl_check(ACL_WHERE_EXPN, NULL, acl_smtp_expn, &user_msg, &log_msg);
     if (rc != OK)
       done = smtp_handle_acl_fail(ACL_WHERE_EXPN, rc, user_msg, log_msg);
@@ -3451,6 +3554,7 @@ while (done <= 0)
     #ifdef SUPPORT_TLS
 
     case STARTTLS_CMD:
     #ifdef SUPPORT_TLS
 
     case STARTTLS_CMD:
+    HAD(SCH_STARTTLS);
     if (!tls_advertised)
       {
       done = synprot_error(L_smtp_protocol_error, 503, NULL,
     if (!tls_advertised)
       {
       done = synprot_error(L_smtp_protocol_error, 503, NULL,
@@ -3564,6 +3668,7 @@ while (done <= 0)
     message. */
 
     case QUIT_CMD:
     message. */
 
     case QUIT_CMD:
+    HAD(SCH_QUIT);
     incomplete_transaction_log(US"QUIT");
 
     if (acl_smtp_quit != NULL)
     incomplete_transaction_log(US"QUIT");
 
     if (acl_smtp_quit != NULL)
@@ -3590,6 +3695,7 @@ while (done <= 0)
 
 
     case RSET_CMD:
 
 
     case RSET_CMD:
+    HAD(SCH_RSET);
     incomplete_transaction_log(US"RSET");
     smtp_reset(reset_point);
     toomany = FALSE;
     incomplete_transaction_log(US"RSET");
     smtp_reset(reset_point);
     toomany = FALSE;
@@ -3599,6 +3705,7 @@ while (done <= 0)
 
 
     case NOOP_CMD:
 
 
     case NOOP_CMD:
+    HAD(SCH_NOOP);
     smtp_printf("250 OK\r\n");
     break;
 
     smtp_printf("250 OK\r\n");
     break;
 
@@ -3608,6 +3715,7 @@ while (done <= 0)
     permitted hosts. */
 
     case HELP_CMD:
     permitted hosts. */
 
     case HELP_CMD:
+    HAD(SCH_HELP);
     smtp_printf("214-Commands supported:\r\n");
       {
       uschar buffer[256];
     smtp_printf("214-Commands supported:\r\n");
       {
       uschar buffer[256];
@@ -3649,6 +3757,7 @@ while (done <= 0)
 
 
     case ETRN_CMD:
 
 
     case ETRN_CMD:
+    HAD(SCH_ETRN);
     if (sender_address != NULL)
       {
       done = synprot_error(L_smtp_protocol_error, 503, NULL,
     if (sender_address != NULL)
       {
       done = synprot_error(L_smtp_protocol_error, 503, NULL,