* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2017 */
/* See the file NOTICE for conditions of use and distribution. */
/* Code for receiving a message and setting up spool files. */
extern int dcc_ok;
#endif
+#ifdef EXPERIMENTAL_DMARC
+# include "dmarc.h"
+#endif /* EXPERIMENTAL_DMARC */
+
/*************************************************
* Local static variables *
*************************************************/
static FILE *data_file = NULL;
static int data_fd = -1;
-static uschar spool_name[256];
+static uschar *spool_name = US"";
+enum CH_STATE {LF_SEEN, MID_LINE, CR_SEEN};
/*************************************************
changing the pointer variables.) */
int
-stdin_getc(void)
+stdin_getc(unsigned lim)
{
return getc(stdin);
}
{
uschar *qnewsender;
if (trusted_caller) return TRUE;
-if (newsender == NULL || untrusted_set_sender == NULL) return FALSE;
-qnewsender = (Ustrchr(newsender, '@') != NULL)?
- newsender : string_sprintf("%s@%s", newsender, qualify_domain_sender);
-return
- match_address_list(qnewsender, TRUE, TRUE, &untrusted_set_sender, NULL, -1,
- 0, NULL) == OK;
+if (!newsender || !untrusted_set_sender) return FALSE;
+qnewsender = Ustrchr(newsender, '@')
+ ? newsender : string_sprintf("%s@%s", newsender, qualify_domain_sender);
+return match_address_list_basic(qnewsender, CUSS &untrusted_set_sender, 0) == OK;
}
{
#ifdef HAVE_STATFS
struct STATVFS statbuf;
+struct stat dummy;
uschar *path;
uschar *name;
uschar buffer[1024];
else
{
int sep = ':'; /* Not variable - outside scripts use */
- uschar *p = log_file_path;
+ const uschar *p = log_file_path;
name = US"log";
/* An empty log_file_path means "use the default". This is the same as an
empty item in a list. */
if (*p == 0) p = US":";
- while ((path = string_nextinlist(&p, &sep, buffer, sizeof(buffer))) != NULL)
- {
- if (Ustrcmp(path, "syslog") != 0) break;
- }
+ while ((path = string_nextinlist(&p, &sep, buffer, sizeof(buffer))))
+ if (Ustrcmp(path, "syslog") != 0)
+ break;
if (path == NULL) /* No log files */
{
memset(&statbuf, 0, sizeof(statbuf));
if (STATVFS(CS path, &statbuf) != 0)
- {
- log_write(0, LOG_MAIN|LOG_PANIC, "cannot accept message: failed to stat "
- "%s directory %s: %s", name, spool_directory, strerror(errno));
- smtp_closedown(US"spool or log directory problem");
- exim_exit(EXIT_FAILURE);
- }
+ if (stat(CS path, &dummy) == -1 && errno == ENOENT)
+ { /* Can happen on first run after installation */
+ *inodeptr = -1;
+ return -1;
+ }
+ else
+ {
+ log_write(0, LOG_MAIN|LOG_PANIC, "cannot accept message: failed to stat "
+ "%s directory %s: %s", name, path, strerror(errno));
+ smtp_closedown(US"spool or log directory problem");
+ exim_exit(EXIT_FAILURE);
+ }
*inodeptr = (statbuf.F_FILES > 0)? statbuf.F_FAVAIL : -1;
return (int)(((double)statbuf.F_BAVAIL * (double)statbuf.F_FRSIZE)/1024.0);
+#else
/* Unable to find partition sizes in this environment. */
-#else
*inodeptr = -1;
return -1;
#endif
void
receive_bomb_out(uschar *reason, uschar *msg)
{
+ static BOOL already_bombing_out;
+/* The smtp_notquit_exit() below can call ACLs which can trigger recursive
+timeouts, if someone has something slow in their quit ACL. Since the only
+things we should be doing are to close down cleanly ASAP, on the second
+pass we also close down stuff that might be opened again, before bypassing
+the ACL call and exiting. */
+
/* If spool_name is set, it contains the name of the data file that is being
written. Unlink it before closing so that it cannot be picked up by a delivery
process. Ensure that any header file is also removed. */
-if (spool_name[0] != 0)
+if (spool_name[0] != '\0')
{
Uunlink(spool_name);
spool_name[Ustrlen(spool_name) - 1] = 'H';
Uunlink(spool_name);
+ spool_name[0] = '\0';
}
/* Now close the file if it is open, either as a fd or a stream. */
-if (data_file != NULL) (void)fclose(data_file);
- else if (data_fd >= 0) (void)close(data_fd);
+if (data_file != NULL)
+ {
+ (void)fclose(data_file);
+ data_file = NULL;
+} else if (data_fd >= 0) {
+ (void)close(data_fd);
+ data_fd = -1;
+ }
/* Attempt to close down an SMTP connection tidily. For non-batched SMTP, call
smtp_notquit_exit(), which runs the NOTQUIT ACL, if present, and handles the
SMTP response. */
-if (smtp_input)
+if (!already_bombing_out)
{
- if (smtp_batched_input)
- moan_smtp_batch(NULL, "421 %s - message abandoned", msg); /* No return */
- smtp_notquit_exit(reason, US"421", US"%s %s - closing connection.",
- smtp_active_hostname, msg);
+ already_bombing_out = TRUE;
+ if (smtp_input)
+ {
+ if (smtp_batched_input)
+ moan_smtp_batch(NULL, "421 %s - message abandoned", msg); /* No return */
+ smtp_notquit_exit(reason, US"421", US"%s %s - closing connection.",
+ smtp_active_hostname, msg);
+ }
}
/* Exit from the program (non-BSMTP cases) */
/* reset optin string pointer for next recipient */
bmi_current_optin = NULL;
#endif
+recipients_list[recipients_count].orcpt = NULL;
+recipients_list[recipients_count].dsn_flags = 0;
recipients_list[recipients_count++].errors_to = NULL;
}
+/*************************************************
+* Send user response message *
+*************************************************/
+
+/* This function is passed a default response code and a user message. It calls
+smtp_message_code() to check and possibly modify the response code, and then
+calls smtp_respond() to transmit the response. I put this into a function
+just to avoid a lot of repetition.
+
+Arguments:
+ code the response code
+ user_msg the user message
+
+Returns: nothing
+*/
+
+#ifndef DISABLE_PRDR
+static void
+smtp_user_msg(uschar *code, uschar *user_msg)
+{
+int len = 3;
+smtp_message_code(&code, &len, &user_msg, NULL, TRUE);
+smtp_respond(code, len, TRUE, user_msg);
+}
+#endif
+
+
+
+
+
/*************************************************
* Remove a recipient from the list *
*************************************************/
{
register int last_ch = '\n';
- for (; (ch = (receive_getc)()) != EOF; last_ch = ch)
+ for (; (ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) != EOF; last_ch = ch)
{
if (ch == 0) body_zerocount++;
if (last_ch == '\r' && ch != '\n')
ch_state = 1;
-while ((ch = (receive_getc)()) != EOF)
+while ((ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) != EOF)
{
if (ch == 0) body_zerocount++;
switch (ch_state)
case 1: /* After written "\n" */
if (ch == '.') { ch_state = 3; continue; }
- if (ch != '\n') ch_state = 0; else linelength = -1;
+ if (ch == '\r') { ch_state = 2; continue; }
+ if (ch == '\n') { body_linecount++; linelength = -1; }
+ else ch_state = 0;
break;
case 2:
{
int ch_state = 0;
int ch;
-register int linelength = 0;
+int linelength = 0;
-while ((ch = (receive_getc)()) != EOF)
+while ((ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) != EOF)
{
if (ch == 0) body_zerocount++;
switch (ch_state)
{
message_size++;
if (fout != NULL && fputc('\n', fout) == EOF) return END_WERROR;
- (void) cutthrough_put_nl();
+ cutthrough_data_put_nl();
if (ch != '\r') ch_state = 1; else continue;
}
break;
ch_state = 4;
continue;
}
- ch_state = 1; /* The dot itself is removed */
+ /* The dot was removed at state 3. For a doubled dot, here, reinstate
+ it to cutthrough. The current ch, dot or not, is passed both to cutthrough
+ and to file below. */
+ if (ch == '.')
+ {
+ uschar c= ch;
+ cutthrough_data_puts(&c, 1);
+ }
+ ch_state = 1;
break;
case 4: /* After [CR] LF . CR */
message_size++;
body_linecount++;
if (fout != NULL && fputc('\n', fout) == EOF) return END_WERROR;
- (void) cutthrough_put_nl();
+ cutthrough_data_put_nl();
if (ch == '\r')
{
ch_state = 2;
message_size++;
linelength++;
- if (fout != NULL)
+ if (fout)
{
if (fputc(ch, fout) == EOF) return END_WERROR;
if (message_size > thismessage_size_limit) return END_SIZE;
}
if(ch == '\n')
- (void) cutthrough_put_nl();
+ cutthrough_data_put_nl();
else
{
- uschar c= ch;
- (void) cutthrough_puts(&c, 1);
+ uschar c = ch;
+ cutthrough_data_puts(&c, 1);
}
}
+/* Variant of the above read_message_data_smtp() specialised for RFC 3030
+CHUNKING. Accept input lines separated by either CRLF or CR or LF and write
+LF-delimited spoolfile. Until we have wireformat spoolfiles, we need the
+body_linecount accounting for proper re-expansion for the wire, so use
+a cut-down version of the state-machine above; we don't need to do leading-dot
+detection and unstuffing.
+
+Arguments:
+ fout a FILE to which to write the message; NULL if skipping;
+ must be open for both writing and reading.
+
+Returns: One of the END_xxx values indicating why it stopped reading
+*/
+
+static int
+read_message_bdat_smtp(FILE *fout)
+{
+int linelength = 0, ch;
+enum CH_STATE ch_state = LF_SEEN;
+BOOL fix_nl = FALSE;
+
+for(;;)
+ {
+ switch ((ch = bdat_getc(GETC_BUFFER_UNLIMITED)))
+ {
+ case EOF: return END_EOF;
+ case ERR: return END_PROTOCOL;
+ case EOD:
+ /* Nothing to get from the sender anymore. We check the last
+ character written to the spool.
+
+ RFC 3030 states, that BDAT chunks are normal text, terminated by CRLF.
+ If we would be strict, we would refuse such broken messages.
+ But we are liberal, so we fix it. It would be easy just to append
+ the "\n" to the spool.
+
+ But there are some more things (line counting, message size calculation and such),
+ that would need to be duplicated here. So we simply do some ungetc
+ trickery.
+ */
+ if (fout)
+ {
+ if (fseek(fout, -1, SEEK_CUR) < 0) return END_PROTOCOL;
+ if (fgetc(fout) == '\n') return END_DOT;
+ }
+
+ if (linelength == -1) /* \r already seen (see below) */
+ {
+ DEBUG(D_receive) debug_printf("Add missing LF\n");
+ bdat_ungetc('\n');
+ continue;
+ }
+ DEBUG(D_receive) debug_printf("Add missing CRLF\n");
+ bdat_ungetc('\r'); /* not even \r was seen */
+ fix_nl = TRUE;
+
+ continue;
+ case '\0': body_zerocount++; break;
+ }
+ switch (ch_state)
+ {
+ case LF_SEEN: /* After LF or CRLF */
+ ch_state = MID_LINE;
+ /* fall through to handle as normal uschar. */
+
+ case MID_LINE: /* Mid-line state */
+ if (ch == '\n')
+ {
+ ch_state = LF_SEEN;
+ body_linecount++;
+ if (linelength > max_received_linelength)
+ max_received_linelength = linelength;
+ linelength = -1;
+ }
+ else if (ch == '\r')
+ {
+ ch_state = CR_SEEN;
+ if (fix_nl) bdat_ungetc('\n');
+ continue; /* don't write CR */
+ }
+ break;
+
+ case CR_SEEN: /* After (unwritten) CR */
+ body_linecount++;
+ if (linelength > max_received_linelength)
+ max_received_linelength = linelength;
+ linelength = -1;
+ if (ch == '\n')
+ ch_state = LF_SEEN;
+ else
+ {
+ message_size++;
+ if (fout && fputc('\n', fout) == EOF) return END_WERROR;
+ cutthrough_data_put_nl();
+ if (ch == '\r') continue; /* don't write CR */
+ ch_state = MID_LINE;
+ }
+ break;
+ }
+
+ /* Add the character to the spool file, unless skipping */
+
+ message_size++;
+ linelength++;
+ if (fout)
+ {
+ if (fputc(ch, fout) == EOF) return END_WERROR;
+ if (message_size > thismessage_size_limit) return END_SIZE;
+ }
+ if(ch == '\n')
+ cutthrough_data_put_nl();
+ else
+ {
+ uschar c = ch;
+ cutthrough_data_puts(&c, 1);
+ }
+ }
+/*NOTREACHED*/
+}
+
+static int
+read_message_bdat_smtp_wire(FILE *fout)
+{
+int ch;
+
+/* Remember that this message uses wireformat. */
+
+DEBUG(D_receive) debug_printf("CHUNKING: writing spoolfile in wire format\n");
+spool_file_wireformat = TRUE;
+
+for (;;)
+ {
+ if (chunking_data_left > 0)
+ {
+ unsigned len = MAX(chunking_data_left, thismessage_size_limit - message_size + 1);
+ uschar * buf = bdat_getbuf(&len);
+
+ message_size += len;
+ if (fout && fwrite(buf, len, 1, fout) != 1) return END_WERROR;
+ }
+ else switch (ch = bdat_getc(GETC_BUFFER_UNLIMITED))
+ {
+ case EOF: return END_EOF;
+ case EOD: return END_DOT;
+ case ERR: return END_PROTOCOL;
+
+ default:
+ message_size++;
+ /*XXX not done:
+ linelength
+ max_received_linelength
+ body_linecount
+ body_zerocount
+ */
+ if (fout && fputc(ch, fout) == EOF) return END_WERROR;
+ break;
+ }
+ if (message_size > thismessage_size_limit) return END_SIZE;
+ }
+/*NOTREACHED*/
+}
+
+
+
+
/*************************************************
* Swallow SMTP message *
*************************************************/
void
receive_swallow_smtp(void)
{
+/*XXX CHUNKING: not enough. read chunks until RSET? */
if (message_ended >= END_NOTENDED)
message_ended = read_message_data_smtp(NULL);
}
{
log_write(L_lost_incoming_connection | L_smtp_connection, LOG_MAIN,
"%s lost while reading message data%s", smtp_get_connection_info(), s);
+smtp_notquit_exit(US"connection-lost", NULL, NULL);
return US"421 Lost incoming connection";
}
error_block eblock;
eblock.next = NULL;
eblock.text1 = text1;
+ eblock.text2 = US"";
if (!moan_to_sender(errcode, &eblock, hptr, f, FALSE))
error_rc = EXIT_FAILURE;
}
-else fprintf(stderr, "exim: %s%s\n", text2, text1); /* Sic */
+else
+ fprintf(stderr, "exim: %s%s\n", text2, text1); /* Sic */
(void)fclose(f);
exim_exit(error_rc);
}
*/
static void
-add_acl_headers(uschar *acl_name)
+add_acl_headers(int where, uschar *acl_name)
{
header_line *h, *next;
header_line *last_received = NULL;
-if (acl_removed_headers != NULL)
+switch(where)
{
- DEBUG(D_receive|D_acl) debug_printf(">>Headers removed by %s ACL:\n", acl_name);
-
- for (h = header_list; h != NULL; h = h->next)
+ case ACL_WHERE_DKIM:
+ case ACL_WHERE_MIME:
+ case ACL_WHERE_DATA:
+ if ( cutthrough.fd >= 0 && cutthrough.delivery
+ && (acl_removed_headers || acl_added_headers))
{
- uschar *list;
- BOOL include_header;
-
- if (h->type == htype_old) continue;
+ log_write(0, LOG_MAIN|LOG_PANIC, "Header modification in data ACLs"
+ " will not take effect on cutthrough deliveries");
+ return;
+ }
+ }
- include_header = TRUE;
- list = acl_removed_headers;
+if (acl_removed_headers)
+ {
+ DEBUG(D_receive|D_acl) debug_printf_indent(">>Headers removed by %s ACL:\n", acl_name);
+ for (h = header_list; h; h = h->next) if (h->type != htype_old)
+ {
+ const uschar * list = acl_removed_headers;
int sep = ':'; /* This is specified as a colon-separated list */
uschar *s;
uschar buffer[128];
- while ((s = string_nextinlist(&list, &sep, buffer, sizeof(buffer)))
- != NULL)
- {
- int len = Ustrlen(s);
- if (header_testname(h, s, len, FALSE))
+
+ while ((s = string_nextinlist(&list, &sep, buffer, sizeof(buffer))))
+ if (header_testname(h, s, Ustrlen(s), FALSE))
{
h->type = htype_old;
- DEBUG(D_receive|D_acl) debug_printf(" %s", h->text);
+ DEBUG(D_receive|D_acl) debug_printf_indent(" %s", h->text);
}
- }
}
acl_removed_headers = NULL;
- DEBUG(D_receive|D_acl) debug_printf(">>\n");
+ DEBUG(D_receive|D_acl) debug_printf_indent(">>\n");
}
-if (acl_added_headers == NULL) return;
-DEBUG(D_receive|D_acl) debug_printf(">>Headers added by %s ACL:\n", acl_name);
+if (!acl_added_headers) return;
+DEBUG(D_receive|D_acl) debug_printf_indent(">>Headers added by %s ACL:\n", acl_name);
-for (h = acl_added_headers; h != NULL; h = next)
+for (h = acl_added_headers; h; h = next)
{
next = h->next;
case htype_add_top:
h->next = header_list;
header_list = h;
- DEBUG(D_receive|D_acl) debug_printf(" (at top)");
+ DEBUG(D_receive|D_acl) debug_printf_indent(" (at top)");
break;
case htype_add_rec:
}
h->next = last_received->next;
last_received->next = h;
- DEBUG(D_receive|D_acl) debug_printf(" (after Received:)");
+ DEBUG(D_receive|D_acl) debug_printf_indent(" (after Received:)");
break;
case htype_add_rfc:
of all headers. Our current header must follow it. */
h->next = last_received->next;
last_received->next = h;
- DEBUG(D_receive|D_acl) debug_printf(" (before any non-Received: or Resent-*: header)");
+ DEBUG(D_receive|D_acl) debug_printf_indent(" (before any non-Received: or Resent-*: header)");
break;
default:
h->type = header_checkname(h, FALSE);
if (h->type >= 'a') h->type = htype_other;
- DEBUG(D_receive|D_acl) debug_printf(" %s", header_last->text);
+ DEBUG(D_receive|D_acl) debug_printf_indent(" %s", header_last->text);
}
acl_added_headers = NULL;
-DEBUG(D_receive|D_acl) debug_printf(">>\n");
+DEBUG(D_receive|D_acl) debug_printf_indent(">>\n");
}
*/
static uschar *
-add_host_info_for_log(uschar *s, int *sizeptr, int *ptrptr)
+add_host_info_for_log(uschar * s, int * sizeptr, int * ptrptr)
{
-if (sender_fullhost != NULL)
+if (sender_fullhost)
{
+ if (LOGGING(dnssec) && sender_host_dnssec) /*XXX sender_helo_dnssec? */
+ s = string_catn(s, sizeptr, ptrptr, US" DS", 3);
s = string_append(s, sizeptr, ptrptr, 2, US" H=", sender_fullhost);
- if ((log_extra_selector & LX_incoming_interface) != 0 &&
- interface_address != NULL)
+ if (LOGGING(incoming_interface) && interface_address != NULL)
{
- uschar *ss = string_sprintf(" I=[%s]:%d", interface_address,
- interface_port);
- s = string_cat(s, sizeptr, ptrptr, ss, Ustrlen(ss));
+ s = string_cat(s, sizeptr, ptrptr,
+ string_sprintf(" I=[%s]:%d", interface_address, interface_port));
}
}
-if (sender_ident != NULL)
+if (tcp_in_fastopen && !tcp_in_fastopen_logged)
+ {
+ s = string_catn(s, sizeptr, ptrptr, US" TFO", 4);
+ tcp_in_fastopen_logged = TRUE;
+ }
+if (sender_ident)
s = string_append(s, sizeptr, ptrptr, 2, US" U=", sender_ident);
-if (received_protocol != NULL)
+if (received_protocol)
s = string_append(s, sizeptr, ptrptr, 2, US" P=", received_protocol);
return s;
}
header_line *my_headerlist;
uschar *user_msg, *log_msg;
int mime_part_count_buffer = -1;
+uschar * mbox_filename;
int rc = OK;
memset(CS rfc822_file_path,0,2048);
/* check if it is a MIME message */
-my_headerlist = header_list;
-while (my_headerlist != NULL)
- {
- /* skip deleted headers */
- if (my_headerlist->type == '*')
- {
- my_headerlist = my_headerlist->next;
- continue;
- }
- if (strncmpic(my_headerlist->text, US"Content-Type:", 13) == 0)
+
+for (my_headerlist = header_list; my_headerlist; my_headerlist = my_headerlist->next)
+ if ( my_headerlist->type != '*' /* skip deleted headers */
+ && strncmpic(my_headerlist->text, US"Content-Type:", 13) == 0
+ )
{
DEBUG(D_receive) debug_printf("Found Content-Type: header - executing acl_smtp_mime.\n");
goto DO_MIME_ACL;
}
- my_headerlist = my_headerlist->next;
- }
DEBUG(D_receive) debug_printf("No Content-Type: header - presumably not a MIME message.\n");
return TRUE;
DO_MIME_ACL:
+
/* make sure the eml mbox file is spooled up */
-mbox_file = spool_mbox(&mbox_size, NULL);
-if (mbox_file == NULL) {
- /* error while spooling */
+if (!(mbox_file = spool_mbox(&mbox_size, NULL, &mbox_filename)))
+ { /* error while spooling */
log_write(0, LOG_MAIN|LOG_PANIC,
"acl_smtp_mime: error while creating mbox spool file, message temporarily rejected.");
Uunlink(spool_name);
message_id[0] = 0; /* Indicate no message accepted */
*smtp_reply_ptr = US""; /* Indicate reply already sent */
return FALSE; /* Indicate skip to end of receive function */
-};
+ }
mime_is_rfc822 = 0;
/* check if we must check any message/rfc822 attachments */
if (rc == OK)
{
- uschar temp_path[1024];
- int n;
- struct dirent *entry;
- DIR *tempdir;
+ uschar * scandir;
+ struct dirent * entry;
+ DIR * tempdir;
- (void)string_format(temp_path, 1024, "%s/scan/%s", spool_directory,
- message_id);
+ scandir = string_copyn(mbox_filename, Ustrrchr(mbox_filename, '/') - mbox_filename);
- tempdir = opendir(CS temp_path);
- n = 0;
- do
+ tempdir = opendir(CS scandir);
+ for (;;)
{
- entry = readdir(tempdir);
- if (entry == NULL) break;
- if (strncmpic(US entry->d_name,US"__rfc822_",9) == 0)
+ if (!(entry = readdir(tempdir)))
+ break;
+ if (strncmpic(US entry->d_name, US"__rfc822_", 9) == 0)
{
- (void)string_format(rfc822_file_path, 2048,"%s/scan/%s/%s", spool_directory, message_id, entry->d_name);
- debug_printf("RFC822 attachment detected: running MIME ACL for '%s'\n", rfc822_file_path);
+ (void) string_format(rfc822_file_path, sizeof(rfc822_file_path),
+ "%s/%s", scandir, entry->d_name);
+ DEBUG(D_receive) debug_printf("RFC822 attachment detected: running MIME ACL for '%s'\n",
+ rfc822_file_path);
break;
}
- } while (1);
+ }
closedir(tempdir);
- if (entry != NULL)
+ if (entry)
{
- mbox_file = Ufopen(rfc822_file_path,"rb");
- if (mbox_file == NULL)
+ if ((mbox_file = Ufopen(rfc822_file_path, "rb")))
{
- log_write(0, LOG_PANIC,
- "acl_smtp_mime: can't open RFC822 spool file, skipping.");
- unlink(CS rfc822_file_path);
- goto END_MIME_ACL;
+ /* set RFC822 expansion variable */
+ mime_is_rfc822 = 1;
+ mime_part_count_buffer = mime_part_count;
+ goto MIME_ACL_CHECK;
}
- /* set RFC822 expansion variable */
- mime_is_rfc822 = 1;
- mime_part_count_buffer = mime_part_count;
- goto MIME_ACL_CHECK;
+ log_write(0, LOG_PANIC,
+ "acl_smtp_mime: can't open RFC822 spool file, skipping.");
+ unlink(CS rfc822_file_path);
}
}
END_MIME_ACL:
-add_acl_headers(US"MIME");
+add_acl_headers(ACL_WHERE_MIME, US"MIME");
if (rc == DISCARD)
{
recipients_count = 0;
#ifdef EXPERIMENTAL_DCC
dcc_ok = 0;
#endif
- if (smtp_handle_acl_fail(ACL_WHERE_MIME, rc, user_msg, log_msg) != 0)
- *smtp_yield_ptr = FALSE; /* No more messsages after dropped connection */
- *smtp_reply_ptr = US""; /* Indicate reply already sent */
+ if ( smtp_input
+ && smtp_handle_acl_fail(ACL_WHERE_MIME, rc, user_msg, log_msg) != 0)
+ {
+ *smtp_yield_ptr = FALSE; /* No more messages after dropped connection */
+ *smtp_reply_ptr = US""; /* Indicate reply already sent */
+ }
message_id[0] = 0; /* Indicate no message accepted */
return FALSE; /* Cause skip to end of receive function */
}
received = expand_string(received_header_text);
received_for = NULL;
-if (received == NULL)
+if (!received)
{
if(spool_name[0] != 0)
Uunlink(spool_name); /* Lose the data file */
uschar *resent_prefix = US"";
uschar *blackholed_by = NULL;
uschar *blackhole_log_msg = US"";
-int cutthrough_done = 0;
+enum {NOT_TRIED, TMP_REJ, PERM_REJ, ACCEPTED} cutthrough_done = NOT_TRIED;
flock_t lock_data;
error_block *bad_addresses = NULL;
header_line *msgid_header = NULL;
header_line *received_header;
+#ifdef EXPERIMENTAL_DMARC
+int dmarc_up = 0;
+#endif /* EXPERIMENTAL_DMARC */
+
/* Variables for use when building the Received: header. */
uschar *timestamp;
cutthrough delivery with the no-spool option. It shouldn't be possible
to set up the combination, but just in case kill any ongoing connection. */
if (extract_recip || !smtp_input)
- cancel_cutthrough_connection("not smtp input");
+ cancel_cutthrough_connection(TRUE, US"not smtp input");
/* Initialize the chain of headers by setting up a place-holder for Received:
header. Temporarily mark it as "old", i.e. not to be used. We keep header_last
message_id[0] = 0;
data_file = NULL;
data_fd = -1;
-spool_name[0] = 0;
+spool_name = US"";
message_size = 0;
warning_count = 0;
received_count = 1; /* For the one we will add */
max_received_linelength = 0;
#ifndef DISABLE_DKIM
-/* Call into DKIM to set up the context. */
-if (smtp_input && !smtp_batched_input && !dkim_disable_verify) dkim_exim_verify_init();
+/* Call into DKIM to set up the context. In CHUNKING mode
+we clear the dot-stuffing flag */
+if (smtp_input && !smtp_batched_input && !dkim_disable_verify)
+ dkim_exim_verify_init(chunking_state <= CHUNKING_OFFERED);
+#endif
+
+#ifdef EXPERIMENTAL_DMARC
+/* initialize libopendmarc */
+dmarc_up = dmarc_init();
#endif
/* Remember the time of reception. Exim uses time+pid for uniqueness of message
/* 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
-things like ultimate message timeouts. */
+things like ultimate message timeouts.XXX */
-received_time = message_id_tv.tv_sec;
+received_time = message_id_tv;
/* If SMTP input, set the special handler for timeouts. The alarm() calls
happen in the smtp_getc() function when it refills its buffer. */
for (;;)
{
- int ch = (receive_getc)();
+ int ch = (receive_getc)(GETC_BUFFER_UNLIMITED);
/* If we hit EOF on a SMTP connection, it's an error, since incoming
SMTP must have a correct "." terminator. */
if (ptr == 0 && ch == '.' && (smtp_input || dot_ends))
{
- ch = (receive_getc)();
+ ch = (receive_getc)(GETC_BUFFER_UNLIMITED);
if (ch == '\r')
{
- ch = (receive_getc)();
+ ch = (receive_getc)(GETC_BUFFER_UNLIMITED);
if (ch != '\n')
{
receive_ungetc(ch);
if (ch == '\r')
{
- ch = (receive_getc)();
+ ch = (receive_getc)(GETC_BUFFER_UNLIMITED);
if (ch == '\n')
{
if (first_line_ended_crlf == TRUE_UNSET) first_line_ended_crlf = TRUE;
if (ch != EOF)
{
- int nextch = (receive_getc)();
+ int nextch = (receive_getc)(GETC_BUFFER_UNLIMITED);
if (nextch == ' ' || nextch == '\t')
{
next->text[ptr++] = nextch;
}
}
+ /* Reject CHUNKING messages that do not CRLF their first header line */
+
+ if (!first_line_ended_crlf && chunking_state > CHUNKING_OFFERED)
+ {
+ log_write(L_size_reject, LOG_MAIN|LOG_REJECT, "rejected from <%s>%s%s%s%s: "
+ "Non-CRLF-terminated header, under CHUNKING: message abandoned",
+ sender_address,
+ sender_fullhost ? " H=" : "", sender_fullhost ? sender_fullhost : US"",
+ sender_ident ? " U=" : "", sender_ident ? sender_ident : US"");
+ smtp_printf("552 Message header not CRLF terminated\r\n", FALSE);
+ bdat_flush_data();
+ smtp_reply = US"";
+ goto TIDYUP; /* Skip to end of function */
+ }
+
/* The line has been handled. If we have hit EOF, break out of the loop,
indicating no pending data line. */
DEBUG(D_receive)
{
debug_printf(">>Headers received:\n");
- for (h = header_list->next; h != NULL; h = h->next)
+ for (h = header_list->next; h; h = h->next)
debug_printf("%s", h->text);
debug_printf("\n");
}
/* Scan the headers to identify them. Some are merely marked for later
processing; some are dealt with here. */
-for (h = header_list->next; h != NULL; h = h->next)
+for (h = header_list->next; h; h = h->next)
{
BOOL is_resent = strncmpic(h->text, US"resent-", 7) == 0;
if (is_resent) contains_resent_headers = TRUE;
uschar *s = Ustrchr(h->text, ':') + 1;
while (isspace(*s)) s++;
len = h->slen - (s - h->text) - 1;
- if (strncmpic(s, originator_login, len) == 0
- && Ustrlen(originator_login) == len)
+ if (Ustrlen(originator_login) == len &&
+ strncmpic(s, originator_login, len) == 0)
{
uschar *name = is_resent? US"Resent-From" : US"From";
header_add(htype_from, "%s: %s <%s@%s>\n", name, originator_name,
/* Now scan the headers */
- for (h = header_list->next; h != NULL; h = h->next)
+ for (h = header_list->next; h; h = h->next)
{
if ((h->type == htype_to || h->type == htype_cc || h->type == htype_bcc) &&
(!contains_resent_headers || strncmpic(h->text, US"resent-", 7) == 0))
pp = recipient = store_get(ss - s + 1);
for (p = s; p < ss; p++) if (*p != '\n') *pp++ = *p;
*pp = 0;
+
+#ifdef SUPPORT_I18N
+ {
+ BOOL b = allow_utf8_domains;
+ allow_utf8_domains = TRUE;
+#endif
recipient = parse_extract_address(recipient, &errmess, &start, &end,
&domain, FALSE);
+#ifdef SUPPORT_I18N
+ if (string_is_utf8(recipient))
+ message_smtputf8 = TRUE;
+ else
+ allow_utf8_domains = b;
+ }
+#endif
+
/* Keep a list of all the bad addresses so we can send a single
error message at the end. However, an empty address is not an error;
just ignore it. This can come from an empty group list like
to be the least significant base-62 digit of the time of arrival. Otherwise
ensure that it is an empty string. */
-message_subdir[0] = split_spool_directory? message_id[5] : 0;
+message_subdir[0] = split_spool_directory ? message_id[5] : 0;
/* Now that we have the message-id, if there is no message-id: header, generate
one, but only for local (without suppress_local_fixups) or submission mode
rewriting. Must copy the count, because later ACLs and the local_scan()
function may mess with the real recipients. */
-if ((log_extra_selector & LX_received_recipients) != 0)
+if (LOGGING(received_recipients))
{
raw_recipients = store_get(recipients_count * sizeof(uschar *));
for (i = 0; i < recipients_count; i++)
}
}
-
/* 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. */
documented as happening *after* recipient addresses are taken from the headers
by the -t command line option. An added Sender: gets rewritten here. */
-for (h = header_list->next; h != NULL; h = h->next)
+for (h = header_list->next; h; h = h->next)
{
header_line *newh = rewrite_header(h, NULL, NULL, global_rewrite_rules,
rewrite_existflags, TRUE);
- if (newh != NULL) h = newh;
+ if (newh) h = newh;
}
return message_ended == END_DOT;
}
-/* Cutthrough delivery:
- We have to create the Received header now rather than at the end of reception,
- so the timestamp behaviour is a change to the normal case.
- XXX Ensure this gets documented XXX.
- Having created it, send the headers to the destination.
+/*XXX CHUNKING: need to cancel cutthrough under BDAT, for now. In future,
+think more if it could be handled. Cannot do onward CHUNKING unless
+inbound is, but inbound chunking ought to be ok with outbound plain.
+Could we do onward CHUNKING given inbound CHUNKING?
*/
-if (cutthrough_fd >= 0)
+if (chunking_state > CHUNKING_OFFERED)
+ cancel_cutthrough_connection(FALSE, US"chunking active");
+
+/* Cutthrough delivery:
+We have to create the Received header now rather than at the end of reception,
+so the timestamp behaviour is a change to the normal case.
+Having created it, send the headers to the destination. */
+
+if (cutthrough.fd >= 0 && cutthrough.delivery)
{
if (received_count > received_headers_max)
{
- cancel_cutthrough_connection("too many headers");
+ cancel_cutthrough_connection(TRUE, US"too many headers");
if (smtp_input) receive_swallow_smtp(); /* Swallow incoming SMTP */
log_write(0, LOG_MAIN|LOG_REJECT, "rejected from <%s>%s%s%s%s: "
"Too many \"Received\" headers",
sender_address,
- (sender_fullhost == NULL)? "" : " H=",
- (sender_fullhost == NULL)? US"" : sender_fullhost,
- (sender_ident == NULL)? "" : " U=",
- (sender_ident == NULL)? US"" : sender_ident);
+ sender_fullhost ? "H=" : "", sender_fullhost ? sender_fullhost : US"",
+ sender_ident ? "U=" : "", sender_ident ? sender_ident : US"");
message_id[0] = 0; /* Indicate no message accepted */
smtp_reply = US"550 Too many \"Received\" headers - suspected mail loop";
goto TIDYUP; /* Skip to end of function */
}
received_header_gen();
- add_acl_headers(US"MAIL or RCPT");
+ add_acl_headers(ACL_WHERE_RCPT, US"MAIL or RCPT");
(void) cutthrough_headers_send();
}
-
+
/* Open a new spool file for the data portion of the message. We need
to access it both via a file descriptor and a stream. Try to make the
-directory if it isn't there. Note re use of sprintf: spool_directory
-is checked on input to be < 200 characters long. */
+directory if it isn't there. */
+
+spool_name = spool_fname(US"input", message_subdir, message_id, US"-D");
+DEBUG(D_receive) debug_printf("Data file name: %s\n", spool_name);
-sprintf(CS spool_name, "%s/input/%s/%s-D", spool_directory, message_subdir,
- message_id);
-data_fd = Uopen(spool_name, O_RDWR|O_CREAT|O_EXCL, SPOOL_MODE);
-if (data_fd < 0)
+if ((data_fd = Uopen(spool_name, O_RDWR|O_CREAT|O_EXCL, SPOOL_MODE)) < 0)
{
if (errno == ENOENT)
{
- uschar temp[16];
- sprintf(CS temp, "input/%s", message_subdir);
- if (message_subdir[0] == 0) temp[5] = 0;
- (void)directory_make(spool_directory, temp, INPUT_DIRECTORY_MODE, TRUE);
+ (void) directory_make(spool_directory,
+ spool_sname(US"input", message_subdir),
+ INPUT_DIRECTORY_MODE, TRUE);
data_fd = Uopen(spool_name, O_RDWR|O_CREAT|O_EXCL, SPOOL_MODE);
}
if (data_fd < 0)
{
if (smtp_input)
{
- message_ended = read_message_data_smtp(data_file);
+ message_ended = chunking_state <= CHUNKING_OFFERED
+ ? read_message_data_smtp(data_file)
+ : spool_wireformat
+ ? read_message_bdat_smtp_wire(data_file)
+ : read_message_bdat_smtp(data_file);
receive_linecount++; /* The terminating "." line */
}
else message_ended = read_message_data(data_file);
receive_linecount += body_linecount; /* For BSMTP errors mainly */
message_linecount += body_linecount;
- /* Handle premature termination of SMTP */
-
- if (smtp_input && message_ended == END_EOF)
+ switch (message_ended)
{
- Uunlink(spool_name); /* Lose data file when closed */
- cancel_cutthrough_connection("sender closed connection");
- message_id[0] = 0; /* Indicate no message accepted */
- smtp_reply = handle_lost_connection(US"");
- smtp_yield = FALSE;
- goto TIDYUP; /* Skip to end of function */
- }
+ /* Handle premature termination of SMTP */
- /* Handle message that is too big. Don't use host_or_ident() in the log
- message; we want to see the ident value even for non-remote messages. */
+ case END_EOF:
+ if (smtp_input)
+ {
+ Uunlink(spool_name); /* Lose data file when closed */
+ cancel_cutthrough_connection(TRUE, US"sender closed connection");
+ message_id[0] = 0; /* Indicate no message accepted */
+ smtp_reply = handle_lost_connection(US"");
+ smtp_yield = FALSE;
+ goto TIDYUP; /* Skip to end of function */
+ }
+ break;
- if (message_ended == END_SIZE)
- {
- Uunlink(spool_name); /* Lose the data file when closed */
- cancel_cutthrough_connection("mail too big");
- if (smtp_input) receive_swallow_smtp(); /* Swallow incoming SMTP */
+ /* Handle message that is too big. Don't use host_or_ident() in the log
+ message; we want to see the ident value even for non-remote messages. */
- log_write(L_size_reject, LOG_MAIN|LOG_REJECT, "rejected from <%s>%s%s%s%s: "
- "message too big: read=%d max=%d",
- sender_address,
- (sender_fullhost == NULL)? "" : " H=",
- (sender_fullhost == NULL)? US"" : sender_fullhost,
- (sender_ident == NULL)? "" : " U=",
- (sender_ident == NULL)? US"" : sender_ident,
- message_size,
- thismessage_size_limit);
+ case END_SIZE:
+ Uunlink(spool_name); /* Lose the data file when closed */
+ cancel_cutthrough_connection(TRUE, US"mail too big");
+ if (smtp_input) receive_swallow_smtp(); /* Swallow incoming SMTP */
- if (smtp_input)
- {
- smtp_reply = US"552 Message size exceeds maximum permitted";
- message_id[0] = 0; /* Indicate no message accepted */
- goto TIDYUP; /* Skip to end of function */
- }
- else
- {
- fseek(data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
- give_local_error(ERRMESS_TOOBIG,
- string_sprintf("message too big (max=%d)", thismessage_size_limit),
- US"message rejected: ", error_rc, data_file, header_list);
- /* Does not return */
- }
+ log_write(L_size_reject, LOG_MAIN|LOG_REJECT, "rejected from <%s>%s%s%s%s: "
+ "message too big: read=%d max=%d",
+ sender_address,
+ (sender_fullhost == NULL)? "" : " H=",
+ (sender_fullhost == NULL)? US"" : sender_fullhost,
+ (sender_ident == NULL)? "" : " U=",
+ (sender_ident == NULL)? US"" : sender_ident,
+ message_size,
+ thismessage_size_limit);
+
+ if (smtp_input)
+ {
+ smtp_reply = US"552 Message size exceeds maximum permitted";
+ message_id[0] = 0; /* Indicate no message accepted */
+ goto TIDYUP; /* Skip to end of function */
+ }
+ else
+ {
+ fseek(data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
+ give_local_error(ERRMESS_TOOBIG,
+ string_sprintf("message too big (max=%d)", thismessage_size_limit),
+ US"message rejected: ", error_rc, data_file, header_list);
+ /* Does not return */
+ }
+ break;
+
+ /* Handle bad BDAT protocol sequence */
+
+ case END_PROTOCOL:
+ Uunlink(spool_name); /* Lose the data file when closed */
+ cancel_cutthrough_connection(TRUE, US"sender protocol error");
+ smtp_reply = US""; /* Response already sent */
+ message_id[0] = 0; /* Indicate no message accepted */
+ goto TIDYUP; /* Skip to end of function */
}
}
log_write(0, LOG_MAIN, "Message abandoned: %s", msg);
Uunlink(spool_name); /* Lose the data file */
- cancel_cutthrough_connection("error writing spoolfile");
+ cancel_cutthrough_connection(TRUE, US"error writing spoolfile");
if (smtp_input)
{
/* If an ACL from any RCPT commands set up any warning headers to add, do so
now, before running the DATA ACL. */
- add_acl_headers(US"MAIL or RCPT");
+ add_acl_headers(ACL_WHERE_RCPT, US"MAIL or RCPT");
}
else
message_body_size = (fstat(data_fd, &statbuf) == 0)?
enable_dollar_recipients = TRUE;
if (recipients_count == 0)
- {
- blackholed_by = recipients_discarded? US"MAIL ACL" : US"RCPT ACL";
- }
+ blackholed_by = recipients_discarded ? US"MAIL ACL" : US"RCPT ACL";
+
else
{
/* Handle interactive SMTP messages */
dkim_exim_verify_finish();
/* Check if we must run the DKIM ACL */
- if ((acl_smtp_dkim != NULL) &&
- (dkim_verify_signers != NULL) &&
- (dkim_verify_signers[0] != '\0'))
+ if (acl_smtp_dkim && dkim_verify_signers && *dkim_verify_signers)
{
uschar *dkim_verify_signers_expanded =
expand_string(dkim_verify_signers);
- if (dkim_verify_signers_expanded == NULL)
- {
+ if (!dkim_verify_signers_expanded)
log_write(0, LOG_MAIN|LOG_PANIC,
"expansion of dkim_verify_signers option failed: %s",
expand_string_message);
- }
+
else
{
int sep = 0;
- uschar *ptr = dkim_verify_signers_expanded;
+ const uschar *ptr = dkim_verify_signers_expanded;
uschar *item = NULL;
uschar *seen_items = NULL;
int seen_items_size = 0;
int seen_items_offset = 0;
- uschar itembuf[256];
/* Default to OK when no items are present */
rc = OK;
- while ((item = string_nextinlist(&ptr, &sep,
- itembuf,
- sizeof(itembuf))) != NULL)
+ while ((item = string_nextinlist(&ptr, &sep, NULL, 0)))
{
/* Prevent running ACL for an empty item */
- if (!item || (item[0] == '\0')) continue;
- /* Only run ACL once for each domain or identity, no matter how often it
- appears in the expanded list. */
- if (seen_items != NULL)
+ if (!item || !*item) continue;
+
+ /* Only run ACL once for each domain or identity,
+ no matter how often it appears in the expanded list. */
+ if (seen_items)
{
uschar *seen_item = NULL;
- uschar seen_item_buf[256];
- uschar *seen_items_list = seen_items;
- int seen_this_item = 0;
-
- while ((seen_item = string_nextinlist(&seen_items_list, &sep,
- seen_item_buf,
- sizeof(seen_item_buf))) != NULL)
- {
- if (Ustrcmp(seen_item,item) == 0)
- {
- seen_this_item = 1;
- break;
- }
- }
+ const uschar *seen_items_list = seen_items;
+ BOOL seen_this_item = FALSE;
- if (seen_this_item > 0)
+ while ((seen_item = string_nextinlist(&seen_items_list, &sep,
+ NULL, 0)))
+ if (Ustrcmp(seen_item,item) == 0)
+ {
+ seen_this_item = TRUE;
+ break;
+ }
+
+ if (seen_this_item)
{
DEBUG(D_receive)
- debug_printf("acl_smtp_dkim: skipping signer %s, already seen\n", item);
+ debug_printf("acl_smtp_dkim: skipping signer %s, "
+ "already seen\n", item);
continue;
}
-
- seen_items = string_append(seen_items,&seen_items_size,&seen_items_offset,1,":");
+
+ seen_items = string_append(seen_items, &seen_items_size,
+ &seen_items_offset, 1, ":");
}
- seen_items = string_append(seen_items,&seen_items_size,&seen_items_offset,1,item);
+ seen_items = string_append(seen_items, &seen_items_size,
+ &seen_items_offset, 1, item);
seen_items[seen_items_offset] = '\0';
DEBUG(D_receive)
- debug_printf("calling acl_smtp_dkim for dkim_cur_signer=%s\n", item);
+ debug_printf("calling acl_smtp_dkim for dkim_cur_signer=%s\n",
+ item);
dkim_exim_acl_setup(item);
- rc = acl_check(ACL_WHERE_DKIM, NULL, acl_smtp_dkim, &user_msg, &log_msg);
+ rc = acl_check(ACL_WHERE_DKIM, NULL, acl_smtp_dkim,
+ &user_msg, &log_msg);
if (rc != OK)
- {
- DEBUG(D_receive)
- debug_printf("acl_smtp_dkim: acl_check returned %d on %s, skipping remaining items\n", rc, item);
- cancel_cutthrough_connection("dkim acl not ok");
- break;
- }
+ {
+ DEBUG(D_receive)
+ debug_printf("acl_smtp_dkim: acl_check returned %d on %s, "
+ "skipping remaining items\n", rc, item);
+ cancel_cutthrough_connection(TRUE, US"dkim acl not ok");
+ break;
+ }
}
- add_acl_headers(US"DKIM");
+ add_acl_headers(ACL_WHERE_DKIM, US"DKIM");
if (rc == DISCARD)
{
recipients_count = 0;
{
Uunlink(spool_name);
if (smtp_handle_acl_fail(ACL_WHERE_DKIM, rc, user_msg, log_msg) != 0)
- smtp_yield = FALSE; /* No more messsages after dropped connection */
+ smtp_yield = FALSE; /* No more messages after dropped connection */
smtp_reply = US""; /* Indicate reply already sent */
message_id[0] = 0; /* Indicate no message accepted */
goto TIDYUP; /* Skip to end of function */
goto TIDYUP;
#endif /* WITH_CONTENT_SCAN */
+#ifdef EXPERIMENTAL_DMARC
+ dmarc_up = dmarc_store_data(from_header);
+#endif /* EXPERIMENTAL_DMARC */
+
+#ifndef DISABLE_PRDR
+ if (prdr_requested && recipients_count > 1 && acl_smtp_data_prdr)
+ {
+ unsigned int c;
+ int all_pass = OK;
+ int all_fail = FAIL;
+
+ smtp_printf("353 PRDR content analysis beginning\r\n", TRUE);
+ /* Loop through recipients, responses must be in same order received */
+ for (c = 0; recipients_count > c; c++)
+ {
+ uschar * addr= recipients_list[c].address;
+ uschar * msg= US"PRDR R=<%s> %s";
+ uschar * code;
+ DEBUG(D_receive)
+ debug_printf("PRDR processing recipient %s (%d of %d)\n",
+ addr, c+1, recipients_count);
+ rc = acl_check(ACL_WHERE_PRDR, addr,
+ acl_smtp_data_prdr, &user_msg, &log_msg);
+
+ /* If any recipient rejected content, indicate it in final message */
+ all_pass |= rc;
+ /* If all recipients rejected, indicate in final message */
+ all_fail &= rc;
+
+ switch (rc)
+ {
+ case OK: case DISCARD: code = US"250"; break;
+ case DEFER: code = US"450"; break;
+ default: code = US"550"; break;
+ }
+ if (user_msg != NULL)
+ smtp_user_msg(code, user_msg);
+ else
+ {
+ switch (rc)
+ {
+ case OK: case DISCARD:
+ msg = string_sprintf(CS msg, addr, "acceptance"); break;
+ case DEFER:
+ msg = string_sprintf(CS msg, addr, "temporary refusal"); break;
+ default:
+ msg = string_sprintf(CS msg, addr, "refusal"); break;
+ }
+ smtp_user_msg(code, msg);
+ }
+ if (log_msg) log_write(0, LOG_MAIN, "PRDR %s %s", addr, log_msg);
+ else if (user_msg) log_write(0, LOG_MAIN, "PRDR %s %s", addr, user_msg);
+ else log_write(0, LOG_MAIN, "%s", CS msg);
+
+ if (rc != OK) { receive_remove_recipient(addr); c--; }
+ }
+ /* Set up final message, used if data acl gives OK */
+ smtp_reply = string_sprintf("%s id=%s message %s",
+ all_fail == FAIL ? US"550" : US"250",
+ message_id,
+ all_fail == FAIL
+ ? US"rejected for all recipients"
+ : all_pass == OK
+ ? US"accepted"
+ : US"accepted for some recipients");
+ if (recipients_count == 0)
+ {
+ message_id[0] = 0; /* Indicate no message accepted */
+ goto TIDYUP;
+ }
+ }
+ else
+ prdr_requested = FALSE;
+#endif /* !DISABLE_PRDR */
+
/* Check the recipients count again, as the MIME ACL might have changed
them. */
if (acl_smtp_data != NULL && recipients_count > 0)
{
rc = acl_check(ACL_WHERE_DATA, NULL, acl_smtp_data, &user_msg, &log_msg);
- add_acl_headers(US"DATA");
+ add_acl_headers(ACL_WHERE_DATA, US"DATA");
if (rc == DISCARD)
{
recipients_count = 0;
blackholed_by = US"DATA ACL";
- if (log_msg != NULL)
+ if (log_msg)
blackhole_log_msg = string_sprintf(": %s", log_msg);
- cancel_cutthrough_connection("data acl discard");
+ cancel_cutthrough_connection(TRUE, US"data acl discard");
}
else if (rc != OK)
{
Uunlink(spool_name);
- cancel_cutthrough_connection("data acl not ok");
+ cancel_cutthrough_connection(TRUE, US"data acl not ok");
#ifdef WITH_CONTENT_SCAN
unspool_mbox();
#endif
dcc_ok = 0;
#endif
if (smtp_handle_acl_fail(ACL_WHERE_DATA, rc, user_msg, log_msg) != 0)
- smtp_yield = FALSE; /* No more messsages after dropped connection */
+ smtp_yield = FALSE; /* No more messages after dropped connection */
smtp_reply = US""; /* Indicate reply already sent */
message_id[0] = 0; /* Indicate no message accepted */
goto TIDYUP; /* Skip to end of function */
/* Does not return */
}
}
- add_acl_headers(US"non-SMTP");
+ add_acl_headers(ACL_WHERE_NOTSMTP, US"non-SMTP");
}
}
version supplied with Exim always accepts, but this is a hook for sysadmins to
supply their own checking code. The local_scan() function is run even when all
the recipients have been discarded. */
+/*XXS could we avoid this for the standard case, given that few people will use it? */
lseek(data_fd, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
goto TEMPREJECT;
case LOCAL_SCAN_REJECT_NOLOGHDR:
- log_extra_selector &= ~LX_rejected_header;
+ BIT_CLEAR(log_selector, log_selector_size, Li_rejected_header);
/* Fall through */
case LOCAL_SCAN_REJECT:
break;
case LOCAL_SCAN_TEMPREJECT_NOLOGHDR:
- log_extra_selector &= ~LX_rejected_header;
+ BIT_CLEAR(log_selector, log_selector_size, Li_rejected_header);
/* Fall through */
case LOCAL_SCAN_TEMPREJECT:
deliver_firsttime = TRUE;
#ifdef EXPERIMENTAL_BRIGHTMAIL
-if (bmi_run == 1) {
- /* rewind data file */
+if (bmi_run == 1)
+ { /* rewind data file */
lseek(data_fd, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
bmi_verdicts = bmi_process_message(header_list, data_fd);
-};
+ }
#endif
-/* Update the timstamp in our Received: header to account for any time taken by
+/* Update the timestamp in our Received: header to account for any time taken by
an ACL or by local_scan(). The new time is the time that all reception
processing is complete. */
/* Write the -H file */
else
- {
if ((msg_size = spool_write_header(message_id, SW_RECEIVING, &errmsg)) < 0)
{
log_write(0, LOG_MAIN, "Message abandoned: %s", errmsg);
/* Does not return */
}
}
- }
/* The message has now been successfully received. */
macro to simplify the coding. We log the arrival of a new message while the
file is still locked, just in case the machine is *really* fast, and delivers
it first! Include any message id that is in the message - since the syntax of a
-message id is actually an addr-spec, we can use the parse routine to canonicize
+message id is actually an addr-spec, we can use the parse routine to canonicalize
it. */
size = 256;
sptr = 0;
s = store_get(size);
-s = string_append(s, &size, &sptr, 2, US"<= ",
- (sender_address[0] == 0)? US"<>" : sender_address);
-if (message_reference != NULL)
+s = string_append(s, &size, &sptr, 2,
+ fake_response == FAIL ? US"(= " : US"<= ",
+ sender_address[0] == 0 ? US"<>" : sender_address);
+if (message_reference)
s = string_append(s, &size, &sptr, 2, US" R=", message_reference);
s = add_host_info_for_log(s, &size, &sptr);
#ifdef SUPPORT_TLS
-if ((log_extra_selector & LX_tls_cipher) != 0 && tls_in.cipher != NULL)
+if (LOGGING(tls_cipher) && tls_in.cipher)
s = string_append(s, &size, &sptr, 2, US" X=", tls_in.cipher);
-if ((log_extra_selector & LX_tls_certificate_verified) != 0 &&
- tls_in.cipher != NULL)
+if (LOGGING(tls_certificate_verified) && tls_in.cipher)
s = string_append(s, &size, &sptr, 2, US" CV=",
- tls_in.certificate_verified? "yes":"no");
-if ((log_extra_selector & LX_tls_peerdn) != 0 && tls_in.peerdn != NULL)
+ tls_in.certificate_verified ? "yes":"no");
+if (LOGGING(tls_peerdn) && tls_in.peerdn)
s = string_append(s, &size, &sptr, 3, US" DN=\"",
string_printing(tls_in.peerdn), US"\"");
-if ((log_extra_selector & LX_tls_sni) != 0 && tls_in.sni != NULL)
+if (LOGGING(tls_sni) && tls_in.sni)
s = string_append(s, &size, &sptr, 3, US" SNI=\"",
string_printing(tls_in.sni), US"\"");
#endif
-if (sender_host_authenticated != NULL)
+if (sender_host_authenticated)
{
s = string_append(s, &size, &sptr, 2, US" A=", sender_host_authenticated);
- if (authenticated_id != NULL)
+ if (authenticated_id)
{
s = string_append(s, &size, &sptr, 2, US":", authenticated_id);
- if (log_extra_selector & LX_smtp_mailauth && authenticated_sender != NULL)
+ if (LOGGING(smtp_mailauth) && authenticated_sender)
s = string_append(s, &size, &sptr, 2, US":", authenticated_sender);
}
}
+#ifndef DISABLE_PRDR
+if (prdr_requested)
+ s = string_catn(s, &size, &sptr, US" PRDR", 5);
+#endif
+
+#ifdef SUPPORT_PROXY
+if (proxy_session && LOGGING(proxy))
+ s = string_append(s, &size, &sptr, 2, US" PRX=", proxy_local_address);
+#endif
+
+if (chunking_state > CHUNKING_OFFERED)
+ s = string_catn(s, &size, &sptr, US" K", 2);
+
sprintf(CS big_buffer, "%d", msg_size);
s = string_append(s, &size, &sptr, 2, US" S=", big_buffer);
0 ... no BODY= used
7 ... 7BIT
8 ... 8BITMIME */
-if (log_extra_selector & LX_8bitmime)
+if (LOGGING(8bitmime))
{
sprintf(CS big_buffer, "%d", body_8bitmime);
s = string_append(s, &size, &sptr, 2, US" M8S=", big_buffer);
}
+if (*queue_name)
+ s = string_append(s, &size, &sptr, 2, US" Q=", queue_name);
+
/* If an addr-spec in a message-id contains a quoted string, it can contain
any characters except " \ and CR and so in particular it can contain NL!
Therefore, make sure we use a printing-characters only version for the log.
Also, allow for domain literals in the message id. */
-if (msgid_header != NULL)
+if (msgid_header)
{
uschar *old_id;
BOOL save_allow_domain_literals = allow_domain_literals;
/* If subject logging is turned on, create suitable printing-character
text. By expanding $h_subject: we make use of the MIME decoding. */
-if ((log_extra_selector & LX_subject) != 0 && subject_header != NULL)
+if (LOGGING(subject) && subject_header != NULL)
{
int i;
uschar *p = big_buffer;
{
int fd;
- sprintf(CS spool_name, "%s/msglog/%s/%s", spool_directory, message_subdir,
- message_id);
- fd = Uopen(spool_name, O_WRONLY|O_APPEND|O_CREAT, SPOOL_MODE);
-
- if (fd < 0 && errno == ENOENT)
+ spool_name = spool_fname(US"msglog", message_subdir, message_id, US"");
+
+ if ( (fd = Uopen(spool_name, O_WRONLY|O_APPEND|O_CREAT, SPOOL_MODE)) < 0
+ && errno == ENOENT
+ )
{
- uschar temp[16];
- sprintf(CS temp, "msglog/%s", message_subdir);
- if (message_subdir[0] == 0) temp[6] = 0;
- (void)directory_make(spool_directory, temp, MSGLOG_DIRECTORY_MODE, TRUE);
+ (void)directory_make(spool_directory,
+ spool_sname(US"msglog", message_subdir),
+ MSGLOG_DIRECTORY_MODE, TRUE);
fd = Uopen(spool_name, O_WRONLY|O_APPEND|O_CREAT, SPOOL_MODE);
}
if (deliver_freeze) fprintf(message_log, "%s frozen by %s\n", now,
frozen_by);
if (queue_only_policy) fprintf(message_log,
- "%s no immediate delivery: queued by %s\n", now, queued_by);
+ "%s no immediate delivery: queued%s%s by %s\n", now,
+ *queue_name ? " in " : "", *queue_name ? CS queue_name : "",
+ queued_by);
(void)fclose(message_log);
}
}
if (select(fileno(smtp_in) + 1, &select_check, NULL, NULL, &tv) != 0)
{
- int c = (receive_getc)();
+ int c = (receive_getc)(GETC_BUFFER_UNLIMITED);
if (c != EOF) (receive_ungetc)(c); else
{
- uschar *msg = US"SMTP connection lost after final dot";
+ smtp_notquit_exit(US"connection-lost", NULL, NULL);
smtp_reply = US""; /* No attempt to send a response */
smtp_yield = FALSE; /* Nothing more on this connection */
/* Re-use the log line workspace */
sptr = 0;
- s = string_cat(s, &size, &sptr, msg, Ustrlen(msg));
+ s = string_cat(s, &size, &sptr, US"SMTP connection lost after final dot");
s = add_host_info_for_log(s, &size, &sptr);
s[sptr] = 0;
log_write(0, LOG_MAIN, "%s", s);
/* Delete the files for this aborted message. */
- sprintf(CS spool_name, "%s/input/%s/%s-D", spool_directory,
- message_subdir, message_id);
- Uunlink(spool_name);
-
- sprintf(CS spool_name, "%s/input/%s/%s-H", spool_directory,
- message_subdir, message_id);
- Uunlink(spool_name);
-
- sprintf(CS spool_name, "%s/msglog/%s/%s", spool_directory,
- message_subdir, message_id);
- Uunlink(spool_name);
+ Uunlink(spool_fname(US"input", message_subdir, message_id, US"-D"));
+ Uunlink(spool_fname(US"input", message_subdir, message_id, US"-H"));
+ Uunlink(spool_fname(US"msglog", message_subdir, message_id, US""));
goto TIDYUP;
}
Send dot onward. If accepted, wipe the spooled files, log as delivered and accept
the sender's dot (below).
- If rejected: copy response to sender, wipe the spooled files, log approriately.
- If temp-reject: accept to sender, keep the spooled files.
+ If rejected: copy response to sender, wipe the spooled files, log appropriately.
+ If temp-reject: normally accept to sender, keep the spooled file - unless defer=pass
+ in which case pass temp-reject back to initiator and dump the files.
Having the normal spool files lets us do data-filtering, and store/forward on temp-reject.
XXX We do not handle queue-only, freezing, or blackholes.
*/
-cutthrough_done = 0;
-if(cutthrough_fd >= 0)
+if(cutthrough.fd >= 0 && cutthrough.delivery)
{
- uschar * msg= cutthrough_finaldot(); /* Ask the target system to accept the messsage */
+ uschar * msg = cutthrough_finaldot(); /* Ask the target system to accept the message */
/* Logging was done in finaldot() */
switch(msg[0])
{
case '2': /* Accept. Do the same to the source; dump any spoolfiles. */
- cutthrough_done = 3;
+ cutthrough_done = ACCEPTED;
break; /* message_id needed for SMTP accept below */
-
+
+ case '4': /* Temp-reject. Keep spoolfiles and accept, unless defer-pass mode.
+ ... for which, pass back the exact error */
+ if (cutthrough.defer_pass) smtp_reply = string_copy_malloc(msg);
+ /*FALLTRHOUGH*/
+
default: /* Unknown response, or error. Treat as temp-reject. */
- case '4': /* Temp-reject. Keep spoolfiles and accept. */
- cutthrough_done = 1; /* Avoid the usual immediate delivery attempt */
+ cutthrough_done = TMP_REJ; /* Avoid the usual immediate delivery attempt */
break; /* message_id needed for SMTP accept below */
-
+
case '5': /* Perm-reject. Do the same to the source. Dump any spoolfiles */
- smtp_reply= msg; /* Pass on the exact error */
- cutthrough_done = 2;
+ smtp_reply = string_copy_malloc(msg); /* Pass on the exact error */
+ cutthrough_done = PERM_REJ;
break;
}
}
-if(smtp_reply == NULL)
+#ifndef DISABLE_PRDR
+if(!smtp_reply || prdr_requested)
+#else
+if(!smtp_reply)
+#endif
{
log_write(0, LOG_MAIN |
- (((log_extra_selector & LX_received_recipients) != 0)? LOG_RECIPIENTS : 0) |
- (((log_extra_selector & LX_received_sender) != 0)? LOG_SENDER : 0),
+ (LOGGING(received_recipients)? LOG_RECIPIENTS : 0) |
+ (LOGGING(received_sender)? LOG_SENDER : 0),
"%s", s);
/* Log any control actions taken by an ACL or local_scan(). */
if (deliver_freeze) log_write(0, LOG_MAIN, "frozen by %s", frozen_by);
if (queue_only_policy) log_write(L_delay_delivery, LOG_MAIN,
- "no immediate delivery: queued by %s", queued_by);
+ "no immediate delivery: queued%s%s by %s",
+ *queue_name ? " in " : "", *queue_name ? CS queue_name : "",
+ queued_by);
}
receive_call_bombout = FALSE;
if (!smtp_batched_input)
{
- if (smtp_reply == NULL)
+ if (!smtp_reply)
{
if (fake_response != OK)
- smtp_respond((fake_response == DEFER)? US"450" : US"550", 3, TRUE,
- fake_response_text);
+ smtp_respond(fake_response == DEFER ? US"450" : US"550",
+ 3, TRUE, fake_response_text);
/* An OK response is required; use "message" text if present. */
- else if (user_msg != NULL)
+ else if (user_msg)
{
uschar *code = US"250";
int len = 3;
- smtp_message_code(&code, &len, &user_msg, NULL);
+ smtp_message_code(&code, &len, &user_msg, NULL, TRUE);
smtp_respond(code, len, TRUE, user_msg);
}
/* Default OK response */
+ else if (chunking_state > CHUNKING_OFFERED)
+ {
+ smtp_printf("250- %u byte chunk, total %d\r\n250 OK id=%s\r\n", FALSE,
+ chunking_datasize, message_size+message_linecount, message_id);
+ chunking_state = CHUNKING_OFFERED;
+ }
else
- smtp_printf("250 OK id=%s\r\n", message_id);
+ smtp_printf("250 OK id=%s\r\n", FALSE, message_id);
+
if (host_checking)
fprintf(stdout,
"\n**** SMTP testing: that is not a real message id!\n\n");
/* smtp_reply is set non-empty */
else if (smtp_reply[0] != 0)
- {
if (fake_response != OK && (smtp_reply[0] == '2'))
smtp_respond((fake_response == DEFER)? US"450" : US"550", 3, TRUE,
fake_response_text);
else
- smtp_printf("%.1024s\r\n", smtp_reply);
- }
+ smtp_printf("%.1024s\r\n", FALSE, smtp_reply);
switch (cutthrough_done)
{
- case 3: log_write(0, LOG_MAIN, "Completed"); /* Delivery was done */
- case 2: { /* Delete spool files */
- sprintf(CS spool_name, "%s/input/%s/%s-D", spool_directory,
- message_subdir, message_id);
- Uunlink(spool_name);
- sprintf(CS spool_name, "%s/input/%s/%s-H", spool_directory,
- message_subdir, message_id);
- Uunlink(spool_name);
- sprintf(CS spool_name, "%s/msglog/%s/%s", spool_directory,
- message_subdir, message_id);
- Uunlink(spool_name);
- }
- case 1: message_id[0] = 0; /* Prevent a delivery from starting */
- default:break;
+ case ACCEPTED:
+ log_write(0, LOG_MAIN, "Completed");/* Delivery was done */
+ case PERM_REJ:
+ /* Delete spool files */
+ Uunlink(spool_fname(US"input", message_subdir, message_id, US"-D"));
+ Uunlink(spool_fname(US"input", message_subdir, message_id, US"-H"));
+ Uunlink(spool_fname(US"msglog", message_subdir, message_id, US""));
+ break;
+
+ case TMP_REJ:
+ if (cutthrough.defer_pass)
+ {
+ Uunlink(spool_fname(US"input", message_subdir, message_id, US"-D"));
+ Uunlink(spool_fname(US"input", message_subdir, message_id, US"-H"));
+ Uunlink(spool_fname(US"msglog", message_subdir, message_id, US""));
+ }
+ default:
+ break;
+ }
+ if (cutthrough_done != NOT_TRIED)
+ {
+ message_id[0] = 0; /* Prevent a delivery from starting */
+ cutthrough.delivery = cutthrough.callout_hold_only = FALSE;
+ cutthrough.defer_pass = FALSE;
}
- cutthrough_delivery = FALSE;
}
/* For batched SMTP, generate an error message on failure, and do
nothing on success. The function moan_smtp_batch() does not return -
it exits from the program with a non-zero return code. */
- else if (smtp_reply != NULL) moan_smtp_batch(NULL, "%s", smtp_reply);
+ else if (smtp_reply)
+ moan_smtp_batch(NULL, "%s", smtp_reply);
}
We must now indicate that nothing was received, to prevent a delivery from
starting. */
-if (blackholed_by != NULL)
+if (blackholed_by)
{
- uschar *detail = (local_scan_data != NULL)?
- string_printing(local_scan_data) :
- string_sprintf("(%s discarded recipients)", blackholed_by);
+ const uschar *detail = local_scan_data
+ ? string_printing(local_scan_data)
+ : string_sprintf("(%s discarded recipients)", blackholed_by);
log_write(0, LOG_MAIN, "=> blackhole %s%s", detail, blackhole_log_msg);
log_write(0, LOG_MAIN, "Completed");
message_id[0] = 0;