X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/31c546c4d0c3baf1b1e0ab292b4d096cffe64c34..4687a69c269ee3f2a7f0625e0147a503fd9d3d0b:/src/src/smtp_in.c diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c index 5d8ffd3d0..79176687d 100644 --- a/src/src/smtp_in.c +++ b/src/src/smtp_in.c @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) The Exim Maintainers 2020 - 2022 */ +/* Copyright (c) The Exim Maintainers 2020 - 2023 */ /* Copyright (c) University of Cambridge 1995 - 2018 */ /* See the file NOTICE for conditions of use and distribution. */ /* SPDX-License-Identifier: GPL-2.0-or-later */ @@ -75,6 +75,9 @@ 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 */ +#ifdef EXPERIMENTAL_XCLIENT + XCLIENT_CMD, /* per xlexkiro implementation */ +#endif /* This is a dummy to identify the non-sync commands when pipelining */ @@ -189,19 +192,34 @@ count of non-mail commands and possibly provoke an error. tls_auth is a pseudo-command, never expected in input. It is activated on TLS startup and looks for a tls authenticator. */ +enum { + CL_RSET = 0, + CL_HELO, + CL_EHLO, + CL_AUTH, +#ifndef DISABLE_TLS + CL_STLS, + CL_TLAU, +#endif +#ifdef EXPERIMENTAL_XCLIENT + CL_XCLI, +#endif +}; + static smtp_cmd_list cmd_list[] = { - /* name len cmd has_arg is_mail_cmd */ + /* name len cmd has_arg is_mail_cmd */ - { "rset", sizeof("rset")-1, RSET_CMD, FALSE, FALSE }, /* First */ - { "helo", sizeof("helo")-1, HELO_CMD, TRUE, FALSE }, - { "ehlo", sizeof("ehlo")-1, EHLO_CMD, TRUE, FALSE }, - { "auth", sizeof("auth")-1, AUTH_CMD, TRUE, TRUE }, + [CL_RSET] = { "rset", sizeof("rset")-1, RSET_CMD, FALSE, FALSE }, /* First */ + [CL_HELO] = { "helo", sizeof("helo")-1, HELO_CMD, TRUE, FALSE }, + [CL_EHLO] = { "ehlo", sizeof("ehlo")-1, EHLO_CMD, TRUE, FALSE }, + [CL_AUTH] = { "auth", sizeof("auth")-1, AUTH_CMD, TRUE, TRUE }, #ifndef DISABLE_TLS - { "starttls", sizeof("starttls")-1, STARTTLS_CMD, FALSE, FALSE }, - { "tls_auth", 0, TLS_AUTH_CMD, FALSE, FALSE }, + [CL_STLS] = { "starttls", sizeof("starttls")-1, STARTTLS_CMD, FALSE, FALSE }, + [CL_TLAU] = { "tls_auth", 0, TLS_AUTH_CMD, FALSE, FALSE }, +#endif +#ifdef EXPERIMENTAL_XCLIENT + [CL_XCLI] = { "xclient", sizeof("xclient")-1, XCLIENT_CMD, TRUE, FALSE }, #endif - -/* If you change anything above here, also fix the definitions below. */ { "mail from:", sizeof("mail from:")-1, MAIL_CMD, TRUE, TRUE }, { "rcpt to:", sizeof("rcpt to:")-1, RCPT_CMD, TRUE, TRUE }, @@ -215,24 +233,30 @@ static smtp_cmd_list cmd_list[] = { { "help", sizeof("help")-1, HELP_CMD, TRUE, FALSE } }; -static smtp_cmd_list *cmd_list_end = - cmd_list + sizeof(cmd_list)/sizeof(smtp_cmd_list); - -#define CMD_LIST_RSET 0 -#define CMD_LIST_HELO 1 -#define CMD_LIST_EHLO 2 -#define CMD_LIST_AUTH 3 -#define CMD_LIST_STARTTLS 4 -#define CMD_LIST_TLS_AUTH 5 - -/* This list of names is used for performing the smtp_no_mail logging action. -It must be kept in step with the SCH_xxx enumerations. */ +/* This list of names is used for performing the smtp_no_mail logging action. */ uschar * smtp_names[] = { - 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" }; + [SCH_NONE] = US"NONE", + [SCH_AUTH] = US"AUTH", + [SCH_DATA] = US"DATA", + [SCH_BDAT] = US"BDAT", + [SCH_EHLO] = US"EHLO", + [SCH_ETRN] = US"ETRN", + [SCH_EXPN] = US"EXPN", + [SCH_HELO] = US"HELO", + [SCH_HELP] = US"HELP", + [SCH_MAIL] = US"MAIL", + [SCH_NOOP] = US"NOOP", + [SCH_QUIT] = US"QUIT", + [SCH_RCPT] = US"RCPT", + [SCH_RSET] = US"RSET", + [SCH_STARTTLS] = US"STARTTLS", + [SCH_VRFY] = US"VRFY", +#ifdef EXPERIMENTAL_XCLIENT + [SCH_XCLIENT] = US"XCLIENT", +#endif + }; static uschar *protocols_local[] = { US"local-smtp", /* HELO */ @@ -333,7 +357,7 @@ Returns: nothing */ static void -incomplete_transaction_log(uschar *what) +incomplete_transaction_log(uschar * what) { if (!sender_address /* No transaction in progress */ || !LOGGING(smtp_incomplete_transaction)) @@ -355,13 +379,21 @@ log_write(L_smtp_incomplete_transaction, LOG_MAIN|LOG_SENDER|LOG_RECIPIENTS, +static void +log_close_event(const uschar * reason) +{ +log_write(L_smtp_connection, LOG_MAIN, "%s D=%s closed %s", + smtp_get_connection_info(), string_timesince(&smtp_connection_start), reason); +} + void smtp_command_timeout_exit(void) { log_write(L_lost_incoming_connection, - LOG_MAIN, "SMTP command timeout on%s connection from %s", - tls_in.active.sock >= 0 ? " TLS" : "", host_and_ident(FALSE)); + LOG_MAIN, "SMTP command timeout on%s connection from %s D=%s", + tls_in.active.sock >= 0 ? " TLS" : "", host_and_ident(FALSE), + string_timesince(&smtp_connection_start)); if (smtp_batched_input) moan_smtp_batch(NULL, "421 SMTP command timeout"); /* Does not return */ smtp_notquit_exit(US"command-timeout", US"421", @@ -373,7 +405,7 @@ exim_exit(EXIT_FAILURE); void smtp_command_sigterm_exit(void) { -log_write(0, LOG_MAIN, "%s closed after SIGTERM", smtp_get_connection_info()); +log_close_event(US"after SIGTERM"); if (smtp_batched_input) moan_smtp_batch(NULL, "421 SIGTERM received"); /* Does not return */ smtp_notquit_exit(US"signal-exit", US"421", @@ -384,9 +416,10 @@ exim_exit(EXIT_FAILURE); void smtp_data_timeout_exit(void) { -log_write(L_lost_incoming_connection, - LOG_MAIN, "SMTP data timeout (message abandoned) on connection from %s F=<%s>", - sender_fullhost ? sender_fullhost : US"local process", sender_address); +log_write(L_lost_incoming_connection, LOG_MAIN, + "SMTP data timeout (message abandoned) on connection from %s F=<%s> D=%s", + sender_fullhost ? sender_fullhost : US"local process", sender_address, + string_timesince(&smtp_connection_start)); receive_bomb_out(US"data-timeout", US"SMTP incoming data timeout"); /* Does not return */ } @@ -394,8 +427,7 @@ receive_bomb_out(US"data-timeout", US"SMTP incoming data timeout"); void smtp_data_sigint_exit(void) { -log_write(0, LOG_MAIN, "%s closed after %s", - smtp_get_connection_info(), had_data_sigint == SIGTERM ? "SIGTERM":"SIGINT"); +log_close_event(had_data_sigint == SIGTERM ? US"SIGTERM":US"SIGINT"); receive_bomb_out(US"signal-exit", US"Service not available - SIGTERM or SIGINT received"); /* Does not return */ @@ -732,7 +764,7 @@ for(;;) return EOD; } - smtp_printf("250 %u byte chunk received\r\n", FALSE, chunking_datasize); + smtp_printf("250 %u byte chunk received\r\n", SP_NO_MORE, chunking_datasize); chunking_state = CHUNKING_OFFERED; DEBUG(D_receive) debug_printf("chunking state %d\n", (int)chunking_state); @@ -770,7 +802,7 @@ next_cmd: case NOOP_CMD: HAD(SCH_NOOP); - smtp_printf("250 OK\r\n", FALSE); + smtp_printf("250 OK\r\n", SP_NO_MORE); goto next_cmd; case BDAT_CMD: @@ -1093,518 +1125,6 @@ had_command_sigterm = sig; -#ifdef SUPPORT_PROXY -/************************************************* -* Check if host is required proxy host * -*************************************************/ -/* The function determines if inbound host will be a regular smtp host -or if it is configured that it must use Proxy Protocol. A local -connection cannot. - -Arguments: none -Returns: bool -*/ - -static BOOL -check_proxy_protocol_host() -{ -int rc; - -if ( sender_host_address - && (rc = verify_check_this_host(CUSS &hosts_proxy, NULL, NULL, - sender_host_address, NULL)) == OK) - { - DEBUG(D_receive) - debug_printf("Detected proxy protocol configured host\n"); - proxy_session = TRUE; - } -return proxy_session; -} - - -/************************************************* -* Read data until newline or end of buffer * -*************************************************/ -/* While SMTP is server-speaks-first, TLS is client-speaks-first, so we can't -read an entire buffer and assume there will be nothing past a proxy protocol -header. Our approach normally is to use stdio, but again that relies upon -"STARTTLS\r\n" and a server response before the client starts TLS handshake, or -reading _nothing_ before client TLS handshake. So we don't want to use the -usual buffering reads which may read enough to block TLS starting. - -So unfortunately we're down to "read one byte at a time, with a syscall each, -and expect a little overhead", for all proxy-opened connections which are v1, -just to handle the TLS-on-connect case. Since SSL functions wrap the -underlying fd, we can't assume that we can feed them any already-read content. - -We need to know where to read to, the max capacity, and we'll read until we -get a CR and one more character. Let the caller scream if it's CR+!LF. - -Return the amount read. -*/ - -static int -swallow_until_crlf(int fd, uschar *base, int already, int capacity) -{ -uschar *to = base + already; -uschar *cr; -int have = 0; -int ret; -int last = 0; - -/* For "PROXY UNKNOWN\r\n" we, at time of writing, expect to have read -up through the \r; for the _normal_ case, we haven't yet seen the \r. */ - -cr = memchr(base, '\r', already); -if (cr != NULL) - { - if ((cr - base) < already - 1) - { - /* \r and presumed \n already within what we have; probably not - actually proxy protocol, but abort cleanly. */ - return 0; - } - /* \r is last character read, just need one more. */ - last = 1; - } - -while (capacity > 0) - { - do { ret = read(fd, to, 1); } while (ret == -1 && errno == EINTR && !had_command_timeout); - if (ret == -1) - return -1; - have++; - if (last) - return have; - if (*to == '\r') - last = 1; - capacity--; - to++; - } - -/* reached end without having room for a final newline, abort */ -errno = EOVERFLOW; -return -1; -} - - -static void -proxy_debug(uschar * buf, unsigned start, unsigned end) -{ -debug_printf("PROXY<<"); -while (start < end) debug_printf(" %02x", buf[start++]); -debug_printf("\n"); -} - - -/************************************************* -* Setup host for proxy protocol * -*************************************************/ -/* The function configures the connection based on a header from the -inbound host to use Proxy Protocol. The specification is very exact -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: Boolean success -*/ - -static void -setup_proxy_protocol_host() -{ -union { - struct { - uschar line[108]; - } v1; - struct { - uschar sig[12]; - uint8_t ver_cmd; - uint8_t fam; - uint16_t len; - union { - struct { /* TCP/UDP over IPv4, len = 12 */ - uint32_t src_addr; - uint32_t dst_addr; - uint16_t src_port; - uint16_t dst_port; - } ip4; - struct { /* TCP/UDP over IPv6, len = 36 */ - uint8_t src_addr[16]; - uint8_t dst_addr[16]; - uint16_t src_port; - uint16_t dst_port; - } ip6; - struct { /* AF_UNIX sockets, len = 216 */ - uschar src_addr[108]; - uschar dst_addr[108]; - } unx; - } addr; - } v2; -} hdr; - -/* Temp variables used in PPv2 address:port parsing */ -uint16_t tmpport; -char tmpip[INET_ADDRSTRLEN]; -struct sockaddr_in tmpaddr; -char tmpip6[INET6_ADDRSTRLEN]; -struct sockaddr_in6 tmpaddr6; - -/* We can't read "all data until end" because while SMTP is -server-speaks-first, the TLS handshake is client-speaks-first, so for -TLS-on-connect ports the proxy protocol header will usually be immediately -followed by a TLS handshake, and with N TLS libraries, we can't reliably -reinject data for reading by those. So instead we first read "enough to be -safely read within the header, and figure out how much more to read". -For v1 we will later read to the end-of-line, for v2 we will read based upon -the stated length. - -The v2 sig is 12 octets, and another 4 gets us the length, so we know how much -data is needed total. For v1, where the line looks like: -PROXY TCPn L3src L3dest SrcPort DestPort \r\n - -However, for v1 there's also `PROXY UNKNOWN\r\n` which is only 15 octets. -We seem to support that. So, if we read 14 octets then we can tell if we're -v2 or v1. If we're v1, we can continue reading as normal. - -If we're v2, we can't slurp up the entire header. We need the length in the -15th & 16th octets, then to read everything after that. - -So to safely handle v1 and v2, with client-sent-first supported correctly, -we have to do a minimum of 3 read calls, not 1. Eww. -*/ - -# define PROXY_INITIAL_READ 14 -# define PROXY_V2_HEADER_SIZE 16 -# if PROXY_INITIAL_READ > PROXY_V2_HEADER_SIZE -# error Code bug in sizes of data to read for proxy usage -# endif - -int get_ok = 0; -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 */ -socklen_t vslen = sizeof(struct timeval); -BOOL yield = FALSE; - -os_non_restarting_signal(SIGALRM, command_timeout_handler); -ALARM(proxy_protocol_timeout); - -do - { - /* The inbound host was declared to be a Proxy Protocol host, so - don't do a PEEK into the data, actually slurp up enough to be - "safe". Can't take it all because TLS-on-connect clients follow - immediately with TLS handshake. */ - ret = read(fd, &hdr, PROXY_INITIAL_READ); - } while (ret == -1 && errno == EINTR && !had_command_timeout); - -if (ret == -1) - goto proxyfail; -DEBUG(D_receive) proxy_debug(US &hdr, 0, ret); - -/* For v2, handle reading the length, and then the rest. */ -if ((ret == PROXY_INITIAL_READ) && (memcmp(&hdr.v2, v2sig, sizeof(v2sig)) == 0)) - { - int retmore; - uint8_t ver; - - DEBUG(D_receive) debug_printf("v2\n"); - - /* First get the length fields. */ - do - { - retmore = read(fd, (uschar*)&hdr + ret, PROXY_V2_HEADER_SIZE - PROXY_INITIAL_READ); - } while (retmore == -1 && errno == EINTR && !had_command_timeout); - if (retmore == -1) - goto proxyfail; - DEBUG(D_receive) proxy_debug(US &hdr, ret, ret + retmore); - - ret += retmore; - - ver = (hdr.v2.ver_cmd & 0xf0) >> 4; - - /* May 2014: haproxy combined the version and command into one byte to - allow two full bytes for the length field in order to proxy SSL - connections. SSL Proxy is not supported in this version of Exim, but - must still separate values here. */ - - if (ver != 0x02) - { - DEBUG(D_receive) debug_printf("Invalid Proxy Protocol version: %d\n", ver); - goto proxyfail; - } - - /* The v2 header will always be 16 bytes per the spec. */ - size = 16 + ntohs(hdr.v2.len); - DEBUG(D_receive) debug_printf("Detected PROXYv2 header, size %d (limit %d)\n", - size, (int)sizeof(hdr)); - - /* We should now have 16 octets (PROXY_V2_HEADER_SIZE), and we know the total - amount that we need. Double-check that the size is not unreasonable, then - get the rest. */ - if (size > sizeof(hdr)) - { - DEBUG(D_receive) debug_printf("PROXYv2 header size unreasonably large; security attack?\n"); - goto proxyfail; - } - - do - { - do - { - retmore = read(fd, (uschar*)&hdr + ret, size-ret); - } while (retmore == -1 && errno == EINTR && !had_command_timeout); - if (retmore == -1) - goto proxyfail; - DEBUG(D_receive) proxy_debug(US &hdr, ret, ret + retmore); - ret += retmore; - DEBUG(D_receive) debug_printf("PROXYv2: have %d/%d required octets\n", ret, size); - } while (ret < size); - - } /* end scope for getting rest of data for v2 */ - -/* At this point: if PROXYv2, we've read the exact size required for all data; -if PROXYv1 then we've read "less than required for any valid line" and should -read the rest". */ - -if (ret >= 16 && memcmp(&hdr.v2, v2sig, 12) == 0) - { - uint8_t cmd = (hdr.v2.ver_cmd & 0x0f); - - switch (cmd) - { - case 0x01: /* PROXY command */ - switch (hdr.v2.fam) - { - case 0x11: /* TCPv4 address type */ - iptype = US"IPv4"; - tmpaddr.sin_addr.s_addr = hdr.v2.addr.ip4.src_addr; - inet_ntop(AF_INET, &tmpaddr.sin_addr, CS &tmpip, sizeof(tmpip)); - if (!string_is_ip_address(US tmpip, NULL)) - { - DEBUG(D_receive) debug_printf("Invalid %s source IP\n", iptype); - goto proxyfail; - } - proxy_local_address = sender_host_address; - sender_host_address = string_copy(US tmpip); - tmpport = ntohs(hdr.v2.addr.ip4.src_port); - proxy_local_port = sender_host_port; - sender_host_port = tmpport; - /* Save dest ip/port */ - tmpaddr.sin_addr.s_addr = hdr.v2.addr.ip4.dst_addr; - inet_ntop(AF_INET, &tmpaddr.sin_addr, CS &tmpip, sizeof(tmpip)); - if (!string_is_ip_address(US tmpip, NULL)) - { - DEBUG(D_receive) debug_printf("Invalid %s dest port\n", iptype); - goto proxyfail; - } - proxy_external_address = string_copy(US tmpip); - tmpport = ntohs(hdr.v2.addr.ip4.dst_port); - proxy_external_port = tmpport; - goto done; - case 0x21: /* TCPv6 address type */ - iptype = US"IPv6"; - memmove(tmpaddr6.sin6_addr.s6_addr, hdr.v2.addr.ip6.src_addr, 16); - inet_ntop(AF_INET6, &tmpaddr6.sin6_addr, CS &tmpip6, sizeof(tmpip6)); - if (!string_is_ip_address(US tmpip6, NULL)) - { - DEBUG(D_receive) debug_printf("Invalid %s source IP\n", iptype); - goto proxyfail; - } - proxy_local_address = sender_host_address; - sender_host_address = string_copy(US tmpip6); - tmpport = ntohs(hdr.v2.addr.ip6.src_port); - proxy_local_port = sender_host_port; - sender_host_port = tmpport; - /* Save dest ip/port */ - memmove(tmpaddr6.sin6_addr.s6_addr, hdr.v2.addr.ip6.dst_addr, 16); - inet_ntop(AF_INET6, &tmpaddr6.sin6_addr, CS &tmpip6, sizeof(tmpip6)); - if (!string_is_ip_address(US tmpip6, NULL)) - { - DEBUG(D_receive) debug_printf("Invalid %s dest port\n", iptype); - goto proxyfail; - } - proxy_external_address = string_copy(US tmpip6); - tmpport = ntohs(hdr.v2.addr.ip6.dst_port); - proxy_external_port = tmpport; - goto done; - default: - DEBUG(D_receive) - debug_printf("Unsupported PROXYv2 connection type: 0x%02x\n", - hdr.v2.fam); - goto proxyfail; - } - /* Unsupported protocol, keep local connection address */ - break; - case 0x00: /* LOCAL command */ - /* Keep local connection address for LOCAL */ - iptype = US"local"; - break; - default: - DEBUG(D_receive) - debug_printf("Unsupported PROXYv2 command: 0x%x\n", cmd); - goto proxyfail; - } - } -else if (ret >= 8 && memcmp(hdr.v1.line, "PROXY", 5) == 0) - { - uschar *p; - uschar *end; - uschar *sp; /* Utility variables follow */ - int tmp_port; - int r2; - char *endc; - - /* get the rest of the line */ - r2 = swallow_until_crlf(fd, (uschar*)&hdr, ret, sizeof(hdr)-ret); - if (r2 == -1) - goto proxyfail; - ret += r2; - - p = string_copy(hdr.v1.line); - end = memchr(p, '\r', ret - 1); - - if (!end || (end == (uschar*)&hdr + ret) || end[1] != '\n') - { - DEBUG(D_receive) debug_printf("Partial or invalid PROXY header\n"); - goto proxyfail; - } - *end = '\0'; /* Terminate the string */ - size = end + 2 - p; /* Skip header + CRLF */ - DEBUG(D_receive) debug_printf("Detected PROXYv1 header\n"); - DEBUG(D_receive) debug_printf("Bytes read not within PROXY header: %d\n", ret - size); - /* Step through the string looking for the required fields. Ensure - strict adherence to required formatting, exit for any error. */ - p += 5; - if (!isspace(*(p++))) - { - DEBUG(D_receive) debug_printf("Missing space after PROXY command\n"); - goto proxyfail; - } - if (!Ustrncmp(p, CCS"TCP4", 4)) - iptype = US"IPv4"; - else if (!Ustrncmp(p,CCS"TCP6", 4)) - iptype = US"IPv6"; - else if (!Ustrncmp(p,CCS"UNKNOWN", 7)) - { - iptype = US"Unknown"; - goto done; - } - else - { - DEBUG(D_receive) debug_printf("Invalid TCP type\n"); - goto proxyfail; - } - - p += Ustrlen(iptype); - if (!isspace(*(p++))) - { - DEBUG(D_receive) debug_printf("Missing space after TCP4/6 command\n"); - goto proxyfail; - } - /* Find the end of the arg */ - if ((sp = Ustrchr(p, ' ')) == NULL) - { - DEBUG(D_receive) - debug_printf("Did not find proxied src %s\n", iptype); - goto proxyfail; - } - *sp = '\0'; - if(!string_is_ip_address(p, NULL)) - { - DEBUG(D_receive) - debug_printf("Proxied src arg is not an %s address\n", iptype); - goto proxyfail; - } - proxy_local_address = sender_host_address; - sender_host_address = p; - p = sp + 1; - if ((sp = Ustrchr(p, ' ')) == NULL) - { - DEBUG(D_receive) - debug_printf("Did not find proxy dest %s\n", iptype); - goto proxyfail; - } - *sp = '\0'; - if(!string_is_ip_address(p, NULL)) - { - DEBUG(D_receive) - debug_printf("Proxy dest arg is not an %s address\n", iptype); - goto proxyfail; - } - proxy_external_address = p; - p = sp + 1; - if ((sp = Ustrchr(p, ' ')) == NULL) - { - DEBUG(D_receive) debug_printf("Did not find proxied src port\n"); - goto proxyfail; - } - *sp = '\0'; - tmp_port = strtol(CCS p, &endc, 10); - if (*endc || tmp_port == 0) - { - DEBUG(D_receive) - debug_printf("Proxied src port '%s' not an integer\n", p); - goto proxyfail; - } - proxy_local_port = sender_host_port; - sender_host_port = tmp_port; - p = sp + 1; - if ((sp = Ustrchr(p, '\0')) == NULL) - { - DEBUG(D_receive) debug_printf("Did not find proxy dest port\n"); - goto proxyfail; - } - tmp_port = strtol(CCS p, &endc, 10); - if (*endc || tmp_port == 0) - { - DEBUG(D_receive) - debug_printf("Proxy dest port '%s' not an integer\n", p); - goto proxyfail; - } - proxy_external_port = tmp_port; - /* Already checked for /r /n above. Good V1 header received. */ - } -else - { - /* Wrong protocol */ - DEBUG(D_receive) debug_printf("Invalid proxy protocol version negotiation\n"); - (void) swallow_until_crlf(fd, (uschar*)&hdr, ret, sizeof(hdr)-ret); - goto proxyfail; - } - -done: - DEBUG(D_receive) - debug_printf("Valid %s sender from Proxy Protocol header\n", iptype); - yield = proxy_session; - -/* Don't flush any potential buffer contents. Any input on proxyfail -should cause a synchronization failure */ - -proxyfail: - DEBUG(D_receive) if (had_command_timeout) - debug_printf("Timeout while reading proxy header\n"); - -bad: - if (yield) - { - sender_host_name = NULL; - (void) host_name_lookup(); - host_build_sender_fullhost(); - } - else - { - f.proxy_session_failed = TRUE; - DEBUG(D_receive) - debug_printf("Failure to extract proxied host, only QUIT allowed\n"); - } - -ALARM(0); -return; -} -#endif /*SUPPORT_PROXY*/ - /************************************************* * Read one command line * *************************************************/ @@ -1677,7 +1197,7 @@ if (hadnull) return BADCHAR_CMD; to the start of the actual data characters. Check for SMTP synchronization if required. */ -for (smtp_cmd_list * p = cmd_list; p < cmd_list_end; p++) +for (smtp_cmd_list * p = cmd_list; p < cmd_list + nelem(cmd_list); p++) { #ifdef SUPPORT_PROXY /* Only allow QUIT command if Proxy Protocol parsing failed */ @@ -1707,7 +1227,7 @@ for (smtp_cmd_list * p = cmd_list; p < cmd_list_end; p++) follow the sender address. */ smtp_cmd_argument = smtp_cmd_buffer + p->len; - while (isspace(*smtp_cmd_argument)) smtp_cmd_argument++; + Uskip_whitespace(&smtp_cmd_argument); Ustrcpy(smtp_data_buffer, smtp_cmd_argument); smtp_cmd_data = smtp_data_buffer; @@ -1755,6 +1275,7 @@ return OTHER_CMD; + /************************************************* * Forced closedown of call * *************************************************/ @@ -1777,7 +1298,7 @@ smtp_closedown(uschar * message) { if (!smtp_in || smtp_batched_input) return; receive_swallow_smtp(); -smtp_printf("421 %s\r\n", FALSE, message); +smtp_printf("421 %s\r\n", SP_NO_MORE, message); for (;;) switch(smtp_read_command(FALSE, GETC_BUFFER_UNLIMITED)) { @@ -1786,16 +1307,16 @@ for (;;) switch(smtp_read_command(FALSE, GETC_BUFFER_UNLIMITED)) case QUIT_CMD: f.smtp_in_quit = TRUE; - smtp_printf("221 %s closing connection\r\n", FALSE, smtp_active_hostname); + smtp_printf("221 %s closing connection\r\n", SP_NO_MORE, smtp_active_hostname); mac_smtp_fflush(); return; case RSET_CMD: - smtp_printf("250 Reset OK\r\n", FALSE); + smtp_printf("250 Reset OK\r\n", SP_NO_MORE); break; default: - smtp_printf("421 %s\r\n", FALSE, message); + smtp_printf("421 %s\r\n", SP_NO_MORE, message); break; } } @@ -1821,21 +1342,29 @@ smtp_get_connection_info(void) { const uschar * hostname = sender_fullhost ? sender_fullhost : sender_host_address; +gstring * g = string_catn(NULL, US"SMTP connection", 15); + +if (LOGGING(connection_id)) + g = string_fmt_append(g, " Ci=%lu", connection_id); +g = string_catn(g, US" from ", 6); if (host_checking) - return string_sprintf("SMTP connection from %s", hostname); + g = string_cat(g, hostname); -if (f.sender_host_unknown || f.sender_host_notsocket) - return string_sprintf("SMTP connection from %s", sender_ident); +else if (f.sender_host_unknown || f.sender_host_notsocket) + g = string_cat(g, sender_ident ? sender_ident : US"NULL"); -if (f.is_inetd) - return string_sprintf("SMTP connection from %s (via inetd)", hostname); +else if (f.is_inetd) + g = string_append(g, 2, hostname, US" (via inetd)"); -if (LOGGING(incoming_interface) && interface_address) - return string_sprintf("SMTP connection from %s I=[%s]:%d", hostname, - interface_address, interface_port); +else if (LOGGING(incoming_interface) && interface_address) + g = string_fmt_append(g, "%s I=[%s]:%d", hostname, interface_address, interface_port); -return string_sprintf("SMTP connection from %s", hostname); +else + g = string_cat(g, hostname); + +gstring_release_unused(g); +return string_from_gstring(g); } @@ -2269,7 +1798,6 @@ while (done <= 0) bsmtp_transaction_linecount = receive_linecount; break; - /* The MAIL FROM command requires an address as an operand. All we do here is to parse it for syntactic correctness. The form "<>" is a special case which converts into an empty string. The start/end @@ -2348,10 +1876,11 @@ while (done <= 0) /* Check maximum number allowed */ - if (recipients_max > 0 && recipients_count + 1 > recipients_max) + if ( recipients_max_expanded > 0 + && recipients_count + 1 > recipients_max_expanded) /* The function moan_smtp_batch() does not return. */ moan_smtp_batch(smtp_cmd_buffer, "%s too many recipients", - recipients_max_reject? "552": "452"); + recipients_max_reject ? "552": "452"); /* Apply SMTP rewrite, then extract address. Don't allow "<>" as a recipient address */ @@ -2523,16 +2052,19 @@ else DEBUG(D_receive) static void log_connect_tls_drop(const uschar * what, const uschar * log_msg) { -gstring * g = s_tlslog(NULL); -uschar * tls = string_from_gstring(g); - -log_write(L_connection_reject, - log_reject_target, "%s%s%s dropped by %s%s%s", - LOGGING(dnssec) && sender_host_dnssec ? US" DS" : US"", - host_and_ident(TRUE), - tls ? tls : US"", - what, - log_msg ? US": " : US"", log_msg); +if (log_reject_target) + { + gstring * g = s_tlslog(NULL); + uschar * tls = string_from_gstring(g); + + log_write(L_connection_reject, + log_reject_target, "%s%s%s dropped by %s%s%s", + LOGGING(dnssec) && sender_host_dnssec ? US" DS" : US"", + host_and_ident(TRUE), + tls ? tls : US"", + what, + log_msg ? US": " : US"", log_msg); + } } @@ -2640,6 +2172,7 @@ lwr_receive_ungetc = NULL; /* Set up the message size limit; this may be host-specific */ +GET_OPTION("message_size_limit"); thismessage_size_limit = expand_string_integer(message_size_limit, TRUE); if (expand_string_message) { @@ -2738,7 +2271,7 @@ if (!f.sender_host_unknown) { log_write(0, LOG_MAIN, "getsockopt() failed from %s: %s", host_and_ident(FALSE), strerror(errno)); - smtp_printf("451 SMTP service not available\r\n", FALSE); + smtp_printf("451 SMTP service not available\r\n", SP_NO_MORE); return FALSE; } } @@ -2842,7 +2375,7 @@ if (!f.sender_host_unknown) log_write(0, LOG_MAIN|LOG_REJECT, "connection from %s refused (IP options)", host_and_ident(FALSE)); - smtp_printf("554 SMTP service not available\r\n", FALSE); + smtp_printf("554 SMTP service not available\r\n", SP_NO_MORE); return FALSE; } @@ -2897,7 +2430,7 @@ if (!f.sender_host_unknown) #ifndef DISABLE_TLS if (!tls_in.on_connect) #endif - smtp_printf("554 SMTP service not available\r\n", FALSE); + smtp_printf("554 SMTP service not available\r\n", SP_NO_MORE); return FALSE; } @@ -2928,7 +2461,7 @@ if (!f.sender_host_unknown) log_write(L_connection_reject, LOG_MAIN|LOG_REJECT, "refused connection from %s " "(tcp wrappers)", host_and_ident(FALSE)); - smtp_printf("554 SMTP service not available\r\n", FALSE); + smtp_printf("554 SMTP service not available\r\n", SP_NO_MORE); } else { @@ -2938,7 +2471,7 @@ if (!f.sender_host_unknown) log_write(L_connection_reject, LOG_MAIN|LOG_REJECT, "temporarily refused connection from %s " "(tcp wrappers errno=%d)", host_and_ident(FALSE), save_errno); - smtp_printf("451 Temporary local problem - please try later\r\n", FALSE); + smtp_printf("451 Temporary local problem - please try later\r\n", SP_NO_MORE); } return FALSE; } @@ -2958,7 +2491,7 @@ if (!f.sender_host_unknown) host_and_ident(FALSE), smtp_accept_count - 1, smtp_accept_max, smtp_accept_reserve, (rc == DEFER)? " (lookup deferred)" : ""); smtp_printf("421 %s: Too many concurrent SMTP connections; " - "please try again later\r\n", FALSE, smtp_active_hostname); + "please try again later\r\n", SP_NO_MORE, smtp_active_hostname); return FALSE; } reserved_host = TRUE; @@ -2979,7 +2512,7 @@ if (!f.sender_host_unknown) LOG_MAIN, "temporarily refused connection from %s: not in " "reserve list and load average = %.2f", host_and_ident(FALSE), (double)load_average/1000.0); - smtp_printf("421 %s: Too much load; please try again later\r\n", FALSE, + smtp_printf("421 %s: Too much load; please try again later\r\n", SP_NO_MORE, smtp_active_hostname); return FALSE; } @@ -3009,23 +2542,35 @@ if (!f.sender_host_unknown) fl.helo_accept_junk = verify_check_host(&helo_accept_junk_hosts) == OK; } +/* Expand recipients_max, if needed */ + { + uschar * rme = expand_string(recipients_max); + recipients_max_expanded = atoi(CCS rme); + } /* For batch SMTP input we are now done. */ if (smtp_batched_input) return TRUE; +#if defined(SUPPORT_PROXY) || defined(SUPPORT_SOCKS) || defined(EXPERIMETAL_XCLIENT) +proxy_session = FALSE; +#endif + +#ifdef SUPPORT_PROXY /* If valid Proxy Protocol source is connecting, set up session. Failure will not allow any SMTP function other than QUIT. */ -#ifdef SUPPORT_PROXY -proxy_session = FALSE; f.proxy_session_failed = FALSE; -if (check_proxy_protocol_host()) - setup_proxy_protocol_host(); +if (proxy_protocol_host()) + { + os_non_restarting_signal(SIGALRM, command_timeout_handler); + proxy_protocol_setup(); + } #endif /* Run the connect ACL if it exists */ user_msg = NULL; +GET_OPTION("acl_smtp_connect"); if (acl_smtp_connect) { int rc; @@ -3050,7 +2595,7 @@ if (tls_in.on_connect) { if (tls_server_start(&user_msg) != OK) return smtp_log_tls_fail(user_msg); - cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = TRUE; + cmd_list[CL_TLAU].is_mail_cmd = TRUE; } #endif @@ -3072,16 +2617,20 @@ if (user_msg) esclen = codelen - 4; } } -else if (!(s = expand_string(smtp_banner))) +else { - log_write(0, f.expand_string_forcedfail ? LOG_MAIN : LOG_MAIN|LOG_PANIC_DIE, - "Expansion of \"%s\" (smtp_banner) failed: %s", - smtp_banner, expand_string_message); - /* for force-fail */ -#ifndef DISABLE_TLS - if (tls_in.on_connect) tls_close(NULL, TLS_SHUTDOWN_WAIT); -#endif - return FALSE; + GET_OPTION("smtp_banner"); + if (!(s = expand_string(smtp_banner))) + { + log_write(0, f.expand_string_forcedfail ? LOG_MAIN : LOG_MAIN|LOG_PANIC_DIE, + "Expansion of \"%s\" (smtp_banner) failed: %s", + smtp_banner, expand_string_message); + /* for force-fail */ + #ifndef DISABLE_TLS + if (tls_in.on_connect) tls_close(NULL, TLS_SHUTDOWN_WAIT); + #endif + return FALSE; + } } /* Remove any terminating newlines; might as well remove trailing space too */ @@ -3147,20 +2696,20 @@ if (!check_sync()) "synchronization error (input sent without waiting for greeting): " "rejected connection from %s input=\"%s\"", host_and_ident(TRUE), string_printing(string_copyn(smtp_inptr, n))); - smtp_printf("554 SMTP synchronization error\r\n", FALSE); + smtp_printf("554 SMTP synchronization error\r\n", SP_NO_MORE); return FALSE; } /* Now output the banner */ /*XXX the ehlo-resp code does its own tls/nontls bit. Maybe subroutine that? */ -smtp_printf("%s", +smtp_printf("%Y", #ifndef DISABLE_PIPE_CONNECT fl.pipe_connect_acceptable && pipeline_connect_sends(), #else - FALSE, + SP_NO_MORE, #endif - string_from_gstring(ss)); + ss); /* Attempt to see if we sent the banner before the last ACK of the 3-way handshake arrived. If so we must have managed a TFO. */ @@ -3209,18 +2758,18 @@ if (++synprot_error_count > smtp_max_synprot_errors) { yield = 1; log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many " - "syntax or protocol errors (last command was \"%s\", %s)", + "syntax or protocol errors (last command was \"%s\", %Y)", host_and_ident(FALSE), string_printing(smtp_cmd_buffer), - string_from_gstring(s_connhad_log(NULL)) + s_connhad_log(NULL) ); } if (code > 0) { - smtp_printf("%d%c%s%s%s\r\n", FALSE, code, yield == 1 ? '-' : ' ', + smtp_printf("%d%c%s%s%s\r\n", SP_NO_MORE, code, yield == 1 ? '-' : ' ', data ? data : US"", data ? US": " : US"", errmess); if (yield == 1) - smtp_printf("%d Too many syntax or protocol errors\r\n", FALSE, code); + smtp_printf("%d Too many syntax or protocol errors\r\n", SP_NO_MORE, code); } return yield; @@ -3247,7 +2796,7 @@ Returns: nothing */ void -smtp_respond(uschar* code, int codelen, BOOL final, uschar *msg) +smtp_respond(uschar * code, int codelen, BOOL final, uschar * msg) { int esclen = 0; uschar *esc = US""; @@ -3296,7 +2845,7 @@ for (;;) } else { - smtp_printf("%.3s-%.*s%.*s\r\n", TRUE, code, esclen, esc, (int)(nl - msg), msg); + smtp_printf("%.3s-%.*s%.*s\r\n", SP_MORE, code, esclen, esc, (int)(nl - msg), msg); msg = nl + 1; Uskip_whitespace(&msg); } @@ -3402,7 +2951,7 @@ Returns: 0 in most cases */ int -smtp_handle_acl_fail(int where, int rc, uschar *user_msg, uschar *log_msg) +smtp_handle_acl_fail(int where, int rc, uschar * user_msg, uschar * log_msg) { BOOL drop = rc == FAIL_DROP; int codelen = 3; @@ -3488,7 +3037,7 @@ if (sender_verified_failed && string_sprintf(": %s", sender_verified_failed->message)); if (rc == FAIL && sender_verified_failed->user_message) - smtp_respond(smtp_code, codelen, FALSE, string_sprintf( + smtp_respond(smtp_code, codelen, SR_NOT_FINAL, string_sprintf( testflag(sender_verified_failed, af_verify_pmfail)? "Postmaster verification failed while checking <%s>\n%s\n" "Several RFCs state that you are required to have a postmaster\n" @@ -3520,7 +3069,7 @@ 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, + smtp_respond(smtp_code, codelen, SR_FINAL, user_msg ? user_msg : US"Administrative prohibition"); /* Send temporary failure response to the command. Don't give any details, @@ -3538,12 +3087,12 @@ else && sender_verified_failed && sender_verified_failed->message ) - smtp_respond(smtp_code, codelen, FALSE, sender_verified_failed->message); + smtp_respond(smtp_code, codelen, SR_NOT_FINAL, sender_verified_failed->message); - smtp_respond(smtp_code, codelen, TRUE, user_msg); + smtp_respond(smtp_code, codelen, SR_FINAL, user_msg); } else - smtp_respond(smtp_code, codelen, TRUE, + smtp_respond(smtp_code, codelen, SR_FINAL, US"Temporary local problem - please try later"); /* Log the incident to the logs that are specified by log_reject_target @@ -3551,7 +3100,7 @@ else the connection is not forcibly to be dropped, return 0. Otherwise, log why it is closing if required and return 2. */ -if (log_reject_target != 0) +if (log_reject_target) { #ifndef DISABLE_TLS gstring * g = s_tlslog(NULL); @@ -3572,8 +3121,7 @@ if (log_reject_target != 0) if (!drop) return 0; -log_write(L_smtp_connection, LOG_MAIN, "%s closed by DROP in ACL", - smtp_get_connection_info()); +log_close_event(US"by DROP in ACL"); /* Run the not-quit ACL, but without any custom messages. This should not be a problem, because we get here only if some other ACL has issued "drop", and @@ -3643,6 +3191,7 @@ fl.smtp_exit_function_called = TRUE; /* Call the not-QUIT ACL, if there is one, unless no reason is given. */ +GET_OPTION("acl_smtp_notquit"); if (acl_smtp_notquit && reason) { smtp_notquit_reason = reason; @@ -3661,7 +3210,7 @@ responses are all internal, they should be reasonable size. */ if (code && defaultrespond) { if (user_msg) - smtp_respond(code, 3, TRUE, user_msg); + smtp_respond(code, 3, SR_FINAL, user_msg); else { gstring * g; @@ -3670,7 +3219,7 @@ if (code && defaultrespond) va_start(ap, defaultrespond); g = string_vformat(NULL, SVFMT_EXTEND|SVFMT_REBUFFER, CS defaultrespond, ap); va_end(ap); - smtp_printf("%s %s\r\n", FALSE, code, string_from_gstring(g)); + smtp_printf("%s %Y\r\n", SP_NO_MORE, code, g); } mac_smtp_fflush(); } @@ -3827,7 +3376,7 @@ smtp_user_msg(uschar *code, uschar *user_msg) { int len = 3; smtp_message_code(&code, &len, &user_msg, NULL, TRUE); -smtp_respond(code, len, TRUE, user_msg); +smtp_respond(code, len, SR_FINAL, user_msg); } @@ -3962,7 +3511,7 @@ if (f.allow_unqualified_recipient || strcmpic(*recipient, US"postmaster") == 0) *recipient = US rewrite_address_qualify(*recipient, TRUE); return rd; } -smtp_printf("501 %s: recipient address must contain a domain\r\n", FALSE, +smtp_printf("501 %s: recipient address must contain a domain\r\n", SP_NO_MORE, smtp_cmd_data); log_write(L_smtp_syntax_error, LOG_MAIN|LOG_REJECT, "unqualified %s rejected: <%s> %s%s", @@ -3979,6 +3528,7 @@ smtp_quit_handler(uschar ** user_msgp, uschar ** log_msgp) HAD(SCH_QUIT); f.smtp_in_quit = TRUE; incomplete_transaction_log(US"QUIT"); +GET_OPTION("acl_smtp_quit"); if ( acl_smtp_quit && acl_check(ACL_WHERE_QUIT, NULL, acl_smtp_quit, user_msgp, log_msgp) == ERROR) @@ -3990,25 +3540,23 @@ if ( acl_smtp_quit #endif if (*user_msgp) - smtp_respond(US"221", 3, TRUE, *user_msgp); + smtp_respond(US"221", 3, SR_FINAL, *user_msgp); else - smtp_printf("221 %s closing connection\r\n", FALSE, smtp_active_hostname); + smtp_printf("221 %s closing connection\r\n", SP_NO_MORE, smtp_active_hostname); #ifdef SERVERSIDE_CLOSE_NOWAIT # ifndef DISABLE_TLS tls_close(NULL, TLS_SHUTDOWN_NOWAIT); # endif -log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT", - smtp_get_connection_info()); +log_close_event(US"by QUIT"); #else # ifndef DISABLE_TLS tls_close(NULL, TLS_SHUTDOWN_WAIT); # endif -log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT", - smtp_get_connection_info()); +log_close_event(US"by QUIT"); /* Pause, hoping client will FIN first so that they get the TIME_WAIT. The socket should become readble (though with no data) */ @@ -4023,8 +3571,8 @@ smtp_rset_handler(void) { HAD(SCH_RSET); incomplete_transaction_log(US"RSET"); -smtp_printf("250 Reset OK\r\n", FALSE); -cmd_list[CMD_LIST_RSET].is_mail_cmd = FALSE; +smtp_printf("250 Reset OK\r\n", SP_NO_MORE); +cmd_list[CL_RSET].is_mail_cmd = FALSE; if (chunking_state > CHUNKING_OFFERED) chunking_state = CHUNKING_OFFERED; } @@ -4086,11 +3634,11 @@ message_ended = END_NOTSTARTED; chunking_state = f.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; +cmd_list[CL_RSET].is_mail_cmd = TRUE; +cmd_list[CL_HELO].is_mail_cmd = TRUE; +cmd_list[CL_EHLO].is_mail_cmd = TRUE; #ifndef DISABLE_TLS -cmd_list[CMD_LIST_STARTTLS].is_mail_cmd = TRUE; +cmd_list[CL_STLS].is_mail_cmd = TRUE; #endif if (lwr_receive_getc != NULL) @@ -4146,14 +3694,15 @@ while (done <= 0) if ( tls_in.active.sock >= 0 && tls_in.peercert && tls_in.certificate_verified - && cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd + && cmd_list[CL_TLAU].is_mail_cmd ) { - cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = FALSE; + cmd_list[CL_TLAU].is_mail_cmd = FALSE; for (auth_instance * au = auths; au; au = au->next) if (strcmpic(US"tls", au->driver_name) == 0) { + GET_OPTION("acl_smtp_auth"); if ( acl_smtp_auth && (rc = acl_check(ACL_WHERE_AUTH, NULL, acl_smtp_auth, &user_msg, &log_msg)) != OK @@ -4209,7 +3758,7 @@ while (done <= 0) case AUTH_CMD: HAD(SCH_AUTH); authentication_failed = TRUE; - cmd_list[CMD_LIST_AUTH].is_mail_cmd = FALSE; + cmd_list[CL_AUTH].is_mail_cmd = FALSE; if (!fl.auth_advertised && !f.allow_auth_unadvertised) { @@ -4232,6 +3781,7 @@ while (done <= 0) /* Check the ACL */ + GET_OPTION("acl_smtp_auth"); if ( acl_smtp_auth && (rc = acl_check(ACL_WHERE_AUTH, NULL, acl_smtp_auth, &user_msg, &log_msg)) != OK @@ -4257,8 +3807,8 @@ while (done <= 0) if (*smtp_cmd_data) { - *smtp_cmd_data++ = 0; - while (isspace(*smtp_cmd_data)) smtp_cmd_data++; + *smtp_cmd_data++ = '\0'; + Uskip_whitespace(&smtp_cmd_data); } /* Search for an authentication mechanism which is configured for use @@ -4278,7 +3828,7 @@ while (done <= 0) { int rc = smtp_in_auth(au, &smtp_resp, &errmsg); - smtp_printf("%s\r\n", FALSE, smtp_resp); + smtp_printf("%s\r\n", SP_NO_MORE, smtp_resp); if (rc != OK) { uschar * logmsg = NULL; @@ -4331,15 +3881,15 @@ while (done <= 0) fl.esmtp = TRUE; HELO_EHLO: /* Common code for HELO and EHLO */ - cmd_list[CMD_LIST_HELO].is_mail_cmd = FALSE; - cmd_list[CMD_LIST_EHLO].is_mail_cmd = FALSE; + cmd_list[CL_HELO].is_mail_cmd = FALSE; + cmd_list[CL_EHLO].is_mail_cmd = FALSE; /* Reject the HELO if its argument was invalid or non-existent. A successful check causes the argument to be saved in malloc store. */ if (!check_helo(smtp_cmd_data)) { - smtp_printf("501 Syntactically invalid %s argument(s)\r\n", FALSE, hello); + smtp_printf("501 Syntactically invalid %s argument(s)\r\n", SP_NO_MORE, hello); log_write(0, LOG_MAIN|LOG_REJECT, "rejected %s from %s: syntactically " "invalid argument(s): %s", hello, host_and_ident(FALSE), @@ -4349,9 +3899,9 @@ while (done <= 0) if (++synprot_error_count > smtp_max_synprot_errors) { log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many " - "syntax or protocol errors (last command was \"%s\", %s)", + "syntax or protocol errors (last command was \"%s\", %Y)", host_and_ident(FALSE), string_printing(smtp_cmd_buffer), - string_from_gstring(s_connhad_log(NULL)) + s_connhad_log(NULL) ); done = 1; } @@ -4369,10 +3919,10 @@ while (done <= 0) if (!f.sender_host_unknown) { BOOL old_helo_verified = f.helo_verified; - uschar *p = smtp_cmd_data; + uschar * p = smtp_cmd_data; - while (*p != 0 && !isspace(*p)) { *p = tolower(*p); p++; } - *p = 0; + while (*p && !isspace(*p)) { *p = tolower(*p); p++; } + *p = '\0'; /* Force a reverse lookup if HELO quoted something in helo_lookup_domains because otherwise the log can be confusing. */ @@ -4404,7 +3954,7 @@ while (done <= 0) { if (fl.helo_verify_required) { - smtp_printf("%d %s argument does not match calling host\r\n", FALSE, + smtp_printf("%d %s argument does not match calling host\r\n", SP_NO_MORE, tempfail? 451 : 550, hello); log_write(0, LOG_MAIN|LOG_REJECT, "%srejected \"%s %s\" from %s", tempfail? "temporarily " : "", @@ -4426,6 +3976,7 @@ while (done <= 0) /* Apply an ACL check if one is defined; afterwards, recheck synchronization in case the client started sending in a delay. */ + GET_OPTION("acl_smtp_helo"); if (acl_smtp_helo) if ((rc = acl_check(ACL_WHERE_HELO, NULL, acl_smtp_helo, &user_msg, &log_msg)) != OK) @@ -4503,7 +4054,7 @@ while (done <= 0) if (fl.esmtp) { - g->s[3] = '-'; + g->s[3] = '-'; /* overwrite the space after the SMTP response code */ /* I'm not entirely happy with this, as an MTA is supposed to check that it has enough room to accept a message of maximum size before @@ -4520,15 +4071,15 @@ while (done <= 0) g = string_catn(g, US"-SIZE\r\n", 7); } -#ifdef EXPERIMENTAL_ESMTP_LIMITS - if ( (smtp_mailcmd_max > 0 || recipients_max) +#ifndef DISABLE_ESMTP_LIMITS + if ( (smtp_mailcmd_max > 0 || recipients_max_expanded > 0) && verify_check_host(&limits_advertise_hosts) == OK) { g = string_fmt_append(g, "%.3s-LIMITS", smtp_code); if (smtp_mailcmd_max > 0) g = string_fmt_append(g, " MAILMAX=%d", smtp_mailcmd_max); - if (recipients_max) - g = string_fmt_append(g, " RCPTMAX=%d", recipients_max); + if (recipients_max_expanded > 0) + g = string_fmt_append(g, " RCPTMAX=%d", recipients_max_expanded); g = string_catn(g, US"\r\n", 2); } #endif @@ -4557,16 +4108,19 @@ while (done <= 0) /* Advertise ETRN/VRFY/EXPN if there's are ACL checking whether a host is permitted to issue them; a check is made when any host actually tries. */ + GET_OPTION("acl_smtp_etrn"); if (acl_smtp_etrn) { g = string_catn(g, smtp_code, 3); g = string_catn(g, US"-ETRN\r\n", 7); } + GET_OPTION("acl_smtp_vrfy"); if (acl_smtp_vrfy) { g = string_catn(g, smtp_code, 3); g = string_catn(g, US"-VRFY\r\n", 7); } + GET_OPTION("acl_smtp_expn"); if (acl_smtp_expn) { g = string_catn(g, smtp_code, 3); @@ -4634,9 +4188,9 @@ while (done <= 0) first = FALSE; fl.auth_advertised = TRUE; } - saveptr = g->ptr; + saveptr = gstring_length(g); g = string_catn(g, US" ", 1); - g = string_cat (g, au->public_name); + g = string_cat(g, au->public_name); while (++saveptr < g->ptr) g->s[saveptr] = toupper(g->s[saveptr]); au->advertised = TRUE; } @@ -4670,7 +4224,13 @@ while (done <= 0) fl.tls_advertised = TRUE; } #endif - +#ifdef EXPERIMENTAL_XCLIENT + if (proxy_session || verify_check_host(&hosts_xclient) != FAIL) + { + g = string_catn(g, smtp_code, 3); + g = xclient_smtp_advertise_str(g); + } +#endif #ifndef DISABLE_PRDR /* Per Recipient Data Response, draft by Eric A. Hall extending RFC */ if (prdr_enable) @@ -4699,25 +4259,29 @@ while (done <= 0) /* Terminate the string (for debug), write it, and note that HELO/EHLO has been seen. */ + { + uschar * ehlo_resp; + int len = len_string_from_gstring(g, &ehlo_resp); #ifndef DISABLE_TLS - if (tls_in.active.sock >= 0) - (void)tls_write(NULL, g->s, g->ptr, + if (tls_in.active.sock >= 0) + (void) tls_write(NULL, ehlo_resp, len, # ifndef DISABLE_PIPE_CONNECT - fl.pipe_connect_acceptable && pipeline_connect_sends()); + fl.pipe_connect_acceptable && pipeline_connect_sends()); # else - FALSE); + FALSE); # endif - else + else #endif - (void) fwrite(g->s, 1, g->ptr, smtp_out); + (void) fwrite(ehlo_resp, 1, len, smtp_out); - DEBUG(D_receive) for (const uschar * t, * s = string_from_gstring(g); - s && (t = Ustrchr(s, '\r')); - s = t + 2) /* \r\n */ - debug_printf("%s %.*s\n", - s == g->s ? "SMTP>>" : " ", - (int)(t - s), s); - fl.helo_seen = TRUE; + DEBUG(D_receive) for (const uschar * t, * s = ehlo_resp; + s && (t = Ustrchr(s, '\r')); + s = t + 2) /* \r\n */ + debug_printf("%s %.*s\n", + s == g->s ? "SMTP>>" : " ", + (int)(t - s), s); + fl.helo_seen = TRUE; + } /* Reset the protocol and the state, abandoning any previous message. */ received_protocol = @@ -4732,6 +4296,41 @@ while (done <= 0) toomany = FALSE; break; /* HELO/EHLO */ +#ifdef EXPERIMENTAL_XCLIENT + case XCLIENT_CMD: + { + BOOL fatal = fl.helo_seen; + uschar * errmsg; + int resp; + + HAD(SCH_XCLIENT); + smtp_mailcmd_count++; + + if ((errmsg = xclient_smtp_command(smtp_cmd_data, &resp, &fatal))) + if (fatal) + done = synprot_error(L_smtp_syntax_error, resp, NULL, errmsg); + else + { + smtp_printf("%d %s\r\n", SP_NO_MORE, resp, errmsg); + log_write(0, LOG_MAIN|LOG_REJECT, "rejected XCLIENT from %s: %s", + host_and_ident(FALSE), errmsg); + } + else + { + fl.helo_seen = FALSE; /* Require another EHLO */ + smtp_code = string_sprintf("%d", resp); + + /*XXX unclear in spec. if this needs to be an ESMTP banner, + nor whether we get the original client's HELO after (or a proxy fake). + We require that we do; the following HELO/EHLO handling will set + sender_helo_name as normal. */ + + smtp_printf("%s XCLIENT success\r\n", SP_NO_MORE, smtp_code); + } + break; /* XCLIENT */ + } +#endif + /* The MAIL command requires an address as an operand. All we do here is to parse it for syntactic correctness. The form "<>" is @@ -4750,7 +4349,7 @@ while (done <= 0) if ( fl.helo_verify_required || verify_check_host(&hosts_require_helo) == OK) { - smtp_printf("503 HELO or EHLO required\r\n", FALSE); + smtp_printf("503 HELO or EHLO required\r\n", SP_NO_MORE); log_write(0, LOG_MAIN|LOG_REJECT, "rejected MAIL from %s: no " "HELO/EHLO given", host_and_ident(FALSE)); break; @@ -4777,7 +4376,7 @@ while (done <= 0) if (smtp_mailcmd_max > 0 && smtp_mailcmd_count > smtp_mailcmd_max) { - smtp_printf("421 too many messages in this connection\r\n", FALSE); + smtp_printf("421 too many messages in this connection\r\n", SP_NO_MORE); log_write(0, LOG_MAIN|LOG_REJECT, "rejected MAIL command %s: too many " "messages in one connection", host_and_ident(TRUE)); break; @@ -4917,6 +4516,7 @@ while (done <= 0) US"invalid data for AUTH"); goto COMMAND_LOOP; } + GET_OPTION("acl_smtp_mailauth"); if (!acl_smtp_mailauth) { ignore_msg = US"client not authenticated"; @@ -5050,7 +4650,7 @@ while (done <= 0) if (thismessage_size_limit > 0 && message_size > thismessage_size_limit) { - smtp_printf("552 Message size exceeds maximum permitted\r\n", FALSE); + smtp_printf("552 Message size exceeds maximum permitted\r\n", SP_NO_MORE); log_write(L_size_reject, LOG_MAIN|LOG_REJECT, "rejected MAIL FROM:<%s> %s: " "message too big: size%s=%d max=%d", @@ -5075,7 +4675,7 @@ while (done <= 0) smtp_check_spool_space && message_size >= 0 ? message_size + 5000 : 0)) { - smtp_printf("452 Space shortage, please try later\r\n", FALSE); + smtp_printf("452 Space shortage, please try later\r\n", SP_NO_MORE); sender_address = NULL; break; } @@ -5097,7 +4697,7 @@ while (done <= 0) } else { - smtp_printf("501 %s: sender address must contain a domain\r\n", FALSE, + smtp_printf("501 %s: sender address must contain a domain\r\n", SP_NO_MORE, smtp_cmd_data); log_write(L_smtp_syntax_error, LOG_MAIN|LOG_REJECT, @@ -5113,6 +4713,7 @@ while (done <= 0) when pipelining is not advertised, do another sync check in case the ACL delayed and the client started sending in the meantime. */ + GET_OPTION("acl_smtp_mail"); if (acl_smtp_mail) { rc = acl_check(ACL_WHERE_MAIL, NULL, acl_smtp_mail, &user_msg, &log_msg); @@ -5177,7 +4778,7 @@ while (done <= 0) { if (f.smtp_in_pipelining_advertised && last_was_rej_mail) { - smtp_printf("503 sender not yet given\r\n", FALSE); + smtp_printf("503 sender not yet given\r\n", SP_NO_MORE); was_rej_mail = TRUE; } else @@ -5321,12 +4922,13 @@ while (done <= 0) /* Check maximum allowed */ - if (rcpt_count+1 < 0 || rcpt_count > recipients_max && recipients_max > 0) + if ( rcpt_count+1 < 0 + || rcpt_count > recipients_max_expanded && recipients_max_expanded > 0) { if (recipients_max_reject) { rcpt_fail_count++; - smtp_printf("552 too many recipients\r\n", FALSE); + smtp_printf("552 too many recipients\r\n", SP_NO_MORE); if (!toomany) log_write(0, LOG_MAIN|LOG_REJECT, "too many recipients: message " "rejected: sender=<%s> %s", sender_address, host_and_ident(TRUE)); @@ -5334,7 +4936,7 @@ while (done <= 0) else { rcpt_defer_count++; - smtp_printf("452 too many recipients\r\n", FALSE); + smtp_printf("452 too many recipients\r\n", SP_NO_MORE); if (!toomany) log_write(0, LOG_MAIN|LOG_REJECT, "too many recipients: excess " "temporarily rejected: sender=<%s> %s", sender_address, @@ -5367,10 +4969,13 @@ while (done <= 0) if (f.recipients_discarded) rc = DISCARD; else + { + GET_OPTION("acl_smtp_rcpt"); if ( (rc = acl_check(ACL_WHERE_RCPT, recipient, acl_smtp_rcpt, &user_msg, &log_msg)) == OK && !f.smtp_in_pipelining_advertised && !check_sync()) goto SYNC_FAILURE; + } /* The ACL was happy */ @@ -5400,7 +5005,7 @@ while (done <= 0) if (user_msg) smtp_user_msg(US"250", user_msg); else - smtp_printf("250 Accepted\r\n", FALSE); + smtp_printf("250 Accepted\r\n", SP_NO_MORE); rcpt_fail_count++; discarded = TRUE; log_write(0, LOG_MAIN|LOG_REJECT, "%s F=<%s> RCPT %s: " @@ -5487,15 +5092,15 @@ while (done <= 0) { uschar *code = US"503"; int len = Ustrlen(rcpt_smtp_response); - smtp_respond(code, 3, FALSE, US"All RCPT commands were rejected with " + smtp_respond(code, 3, SR_NOT_FINAL, US"All RCPT commands were rejected with " "this error:"); /* Responses from smtp_printf() will have \r\n on the end */ if (len > 2 && rcpt_smtp_response[len-2] == '\r') rcpt_smtp_response[len-2] = 0; - smtp_respond(code, 3, FALSE, rcpt_smtp_response); + smtp_respond(code, 3, SR_NOT_FINAL, rcpt_smtp_response); } if (f.smtp_in_pipelining_advertised && last_was_rcpt) - smtp_printf("503 Valid RCPT command must precede %s\r\n", FALSE, + smtp_printf("503 Valid RCPT command must precede %s\r\n", SP_NO_MORE, smtp_names[smtp_connection_had[SMTP_HBUFF_PREV(smtp_ch_index)]]); else done = synprot_error(L_smtp_protocol_error, 503, NULL, @@ -5515,7 +5120,7 @@ while (done <= 0) { sender_address = NULL; /* This will allow a new MAIL without RSET */ sender_address_unrewritten = NULL; - smtp_printf("554 Too many recipients\r\n", FALSE); + smtp_printf("554 Too many recipients\r\n", SP_NO_MORE); if (chunking_state > CHUNKING_OFFERED) { @@ -5526,13 +5131,14 @@ while (done <= 0) } if (chunking_state > CHUNKING_OFFERED) - rc = OK; /* No predata ACL or go-ahead output for BDAT */ + rc = OK; /* There is no predata ACL or go-ahead output for BDAT */ else { - /* 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 there is a predata-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. */ + GET_OPTION("acl_smtp_predata"); if (!acl_smtp_predata && cutthrough.cctx.sock < 0) rc = OK; else @@ -5556,7 +5162,7 @@ while (done <= 0) smtp_user_msg(US"354", user_msg); else smtp_printf( - "354 Enter message, ending with \".\" on a line by itself\r\n", FALSE); + "354 Enter message, ending with \".\" on a line by itself\r\n", SP_NO_MORE); } if (f.bdat_readers_wanted) @@ -5582,7 +5188,7 @@ while (done <= 0) if (!(address = parse_extract_address(smtp_cmd_data, &errmess, &start, &end, &recipient_domain, FALSE))) { - smtp_printf("501 %s\r\n", FALSE, errmess); + smtp_printf("501 %s\r\n", SP_NO_MORE, errmess); break; } @@ -5591,6 +5197,7 @@ while (done <= 0) US"verify"))) break; + GET_OPTION("acl_smtp_vrfy"); if ((rc = acl_check(ACL_WHERE_VRFY, address, acl_smtp_vrfy, &user_msg, &log_msg)) != OK) done = smtp_handle_acl_fail(ACL_WHERE_VRFY, rc, user_msg, log_msg); @@ -5621,7 +5228,7 @@ while (done <= 0) break; } - smtp_printf("%s\r\n", FALSE, s); + smtp_printf("%s\r\n", SP_NO_MORE, s); } break; } @@ -5629,6 +5236,7 @@ while (done <= 0) case EXPN_CMD: HAD(SCH_EXPN); + GET_OPTION("acl_smtp_expn"); rc = acl_check(ACL_WHERE_EXPN, NULL, acl_smtp_expn, &user_msg, &log_msg); if (rc != OK) done = smtp_handle_acl_fail(ACL_WHERE_EXPN, rc, user_msg, log_msg); @@ -5658,6 +5266,7 @@ while (done <= 0) /* Apply an ACL check if one is defined */ + GET_OPTION("acl_smtp_starttls"); if ( acl_smtp_starttls && (rc = acl_check(ACL_WHERE_STARTTLS, NULL, acl_smtp_starttls, &user_msg, &log_msg)) != OK @@ -5676,7 +5285,7 @@ while (done <= 0) cancel_cutthrough_connection(TRUE, US"STARTTLS received"); reset_point = smtp_reset(reset_point); toomany = FALSE; - cmd_list[CMD_LIST_STARTTLS].is_mail_cmd = FALSE; + cmd_list[CL_STLS].is_mail_cmd = FALSE; /* There's an attack where more data is read in past the STARTTLS command before TLS is negotiated, then assumed to be part of the secure session @@ -5717,9 +5326,9 @@ while (done <= 0) { if (!tls_remember_esmtp) fl.helo_seen = fl.esmtp = fl.auth_advertised = f.smtp_in_pipelining_advertised = FALSE; - cmd_list[CMD_LIST_EHLO].is_mail_cmd = TRUE; - cmd_list[CMD_LIST_AUTH].is_mail_cmd = TRUE; - cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = TRUE; + cmd_list[CL_EHLO].is_mail_cmd = TRUE; + cmd_list[CL_AUTH].is_mail_cmd = TRUE; + cmd_list[CL_TLAU].is_mail_cmd = TRUE; if (sender_helo_name) { sender_helo_name = NULL; @@ -5749,7 +5358,7 @@ while (done <= 0) if (rc == DEFER) { - smtp_printf("454 TLS currently unavailable\r\n", FALSE); + smtp_printf("454 TLS currently unavailable\r\n", SP_NO_MORE); break; } @@ -5763,8 +5372,7 @@ while (done <= 0) while (done <= 0) switch(smtp_read_command(FALSE, GETC_BUFFER_UNLIMITED)) { case EOF_CMD: - log_write(L_smtp_connection, LOG_MAIN, "%s closed by EOF", - smtp_get_connection_info()); + log_close_event(US"by EOF"); smtp_notquit_exit(US"tls-failed", NULL, NULL); done = 2; break; @@ -5777,22 +5385,22 @@ while (done <= 0) case QUIT_CMD: f.smtp_in_quit = TRUE; user_msg = NULL; + GET_OPTION("acl_smtp_quit"); 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); + smtp_respond(US"221", 3, SR_FINAL, user_msg); else - smtp_printf("221 %s closing connection\r\n", FALSE, smtp_active_hostname); - log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT", - smtp_get_connection_info()); + smtp_printf("221 %s closing connection\r\n", SP_NO_MORE, smtp_active_hostname); + log_close_event(US"by QUIT"); done = 2; break; default: - smtp_printf("554 Security failure\r\n", FALSE); + smtp_printf("554 Security failure\r\n", SP_NO_MORE); break; } tls_close(NULL, TLS_SHUTDOWN_NOWAIT); @@ -5820,7 +5428,7 @@ while (done <= 0) case NOOP_CMD: HAD(SCH_NOOP); - smtp_printf("250 OK\r\n", FALSE); + smtp_printf("250 OK\r\n", SP_NO_MORE); break; @@ -5831,23 +5439,23 @@ while (done <= 0) case HELP_CMD: HAD(SCH_HELP); - smtp_printf("214-Commands supported:\r\n", TRUE); - { - uschar buffer[256]; - buffer[0] = 0; - Ustrcat(buffer, US" AUTH"); - #ifndef DISABLE_TLS - if (tls_in.active.sock < 0 && - verify_check_host(&tls_advertise_hosts) != FAIL) - Ustrcat(buffer, US" STARTTLS"); - #endif - Ustrcat(buffer, US" HELO EHLO MAIL RCPT DATA BDAT"); - Ustrcat(buffer, US" NOOP QUIT RSET HELP"); - if (acl_smtp_etrn) Ustrcat(buffer, US" ETRN"); - if (acl_smtp_expn) Ustrcat(buffer, US" EXPN"); - if (acl_smtp_vrfy) Ustrcat(buffer, US" VRFY"); - smtp_printf("214%s\r\n", FALSE, buffer); - } + smtp_printf("214-Commands supported:\r\n214", SP_MORE); + smtp_printf(" AUTH", SP_MORE); +#ifndef DISABLE_TLS + if (tls_in.active.sock < 0 && + verify_check_host(&tls_advertise_hosts) != FAIL) + smtp_printf(" STARTTLS", SP_MORE); +#endif + smtp_printf(" HELO EHLO MAIL RCPT DATA BDAT", SP_MORE); + smtp_printf(" NOOP QUIT RSET HELP", SP_MORE); + if (acl_smtp_etrn) smtp_printf(" ETRN", SP_MORE); + if (acl_smtp_expn) smtp_printf(" EXPN", SP_MORE); + if (acl_smtp_vrfy) smtp_printf(" VRFY", SP_MORE); +#ifdef EXPERIMENTAL_XCLIENT + if (proxy_session || verify_check_host(&hosts_xclient) != FAIL) + smtp_printf(" XCLIENT", SP_MORE); +#endif + smtp_printf("\r\n", SP_NO_MORE); break; @@ -5897,6 +5505,7 @@ while (done <= 0) log_write(L_etrn, LOG_MAIN, "ETRN %s received from %s", smtp_cmd_argument, host_and_ident(FALSE)); + GET_OPTION("acl_smtp_etrn"); if ((rc = acl_check(ACL_WHERE_ETRN, NULL, acl_smtp_etrn, &user_msg, &log_msg)) != OK) { @@ -5913,20 +5522,21 @@ while (done <= 0) since that is strictly the only kind of ETRN that can be implemented according to the RFC. */ + GET_OPTION("smtp_etrn_command"); if (smtp_etrn_command) { uschar *error; BOOL rc; etrn_command = smtp_etrn_command; deliver_domain = smtp_cmd_data; - rc = transport_set_up_command(&argv, smtp_etrn_command, TRUE, 0, NULL, - FALSE, US"ETRN processing", &error); + rc = transport_set_up_command(&argv, smtp_etrn_command, TSUC_EXPAND_ARGS, 0, NULL, + US"ETRN processing", &error); deliver_domain = NULL; if (!rc) { log_write(0, LOG_MAIN|LOG_PANIC, "failed to set up ETRN command: %s", error); - smtp_printf("458 Internal failure\r\n", FALSE); + smtp_printf("458 Internal failure\r\n", SP_NO_MORE); break; } } @@ -5957,7 +5567,7 @@ while (done <= 0) debug_printf("ETRN command is: %s\n", etrn_command); debug_printf("ETRN command execution skipped\n"); } - if (user_msg == NULL) smtp_printf("250 OK\r\n", FALSE); + if (user_msg == NULL) smtp_printf("250 OK\r\n", SP_NO_MORE); else smtp_user_msg(US"250", user_msg); break; } @@ -5968,7 +5578,7 @@ while (done <= 0) if (smtp_etrn_serialize && !enq_start(etrn_serialize_key, 1)) { - smtp_printf("458 Already processing %s\r\n", FALSE, smtp_cmd_data); + smtp_printf("458 Already processing %s\r\n", SP_NO_MORE, smtp_cmd_data); break; } @@ -6033,12 +5643,12 @@ while (done <= 0) { log_write(0, LOG_MAIN|LOG_PANIC, "fork of process for ETRN failed: %s", strerror(errno)); - smtp_printf("458 Unable to fork process\r\n", FALSE); + smtp_printf("458 Unable to fork process\r\n", SP_NO_MORE); if (smtp_etrn_serialize) enq_end(etrn_serialize_key); } else if (!user_msg) - smtp_printf("250 OK\r\n", FALSE); + smtp_printf("250 OK\r\n", SP_NO_MORE); else smtp_user_msg(US"250", user_msg); @@ -6058,33 +5668,33 @@ while (done <= 0) done = synprot_error(L_smtp_syntax_error, 0, NULL, /* Just logs */ US"NUL character(s) present (shown as '?')"); smtp_printf("501 NUL characters are not allowed in SMTP commands\r\n", - FALSE); + SP_NO_MORE); break; case BADSYN_CMD: SYNC_FAILURE: - if (smtp_inend >= smtp_inbuffer + IN_BUFFER_SIZE) - smtp_inend = smtp_inbuffer + IN_BUFFER_SIZE - 1; - c = smtp_inend - smtp_inptr; - if (c > 150) c = 150; /* limit logged amount */ - smtp_inptr[c] = 0; - incomplete_transaction_log(US"sync failure"); - log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol synchronization error " - "(next input sent too soon: pipelining was%s advertised): " - "rejected \"%s\" %s next input=\"%s\"", - f.smtp_in_pipelining_advertised ? "" : " not", - smtp_cmd_buffer, host_and_ident(TRUE), - string_printing(smtp_inptr)); - smtp_notquit_exit(US"synchronization-error", US"554", - US"SMTP synchronization error"); - done = 1; /* Pretend eof - drops connection */ - break; + { + unsigned nchars = 150; + uschar * buf = receive_getbuf(&nchars); /* destructive read */ + buf[nchars] = '\0'; + incomplete_transaction_log(US"sync failure"); + log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol synchronization error " + "(next input sent too soon: pipelining was%s advertised): " + "rejected \"%s\" %s next input=\"%s\" (%u bytes)", + f.smtp_in_pipelining_advertised ? "" : " not", + smtp_cmd_buffer, host_and_ident(TRUE), + string_printing(buf), nchars); + smtp_notquit_exit(US"synchronization-error", US"554", + US"SMTP synchronization error"); + done = 1; /* Pretend eof - drops connection */ + break; + } case TOO_MANY_NONMAIL_CMD: s = smtp_cmd_buffer; - while (*s != 0 && !isspace(*s)) s++; + while (*s && !isspace(*s)) s++; incomplete_transaction_log(US"too many non-mail commands"); log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many " "nonmail commands (last was \"%.*s\")", host_and_ident(FALSE), @@ -6095,7 +5705,7 @@ while (done <= 0) #ifdef SUPPORT_PROXY case PROXY_FAIL_IGNORE_CMD: - smtp_printf("503 Command refused, required Proxy negotiation failed\r\n", FALSE); + smtp_printf("503 Command refused, required Proxy negotiation failed\r\n", SP_NO_MORE); break; #endif