-/* $Cambridge: exim/src/src/receive.c,v 1.36 2007/04/13 15:13:47 ph10 Exp $ */
-
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2007 */
+/* Copyright (c) University of Cambridge 1995 - 2009 */
/* See the file NOTICE for conditions of use and distribution. */
/* Code for receiving a message and setting up spool files. */
#include "exim.h"
-#ifdef EXPERIMENTAL_DOMAINKEYS
-#define RECEIVE_GETC dk_receive_getc
-#define RECEIVE_UNGETC dk_receive_ungetc
-#else
-#define RECEIVE_GETC receive_getc
-#define RECEIVE_UNGETC receive_ungetc
+#ifdef EXPERIMENTAL_DCC
+extern int dcc_ok;
#endif
/*************************************************
}
}
-/* We now have the patch; do the business */
+/* We now have the path; do the business */
memset(&statbuf, 0, sizeof(statbuf));
function if there is an ultimate disaster. That is why it is globally
accessible.
-Arguments: SMTP response to give if in an SMTP session
+Arguments:
+ reason text reason to pass to the not-quit ACL
+ msg default SMTP response to give if in an SMTP session
Returns: it doesn't
*/
void
-receive_bomb_out(uschar *msg)
+receive_bomb_out(uschar *reason, uschar *msg)
{
/* 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
if (data_file != NULL) (void)fclose(data_file);
else if (data_fd >= 0) (void)close(data_fd);
-/* Attempt to close down an SMTP connection tidily. */
+/* 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 (!smtp_batched_input)
- {
- smtp_printf("421 %s %s - closing connection.\r\n", smtp_active_hostname,
- msg);
- mac_smtp_fflush();
- }
-
- /* Control does not return from moan_smtp_batch(). */
-
- else moan_smtp_batch(NULL, "421 %s - message abandoned", msg);
+ 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) */
LOG_MAIN, "timed out while reading local message");
}
-receive_bomb_out(msg); /* Does not return */
+receive_bomb_out(US"data-timeout", msg); /* Does not return */
}
sig = sig; /* Keep picky compilers happy */
log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() function timed out - "
"message temporarily rejected (size %d)", message_size);
-receive_bomb_out(US"local verification problem"); /* Does not return */
+/* Does not return */
+receive_bomb_out(US"local-scan-timeout", US"local verification problem");
}
{
log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() function crashed with "
"signal %d - message temporarily rejected (size %d)", sig, message_size);
-receive_bomb_out(US"local verification problem"); /* Does not return */
+/* Does not return */
+receive_bomb_out(US"local-scan-error", US"local verification problem");
}
}
}
-receive_bomb_out(msg); /* Does not return */
+receive_bomb_out(US"signal-exit", msg); /* Does not return */
}
{
int ch_state;
register int ch;
+register int linelength = 0;
/* Handle the case when only EOF terminates the message */
{
register int last_ch = '\n';
- for (; (ch = (RECEIVE_GETC)()) != EOF; last_ch = ch)
+ for (; (ch = (receive_getc)()) != EOF; last_ch = ch)
{
if (ch == 0) body_zerocount++;
if (last_ch == '\r' && ch != '\n')
{
+ if (linelength > max_received_linelength)
+ max_received_linelength = linelength;
+ linelength = 0;
if (fputc('\n', fout) == EOF) return END_WERROR;
message_size++;
body_linecount++;
if (ch == '\r') continue;
if (fputc(ch, fout) == EOF) return END_WERROR;
- if (ch == '\n') body_linecount++;
+ if (ch == '\n')
+ {
+ if (linelength > max_received_linelength)
+ max_received_linelength = linelength;
+ linelength = 0;
+ body_linecount++;
+ }
+ else linelength++;
if (++message_size > thismessage_size_limit) return END_SIZE;
}
if (last_ch != '\n')
{
+ if (linelength > max_received_linelength)
+ max_received_linelength = linelength;
if (fputc('\n', fout) == EOF) return END_WERROR;
message_size++;
body_linecount++;
ch_state = 1;
-while ((ch = (RECEIVE_GETC)()) != EOF)
+while ((ch = (receive_getc)()) != EOF)
{
if (ch == 0) body_zerocount++;
switch (ch_state)
{
case 0: /* Normal state (previous char written) */
if (ch == '\n')
- { body_linecount++; ch_state = 1; }
+ {
+ body_linecount++;
+ if (linelength > max_received_linelength)
+ max_received_linelength = linelength;
+ linelength = -1;
+ ch_state = 1;
+ }
else if (ch == '\r')
{ ch_state = 2; continue; }
break;
case 1: /* After written "\n" */
if (ch == '.') { ch_state = 3; continue; }
- if (ch != '\n') ch_state = 0;
+ if (ch != '\n') ch_state = 0; else linelength = -1;
break;
case 2:
body_linecount++; /* After unwritten "\r" */
+ if (linelength > max_received_linelength)
+ max_received_linelength = linelength;
if (ch == '\n')
- { ch_state = 1; }
+ {
+ ch_state = 1;
+ linelength = -1;
+ }
else
{
if (message_size++, fputc('\n', fout) == EOF) return END_WERROR;
if (ch == '\r') continue;
ch_state = 0;
+ linelength = 0;
}
break;
if (ch == '\n') return END_DOT;
if (ch == '\r') { ch_state = 4; continue; }
message_size++;
+ linelength++;
if (fputc('.', fout) == EOF) return END_WERROR;
ch_state = 0;
break;
break;
}
+ linelength++;
if (fputc(ch, fout) == EOF) return END_WERROR;
if (++message_size > thismessage_size_limit) return END_SIZE;
}
{
int ch_state = 0;
register int ch;
+register int linelength = 0;
-while ((ch = (RECEIVE_GETC)()) != EOF)
+while ((ch = (receive_getc)()) != EOF)
{
if (ch == 0) body_zerocount++;
switch (ch_state)
{
ch_state = 0;
body_linecount++;
+ if (linelength > max_received_linelength)
+ max_received_linelength = linelength;
+ linelength = -1;
}
else if (ch == '\r')
{
case 2: /* After (unwritten) CR */
body_linecount++;
+ if (linelength > max_received_linelength)
+ max_received_linelength = linelength;
+ linelength = -1;
if (ch == '\n')
{
ch_state = 0;
next. */
message_size++;
+ linelength++;
if (fout != NULL)
{
if (fputc(ch, fout) == EOF) return END_WERROR;
header_line *my_headerlist;
uschar *user_msg, *log_msg;
int mime_part_count_buffer = -1;
-int rc;
+int rc = OK;
memset(CS rfc822_file_path,0,2048);
DO_MIME_ACL:
/* make sure the eml mbox file is spooled up */
-mbox_file = spool_mbox(&mbox_size);
+mbox_file = spool_mbox(&mbox_size, NULL);
if (mbox_file == NULL) {
/* 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);
unspool_mbox();
+#ifdef EXPERIMENTAL_DCC
+ dcc_ok = 0;
+#endif
smtp_respond(US"451", 3, TRUE, US"temporary local problem");
message_id[0] = 0; /* Indicate no message accepted */
*smtp_reply_ptr = US""; /* Indicate reply already sent */
{
Uunlink(spool_name);
unspool_mbox();
+#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 */
BOOL
receive_msg(BOOL extract_recip)
{
-int i, rc;
+int i;
+int rc = FAIL;
int msg_size = 0;
int process_info_len = Ustrlen(process_info);
int error_rc = (error_handling == ERRORS_SENDER)?
int start, end, domain, size, sptr;
int id_resolution;
int had_zero = 0;
+int prevlines_length = 0;
register int ptr = 0;
spool_name[0] = 0;
message_size = 0;
warning_count = 0;
-received_count = 1; /* For the one we will add */
+received_count = 1; /* For the one we will add */
if (thismessage_size_limit <= 0) thismessage_size_limit = INT_MAX;
/* While reading the message, the following counts are computed. */
-message_linecount = body_linecount = body_zerocount = 0;
+message_linecount = body_linecount = body_zerocount =
+ max_received_linelength = 0;
-#ifdef EXPERIMENTAL_DOMAINKEYS
-/* Call into DK to set up the context. Check if DK is to be run are carried out
- inside dk_exim_verify_init(). */
-dk_exim_verify_init();
+#ifndef DISABLE_DKIM
+/* Call into DKIM to set up the context. */
+if (smtp_input && !smtp_batched_input && !dkim_disable_verify) dkim_exim_verify_init();
#endif
/* Remember the time of reception. Exim uses time+pid for uniqueness of message
for (;;)
{
- int ch = (RECEIVE_GETC)();
+ int ch = (receive_getc)();
/* If we hit EOF on a SMTP connection, it's an error, since incoming
SMTP must have a correct "." terminator. */
if (ch == '\n')
{
if (first_line_ended_crlf == TRUE_UNSET) first_line_ended_crlf = FALSE;
- else if (first_line_ended_crlf) RECEIVE_UNGETC(' ');
+ else if (first_line_ended_crlf) receive_ungetc(' ');
goto EOL;
}
if (ptr == 0 && ch == '.' && (smtp_input || dot_ends))
{
- ch = (RECEIVE_GETC)();
+ ch = (receive_getc)();
if (ch == '\r')
{
- ch = (RECEIVE_GETC)();
+ ch = (receive_getc)();
if (ch != '\n')
{
- RECEIVE_UNGETC(ch);
+ receive_ungetc(ch);
ch = '\r'; /* Revert to CR */
}
}
if (ch == '\r')
{
- ch = (RECEIVE_GETC)();
+ ch = (receive_getc)();
if (ch == '\n')
{
if (first_line_ended_crlf == TRUE_UNSET) first_line_ended_crlf = TRUE;
/* Otherwise, put back the character after CR, and turn the bare CR
into LF SP. */
- ch = (RECEIVE_UNGETC)(ch);
+ ch = (receive_ungetc)(ch);
next->text[ptr++] = '\n';
message_size++;
ch = ' ';
receive_linecount++;
message_linecount++;
+ /* Keep track of maximum line length */
+
+ if (ptr - prevlines_length > max_received_linelength)
+ max_received_linelength = ptr - prevlines_length;
+ prevlines_length = ptr + 1;
+
/* Now put in the terminating newline. There is always space for
at least two more characters. */
if (ch != EOF)
{
- int nextch = (RECEIVE_GETC)();
+ int nextch = (receive_getc)();
if (nextch == ' ' || nextch == '\t')
{
next->text[ptr++] = nextch;
message_size++;
continue; /* Iterate the loop */
}
- else if (nextch != EOF) (RECEIVE_UNGETC)(nextch); /* For next time */
+ else if (nextch != EOF) (receive_ungetc)(nextch); /* For next time */
else ch = EOF; /* Cause main loop to exit at end */
}
next->text = store_get(header_size);
ptr = 0;
had_zero = 0;
+ prevlines_length = 0;
} /* Continue, starting to read the next header */
/* At this point, we have read all the headers into a data structure in main
/* Record whether a Date: or Resent-Date: header exists, as appropriate. */
case htype_date:
- date_header_exists = !resents_exist || is_resent;
+ if (!resents_exist || is_resent) date_header_exists = TRUE;
break;
/* Same comments as about Return-Path: below. */
}
}
- /* Add the header line */
+ /* Add the header line
+ * Resent-* headers are prepended, per RFC 5322 3.6.6. Non-Resent-* are
+ * appended, to preserve classical expectations of header ordering. */
- header_add(htype_id, "%sMessage-Id: <%s%s%s@%s>\n", resent_prefix,
- message_id_external, (*id_text == 0)? "" : ".", id_text, id_domain);
+ header_add_at_position(!resents_exist, NULL, FALSE, htype_id,
+ "%sMessage-Id: <%s%s%s@%s>\n", resent_prefix, message_id_external,
+ (*id_text == 0)? "" : ".", id_text, id_domain);
}
/* If we are to log recipients, keep a copy of the raw ones before any possible
if (sender_address_unrewritten == NULL)
sender_address_unrewritten = sender_address;
sender_address = generated_sender_address;
- log_write(L_address_rewrite, LOG_MAIN,
- "\"%s\" from env-from rewritten as \"%s\" by submission mode",
- sender_address_unrewritten, generated_sender_address);
+ if (Ustrcmp(sender_address_unrewritten, generated_sender_address) != 0)
+ log_write(L_address_rewrite, LOG_MAIN,
+ "\"%s\" from env-from rewritten as \"%s\" by submission mode",
+ sender_address_unrewritten, generated_sender_address);
}
}
/* If there is no date header, generate one if the message originates locally
(i.e. not over TCP/IP) and suppress_local_fixups is not set, or if the
submission mode flag is set. Messages without Date: are not valid, but it seems
-to be more confusing if Exim adds one to all remotely-originated messages. */
+to be more confusing if Exim adds one to all remotely-originated messages.
+As per Message-Id, we prepend if resending, else append.
+*/
if (!date_header_exists &&
((sender_host_address == NULL && !suppress_local_fixups)
|| submission_mode))
- header_add(htype_other, "%sDate: %s\n", resent_prefix, tod_stamp(tod_full));
+ header_add_at_position(!resents_exist, NULL, FALSE, htype_other,
+ "%sDate: %s\n", resent_prefix, tod_stamp(tod_full));
search_tidyup(); /* Free any cached resources */
if (smtp_input && !smtp_batched_input)
{
-#ifdef EXPERIMENTAL_DOMAINKEYS
- dk_exim_verify_finish();
-#endif
+#ifndef DISABLE_DKIM
+ if (!dkim_disable_verify)
+ {
+ /* Finish verification, this will log individual signature results to
+ the mainlog */
+ 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'))
+ {
+ uschar *dkim_verify_signers_expanded =
+ expand_string(dkim_verify_signers);
+ if (dkim_verify_signers_expanded == NULL)
+ {
+ 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;
+ 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)
+ {
+ /* 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)
+ {
+ 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;
+ }
+ }
+
+ if (seen_this_item > 0)
+ {
+ DEBUG(D_receive)
+ 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,item);
+ seen_items[seen_items_offset] = '\0';
+
+ DEBUG(D_receive)
+ 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);
+
+ if (rc != OK)
+ {
+ DEBUG(D_receive)
+ debug_printf("acl_smtp_dkim: acl_check returned %d on %s, skipping remaining items\n", rc, item);
+ break;
+ }
+ }
+ add_acl_headers(US"DKIM");
+ if (rc == DISCARD)
+ {
+ recipients_count = 0;
+ blackholed_by = US"DKIM ACL";
+ if (log_msg != NULL)
+ blackhole_log_msg = string_sprintf(": %s", log_msg);
+ }
+ else if (rc != OK)
+ {
+ 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_reply = US""; /* Indicate reply already sent */
+ message_id[0] = 0; /* Indicate no message accepted */
+ goto TIDYUP; /* Skip to end of function */
+ }
+ }
+ }
+ }
+#endif /* DISABLE_DKIM */
#ifdef WITH_CONTENT_SCAN
- if (acl_smtp_mime != NULL &&
+ if (recipients_count > 0 &&
+ acl_smtp_mime != NULL &&
!run_mime_acl(acl_smtp_mime, &smtp_yield, &smtp_reply, &blackholed_by))
goto TIDYUP;
#endif /* WITH_CONTENT_SCAN */
Uunlink(spool_name);
#ifdef WITH_CONTENT_SCAN
unspool_mbox();
+#endif
+#ifdef EXPERIMENTAL_DCC
+ 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 */
Uunlink(spool_name);
#ifdef WITH_CONTENT_SCAN
unspool_mbox();
+#endif
+#ifdef EXPERIMENTAL_DCC
+ dcc_ok = 0;
#endif
/* The ACL can specify where rejections are to be logged, possibly
nowhere. The default is main and reject logs. */
unspool_mbox();
#endif
+#ifdef EXPERIMENTAL_DCC
+dcc_ok = 0;
+#endif
+
+
/* The final check on the message is to run the scan_local() function. The
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
s = string_append(s, &size, &sptr, 2, US" CV=",
tls_certificate_verified? "yes":"no");
if ((log_extra_selector & LX_tls_peerdn) != 0 && tls_peerdn != NULL)
- s = string_append(s, &size, &sptr, 3, US" DN=\"", tls_peerdn, US"\"");
+ s = string_append(s, &size, &sptr, 3, US" DN=\"",
+ string_printing(tls_peerdn), US"\"");
#endif
if (sender_host_authenticated != NULL)
receive_call_bombout = TRUE;
-/* Before sending an SMTP response in a TCP/IP session, we check to see if
-there is unconsumed input (which there shouldn't be) or if the connection has
-gone away. This can be done because the end of a message is always a
-synchronization point. If the connection is still present, but there is no
-pending input, the result of a select() call will be zero. If, however, the
-connection has gone away, or if there is pending input, the result of select()
-will be non-zero. The two cases can be distinguished by trying to read the next
-input character. 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.
+/* 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
+input to be sent following the final dot, so the presence of following input is
+not an error.
+
+If the connection is still present, but there is no unread input for the
+socket, the result of a select() call will be zero. If, however, the connection
+has gone away, or if there is pending input, the result of select() will be
+non-zero. The two cases can be distinguished by trying to read the next input
+character. If we succeed, we can unread it so that it remains in the local
+buffer for handling later. If not, the connection has been lost.
-We also check for input that has already been received and is in the local
-input buffer (plain SMTP or TLS) by calling receive_smtp_buffered(). */
+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 != NULL && !sender_host_notsocket)
+if (smtp_input && sender_host_address != NULL && !sender_host_notsocket &&
+ !receive_smtp_buffered())
{
struct timeval tv;
fd_set select_check;
tv.tv_sec = 0;
tv.tv_usec = 0;
- if (select(fileno(smtp_in) + 1, &select_check, NULL, NULL, &tv) != 0 ||
- receive_smtp_buffered())
+ if (select(fileno(smtp_in) + 1, &select_check, NULL, NULL, &tv) != 0)
{
- uschar *msg;
- if ((RECEIVE_GETC)() == EOF)
+ int c = (receive_getc)();
+ if (c != EOF) (receive_ungetc)(c); else
{
- msg = US"SMTP connection lost after final dot";
- smtp_reply = US""; /* No attempt to send a response */
- }
- else
- {
- msg = US"Synchronization error (data after final dot)";
- smtp_reply = US"550 Synchronization error (data after final dot)";
- }
+ uschar *msg = US"SMTP connection lost after final dot";
+ smtp_reply = US""; /* No attempt to send a response */
+ smtp_yield = FALSE; /* Nothing more on this connection */
- /* Overwrite the log line workspace */
+ /* Re-use the log line workspace */
- sptr = 0;
- s = string_cat(s, &size, &sptr, msg, Ustrlen(msg));
- s = add_host_info_for_log(s, &size, &sptr);
- s[sptr] = 0;
- log_write(0, LOG_MAIN, "%s", s);
+ sptr = 0;
+ s = string_cat(s, &size, &sptr, msg, Ustrlen(msg));
+ s = add_host_info_for_log(s, &size, &sptr);
+ s[sptr] = 0;
+ log_write(0, LOG_MAIN, "%s", s);
- /* We now have to delete the files for this aborted message. */
+ /* 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/input/%s/%s-D", 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);
+ sprintf(CS spool_name, "%s/input/%s/%s-H", spool_directory,
+ message_subdir, message_id);
+ Uunlink(spool_name);
- /* Do not accept any more messages on this connection. */
+ sprintf(CS spool_name, "%s/msglog/%s/%s", spool_directory,
+ message_subdir, message_id);
+ Uunlink(spool_name);
- smtp_yield = FALSE;
- goto TIDYUP;
+ goto TIDYUP;
+ }
}
}