X-Git-Url: https://git.netwichtig.de/gitweb/?a=blobdiff_plain;f=src%2Fsrc%2Fsmtp_in.c;h=d7080168a1f26fa07aa7707f8f1d2f4252535b6e;hb=c9eab729ed1dfc555a784aa98d4e58f004ef1e42;hp=b00537eb58af560023a28135dfb9e34a170285d6;hpb=18481de384caecff421f23f715be916403f5d0ee;p=user%2Fhenk%2Fcode%2Fexim.git diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c index b00537eb5..d7080168a 100644 --- a/src/src/smtp_in.c +++ b/src/src/smtp_in.c @@ -73,7 +73,6 @@ 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 */ @@ -83,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, @@ -291,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() * *************************************************/ @@ -310,6 +325,7 @@ smtp_getc(void) if (smtp_inptr >= smtp_inend) { int rc, save_errno; + if (!smtp_out) return EOF; fflush(smtp_out); if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout); rc = read(fileno(smtp_in), smtp_inbuffer, in_buffer_size); @@ -337,6 +353,145 @@ if (smtp_inptr >= smtp_inend) return *smtp_inptr++; } +void +smtp_get_cache(void) +{ +#ifndef DISABLE_DKIM +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(NULL, 0); /* notify EOD */ +#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 */ + } + } + } +} + +static void +bdat_flush_data(void) +{ +while (chunking_data_left-- > 0) + if (lwr_receive_getc() < 0) + break; + +receive_getc = lwr_receive_getc; +receive_ungetc = lwr_receive_ungetc; + +if (chunking_state != CHUNKING_LAST) + chunking_state = CHUNKING_OFFERED; +} + + /************************************************* @@ -355,11 +510,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); +} + /************************************************* @@ -599,10 +761,10 @@ Arguments: fd - File descriptor for input Returns: none */ static void -restore_socket_timeout(int fd, int get_ok, struct timeval tvtmp, socklen_t vslen) +restore_socket_timeout(int fd, int get_ok, struct timeval * tvtmp, socklen_t vslen) { if (get_ok == 0) - setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tvtmp, vslen); + (void) setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, CS tvtmp, vslen); } /************************************************* @@ -643,7 +805,7 @@ so exit with an error if do not find the exact required pieces. This includes an incorrect number of spaces separating args. Arguments: none -Returns: int +Returns: Boolean success */ static BOOL @@ -687,27 +849,23 @@ char tmpip6[INET6_ADDRSTRLEN]; struct sockaddr_in6 tmpaddr6; int get_ok = 0; -int size, ret, fd; +int size, ret; +int fd = fileno(smtp_in); const char v2sig[12] = "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A"; uschar *iptype; /* To display debug info */ struct timeval tv; -socklen_t vslen = 0; struct timeval tvtmp; - -vslen = sizeof(struct timeval); - -fd = fileno(smtp_in); +socklen_t vslen = sizeof(struct timeval); /* Save current socket timeout values */ -get_ok = getsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tvtmp, - &vslen); +get_ok = getsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, CS &tvtmp, &vslen); /* Proxy Protocol host must send header within a short time (default 3 seconds) or it's considered invalid */ tv.tv_sec = PROXY_NEGOTIATION_TIMEOUT_SEC; tv.tv_usec = PROXY_NEGOTIATION_TIMEOUT_USEC; -setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, - sizeof(struct timeval)); +if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, CS &tv, sizeof(tv)) < 0) + return FALSE; do { @@ -718,10 +876,7 @@ do while (ret == -1 && errno == EINTR); if (ret == -1) - { - restore_socket_timeout(fd, get_ok, tvtmp, vslen); - return (errno == EAGAIN) ? 0 : ERRNO_PROXYFAIL; - } + goto proxyfail; if (ret >= 16 && memcmp(&hdr.v2, v2sig, 12) == 0) @@ -761,7 +916,7 @@ if (ret >= 16 && if (!string_is_ip_address(US tmpip,NULL)) { DEBUG(D_receive) debug_printf("Invalid %s source IP\n", iptype); - return ERRNO_PROXYFAIL; + goto proxyfail; } proxy_local_address = sender_host_address; sender_host_address = string_copy(US tmpip); @@ -774,7 +929,7 @@ if (ret >= 16 && if (!string_is_ip_address(US tmpip,NULL)) { DEBUG(D_receive) debug_printf("Invalid %s dest port\n", iptype); - return ERRNO_PROXYFAIL; + goto proxyfail; } proxy_external_address = string_copy(US tmpip); tmpport = ntohs(hdr.v2.addr.ip4.dst_port); @@ -787,7 +942,7 @@ if (ret >= 16 && if (!string_is_ip_address(US tmpip6,NULL)) { DEBUG(D_receive) debug_printf("Invalid %s source IP\n", iptype); - return ERRNO_PROXYFAIL; + goto proxyfail; } proxy_local_address = sender_host_address; sender_host_address = string_copy(US tmpip6); @@ -800,7 +955,7 @@ if (ret >= 16 && if (!string_is_ip_address(US tmpip6,NULL)) { DEBUG(D_receive) debug_printf("Invalid %s dest port\n", iptype); - return ERRNO_PROXYFAIL; + goto proxyfail; } proxy_external_address = string_copy(US tmpip6); tmpport = ntohs(hdr.v2.addr.ip6.dst_port); @@ -941,13 +1096,13 @@ else } proxyfail: -restore_socket_timeout(fd, get_ok, tvtmp, vslen); +restore_socket_timeout(fd, get_ok, &tvtmp, vslen); /* Don't flush any potential buffer contents. Any input should cause a synchronization failure */ return FALSE; done: -restore_socket_timeout(fd, get_ok, tvtmp, vslen); +restore_socket_timeout(fd, get_ok, &tvtmp, vslen); DEBUG(D_receive) debug_printf("Valid %s sender from Proxy Protocol header\n", iptype); return proxy_session; @@ -1182,26 +1337,23 @@ if (smtp_in == NULL || smtp_batched_input) return; receive_swallow_smtp(); smtp_printf("421 %s\r\n", message); -for (;;) +for (;;) switch(smtp_read_command(FALSE)) { - switch(smtp_read_command(FALSE)) - { - case EOF_CMD: - return; + case EOF_CMD: + return; - case QUIT_CMD: - smtp_printf("221 %s closing connection\r\n", smtp_active_hostname); - mac_smtp_fflush(); - return; + case QUIT_CMD: + smtp_printf("221 %s closing connection\r\n", smtp_active_hostname); + mac_smtp_fflush(); + return; - case RSET_CMD: - smtp_printf("250 Reset OK\r\n"); - break; + case RSET_CMD: + smtp_printf("250 Reset OK\r\n"); + break; - default: - smtp_printf("421 %s\r\n", message); - break; - } + default: + smtp_printf("421 %s\r\n", message); + break; } } @@ -1224,8 +1376,8 @@ Returns: a string describing the connection uschar * smtp_get_connection_info(void) { -uschar *hostname = (sender_fullhost == NULL)? - sender_host_address : sender_fullhost; +const uschar * hostname = sender_fullhost + ? sender_fullhost : sender_host_address; if (host_checking) return string_sprintf("SMTP connection from %s", hostname); @@ -1527,7 +1679,6 @@ 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; @@ -1889,10 +2040,10 @@ acl_var_c = NULL; /* Allow for trailing 0 in the command and data buffers. */ -smtp_cmd_buffer = (uschar *)malloc(2*smtp_cmd_buffer_size + 2); -if (smtp_cmd_buffer == NULL) +if (!(smtp_cmd_buffer = US malloc(2*smtp_cmd_buffer_size + 2))) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "malloc() failed for SMTP command buffer"); + smtp_cmd_buffer[0] = 0; smtp_data_buffer = smtp_cmd_buffer + smtp_cmd_buffer_size + 1; @@ -1901,7 +2052,7 @@ command line by a trusted caller. */ if (smtp_batched_input) { - if (received_protocol == NULL) received_protocol = US"local-bsmtp"; + if (!received_protocol) received_protocol = US"local-bsmtp"; } /* For non-batched SMTP input, the protocol setting is forced here. It will be @@ -1914,10 +2065,11 @@ else /* Set up the buffer for inputting using direct read() calls, and arrange to call the local functions instead of the standard C ones. */ -smtp_inbuffer = (uschar *)malloc(in_buffer_size); -if (smtp_inbuffer == NULL) +if (!(smtp_inbuffer = (uschar *)malloc(in_buffer_size))) 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; @@ -2314,8 +2466,7 @@ if (smtp_batched_input) return TRUE; proxy_session = FALSE; proxy_session_failed = FALSE; if (check_proxy_protocol_host()) - { - if (setup_proxy_protocol_host() == FALSE) + if (!setup_proxy_protocol_host()) { proxy_session_failed = TRUE; DEBUG(D_receive) @@ -2327,20 +2478,18 @@ if (check_proxy_protocol_host()) (void)host_name_lookup(); host_build_sender_fullhost(); } - } #endif /* Run the ACL if it exists */ user_msg = NULL; -if (acl_smtp_connect != NULL) +if (acl_smtp_connect) { int rc; - rc = acl_check(ACL_WHERE_CONNECT, NULL, acl_smtp_connect, &user_msg, - &log_msg); - if (rc != OK) + if ((rc = acl_check(ACL_WHERE_CONNECT, NULL, acl_smtp_connect, &user_msg, + &log_msg)) != OK) { - (void)smtp_handle_acl_fail(ACL_WHERE_CONNECT, rc, user_msg, log_msg); + (void) smtp_handle_acl_fail(ACL_WHERE_CONNECT, rc, user_msg, log_msg); return FALSE; } } @@ -2706,16 +2855,16 @@ uschar *lognl; uschar *sender_info = US""; uschar *what = #ifdef WITH_CONTENT_SCAN - (where == ACL_WHERE_MIME)? US"during MIME ACL checks" : + where == ACL_WHERE_MIME ? US"during MIME ACL checks" : #endif - (where == ACL_WHERE_PREDATA)? US"DATA" : - (where == ACL_WHERE_DATA)? US"after DATA" : + where == ACL_WHERE_PREDATA ? US"DATA" : + where == ACL_WHERE_DATA ? US"after DATA" : #ifndef DISABLE_PRDR - (where == ACL_WHERE_PRDR)? US"after DATA PRDR" : + where == ACL_WHERE_PRDR ? US"after DATA PRDR" : #endif - (smtp_cmd_data == NULL)? - string_sprintf("%s in \"connect\" ACL", acl_wherenames[where]) : - string_sprintf("%s %s", acl_wherenames[where], smtp_cmd_data); + smtp_cmd_data ? + string_sprintf("%s %s", acl_wherenames[where], smtp_cmd_data) : + string_sprintf("%s in \"connect\" ACL", acl_wherenames[where]); if (drop) rc = FAIL; @@ -2792,16 +2941,16 @@ if (sender_verified_failed != NULL && /* Sort out text for logging */ -log_msg = (log_msg == NULL)? US"" : string_sprintf(": %s", log_msg); -lognl = Ustrchr(log_msg, '\n'); -if (lognl != NULL) *lognl = 0; +log_msg = log_msg ? string_sprintf(": %s", log_msg) : US""; +if ((lognl = Ustrchr(log_msg, '\n'))) *lognl = 0; /* Send permanent failure response to the command, but the code used isn't always a 5xx one - see comments at the start of this function. If the original rc was FAIL_DROP we drop the connection and yield 2. */ -if (rc == FAIL) smtp_respond(smtp_code, codelen, TRUE, (user_msg == NULL)? - US"Administrative prohibition" : user_msg); +if (rc == FAIL) + smtp_respond(smtp_code, codelen, TRUE, + user_msg ? user_msg : US"Administrative prohibition"); /* Send temporary failure response to the command. Don't give any details, unless acl_temp_details is set. This is TRUE for a callout defer, a "defer" @@ -2812,21 +2961,19 @@ interactions between temp_details and return_error_details. One day it should be re-implemented in a tidier fashion. */ else - { - if (acl_temp_details && user_msg != NULL) + if (acl_temp_details && user_msg) { - if (smtp_return_error_details && - sender_verified_failed != NULL && - sender_verified_failed->message != NULL) - { + if ( smtp_return_error_details + && sender_verified_failed + && sender_verified_failed->message + ) smtp_respond(smtp_code, codelen, FALSE, sender_verified_failed->message); - } + smtp_respond(smtp_code, codelen, TRUE, user_msg); } else smtp_respond(smtp_code, codelen, TRUE, US"Temporary local problem - please try later"); - } /* Log the incident to the logs that are specified by log_reject_target (default main, reject). This can be empty to suppress logging of rejections. If @@ -2841,7 +2988,8 @@ if (log_reject_target != 0) #else uschar * tls = US""; #endif - log_write(0, log_reject_target, "%s%s%s %s%srejected %s%s", + log_write(where == ACL_WHERE_CONNECT ? L_connection_reject : 0, + log_reject_target, "%s%s%s %s%srejected %s%s", LOGGING(dnssec) && sender_host_dnssec ? US" DS" : US"", host_and_ident(TRUE), tls, @@ -2910,12 +3058,11 @@ smtp_exit_function_called = TRUE; /* Call the not-QUIT ACL, if there is one, unless no reason is given. */ -if (acl_smtp_notquit != NULL && reason != NULL) +if (acl_smtp_notquit && reason) { smtp_notquit_reason = reason; - rc = acl_check(ACL_WHERE_NOTQUIT, NULL, acl_smtp_notquit, &user_msg, - &log_msg); - if (rc == ERROR) + if ((rc = acl_check(ACL_WHERE_NOTQUIT, NULL, acl_smtp_notquit, &user_msg, + &log_msg)) == ERROR) log_write(0, LOG_MAIN|LOG_PANIC, "ACL for not-QUIT returned ERROR: %s", log_msg); } @@ -2925,9 +3072,11 @@ responses are all internal, they should always fit in the buffer, but code a warning, just in case. Note that string_vformat() still leaves a complete string, even if it is incomplete. */ -if (code != NULL && defaultrespond != NULL) +if (code && defaultrespond) { - if (user_msg == NULL) + if (user_msg) + smtp_respond(code, 3, TRUE, user_msg); + else { uschar buffer[128]; va_list ap; @@ -2937,8 +3086,6 @@ if (code != NULL && defaultrespond != NULL) smtp_printf("%s %s\r\n", code, buffer); va_end(ap); } - else - smtp_respond(code, 3, TRUE, user_msg); mac_smtp_fflush(); } } @@ -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) + { + 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; @@ -3358,6 +3544,12 @@ while (done <= 0) } #endif +#ifdef TCP_QUICKACK + if (smtp_in) /* Avoid pure-ACKs while in cmd pingpong phase */ + (void) setsockopt(fileno(smtp_in), IPPROTO_TCP, TCP_QUICKACK, + US &off, sizeof(off)); +#endif + switch(smtp_read_command(TRUE)) { /* The AUTH command is not permitted to occur inside a transaction, and may @@ -3775,6 +3967,7 @@ while (done <= 0) { 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; } @@ -4460,20 +4653,22 @@ while (done <= 0) there may be a delay in this, re-check for a synchronization error afterwards, unless pipelining was advertised. */ - if (recipients_discarded) rc = DISCARD; else - { - rc = acl_check(ACL_WHERE_RCPT, recipient, acl_smtp_rcpt, &user_msg, - &log_msg); - if (rc == OK && !pipelining_advertised && !check_sync()) + if (recipients_discarded) + rc = DISCARD; + else + if ( (rc = acl_check(ACL_WHERE_RCPT, recipient, acl_smtp_rcpt, &user_msg, + &log_msg)) == OK + && !pipelining_advertised && !check_sync()) goto SYNC_FAILURE; - } /* The ACL was happy */ if (rc == OK) { - if (user_msg == NULL) smtp_printf("250 Accepted\r\n"); - else smtp_user_msg(US"250", user_msg); + if (user_msg) + smtp_user_msg(US"250", user_msg); + else + smtp_printf("250 Accepted\r\n"); receive_add_recipient(recipient, -1); /* Set the dsn flags in the recipients_list */ @@ -4489,8 +4684,10 @@ while (done <= 0) else if (rc == DISCARD) { - if (user_msg == NULL) smtp_printf("250 Accepted\r\n"); - else smtp_user_msg(US"250", user_msg); + if (user_msg) + smtp_user_msg(US"250", user_msg); + else + smtp_printf("250 Accepted\r\n"); rcpt_fail_count++; discarded = TRUE; log_write(0, LOG_MAIN|LOG_REJECT, "%s F=<%s> RCPT %s: " @@ -4546,12 +4743,18 @@ while (done <= 0) if (sscanf(CS smtp_cmd_data, "%u %n", &chunking_datasize, &n) < 1) { - done = synprot_error(L_smtp_protocol_error, 503, NULL, + 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); @@ -4583,6 +4786,9 @@ while (done <= 0) smtp_connection_had[smtp_ch_index-1] == SCH_DATA ? US"valid RCPT command must precede DATA" : US"valid RCPT command must precede BDAT"); + + if (chunking_state > CHUNKING_OFFERED) + bdat_flush_data(); break; } @@ -4594,45 +4800,48 @@ 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; + if (chunking_state > CHUNKING_OFFERED) + rc = OK; /* No predata ACL or go-ahead output for BDAT */ 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 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 (rc == OK) - { - 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 (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. */ +#ifdef TCP_QUICKACK + if (smtp_in) /* all ACKs needed to ramp window up for bulk data */ + (void) setsockopt(fileno(smtp_in), IPPROTO_TCP, TCP_QUICKACK, + US &on, sizeof(on)); +#endif + 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; @@ -4818,45 +5027,39 @@ while (done <= 0) set, but we must still reject all incoming commands. */ DEBUG(D_tls) debug_printf("TLS failed to start\n"); - while (done <= 0) + while (done <= 0) switch(smtp_read_command(FALSE)) { - switch(smtp_read_command(FALSE)) - { - case EOF_CMD: - log_write(L_smtp_connection, LOG_MAIN, "%s closed by EOF", - smtp_get_connection_info()); - smtp_notquit_exit(US"tls-failed", NULL, NULL); - done = 2; - break; - - /* It is perhaps arguable as to which exit ACL should be called here, - but as it is probably a situation that almost never arises, it - probably doesn't matter. We choose to call the real QUIT ACL, which in - some sense is perhaps "right". */ + case EOF_CMD: + log_write(L_smtp_connection, LOG_MAIN, "%s closed by EOF", + smtp_get_connection_info()); + smtp_notquit_exit(US"tls-failed", NULL, NULL); + done = 2; + break; - case QUIT_CMD: - user_msg = NULL; - 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); - log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT", - smtp_get_connection_info()); - done = 2; - break; + /* It is perhaps arguable as to which exit ACL should be called here, + but as it is probably a situation that almost never arises, it + probably doesn't matter. We choose to call the real QUIT ACL, which in + some sense is perhaps "right". */ + + case QUIT_CMD: + user_msg = NULL; + if ( acl_smtp_quit + && ((rc = acl_check(ACL_WHERE_QUIT, NULL, acl_smtp_quit, &user_msg, + &log_msg)) == ERROR)) + log_write(0, LOG_MAIN|LOG_PANIC, "ACL for QUIT returned ERROR: %s", + log_msg); + if (user_msg) + smtp_respond(US"221", 3, TRUE, user_msg); + else + smtp_printf("221 %s closing connection\r\n", smtp_active_hostname); + log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT", + smtp_get_connection_info()); + done = 2; + break; - default: - smtp_printf("554 Security failure\r\n"); - break; - } + default: + smtp_printf("554 Security failure\r\n"); + break; } tls_close(TRUE, TRUE); break; @@ -4868,37 +5071,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; @@ -4925,7 +5106,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");