From e4bdf6524e2611c6bc0279b33037c31eec6daa35 Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Thu, 26 Apr 2012 23:59:34 +0100 Subject: [PATCH 1/1] Basic cutthrough delivery. --- src/src/acl.c | 77 ++++++++++++- src/src/deliver.c | 265 ++++++++++++++++++++++++-------------------- src/src/functions.h | 10 ++ src/src/globals.c | 2 + src/src/globals.h | 2 + src/src/receive.c | 256 +++++++++++++++++++++++++++++++++--------- src/src/smtp_in.c | 1 + src/src/verify.c | 249 ++++++++++++++++++++++++++++++++++++++++- 8 files changed, 689 insertions(+), 173 deletions(-) diff --git a/src/src/acl.c b/src/src/acl.c index 505ccf949..d0688d19a 100644 --- a/src/src/acl.c +++ b/src/src/acl.c @@ -177,6 +177,7 @@ enum { CONTROL_ERROR, CONTROL_CASEFUL_LOCAL_PART, CONTROL_CASELOWER_LOCAL_PART, + CONTROL_CUTTHROUGH_DELIVERY, CONTROL_ENFORCE_SYNC, CONTROL_NO_ENFORCE_SYNC, CONTROL_FREEZE, @@ -212,6 +213,7 @@ static uschar *controls[] = { US"error", US"caseful_local_part", US"caselower_local_part", + US"cutthrough_delivery", US"enforce_sync", US"no_enforce_sync", US"freeze", @@ -538,6 +540,9 @@ static unsigned int control_forbids[] = { (unsigned int) ~(1<next) case CONTROL_SUPPRESS_LOCAL_FIXUPS: suppress_local_fixups = TRUE; break; + + case CONTROL_CUTTHROUGH_DELIVERY: + if (deliver_freeze) + { + *log_msgptr = string_sprintf("\"control=%s\" on frozen item", arg); + return ERROR; + } + if (queue_only_policy) + { + *log_msgptr = string_sprintf("\"control=%s\" on queue-only item", arg); + return ERROR; + } + cutthrough_delivery = TRUE; + break; } break; @@ -3895,6 +3915,61 @@ if (where == ACL_WHERE_RCPT) rc = acl_check_internal(where, addr, s, 0, user_msgptr, log_msgptr); +/*XXX cutthrough - if requested, +and WHERE_RCPT and not yet opened conn as reult of verify, +and rc==OK +open one now and run it up to RCPT acceptance. +Query: what to do with xple rcpts? Avoid for now by only doing on 1st, and +cancelling on any subsequents. +A failed verify should cancel cutthrough request. +For now, ensure we only accept requests to cutthrough pre-data. Maybe relax that later. + +On a pre-data acl, if not accept and a cutthrough conn is open, close it. If accept and +a cutthrough conn is open, send DATA command and setup byte-by-byte copy mode and +cancel spoolfile-write mode. +NB this means no DATA acl, no content checking - might want an option for that?. + +Initial implementation: dual-write to spool (do the no-spool later). +Assume the rxd datastream is now being copied byte-for-byte to an open cutthrough connection. + +Cease cutthrough copy on rxd final dot; do not send one. + +On a data acl, if not accept and a cutthrough conn is open, hard-close it (no SMTP niceness). + +On data acl accept, terminate the dataphase on an open cutthrough conn. If accepted or +perm-rejected, reflect that to the original sender - and dump the spooled copy. +If temp-reject, close the conn (and keep the spooled copy). +If conn-failure, no action (and keep the spooled copy). + + +XXX What about TLS? Callouts never seem to do it atm. but we ought to support it eventually. +XXX What about pipelining? Callouts don't, and we probably don't care too much. +*/ +switch (where) +{ +case ACL_WHERE_RCPT: + if( rcpt_count > 1 ) + cancel_cutthrough_connection(); + else if (rc == OK && cutthrough_delivery && cutthrough_fd < 0) + open_cutthrough_connection(addr); + break; + +case ACL_WHERE_PREDATA: + if( rc == OK ) + cutthrough_predata(); + else + cancel_cutthrough_connection(); + break; + +case ACL_WHERE_QUIT: +case ACL_WHERE_NOTQUIT: + cancel_cutthrough_connection(); + break; + +default: + break; +} + deliver_domain = deliver_localpart = deliver_address_data = sender_address_data = NULL; diff --git a/src/src/deliver.c b/src/src/deliver.c index d4ea2d868..b4d0251a7 100644 --- a/src/src/deliver.c +++ b/src/src/deliver.c @@ -673,6 +673,136 @@ while (addr->parent != NULL) +void +delivery_log(address_item * addr, int logchar) +{ +uschar *log_address; +int size = 256; /* Used for a temporary, */ +int ptr = 0; /* expanding buffer, for */ +uschar *s; /* building log lines; */ +void *reset_point; /* released afterwards. */ + + +/* Log the delivery on the main log. We use an extensible string to build up +the log line, and reset the store afterwards. Remote deliveries should always +have a pointer to the host item that succeeded; local deliveries can have a +pointer to a single host item in their host list, for use by the transport. */ + +s = reset_point = store_get(size); +s[ptr++] = logchar; + +log_address = string_log_address(addr, (log_write_selector & L_all_parents) != 0, TRUE); +s = string_append(s, &size, &ptr, 2, US"> ", log_address); + +if ((log_extra_selector & LX_sender_on_delivery) != 0) + s = string_append(s, &size, &ptr, 3, US" F=<", sender_address, US">"); + +#ifdef EXPERIMENTAL_SRS +if(addr->p.srs_sender) + s = string_append(s, &size, &ptr, 3, US" SRS=<", addr->p.srs_sender, US">"); +#endif + +/* You might think that the return path must always be set for a successful +delivery; indeed, I did for some time, until this statement crashed. The case +when it is not set is for a delivery to /dev/null which is optimised by not +being run at all. */ + +if (used_return_path != NULL && + (log_extra_selector & LX_return_path_on_delivery) != 0) + s = string_append(s, &size, &ptr, 3, US" P=<", used_return_path, US">"); + +/* For a delivery from a system filter, there may not be a router */ + +if (addr->router != NULL) + s = string_append(s, &size, &ptr, 2, US" R=", addr->router->name); + +s = string_append(s, &size, &ptr, 2, US" T=", addr->transport->name); + +if ((log_extra_selector & LX_delivery_size) != 0) + s = string_append(s, &size, &ptr, 2, US" S=", + string_sprintf("%d", transport_count)); + +/* Local delivery */ + +if (addr->transport->info->local) + { + if (addr->host_list != NULL) + s = string_append(s, &size, &ptr, 2, US" H=", addr->host_list->name); + if (addr->shadow_message != NULL) + s = string_cat(s, &size, &ptr, addr->shadow_message, + Ustrlen(addr->shadow_message)); + } + +/* Remote delivery */ + +else + { + if (addr->host_used != NULL) + { + s = string_append(s, &size, &ptr, 5, US" H=", addr->host_used->name, + US" [", addr->host_used->address, US"]"); + if ((log_extra_selector & LX_outgoing_port) != 0) + s = string_append(s, &size, &ptr, 2, US":", string_sprintf("%d", + addr->host_used->port)); + if (continue_sequence > 1) + s = string_cat(s, &size, &ptr, US"*", 1); + } + + #ifdef SUPPORT_TLS + if ((log_extra_selector & LX_tls_cipher) != 0 && addr->cipher != NULL) + s = string_append(s, &size, &ptr, 2, US" X=", addr->cipher); + if ((log_extra_selector & LX_tls_certificate_verified) != 0 && + addr->cipher != NULL) + s = string_append(s, &size, &ptr, 2, US" CV=", + testflag(addr, af_cert_verified)? "yes":"no"); + if ((log_extra_selector & LX_tls_peerdn) != 0 && addr->peerdn != NULL) + s = string_append(s, &size, &ptr, 3, US" DN=\"", + string_printing(addr->peerdn), US"\""); + #endif + + if ((log_extra_selector & LX_smtp_confirmation) != 0 && + addr->message != NULL) + { + int i; + uschar *p = big_buffer; + uschar *ss = addr->message; + *p++ = '\"'; + for (i = 0; i < 100 && ss[i] != 0; i++) + { + if (ss[i] == '\"' || ss[i] == '\\') *p++ = '\\'; + *p++ = ss[i]; + } + *p++ = '\"'; + *p = 0; + s = string_append(s, &size, &ptr, 2, US" C=", big_buffer); + } + } + +/* Time on queue and actual time taken to deliver */ + +if ((log_extra_selector & LX_queue_time) != 0) + { + s = string_append(s, &size, &ptr, 2, US" QT=", + readconf_printtime(time(NULL) - received_time)); + } + +if ((log_extra_selector & LX_deliver_time) != 0) + { + s = string_append(s, &size, &ptr, 2, US" DT=", + readconf_printtime(addr->more_errno)); + } + +/* string_cat() always leaves room for the terminator. Release the +store we used to build the line after writing it. */ + +s[ptr] = 0; +log_write(0, LOG_MAIN, "%s", s); +store_reset(reset_point); +return; +} + + + /************************************************* * Actions at the end of handling an address * *************************************************/ @@ -835,12 +965,6 @@ if (addr->return_file >= 0 && addr->return_filename != NULL) (void)close(addr->return_file); } -/* Create the address string for logging. Must not do this earlier, because -an OK result may be changed to FAIL when a pipe returns text. */ - -log_address = string_log_address(addr, - (log_write_selector & L_all_parents) != 0, result == OK); - /* The sucess case happens only after delivery by a transport. */ if (result == OK) @@ -868,120 +992,7 @@ if (result == OK) child_done(addr, now); } - /* Log the delivery on the main log. We use an extensible string to build up - the log line, and reset the store afterwards. Remote deliveries should always - have a pointer to the host item that succeeded; local deliveries can have a - pointer to a single host item in their host list, for use by the transport. */ - - s = reset_point = store_get(size); - s[ptr++] = logchar; - - s = string_append(s, &size, &ptr, 2, US"> ", log_address); - - if ((log_extra_selector & LX_sender_on_delivery) != 0) - s = string_append(s, &size, &ptr, 3, US" F=<", sender_address, US">"); - - #ifdef EXPERIMENTAL_SRS - if(addr->p.srs_sender) - s = string_append(s, &size, &ptr, 3, US" SRS=<", addr->p.srs_sender, US">"); - #endif - - /* You might think that the return path must always be set for a successful - delivery; indeed, I did for some time, until this statement crashed. The case - when it is not set is for a delivery to /dev/null which is optimised by not - being run at all. */ - - if (used_return_path != NULL && - (log_extra_selector & LX_return_path_on_delivery) != 0) - s = string_append(s, &size, &ptr, 3, US" P=<", used_return_path, US">"); - - /* For a delivery from a system filter, there may not be a router */ - - if (addr->router != NULL) - s = string_append(s, &size, &ptr, 2, US" R=", addr->router->name); - - s = string_append(s, &size, &ptr, 2, US" T=", addr->transport->name); - - if ((log_extra_selector & LX_delivery_size) != 0) - s = string_append(s, &size, &ptr, 2, US" S=", - string_sprintf("%d", transport_count)); - - /* Local delivery */ - - if (addr->transport->info->local) - { - if (addr->host_list != NULL) - s = string_append(s, &size, &ptr, 2, US" H=", addr->host_list->name); - if (addr->shadow_message != NULL) - s = string_cat(s, &size, &ptr, addr->shadow_message, - Ustrlen(addr->shadow_message)); - } - - /* Remote delivery */ - - else - { - if (addr->host_used != NULL) - { - s = string_append(s, &size, &ptr, 5, US" H=", addr->host_used->name, - US" [", addr->host_used->address, US"]"); - if ((log_extra_selector & LX_outgoing_port) != 0) - s = string_append(s, &size, &ptr, 2, US":", string_sprintf("%d", - addr->host_used->port)); - if (continue_sequence > 1) - s = string_cat(s, &size, &ptr, US"*", 1); - } - - #ifdef SUPPORT_TLS - if ((log_extra_selector & LX_tls_cipher) != 0 && addr->cipher != NULL) - s = string_append(s, &size, &ptr, 2, US" X=", addr->cipher); - if ((log_extra_selector & LX_tls_certificate_verified) != 0 && - addr->cipher != NULL) - s = string_append(s, &size, &ptr, 2, US" CV=", - testflag(addr, af_cert_verified)? "yes":"no"); - if ((log_extra_selector & LX_tls_peerdn) != 0 && addr->peerdn != NULL) - s = string_append(s, &size, &ptr, 3, US" DN=\"", - string_printing(addr->peerdn), US"\""); - #endif - - if ((log_extra_selector & LX_smtp_confirmation) != 0 && - addr->message != NULL) - { - int i; - uschar *p = big_buffer; - uschar *ss = addr->message; - *p++ = '\"'; - for (i = 0; i < 100 && ss[i] != 0; i++) - { - if (ss[i] == '\"' || ss[i] == '\\') *p++ = '\\'; - *p++ = ss[i]; - } - *p++ = '\"'; - *p = 0; - s = string_append(s, &size, &ptr, 2, US" C=", big_buffer); - } - } - - /* Time on queue and actual time taken to deliver */ - - if ((log_extra_selector & LX_queue_time) != 0) - { - s = string_append(s, &size, &ptr, 2, US" QT=", - readconf_printtime(time(NULL) - received_time)); - } - - if ((log_extra_selector & LX_deliver_time) != 0) - { - s = string_append(s, &size, &ptr, 2, US" DT=", - readconf_printtime(addr->more_errno)); - } - - /* string_cat() always leaves room for the terminator. Release the - store we used to build the line after writing it. */ - - s[ptr] = 0; - log_write(0, LOG_MAIN, "%s", s); - store_reset(reset_point); + delivery_log(addr, logchar); } @@ -1029,6 +1040,13 @@ else if (result == DEFER || result == PANIC) log. */ s = reset_point = store_get(size); + + /* Create the address string for logging. Must not do this earlier, because + an OK result may be changed to FAIL when a pipe returns text. */ + + log_address = string_log_address(addr, + (log_write_selector & L_all_parents) != 0, result == OK); + s = string_cat(s, &size, &ptr, log_address, Ustrlen(log_address)); /* Either driver_name contains something and driver_kind contains @@ -1129,6 +1147,13 @@ else /* Build up the log line for the message and main logs */ s = reset_point = store_get(size); + + /* Create the address string for logging. Must not do this earlier, because + an OK result may be changed to FAIL when a pipe returns text. */ + + log_address = string_log_address(addr, + (log_write_selector & L_all_parents) != 0, result == OK); + s = string_cat(s, &size, &ptr, log_address, Ustrlen(log_address)); if ((log_extra_selector & LX_sender_on_delivery) != 0) diff --git a/src/src/functions.h b/src/src/functions.h index 3e78aa639..aa3d18404 100644 --- a/src/src/functions.h +++ b/src/src/functions.h @@ -66,10 +66,17 @@ extern int auth_get_no64_data(uschar **, uschar *); extern uschar *auth_xtextencode(uschar *, int); extern int auth_xtextdecode(uschar *, uschar **); +extern void cancel_cutthrough_connection(void); extern int check_host(void *, uschar *, uschar **, uschar **); extern uschar **child_exec_exim(int, BOOL, int *, BOOL, int, ...); extern pid_t child_open_uid(uschar **, uschar **, int, uid_t *, gid_t *, int *, int *, uschar *, BOOL); +extern uschar *cutthrough_finaldot(void); +extern BOOL cutthrough_flush_send(void); +extern BOOL cutthrough_headers_send(void); +extern BOOL cutthrough_predata(void); +extern BOOL cutthrough_puts(uschar *, int); +extern BOOL cutthrough_put_nl(void); extern void daemon_go(void); @@ -86,6 +93,7 @@ extern void debug_vprintf(const char *, va_list); extern void decode_bits(unsigned int *, unsigned int *, int, int, uschar *, bit_table *, int, uschar *, int); extern address_item *deliver_make_addr(uschar *, BOOL); +extern void delivery_log(address_item *, int); extern int deliver_message(uschar *, BOOL, BOOL); extern void deliver_msglog(const char *, ...) PRINTF_FUNCTION(1,2); extern void deliver_set_expansions(address_item *); @@ -198,6 +206,8 @@ extern BOOL moan_to_sender(int, error_block *, header_line *, FILE *, BOOL); extern void moan_write_from(FILE *); extern FILE *modefopen(const uschar *, const char *, mode_t); +extern void open_cutthrough_connection( address_item * addr ); + extern uschar *parse_extract_address(uschar *, uschar **, int *, int *, int *, BOOL); extern int parse_forward_list(uschar *, int, address_item **, uschar **, diff --git a/src/src/globals.c b/src/src/globals.c index fa7416ed7..60b5a27d7 100644 --- a/src/src/globals.c +++ b/src/src/globals.c @@ -429,6 +429,8 @@ int continue_sequence = 1; uschar *continue_transport = NULL; uschar *csa_status = NULL; +BOOL cutthrough_delivery = FALSE; +int cutthrough_fd = -1; BOOL daemon_listen = FALSE; uschar *daemon_smtp_port = US"smtp"; diff --git a/src/src/globals.h b/src/src/globals.h index 98b9a1950..dd28b914b 100644 --- a/src/src/globals.h +++ b/src/src/globals.h @@ -251,6 +251,8 @@ extern int continue_sequence; /* Sequence num for continued delivery */ extern uschar *continue_transport; /* Transport for continued delivery */ extern uschar *csa_status; /* Client SMTP Authorization result */ +extern BOOL cutthrough_delivery; /* Deliver in foreground */ +extern int cutthrough_fd; /* Connection for ditto */ extern BOOL daemon_listen; /* True if listening required */ extern uschar *daemon_smtp_port; /* Can be a list of ports */ diff --git a/src/src/receive.c b/src/src/receive.c index 2c1b38499..108e8d436 100644 --- a/src/src/receive.c +++ b/src/src/receive.c @@ -716,12 +716,15 @@ Arguments: 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) @@ -768,6 +771,7 @@ 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; @@ -788,6 +792,7 @@ while ((ch = (receive_getc)()) != EOF) message_size++; body_linecount++; if (fout != NULL && fputc('\n', fout) == EOF) return END_WERROR; + (void) cutthrough_put_nl(); if (ch == '\r') { ch_state = 2; @@ -807,6 +812,13 @@ while ((ch = (receive_getc)()) != EOF) 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, @@ -1197,6 +1209,52 @@ return TRUE; #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 * *************************************************/ @@ -1346,7 +1404,6 @@ header_line *received_header; /* Variables for use when building the Received: header. */ -uschar *received; uschar *timestamp; int tslen; @@ -1356,6 +1413,13 @@ might take a fair bit of real time. */ 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. */ @@ -1974,6 +2038,7 @@ for (h = header_list->next; h != NULL; h = h->next) case htype_received: h->type = htype_received; +/*XXX cutthrough delivery - need to error on excessive number here */ received_count++; break; @@ -2645,6 +2710,23 @@ if (filter_test != FTEST_NONE) 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 @@ -2701,6 +2783,7 @@ if (next != NULL) { 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 */ } @@ -2709,10 +2792,13 @@ if (next != NULL) (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 */ } @@ -2726,6 +2812,7 @@ if (!ferror(data_file) && !(receive_feof)() && message_ended != END_DOT) 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; @@ -2738,6 +2825,7 @@ if (!ferror(data_file) && !(receive_feof)() && message_ended != END_DOT) 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: " @@ -2781,6 +2869,7 @@ we can then give up. Note that for SMTP input we must swallow the remainder of 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)()) { @@ -2793,6 +2882,7 @@ if (fflush(data_file) == EOF || ferror(data_file) || log_write(0, LOG_MAIN, "Message abandoned: %s", msg); Uunlink(spool_name); /* Lose the data file */ + cancel_cutthrough_connection(); if (smtp_input) { @@ -2819,6 +2909,7 @@ if (fflush(data_file) == EOF || ferror(data_file) || /* 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); @@ -2833,6 +2924,9 @@ recipients or stderr error writing, throw the data file away afterwards, and 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) @@ -2912,50 +3006,29 @@ for use when we generate the Received: header. 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) +if (received_header->text == NULL) /* Non-cutthrough case */ { - 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); - } + received_header_gen(); -/* 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); - -/* 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 @@ -2983,6 +3056,7 @@ else #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(); @@ -3060,6 +3134,7 @@ else { DEBUG(D_receive) debug_printf("acl_smtp_dkim: acl_check returned %d on %s, skipping remaining items\n", rc, item); + cancel_cutthrough_connection(); break; } } @@ -3086,6 +3161,7 @@ else #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)) @@ -3095,6 +3171,9 @@ else /* 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); @@ -3105,10 +3184,12 @@ else 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 @@ -3219,6 +3300,7 @@ local_scan_data = NULL; 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); @@ -3366,6 +3448,7 @@ the message to be abandoned. */ signal(SIGTERM, SIG_IGN); signal(SIGINT, SIG_IGN); + /* Ensure the first time flag is set in the newly-received message. */ deliver_firsttime = TRUE; @@ -3373,6 +3456,7 @@ 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); }; @@ -3414,6 +3498,9 @@ if (host_checking || blackholed_by != NULL) 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); @@ -3551,7 +3638,7 @@ s[sptr] = 0; /* 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) @@ -3672,17 +3759,78 @@ if (smtp_input && sender_host_address != NULL && !sender_host_notsocket && /* 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. + + 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. + + 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; + } + } -/* Log any control actions taken by an ACL or local_scan(). */ +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); + 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 */ @@ -3710,6 +3858,7 @@ data file will be flushed(!) out thereby. Nevertheless, it is theoretically 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 */ @@ -3735,6 +3884,7 @@ if (smtp_input) 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) @@ -3770,6 +3920,12 @@ if (smtp_input) 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 diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c index 58d1a971e..38162cd4c 100644 --- a/src/src/smtp_in.c +++ b/src/src/smtp_in.c @@ -1036,6 +1036,7 @@ store_reset(reset_point); recipients_list = NULL; rcpt_count = rcpt_defer_count = rcpt_fail_count = raw_recipients_count = recipients_count = recipients_list_max = 0; +cancel_cutthrough_connection(); message_linecount = 0; message_size = -1; acl_added_headers = NULL; diff --git a/src/src/verify.c b/src/src/verify.c index fed6ae37d..ff18847e7 100644 --- a/src/src/verify.c +++ b/src/src/verify.c @@ -11,6 +11,9 @@ caching was contributed by Kevin Fleming (but I hacked it around a bit). */ #include "exim.h" +#define CUTTHROUGH_CMD_TIMEOUT 30 /* timeout for cutthrough-routing calls */ +#define CUTTHROUGH_DATA_TIMEOUT 60 /* timeout for cutthrough-routing calls */ +address_item cutthrough_addr; /* Structure for caching DNSBL lookups */ @@ -641,6 +644,9 @@ for (host = host_list; host != NULL && !done; host = host->next) if (done && pm_mailfrom != NULL) { + /*XXX not suitable for cutthrough - sequencing problems */ + cutthrough_delivery= FALSE; + done = smtp_write_command(&outblock, FALSE, "RSET\r\n") >= 0 && smtp_read_response(&inblock, responsebuffer, @@ -733,8 +739,32 @@ for (host = host_list; host != NULL && !done; host = host->next) /* End the SMTP conversation and close the connection. */ - if (send_quit) (void)smtp_write_command(&outblock, FALSE, "QUIT\r\n"); - (void)close(inblock.sock); + /*XXX cutthrough - if "done" + and "yeild" is OK + and we have no cutthrough conn so far + here is where we want to leave the conn open */ + /* and leave some form of marker for it */ + /*XXX in fact for simplicity we should abandon cutthrough as soon as more than one address + comes into play */ +/*XXX what about TLS? */ + if ( cutthrough_delivery + && done + && yield == OK + && cutthrough_fd < 0 + && (options & (vopt_callout_recipsender|vopt_callout_recippmaster)) == vopt_callout_recipsender + && !random_local_part + && !pm_mailfrom + ) + { + cutthrough_fd= outblock.sock; /* We assume no buffer in use in the outblock */ + cutthrough_addr= *addr; /* Save the address_item for later logging */ + } + else + { + if (send_quit) (void)smtp_write_command(&outblock, FALSE, "QUIT\r\n"); + (void)close(inblock.sock); + } + } /* Loop through all hosts, while !done */ /* If we get here with done == TRUE, a successful callout happened, and yield @@ -827,6 +857,218 @@ return yield; +void +open_cutthrough_connection( address_item * addr ) +{ +address_item addr2; + +/* Use a recipient-verify-callout to set up the cutthrough connection. */ +/* We must use a copy of the address for verification, because it might +get rewritten. */ + +addr2 = *addr; +HDEBUG(D_acl) debug_printf("----------- start cutthrough setup ------------\n"); +(void) verify_address(&addr2, NULL, + vopt_is_recipient | vopt_callout_recipsender | vopt_callout_no_cache, + CUTTHROUGH_CMD_TIMEOUT, -1, -1, + NULL, NULL, NULL); +HDEBUG(D_acl) debug_printf("----------- end cutthrough setup ------------\n"); +return; +} + + +static smtp_outblock ctblock; +uschar ctbuffer[8192]; + + +void +cancel_cutthrough_connection( void ) +{ +ctblock.ptr = ctbuffer; +cutthrough_delivery= FALSE; +if(cutthrough_fd >= 0) /*XXX get that initialised, also at RSET */ + { + int rc; + + /* We could be sending this after a bunch of data, but that is ok as + the only way to cancel the transfer in dataphase is to drop the tcp + conn before the final dot. + */ + HDEBUG(D_transport|D_acl|D_v) debug_printf(" SMTP>> QUIT\n"); + rc= send(cutthrough_fd, "QUIT\r\n", 6, 0); + /*XXX error handling? TLS? See flush_buffer() in smtp_out.c */ + + (void)close(cutthrough_fd); + cutthrough_fd= -1; + HDEBUG(D_acl) debug_printf("----------- cutthrough shutdown ------------\n"); + } +} + + + +/* Buffered output counted data block. Return boolean success */ +BOOL +cutthrough_puts(uschar * cp, int n) +{ +if(cutthrough_fd >= 0) + while(n--) + { + /*XXX TLS? See flush_buffer() in smtp_out.c */ + + if(ctblock.ptr >= ctblock.buffer+ctblock.buffersize) + { + if(send(cutthrough_fd, ctblock.buffer, ctblock.buffersize, 0) < 0) + goto bad; + transport_count += ctblock.buffersize; + ctblock.ptr= ctblock.buffer; + } + + *ctblock.ptr++ = *cp++; + } +return TRUE; + +bad: +cancel_cutthrough_connection(); +return FALSE; +} + +BOOL +cutthrough_flush_send( void ) +{ +if(cutthrough_fd >= 0) + { + if(send(cutthrough_fd, ctblock.buffer, ctblock.ptr-ctblock.buffer, 0) < 0) + goto bad; + transport_count += ctblock.ptr-ctblock.buffer; + ctblock.ptr= ctblock.buffer; + } +return TRUE; + +bad: +cancel_cutthrough_connection(); +return FALSE; +} + + +BOOL +cutthrough_put_nl( void ) +{ +return cutthrough_puts(US"\r\n", 2); +} + + +/* Get and check response from cutthrough target */ +static uschar +cutthrough_response(char expect, uschar ** copy) +{ +smtp_inblock inblock; +uschar inbuffer[4096]; +uschar responsebuffer[4096]; + +inblock.buffer = inbuffer; +inblock.buffersize = sizeof(inbuffer); +inblock.ptr = inbuffer; +inblock.ptrend = inbuffer; +inblock.sock = cutthrough_fd; +if(!smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), expect, CUTTHROUGH_DATA_TIMEOUT)) + cancel_cutthrough_connection(); + +if(copy != NULL) + { + uschar * cp; + *copy= cp= string_copy(responsebuffer); + /* Trim the trailing end of line */ + cp += Ustrlen(responsebuffer); + if(cp > *copy && cp[-1] == '\n') *--cp = '\0'; + if(cp > *copy && cp[-1] == '\r') *--cp = '\0'; + } + +return responsebuffer[0]; +} + + +/* Negotiate dataphase with the cutthrough target, returning success boolean */ +BOOL +cutthrough_predata( void ) +{ +int rc; + +if(cutthrough_fd < 0) + return FALSE; + +HDEBUG(D_transport|D_acl|D_v) debug_printf(" SMTP>> DATA\n"); +rc= send(cutthrough_fd, "DATA\r\n", 6, 0); +if (rc <= 0) + { + HDEBUG(D_transport|D_acl) debug_printf("send failed: %s\n", strerror(errno)); + cancel_cutthrough_connection(); + return FALSE; + } +/*XXX error handling? TLS? See flush_buffer() in smtp_out.c */ + +/* Assume nothing buffered. If it was it gets ignored. */ +return cutthrough_response('3', NULL) == '3'; +} + + +/* Buffered send of headers. Return success boolean. */ +/* Also sends header-terminating blank line. */ +/* Sets up the "ctblock" buffer as a side-effect. */ +BOOL +cutthrough_headers_send( void ) +{ +header_line * h; + +if(cutthrough_fd < 0) + return FALSE; + +ctblock.buffer = ctbuffer; +ctblock.buffersize = sizeof(ctbuffer); +ctblock.ptr = ctbuffer; +/* ctblock.cmd_count = 0; ctblock.authenticating = FALSE; */ +ctblock.sock = cutthrough_fd; + +for(h= header_list; h != NULL; h= h->next) + if(h->type != htype_old && h->text != NULL) + if(!cutthrough_puts(h->text, h->slen)) + return FALSE; + +if(!cutthrough_put_nl()) + return TRUE; +} + + +/* Have senders final-dot. Send one to cutthrough target, and grab the response. + Log an OK response as a transmission. + Return smtp response-class digit. + XXX where do fail responses from target get logged? +*/ +uschar * +cutthrough_finaldot( void ) +{ +HDEBUG(D_transport|D_acl|D_v) debug_printf(" SMTP>> .\n"); + +/* Assume data finshed with new-line */ +if(!cutthrough_puts(US".", 1) || !cutthrough_put_nl() + || !cutthrough_flush_send() + || cutthrough_response('2', &cutthrough_addr.message) != '2') + return cutthrough_addr.message; + +(void)close(cutthrough_fd); +cutthrough_fd= -1; +HDEBUG(D_acl) debug_printf("----------- cutthrough close ------------\n"); + +delivery_log(&cutthrough_addr, (int)'>'); +/* C= ok */ +/* QT ok */ +/* DT always 0? */ +/* delivery S= zero! (transport_count) */ +/* not TLS yet hence no X, CV, DN */ + +return cutthrough_addr.message; +} + + /************************************************* * Copy error to toplevel address * *************************************************/ @@ -1282,6 +1524,7 @@ while (addr_new != NULL) } respond_printf(f, "%s\n", cr); } + cancel_cutthrough_connection(); if (!full_info) return copy_error(vaddr, addr, FAIL); else yield = FAIL; @@ -1316,6 +1559,8 @@ while (addr_new != NULL) } respond_printf(f, "%s\n", cr); } + cancel_cutthrough_connection(); + if (!full_info) return copy_error(vaddr, addr, DEFER); else if (yield == OK) yield = DEFER; } -- 2.30.2