SPDX: Mass-update to GPL-2.0-or-later
[exim.git] / src / src / receive.c
index c2b313c637e5c7be54d6a0e5c25538afba14abbe..9bf834aafdc107c11f4744f7b34dede79ddc92cf 100644 (file)
@@ -2,9 +2,10 @@
 *     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 */
 /* See the file NOTICE for conditions of use and distribution. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 
 /* Code for receiving a message and setting up spool files. */
 
@@ -44,42 +45,71 @@ receive_getc initially. They just call the standard functions, passing stdin as
 the file. (When SMTP input is occurring, different functions are used by
 changing the pointer variables.) */
 
-int
-stdin_getc(unsigned lim)
-{
-int c = getc(stdin);
+uschar stdin_buf[4096];
+uschar * stdin_inptr = stdin_buf;
+uschar * stdin_inend = stdin_buf;
 
-if (had_data_timeout)
-  {
-  fprintf(stderr, "exim: timed out while reading - message abandoned\n");
-  log_write(L_lost_incoming_connection,
-            LOG_MAIN, "timed out while reading local message");
-  receive_bomb_out(US"data-timeout", NULL);   /* Does not return */
-  }
-if (had_data_sigint)
+static BOOL
+stdin_refill(void)
+{
+size_t rc = fread(stdin_buf, 1, sizeof(stdin_buf), stdin);
+if (rc <= 0)
   {
-  if (filter_test == FTEST_NONE)
+  if (had_data_timeout)
+    {
+    fprintf(stderr, "exim: timed out while reading - message abandoned\n");
+    log_write(L_lost_incoming_connection,
+             LOG_MAIN, "timed out while reading local message");
+    receive_bomb_out(US"data-timeout", NULL);   /* Does not return */
+    }
+  if (had_data_sigint)
     {
-    fprintf(stderr, "\nexim: %s received - message abandoned\n",
-      had_data_sigint == SIGTERM ? "SIGTERM" : "SIGINT");
-    log_write(0, LOG_MAIN, "%s received while reading local message",
-      had_data_sigint == SIGTERM ? "SIGTERM" : "SIGINT");
+    if (filter_test == FTEST_NONE)
+      {
+      fprintf(stderr, "\nexim: %s received - message abandoned\n",
+       had_data_sigint == SIGTERM ? "SIGTERM" : "SIGINT");
+      log_write(0, LOG_MAIN, "%s received while reading local message",
+       had_data_sigint == SIGTERM ? "SIGTERM" : "SIGINT");
+      }
+    receive_bomb_out(US"signal-exit", NULL);    /* Does not return */
     }
-  receive_bomb_out(US"signal-exit", NULL);    /* Does not return */
+  return FALSE;
   }
-return c;
+stdin_inend = stdin_buf + rc;
+stdin_inptr = stdin_buf;
+return TRUE;
+}
+
+int
+stdin_getc(unsigned lim)
+{
+if (stdin_inptr >= stdin_inend)
+  if (!stdin_refill())
+      return EOF;
+return *stdin_inptr++;
+}
+
+
+BOOL
+stdin_hasc(void)
+{
+return stdin_inptr < stdin_inend;
 }
 
 int
 stdin_ungetc(int c)
 {
-return ungetc(c, stdin);
+if (stdin_inptr <= stdin_buf)
+  log_write(0, LOG_MAIN|LOG_PANIC_DIE, "buffer underflow in stdin_ungetc");
+
+*--stdin_inptr = c;
+return c;
 }
 
 int
 stdin_feof(void)
 {
-return feof(stdin);
+return stdin_hasc() ? FALSE : feof(stdin);
 }
 
 int
@@ -498,7 +528,7 @@ if (recipients_count >= recipients_list_max)
     }
 
   recipients_list_max = recipients_list_max ? 2*recipients_list_max : 50;
-  recipients_list = store_get(recipients_list_max * sizeof(recipient_item), FALSE);
+  recipients_list = store_get(recipients_list_max * sizeof(recipient_item), GET_UNTAINTED);
   if (oldlist)
     memcpy(recipients_list, oldlist, oldmax * sizeof(recipient_item));
   }
