diff options
author | Jeremy Harris <jgh146exb@wizmail.org> | 2016-08-06 14:04:45 +0100 |
---|---|---|
committer | Jeremy Harris <jgh146exb@wizmail.org> | 2016-08-06 14:04:45 +0100 |
commit | 44bc8f0c2f3576b46bd6df1b818cb29eaf84df5b (patch) | |
tree | 2f0eedf806a963009394e9f4bc6e4af66fd3822e /src | |
parent | 5dac72a78aa02e48af484ac75059e760151ba1fc (diff) | |
parent | 2d14f39731e88a6d6bb9f1b5c56f497eb12198c4 (diff) |
Merge branch 'CHUNKING'
Diffstat (limited to 'src')
-rw-r--r-- | src/src/deliver.c | 38 | ||||
-rw-r--r-- | src/src/dkim.c | 3 | ||||
-rw-r--r-- | src/src/exim.c | 67 | ||||
-rw-r--r-- | src/src/functions.h | 8 | ||||
-rw-r--r-- | src/src/globals.c | 23 | ||||
-rw-r--r-- | src/src/globals.h | 15 | ||||
-rw-r--r-- | src/src/macros.h | 12 | ||||
-rw-r--r-- | src/src/readconf.c | 1 | ||||
-rw-r--r-- | src/src/receive.c | 206 | ||||
-rw-r--r-- | src/src/smtp_in.c | 351 | ||||
-rw-r--r-- | src/src/smtp_out.c | 74 | ||||
-rw-r--r-- | src/src/structs.h | 46 | ||||
-rw-r--r-- | src/src/tls-gnu.c | 13 | ||||
-rw-r--r-- | src/src/tls-openssl.c | 12 | ||||
-rw-r--r-- | src/src/transport.c | 319 | ||||
-rw-r--r-- | src/src/transports/autoreply.c | 3 | ||||
-rw-r--r-- | src/src/transports/lmtp.c | 3 | ||||
-rw-r--r-- | src/src/transports/pipe.c | 2 | ||||
-rw-r--r-- | src/src/transports/smtp.c | 353 | ||||
-rw-r--r-- | src/src/transports/smtp.h | 1 | ||||
-rw-r--r-- | src/src/verify.c | 18 |
21 files changed, 1105 insertions, 463 deletions
diff --git a/src/src/deliver.c b/src/src/deliver.c index 956a33250..eae675753 100644 --- a/src/src/deliver.c +++ b/src/src/deliver.c @@ -715,7 +715,7 @@ host_item * h = addr->host_used; s = string_append(s, sp, pp, 2, US" H=", h->name); if (LOGGING(dnssec) && h->dnssec == DS_YES) - s = string_cat(s, sp, pp, US" DS"); + s = string_catn(s, sp, pp, US" DS", 3); s = string_append(s, sp, pp, 3, US" [", h->address, US"]"); @@ -1139,8 +1139,11 @@ else #ifndef DISABLE_PRDR if (addr->flags & af_prdr_used) - s = string_append(s, &size, &ptr, 1, US" PRDR"); + s = string_catn(s, &size, &ptr, US" PRDR", 5); #endif + + if (addr->flags & af_chunking_used) + s = string_catn(s, &size, &ptr, US" K", 2); } /* confirmation message (SMTP (host_used) and LMTP (driver_name)) */ @@ -3482,6 +3485,10 @@ while (!done) break; #endif + case 'K': + addr->flags |= af_chunking_used; + break; + case 'D': if (!addr) goto ADDR_MISMATCH; memcpy(&(addr->dsn_aware), ptr, sizeof(addr->dsn_aware)); @@ -4720,6 +4727,9 @@ for (delivery_count = 0; addr_remote; delivery_count++) rmt_dlv_checked_write(fd, 'P', '0', NULL, 0); #endif + if (addr->flags & af_chunking_used) + rmt_dlv_checked_write(fd, 'K', '0', NULL, 0); + memcpy(big_buffer, &addr->dsn_aware, sizeof(addr->dsn_aware)); rmt_dlv_checked_write(fd, 'D', '0', big_buffer, sizeof(addr->dsn_aware)); DEBUG(D_deliver) debug_printf("DSN write: addr->dsn_aware = %d\n", addr->dsn_aware); @@ -7100,7 +7110,7 @@ if (addr_senddsn) FILE *f = fdopen(fd, "wb"); /* header only as required by RFC. only failure DSN needs to honor RET=FULL */ uschar * bound; - transport_ctx tctx; + transport_ctx tctx = {0}; DEBUG(D_deliver) debug_printf("sending error message to: %s\n", sender_address); @@ -7180,7 +7190,6 @@ if (addr_senddsn) /* Write the original email out */ - bzero(&tctx, sizeof(tctx)); tctx.options = topt_add_return_path | topt_no_body; transport_write_message(fileno(f), &tctx, 0); fflush(f); @@ -7638,16 +7647,14 @@ wording. */ transport_filter_argv = NULL; /* Just in case */ return_path = sender_address; /* In case not previously set */ { /* Dummy transport for headers add */ - transport_ctx * tctx = - store_get(sizeof(*tctx) + sizeof(transport_instance)); - transport_instance * tb = (transport_instance *)(tctx+1); + transport_ctx tctx = {0}; + transport_instance tb = {0}; - bzero(tctx, sizeof(*tctx)+sizeof(*tb)); - tctx->tblock = tb; - tctx->options = topt; - tb->add_headers = dsnnotifyhdr; + tctx.tblock = &tb; + tctx.options = topt; + tb.add_headers = dsnnotifyhdr; - transport_write_message(fileno(f), tctx, 0); + transport_write_message(fileno(f), &tctx, 0); } fflush(f); @@ -7964,9 +7971,7 @@ else if (addr_defer != (address_item *)(+1)) FILE *wmf = NULL; FILE *f = fdopen(fd, "wb"); uschar * bound; - transport_ctx tctx; - - bzero(&tctx, sizeof(tctx)); + transport_ctx tctx = {0}; if (warn_message_file) if (!(wmf = Ufopen(warn_message_file, "rb"))) @@ -8282,6 +8287,9 @@ if (!regex_STARTTLS) regex_STARTTLS = regex_must_compile(US"\\n250[\\s\\-]STARTTLS(\\s|\\n|$)", FALSE, TRUE); #endif +if (!regex_CHUNKING) regex_CHUNKING = + regex_must_compile(US"\\n250[\\s\\-]CHUNKING(\\s|\\n|$)", FALSE, TRUE); + #ifndef DISABLE_PRDR if (!regex_PRDR) regex_PRDR = regex_must_compile(US"\\n250[\\s\\-]PRDR(\\s|\\n|$)", FALSE, TRUE); diff --git a/src/src/dkim.c b/src/src/dkim.c index e027a23b3..f09d438d5 100644 --- a/src/src/dkim.c +++ b/src/src/dkim.c @@ -88,6 +88,9 @@ if (dkim_verify_ctx) dkim_verify_ctx = pdkim_init_verify(&dkim_exim_query_dns_txt); dkim_collect_input = !!dkim_verify_ctx; +/* Start feed up with any cached data */ +receive_get_cache(); + store_pool = dkim_verify_oldpool; } diff --git a/src/src/exim.c b/src/src/exim.c index 01770df73..14e0b9d67 100644 --- a/src/src/exim.c +++ b/src/src/exim.c @@ -2717,76 +2717,63 @@ for (i = 1; i < argc; i++) break; } + else if (*argrest == 'C' && argrest[1] && !argrest[2]) + { + switch(argrest[1]) + { /* -MCA: set the smtp_authenticated flag; this is useful only when it precedes -MC (see above). The flag indicates that the host to which Exim is connected has accepted an AUTH sequence. */ - else if (Ustrcmp(argrest, "CA") == 0) - { - smtp_authenticated = TRUE; - break; - } + case 'A': smtp_authenticated = TRUE; break; /* -MCD: set the smtp_use_dsn flag; this indicates that the host that exim is connected to supports the esmtp extension DSN */ - else if (Ustrcmp(argrest, "CD") == 0) - { - smtp_use_dsn = TRUE; - break; - } + case 'D': smtp_peer_options |= PEER_OFFERED_DSN; break; /* -MCG: set the queue name, to a non-default value */ - else if (Ustrcmp(argrest, "CG") == 0) - { - if (++i < argc) queue_name = string_copy(argv[i]); - else badarg = TRUE; - break; - } + case 'G': if (++i < argc) queue_name = string_copy(argv[i]); + else badarg = TRUE; + break; + + /* -MCK: the peer offered CHUNKING. Must precede -MC */ + + case 'K': smtp_peer_options |= PEER_OFFERED_CHUNKING; break; /* -MCP: set the smtp_use_pipelining flag; this is useful only when it preceded -MC (see above) */ - else if (Ustrcmp(argrest, "CP") == 0) - { - smtp_use_pipelining = TRUE; - break; - } + case 'P': smtp_peer_options |= PEER_OFFERED_PIPE; break; /* -MCQ: pass on the pid of the queue-running process that started this chain of deliveries and the fd of its synchronizing pipe; this is useful only when it precedes -MC (see above) */ - else if (Ustrcmp(argrest, "CQ") == 0) - { - if (++i < argc) passed_qr_pid = (pid_t)(Uatol(argv[i])); - else badarg = TRUE; - if (++i < argc) passed_qr_pipe = (int)(Uatol(argv[i])); - else badarg = TRUE; - break; - } + case 'Q': if (++i < argc) passed_qr_pid = (pid_t)(Uatol(argv[i])); + else badarg = TRUE; + if (++i < argc) passed_qr_pipe = (int)(Uatol(argv[i])); + else badarg = TRUE; + break; /* -MCS: set the smtp_use_size flag; this is useful only when it precedes -MC (see above) */ - else if (Ustrcmp(argrest, "CS") == 0) - { - smtp_use_size = TRUE; - break; - } + case 'S': smtp_peer_options |= PEER_OFFERED_SIZE; break; +#ifdef SUPPORT_TLS /* -MCT: set the tls_offered flag; this is useful only when it precedes -MC (see above). The flag indicates that the host to which Exim is connected has offered TLS support. */ - #ifdef SUPPORT_TLS - else if (Ustrcmp(argrest, "CT") == 0) - { - tls_offered = TRUE; - break; + case 'T': smtp_peer_options |= PEER_OFFERED_TLS; break; +#endif + + default: badarg = TRUE; break; + } + break; } - #endif /* -M[x]: various operations on the following list of message ids: -M deliver the messages, ignoring next retry times and thawing diff --git a/src/src/functions.h b/src/src/functions.h index 2c141636e..8745a399c 100644 --- a/src/src/functions.h +++ b/src/src/functions.h @@ -56,6 +56,7 @@ extern int tls_feof(void); extern int tls_ferror(void); extern void tls_free_cert(void **); extern int tls_getc(void); +extern void tls_get_cache(void); extern int tls_import_cert(const uschar *, void **); extern int tls_read(BOOL, uschar *, size_t); extern int tls_server_start(const uschar *); @@ -100,6 +101,7 @@ extern int auth_xtextdecode(uschar *, uschar **); extern uschar *b64encode(uschar *, int); extern int b64decode(uschar *, uschar **); +extern int bdat_getc(void); extern void bits_clear(unsigned int *, size_t, int *); extern void bits_set(unsigned int *, size_t, int *); @@ -389,6 +391,7 @@ extern BOOL smtp_get_interface(uschar *, int, address_item *, uschar **, uschar *); extern BOOL smtp_get_port(uschar *, address_item *, int *, uschar *); extern int smtp_getc(void); +extern void smtp_get_cache(void); extern int smtp_handle_acl_fail(int, int, uschar *, uschar *); extern void smtp_log_no_mail(void); extern void smtp_message_code(uschar **, int *, uschar **, uschar **, BOOL); @@ -466,9 +469,8 @@ extern BOOL transport_set_up_command(const uschar ***, uschar *, extern void transport_update_waiting(host_item *, uschar *); extern BOOL transport_write_block(int, uschar *, int); extern BOOL transport_write_string(int, const char *, ...); -extern BOOL transport_headers_send(address_item *, int, transport_instance *, - BOOL (*)(int, uschar *, int, BOOL), - BOOL); +extern BOOL transport_headers_send(int, transport_ctx *, + BOOL (*)(int, transport_ctx *, uschar *, int)); extern BOOL transport_write_message(int, transport_ctx *, int); extern void tree_add_duplicate(uschar *, address_item *); extern void tree_add_nonrecipient(uschar *); diff --git a/src/src/globals.c b/src/src/globals.c index 4f5a922b4..9e6f9d347 100644 --- a/src/src/globals.c +++ b/src/src/globals.c @@ -138,7 +138,6 @@ tls_support tls_out = { uschar *dsn_envid = NULL; int dsn_ret = 0; const pcre *regex_DSN = NULL; -BOOL smtp_use_dsn = FALSE; uschar *dsn_advertise_hosts = NULL; #ifdef SUPPORT_TLS @@ -158,7 +157,6 @@ uschar *tls_eccurve = US"prime256v1"; # ifndef DISABLE_OCSP uschar *tls_ocsp_file = NULL; # endif -BOOL tls_offered = FALSE; uschar *tls_privatekey = NULL; BOOL tls_remember_esmtp = FALSE; uschar *tls_require_ciphers = NULL; @@ -185,7 +183,10 @@ incoming TCP/IP. The defaults use stdin. We never need these for any stand-alone tests. */ #ifndef STAND_ALONE +int (*lwr_receive_getc)(void) = stdin_getc; +int (*lwr_receive_ungetc)(int) = stdin_ungetc; int (*receive_getc)(void) = stdin_getc; +void (*receive_get_cache)(void)= NULL; int (*receive_ungetc)(int) = stdin_ungetc; int (*receive_feof)(void) = stdin_feof; int (*receive_ferror)(void) = stdin_ferror; @@ -492,9 +493,17 @@ int check_log_space = 0; BOOL check_rfc2047_length = TRUE; int check_spool_inodes = 0; int check_spool_space = 0; -uschar *client_authenticator = NULL; -uschar *client_authenticated_id = NULL; -uschar *client_authenticated_sender = NULL; + +uschar *chunking_advertise_hosts = US"*"; +unsigned chunking_datasize = 0; +unsigned chunking_data_left = 0; +BOOL chunking_offered = FALSE; +chunking_state_t chunking_state= CHUNKING_NOT_OFFERED; +const pcre *regex_CHUNKING = NULL; + +uschar *client_authenticator = NULL; +uschar *client_authenticated_id = NULL; +uschar *client_authenticated_sender = NULL; int clmacro_count = 0; uschar *clmacros[MAX_CLMACROS]; BOOL config_changed = FALSE; @@ -1311,8 +1320,8 @@ int smtp_rlr_base = 0; double smtp_rlr_factor = 0.0; int smtp_rlr_limit = 0; int smtp_rlr_threshold = INT_MAX; -BOOL smtp_use_pipelining = FALSE; -BOOL smtp_use_size = FALSE; +unsigned smtp_peer_options = 0; +unsigned smtp_peer_options_wrap= 0; #ifdef SUPPORT_I18N uschar *smtputf8_advertise_hosts = US"*"; /* overridden under test-harness */ #endif diff --git a/src/src/globals.h b/src/src/globals.h index 6e42bc3d7..b2bfca64c 100644 --- a/src/src/globals.h +++ b/src/src/globals.h @@ -119,7 +119,6 @@ extern uschar *tls_eccurve; /* EC curve */ # ifndef DISABLE_OCSP extern uschar *tls_ocsp_file; /* OCSP stapling proof file */ # endif -extern BOOL tls_offered; /* Server offered TLS */ extern uschar *tls_privatekey; /* Private key file */ extern BOOL tls_remember_esmtp; /* For YAEB */ extern uschar *tls_require_ciphers; /* So some can be avoided */ @@ -132,13 +131,15 @@ extern uschar *tls_advertise_hosts; /* host for which TLS is advertised */ extern uschar *dsn_envid; /* DSN envid string */ extern int dsn_ret; /* DSN ret type*/ extern const pcre *regex_DSN; /* For recognizing DSN settings */ -extern BOOL smtp_use_dsn; /* Global for passed connections */ extern uschar *dsn_advertise_hosts; /* host for which TLS is advertised */ /* Input-reading functions for messages, so we can use special ones for incoming TCP/IP. */ +extern int (*lwr_receive_getc)(void); +extern int (*lwr_receive_ungetc)(int); extern int (*receive_getc)(void); +extern void (*receive_get_cache)(void); extern int (*receive_ungetc)(int); extern int (*receive_feof)(void); extern int (*receive_ferror)(void); @@ -267,6 +268,11 @@ extern int check_log_space; /* Minimum for message acceptance */ extern BOOL check_rfc2047_length; /* Check RFC 2047 encoded string length */ extern int check_spool_inodes; /* Minimum for message acceptance */ extern int check_spool_space; /* Minimum for message acceptance */ +extern uschar *chunking_advertise_hosts; /* RFC 3030 CHUNKING */ +extern unsigned chunking_datasize; +extern unsigned chunking_data_left; +extern BOOL chunking_offered; +extern chunking_state_t chunking_state; extern uschar *client_authenticator; /* Authenticator name used for smtp delivery */ extern uschar *client_authenticated_id; /* "login" name used for SMTP AUTH */ extern uschar *client_authenticated_sender; /* AUTH option to SMTP MAIL FROM (not yet used) */ @@ -711,6 +717,7 @@ extern int recipients_max_reject; /* If TRUE, reject whole message */ extern const pcre *regex_AUTH; /* For recognizing AUTH settings */ extern const pcre *regex_check_dns_names; /* For DNS name checking */ extern const pcre *regex_From; /* For recognizing "From_" lines */ +extern const pcre *regex_CHUNKING; /* For recognizing CHUNKING (RFC 3030) */ extern const pcre *regex_IGNOREQUOTA; /* For recognizing IGNOREQUOTA (LMTP) */ extern const pcre *regex_PIPELINING; /* For recognizing PIPELINING */ extern const pcre *regex_SIZE; /* For recognizing SIZE settings */ @@ -829,8 +836,8 @@ extern int smtp_rlr_base; /* Base interval for RCPT rate limit */ extern double smtp_rlr_factor; /* Factor for RCPT rate limit */ extern int smtp_rlr_limit; /* Max delay */ extern int smtp_rlr_threshold; /* Threshold for RCPT rate limit */ -extern BOOL smtp_use_pipelining; /* Global for passed connections */ -extern BOOL smtp_use_size; /* Global for passed connections */ +extern unsigned smtp_peer_options; /* Global flags for passed connections */ +extern unsigned smtp_peer_options_wrap; /* stacked version hidden by TLS */ #ifdef SUPPORT_I18N extern uschar *smtputf8_advertise_hosts; /* ingress control */ #endif diff --git a/src/src/macros.h b/src/src/macros.h index 53abeb5c2..c62f16e7d 100644 --- a/src/src/macros.h +++ b/src/src/macros.h @@ -320,6 +320,13 @@ for having to swallow the rest of an SMTP message is whether the value is #define END_NOTENDED 3 /* Message reading not yet ended */ #define END_SIZE 4 /* Reading ended because message too big */ #define END_WERROR 5 /* Write error while reading the message */ +#define END_PROTOCOL 6 /* Protocol error in CHUNKING sequence */ + +/* result codes for bdat_getc() (which can also return EOF) */ + +#define EOD (-2) +#define ERR (-3) + /* Bit masks for debug and log selectors */ @@ -787,7 +794,8 @@ most recent SMTP commands. Must be kept in step with the list of names in smtp_in.c that is used for creating the smtp_no_mail logging action. SCH_NONE is "empty". */ -enum { SCH_NONE, SCH_AUTH, SCH_DATA, SCH_EHLO, SCH_ETRN, SCH_EXPN, SCH_HELO, +enum { SCH_NONE, SCH_AUTH, SCH_DATA, SCH_BDAT, + SCH_EHLO, SCH_ETRN, SCH_EXPN, SCH_HELO, SCH_HELP, SCH_MAIL, SCH_NOOP, SCH_QUIT, SCH_RCPT, SCH_RSET, SCH_STARTTLS, SCH_VRFY }; @@ -837,6 +845,7 @@ enum { #define topt_no_headers 0x020 /* Omit headers */ #define topt_no_body 0x040 /* Omit body */ #define topt_escape_headers 0x080 /* Apply escape check to headers */ +#define topt_use_bdat 0x100 /* prepend chunks with RFC3030 BDAT header */ /* Flags for recipient_block, used in DSN support */ @@ -952,6 +961,7 @@ enum { FILTER_UNSET, FILTER_FORWARD, FILTER_EXIM, FILTER_SIEVE }; #define PEER_OFFERED_DSN BIT(4) #define PEER_OFFERED_PIPE BIT(5) #define PEER_OFFERED_SIZE BIT(6) +#define PEER_OFFERED_CHUNKING BIT(7) /* End of macros.h */ diff --git a/src/src/readconf.c b/src/src/readconf.c index 25ff58eb9..a1591e2a1 100644 --- a/src/src/readconf.c +++ b/src/src/readconf.c @@ -212,6 +212,7 @@ static optionlist optionlist_config[] = { { "check_rfc2047_length", opt_bool, &check_rfc2047_length }, { "check_spool_inodes", opt_int, &check_spool_inodes }, { "check_spool_space", opt_Kint, &check_spool_space }, + { "chunking_advertise_hosts", opt_stringptr, &chunking_advertise_hosts }, { "daemon_smtp_port", opt_stringptr|opt_hidden, &daemon_smtp_port }, { "daemon_smtp_ports", opt_stringptr, &daemon_smtp_port }, { "daemon_startup_retries", opt_int, &daemon_startup_retries }, diff --git a/src/src/receive.c b/src/src/receive.c index 32154792e..6a8ce8841 100644 --- a/src/src/receive.c +++ b/src/src/receive.c @@ -777,7 +777,7 @@ read_message_data_smtp(FILE *fout) { int ch_state = 0; int ch; -register int linelength = 0; +int linelength = 0; while ((ch = (receive_getc)()) != EOF) { @@ -867,7 +867,7 @@ while ((ch = (receive_getc)()) != EOF) message_size++; linelength++; - if (fout != NULL) + if (fout) { if (fputc(ch, fout) == EOF) return END_WERROR; if (message_size > thismessage_size_limit) return END_SIZE; @@ -876,7 +876,7 @@ while ((ch = (receive_getc)()) != EOF) (void) cutthrough_put_nl(); else { - uschar c= ch; + uschar c = ch; (void) cutthrough_puts(&c, 1); } } @@ -890,6 +890,63 @@ return END_EOF; +/* Variant of the above read_message_data_smtp() specialised for RFC 3030 +CHUNKING. We assume that the incoming has proper CRLF, so only have to scan +for and strip CR. On the downside there are more protocol reasons to stop. + +Arguments: + fout a FILE to which to write the message; NULL if skipping + +Returns: One of the END_xxx values indicating why it stopped reading +*/ + +static int +read_message_bdat_smtp(FILE *fout) +{ +int ch; +int linelength = 0; + +for (;;) switch (ch = bdat_getc()) + { + case EOF: return END_EOF; + case EOD: return END_DOT; + case ERR: return END_PROTOCOL; + + case '\r': + body_linecount++; + if (linelength > max_received_linelength) + max_received_linelength = linelength; + linelength = -1; + break; + + case 0: + body_zerocount++; + /*FALLTHROUGH*/ + default: + message_size++; + linelength++; + if (fout) + { + if (fputc(ch, fout) == EOF) return END_WERROR; + if (message_size > thismessage_size_limit) return END_SIZE; + } +#ifdef notyet + if(ch == '\n') + (void) cutthrough_put_nl(); + else + { + uschar c = ch; + (void) cutthrough_puts(&c, 1); + } +#endif + break; + } +/*NOTREACHED*/ +} + + + + /************************************************* * Swallow SMTP message * *************************************************/ @@ -906,6 +963,7 @@ Returns: nothing void receive_swallow_smtp(void) { +/*XXX CHUNKING: not enough. read chunks until RSET? */ if (message_ended >= END_NOTENDED) message_ended = read_message_data_smtp(NULL); } @@ -2832,6 +2890,14 @@ if (filter_test != FTEST_NONE) return message_ended == END_DOT; } +/*XXX CHUNKING: need to cancel cutthrough under BDAT, for now. In future, +think more if it could be handled. Cannot do onward CHUNKING unless +inbound is, but inbound chunking ought to be ok with outbound plain. +Could we do onward CHUNKING given inbound CHUNKING? +*/ +if (chunking_state > CHUNKING_OFFERED) + cancel_cutthrough_connection("chunking active"); + /* Cutthrough delivery: We have to create the Received header now rather than at the end of reception, so the timestamp behaviour is a change to the normal case. @@ -2929,7 +2995,9 @@ if (!ferror(data_file) && !(receive_feof)() && message_ended != END_DOT) { if (smtp_input) { - message_ended = read_message_data_smtp(data_file); + message_ended = chunking_state > CHUNKING_OFFERED + ? read_message_bdat_smtp(data_file) + : read_message_data_smtp(data_file); receive_linecount++; /* The terminating "." line */ } else message_ended = read_message_data(data_file); @@ -2937,51 +3005,64 @@ if (!ferror(data_file) && !(receive_feof)() && message_ended != END_DOT) receive_linecount += body_linecount; /* For BSMTP errors mainly */ message_linecount += body_linecount; - /* Handle premature termination of SMTP */ - - if (smtp_input && message_ended == END_EOF) + switch (message_ended) { - Uunlink(spool_name); /* Lose data file when closed */ - cancel_cutthrough_connection("sender closed connection"); - message_id[0] = 0; /* Indicate no message accepted */ - smtp_reply = handle_lost_connection(US""); - smtp_yield = FALSE; - goto TIDYUP; /* Skip to end of function */ - } + /* Handle premature termination of SMTP */ - /* Handle message that is too big. Don't use host_or_ident() in the log - message; we want to see the ident value even for non-remote messages. */ + case END_EOF: + if (smtp_input) + { + Uunlink(spool_name); /* Lose data file when closed */ + cancel_cutthrough_connection("sender closed connection"); + message_id[0] = 0; /* Indicate no message accepted */ + smtp_reply = handle_lost_connection(US""); + smtp_yield = FALSE; + goto TIDYUP; /* Skip to end of function */ + } + break; - if (message_ended == END_SIZE) - { - Uunlink(spool_name); /* Lose the data file when closed */ - cancel_cutthrough_connection("mail too big"); - if (smtp_input) receive_swallow_smtp(); /* Swallow incoming SMTP */ + /* Handle message that is too big. Don't use host_or_ident() in the log + message; we want to see the ident value even for non-remote messages. */ - log_write(L_size_reject, LOG_MAIN|LOG_REJECT, "rejected from <%s>%s%s%s%s: " - "message too big: read=%d max=%d", - sender_address, - (sender_fullhost == NULL)? "" : " H=", - (sender_fullhost == NULL)? US"" : sender_fullhost, - (sender_ident == NULL)? "" : " U=", - (sender_ident == NULL)? US"" : sender_ident, - message_size, - thismessage_size_limit); + case END_SIZE: + Uunlink(spool_name); /* Lose the data file when closed */ + cancel_cutthrough_connection("mail too big"); + if (smtp_input) receive_swallow_smtp(); /* Swallow incoming SMTP */ - if (smtp_input) - { - smtp_reply = US"552 Message size exceeds maximum permitted"; - message_id[0] = 0; /* Indicate no message accepted */ - goto TIDYUP; /* Skip to end of function */ - } - else - { - fseek(data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET); - give_local_error(ERRMESS_TOOBIG, - string_sprintf("message too big (max=%d)", thismessage_size_limit), - US"message rejected: ", error_rc, data_file, header_list); - /* Does not return */ - } + log_write(L_size_reject, LOG_MAIN|LOG_REJECT, "rejected from <%s>%s%s%s%s: " + "message too big: read=%d max=%d", + sender_address, + (sender_fullhost == NULL)? "" : " H=", + (sender_fullhost == NULL)? US"" : sender_fullhost, + (sender_ident == NULL)? "" : " U=", + (sender_ident == NULL)? US"" : sender_ident, + message_size, + thismessage_size_limit); + + if (smtp_input) + { + smtp_reply = US"552 Message size exceeds maximum permitted"; + message_id[0] = 0; /* Indicate no message accepted */ + goto TIDYUP; /* Skip to end of function */ + } + else + { + fseek(data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET); + give_local_error(ERRMESS_TOOBIG, + string_sprintf("message too big (max=%d)", thismessage_size_limit), + US"message rejected: ", error_rc, data_file, header_list); + /* Does not return */ + } + break; + + /* Handle bad BDAT protocol sequence */ + + case END_PROTOCOL: + Uunlink(spool_name); /* Lose the data file when closed */ + cancel_cutthrough_connection("sender protocol error"); + smtp_reply = US""; /* Response already sent */ + message_id[0] = 0; /* Indicate no message accepted */ + goto TIDYUP; /* Skip to end of function */ } } @@ -3164,9 +3245,8 @@ user_msg = NULL; enable_dollar_recipients = TRUE; if (recipients_count == 0) - { - blackholed_by = recipients_discarded? US"MAIL ACL" : US"RCPT ACL"; - } + blackholed_by = recipients_discarded ? US"MAIL ACL" : US"RCPT ACL"; + else { /* Handle interactive SMTP messages */ @@ -3182,18 +3262,15 @@ else dkim_exim_verify_finish(); /* Check if we must run the DKIM ACL */ - if ((acl_smtp_dkim != NULL) && - (dkim_verify_signers != NULL) && - (dkim_verify_signers[0] != '\0')) + if (acl_smtp_dkim && dkim_verify_signers && *dkim_verify_signers) { uschar *dkim_verify_signers_expanded = expand_string(dkim_verify_signers); - if (dkim_verify_signers_expanded == NULL) - { + if (!dkim_verify_signers_expanded) log_write(0, LOG_MAIN|LOG_PANIC, "expansion of dkim_verify_signers option failed: %s", expand_string_message); - } + else { int sep = 0; @@ -3202,28 +3279,23 @@ else uschar *seen_items = NULL; int seen_items_size = 0; int seen_items_offset = 0; - uschar itembuf[256]; /* Default to OK when no items are present */ rc = OK; - while ((item = string_nextinlist(&ptr, &sep, - itembuf, - sizeof(itembuf)))) + while ((item = string_nextinlist(&ptr, &sep, NULL, 0))) { /* Prevent running ACL for an empty item */ - if (!item || (item[0] == '\0')) continue; + if (!item || !*item) continue; /* Only run ACL once for each domain or identity, no matter how often it appears in the expanded list. */ if (seen_items) { uschar *seen_item = NULL; - uschar seen_item_buf[256]; const uschar *seen_items_list = seen_items; BOOL seen_this_item = FALSE; while ((seen_item = string_nextinlist(&seen_items_list, &sep, - seen_item_buf, - sizeof(seen_item_buf)))) + NULL, 0))) if (Ustrcmp(seen_item,item) == 0) { seen_this_item = TRUE; @@ -3783,7 +3855,7 @@ if (sender_host_authenticated) #ifndef DISABLE_PRDR if (prdr_requested) - s = string_append(s, &size, &sptr, 1, US" PRDR"); + s = string_catn(s, &size, &sptr, US" PRDR", 5); #endif #ifdef SUPPORT_PROXY @@ -3791,6 +3863,9 @@ if (proxy_session && LOGGING(proxy)) s = string_append(s, &size, &sptr, 2, US" PRX=", proxy_local_address); #endif +if (chunking_state > CHUNKING_OFFERED) + s = string_catn(s, &size, &sptr, US" K", 2); + sprintf(CS big_buffer, "%d", msg_size); s = string_append(s, &size, &sptr, 2, US" S=", big_buffer); @@ -4093,8 +4168,15 @@ if (smtp_input) /* Default OK response */ + else if (chunking_state > CHUNKING_OFFERED) + { + smtp_printf("250- %u byte chunk, total %d\r\n250 OK id=%s\r\n", + chunking_datasize, message_size+message_linecount, message_id); + chunking_state = CHUNKING_OFFERED; + } else smtp_printf("250 OK id=%s\r\n", message_id); + if (host_checking) fprintf(stdout, "\n**** SMTP testing: that is not a real message id!\n\n"); diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c index 53387011c..8994c6edb 100644 --- a/src/src/smtp_in.c +++ b/src/src/smtp_in.c @@ -82,6 +82,15 @@ enum { MAIL_CMD, RCPT_CMD, RSET_CMD, + /* RFC3030 section 2: "After all MAIL and RCPT responses are collected and + processed the message is sent using a series of BDAT commands" + implies that BDAT should be synchronized. However, we see Google, at least, + sending MAIL,RCPT,BDAT-LAST in a single packet, clearly not waiting for + processing of the RPCT response(s). We shall do the same, and not require + synch for BDAT. */ + + BDAT_CMD, + /* This is a dummy to identify the non-sync commands when not pipelining */ NON_SYNC_CMD_NON_PIPELINING, @@ -182,6 +191,7 @@ static smtp_cmd_list cmd_list[] = { { "mail from:", sizeof("mail from:")-1, MAIL_CMD, TRUE, TRUE }, { "rcpt to:", sizeof("rcpt to:")-1, RCPT_CMD, TRUE, TRUE }, { "data", sizeof("data")-1, DATA_CMD, FALSE, TRUE }, + { "bdat", sizeof("bdat")-1, BDAT_CMD, TRUE, TRUE }, { "quit", sizeof("quit")-1, QUIT_CMD, FALSE, TRUE }, { "noop", sizeof("noop")-1, NOOP_CMD, TRUE, FALSE }, { "etrn", sizeof("etrn")-1, ETRN_CMD, TRUE, FALSE }, @@ -205,9 +215,9 @@ It must be kept in step with the SCH_xxx enumerations. */ static uschar *smtp_names[] = { - US"NONE", US"AUTH", US"DATA", US"EHLO", US"ETRN", US"EXPN", US"HELO", - US"HELP", US"MAIL", US"NOOP", US"QUIT", US"RCPT", US"RSET", US"STARTTLS", - US"VRFY" }; + US"NONE", US"AUTH", US"DATA", US"BDAT", US"EHLO", US"ETRN", US"EXPN", + US"HELO", US"HELP", US"MAIL", US"NOOP", US"QUIT", US"RCPT", US"RSET", + US"STARTTLS", US"VRFY" }; static uschar *protocols_local[] = { US"local-smtp", /* HELO */ @@ -289,6 +299,13 @@ static int smtp_had_eof; static int smtp_had_error; +/* forward declarations */ +int bdat_ungetc(int ch); +static int smtp_read_command(BOOL check_sync); +static int synprot_error(int type, int code, uschar *data, uschar *errmess); +static void smtp_quit_handler(uschar **, uschar **); +static void smtp_rset_handler(void); + /************************************************* * SMTP version of getc() * *************************************************/ @@ -335,6 +352,131 @@ if (smtp_inptr >= smtp_inend) return *smtp_inptr++; } +#ifndef DISABLE_DKIM +void +smtp_get_cache(void) +{ +int n = smtp_inend - smtp_inptr; +if (n > 0) + dkim_exim_verify_feed(smtp_inptr, n); +} +#endif + + +/* Get a byte from the smtp input, in CHUNKING mode. Handle ack of the +previous BDAT chunk and getting new ones when we run out. Uses the +underlying smtp_getc or tls_getc both for that and for getting the +(buffered) data byte. EOD signals (an expected) no further data. +ERR signals a protocol error, and EOF a closed input stream. + +Called from read_bdat_smtp() in receive.c for the message body, but also +by the headers read loop in receive_msg(); manipulates chunking_state +to handle the BDAT command/response. +Placed here due to the correlation with the above smtp_getc(), which it wraps, +and also by the need to do smtp command/response handling. + +Arguments: none +Returns: the next character or ERR, EOD or EOF +*/ + +int +bdat_getc(void) +{ +uschar * user_msg = NULL; +uschar * log_msg; + +for(;;) + { + if (chunking_data_left-- > 0) + return lwr_receive_getc(); + + receive_getc = lwr_receive_getc; + receive_ungetc = lwr_receive_ungetc; + + /* If not the last, ack the received chunk. The last response is delayed + until after the data ACL decides on it */ + + if (chunking_state == CHUNKING_LAST) + { +#ifndef DISABLE_DKIM + dkim_exim_verify_feed(".\r\n", 3); /* for consistency with .-term MAIL */ +#endif + return EOD; + } + + chunking_state = CHUNKING_OFFERED; + smtp_printf("250 %u byte chunk received\r\n", chunking_datasize); + + /* Expect another BDAT cmd from input. RFC 3030 says nothing about + QUIT, RSET or NOOP but handling them seems obvious */ + +next_cmd: + switch(smtp_read_command(TRUE)) + { + default: + (void) synprot_error(L_smtp_protocol_error, 503, NULL, + US"only BDAT permissible after non-LAST BDAT"); + + repeat_until_rset: + switch(smtp_read_command(TRUE)) + { + case QUIT_CMD: smtp_quit_handler(&user_msg, &log_msg); /*FALLTHROUGH */ + case EOF_CMD: return EOF; + case RSET_CMD: smtp_rset_handler(); return ERR; + default: if (synprot_error(L_smtp_protocol_error, 503, NULL, + US"only RSET accepted now") > 0) + return EOF; + goto repeat_until_rset; + } + + case QUIT_CMD: + smtp_quit_handler(&user_msg, &log_msg); + /*FALLTHROUGH*/ + case EOF_CMD: + return EOF; + + case RSET_CMD: + smtp_rset_handler(); + return ERR; + + case NOOP_CMD: + HAD(SCH_NOOP); + smtp_printf("250 OK\r\n"); + goto next_cmd; + + case BDAT_CMD: + { + int n; + + if (sscanf(CS smtp_cmd_data, "%u %n", &chunking_datasize, &n) < 1) + { + (void) synprot_error(L_smtp_protocol_error, 501, NULL, + US"missing size for BDAT command"); + return ERR; + } + chunking_state = strcmpic(smtp_cmd_data+n, US"LAST") == 0 + ? CHUNKING_LAST : CHUNKING_ACTIVE; + chunking_data_left = chunking_datasize; + + if (chunking_datasize == 0) + if (chunking_state == CHUNKING_LAST) + return EOD; + else + { + (void) synprot_error(L_smtp_protocol_error, 504, NULL, + US"zero size for BDAT command"); + goto repeat_until_rset; + } + + receive_getc = bdat_getc; + receive_ungetc = bdat_ungetc; + break; /* to top of main loop */ + } + } + } +} + + /************************************************* @@ -353,11 +495,18 @@ Returns: the character int smtp_ungetc(int ch) { -*(--smtp_inptr) = ch; +*--smtp_inptr = ch; return ch; } +int +bdat_ungetc(int ch) +{ +chunking_data_left++; +return lwr_receive_ungetc(ch); +} + /************************************************* @@ -1520,14 +1669,6 @@ sender_verified_list = NULL; /* No senders verified */ memset(sender_address_cache, 0, sizeof(sender_address_cache)); memset(sender_domain_cache, 0, sizeof(sender_domain_cache)); -#ifndef DISABLE_PRDR -prdr_requested = FALSE; -#endif - -/* Reset the DSN flags */ -dsn_ret = 0; -dsn_envid = NULL; - authenticated_sender = NULL; #ifdef EXPERIMENTAL_BRIGHTMAIL bmi_run = 0; @@ -1538,6 +1679,11 @@ dkim_signers = NULL; dkim_disable_verify = FALSE; dkim_collect_input = FALSE; #endif +dsn_ret = 0; +dsn_envid = NULL; +#ifndef DISABLE_PRDR +prdr_requested = FALSE; +#endif #ifdef EXPERIMENTAL_SPF spf_header_comment = NULL; spf_received = NULL; @@ -1918,6 +2064,7 @@ smtp_inbuffer = (uschar *)malloc(in_buffer_size); if (smtp_inbuffer == NULL) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "malloc() failed for SMTP input buffer"); receive_getc = smtp_getc; +receive_get_cache = smtp_get_cache; receive_ungetc = smtp_ungetc; receive_feof = smtp_feof; receive_ferror = smtp_ferror; @@ -3239,6 +3386,43 @@ return 0; +static void +smtp_quit_handler(uschar ** user_msgp, uschar ** log_msgp) +{ +HAD(SCH_QUIT); +incomplete_transaction_log(US"QUIT"); +if (acl_smtp_quit != NULL) + { + int rc = acl_check(ACL_WHERE_QUIT, NULL, acl_smtp_quit, user_msgp, log_msgp); + if (rc == ERROR) + log_write(0, LOG_MAIN|LOG_PANIC, "ACL for QUIT returned ERROR: %s", + *log_msgp); + } +if (*user_msgp) + smtp_respond(US"221", 3, TRUE, *user_msgp); +else + smtp_printf("221 %s closing connection\r\n", smtp_active_hostname); + +#ifdef SUPPORT_TLS +tls_close(TRUE, TRUE); +#endif + +log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT", + smtp_get_connection_info()); +} + + +static void +smtp_rset_handler(void) +{ +HAD(SCH_RSET); +incomplete_transaction_log(US"RSET"); +smtp_printf("250 Reset OK\r\n"); +cmd_list[CMD_LIST_RSET].is_mail_cmd = FALSE; +} + + + /************************************************* * Initialize for SMTP incoming message * *************************************************/ @@ -3285,6 +3469,8 @@ for the host). Note: we do NOT reset AUTH at this point. */ smtp_reset(reset_point); message_ended = END_NOTSTARTED; +chunking_state = chunking_offered ? CHUNKING_OFFERED : CHUNKING_NOT_OFFERED; + cmd_list[CMD_LIST_RSET].is_mail_cmd = TRUE; cmd_list[CMD_LIST_HELO].is_mail_cmd = TRUE; cmd_list[CMD_LIST_EHLO].is_mail_cmd = TRUE; @@ -3769,6 +3955,16 @@ while (done <= 0) if (!first) s = string_catn(s, &size, &ptr, US"\r\n", 2); } + /* RFC 3030 CHUNKING */ + + if (verify_check_host(&chunking_advertise_hosts) != FAIL) + { + s = string_catn(s, &size, &ptr, smtp_code, 3); + s = string_catn(s, &size, &ptr, US"-CHUNKING\r\n", 11); + chunking_offered = TRUE; + chunking_state = CHUNKING_OFFERED; + } + /* Advertise TLS (Transport Level Security) aka SSL (Secure Socket Layer) if it has been included in the binary, and the host matches tls_advertise_hosts. We must *not* advertise if we are already in a @@ -4521,8 +4717,44 @@ while (done <= 0) (often indicating some kind of system error), it is helpful to include it with the DATA rejection (an idea suggested by Tony Finch). */ + case BDAT_CMD: + HAD(SCH_BDAT); + { + int n; + + if (chunking_state != CHUNKING_OFFERED) + { + done = synprot_error(L_smtp_protocol_error, 503, NULL, + US"BDAT command used when CHUNKING not advertised"); + break; + } + + /* grab size, endmarker */ + + if (sscanf(CS smtp_cmd_data, "%u %n", &chunking_datasize, &n) < 1) + { + done = synprot_error(L_smtp_protocol_error, 501, NULL, + US"missing size for BDAT command"); + break; + } + chunking_state = strcmpic(smtp_cmd_data+n, US"LAST") == 0 + ? CHUNKING_LAST : CHUNKING_ACTIVE; + chunking_data_left = chunking_datasize; + + lwr_receive_getc = receive_getc; + lwr_receive_ungetc = receive_ungetc; + receive_getc = bdat_getc; + receive_ungetc = bdat_ungetc; + + DEBUG(D_any) + debug_printf("chunking state %d\n", (int)chunking_state); + goto DATA_BDAT; + } + case DATA_CMD: HAD(SCH_DATA); + + DATA_BDAT: /* Common code for DATA and BDAT */ if (!discarded && recipients_count <= 0) { if (rcpt_smtp_response_same && rcpt_smtp_response != NULL) @@ -4537,10 +4769,13 @@ while (done <= 0) smtp_respond(code, 3, FALSE, rcpt_smtp_response); } if (pipelining_advertised && last_was_rcpt) - smtp_printf("503 Valid RCPT command must precede DATA\r\n"); + smtp_printf("503 Valid RCPT command must precede %s\r\n", + smtp_names[smtp_connection_had[smtp_ch_index-1]]); else done = synprot_error(L_smtp_protocol_error, 503, NULL, - US"valid RCPT command must precede DATA"); + smtp_connection_had[smtp_ch_index-1] == SCH_DATA + ? US"valid RCPT command must precede DATA" + : US"valid RCPT command must precede BDAT"); break; } @@ -4552,35 +4787,45 @@ while (done <= 0) break; } - /* If there is an ACL, re-check the synchronization afterwards, since the - ACL may have delayed. To handle cutthrough delivery enforce a dummy call - to get the DATA command sent. */ - - if (acl_smtp_predata == NULL && cutthrough.fd < 0) rc = OK; else - { - uschar * acl= acl_smtp_predata ? acl_smtp_predata : US"accept"; - enable_dollar_recipients = TRUE; - rc = acl_check(ACL_WHERE_PREDATA, NULL, acl, &user_msg, - &log_msg); - enable_dollar_recipients = FALSE; - if (rc == OK && !check_sync()) goto SYNC_FAILURE; + if (chunking_state > CHUNKING_OFFERED) + { /* No predata ACL or go-ahead output for BDAT */ + rc = OK; } - - if (rc == OK) + else { - uschar * code; - code = US"354"; - if (user_msg == NULL) - smtp_printf("%s Enter message, ending with \".\" on a line by itself\r\n", code); - else smtp_user_msg(code, user_msg); - done = 3; - message_ended = END_NOTENDED; /* Indicate in middle of data */ + /* If there is an ACL, re-check the synchronization afterwards, since the + ACL may have delayed. To handle cutthrough delivery enforce a dummy call + to get the DATA command sent. */ + + if (acl_smtp_predata == NULL && cutthrough.fd < 0) + rc = OK; + else + { + uschar * acl = acl_smtp_predata ? acl_smtp_predata : US"accept"; + enable_dollar_recipients = TRUE; + rc = acl_check(ACL_WHERE_PREDATA, NULL, acl, &user_msg, + &log_msg); + enable_dollar_recipients = FALSE; + if (rc == OK && !check_sync()) + goto SYNC_FAILURE; + + if (rc != OK) + { /* Either the ACL failed the address, or it was deferred. */ + done = smtp_handle_acl_fail(ACL_WHERE_PREDATA, rc, user_msg, log_msg); + break; + } + } + + if (user_msg) + smtp_user_msg(US"354", user_msg); + else + smtp_printf( + "354 Enter message, ending with \".\" on a line by itself\r\n"); } - /* Either the ACL failed the address, or it was deferred. */ + done = 3; + message_ended = END_NOTENDED; /* Indicate in middle of data */ - else - done = smtp_handle_acl_fail(ACL_WHERE_PREDATA, rc, user_msg, log_msg); break; @@ -4699,7 +4944,7 @@ while (done <= 0) if (receive_smtp_buffered()) { DEBUG(D_any) - debug_printf("Non-empty input buffer after STARTTLS; naive attack?"); + debug_printf("Non-empty input buffer after STARTTLS; naive attack?\n"); if (tls_in.active < 0) smtp_inend = smtp_inptr = smtp_inbuffer; /* and if TLS is already active, tls_server_start() should fail */ @@ -4816,37 +5061,15 @@ while (done <= 0) message. */ case QUIT_CMD: - HAD(SCH_QUIT); - incomplete_transaction_log(US"QUIT"); - if (acl_smtp_quit != NULL) - { - rc = acl_check(ACL_WHERE_QUIT, NULL, acl_smtp_quit, &user_msg, &log_msg); - if (rc == ERROR) - log_write(0, LOG_MAIN|LOG_PANIC, "ACL for QUIT returned ERROR: %s", - log_msg); - } - if (user_msg == NULL) - smtp_printf("221 %s closing connection\r\n", smtp_active_hostname); - else - smtp_respond(US"221", 3, TRUE, user_msg); - - #ifdef SUPPORT_TLS - tls_close(TRUE, TRUE); - #endif - + smtp_quit_handler(&user_msg, &log_msg); done = 2; - log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT", - smtp_get_connection_info()); break; case RSET_CMD: - HAD(SCH_RSET); - incomplete_transaction_log(US"RSET"); + smtp_rset_handler(); smtp_reset(reset_point); toomany = FALSE; - smtp_printf("250 Reset OK\r\n"); - cmd_list[CMD_LIST_RSET].is_mail_cmd = FALSE; break; @@ -4873,7 +5096,7 @@ while (done <= 0) verify_check_host(&tls_advertise_hosts) != FAIL) Ustrcat(buffer, " STARTTLS"); #endif - Ustrcat(buffer, " HELO EHLO MAIL RCPT DATA"); + Ustrcat(buffer, " HELO EHLO MAIL RCPT DATA BDAT"); Ustrcat(buffer, " NOOP QUIT RSET HELP"); if (acl_smtp_etrn != NULL) Ustrcat(buffer, " ETRN"); if (acl_smtp_expn != NULL) Ustrcat(buffer, " EXPN"); diff --git a/src/src/smtp_out.c b/src/src/smtp_out.c index 6b4843175..76181b5f1 100644 --- a/src/src/smtp_out.c +++ b/src/src/smtp_out.c @@ -357,6 +357,7 @@ Arguments: noflush if TRUE, save the command in the output buffer, for pipelining format a format, starting with one of of HELO, MAIL FROM, RCPT TO, DATA, ".", or QUIT. + If NULL, flush pipeline buffer only. ... data for the format Returns: 0 if command added to pipelining buffer, with nothing transmitted @@ -371,48 +372,51 @@ int count; int rc = 0; va_list ap; -va_start(ap, format); -if (!string_vformat(big_buffer, big_buffer_size, CS format, ap)) - log_write(0, LOG_MAIN|LOG_PANIC_DIE, "overlong write_command in outgoing " - "SMTP"); -va_end(ap); -count = Ustrlen(big_buffer); - -if (count > outblock->buffersize) - log_write(0, LOG_MAIN|LOG_PANIC_DIE, "overlong write_command in outgoing " - "SMTP"); - -if (count > outblock->buffersize - (outblock->ptr - outblock->buffer)) +if (format) { - rc = outblock->cmd_count; /* flush resets */ - if (!flush_buffer(outblock)) return -1; - } + va_start(ap, format); + if (!string_vformat(big_buffer, big_buffer_size, CS format, ap)) + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "overlong write_command in outgoing " + "SMTP"); + va_end(ap); + count = Ustrlen(big_buffer); + + if (count > outblock->buffersize) + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "overlong write_command in outgoing " + "SMTP"); + + if (count > outblock->buffersize - (outblock->ptr - outblock->buffer)) + { + rc = outblock->cmd_count; /* flush resets */ + if (!flush_buffer(outblock)) return -1; + } -Ustrncpy(CS outblock->ptr, big_buffer, count); -outblock->ptr += count; -outblock->cmd_count++; -count -= 2; -big_buffer[count] = 0; /* remove \r\n for error message */ + Ustrncpy(CS outblock->ptr, big_buffer, count); + outblock->ptr += count; + outblock->cmd_count++; + count -= 2; + big_buffer[count] = 0; /* remove \r\n for error message */ -/* We want to hide the actual data sent in AUTH transactions from reflections -and logs. While authenticating, a flag is set in the outblock to enable this. -The AUTH command itself gets any data flattened. Other lines are flattened -completely. */ + /* We want to hide the actual data sent in AUTH transactions from reflections + and logs. While authenticating, a flag is set in the outblock to enable this. + The AUTH command itself gets any data flattened. Other lines are flattened + completely. */ -if (outblock->authenticating) - { - uschar *p = big_buffer; - if (Ustrncmp(big_buffer, "AUTH ", 5) == 0) + if (outblock->authenticating) { - p += 5; - while (isspace(*p)) p++; - while (!isspace(*p)) p++; - while (isspace(*p)) p++; + uschar *p = big_buffer; + if (Ustrncmp(big_buffer, "AUTH ", 5) == 0) + { + p += 5; + while (isspace(*p)) p++; + while (!isspace(*p)) p++; + while (isspace(*p)) p++; + } + while (*p != 0) *p++ = '*'; } - while (*p != 0) *p++ = '*'; - } -HDEBUG(D_transport|D_acl|D_v) debug_printf(" SMTP>> %s\n", big_buffer); + HDEBUG(D_transport|D_acl|D_v) debug_printf(" SMTP>> %s\n", big_buffer); + } if (!noflush) { diff --git a/src/src/structs.h b/src/src/structs.h index 6666c52ca..ffbc899a5 100644 --- a/src/src/structs.h +++ b/src/src/structs.h @@ -51,6 +51,11 @@ typedef struct ugid_block { BOOL initgroups; } ugid_block; +typedef enum { CHUNKING_NOT_OFFERED = -1, + CHUNKING_OFFERED, + CHUNKING_ACTIVE, + CHUNKING_LAST} chunking_state_t; + /* Structure for holding information about a host for use mainly by routers, but also used when checking lists of hosts and when transporting. Looking up host addresses is done using this structure. */ @@ -218,14 +223,38 @@ typedef struct transport_info { } transport_info; +/* smtp transport datachunk callback */ + +#define tc_reap_prev BIT(0) /* Flags: reap previous SMTP cmd responses */ +#define tc_reap_one BIT(1) /* reap one SMTP response */ +#define tc_chunk_last BIT(2) /* annotate chunk SMTP cmd as LAST */ + +struct transport_context; +typedef int (*tpt_chunk_cmd_cb)(int fd, struct transport_context * tctx, + unsigned len, unsigned flags); + /* Structure for information about a delivery-in-progress */ typedef struct transport_context { - transport_instance * tblock; - struct address_item * addr; - uschar * check_string; - uschar * escape_string; - int options; /* topt_* */ + transport_instance * tblock; /* transport */ + struct address_item * addr; + uschar * check_string; /* string replacement */ + uschar * escape_string; + int options; /* output processing topt_* */ + + /* items below only used with option topt_use_bdat */ + tpt_chunk_cmd_cb chunk_cb; /* per-datachunk callback */ + struct smtp_inblock * inblock; + struct smtp_outblock * outblock; + host_item * host; + struct address_item * first_addr; + struct address_item **sync_addr; + BOOL pending_MAIL; + BOOL pending_BDAT; + BOOL good_RCPT; + BOOL * completed_address; + int cmd_count; + uschar * buffer; } transport_ctx; @@ -517,12 +546,13 @@ typedef struct address_item_propagated { #ifndef DISABLE_PRDR # define af_prdr_used 0x08000000 /* delivery used SMTP PRDR */ #endif -#define af_force_command 0x10000000 /* force_command in pipe transport */ +#define af_chunking_used 0x10000000 /* delivery used SMTP CHUNKING */ +#define af_force_command 0x20000000 /* force_command in pipe transport */ #ifdef EXPERIMENTAL_DANE -# define af_dane_verified 0x20000000 /* TLS cert verify done with DANE */ +# define af_dane_verified 0x40000000 /* TLS cert verify done with DANE */ #endif #ifdef SUPPORT_I18N -# define af_utf8_downcvt 0x40000000 /* downconvert was done for delivery */ +# define af_utf8_downcvt 0x80000000 /* downconvert was done for delivery */ #endif /* These flags must be propagated when a child is created */ diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c index c7c6b2674..0774850ca 100644 --- a/src/src/tls-gnu.c +++ b/src/src/tls-gnu.c @@ -1879,6 +1879,7 @@ and initialize appropriately. */ state->xfer_buffer = store_malloc(ssl_xfer_buffer_size); receive_getc = tls_getc; +receive_get_cache = tls_get_cache; receive_ungetc = tls_ungetc; receive_feof = tls_feof; receive_ferror = tls_ferror; @@ -2179,6 +2180,7 @@ if (state->xfer_buffer_lwm >= state->xfer_buffer_hwm) DEBUG(D_tls) debug_printf("Got TLS_EOF\n"); receive_getc = smtp_getc; + receive_get_cache = smtp_get_cache; receive_ungetc = smtp_ungetc; receive_feof = smtp_feof; receive_ferror = smtp_ferror; @@ -2217,6 +2219,17 @@ if (state->xfer_buffer_lwm >= state->xfer_buffer_hwm) return state->xfer_buffer[state->xfer_buffer_lwm++]; } +#ifndef DISABLE_DKIM +void +tls_get_cache() +{ +exim_gnutls_state_st * state = &state_server; +int n = state->xfer_buffer_hwm - state->xfer_buffer_lwm; +if (n > 0) + dkim_exim_verify_feed(state->xfer_buffer+state->xfer_buffer_lwm, n); +} +#endif + diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index c24eb4544..242394f95 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -1943,6 +1943,7 @@ ssl_xfer_buffer_lwm = ssl_xfer_buffer_hwm = 0; ssl_xfer_eof = ssl_xfer_error = 0; receive_getc = tls_getc; +receive_get_cache = tls_get_cache; receive_ungetc = tls_ungetc; receive_feof = tls_feof; receive_ferror = tls_ferror; @@ -2313,6 +2314,7 @@ if (ssl_xfer_buffer_lwm >= ssl_xfer_buffer_hwm) DEBUG(D_tls) debug_printf("Got SSL_ERROR_ZERO_RETURN\n"); receive_getc = smtp_getc; + receive_get_cache = smtp_get_cache; receive_ungetc = smtp_ungetc; receive_feof = smtp_feof; receive_ferror = smtp_ferror; @@ -2358,6 +2360,16 @@ if (ssl_xfer_buffer_lwm >= ssl_xfer_buffer_hwm) return ssl_xfer_buffer[ssl_xfer_buffer_lwm++]; } +#ifndef DISABLE_DKIM +void +tls_get_cache() +{ +int n = ssl_xfer_buffer_hwm - ssl_xfer_buffer_lwm; +if (n > 0) + dkim_exim_verify_feed(ssl_xfer_buffer+ssl_xfer_buffer_lwm, n); +} +#endif + /************************************************* diff --git a/src/src/transport.c b/src/src/transport.c index 8e0a80240..a68c22f29 100644 --- a/src/src/transport.c +++ b/src/src/transport.c @@ -236,10 +236,12 @@ for (i = 0; i < 100; i++) else { alarm(local_timeout); - #ifdef SUPPORT_TLS - if (tls_out.active == fd) rc = tls_write(FALSE, block, len); else - #endif - rc = write(fd, block, len); +#ifdef SUPPORT_TLS + if (tls_out.active == fd) + rc = tls_write(FALSE, block, len); + else +#endif + rc = write(fd, block, len); save_errno = errno; local_timeout = alarm(0); if (sigalrm_seen) @@ -357,7 +359,7 @@ Arguments: fd file descript to write to chunk pointer to data to write len length of data to write - usr_crlf TRUE if CR LF is wanted at the end of each line + tctx transport context - processing to be done during output In addition, the static nl_xxx variables must be set as required. @@ -365,7 +367,7 @@ Returns: TRUE on success, FALSE on failure (with errno preserved) */ static BOOL -write_chunk(int fd, uschar *chunk, int len, BOOL use_crlf) +write_chunk(int fd, transport_ctx * tctx, uschar *chunk, int len) { uschar *start = chunk; uschar *end = chunk + len; @@ -408,16 +410,22 @@ possible. */ for (ptr = start; ptr < end; ptr++) { - int ch; + int ch, len; /* Flush the buffer if it has reached the threshold - we want to leave enough room for the next uschar, plus a possible extra CR for an LF, plus the escape string. */ - if (chunk_ptr - deliver_out_buffer > mlen) + if ((len = chunk_ptr - deliver_out_buffer) > mlen) { - if (!transport_write_block(fd, deliver_out_buffer, - chunk_ptr - deliver_out_buffer)) + /* If CHUNKING, prefix with BDAT (size) NON-LAST. Also, reap responses + from previous SMTP commands. */ + + if (tctx && tctx->options & topt_use_bdat && tctx->chunk_cb) + if (tctx->chunk_cb(fd, tctx, (unsigned)len, tc_reap_prev|tc_reap_one) != OK) + return FALSE; + + if (!transport_write_block(fd, deliver_out_buffer, len)) return FALSE; chunk_ptr = deliver_out_buffer; } @@ -428,7 +436,7 @@ for (ptr = start; ptr < end; ptr++) /* Insert CR before NL if required */ - if (use_crlf) *chunk_ptr++ = '\r'; + if (tctx && tctx->options & topt_use_crlf) *chunk_ptr++ = '\r'; *chunk_ptr++ = '\n'; transport_newlines++; @@ -545,14 +553,14 @@ Arguments: pdlist address of anchor of the list of processed addresses first TRUE if this is the first address; set it FALSE afterwards fd the file descriptor to write to - use_crlf to be passed on to write_chunk() + tctx transport context - processing to be done during output Returns: FALSE if writing failed */ static BOOL write_env_to(address_item *p, struct aci **pplist, struct aci **pdlist, - BOOL *first, int fd, BOOL use_crlf) + BOOL *first, int fd, transport_ctx * tctx) { address_item *pp; struct aci *ppp; @@ -574,7 +582,7 @@ for (pp = p;; pp = pp->parent) address_item *dup; for (dup = addr_duplicate; dup; dup = dup->next) if (dup->dupof == pp) /* a dup of our address */ - if (!write_env_to(dup, pplist, pdlist, first, fd, use_crlf)) + if (!write_env_to(dup, pplist, pdlist, first, fd, tctx)) return FALSE; if (!pp->parent) break; } @@ -591,9 +599,9 @@ ppp->next = *pplist; *pplist = ppp; ppp->ptr = pp; -if (!(*first) && !write_chunk(fd, US",\n ", 3, use_crlf)) return FALSE; +if (!*first && !write_chunk(fd, tctx, US",\n ", 3)) return FALSE; *first = FALSE; -return write_chunk(fd, pp->address, Ustrlen(pp->address), use_crlf); +return write_chunk(fd, tctx, pp->address, Ustrlen(pp->address)); } @@ -608,20 +616,19 @@ Arguments: addr (chain of) addresses (for extra headers), or NULL; only the first address is used fd file descriptor to write the message to - sendfn function for output - use_crlf turn NL into CR LF - rewrite_rules chain of header rewriting rules - rewrite_existflags flags for the rewriting rules + tctx transport context + sendfn function for output (transport or verify) Returns: TRUE on success; FALSE on failure. */ BOOL -transport_headers_send(address_item *addr, int fd, transport_instance * tblock, - BOOL (*sendfn)(int fd, uschar * s, int len, BOOL use_crlf), - BOOL use_crlf) +transport_headers_send(int fd, transport_ctx * tctx, + BOOL (*sendfn)(int fd, transport_ctx * tctx, uschar * s, int len)) { header_line *h; const uschar *list; +transport_instance * tblock = tctx ? tctx->tblock : NULL; +address_item * addr = tctx ? tctx->addr : NULL; /* Then the message's headers. Don't write any that are flagged as "old"; that means they were rewritten, or are a record of envelope rewriting, or @@ -676,7 +683,7 @@ for (h = header_list; h; h = h->next) if (h->type != htype_old) if ((hh = rewrite_header(h, NULL, NULL, tblock->rewrite_rules, tblock->rewrite_existflags, FALSE))) { - if (!sendfn(fd, hh->text, hh->slen, use_crlf)) return FALSE; + if (!sendfn(fd, tctx, hh->text, hh->slen)) return FALSE; store_reset(reset_point); continue; /* With the next header line */ } @@ -684,7 +691,7 @@ for (h = header_list; h; h = h->next) if (h->type != htype_old) /* Either no rewriting rules, or it didn't get rewritten */ - if (!sendfn(fd, h->text, h->slen, use_crlf)) return FALSE; + if (!sendfn(fd, tctx, h->text, h->slen)) return FALSE; } /* Header removed */ @@ -719,7 +726,7 @@ if (addr) hprev = h; if (i == 1) { - if (!sendfn(fd, h->text, h->slen, use_crlf)) return FALSE; + if (!sendfn(fd, tctx, h->text, h->slen)) return FALSE; DEBUG(D_transport) debug_printf("added header line(s):\n%s---\n", h->text); } @@ -744,8 +751,8 @@ if (tblock && (list = CUS tblock->add_headers)) int len = Ustrlen(s); if (len > 0) { - if (!sendfn(fd, s, len, use_crlf)) return FALSE; - if (s[len-1] != '\n' && !sendfn(fd, US"\n", 1, use_crlf)) + if (!sendfn(fd, tctx, s, len)) return FALSE; + if (s[len-1] != '\n' && !sendfn(fd, tctx, US"\n", 1)) return FALSE; DEBUG(D_transport) { @@ -761,7 +768,7 @@ if (tblock && (list = CUS tblock->add_headers)) /* Separate headers from body with a blank line */ -return sendfn(fd, US"\n", 1, use_crlf); +return sendfn(fd, tctx, US"\n", 1); } @@ -814,12 +821,12 @@ Arguments: end_dot if TRUE, send a terminating "." line at the end no_headers if TRUE, omit the headers no_body if TRUE, omit the body - size_limit if > 0, this is a limit to the size of message written; + check_string a string to check for at the start of lines, or NULL + escape_string a string to insert in front of any check string + size_limit if > 0, this is a limit to the size of message written; it is used when returning messages to their senders, and is approximate rather than exact, owing to chunk buffering - check_string a string to check for at the start of lines, or NULL - escape_string a string to insert in front of any check string Returns: TRUE on success; FALSE (with errno) on failure. In addition, the global variable transport_count @@ -829,9 +836,7 @@ Returns: TRUE on success; FALSE (with errno) on failure. static BOOL internal_transport_write_message(int fd, transport_ctx * tctx, int size_limit) { -int written = 0; int len; -BOOL use_crlf = (tctx->options & topt_use_crlf) != 0; /* Initialize pointer in output buffer. */ @@ -869,7 +874,7 @@ if (!(tctx->options & topt_no_headers)) uschar buffer[ADDRESS_MAXLENGTH + 20]; int n = sprintf(CS buffer, "Return-path: <%.*s>\n", ADDRESS_MAXLENGTH, return_path); - if (!write_chunk(fd, buffer, n, use_crlf)) return FALSE; + if (!write_chunk(fd, tctx, buffer, n)) return FALSE; } /* Add envelope-to: if requested */ @@ -882,19 +887,19 @@ if (!(tctx->options & topt_no_headers)) struct aci *dlist = NULL; void *reset_point = store_get(0); - if (!write_chunk(fd, US"Envelope-to: ", 13, use_crlf)) return FALSE; + if (!write_chunk(fd, tctx, US"Envelope-to: ", 13)) return FALSE; /* Pick up from all the addresses. The plist and dlist variables are anchors for lists of addresses already handled; they have to be defined at this level becuase write_env_to() calls itself recursively. */ for (p = tctx->addr; p; p = p->next) - if (!write_env_to(p, &plist, &dlist, &first, fd, use_crlf)) + if (!write_env_to(p, &plist, &dlist, &first, fd, tctx)) return FALSE; /* Add a final newline and reset the store used for tracking duplicates */ - if (!write_chunk(fd, US"\n", 1, use_crlf)) return FALSE; + if (!write_chunk(fd, tctx, US"\n", 1)) return FALSE; store_reset(reset_point); } @@ -904,7 +909,7 @@ if (!(tctx->options & topt_no_headers)) { uschar buffer[100]; int n = sprintf(CS buffer, "Delivery-date: %s\n", tod_stamp(tod_full)); - if (!write_chunk(fd, buffer, n, use_crlf)) return FALSE; + if (!write_chunk(fd, tctx, buffer, n)) return FALSE; } /* Then the message's headers. Don't write any that are flagged as "old"; @@ -913,8 +918,63 @@ if (!(tctx->options & topt_no_headers)) match any entries therein. Then check addr->prop.remove_headers too, provided that addr is not NULL. */ - if (!transport_headers_send(tctx->addr, fd, tctx->tblock, &write_chunk, use_crlf)) + if (!transport_headers_send(fd, tctx, &write_chunk)) + return FALSE; + } + +/* When doing RFC3030 CHUNKING output, work out how much data will be in the +last BDAT, consisting of the current write_chunk() output buffer fill +(optimally, all of the headers - but it does not matter if we already had to +flush that buffer with non-last BDAT prependix) plus the amount of body data +(as expanded for CRLF lines). Then create and write the BDAT, and ensure +that further use of write_chunk() will not prepend BDATs. +The first BDAT written will also first flush any outstanding MAIL and RCPT +commands which were buffered thans to PIPELINING. +Commands go out (using a send()) from a different buffer to data (using a +write()). They might not end up in the same TCP segment, which is +suboptimal. */ + +if (tctx->options & topt_use_bdat) + { + off_t fsize; + int hsize, size; + + if ((hsize = chunk_ptr - deliver_out_buffer) < 0) + hsize = 0; + if (!(tctx->options & topt_no_body)) + { + if ((fsize = lseek(deliver_datafile, 0, SEEK_END)) < 0) return FALSE; + fsize -= SPOOL_DATA_START_OFFSET; + if (size_limit > 0 && fsize > size_limit) + fsize = size_limit; + size = hsize + fsize; + if (tctx->options & topt_use_crlf) + size += body_linecount; /* account for CRLF-expansion */ + } + + /* If the message is large, emit first a non-LAST chunk with just the + headers, and reap the command responses. This lets us error out early + on RCPT rejects rather than sending megabytes of data. Include headers + on the assumption they are cheap enough and some clever implementations + might errorcheck them too, on-the-fly, and reject that chunk. */ + + if (size > DELIVER_OUT_BUFFER_SIZE && hsize > 0) + { + if ( tctx->chunk_cb(fd, tctx, hsize, 0) != OK + || !transport_write_block(fd, deliver_out_buffer, hsize) + || tctx->chunk_cb(fd, tctx, 0, tc_reap_prev) != OK + ) + return FALSE; + chunk_ptr = deliver_out_buffer; + size -= hsize; + } + + /* Emit a LAST datachunk command. */ + + if (tctx->chunk_cb(fd, tctx, size, tc_chunk_last) != OK) return FALSE; + + tctx->options &= ~topt_use_bdat; } /* If the body is required, ensure that the data for check strings (formerly @@ -925,23 +985,18 @@ it, applying the size limit if required. */ if (!(tctx->options & topt_no_body)) { + int size = size_limit; + nl_check_length = abs(nl_check_length); nl_partial_match = 0; if (lseek(deliver_datafile, SPOOL_DATA_START_OFFSET, SEEK_SET) < 0) return FALSE; - while ((len = read(deliver_datafile, deliver_in_buffer, - DELIVER_IN_BUFFER_SIZE)) > 0) + while ( (len = MAX(DELIVER_IN_BUFFER_SIZE, size)) > 0 + && (len = read(deliver_datafile, deliver_in_buffer, len)) > 0) { - if (!write_chunk(fd, deliver_in_buffer, len, use_crlf)) return FALSE; - if (size_limit > 0) - { - written += len; - if (written > size_limit) - { - len = 0; /* Pretend EOF */ - break; - } - } + if (!write_chunk(fd, tctx, deliver_in_buffer, len)) + return FALSE; + size -= len; } /* A read error on the body will have left len == -1 and errno set. */ @@ -955,7 +1010,7 @@ nl_check_length = nl_escape_length = 0; /* If requested, add a terminating "." line (SMTP output). */ -if (tctx->options & topt_end_dot && !write_chunk(fd, US".\n", 2, use_crlf)) +if (tctx->options & topt_end_dot && !write_chunk(fd, tctx, US".\n", 2)) return FALSE; /* Write out any remaining data in the buffer before returning. */ @@ -997,7 +1052,9 @@ uschar * dkim_spool_name; int sread = 0; int wwritten = 0; uschar *dkim_signature = NULL; +int siglen = 0; off_t k_file_size; +int options; /* If we can't sign, just call the original function. */ @@ -1017,7 +1074,10 @@ if ((dkim_fd = Uopen(dkim_spool_name, O_RDWR|O_CREAT|O_TRUNC, SPOOL_MODE)) < 0) /* Call original function to write the -K file; does the CRLF expansion */ +options = tctx->options; +tctx->options &= ~topt_use_bdat; rc = transport_write_message(dkim_fd, tctx, 0); +tctx->options = options; /* Save error state. We must clean up before returning. */ if (!rc) @@ -1026,60 +1086,61 @@ if (!rc) goto CLEANUP; } -if (dkim->dkim_private_key && dkim->dkim_domain && dkim->dkim_selector) +/* Rewind file and feed it to the goats^W DKIM lib */ +lseek(dkim_fd, 0, SEEK_SET); +dkim_signature = dkim_exim_sign(dkim_fd, + dkim->dkim_private_key, + dkim->dkim_domain, + dkim->dkim_selector, + dkim->dkim_canon, + dkim->dkim_sign_headers); +if (dkim_signature) + siglen = Ustrlen(dkim_signature); +else if (dkim->dkim_strict) { - /* Rewind file and feed it to the goats^W DKIM lib */ - lseek(dkim_fd, 0, SEEK_SET); - dkim_signature = dkim_exim_sign(dkim_fd, - dkim->dkim_private_key, - dkim->dkim_domain, - dkim->dkim_selector, - dkim->dkim_canon, - dkim->dkim_sign_headers); - if (!dkim_signature) - { - if (dkim->dkim_strict) + uschar *dkim_strict_result = expand_string(dkim->dkim_strict); + if (dkim_strict_result) + if ( (strcmpic(dkim->dkim_strict,US"1") == 0) || + (strcmpic(dkim->dkim_strict,US"true") == 0) ) { - uschar *dkim_strict_result = expand_string(dkim->dkim_strict); - if (dkim_strict_result) - if ( (strcmpic(dkim->dkim_strict,US"1") == 0) || - (strcmpic(dkim->dkim_strict,US"true") == 0) ) - { - /* Set errno to something halfway meaningful */ - save_errno = EACCES; - log_write(0, LOG_MAIN, "DKIM: message could not be signed," - " and dkim_strict is set. Deferring message delivery."); - rc = FALSE; - goto CLEANUP; - } + /* Set errno to something halfway meaningful */ + save_errno = EACCES; + log_write(0, LOG_MAIN, "DKIM: message could not be signed," + " and dkim_strict is set. Deferring message delivery."); + rc = FALSE; + goto CLEANUP; } - } + } - if (dkim_signature) - { - int siglen = Ustrlen(dkim_signature); - while(siglen > 0) - { -#ifdef SUPPORT_TLS - wwritten = tls_out.active == out_fd - ? tls_write(FALSE, dkim_signature, siglen) - : write(out_fd, dkim_signature, siglen); -#else - wwritten = write(out_fd, dkim_signature, siglen); +#ifndef HAVE_LINUX_SENDFILE +if (options & topt_use_bdat) #endif - if (wwritten == -1) - { - /* error, bail out */ - save_errno = errno; - rc = FALSE; - goto CLEANUP; - } - siglen -= wwritten; - dkim_signature += wwritten; - } + k_file_size = lseek(dkim_fd, 0, SEEK_END); /* Fetch file size */ + +if (options & topt_use_bdat) + { + + /* On big messages output a precursor chunk to get any pipelined + MAIL & RCPT commands flushed, then reap the responses so we can + error out on RCPT rejects before sending megabytes. */ + + if (siglen + k_file_size > DELIVER_OUT_BUFFER_SIZE && siglen > 0) + { + if ( tctx->chunk_cb(out_fd, tctx, siglen, 0) != OK + || !transport_write_block(out_fd, dkim_signature, siglen) + || tctx->chunk_cb(out_fd, tctx, 0, tc_reap_prev) != OK + ) + goto err; + siglen = 0; } + + if (tctx->chunk_cb(out_fd, tctx, siglen + k_file_size, tc_chunk_last) != OK) + goto err; } +if(siglen > 0 && !transport_write_block(out_fd, dkim_signature, siglen)) + goto err; + #ifdef HAVE_LINUX_SENDFILE /* We can use sendfile() to shove the file contents to the socket. However only if we don't use TLS, @@ -1090,18 +1151,13 @@ if (tls_out.active != out_fd) ssize_t copied = 0; off_t offset = 0; - k_file_size = lseek(dkim_fd, 0, SEEK_END); /* Fetch file size */ - /* Rewind file */ lseek(dkim_fd, 0, SEEK_SET); while(copied >= 0 && offset < k_file_size) copied = sendfile(out_fd, dkim_fd, &offset, k_file_size - offset); if (copied < 0) - { - save_errno = errno; - rc = FALSE; - } + goto err; } else @@ -1127,12 +1183,7 @@ else wwritten = write(out_fd, p, sread); #endif if (wwritten == -1) - { - /* error, bail out */ - save_errno = errno; - rc = FALSE; - goto CLEANUP; - } + goto err; p += wwritten; sread -= wwritten; } @@ -1146,11 +1197,16 @@ else } CLEANUP: -/* unlink -K file */ -(void)close(dkim_fd); -Uunlink(dkim_spool_name); -errno = save_errno; -return rc; + /* unlink -K file */ + (void)close(dkim_fd); + Uunlink(dkim_spool_name); + errno = save_errno; + return rc; + +err: + save_errno = errno; + rc = FALSE; + goto CLEANUP; } #endif @@ -1177,12 +1233,12 @@ Returns: TRUE on success; FALSE (with errno) for any failure BOOL transport_write_message(int fd, transport_ctx * tctx, int size_limit) { -BOOL use_crlf; +unsigned wck_flags; BOOL last_filter_was_NL = TRUE; int rc, len, yield, fd_read, fd_write, save_errno; int pfd[2] = {-1, -1}; pid_t filter_pid, write_pid; -static transport_ctx dummy_tctx = { NULL, NULL, NULL, NULL, 0 }; +static transport_ctx dummy_tctx = {0}; if (!tctx) tctx = &dummy_tctx; @@ -1201,7 +1257,7 @@ if ( !transport_filter_argv before being written to the incoming fd. First set up the special processing to be done during the copying. */ -use_crlf = (tctx->options & topt_use_crlf) != 0; +wck_flags = tctx->options & topt_use_crlf; nl_partial_match = -1; if (tctx->check_string && tctx->escape_string) @@ -1248,7 +1304,7 @@ if ((write_pid = fork()) == 0) nl_check_length = nl_escape_length = 0; tctx->check_string = tctx->escape_string = NULL; - tctx->options &= ~(topt_use_crlf | topt_end_dot); + tctx->options &= ~(topt_use_crlf | topt_end_dot | topt_use_bdat); rc = internal_transport_write_message(fd_write, tctx, size_limit); @@ -1317,7 +1373,7 @@ for (;;) if (len > 0) { - if (!write_chunk(fd, deliver_in_buffer, len, use_crlf)) goto TIDY_UP; + if (!write_chunk(fd, tctx, deliver_in_buffer, len)) goto TIDY_UP; last_filter_was_NL = (deliver_in_buffer[len-1] == '\n'); } @@ -1399,8 +1455,8 @@ if (yield) nl_check_length = nl_escape_length = 0; if ( tctx->options & topt_end_dot && ( last_filter_was_NL - ? !write_chunk(fd, US".\n", 2, use_crlf) - : !write_chunk(fd, US"\n.\n", 3, use_crlf) + ? !write_chunk(fd, tctx, US".\n", 2) + : !write_chunk(fd, tctx, US"\n.\n", 3) ) ) yield = FALSE; @@ -1883,7 +1939,7 @@ DEBUG(D_transport) debug_printf("transport_pass_socket entered\n"); if ((pid = fork()) == 0) { - int i = 16; + int i = 17; const uschar **argv; /* Disconnect entirely from the parent process. If we are running in the @@ -1899,16 +1955,15 @@ if ((pid = fork()) == 0) argv = CUSS child_exec_exim(CEE_RETURN_ARGV, TRUE, &i, FALSE, 0); - if (smtp_use_dsn) argv[i++] = US"-MCD"; - if (smtp_authenticated) argv[i++] = US"-MCA"; - #ifdef SUPPORT_TLS - if (tls_offered) argv[i++] = US"-MCT"; - #endif - - if (smtp_use_size) argv[i++] = US"-MCS"; - if (smtp_use_pipelining) argv[i++] = US"-MCP"; + if (smtp_peer_options & PEER_OFFERED_CHUNKING) argv[i++] = US"-MCK"; + if (smtp_peer_options & PEER_OFFERED_DSN) argv[i++] = US"-MCD"; + if (smtp_peer_options & PEER_OFFERED_PIPE) argv[i++] = US"-MCP"; + if (smtp_peer_options & PEER_OFFERED_SIZE) argv[i++] = US"-MCS"; +#ifdef SUPPORT_TLS + if (smtp_peer_options & PEER_OFFERED_TLS) argv[i++] = US"-MCT"; +#endif if (queue_run_pid != (pid_t)0) { diff --git a/src/src/transports/autoreply.c b/src/src/transports/autoreply.c index 36a68b92c..f07cd83cf 100644 --- a/src/src/transports/autoreply.c +++ b/src/src/transports/autoreply.c @@ -694,8 +694,7 @@ if (return_message) transport_ctx tctx = { tblock, addr, - NULL, - NULL, + NULL, NULL, (tblock->body_only ? topt_no_headers : 0) | (tblock->headers_only ? topt_no_body : 0) | (tblock->return_path_add ? topt_add_return_path : 0) | diff --git a/src/src/transports/lmtp.c b/src/src/transports/lmtp.c index 9624ece6a..0cc981064 100644 --- a/src/src/transports/lmtp.c +++ b/src/src/transports/lmtp.c @@ -612,8 +612,7 @@ if (send_data) transport_ctx tctx = { tblock, addrlist, - US".", - US"..", + US".", US"..", ob->options }; diff --git a/src/src/transports/pipe.c b/src/src/transports/pipe.c index e3d01974a..d3841e050 100644 --- a/src/src/transports/pipe.c +++ b/src/src/transports/pipe.c @@ -554,7 +554,7 @@ const uschar **argv; uschar *envp[50]; const uschar *envlist = ob->environment; uschar *cmd, *ss; -uschar *eol = (ob->use_crlf)? US"\r\n" : US"\n"; +uschar *eol = ob->use_crlf ? US"\r\n" : US"\n"; transport_ctx tctx = { tblock, addr, diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c index bbfef0632..52b2b913f 100644 --- a/src/src/transports/smtp.c +++ b/src/src/transports/smtp.c @@ -12,6 +12,8 @@ #define PENDING_DEFER (PENDING + DEFER) #define PENDING_OK (PENDING + OK) +#define DELIVER_BUFFER_SIZE 4096 + /* Options specific to the smtp transport. This transport also supports LMTP over TCP/IP. The options must be in alphabetic order (note that "_" comes @@ -116,6 +118,8 @@ optionlist smtp_transport_options[] = { #endif { "hosts_try_auth", opt_stringptr, (void *)offsetof(smtp_transport_options_block, hosts_try_auth) }, + { "hosts_try_chunking", opt_stringptr, + (void *)offsetof(smtp_transport_options_block, hosts_try_chunking) }, #if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_DANE) { "hosts_try_dane", opt_stringptr, (void *)offsetof(smtp_transport_options_block, hosts_try_dane) }, @@ -200,12 +204,13 @@ smtp_transport_options_block smtp_transport_option_defaults = { NULL, /* serialize_hosts */ NULL, /* hosts_try_auth */ NULL, /* hosts_require_auth */ + US"*", /* hosts_try_chunking */ #ifdef EXPERIMENTAL_DANE NULL, /* hosts_try_dane */ NULL, /* hosts_require_dane */ #endif #ifndef DISABLE_PRDR - US"*", /* hosts_try_prdr */ + US"*", /* hosts_try_prdr */ #endif #ifndef DISABLE_OCSP US"*", /* hosts_request_ocsp (except under DANE; tls_client_start()) */ @@ -778,6 +783,7 @@ if (pending_MAIL) count--; if (!smtp_read_response(inblock, buffer, buffsize, '2', timeout)) { + DEBUG(D_transport) debug_printf("bad response for MAIL\n"); Ustrcpy(big_buffer, mail_command); /* Fits, because it came from there! */ if (errno == 0 && buffer[0] != 0) { @@ -1321,6 +1327,10 @@ if ( checks & PEER_OFFERED_IGNQ PCRE_EOPT, NULL, 0) < 0) checks &= ~PEER_OFFERED_IGNQ; +if ( checks & PEER_OFFERED_CHUNKING + && pcre_exec(regex_CHUNKING, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0) + checks &= ~PEER_OFFERED_CHUNKING; + #ifndef DISABLE_PRDR if ( checks & PEER_OFFERED_PRDR && pcre_exec(regex_PRDR, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0) @@ -1350,6 +1360,99 @@ return checks; } + +/* Callback for emitting a BDAT data chunk header. + +If given a nonzero size, first flush any buffered SMTP commands +then emit the command. + +Reap previous SMTP command responses if requested. +Reap one SMTP command response if requested. + +Returns: OK or ERROR +*/ + +static int +smtp_chunk_cmd_callback(int fd, transport_ctx * tctx, + unsigned chunk_size, unsigned flags) +{ +smtp_transport_options_block * ob = + (smtp_transport_options_block *)(tctx->tblock->options_block); +int cmd_count = 0; +int prev_cmd_count; +uschar * buffer = tctx->buffer; + + +/* Write SMTP chunk header command */ + +if (chunk_size > 0) + if((cmd_count = smtp_write_command(tctx->outblock, FALSE, "BDAT %u%s\r\n", + chunk_size, + flags & tc_chunk_last ? " LAST" : "") + ) < 0) return ERROR; + +prev_cmd_count = cmd_count += tctx->cmd_count; + +/* Reap responses for any previous, but not one we just emitted */ + +if (chunk_size > 0) + prev_cmd_count--; +if (tctx->pending_BDAT) + prev_cmd_count--; + +if (flags & tc_reap_prev && prev_cmd_count > 0) + { + + switch(sync_responses(tctx->first_addr, tctx->tblock->rcpt_include_affixes, + tctx->sync_addr, tctx->host, prev_cmd_count, + ob->address_retry_include_sender, + tctx->pending_MAIL, 0, + tctx->inblock, + ob->command_timeout, + buffer, DELIVER_BUFFER_SIZE)) + { + case 1: /* 2xx (only) => OK */ + case 3: tctx->good_RCPT = TRUE; /* 2xx & 5xx => OK & progress made */ + case 2: *tctx->completed_address = TRUE; /* 5xx (only) => progress made */ + case 0: break; /* No 2xx or 5xx, but no probs */ + + case -1: /* Timeout on RCPT */ + default: return ERROR; /* I/O error, or any MAIL/DATA error */ + } + cmd_count = 1; + if (!tctx->pending_BDAT) + pipelining_active = FALSE; + } + +/* Reap response for the cmd we just emitted, or an outstanding BDAT */ + +if (flags & tc_reap_one || tctx->pending_BDAT) + { + if (!smtp_read_response(tctx->inblock, buffer, DELIVER_BUFFER_SIZE, '2', + ob->command_timeout)) + { + if (errno == 0 && buffer[0] == '4') + { + errno = ERRNO_DATA4XX; /*XXX does this actually get used? */ + tctx->first_addr->more_errno |= + ((buffer[1] - '0')*10 + buffer[2] - '0') << 8; + } + return ERROR; + } + cmd_count--; + tctx->pending_BDAT = FALSE; + pipelining_active = FALSE; + } +else if (chunk_size > 0) + tctx->pending_BDAT = TRUE; + + +tctx->cmd_count = cmd_count; +return OK; +} + + + /************************************************* * Deliver address list to given host * *************************************************/ @@ -1419,7 +1522,7 @@ BOOL completed_address = FALSE; BOOL esmtp = TRUE; BOOL pending_MAIL; BOOL pass_message = FALSE; -uschar peer_offered = 0; /*XXX should this be handed on cf. tls_offered, smtp_use_dsn ? */ +uschar peer_offered = 0; #ifndef DISABLE_PRDR BOOL prdr_active; #endif @@ -1446,7 +1549,7 @@ uschar *helo_data = NULL; uschar *message = NULL; uschar new_message_id[MESSAGE_ID_LENGTH + 1]; uschar *p; -uschar buffer[4096]; +uschar buffer[DELIVER_BUFFER_SIZE]; uschar inbuffer[4096]; uschar outbuffer[4096]; @@ -1654,7 +1757,7 @@ goto SEND_QUIT; #ifdef SUPPORT_TLS if (smtps) { - tls_offered = TRUE; + smtp_peer_options |= PEER_OFFERED_TLS; suppress_tls = FALSE; ob->tls_tempfail_tryclear = FALSE; smtp_command = US"SSL-on-connect"; @@ -1703,7 +1806,10 @@ goto SEND_QUIT; if (!good_response) goto RESPONSE_FAILED; } + peer_offered = smtp_peer_options = 0; + if (esmtp || lmtp) + { peer_offered = ehlo_response(buffer, Ustrlen(buffer), PEER_OFFERED_TLS /* others checked later */ ); @@ -1711,14 +1817,15 @@ goto SEND_QUIT; /* Set tls_offered if the response to EHLO specifies support for STARTTLS. */ #ifdef SUPPORT_TLS - tls_offered = !!(peer_offered & PEER_OFFERED_TLS); + smtp_peer_options |= peer_offered & PEER_OFFERED_TLS; #endif + } } /* For continuing deliveries down the same channel, the socket is the standard input, and we don't need to redo EHLO here (but may need to do so for TLS - see below). Set up the pointer to where subsequent commands will be left, for -error messages. Note that smtp_use_size and smtp_use_pipelining will have been +error messages. Note that smtp_peer_options will have been set from the command line if they were set in the process that passed the connection on. */ @@ -1743,7 +1850,7 @@ the client not be required to use TLS. If the response is bad, copy the buffer for error analysis. */ #ifdef SUPPORT_TLS -if ( tls_offered +if ( smtp_peer_options & PEER_OFFERED_TLS && !suppress_tls && verify_check_given_host(&ob->hosts_avoid_tls, host) != OK) { @@ -1805,6 +1912,7 @@ if ( tls_offered /* TLS session is set up */ + smtp_peer_options_wrap = smtp_peer_options; for (addr = addrlist; addr; addr = addr->next) if (addr->transport_return == PENDING_DEFER) { @@ -1874,6 +1982,7 @@ if (tls_out.active >= 0) helo_response = string_copy(buffer); #endif if (!good_response) goto RESPONSE_FAILED; + smtp_peer_options = 0; } /* If the host is required to use a secure channel, ensure that we @@ -1888,8 +1997,8 @@ else if ( smtps { save_errno = ERRNO_TLSREQUIRED; message = string_sprintf("a TLS session is required, but %s", - tls_offered ? "an attempt to start TLS failed" - : "the server did not offer TLS support"); + smtp_peer_options & PEER_OFFERED_TLS + ? "an attempt to start TLS failed" : "the server did not offer TLS support"); goto TLS_FAILED; } #endif /*SUPPORT_TLS*/ @@ -1906,13 +2015,15 @@ if (continue_hostname == NULL ) { if (esmtp || lmtp) + { peer_offered = ehlo_response(buffer, Ustrlen(buffer), 0 /* no TLS */ | (lmtp && ob->lmtp_ignore_quota ? PEER_OFFERED_IGNQ : 0) + | PEER_OFFERED_CHUNKING | PEER_OFFERED_PRDR #ifdef SUPPORT_I18N | (addrlist->prop.utf8_msg ? PEER_OFFERED_UTF8 : 0) - /*XXX if we hand peercaps on to continued-conn processes, + /*XXX if we hand peercaps on to continued-conn processes, must not depend on this addr */ #endif | PEER_OFFERED_DSN @@ -1920,54 +2031,64 @@ if (continue_hostname == NULL | (ob->size_addition >= 0 ? PEER_OFFERED_SIZE : 0) ); - /* Set for IGNOREQUOTA if the response to LHLO specifies support and the - lmtp_ignore_quota option was set. */ + /* Set for IGNOREQUOTA if the response to LHLO specifies support and the + lmtp_ignore_quota option was set. */ + + igquotstr = peer_offered & PEER_OFFERED_IGNQ ? US" IGNOREQUOTA" : US""; - igquotstr = peer_offered & PEER_OFFERED_IGNQ ? US" IGNOREQUOTA" : US""; + /* If the response to EHLO specified support for the SIZE parameter, note + this, provided size_addition is non-negative. */ - /* If the response to EHLO specified support for the SIZE parameter, note - this, provided size_addition is non-negative. */ + smtp_peer_options |= peer_offered & PEER_OFFERED_SIZE; - smtp_use_size = !!(peer_offered & PEER_OFFERED_SIZE); + /* Note whether the server supports PIPELINING. If hosts_avoid_esmtp matched + the current host, esmtp will be false, so PIPELINING can never be used. If + the current host matches hosts_avoid_pipelining, don't do it. */ - /* Note whether the server supports PIPELINING. If hosts_avoid_esmtp matched - the current host, esmtp will be false, so PIPELINING can never be used. If - the current host matches hosts_avoid_pipelining, don't do it. */ + if ( peer_offered & PEER_OFFERED_PIPE + && verify_check_given_host(&ob->hosts_avoid_pipelining, host) != OK) + smtp_peer_options |= PEER_OFFERED_PIPE; - smtp_use_pipelining = peer_offered & PEER_OFFERED_PIPE - && verify_check_given_host(&ob->hosts_avoid_pipelining, host) != OK; + DEBUG(D_transport) debug_printf("%susing PIPELINING\n", + smtp_peer_options & PEER_OFFERED_PIPE ? "" : "not "); - DEBUG(D_transport) debug_printf("%susing PIPELINING\n", - smtp_use_pipelining ? "" : "not "); + if ( peer_offered & PEER_OFFERED_CHUNKING + && verify_check_given_host(&ob->hosts_try_chunking, host) != OK) + peer_offered &= ~PEER_OFFERED_CHUNKING; + + if (peer_offered & PEER_OFFERED_CHUNKING) + {DEBUG(D_transport) debug_printf("CHUNKING usable\n");} #ifndef DISABLE_PRDR - if ( peer_offered & PEER_OFFERED_PRDR - && verify_check_given_host(&ob->hosts_try_prdr, host) != OK) - peer_offered &= ~PEER_OFFERED_PRDR; + if ( peer_offered & PEER_OFFERED_PRDR + && verify_check_given_host(&ob->hosts_try_prdr, host) != OK) + peer_offered &= ~PEER_OFFERED_PRDR; - if (peer_offered & PEER_OFFERED_PRDR) - {DEBUG(D_transport) debug_printf("PRDR usable\n");} + if (peer_offered & PEER_OFFERED_PRDR) + {DEBUG(D_transport) debug_printf("PRDR usable\n");} #endif - /* Note if the server supports DSN */ - smtp_use_dsn = !!(peer_offered & PEER_OFFERED_DSN); - DEBUG(D_transport) debug_printf("%susing DSN\n", smtp_use_dsn ? "" : "not "); + /* Note if the server supports DSN */ + smtp_peer_options |= peer_offered & PEER_OFFERED_DSN; + DEBUG(D_transport) debug_printf("%susing DSN\n", + peer_offered & PEER_OFFERED_DSN ? "" : "not "); - /* Note if the response to EHLO specifies support for the AUTH extension. - If it has, check that this host is one we want to authenticate to, and do - the business. The host name and address must be available when the - authenticator's client driver is running. */ + /* Note if the response to EHLO specifies support for the AUTH extension. + If it has, check that this host is one we want to authenticate to, and do + the business. The host name and address must be available when the + authenticator's client driver is running. */ - switch (yield = smtp_auth(buffer, sizeof(buffer), addrlist, host, - ob, esmtp, &inblock, &outblock)) - { - default: goto SEND_QUIT; - case OK: break; - case FAIL_SEND: goto SEND_FAILED; - case FAIL: goto RESPONSE_FAILED; + switch (yield = smtp_auth(buffer, sizeof(buffer), addrlist, host, + ob, esmtp, &inblock, &outblock)) + { + default: goto SEND_QUIT; + case OK: break; + case FAIL_SEND: goto SEND_FAILED; + case FAIL: goto RESPONSE_FAILED; + } } } -pipelining_active = smtp_use_pipelining; +pipelining_active = !!(smtp_peer_options & PEER_OFFERED_PIPE); /* The setting up of the SMTP call is now complete. Any subsequent errors are message-specific. */ @@ -2013,6 +2134,16 @@ if (tblock->filter_command != NULL) yield = ERROR; goto SEND_QUIT; } + + if ( transport_filter_argv + && *transport_filter_argv + && **transport_filter_argv + && peer_offered & PEER_OFFERED_CHUNKING + ) + { + peer_offered &= ~PEER_OFFERED_CHUNKING; + DEBUG(D_transport) debug_printf("CHUNKING not usable due to transport filter\n"); + } } @@ -2042,7 +2173,7 @@ included in the count.) */ p = buffer; *p = 0; -if (smtp_use_size) +if (peer_offered & PEER_OFFERED_SIZE) { sprintf(CS p, " SIZE=%d", message_size+message_linecount+ob->size_addition); while (*p) p++; @@ -2086,17 +2217,14 @@ for (dsn_all_lasthop = TRUE, addr = first_addr; /* Add any DSN flags to the mail command */ -if (smtp_use_dsn && !dsn_all_lasthop) +if (peer_offered & PEER_OFFERED_DSN && !dsn_all_lasthop) { if (dsn_ret == dsn_ret_hdrs) - { - Ustrcpy(p, " RET=HDRS"); p += 9; - } + { Ustrcpy(p, " RET=HDRS"); p += 9; } else if (dsn_ret == dsn_ret_full) - { - Ustrcpy(p, " RET=FULL"); p += 9; - } - if (dsn_envid != NULL) + { Ustrcpy(p, " RET=FULL"); p += 9; } + + if (dsn_envid) { string_format(p, sizeof(buffer) - (p-buffer), " ENVID=%s", dsn_envid); while (*p) p++; @@ -2147,7 +2275,7 @@ pending_MAIL = TRUE; /* The block starts with MAIL */ } #endif - rc = smtp_write_command(&outblock, smtp_use_pipelining, + rc = smtp_write_command(&outblock, pipelining_active, "MAIL FROM:<%s>%s\r\n", s, buffer); } @@ -2194,21 +2322,22 @@ for (addr = first_addr; BOOL no_flush; uschar * rcpt_addr; - addr->dsn_aware = smtp_use_dsn ? dsn_support_yes : dsn_support_no; + addr->dsn_aware = peer_offered & PEER_OFFERED_DSN + ? dsn_support_yes : dsn_support_no; if (addr->transport_return != PENDING_DEFER) continue; address_count++; - no_flush = smtp_use_pipelining && (!mua_wrapper || addr->next); + no_flush = pipelining_active && (!mua_wrapper || addr->next); /* Add any DSN flags to the rcpt command and add to the sent string */ p = buffer; *p = 0; - if (smtp_use_dsn && !(addr->dsn_flags & rf_dsnlasthop)) + if (peer_offered & PEER_OFFERED_DSN && !(addr->dsn_flags & rf_dsnlasthop)) { - if ((addr->dsn_flags & rf_dsnflags) != 0) + if (addr->dsn_flags & rf_dsnflags) { int i; BOOL first = TRUE; @@ -2302,15 +2431,19 @@ if (mua_wrapper) send DATA, but if it is FALSE (in the normal, non-wrapper case), we may still have a good recipient buffered up if we are pipelining. We don't want to waste time sending DATA needlessly, so we only send it if either ok is TRUE or if we -are pipelining. The responses are all handled by sync_responses(). */ +are pipelining. The responses are all handled by sync_responses(). +If using CHUNKING, do not send a BDAT until we know how big a chunk we want +to send is. */ -if (ok || (smtp_use_pipelining && !mua_wrapper)) +if ( !(peer_offered & PEER_OFFERED_CHUNKING) + && (ok || (pipelining_active && !mua_wrapper))) { int count = smtp_write_command(&outblock, FALSE, "DATA\r\n"); + if (count < 0) goto SEND_FAILED; switch(sync_responses(first_addr, tblock->rcpt_include_affixes, &sync_addr, host, count, ob->address_retry_include_sender, pending_MAIL, - ok? +1 : -1, &inblock, ob->command_timeout, buffer, sizeof(buffer))) + ok ? +1 : -1, &inblock, ob->command_timeout, buffer, sizeof(buffer))) { case 3: ok = TRUE; /* 2xx & 5xx => OK & progress made */ case 2: completed_address = TRUE; /* 5xx (only) => progress made */ @@ -2326,10 +2459,6 @@ if (ok || (smtp_use_pipelining && !mua_wrapper)) pipelining_active = FALSE; } -/* Save the first address of the next batch. */ - -first_addr = addr; - /* If there were no good recipients (but otherwise there have been no problems), just set ok TRUE, since we have handled address-specific errors already. Otherwise, it's OK to send the message. Use the check/escape mechanism @@ -2337,15 +2466,20 @@ for handling the SMTP dot-handling protocol, flagging to apply to headers as well as body. Set the appropriate timeout value to be used for each chunk. (Haven't been able to make it work using select() for writing yet.) */ -if (!ok) +if (!(peer_offered & PEER_OFFERED_CHUNKING) && !ok) + { + /* Save the first address of the next batch. */ + first_addr = addr; + ok = TRUE; + } else { transport_ctx tctx = { tblock, addrlist, US".", US"..", /* Escaping strings */ - topt_use_crlf | topt_end_dot | topt_escape_headers + topt_use_crlf | topt_escape_headers | (tblock->body_only ? topt_no_headers : 0) | (tblock->headers_only ? topt_no_body : 0) | (tblock->return_path_add ? topt_add_return_path : 0) @@ -2353,11 +2487,47 @@ else | (tblock->envelope_to_add ? topt_add_envelope_to : 0) }; + /* If using CHUNKING we need a callback from the generic transport + support to us, for the sending of BDAT smtp commands and the reaping + of responses. The callback needs a whole bunch of state so set up + a transport-context structure to be passed around. */ + + if (peer_offered & PEER_OFFERED_CHUNKING) + { + tctx.check_string = tctx.escape_string = NULL; + tctx.options |= topt_use_bdat; + tctx.chunk_cb = smtp_chunk_cmd_callback; + tctx.inblock = &inblock; + tctx.outblock = &outblock; + tctx.host = host; + tctx.first_addr = first_addr; + tctx.sync_addr = &sync_addr; + tctx.pending_MAIL = pending_MAIL; + tctx.pending_BDAT = FALSE; + tctx.good_RCPT = ok; + tctx.completed_address = &completed_address; + tctx.cmd_count = 0; + tctx.buffer = buffer; + } + else + tctx.options |= topt_end_dot; + + /* Save the first address of the next batch. */ + first_addr = addr; + + /* Responses from CHUNKING commands go in buffer. Otherwise, + there has not been a response. */ + + buffer[0] = 0; + sigalrm_seen = FALSE; transport_write_timeout = ob->data_timeout; smtp_command = US"sending data block"; /* For error messages */ DEBUG(D_transport|D_v) - debug_printf(" SMTP>> writing message and terminating \".\"\n"); + if (peer_offered & PEER_OFFERED_CHUNKING) + debug_printf(" will write message using CHUNKING\n"); + else + debug_printf(" SMTP>> writing message and terminating \".\"\n"); transport_count = 0; #ifndef DISABLE_DKIM @@ -2374,13 +2544,11 @@ else transport_write_timeout = 0; /* for subsequent transports */ /* Failure can either be some kind of I/O disaster (including timeout), - or the failure of a transport filter or the expansion of added headers. */ + or the failure of a transport filter or the expansion of added headers. + Or, when CHUNKING, it can be a protocol-detected failure. */ if (!ok) - { - buffer[0] = 0; /* There hasn't been a response */ goto RESPONSE_FAILED; - } /* We used to send the terminating "." explicitly here, but because of buffering effects at both ends of TCP/IP connections, you don't gain @@ -2390,6 +2558,27 @@ else smtp_command = US"end of data"; + if (peer_offered & PEER_OFFERED_CHUNKING && tctx.cmd_count > 1) + { + /* Reap any outstanding MAIL & RCPT commands, but not a DATA-go-ahead */ + switch(sync_responses(first_addr, tblock->rcpt_include_affixes, &sync_addr, + host, tctx.cmd_count-1, ob->address_retry_include_sender, + pending_MAIL, 0, + &inblock, ob->command_timeout, buffer, sizeof(buffer))) + { + case 3: ok = TRUE; /* 2xx & 5xx => OK & progress made */ + case 2: completed_address = TRUE; /* 5xx (only) => progress made */ + break; + + case 1: ok = TRUE; /* 2xx (only) => OK, but if LMTP, */ + if (!lmtp) completed_address = TRUE; /* can't tell about progress yet */ + case 0: break; /* No 2xx or 5xx, but no probs */ + + case -1: goto END_OFF; /* Timeout on RCPT */ + default: goto RESPONSE_FAILED; /* I/O error, or any MAIL/DATA error */ + } + } + #ifndef DISABLE_PRDR /* For PRDR we optionally get a partial-responses warning * followed by the individual responses, before going on with @@ -2399,16 +2588,16 @@ else { ok = smtp_read_response(&inblock, buffer, sizeof(buffer), '3', ob->final_timeout); - if (!ok && errno == 0) - switch(buffer[0]) - { - case '2': prdr_active = FALSE; - ok = TRUE; - break; - case '4': errno = ERRNO_DATA4XX; - addrlist->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8; - break; - } + if (!ok && errno == 0) switch(buffer[0]) + { + case '2': prdr_active = FALSE; + ok = TRUE; + break; + case '4': errno = ERRNO_DATA4XX; + addrlist->more_errno |= + ((buffer[1] - '0')*10 + buffer[2] - '0') << 8; + break; + } } else #endif @@ -2527,6 +2716,7 @@ else #ifndef DISABLE_PRDR if (prdr_active) addr->flags |= af_prdr_used; #endif + if (peer_offered & PEER_OFFERED_CHUNKING) addr->flags |= af_chunking_used; flag = '-'; #ifndef DISABLE_PRDR @@ -2853,6 +3043,7 @@ if (completed_address && ok && send_quit) if (tls_out.active >= 0) { tls_close(FALSE, TRUE); + smtp_peer_options = smtp_peer_options_wrap; if (smtps) ok = FALSE; else diff --git a/src/src/transports/smtp.h b/src/src/transports/smtp.h index 804b9942f..d3666ae78 100644 --- a/src/src/transports/smtp.h +++ b/src/src/transports/smtp.h @@ -21,6 +21,7 @@ typedef struct { uschar *serialize_hosts; uschar *hosts_try_auth; uschar *hosts_require_auth; + uschar *hosts_try_chunking; #ifdef EXPERIMENTAL_DANE uschar *hosts_try_dane; uschar *hosts_require_dane; diff --git a/src/src/verify.c b/src/src/verify.c index c04b288dc..d890f5fc6 100644 --- a/src/src/verify.c +++ b/src/src/verify.c @@ -759,7 +759,7 @@ can do it there for the non-rcpt-verify case. For this we keep an addresscount. ? string_sprintf(" SIZE=%d", message_size + ob->size_addition) : US""; #ifdef SUPPORT_TLS - tls_offered = !!(peer_offered & PEER_OFFERED_TLS); + smtp_peer_options |= peer_offered & PEER_OFFERED_TLS; #endif /* If TLS is available on this connection attempt to @@ -1559,9 +1559,9 @@ return cutthrough_response('3', NULL) == '3'; } -/* fd and use_crlf args only to match write_chunk() */ +/* fd and tctx args only to match write_chunk() */ static BOOL -cutthrough_write_chunk(int fd, uschar * s, int len, BOOL use_crlf) +cutthrough_write_chunk(int fd, transport_ctx * tctx, uschar * s, int len) { uschar * s2; while(s && (s2 = Ustrchr(s, '\n'))) @@ -1580,6 +1580,8 @@ return TRUE; BOOL cutthrough_headers_send(void) { +transport_ctx tctx; + if(cutthrough.fd < 0) return FALSE; @@ -1588,9 +1590,13 @@ if(cutthrough.fd < 0) */ HDEBUG(D_acl) debug_printf("----------- start cutthrough headers send -----------\n"); -if (!transport_headers_send(&cutthrough.addr, cutthrough.fd, - cutthrough.addr.transport, - &cutthrough_write_chunk, TRUE)) +tctx.tblock = cutthrough.addr.transport; +tctx.addr = &cutthrough.addr; +tctx.check_string = US"."; +tctx.escape_string = US".."; +tctx.options = topt_use_crlf; + +if (!transport_headers_send(cutthrough.fd, &tctx, &cutthrough_write_chunk)) return FALSE; HDEBUG(D_acl) debug_printf("----------- done cutthrough headers send ------------\n"); |