CONTROL_ERROR,
CONTROL_CASEFUL_LOCAL_PART,
CONTROL_CASELOWER_LOCAL_PART,
+ CONTROL_CUTTHROUGH_DELIVERY,
CONTROL_ENFORCE_SYNC,
CONTROL_NO_ENFORCE_SYNC,
CONTROL_FREEZE,
US"error",
US"caseful_local_part",
US"caselower_local_part",
+ US"cutthrough_delivery",
US"enforce_sync",
US"no_enforce_sync",
US"freeze",
(unsigned int)
~(1<<ACL_WHERE_RCPT), /* caselower_local_part */
+ (unsigned int)
+ 0, /* cutthrough_delivery */
+
(1<<ACL_WHERE_NOTSMTP)| /* enforce_sync */
(1<<ACL_WHERE_NOTSMTP_START),
{ US"fakedefer", CONTROL_FAKEDEFER, TRUE },
{ US"fakereject", CONTROL_FAKEREJECT, TRUE },
{ US"submission", CONTROL_SUBMISSION, TRUE },
- { US"suppress_local_fixups", CONTROL_SUPPRESS_LOCAL_FIXUPS, FALSE }
+ { US"suppress_local_fixups", CONTROL_SUPPRESS_LOCAL_FIXUPS, FALSE },
+ { US"cutthrough_delivery", CONTROL_CUTTHROUGH_DELIVERY, FALSE }
};
/* Support data structures for Client SMTP Authorization. acl_verify_csa()
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;
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;
+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 *
*************************************************/
(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)
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);
}
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
/* 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)
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);
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 *);
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 **,
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";
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 */
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,
#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 *
*************************************************/
/* 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;
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)
+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
#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();
{
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
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);
/* 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.
+
+ 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 */
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
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;
#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 */
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,
/* 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
+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 *
*************************************************/
}
respond_printf(f, "%s\n", cr);
}
+ cancel_cutthrough_connection();
if (!full_info) return copy_error(vaddr, addr, FAIL);
else yield = FAIL;
}
respond_printf(f, "%s\n", cr);
}
+ cancel_cutthrough_connection();
+
if (!full_info) return copy_error(vaddr, addr, DEFER);
else if (yield == OK) yield = DEFER;
}