From 18481de384caecff421f23f715be916403f5d0ee Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Mon, 11 Jul 2016 23:36:45 +0100 Subject: [PATCH] recive smtp command --- src/src/globals.c | 4 +++ src/src/globals.h | 2 ++ src/src/macros.h | 3 +- src/src/receive.c | 6 ++++ src/src/smtp_in.c | 71 +++++++++++++++++++++++++++++++++++++++-------- src/src/structs.h | 2 ++ 6 files changed, 76 insertions(+), 12 deletions(-) diff --git a/src/src/globals.c b/src/src/globals.c index 5ff0f844b..2e3fe4074 100644 --- a/src/src/globals.c +++ b/src/src/globals.c @@ -492,7 +492,11 @@ int check_log_space = 0; BOOL check_rfc2047_length = TRUE; int check_spool_inodes = 0; int check_spool_space = 0; + uschar *chunking_advertise_hosts = US"*"; +unsigned chunking_datasize = 0; +chunking_state_t chunking_state= CHUNKING_NOT_OFFERED; + uschar *client_authenticator = NULL; uschar *client_authenticated_id = NULL; uschar *client_authenticated_sender = NULL; diff --git a/src/src/globals.h b/src/src/globals.h index e5bdec4a2..184a144f2 100644 --- a/src/src/globals.h +++ b/src/src/globals.h @@ -268,6 +268,8 @@ 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 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) */ diff --git a/src/src/macros.h b/src/src/macros.h index 53abeb5c2..dbc49f01e 100644 --- a/src/src/macros.h +++ b/src/src/macros.h @@ -787,7 +787,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 }; diff --git a/src/src/receive.c b/src/src/receive.c index 52e041c90..f6bdf4742 100644 --- a/src/src/receive.c +++ b/src/src/receive.c @@ -770,6 +770,9 @@ Arguments: Returns: One of the END_xxx values indicating why it stopped reading */ +/*XXX CHUNKING: maybe a variant routing specialised for BDAT, assuming +string RFC compliance ie. CRLF always? We still have to strip the CR +but we are not dealing with variant lunacy or looking for the end-dot */ static int read_message_data_smtp(FILE *fout) @@ -1613,6 +1616,7 @@ next->text. */ for (;;) { +/*XXX CHUNKING: account for BDAT size & last, and do more chunks as needed */ int ch = (receive_getc)(); /* If we hit EOF on a SMTP connection, it's an error, since incoming @@ -2831,6 +2835,7 @@ if (filter_test != FTEST_NONE) return message_ended == END_DOT; } +/*XXX CHUNKING: need to cancel cutthrough under BDAT, for now */ /* 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. @@ -2928,6 +2933,7 @@ if (!ferror(data_file) && !(receive_feof)() && message_ended != END_DOT) { if (smtp_input) { +/*XXX CHUNKING: main data read, for message body */ message_ended = read_message_data_smtp(data_file); receive_linecount++; /* The terminating "." line */ } diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c index bc53166e5..b00537eb5 100644 --- a/src/src/smtp_in.c +++ b/src/src/smtp_in.c @@ -73,6 +73,7 @@ enum { ETRN_CMD, /* This by analogy with TURN from the RFC */ STARTTLS_CMD, /* Required by the STARTTLS RFC */ TLS_AUTH_CMD, /* auto-command at start of SSL */ + BDAT_CMD, /* Implied by RFC3030 "After all MAIL and..." */ /* This is a dummy to identify the non-sync commands when pipelining */ @@ -182,6 +183,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 +207,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 */ @@ -1525,6 +1527,7 @@ authenticated_sender = NULL; bmi_run = 0; bmi_verdicts = NULL; #endif +chunking_state = CHUNKING_NOT_OFFERED; #ifndef DISABLE_DKIM dkim_signers = NULL; dkim_disable_verify = FALSE; @@ -3766,17 +3769,20 @@ while (done <= 0) if (!first) s = string_catn(s, &size, &ptr, US"\r\n", 2); } - /* 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 - secure connection. */ + /* 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_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 + secure connection. */ + #ifdef SUPPORT_TLS if (tls_in.active < 0 && verify_check_host(&tls_advertise_hosts) != FAIL) @@ -4524,8 +4530,38 @@ 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, 503, NULL, + US"missing size for BDAT command"); + break; + } + chunking_state = strcmpic(smtp_cmd_data+n, US"LAST") == 0 + ? CHUNKING_LAST : CHUNKING_ACTIVE; + + 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) @@ -4540,10 +4576,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; } @@ -4555,11 +4594,21 @@ while (done <= 0) break; } + /* No go-ahead output for BDAT */ + + if (smtp_connection_had[smtp_ch_index-1] == SCH_BDAT) + { + rc = OK; + 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 + 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; @@ -4702,7 +4751,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 */ diff --git a/src/src/structs.h b/src/src/structs.h index 1dd363a5c..2b449a648 100644 --- a/src/src/structs.h +++ b/src/src/structs.h @@ -51,6 +51,8 @@ typedef struct ugid_block { BOOL initgroups; } ugid_block; +typedef enum {CHUNKING_NOT_OFFERED, 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. */ -- 2.30.2