-/* $Cambridge: exim/src/src/receive.c,v 1.53 2009/11/16 19:50:37 nm4 Exp $ */
-
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2012 */
/* See the file NOTICE for conditions of use and distribution. */
/* Code for receiving a message and setting up spool files. */
Returns: One of the END_xxx values indicating why it stopped reading
*/
+/*XXX cutthrough - need to copy to destination, not including the
+ terminating dot, canonicalizing newlines.
+*/
static int
read_message_data_smtp(FILE *fout)
{
int ch_state = 0;
-register int ch;
+int ch;
register int linelength = 0;
while ((ch = (receive_getc)()) != EOF)
{
message_size++;
if (fout != NULL && fputc('\n', fout) == EOF) return END_WERROR;
+ (void) cutthrough_put_nl();
if (ch != '\r') ch_state = 1; else continue;
}
break;
message_size++;
body_linecount++;
if (fout != NULL && fputc('\n', fout) == EOF) return END_WERROR;
+ (void) cutthrough_put_nl();
if (ch == '\r')
{
ch_state = 2;
if (fputc(ch, fout) == EOF) return END_WERROR;
if (message_size > thismessage_size_limit) return END_SIZE;
}
+ if(ch == '\n')
+ (void) cutthrough_put_nl();
+ else
+ {
+ uschar c= ch;
+ (void) cutthrough_puts(&c, 1);
+ }
}
/* Fall through here if EOF encountered. This indicates some kind of error,
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 */
#endif /* WITH_CONTENT_SCAN */
+
+void
+received_header_gen(void)
+{
+uschar *received;
+uschar *timestamp;
+header_line *received_header= header_list;
+
+timestamp = expand_string(US"${tod_full}");
+if (recipients_count == 1) received_for = recipients_list[0].address;
+received = expand_string(received_header_text);
+received_for = NULL;
+
+if (received == NULL)
+ {
+ if(spool_name[0] != 0)
+ Uunlink(spool_name); /* Lose the data file */
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Expansion of \"%s\" "
+ "(received_header_text) failed: %s", string_printing(received_header_text),
+ expand_string_message);
+ }
+
+/* The first element on the header chain is reserved for the Received header,
+so all we have to do is fill in the text pointer, and set the type. However, if
+the result of the expansion is an empty string, we leave the header marked as
+"old" so as to refrain from adding a Received header. */
+
+if (received[0] == 0)
+ {
+ received_header->text = string_sprintf("Received: ; %s\n", timestamp);
+ received_header->type = htype_old;
+ }
+else
+ {
+ received_header->text = string_sprintf("%s; %s\n", received, timestamp);
+ received_header->type = htype_received;
+ }
+
+received_header->slen = Ustrlen(received_header->text);
+
+DEBUG(D_receive) debug_printf(">>Generated Received: header line\n%c %s",
+ received_header->type, received_header->text);
+}
+
+
+
/*************************************************
* Receive message *
*************************************************/
both. The flag sender_local is true for locally generated messages. The flag
submission_mode is true if an ACL has obeyed "control = submission". The flag
suppress_local_fixups is true if an ACL has obeyed "control =
-suppress_local_fixups". The flag smtp_input is true if the message is to be
+suppress_local_fixups" or -G was passed on the command-line.
+The flag smtp_input is true if the message is to be
handled using SMTP conventions about termination and lines starting with dots.
For non-SMTP messages, dot_ends is true for dot-terminated messages.
/* Variables for use when building the Received: header. */
-uschar *received;
uschar *timestamp;
int tslen;
search_tidyup();
+/* Extracting the recipient list from an input file is incompatible with
+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. */
+/*XXX add no-spool */
+if (extract_recip || !smtp_input)
+ cancel_cutthrough_connection();
+
/* 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
pointing to the end of the chain to make adding headers simple. */
case htype_received:
h->type = htype_received;
+/*XXX cutthrough delivery - need to error on excessive number here */
received_count++;
break;
}
}
- /* 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_at_position(FALSE, NULL, FALSE, htype_id,
+ 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 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_at_position(FALSE, NULL, FALSE, 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 */
return message_ended == END_DOT;
}
+/*XXX cutthrough deliver:
+ 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.
+*/
+if (cutthrough_fd >= 0)
+ {
+ received_header_gen();
+ add_acl_headers(US"MAIL or RCPT");
+ (void) cutthrough_headers_send();
+ }
+
+
+/*XXX cutthrough deliver:
+ Here's where we open the data spoolfile. Want to optionally avoid.
+*/
+
/* 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
{
uschar *s = next->text;
int len = next->slen;
+ /*XXX cutthrough - writing the data spool file here. Want to optionally avoid. */
(void)fwrite(s, 1, len, data_file);
body_linecount++; /* Assumes only 1 line */
}
(indicated by '.'), or might have encountered an error while writing the
message id or "next" line. */
+/* XXX cutthrough - no-spool option....... */
if (!ferror(data_file) && !(receive_feof)() && message_ended != END_DOT)
{
if (smtp_input)
{
+ /*XXX cutthrough - writing the data spool file here. Want to optionally avoid. */
+ /* Would suffice to leave data_file arg NULL */
message_ended = read_message_data_smtp(data_file);
receive_linecount++; /* The terminating "." line */
}
if (smtp_input && message_ended == END_EOF)
{
Uunlink(spool_name); /* Lose data file when closed */
+ cancel_cutthrough_connection();
message_id[0] = 0; /* Indicate no message accepted */
smtp_reply = handle_lost_connection(US"");
smtp_yield = FALSE;
if (message_ended == END_SIZE)
{
Uunlink(spool_name); /* Lose the data file when closed */
+ cancel_cutthrough_connection();
if (smtp_input) receive_swallow_smtp(); /* Swallow incoming SMTP */
log_write(L_size_reject, LOG_MAIN|LOG_REJECT, "rejected from <%s>%s%s%s%s: "
the input in cases of output errors, since the far end doesn't expect to see
anything until the terminating dot line is sent. */
+/* XXX cutthrough - no-spool option....... */
if (fflush(data_file) == EOF || ferror(data_file) ||
EXIMfsync(fileno(data_file)) < 0 || (receive_ferror)())
{
log_write(0, LOG_MAIN, "Message abandoned: %s", msg);
Uunlink(spool_name); /* Lose the data file */
+ cancel_cutthrough_connection();
if (smtp_input)
{
/* No I/O errors were encountered while writing the data file. */
+/*XXX cutthrough - avoid message if no-spool option */
DEBUG(D_receive) debug_printf("Data file written for message %s\n", message_id);
exit. (This can't be SMTP, which always ensures there's at least one
syntactically good recipient address.) */
+/*XXX cutthrough - can't if no-spool option. extract_recip is a fn arg.
+ Make incompat with no-spool at fn start. */
+
if (extract_recip && (bad_addresses != NULL || recipients_count == 0))
{
DEBUG(D_receive)
Note: the checking for too many Received: headers is handled by the delivery
code. */
+/*XXX eventually add excess Received: check for cutthrough case back when classifying them */
-timestamp = expand_string(US"${tod_full}");
-if (recipients_count == 1) received_for = recipients_list[0].address;
-received = expand_string(received_header_text);
-received_for = NULL;
-
-if (received == NULL)
- {
- Uunlink(spool_name); /* Lose the data file */
- log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Expansion of \"%s\" "
- "(received_header_text) failed: %s", string_printing(received_header_text),
- expand_string_message);
- }
-
-/* The first element on the header chain is reserved for the Received header,
-so all we have to do is fill in the text pointer, and set the type. However, if
-the result of the expansion is an empty string, we leave the header marked as
-"old" so as to refrain from adding a Received header. */
-
-if (received[0] == 0)
+if (received_header->text == NULL) /* Non-cutthrough case */
{
- received_header->text = string_sprintf("Received: ; %s\n", timestamp);
- received_header->type = htype_old;
- }
-else
- {
- received_header->text = string_sprintf("%s; %s\n", received, timestamp);
- received_header->type = htype_received;
- }
-
-received_header->slen = Ustrlen(received_header->text);
-
-DEBUG(D_receive) debug_printf(">>Generated Received: header line\n%c %s",
- received_header->type, received_header->text);
+ received_header_gen();
-/* Set the value of message_body_size for the DATA ACL and for local_scan() */
+ /* Set the value of message_body_size for the DATA ACL and for local_scan() */
-message_body_size = (fstat(data_fd, &statbuf) == 0)?
- statbuf.st_size - SPOOL_DATA_START_OFFSET : -1;
+ message_body_size = (fstat(data_fd, &statbuf) == 0)?
+ statbuf.st_size - SPOOL_DATA_START_OFFSET : -1;
-/* If an ACL from any RCPT commands set up any warning headers to add, do so
-now, before running the DATA ACL. */
+ /* 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(US"MAIL or RCPT");
+ }
+else if (data_fd >= 0)
+ message_body_size = (fstat(data_fd, &statbuf) == 0)?
+ statbuf.st_size - SPOOL_DATA_START_OFFSET : -1;
+else
+ /*XXX cutthrough - XXX how to get the body size? */
+ /* perhaps a header-size to subtract from message_size? */
+ message_body_size = message_size - 1;
/* If an ACL is specified for checking things at this stage of reception of a
message, run it, unless all the recipients were removed by "discard" in earlier
#ifndef DISABLE_DKIM
if (!dkim_disable_verify)
{
+/* XXX cutthrough - no-spool option....... */
/* Finish verification, this will log individual signature results to
the mainlog */
dkim_exim_verify_finish();
appears in the expanded list. */
if (seen_items != NULL)
{
+ uschar *seen_item = NULL;
+ uschar seen_item_buf[256];
uschar *seen_items_list = seen_items;
- if (match_isinlist(item,
- &seen_items_list,0,NULL,NULL,MCL_STRING,TRUE,NULL) == OK)
+ 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,":");
}
{
DEBUG(D_receive)
debug_printf("acl_smtp_dkim: acl_check returned %d on %s, skipping remaining items\n", rc, item);
+ cancel_cutthrough_connection();
break;
}
}
#endif /* DISABLE_DKIM */
#ifdef WITH_CONTENT_SCAN
+/* XXX cutthrough - no-spool option....... */
if (recipients_count > 0 &&
acl_smtp_mime != NULL &&
!run_mime_acl(acl_smtp_mime, &smtp_yield, &smtp_reply, &blackholed_by))
/* Check the recipients count again, as the MIME ACL might have changed
them. */
+/* XXX cutthrough - no-spool option must document that data-acl has no file access */
+/* but can peek at headers */
+
if (acl_smtp_data != NULL && recipients_count > 0)
{
rc = acl_check(ACL_WHERE_DATA, NULL, acl_smtp_data, &user_msg, &log_msg);
blackholed_by = US"DATA ACL";
if (log_msg != NULL)
blackhole_log_msg = string_sprintf(": %s", log_msg);
+ cancel_cutthrough_connection();
}
else if (rc != OK)
{
Uunlink(spool_name);
+ cancel_cutthrough_connection();
#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. */
os_non_restarting_signal(SIGALRM, local_scan_timeout_handler);
if (local_scan_timeout > 0) alarm(local_scan_timeout);
+/* XXX cutthrough - no-spool option..... */
rc = local_scan(data_fd, &local_scan_data);
alarm(0);
os_non_restarting_signal(SIGALRM, sigalrm_handler);
signal(SIGTERM, SIG_IGN);
signal(SIGINT, SIG_IGN);
+
/* Ensure the first time flag is set in the newly-received message. */
deliver_firsttime = TRUE;
#ifdef EXPERIMENTAL_BRIGHTMAIL
if (bmi_run == 1) {
/* rewind data file */
+ /* XXX cutthrough - no-spool option..... */
lseek(data_fd, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
bmi_verdicts = bmi_process_message(header_list, data_fd);
};
else
{
+ /*XXX cutthrough -
+ Optionally want to avoid writing spool files (when no data-time filtering needed) */
+
if ((msg_size = spool_write_header(message_id, SW_RECEIVING, &errmsg)) < 0)
{
log_write(0, LOG_MAIN, "Message abandoned: %s", errmsg);
if ((log_extra_selector & LX_tls_peerdn) != 0 && tls_peerdn != NULL)
s = string_append(s, &size, &sptr, 3, US" DN=\"",
string_printing(tls_peerdn), US"\"");
+if ((log_extra_selector & LX_tls_sni) != 0 && tls_sni != NULL)
+ s = string_append(s, &size, &sptr, 3, US" SNI=\"",
+ string_printing(tls_sni), US"\"");
#endif
if (sender_host_authenticated != NULL)
/* Create a message log file if message logs are being used and this message is
not blackholed. Write the reception stuff to it. We used to leave message log
-creation until the first delivery, but this has proved confusing for somep
+creation until the first delivery, but this has proved confusing for some
people. */
if (message_logs && blackholed_by == NULL)
/* The connection has not gone away; we really are going to take responsibility
for this message. */
-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),
- "%s", s);
-receive_call_bombout = FALSE;
+/*XXX cutthrough - had sender last-dot; assume we've sent or bufferred all
+ data onward by now.
-/* Log any control actions taken by an ACL or local_scan(). */
+ Send dot onward. If accepted, can the spooled files, log as delivered and accept
+ the sender's dot (below).
+ If not accepted: copy response to sender, can the spooled files, log approriately.
-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);
+ Having the normal spool files lets us do data-filtering, and store/forward on temp-reject.
+*/
+if(cutthrough_fd >= 0)
+ {
+ uschar * msg= cutthrough_finaldot(); /* Ask the target system to accept the messsage */
+ switch(msg[0])
+ {
+ case '2': /* Accept. Do the same to the source; dump any spoolfiles. */
+ /* logging was done in finaldot() */
+ if(data_file != NULL)
+ {
+ 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);
+ }
+ break;
+
+ default: /* Unknown response, or error. Treat as temp-reject. */
+ case '4': /* Temp-reject. If we wrote spoolfiles, keep them and accept. */
+ /* If not, temp-reject the source. */
+ /*XXX could we mark the spoolfile queue-only or already-tried? */
+ log_write(0, LOG_MAIN, "cutthrough target temp-reject: %s", msg);
+ if(data_file == NULL)
+ smtp_reply= msg; /* Pass on the exact error */
+ break;
+
+ case '5': /* Perm-reject. Do the same to the source. Dump any spoolfiles */
+ log_write(0, LOG_MAIN, "cutthrough target perm-reject: %s", msg);
+ smtp_reply= msg; /* Pass on the exact error */
+ if(data_file != NULL)
+ {
+ 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);
+ }
+ break;
+ }
+ }
+
+if(smtp_reply == NULL)
+ {
+ 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),
+ "%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);
+ }
+receive_call_bombout = FALSE;
store_reset(s); /* The store for the main log message can be reused */
possible for fclose() to fail - but what to do? What has happened to the lock
if this happens? */
+
TIDYUP:
process_info[process_info_len] = 0; /* Remove message id */
if (data_file != NULL) (void)fclose(data_file); /* Frees the lock */
if (!smtp_batched_input)
{
+/*XXX cutthrough - here's where the originating sender gets given the data-acceptance */
if (smtp_reply == NULL)
{
if (fake_response != OK)
else
smtp_printf("%.1024s\r\n", smtp_reply);
}
+
+ if (cutthrough_delivery)
+ {
+ log_write(0, LOG_MAIN, "Completed");
+ message_id[0] = 0; /* Prevent a delivery from starting */
+ }
}
/* For batched SMTP, generate an error message on failure, and do