@@ -588,19 +618,15 @@ the file copy. */
 static void
 log_close_chk(void)
 {
-if (!receive_timeout)
+if (!receive_timeout && !receive_hasc())
   {
   struct timeval t;
   timesince(&t, &received_time);
   if (t.tv_sec > 30*60)
     mainlog_close();
   else
-    {
-    fd_set r;
-    FD_ZERO(&r); FD_SET(0, &r);
-    t.tv_sec = 30*60 - t.tv_sec; t.tv_usec = 0;
-    if (select(1, &r, NULL, NULL, &t) == 0) mainlog_close();
-    }
+    if (poll_one_fd(0, POLLIN, (30*60 - t.tv_sec) * 1000) == 0)
+      mainlog_close();
   }
 }
 
@@ -654,11 +680,6 @@ if (!f.dot_ends)
   {
   int last_ch = '\n';
 
-/*XXX we do a gettimeofday before checking for every received char,
-which is hardly clever.  The function-indirection doesn't help, but
-an additional function to check for nonempty read buffer would help.
-See stdin_getc() / smtp_getc() / tls_getc() / bdat_getc(). */
-
   for ( ;
        log_close_chk(), (ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) != EOF;
        last_ch = ch)
@@ -947,7 +968,7 @@ Returns:    One of the END_xxx values indicating why it stopped reading
 */
 
 static int
-read_message_bdat_smtp(FILE *fout)
+read_message_bdat_smtp(FILE * fout)
 {
 int linelength = 0, ch;
 enum CH_STATE ch_state = LF_SEEN;
@@ -1053,7 +1074,7 @@ for(;;)
 }
 
 static int
-read_message_bdat_smtp_wire(FILE *fout)
+read_message_bdat_smtp_wire(FILE * fout)
 {
 int ch;
 
@@ -1663,10 +1684,9 @@ int  process_info_len = Ustrlen(process_info);
 int  error_rc = error_handling == ERRORS_SENDER
        ? errors_sender_rc : EXIT_FAILURE;
 int  header_size = 256;
-int  start, end, domain;
-int  id_resolution = 0;
 int  had_zero = 0;
 int  prevlines_length = 0;
+const int id_resolution = BASE_62 == 62 ? 5000 : 10000;
 
 int ptr = 0;
 
@@ -1720,6 +1740,10 @@ BOOL msgid_header_newly_created = FALSE;
 uschar *timestamp;
 int tslen;
 
+/* Time of creation of message_id */
+
+static struct timeval message_id_tv = { 0, 0 };
+
 
 /* Release any open files that might have been cached while preparing to
 accept the message - e.g. by verifying addresses - because reading a message
@@ -1737,17 +1761,18 @@ if (extract_recip || !smtp_input)
 header. Temporarily mark it as "old", i.e. not to be used. We keep header_last
 pointing to the end of the chain to make adding headers simple. */
 
-received_header = header_list = header_last = store_get(sizeof(header_line), FALSE);
+received_header = header_list = header_last = store_get(sizeof(header_line), GET_UNTAINTED);
 header_list->next = NULL;
 header_list->type = htype_old;
 header_list->text = NULL;
 header_list->slen = 0;
 
-/* Control block for the next header to be read. */
+/* Control block for the next header to be read.
+The data comes from the message, so is tainted. */
 
 reset_point = store_mark();
-next = store_get(sizeof(header_line), FALSE);  /* not tainted */
-next->text = store_get(header_size, TRUE);     /* tainted */
+next = store_get(sizeof(header_line), GET_UNTAINTED);
+next->text = store_get(header_size, GET_TAINTED);
 
 /* Initialize message id to be null (indicating no message read), and the
 header names list to be the normal list. Indicate there is no data file open
@@ -1786,13 +1811,32 @@ if (smtp_input && !smtp_batched_input && !f.dkim_disable_verify)
 if (sender_host_address) dmarc_init(); /* initialize libopendmarc */
 #endif
 
+/* In SMTP sessions we may receive several messages in one connection. Before
+each subsequent one, we wait for the clock to tick at the level of message-id
+granularity.
+This is so that the combination of time+pid is unique, even on systems where the
+pid can be re-used within our time interval. We can't shorten the interval
+without re-designing the message-id. See comments above where the message id is
+created. This is Something For The Future.
+Do this wait any time we have previously created a message-id, even if we
+rejected the message.  This gives unique IDs for logging done by ACLs.
+The initial timestamp must have been obtained via exim_gettime() to avoid
+issues on Linux with suspend/resume. */
+
+if (message_id_tv.tv_sec)
+  {
+  message_id_tv.tv_usec = (message_id_tv.tv_usec/id_resolution) * id_resolution;
+  exim_wait_tick(&message_id_tv, id_resolution);
+  }
+
 /* Remember the time of reception. Exim uses time+pid for uniqueness of message
 ids, and fractions of a second are required. See the comments that precede the
 message id creation below.
 We use a routine that if possible uses a monotonic clock, and can be used again
 after reception for the tick-wait even under the Linux non-Posix behaviour. */
 
-exim_gettime(&message_id_tv);
+else
+  exim_gettime(&message_id_tv);
 
 /* For other uses of the received time we can operate with granularity of one
 second, and for that we use the global variable received_time. This is for
@@ -1846,12 +1890,15 @@ for (;;)
   /* If we hit EOF on a SMTP connection, it's an error, since incoming
   SMTP must have a correct "." terminator. */
 
-  if (ch == EOF && smtp_input /* && !smtp_batched_input */)
-    {
-    smtp_reply = handle_lost_connection(US" (header)");
-    smtp_yield = FALSE;
-    goto TIDYUP;                       /* Skip to end of function */
-    }
+  if (smtp_input /* && !smtp_batched_input */)
+    if (ch == EOF)
+      {
+      smtp_reply = handle_lost_connection(US" (header)");
+      smtp_yield = FALSE;
+      goto TIDYUP;                       /* Skip to end of function */
+      }
+    else if (ch == ERR)
+      goto TIDYUP;
 
   /* See if we are at the current header's size limit - there must be at least
   four bytes left. This allows for the new character plus a zero, plus two for
@@ -1875,10 +1922,8 @@ for (;;)
       goto OVERSIZE;
     header_size *= 2;
 
-    /* The data came from the message, so is tainted. */
-
-    if (!store_extend(next->text, TRUE, oldsize, header_size))
-      next->text = store_newblock(next->text, TRUE, header_size, ptr);
+    if (!store_extend(next->text, oldsize, header_size))
+      next->text = store_newblock(next->text, header_size, ptr);
     }
 
   /* Cope with receiving a binary zero. There is dispute about whether
@@ -1893,7 +1938,7 @@ for (;;)
   those from data files use just LF. Treat LF in local SMTP input as a
   terminator too. Treat EOF as a line terminator always. */
 
-  if (ch == EOF) goto EOL;
+  if (ch < 0) goto EOL;
 
   /* FUDGE: There are sites out there that don't send CRs before their LFs, and
   other MTAs accept this. We are therefore forced into this "liberalisation"
@@ -1918,7 +1963,7 @@ for (;;)
   prevent further reading), and break out of the loop, having freed the
   empty header, and set next = NULL to indicate no data line. */
 
-  if (ptr == 0 && ch == '.' && f.dot_ends)
+  if (f.dot_ends && ptr == 0 && ch == '.')
     {
     ch = (receive_getc)(GETC_BUFFER_UNLIMITED);
     if (ch == '\r')
@@ -1926,7 +1971,7 @@ for (;;)
       ch = (receive_getc)(GETC_BUFFER_UNLIMITED);
       if (ch != '\n')
         {
-        receive_ungetc(ch);
+       if (ch >= 0) receive_ungetc(ch);
         ch = '\r';              /* Revert to CR */
         }
       }
@@ -1964,7 +2009,7 @@ for (;;)
     /* Otherwise, put back the character after CR, and turn the bare CR
     into LF SP. */
 
-    ch = (receive_ungetc)(ch);
+    if (ch >= 0) (receive_ungetc)(ch);
     next->text[ptr++] = '\n';
     message_size++;
     ch = ' ';
@@ -2048,7 +2093,7 @@ OVERSIZE:
   whitespace character. If it is, we have a continuation of this header line.
   There is always space for at least one character at this point. */
 
-  if (ch != EOF)
+  if (ch >= 0)
     {
     int nextch = (receive_getc)(GETC_BUFFER_UNLIMITED);
     if (nextch == ' ' || nextch == '\t')
@@ -2058,8 +2103,9 @@ OVERSIZE:
        goto OVERSIZE;
       continue;                      /* Iterate the loop */
       }
-    else if (nextch != EOF) (receive_ungetc)(nextch);   /* For next time */
-    else ch = EOF;                   /* Cause main loop to exit at end */
+    else if (nextch >= 0)      /* not EOF, ERR etc */
+      (receive_ungetc)(nextch);   /* For next time */
+    else ch = nextch;                   /* Cause main loop to exit at end */
     }
 
   /* We have got to the real line end. Terminate the string and release store
@@ -2161,7 +2207,7 @@ OVERSIZE:
 
   else
     {
-    uschar *p = next->text;
+    uschar * p = next->text;
 
     /* If not a valid header line, break from the header reading loop, leaving
     next != NULL, indicating that it holds the first line of the body. */
@@ -2259,16 +2305,22 @@ OVERSIZE:
     }
 
   /* The line has been handled. If we have hit EOF, break out of the loop,
-  indicating no pending data line. */
+  indicating no pending data line and no more data for the message */
 
-  if (ch == EOF) { next = NULL; break; }
+  if (ch < 0)
+    {
+    next = NULL;
+    if (ch == EOF)     message_ended = END_DOT;
+    else if (ch == ERR) message_ended = END_PROTOCOL;
+    break;
+    }
 
   /* Set up for the next header */
 
   reset_point = store_mark();
   header_size = 256;
-  next = store_get(sizeof(header_line), FALSE);
-  next->text = store_get(header_size, TRUE);
+  next = store_get(sizeof(header_line), GET_UNTAINTED);
+  next->text = store_get(header_size, GET_TAINTED);
   ptr = 0;
   had_zero = 0;
   prevlines_length = 0;
@@ -2291,14 +2343,21 @@ DEBUG(D_receive)
 /* End of file on any SMTP connection is an error. If an incoming SMTP call
 is dropped immediately after valid headers, the next thing we will see is EOF.
 We must test for this specially, as further down the reading of the data is
-skipped if already at EOF. */
+skipped if already at EOF.
+In CHUNKING mode, a protocol error makes us give up on the message. */
 
-if (smtp_input && (receive_feof)())
-  {
-  smtp_reply = handle_lost_connection(US" (after header)");
-  smtp_yield = FALSE;
-  goto TIDYUP;                       /* Skip to end of function */
-  }
+if (smtp_input)
+  if ((receive_feof)())
+    {
+    smtp_reply = handle_lost_connection(US" (after header)");
+    smtp_yield = FALSE;
+    goto TIDYUP;                       /* Skip to end of function */
+    }
+  else if (message_ended == END_PROTOCOL)
+    {
+    smtp_reply = US"";                 /* no reply needed */
+    goto TIDYUP;
+    }
 
 /* If this is a filter test run and no headers were read, output a warning
 in case there is a mistake in the test message. */
@@ -2552,7 +2611,7 @@ if (extract_recip)
         white space that follows the newline must not be removed - it is part
         of the header. */
 
-        pp = recipient = store_get(ss - s + 1, is_tainted(s));
+        pp = recipient = store_get(ss - s + 1, s);
         for (uschar * p = s; p < ss; p++) if (*p != '\n') *pp++ = *p;
         *pp = 0;
 
@@ -2584,7 +2643,7 @@ if (extract_recip)
         if (!recipient && Ustrcmp(errmess, "empty address") != 0)
           {
           int len = Ustrlen(s);
-          error_block *b = store_get(sizeof(error_block), FALSE);
+          error_block * b = store_get(sizeof(error_block), GET_UNTAINTED);
           while (len > 0 && isspace(s[len-1])) len--;
           b->next = NULL;
           b->text1 = string_printing(string_copyn(s, len));
@@ -2681,28 +2740,20 @@ message_id[6] = '-';
 Ustrncpy(message_id + 7, string_base62((long int)getpid()), 6);
 
 /* Deal with the case where the host number is set. The value of the number was
-checked when it was read, to ensure it isn't too big. The timing granularity is
-left in id_resolution so that an appropriate wait can be done after receiving
-the message, if necessary (we hope it won't be). */
+checked when it was read, to ensure it isn't too big. */
 
 if (host_number_string)
-  {
-  id_resolution = BASE_62 == 62 ? 5000 : 10000;
   sprintf(CS(message_id + MESSAGE_ID_LENGTH - 3), "-%2s",
     string_base62((long int)(
       host_number * (1000000/id_resolution) +
         message_id_tv.tv_usec/id_resolution)) + 4);
-  }
 
 /* Host number not set: final field is just the fractional time at an
 appropriate resolution. */
 
 else
-  {
-  id_resolution = BASE_62 == 62 ? 500 : 1000;
   sprintf(CS(message_id + MESSAGE_ID_LENGTH - 3), "-%2s",
     string_base62((long int)(message_id_tv.tv_usec/id_resolution)) + 4);
-  }
 
 /* Add the current message id onto the current process info string if
 it will fit. */
@@ -2791,7 +2842,7 @@ function may mess with the real recipients. */
 
 if (LOGGING(received_recipients))
   {
-  raw_recipients = store_get(recipients_count * sizeof(uschar *), FALSE);
+  raw_recipients = store_get(recipients_count * sizeof(uschar *), GET_UNTAINTED);
   for (int i = 0; i < recipients_count; i++)
     raw_recipients[i] = string_copy(recipients_list[i].address);
   raw_recipients_count = recipients_count;
@@ -2801,10 +2852,13 @@ if (LOGGING(received_recipients))
 recipients will get here only if the conditions were right (allow_unqualified_
 recipient is TRUE). */
 
+DEBUG(D_rewrite)
+  { debug_printf_indent("qualify & rewrite recipients list\n"); acl_level++; }
 for (int i = 0; i < recipients_count; i++)
   recipients_list[i].address = /* deconst ok as src was not cont */
     US rewrite_address(recipients_list[i].address, TRUE, TRUE,
       global_rewrite_rules, rewrite_existflags);
+DEBUG(D_rewrite) acl_level--;
 
 /* If there is no From: header, generate one for local (without
 suppress_local_fixups) or submission_mode messages. If there is no sender
@@ -2976,6 +3030,8 @@ if (  from_header
 /* If there are any rewriting rules, apply them to the sender address, unless
 it has already been rewritten as part of verification for SMTP input. */
 
+DEBUG(D_rewrite)
+  { debug_printf("global rewrite rules\n"); acl_level++; }
 if (global_rewrite_rules && !sender_address_unrewritten && *sender_address)
   {
   /* deconst ok as src was not const */
@@ -2984,6 +3040,7 @@ if (global_rewrite_rules && !sender_address_unrewritten && *sender_address)
   DEBUG(D_receive|D_rewrite)
     debug_printf("rewritten sender = %s\n", sender_address);
   }
+DEBUG(D_rewrite) acl_level--;
 
 
 /* The headers must be run through rewrite_header(), because it ensures that
@@ -3000,12 +3057,13 @@ We start at the second header, skipping our own Received:. This rewriting is
 documented as happening *after* recipient addresses are taken from the headers
 by the -t command line option. An added Sender: gets rewritten here. */
 
-for (header_line * h = header_list->next; h; h = h->next)
-  {
-  header_line *newh = rewrite_header(h, NULL, NULL, global_rewrite_rules,
-    rewrite_existflags, TRUE);
-  if (newh) h = newh;
-  }
+DEBUG(D_rewrite)
+  { debug_printf("rewrite headers\n"); acl_level++; }
+for (header_line * h = header_list->next, * newh; h; h = h->next)
+  if ((newh = rewrite_header(h, NULL, NULL, global_rewrite_rules,
+                             rewrite_existflags, TRUE)))
+    h = newh;
+DEBUG(D_rewrite) acl_level--;
 
 
 /* An RFC 822 (sic) message is not legal unless it has at least one of "to",
@@ -4084,6 +4142,8 @@ if (  LOGGING(msg_id) && msgid_header
   uschar * old_id;
   BOOL save_allow_domain_literals = allow_domain_literals;
   allow_domain_literals = TRUE;
+  int start, end, domain;
+
   old_id = parse_extract_address(Ustrchr(msgid_header->text, ':') + 1,
     &errmsg, &start, &end, &domain, FALSE);
   allow_domain_literals = save_allow_domain_literals;
@@ -4176,7 +4236,7 @@ f.receive_call_bombout = TRUE;
 /* Before sending an SMTP response in a TCP/IP session, we check to see if the
 connection has gone away. This can only be done if there is no unconsumed input
 waiting in the local input buffer. We can test for this by calling
-receive_smtp_buffered(). RFC 2920 (pipelining) explicitly allows for additional
+receive_hasc(). RFC 2920 (pipelining) explicitly allows for additional
 input to be sent following the final dot, so the presence of following input is
 not an error.
 
@@ -4191,18 +4251,14 @@ Of course, since TCP/IP is asynchronous, there is always a chance that the
 connection will vanish between the time of this test and the sending of the
 response, but the chance of this happening should be small. */
 
-if (smtp_input && sender_host_address && !f.sender_host_notsocket &&
-    !receive_smtp_buffered())
+if (  smtp_input && sender_host_address && !f.sender_host_notsocket
+   && !receive_hasc())
   {
-  struct timeval tv = {.tv_sec = 0, .tv_usec = 0};
-  fd_set select_check;
-  FD_ZERO(&select_check);
-  FD_SET(fileno(smtp_in), &select_check);
-
-  if (select(fileno(smtp_in) + 1, &select_check, NULL, NULL, &tv) != 0)
+  if (poll_one_fd(fileno(smtp_in), POLLIN, 0) != 0)
     {
     int c = (receive_getc)(GETC_BUFFER_UNLIMITED);
-    if (c != EOF) (receive_ungetc)(c); else
+    if (c != EOF) (receive_ungetc)(c);
+    else
       {
       smtp_notquit_exit(US"connection-lost", NULL, NULL);
       smtp_reply = US"";    /* No attempt to send a response */
@@ -4323,26 +4379,6 @@ then we can think about properly declaring the message not-received. */
 
 
 TIDYUP:
-/* In SMTP sessions we may receive several messages in one connection. After
-each one, we wait for the clock to tick at the level of message-id granularity.
-This is so that the combination of time+pid is unique, even on systems where the
-pid can be re-used within our time interval. We can't shorten the interval
-without re-designing the message-id. See comments above where the message id is
-created. This is Something For The Future.
-Do this wait any time we have created a message-id, even if we rejected the
-message.  This gives unique IDs for logging done by ACLs.
-The initial timestamp must have been obtained via exim_gettime() to avoid
-issues on Linux with suspend/resume.
-It would be Nicer to only pause before a follow-on message. */
-
-if (id_resolution != 0)
-  {
-  message_id_tv.tv_usec = (message_id_tv.tv_usec/id_resolution) * id_resolution;
-  exim_wait_tick(&message_id_tv, id_resolution);
-  id_resolution = 0;
-  }
-
-
 process_info[process_info_len] = 0;                    /* Remove message id */
 if (spool_data_file && cutthrough_done == NOT_TRIED)
   {
@@ -4398,12 +4434,12 @@ if (smtp_input)
        the socket. */
 
         smtp_printf("250- %u byte chunk, total %d\r\n250 OK id=%s\r\n",
-           receive_smtp_buffered(),
+           receive_hasc(),
            chunking_datasize, message_size+message_linecount, message_id);
        chunking_state = CHUNKING_OFFERED;
        }
       else
-        smtp_printf("250 OK id=%s\r\n", receive_smtp_buffered(), message_id);
+        smtp_printf("250 OK id=%s\r\n", receive_hasc(), message_id);
 
       if (host_checking)
         fprintf(stdout,