/*++ /* NAME /* smtp_proto 3 /* SUMMARY /* client SMTP/LMTP protocol /* SYNOPSIS /* #include "smtp.h" /* /* int smtp_helo(state) /* SMTP_STATE *state; /* /* int smtp_xfer(state) /* SMTP_STATE *state; /* /* int smtp_rset(state) /* SMTP_STATE *state; /* /* int smtp_quit(state) /* SMTP_STATE *state; /* DESCRIPTION /* In the subsequent text, SMTP implies LMTP. /* This module implements the client side of the SMTP protocol. /* /* smtp_helo() performs the initial handshake with the SMTP server. /* When TLS is enabled, this includes STARTTLS negotiations. /* /* smtp_xfer() sends message envelope information followed by the /* message data, and finishes the SMTP conversation. These operations /* are combined in one function, in order to implement SMTP pipelining. /* Recipients are marked as "done" in the mail queue file when /* bounced or delivered. The message delivery status is updated /* accordingly. /* /* smtp_rset() sends a single RSET command and waits for the /* response. In case of a negative reply it sets the /* CANT_RSET_THIS_SESSION flag. /* /* smtp_quit() sends a single QUIT command and waits for the /* response if configured to do so. It always turns off connection /* caching. /* DIAGNOSTICS /* smtp_helo(), smtp_xfer(), smtp_rset() and smtp_quit() return /* 0 in case of success, -1 in case of failure. For smtp_xfer(), /* smtp_rset() and smtp_quit(), success means the ability to /* perform an SMTP conversation, not necessarily the ability /* to deliver mail, or the achievement of server happiness. /* /* In case of a rejected or failed connection, a connection /* is marked as "bad, do not cache". Otherwise, connection /* caching may be turned off (without being marked "bad") at /* the discretion of the code that implements the individual /* protocol steps. /* /* Warnings: corrupt message file. A corrupt message is marked /* as "corrupt" by changing its queue file permissions. /* BUGS /* Some SMTP servers will abort when the number of recipients /* for one message exceeds their capacity. This behavior violates /* the SMTP protocol. /* The only way around this is to limit the number of recipients /* per transaction to an artificially-low value. /* SEE ALSO /* smtp(3h) internal data structures /* smtp_chat(3) query/reply SMTP support /* smtp_trouble(3) error handlers /* LICENSE /* .ad /* .fi /* The Secure Mailer license must be distributed with this software. /* AUTHOR(S) /* Wietse Venema /* IBM T.J. Watson Research /* P.O. Box 704 /* Yorktown Heights, NY 10598, USA /* /* Wietse Venema /* Google, Inc. /* 111 8th Avenue /* New York, NY 10011, USA /* /* Pipelining code in cooperation with: /* Jon Ribbens /* Oaktree Internet Solutions Ltd., /* Internet House, /* Canal Basin, /* Coventry, /* CV1 4LY, United Kingdom. /* /* Connection caching in cooperation with: /* Victor Duchovni /* Morgan Stanley /* /* TLS support originally by: /* Lutz Jaenicke /* BTU Cottbus /* Allgemeine Elektrotechnik /* Universitaetsplatz 3-4 /* D-03044 Cottbus, Germany /*--*/ /* System library. */ #include #include #include /* shutdown(2) */ #include /* ntohs() */ #include #include #include /* 44BSD stdarg.h uses abort() */ #include #include #ifdef STRCASECMP_IN_STRINGS_H #include #endif /* Utility library. */ #include #include #include #include #include #include #include #include #include #include /* Global library. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Application-specific. */ #include "smtp.h" #include "smtp_sasl.h" /* * Sender and receiver state. A session does not necessarily go through a * linear progression, but states are guaranteed to not jump backwards. * Normal sessions go from MAIL->RCPT->DATA->DOT->QUIT->LAST. The states * MAIL, RCPT, and DATA may also be followed by ABORT->QUIT->LAST. * * When connection caching is enabled, the QUIT state is suppressed. Normal * sessions proceed as MAIL->RCPT->DATA->DOT->LAST, while aborted sessions * end with ABORT->LAST. The connection is left open for a limited time. An * RSET probe should be sent before attempting to reuse an open connection * for a new transaction. * * The code to send an RSET probe is a special case with its own initial state * and with its own dedicated state transitions. The session proceeds as * RSET->LAST. This code is kept inside the main protocol engine for * consistent error handling and error reporting. It is not to be confused * with the code that sends RSET to abort a mail transaction in progress. * * The code to send QUIT without message delivery transaction jumps into the * main state machine. If this introduces complications, then we should * introduce a second QUIT state with its own dedicated state transitions, * just like we did for RSET probes. * * By default, the receiver skips the QUIT response. Some SMTP servers * disconnect after responding to ".", and some SMTP servers wait before * responding to QUIT. * * Client states that are associated with sending mail (up to and including * SMTP_STATE_DOT) must have smaller numerical values than the non-sending * states (SMTP_STATE_ABORT .. SMTP_STATE_LAST). */ #define SMTP_STATE_XFORWARD_NAME_ADDR 0 #define SMTP_STATE_XFORWARD_PROTO_HELO 1 #define SMTP_STATE_MAIL 2 #define SMTP_STATE_RCPT 3 #define SMTP_STATE_DATA 4 #define SMTP_STATE_DOT 5 #define SMTP_STATE_ABORT 6 #define SMTP_STATE_RSET 7 #define SMTP_STATE_QUIT 8 #define SMTP_STATE_LAST 9 int *xfer_timeouts[SMTP_STATE_LAST] = { &var_smtp_xfwd_tmout, /* name/addr */ &var_smtp_xfwd_tmout, /* helo/proto */ &var_smtp_mail_tmout, &var_smtp_rcpt_tmout, &var_smtp_data0_tmout, &var_smtp_data2_tmout, &var_smtp_rset_tmout, &var_smtp_rset_tmout, &var_smtp_quit_tmout, }; char *xfer_states[SMTP_STATE_LAST] = { "sending XFORWARD name/address", "sending XFORWARD protocol/helo_name", "sending MAIL FROM", "sending RCPT TO", "sending DATA command", "sending end of data -- message may be sent more than once", "sending final RSET", "sending RSET probe", "sending QUIT", }; char *xfer_request[SMTP_STATE_LAST] = { "XFORWARD name/address command", "XFORWARD helo/protocol command", "MAIL FROM command", "RCPT TO command", "DATA command", "end of DATA command", "final RSET command", "RSET probe", "QUIT command", }; /* * Note: MIME downgrade never happens for mail that must be delivered with * SMTPUTF8 (the sender requested SMTPUTF8, AND the delivery request * involves at least one UTF-8 envelope address or header value. */ #define SMTP_MIME_DOWNGRADE(session, request) \ (var_disable_mime_oconv == 0 \ && (session->features & SMTP_FEATURE_8BITMIME) == 0 \ && strcmp(request->encoding, MAIL_ATTR_ENC_7BIT) != 0) #ifdef USE_TLS static int smtp_start_tls(SMTP_STATE *); #endif /* * Call-back information for header/body checks. We don't provide call-backs * for actions that change the message delivery time or destination. */ static void smtp_hbc_logger(void *, const char *, const char *, const char *, const char *); static void smtp_text_out(void *, int, const char *, ssize_t, off_t); HBC_CALL_BACKS smtp_hbc_callbacks[1] = { smtp_hbc_logger, smtp_text_out, }; static int smtp_vrfy_tgt; /* smtp_vrfy_init - initialize */ void smtp_vrfy_init(void) { static const NAME_CODE vrfy_init_table[] = { SMTP_VRFY_TGT_RCPT, SMTP_STATE_RCPT, SMTP_VRFY_TGT_DATA, SMTP_STATE_DATA, 0, }; if ((smtp_vrfy_tgt = name_code(vrfy_init_table, NAME_CODE_FLAG_NONE, var_smtp_vrfy_tgt)) == 0) msg_fatal("bad protocol stage: \"%s = %s\"", VAR_SMTP_VRFY_TGT, var_smtp_vrfy_tgt); } /* smtp_helo - perform initial handshake with SMTP server */ int smtp_helo(SMTP_STATE *state) { const char *myname = "smtp_helo"; SMTP_SESSION *session = state->session; DELIVER_REQUEST *request = state->request; SMTP_ITERATOR *iter = state->iterator; SMTP_RESP *resp; SMTP_RESP fake; int except; char *lines; char *words; char *word; int n; static const NAME_CODE xforward_features[] = { XFORWARD_NAME, SMTP_FEATURE_XFORWARD_NAME, XFORWARD_ADDR, SMTP_FEATURE_XFORWARD_ADDR, XFORWARD_PORT, SMTP_FEATURE_XFORWARD_PORT, XFORWARD_PROTO, SMTP_FEATURE_XFORWARD_PROTO, XFORWARD_HELO, SMTP_FEATURE_XFORWARD_HELO, XFORWARD_IDENT, SMTP_FEATURE_XFORWARD_IDENT, XFORWARD_DOMAIN, SMTP_FEATURE_XFORWARD_DOMAIN, 0, 0, }; const char *ehlo_words; int discard_mask; static const NAME_MASK pix_bug_table[] = { PIX_BUG_DISABLE_ESMTP, SMTP_FEATURE_PIX_NO_ESMTP, PIX_BUG_DELAY_DOTCRLF, SMTP_FEATURE_PIX_DELAY_DOTCRLF, 0, }; const char *pix_bug_words; const char *pix_bug_source; int pix_bug_mask; #ifdef USE_TLS int saved_features = session->features; int tls_helo_status; #endif const char *NOCLOBBER where; /* * Skip the plaintext SMTP handshake when connecting in SMTPS mode. */ #ifdef USE_TLS if (var_smtp_tls_wrappermode && (state->misc_flags & SMTP_MISC_FLAG_IN_STARTTLS) == 0) { /* XXX Mix-up of per-session and per-request flags. */ state->misc_flags |= SMTP_MISC_FLAG_IN_STARTTLS; smtp_stream_setup(state->session->stream, var_smtp_starttls_tmout, var_smtp_req_deadline, 0); tls_helo_status = smtp_start_tls(state); state->misc_flags &= ~SMTP_MISC_FLAG_IN_STARTTLS; return (tls_helo_status); } #endif /* * Prepare for disaster. */ smtp_stream_setup(state->session->stream, var_smtp_helo_tmout, var_smtp_req_deadline, 0); if ((except = vstream_setjmp(state->session->stream)) != 0) return (smtp_stream_except(state, except, where)); /* * If not recursing after STARTTLS, examine the server greeting banner * and decide if we are going to send EHLO as the next command. */ if (var_smtp_tls_wrappermode || (state->misc_flags & SMTP_MISC_FLAG_IN_STARTTLS) == 0) { /* * Read and parse the server's SMTP greeting banner. */ where = "receiving the initial server greeting"; switch ((resp = smtp_chat_resp(session))->code / 100) { case 2: break; case 5: if (var_smtp_skip_5xx_greeting) STR(resp->dsn_buf)[0] = '4'; /* FALLTHROUGH */ default: return (smtp_site_fail(state, STR(iter->host), resp, "host %s refused to talk to me: %s", session->namaddr, translit(resp->str, "\n", " "))); } /* * If the policy table specifies a bogus TLS security level, fail * now. */ #ifdef USE_TLS if (state->tls->level == TLS_LEV_INVALID) /* Warning is already logged. */ return (smtp_site_fail(state, DSN_BY_LOCAL_MTA, SMTP_RESP_FAKE(&fake, "4.7.0"), "client TLS configuration problem")); #endif /* * XXX Some PIX firewall versions require flush before "." so * it does not span a packet boundary. This hurts performance so it * is not on by default. */ if (resp->str[strspn(resp->str, "20 *\t\n")] == 0) { /* Best effort only. Ignore errors. */ if (smtp_pix_bug_maps != 0 && (pix_bug_words = maps_find(smtp_pix_bug_maps, STR(iter->addr), 0)) != 0) { pix_bug_source = VAR_LMTP_SMTP(PIX_BUG_MAPS); } else { pix_bug_words = var_smtp_pix_bug_words; pix_bug_source = VAR_LMTP_SMTP(PIX_BUG_WORDS); } if (*pix_bug_words) { pix_bug_mask = name_mask_opt(pix_bug_source, pix_bug_table, pix_bug_words, NAME_MASK_ANY_CASE | NAME_MASK_IGNORE); if ((pix_bug_mask & SMTP_FEATURE_PIX_DELAY_DOTCRLF) && request->msg_stats.incoming_arrival.tv_sec > vstream_ftime(state->session->stream) - var_smtp_pix_thresh) pix_bug_mask &= ~SMTP_FEATURE_PIX_DELAY_DOTCRLF; msg_info("%s: enabling PIX workarounds: %s for %s", request->queue_id, str_name_mask("pix workaround bitmask", pix_bug_table, pix_bug_mask), session->namaddrport); session->features |= pix_bug_mask; } } /* * See if we are talking to ourself. This should not be possible with * the way we implement DNS lookups. However, people are known to * sometimes screw up the naming service. And, mailer loops are still * possible when our own mailer routing tables are mis-configured. */ words = resp->str; (void) mystrtok(&words, "- \t\n"); for (n = 0; (word = mystrtok(&words, " \t\n")) != 0; n++) { if (n == 0 && strcasecmp(word, var_myhostname) == 0) { if (state->misc_flags & SMTP_MISC_FLAG_LOOP_DETECT) msg_warn("host %s greeted me with my own hostname %s", session->namaddrport, var_myhostname); } else if (strcasecmp(word, "ESMTP") == 0) session->features |= SMTP_FEATURE_ESMTP; } if (smtp_mode) { if (var_smtp_always_ehlo && (session->features & SMTP_FEATURE_PIX_NO_ESMTP) == 0) session->features |= SMTP_FEATURE_ESMTP; if (var_smtp_never_ehlo || (session->features & SMTP_FEATURE_PIX_NO_ESMTP) != 0) session->features &= ~SMTP_FEATURE_ESMTP; } else { session->features |= SMTP_FEATURE_ESMTP; } } /* * If recursing after STARTTLS, there is no server greeting banner. * Always send EHLO as the next command. */ else { session->features |= SMTP_FEATURE_ESMTP; } /* * Return the compliment. Fall back to SMTP if our ESMTP recognition * heuristic failed. */ if (smtp_mode) { where = "performing the EHLO handshake"; if (session->features & SMTP_FEATURE_ESMTP) { smtp_chat_cmd(session, "EHLO %s", var_smtp_helo_name); if ((resp = smtp_chat_resp(session))->code / 100 != 2) { if (resp->code == 421) return (smtp_site_fail(state, STR(iter->host), resp, "host %s refused to talk to me: %s", session->namaddr, translit(resp->str, "\n", " "))); else session->features &= ~SMTP_FEATURE_ESMTP; } } if ((session->features & SMTP_FEATURE_ESMTP) == 0) { where = "performing the HELO handshake"; smtp_chat_cmd(session, "HELO %s", var_smtp_helo_name); if ((resp = smtp_chat_resp(session))->code / 100 != 2) return (smtp_site_fail(state, STR(iter->host), resp, "host %s refused to talk to me: %s", session->namaddr, translit(resp->str, "\n", " "))); } } else { where = "performing the LHLO handshake"; smtp_chat_cmd(session, "LHLO %s", var_smtp_helo_name); if ((resp = smtp_chat_resp(session))->code / 100 != 2) return (smtp_site_fail(state, STR(iter->host), resp, "host %s refused to talk to me: %s", session->namaddr, translit(resp->str, "\n", " "))); } /* * No early returns allowed, to ensure consistent handling of TLS and * SASL policies. */ if (session->features & SMTP_FEATURE_ESMTP) { /* * Determine what server EHLO keywords to ignore, typically to avoid * inter-operability problems. */ if (smtp_ehlo_dis_maps == 0 || (ehlo_words = maps_find(smtp_ehlo_dis_maps, STR(iter->addr), 0)) == 0) ehlo_words = var_smtp_ehlo_dis_words; if (smtp_ehlo_dis_maps && smtp_ehlo_dis_maps->error) { msg_warn("%s: %s map lookup error for %s", session->state->request->queue_id, smtp_ehlo_dis_maps->title, STR(iter->addr)); vstream_longjmp(session->stream, SMTP_ERR_DATA); } discard_mask = ehlo_mask(ehlo_words); if (discard_mask && !(discard_mask & EHLO_MASK_SILENT)) msg_info("discarding EHLO keywords: %s", str_ehlo_mask(discard_mask)); /* * Pick up some useful features offered by the SMTP server. XXX Until * we have a portable routine to convert from string to off_t with * proper overflow detection, ignore the message size limit * advertised by the SMTP server. Otherwise, we might do the wrong * thing when the server advertises a really huge message size limit. * * XXX Allow for "code (SP|-) ehlo-keyword (SP|=) ehlo-param...", * because MicroSoft implemented AUTH based on an old draft. */ lines = resp->str; for (n = 0; (words = mystrtok(&lines, "\n")) != 0; /* see below */ ) { if (mystrtok(&words, "- ") && (word = mystrtok(&words, " \t=")) != 0) { if (n == 0) { if (session->helo != 0) myfree(session->helo); /* * XXX: Keep the original case: we don't expect a single * SMTP server to randomly change the case of its helo * response. If different capitalization is detected, we * should assume disjoint TLS caches. */ session->helo = mystrdup(word); if (strcasecmp(word, var_myhostname) == 0 && (state->misc_flags & SMTP_MISC_FLAG_LOOP_DETECT) != 0) { msg_warn("host %s replied to HELO/EHLO" " with my own hostname %s", session->namaddrport, var_myhostname); if (session->features & SMTP_FEATURE_BEST_MX) return (smtp_site_fail(state, DSN_BY_LOCAL_MTA, SMTP_RESP_FAKE(&fake, "5.4.6"), "mail for %s loops back to myself", request->nexthop)); else return (smtp_site_fail(state, DSN_BY_LOCAL_MTA, SMTP_RESP_FAKE(&fake, "4.4.6"), "mail for %s loops back to myself", request->nexthop)); } } else if (strcasecmp(word, "8BITMIME") == 0) { if ((discard_mask & EHLO_MASK_8BITMIME) == 0) session->features |= SMTP_FEATURE_8BITMIME; } else if (strcasecmp(word, "PIPELINING") == 0) { if ((discard_mask & EHLO_MASK_PIPELINING) == 0) session->features |= SMTP_FEATURE_PIPELINING; } else if (strcasecmp(word, "XFORWARD") == 0) { if ((discard_mask & EHLO_MASK_XFORWARD) == 0) while ((word = mystrtok(&words, " \t")) != 0) session->features |= name_code(xforward_features, NAME_CODE_FLAG_NONE, word); } else if (strcasecmp(word, "SIZE") == 0) { if ((discard_mask & EHLO_MASK_SIZE) == 0) { session->features |= SMTP_FEATURE_SIZE; if ((word = mystrtok(&words, " \t")) != 0) { if (!alldig(word)) msg_warn("bad EHLO SIZE limit \"%s\" from %s", word, session->namaddrport); else session->size_limit = off_cvt_string(word); } } #ifdef USE_TLS } else if (strcasecmp(word, "STARTTLS") == 0) { /* Ignored later if we already sent STARTTLS. */ if ((discard_mask & EHLO_MASK_STARTTLS) == 0) session->features |= SMTP_FEATURE_STARTTLS; #endif #ifdef USE_SASL_AUTH } else if (var_smtp_sasl_enable && strcasecmp(word, "AUTH") == 0) { if ((discard_mask & EHLO_MASK_AUTH) == 0) smtp_sasl_helo_auth(session, words); #endif } else if (strcasecmp(word, "DSN") == 0) { if ((discard_mask & EHLO_MASK_DSN) == 0) session->features |= SMTP_FEATURE_DSN; } else if (strcasecmp(word, "SMTPUTF8") == 0) { if ((discard_mask & EHLO_MASK_SMTPUTF8) == 0) session->features |= SMTP_FEATURE_SMTPUTF8; } n++; } } } if (msg_verbose) msg_info("server features: 0x%x size %.0f", session->features, (double) session->size_limit); /* * Decide if this delivery requires SMTPUTF8 server support. * * For now, we require that the remote SMTP server supports SMTPUTF8 when * the sender requested SMTPUTF8 support. * * XXX EAI Refine this to: the sender requested SMTPUTF8 support AND the * delivery request involves at least one UTF-8 envelope address or * header value. * * If the sender requested SMTPUTF8 support but the delivery request * involves no UTF-8 envelope address or header value, then we could * still deliver such mail to a non-SMTPUTF8 server, except that we must * either uxtext-encode ORCPT parameters or not send them. We cannot * encode the ORCPT in xtext, because legacy SMTP requires that the * unencoded address consist entirely of printable (graphic and white * space) characters from the US-ASCII repertoire (RFC 3461 section 4). A * correct uxtext encoder will produce a result that an xtext decoder * will pass through unchanged. * * XXX Should we try to encode headers with RFC 2047 when delivering to a * non-SMTPUTF8 server? That could make life easier for mailing lists. */ #define DELIVERY_REQUIRES_SMTPUTF8 \ ((request->smtputf8 & SMTPUTF8_FLAG_REQUESTED) \ && (request->smtputf8 & ~SMTPUTF8_FLAG_REQUESTED)) /* * Require that the server supports SMTPUTF8 when delivery requires * SMTPUTF8. * * Fix 20140706: moved this before negotiating TLS, AUTH, and so on. */ if ((session->features & SMTP_FEATURE_SMTPUTF8) == 0 && DELIVERY_REQUIRES_SMTPUTF8) return (smtp_mesg_fail(state, DSN_BY_LOCAL_MTA, SMTP_RESP_FAKE(&fake, "5.6.7"), "SMTPUTF8 is required, " "but was not offered by host %s", session->namaddr)); /* * Fix 20140706: don't do silly things when the remote server announces * SMTPUTF8 but not 8BITMIME support. Our primary mission is to deliver * mail, not to force people into compliance. */ if ((session->features & SMTP_FEATURE_SMTPUTF8) != 0 && (session->features & SMTP_FEATURE_8BITMIME) == 0) { msg_info("host %s offers SMTPUTF8 support, but not 8BITMIME", session->namaddr); session->features |= SMTP_FEATURE_8BITMIME; } /* * We use SMTP command pipelining if the server said it supported it. * Since we use blocking I/O, RFC 2197 says that we should inspect the * TCP window size and not send more than this amount of information. * Unfortunately this information is unavailable using the sockets * interface. However, we *can* get the TCP send buffer size on the local * TCP/IP stack. We should be able to fill this buffer without being * blocked, and then the kernel will effectively do non-blocking I/O for * us by automatically writing out the contents of its send buffer while * we are reading in the responses. In addition to TCP buffering we have * to be aware of application-level buffering by the vstream module, * which is limited to a couple kbytes. * * XXX No need to do this before and after STARTTLS, but it's not a big deal * if we do. * * XXX When TLS is turned on, the SMTP-level writes will be encapsulated as * TLS messages. Thus, the TCP-level payload will be larger than the * SMTP-level payload. This has implications for the PIPELINING engine. * * To avoid deadlock, the PIPELINING engine needs to request a TCP send * buffer size that can hold the unacknowledged commands plus the TLS * encapsulation overhead. * * The PIPELINING engine keeps the unacknowledged command size <= the * default VSTREAM buffer size (to avoid small-write performance issues * when the VSTREAM buffer size is at its default size). With a default * VSTREAM buffer size of 4096 there is no reason to increase the * unacknowledged command size as the TCP MSS increases. It's safer to * spread the remote SMTP server's recipient processing load over time, * than dumping a very large recipient list all at once. * * For TLS encapsulation overhead we make a conservative guess: take the * current protocol overhead of ~40 bytes, double the number for future * proofing (~80 bytes), then round up the result to the nearest power of * 2 (128 bytes). Plus, be prepared for worst-case compression that * expands data by 1 kbyte, so that the worst-case SMTP payload per TLS * message becomes 15 kbytes. */ #define PIPELINING_BUFSIZE VSTREAM_BUFSIZE #ifdef USE_TLS #define TLS_WORST_PAYLOAD 16384 #define TLS_WORST_COMP_OVERHD 1024 #define TLS_WORST_PROTO_OVERHD 128 #define TLS_WORST_SMTP_PAYLOAD (TLS_WORST_PAYLOAD - TLS_WORST_COMP_OVERHD) #define TLS_WORST_TOTAL_OVERHD (TLS_WORST_COMP_OVERHD + TLS_WORST_PROTO_OVERHD) #endif if (session->features & SMTP_FEATURE_PIPELINING) { SOCKOPT_SIZE optlen; int tcp_bufsize; int enc_overhead = 0; optlen = sizeof(tcp_bufsize); if (getsockopt(vstream_fileno(session->stream), SOL_SOCKET, SO_SNDBUF, (char *) &tcp_bufsize, &optlen) < 0) msg_fatal("%s: getsockopt: %m", myname); #ifdef USE_TLS if (state->misc_flags & SMTP_MISC_FLAG_IN_STARTTLS) enc_overhead += (1 + (PIPELINING_BUFSIZE - 1) / TLS_WORST_SMTP_PAYLOAD) * TLS_WORST_TOTAL_OVERHD; #endif if (tcp_bufsize < PIPELINING_BUFSIZE + enc_overhead) { tcp_bufsize = PIPELINING_BUFSIZE + enc_overhead; if (setsockopt(vstream_fileno(session->stream), SOL_SOCKET, SO_SNDBUF, (char *) &tcp_bufsize, optlen) < 0) msg_fatal("%s: setsockopt: %m", myname); } if (msg_verbose) msg_info("Using %s PIPELINING, TCP send buffer size is %d, " "PIPELINING buffer size is %d", smtp_mode ? "ESMTP" : "LMTP", tcp_bufsize, PIPELINING_BUFSIZE); } #ifdef USE_TLS /* * Skip this part if we already sent STARTTLS. */ if ((state->misc_flags & SMTP_MISC_FLAG_IN_STARTTLS) == 0) { /* * Optionally log unused STARTTLS opportunities. */ if ((session->features & SMTP_FEATURE_STARTTLS) && var_smtp_tls_note_starttls_offer && state->tls->level <= TLS_LEV_NONE) msg_info("Host offered STARTTLS: [%s]", STR(iter->host)); /* * Decide whether or not to send STARTTLS. */ if ((session->features & SMTP_FEATURE_STARTTLS) != 0 && smtp_tls_ctx != 0 && state->tls->level >= TLS_LEV_MAY) { /* * Prepare for disaster. */ smtp_stream_setup(state->session->stream, var_smtp_starttls_tmout, var_smtp_req_deadline, 0); if ((except = vstream_setjmp(state->session->stream)) != 0) return (smtp_stream_except(state, except, "receiving the STARTTLS response")); /* * Send STARTTLS. Recurse when the server accepts STARTTLS, after * resetting the SASL and EHLO features lists. * * Reset the SASL mechanism list to avoid spurious warnings. * * Use the smtp_sasl_tls_security_options feature to allow SASL * mechanisms that may not be allowed with plain-text * connections. */ smtp_chat_cmd(session, "STARTTLS"); if ((resp = smtp_chat_resp(session))->code / 100 == 2) { #ifdef USE_SASL_AUTH if (session->features & SMTP_FEATURE_AUTH) smtp_sasl_cleanup(session); #endif session->features = saved_features; /* XXX Mix-up of per-session and per-request flags. */ state->misc_flags |= SMTP_MISC_FLAG_IN_STARTTLS; tls_helo_status = smtp_start_tls(state); state->misc_flags &= ~SMTP_MISC_FLAG_IN_STARTTLS; return (tls_helo_status); } /* * Give up if we must use TLS but the server rejects STARTTLS * although support for it was announced in the EHLO response. */ session->features &= ~SMTP_FEATURE_STARTTLS; if (TLS_REQUIRED(state->tls->level)) return (smtp_site_fail(state, STR(iter->host), resp, "TLS is required, but host %s refused to start TLS: %s", session->namaddr, translit(resp->str, "\n", " "))); /* Else try to continue in plain-text mode. */ } /* * Give up if we must use TLS but can't for various reasons. * * 200412 Be sure to provide the default clause at the bottom of this * block. When TLS is required we must never, ever, end up in * plain-text mode. */ if (TLS_REQUIRED(state->tls->level)) { if (!(session->features & SMTP_FEATURE_STARTTLS)) { return (smtp_site_fail(state, DSN_BY_LOCAL_MTA, SMTP_RESP_FAKE(&fake, "4.7.4"), "TLS is required, but was not offered by host %s", session->namaddr)); } else if (smtp_tls_ctx == 0) { return (smtp_site_fail(state, DSN_BY_LOCAL_MTA, SMTP_RESP_FAKE(&fake, "4.7.5"), "TLS is required, but our TLS engine is unavailable")); } else { msg_warn("%s: TLS is required but unavailable, don't know why", myname); return (smtp_site_fail(state, DSN_BY_LOCAL_MTA, SMTP_RESP_FAKE(&fake, "4.7.0"), "TLS is required, but unavailable")); } } } #endif #ifdef USE_SASL_AUTH if (var_smtp_sasl_enable && (session->features & SMTP_FEATURE_AUTH)) return (smtp_sasl_helo_login(state)); #endif return (0); } #ifdef USE_TLS /* smtp_start_tls - turn on TLS and recurse into the HELO dialog */ static int smtp_start_tls(SMTP_STATE *state) { SMTP_SESSION *session = state->session; SMTP_ITERATOR *iter = state->iterator; TLS_CLIENT_START_PROPS start_props; VSTRING *serverid; SMTP_RESP fake; TLS_CLIENT_INIT_PROPS init_props; VSTREAM *tlsproxy; VSTRING *port_buf; /* * When the TLS handshake succeeds, we can reuse a connection only if TLS * remains turned on for the lifetime of that connection. This requires * that the TLS library state is maintained in some proxy process, for * example, in tlsproxy(8). We then store the proxy file handle in the * connection cache, and reuse that file handle. * * Otherwise, we must turn off connection caching. We can't turn off TLS in * one SMTP client process, save the open connection to a cache which is * shared with all SMTP clients, migrate the connection to another SMTP * client, and resume TLS there. When the TLS handshake fails, we can't * reuse the SMTP connection either, because the conversation is in an * unknown state. */ if (state->tls->conn_reuse == 0) DONT_CACHE_THIS_SESSION; /* * The following assumes sites that use TLS in a perverse configuration: * multiple hosts per hostname, or even multiple hosts per IP address. * All this without a shared TLS session cache, and they still want to * use TLS session caching??? * * The TLS session cache records the trust chain verification status of * cached sessions. Different transports may have different CAfile or * CApath settings, perhaps to allow authenticated connections to sites * with private CA certs without trusting said private certs for other * sites. So we cannot assume that a trust chain valid for one transport * is valid for another. Therefore the client session id must include * either the transport name or the values of CAfile and CApath. We use * the transport name. * * XXX: We store only one session per lookup key. Ideally the key maps * 1-to-1 to a server TLS session cache. We use the IP address, port and * ehlo response name to build a lookup key that works for split caches * (that announce distinct names) behind a load balancer. * * XXX: The TLS library will salt the serverid with further details of the * protocol and cipher requirements including the server ehlo response. * Deferring the helo to the digested suffix results in more predictable * SSL session lookup key lengths. */ serverid = vstring_alloc(10); smtp_key_prefix(serverid, "&", state->iterator, SMTP_KEY_FLAG_SERVICE | SMTP_KEY_FLAG_CUR_NEXTHOP /* With port */ | SMTP_KEY_FLAG_HOSTNAME | SMTP_KEY_FLAG_ADDR); if (state->tls->conn_reuse) { TLS_CLIENT_PARAMS tls_params; /* * Send all our wishes in one big request. */ TLS_PROXY_CLIENT_INIT_PROPS(&init_props, log_param = VAR_LMTP_SMTP(TLS_LOGLEVEL), log_level = var_smtp_tls_loglevel, verifydepth = var_smtp_tls_scert_vd, cache_type = LMTP_SMTP_SUFFIX(TLS_MGR_SCACHE), chain_files = var_smtp_tls_chain_files, cert_file = var_smtp_tls_cert_file, key_file = var_smtp_tls_key_file, dcert_file = var_smtp_tls_dcert_file, dkey_file = var_smtp_tls_dkey_file, eccert_file = var_smtp_tls_eccert_file, eckey_file = var_smtp_tls_eckey_file, CAfile = var_smtp_tls_CAfile, CApath = var_smtp_tls_CApath, mdalg = var_smtp_tls_fpt_dgst); TLS_PROXY_CLIENT_START_PROPS(&start_props, timeout = var_smtp_starttls_tmout, tls_level = state->tls->level, nexthop = session->tls_nexthop, host = STR(iter->host), namaddr = session->namaddrport, sni = state->tls->sni, serverid = vstring_str(serverid), helo = session->helo, protocols = state->tls->protocols, cipher_grade = state->tls->grade, cipher_exclusions = vstring_str(state->tls->exclusions), matchargv = state->tls->matchargv, mdalg = var_smtp_tls_fpt_dgst, dane = state->tls->dane); /* * The tlsproxy(8) server enforces timeouts that are larger than * those specified by the tlsproxy(8) client. These timeouts are a * safety net for the case that the tlsproxy(8) client fails to * enforce time limits. Normally, the tlsproxy(8) client would time * out and trigger a plaintext event in the tlsproxy(8) server, and * cause it to tear down the session. * * However, the tlsproxy(8) server has no insight into the SMTP * protocol, and therefore it cannot by itself support different * timeouts at different SMTP protocol stages. Instead, we specify * the largest timeout (end-of-data) and rely on the SMTP client to * time out first, which normally results in a plaintext event in the * tlsproxy(8) server. Unfortunately, we cannot permit plaintext * events during the TLS handshake, so we specify a separate timeout * for that stage (the end-of-data timeout would be unreasonably * large anyway). */ #define PROXY_OPEN_FLAGS \ (TLS_PROXY_FLAG_ROLE_CLIENT | TLS_PROXY_FLAG_SEND_CONTEXT) port_buf = vstring_alloc(100); /* minimize fragmentation */ vstring_sprintf(port_buf, "%d", ntohs(iter->port)); tlsproxy = tls_proxy_open(var_tlsproxy_service, PROXY_OPEN_FLAGS, session->stream, STR(iter->addr), STR(port_buf), var_smtp_starttls_tmout, var_smtp_data2_tmout, state->service, tls_proxy_client_param_from_config(&tls_params), &init_props, &start_props); vstring_free(port_buf); /* * To insert tlsproxy(8) between this process and the remote SMTP * server, we swap the file descriptors between the tlsproxy and * session->stream VSTREAMS, so that we don't lose all the * user-configurable session->stream attributes (such as longjump * buffers or timeouts). * * TODO: the tlsproxy RPCs should return more error detail than a "NO" * result. OTOH, the in-process TLS engine does not return such info * either. * * If the tlsproxy request fails we do not fall back to the in-process * TLS stack. Reason: the admin enabled connection reuse to respect * receiver policy; silently violating such policy would not be * useful. * * We also don't fall back to the in-process TLS stack under low-traffic * conditions, to avoid frustrating attempts to debug a problem with * using the tlsproxy(8) service. */ if (tlsproxy == 0) { session->tls_context = 0; } else { vstream_control(tlsproxy, CA_VSTREAM_CTL_DOUBLE, CA_VSTREAM_CTL_END); vstream_control(session->stream, CA_VSTREAM_CTL_SWAP_FD(tlsproxy), CA_VSTREAM_CTL_END); (void) vstream_fclose(tlsproxy); /* direct-to-server stream! */ /* * There must not be any pending data in the stream buffers * before we read the TLS context attributes. */ vstream_fpurge(session->stream, VSTREAM_PURGE_BOTH); /* * After plumbing the plaintext stream, receive the TLS context * object. For this we use the same VSTREAM buffer that we also * use to receive subsequent SMTP commands, therefore we must be * prepared for the possibility that the remote SMTP server * starts talking immediately. The tlsproxy implementation sends * the TLS context before remote content. The attribute protocol * is robust enough that an adversary cannot insert their own TLS * context attributes. */ session->tls_context = tls_proxy_context_receive(session->stream); if (session->tls_context) { session->features |= SMTP_FEATURE_FROM_PROXY; tls_log_summary(TLS_ROLE_CLIENT, TLS_USAGE_NEW, session->tls_context); } } } else { /* state->tls->conn_reuse */ /* * As of Postfix 2.5, tls_client_start() tries hard to always * complete the TLS handshake. It records the verification and match * status in the resulting TLScontext. It is now up to the * application to abort the TLS connection if it chooses. * * XXX When tls_client_start() fails then we don't know what state the * SMTP connection is in, so we give up on this connection even if we * are not required to use TLS. * * Large parameter lists are error-prone, so we emulate a language * feature that C does not have natively: named parameter lists. */ session->tls_context = TLS_CLIENT_START(&start_props, ctx = smtp_tls_ctx, stream = session->stream, fd = -1, timeout = var_smtp_starttls_tmout, tls_level = state->tls->level, nexthop = session->tls_nexthop, host = STR(iter->host), namaddr = session->namaddrport, sni = state->tls->sni, serverid = vstring_str(serverid), helo = session->helo, protocols = state->tls->protocols, cipher_grade = state->tls->grade, cipher_exclusions = vstring_str(state->tls->exclusions), matchargv = state->tls->matchargv, mdalg = var_smtp_tls_fpt_dgst, dane = state->tls->dane); /* * At this point there must not be any pending data in the stream * buffers. */ vstream_fpurge(session->stream, VSTREAM_PURGE_BOTH); } /* state->tls->conn_reuse */ vstring_free(serverid); if (session->tls_context == 0) { /* * We must avoid further I/O, the peer is in an undefined state. */ DONT_USE_FORBIDDEN_SESSION; /* * If TLS is optional, try delivery to the same server over a * plaintext connection. Otherwise we would defer mail forever with * destinations that have no alternate MX host. * * Don't fall back to plaintext if we were willing to use SASL-over-TLS * authentication. If the server doesn't announce SASL support over * plaintext connections, then we don't want delivery to fail with * "relay access denied". * * If TLS is opportunistic, don't throttle the destination, otherwise if * the mail is volume is high enough we may have difficulty ever * draining even the deferred mail, as new mail provides a constant * stream of negative feedback. */ if (PLAINTEXT_FALLBACK_OK_AFTER_STARTTLS_FAILURE) RETRY_AS_PLAINTEXT; return (smtp_misc_fail(state, state->tls->level == TLS_LEV_MAY ? SMTP_NOTHROTTLE : SMTP_THROTTLE, DSN_BY_LOCAL_MTA, SMTP_RESP_FAKE(&fake, "4.7.5"), "Cannot start TLS: handshake failure")); } /* * If we are verifying the server certificate and are not happy with the * result, abort the delivery here. We have a usable TLS session with the * server, so no need to disable I/O, ... we can even be polite and send * "QUIT". * * See src/tls/tls_level.c and src/tls/tls.h. Levels above "encrypt" require * matching. * * NOTE: We use "IS_MATCHED" to satisfy policy, but "IS_SECURED" to log * effective security. Thus "half-dane" is never "Verified" only * "Trusted", but matching is enforced here. * * NOTE: When none of the TLSA records were usable, "dane" and "half-dane" * fall back to "encrypt", updating the tls_context level accordingly, so * we must check that here, and not state->tls->level. */ if (TLS_MUST_MATCH(session->tls_context->level)) if (!TLS_CERT_IS_MATCHED(session->tls_context)) return (smtp_site_fail(state, DSN_BY_LOCAL_MTA, SMTP_RESP_FAKE(&fake, "4.7.5"), "Server certificate not verified")); /* * At this point we have to re-negotiate the "EHLO" to reget the * feature-list. */ return (smtp_helo(state)); } #endif /* smtp_hbc_logger - logging call-back for header/body checks */ static void smtp_hbc_logger(void *context, const char *action, const char *where, const char *content, const char *text) { const SMTP_STATE *state = (SMTP_STATE *) context; if (*text) { msg_info("%s: %s: %s %.60s: %s", state->request->queue_id, action, where, content, text); } else { msg_info("%s: %s: %s %.60s", state->request->queue_id, action, where, content); } } /* smtp_text_out - output one header/body record */ static void smtp_text_out(void *context, int rec_type, const char *text, ssize_t len, off_t unused_offset) { SMTP_STATE *state = (SMTP_STATE *) context; SMTP_SESSION *session = state->session; ssize_t data_left; const char *data_start; /* * Deal with an impedance mismatch between Postfix queue files (record * length <= $message_line_length_limit) and SMTP (DATA record length <= * $smtp_line_length_limit). The code below does a little too much work * when the SMTP line length limit is disabled, but it avoids code * duplication, and thus, it avoids testing and maintenance problems. */ data_left = len; data_start = text; do { if (state->space_left == var_smtp_line_limit && data_left > 0 && *data_start == '.') smtp_fputc('.', session->stream); if (ENFORCING_SIZE_LIMIT(var_smtp_line_limit) && data_left >= state->space_left) { smtp_fputs(data_start, state->space_left, session->stream); data_start += state->space_left; data_left -= state->space_left; state->space_left = var_smtp_line_limit; if (data_left > 0 || rec_type == REC_TYPE_CONT) { smtp_fputc(' ', session->stream); state->space_left -= 1; /* * XXX This can insert a line break into the middle of a * multi-byte character (not necessarily UTF-8). Note that * multibyte characters can span queue file records, for * example if line_length_limit == smtp_line_length_limit. */ if (state->logged_line_length_limit == 0) { msg_info("%s: breaking line > %d bytes with SPACE", state->request->queue_id, var_smtp_line_limit); state->logged_line_length_limit = 1; } } } else { if (rec_type == REC_TYPE_CONT) { smtp_fwrite(data_start, data_left, session->stream); state->space_left -= data_left; } else { smtp_fputs(data_start, data_left, session->stream); state->space_left = var_smtp_line_limit; } break; } } while (data_left > 0); } /* smtp_format_out - output one header/body record */ static void PRINTFLIKE(3, 4) smtp_format_out(void *, int, const char *,...); static void smtp_format_out(void *context, int rec_type, const char *fmt,...) { static VSTRING *vp; va_list ap; if (vp == 0) vp = vstring_alloc(100); va_start(ap, fmt); vstring_vsprintf(vp, fmt, ap); va_end(ap); smtp_text_out(context, rec_type, vstring_str(vp), VSTRING_LEN(vp), 0); } /* smtp_header_out - output one message header */ static void smtp_header_out(void *context, int unused_header_class, const HEADER_OPTS *unused_info, VSTRING *buf, off_t offset) { char *start = vstring_str(buf); char *line; char *next_line; /* * This code destroys the header. We could try to avoid clobbering it, * but we're not going to use the data any further. */ for (line = start; line; line = next_line) { next_line = split_at(line, '\n'); smtp_text_out(context, REC_TYPE_NORM, line, next_line ? next_line - line - 1 : strlen(line), offset); } } /* smtp_header_rewrite - rewrite message header before output */ static void smtp_header_rewrite(void *context, int header_class, const HEADER_OPTS *header_info, VSTRING *buf, off_t offset) { SMTP_STATE *state = (SMTP_STATE *) context; int did_rewrite = 0; char *line; char *start; char *next_line; char *end_line; char *result; /* * Apply optional header filtering. */ if (smtp_header_checks) { result = hbc_header_checks(context, smtp_header_checks, header_class, header_info, buf, offset); if (result == 0) return; if (result == HBC_CHECKS_STAT_ERROR) { msg_warn("%s: smtp header checks lookup error", state->request->queue_id); vstream_longjmp(state->session->stream, SMTP_ERR_DATA); } if (result != STR(buf)) { vstring_strcpy(buf, result); myfree(result); } } /* * Rewrite primary header addresses that match the smtp_generic_maps. The * cleanup server already enforces that all headers have proper lengths * and that all addresses are in proper form, so we don't have to repeat * that. */ if (smtp_generic_maps && header_info && header_class == MIME_HDR_PRIMARY && (header_info->flags & (HDR_OPT_SENDER | HDR_OPT_RECIP)) != 0) { TOK822 *tree; TOK822 **addr_list; TOK822 **tpp; tree = tok822_parse(vstring_str(buf) + strlen(header_info->name) + 1); addr_list = tok822_grep(tree, TOK822_ADDR); for (tpp = addr_list; *tpp; tpp++) did_rewrite |= smtp_map11_tree(tpp[0], smtp_generic_maps, smtp_ext_prop_mask & EXT_PROP_GENERIC); if (did_rewrite) { vstring_truncate(buf, strlen(header_info->name)); vstring_strcat(buf, ": "); tok822_externalize(buf, tree, TOK822_STR_HEAD); } myfree((void *) addr_list); tok822_free_tree(tree); } /* * Pass through unmodified headers without reconstruction. */ if (did_rewrite == 0) { smtp_header_out(context, header_class, header_info, buf, offset); return; } /* * A rewritten address list contains one address per line. The code below * replaces newlines by spaces, to fit as many addresses on a line as * possible (without rearranging the order of addresses). Prepending * white space to the beginning of lines is delegated to the output * routine. * * Code derived from cleanup_fold_header(). */ for (line = start = vstring_str(buf); line != 0; line = next_line) { end_line = line + strcspn(line, "\n"); if (line > start) { if (end_line - start < 70) { /* TAB counts as one */ line[-1] = ' '; } else { start = line; } } next_line = *end_line ? end_line + 1 : 0; } /* * Prepend a tab to continued header lines that went through the address * rewriting machinery. Just like smtp_header_out(), this code destroys * the header. We could try to avoid clobbering it, but we're not going * to use the data any further. * * Code derived from cleanup_out_header(). */ for (line = start = vstring_str(buf); line != 0; line = next_line) { next_line = split_at(line, '\n'); if (line == start || IS_SPACE_TAB(*line)) { smtp_text_out(state, REC_TYPE_NORM, line, next_line ? next_line - line - 1 : strlen(line), offset); } else { smtp_format_out(state, REC_TYPE_NORM, "\t%s", line); } } } /* smtp_body_rewrite - rewrite message body before output */ static void smtp_body_rewrite(void *context, int type, const char *buf, ssize_t len, off_t offset) { SMTP_STATE *state = (SMTP_STATE *) context; char *result; /* * Apply optional body filtering. */ if (smtp_body_checks) { result = hbc_body_checks(context, smtp_body_checks, buf, len, offset); if (result == buf) { smtp_text_out(state, type, buf, len, offset); } else if (result == HBC_CHECKS_STAT_ERROR) { msg_warn("%s: smtp body checks lookup error", state->request->queue_id); vstream_longjmp(state->session->stream, SMTP_ERR_DATA); } else if (result != 0) { smtp_text_out(state, type, result, strlen(result), offset); myfree(result); } } } /* smtp_mime_fail - MIME problem */ static void smtp_mime_fail(SMTP_STATE *state, int mime_errs) { const MIME_STATE_DETAIL *detail; SMTP_RESP fake; detail = mime_state_detail(mime_errs); smtp_mesg_fail(state, DSN_BY_LOCAL_MTA, SMTP_RESP_FAKE(&fake, detail->dsn), "%s", detail->text); } /* smtp_out_raw_or_mime - output buffer, raw output or MIME-aware */ static int smtp_out_raw_or_mime(SMTP_STATE *state, int rec_type, VSTRING *buf) { SMTP_SESSION *session = state->session; int mime_errs; if (session->mime_state == 0) { smtp_text_out((void *) state, rec_type, vstring_str(buf), VSTRING_LEN(buf), (off_t) 0); } else { mime_errs = mime_state_update(session->mime_state, rec_type, vstring_str(buf), VSTRING_LEN(buf)); if (mime_errs) { smtp_mime_fail(state, mime_errs); return (-1); } } return (0); } /* smtp_out_add_header - format address header, uses session->scratch* */ static int smtp_out_add_header(SMTP_STATE *state, const char *label, const char *lt, const char *addr, const char *gt) { SMTP_SESSION *session = state->session; smtp_rewrite_generic_internal(session->scratch2, addr); vstring_sprintf(session->scratch, "%s: %s", label, lt); smtp_quote_822_address_flags(session->scratch, vstring_str(session->scratch2), QUOTE_FLAG_DEFAULT | QUOTE_FLAG_APPEND); vstring_strcat(session->scratch, gt); return (smtp_out_raw_or_mime(state, REC_TYPE_NORM, session->scratch)); } /* smtp_out_add_headers - output additional headers, uses session->scratch* */ static int smtp_out_add_headers(SMTP_STATE *state) { /* Prepend headers in the same order as mail_copy.c. */ if (smtp_cli_attr.flags & SMTP_CLI_FLAG_RETURN_PATH) if (smtp_out_add_header(state, "Return-Path", "<", state->request->sender, ">") < 0) return (-1); if (smtp_cli_attr.flags & SMTP_CLI_FLAG_ORIG_RCPT) if (smtp_out_add_header(state, "X-Original-To", "", state->request->rcpt_list.info->orig_addr, "") < 0) return (-1); if (smtp_cli_attr.flags & SMTP_CLI_FLAG_DELIVERED_TO) if (smtp_out_add_header(state, "Delivered-To", "", state->request->rcpt_list.info->address, "") < 0) return (-1); return (0); } /* smtp_loop - exercise the SMTP protocol engine */ static int smtp_loop(SMTP_STATE *state, NOCLOBBER int send_state, NOCLOBBER int recv_state) { const char *myname = "smtp_loop"; DELIVER_REQUEST *request = state->request; SMTP_SESSION *session = state->session; SMTP_ITERATOR *iter = state->iterator; SMTP_RESP *resp; RECIPIENT *rcpt; VSTRING *next_command = vstring_alloc(100); int *NOCLOBBER survivors = 0; NOCLOBBER int next_state; NOCLOBBER int next_rcpt; NOCLOBBER int send_rcpt; NOCLOBBER int recv_rcpt; NOCLOBBER int nrcpt; NOCLOBBER int recv_done; int except; int rec_type; NOCLOBBER int prev_type = 0; NOCLOBBER int mail_from_rejected; NOCLOBBER int downgrading; int mime_errs; SMTP_RESP fake; int fail_status; /* Caution: changes to RETURN() also affect code outside the main loop. */ #define RETURN(x) do { \ if (recv_state != SMTP_STATE_LAST) \ DONT_CACHE_THIS_SESSION; \ vstring_free(next_command); \ if (survivors) \ myfree((void *) survivors); \ if (session->mime_state) \ session->mime_state = mime_state_free(session->mime_state); \ return (x); \ } while (0) #define SENDER_IS_AHEAD \ (recv_state < send_state || recv_rcpt != send_rcpt) #define SENDER_IN_WAIT_STATE \ (send_state == SMTP_STATE_DOT || send_state == SMTP_STATE_LAST) #define SENDING_MAIL \ (recv_state <= SMTP_STATE_DOT) #define CANT_RSET_THIS_SESSION \ (session->features |= SMTP_FEATURE_RSET_REJECTED) /* * Pipelining support requires two loops: one loop for sending and one * for receiving. Each loop has its own independent state. Most of the * time the sender can run ahead of the receiver by as much as the TCP * send buffer permits. There are only two places where the sender must * wait for status information from the receiver: once after sending DATA * and once after sending QUIT. * * The sender state advances until the TCP send buffer would overflow, or * until the sender needs status information from the receiver. At that * point the receiver starts processing responses. Once the receiver has * caught up with the sender, the sender resumes sending commands. If the * receiver detects a serious problem (MAIL FROM rejected, all RCPT TO * commands rejected, DATA rejected) it forces the sender to abort the * SMTP dialog with RSET and QUIT. */ nrcpt = 0; next_rcpt = send_rcpt = recv_rcpt = recv_done = 0; mail_from_rejected = 0; /* * Prepare for disaster. This should not be needed because the design * guarantees that no output is flushed before smtp_chat_resp() is * called. * * 1) Every SMTP command fits entirely in a VSTREAM output buffer. * * 2) smtp_loop() never invokes smtp_chat_cmd() without making sure that * there is sufficient space for the command in the output buffer. * * 3) smtp_loop() flushes the output buffer to avoid server timeouts. * * Changing any of these would violate the design, and would likely break * SMTP pipelining. * * We set up the error handler anyway (only upon entry to avoid wasting * resources) because 1) there is code below that expects that VSTREAM * timeouts are enabled, and 2) this allows us to detect if someone broke * Postfix by introducing spurious flush before read operations. */ if (send_state < SMTP_STATE_XFORWARD_NAME_ADDR || send_state > SMTP_STATE_QUIT) msg_panic("%s: bad sender state %d (receiver state %d)", myname, send_state, recv_state); smtp_stream_setup(session->stream, *xfer_timeouts[send_state], var_smtp_req_deadline, 0); if ((except = vstream_setjmp(session->stream)) != 0) { msg_warn("smtp_proto: spurious flush before read in send state %d", send_state); RETURN(SENDING_MAIL ? smtp_stream_except(state, except, xfer_states[send_state]) : -1); } /* * The main protocol loop. */ do { /* * Build the next command. */ switch (send_state) { /* * Sanity check. */ default: msg_panic("%s: bad sender state %d", myname, send_state); /* * Build the XFORWARD command. With properly sanitized * information, the command length stays within the 512 byte * command line length limit. * * XXX smtpd_xforward_preset() initializes some fields as "unknown" * and some as null; historically, pickup(8) does not send any of * these, and the queue manager presets absent fields to "not * available" except for the rewrite context which is preset to * local by way of migration aid. These definitions need to be * centralized for maintainability. */ #ifndef CAN_FORWARD_CLIENT_NAME #define _ATTR_AVAIL_AND_KNOWN_(val) \ (DEL_REQ_ATTR_AVAIL(val) && strcasecmp((val), "unknown")) #define CAN_FORWARD_CLIENT_NAME _ATTR_AVAIL_AND_KNOWN_ #define CAN_FORWARD_CLIENT_ADDR _ATTR_AVAIL_AND_KNOWN_ #define CAN_FORWARD_CLIENT_PORT _ATTR_AVAIL_AND_KNOWN_ #define CAN_FORWARD_PROTO_NAME _ATTR_AVAIL_AND_KNOWN_ #define CAN_FORWARD_HELO_NAME DEL_REQ_ATTR_AVAIL #define CAN_FORWARD_IDENT_NAME DEL_REQ_ATTR_AVAIL #define CAN_FORWARD_RWR_CONTEXT DEL_REQ_ATTR_AVAIL #endif case SMTP_STATE_XFORWARD_NAME_ADDR: vstring_strcpy(next_command, XFORWARD_CMD); if ((session->features & SMTP_FEATURE_XFORWARD_NAME) && CAN_FORWARD_CLIENT_NAME(request->client_name)) { vstring_strcat(next_command, " " XFORWARD_NAME "="); xtext_quote_append(next_command, request->client_name, ""); } if ((session->features & SMTP_FEATURE_XFORWARD_ADDR) && CAN_FORWARD_CLIENT_ADDR(request->client_addr)) { vstring_strcat(next_command, " " XFORWARD_ADDR "="); xtext_quote_append(next_command, request->client_addr, ""); } if ((session->features & SMTP_FEATURE_XFORWARD_PORT) && CAN_FORWARD_CLIENT_PORT(request->client_port)) { vstring_strcat(next_command, " " XFORWARD_PORT "="); xtext_quote_append(next_command, request->client_port, ""); } if (session->send_proto_helo) next_state = SMTP_STATE_XFORWARD_PROTO_HELO; else next_state = SMTP_STATE_MAIL; break; case SMTP_STATE_XFORWARD_PROTO_HELO: vstring_strcpy(next_command, XFORWARD_CMD); if ((session->features & SMTP_FEATURE_XFORWARD_PROTO) && CAN_FORWARD_PROTO_NAME(request->client_proto)) { vstring_strcat(next_command, " " XFORWARD_PROTO "="); xtext_quote_append(next_command, request->client_proto, ""); } if ((session->features & SMTP_FEATURE_XFORWARD_HELO) && CAN_FORWARD_HELO_NAME(request->client_helo)) { vstring_strcat(next_command, " " XFORWARD_HELO "="); xtext_quote_append(next_command, request->client_helo, ""); } if ((session->features & SMTP_FEATURE_XFORWARD_IDENT) && CAN_FORWARD_IDENT_NAME(request->log_ident)) { vstring_strcat(next_command, " " XFORWARD_IDENT "="); xtext_quote_append(next_command, request->log_ident, ""); } if ((session->features & SMTP_FEATURE_XFORWARD_DOMAIN) && CAN_FORWARD_RWR_CONTEXT(request->rewrite_context)) { vstring_strcat(next_command, " " XFORWARD_DOMAIN "="); xtext_quote_append(next_command, strcmp(request->rewrite_context, MAIL_ATTR_RWR_LOCAL) ? XFORWARD_DOM_REMOTE : XFORWARD_DOM_LOCAL, ""); } next_state = SMTP_STATE_MAIL; break; /* * Build the MAIL FROM command. */ case SMTP_STATE_MAIL: request->msg_stats.reuse_count = session->reuse_count; GETTIMEOFDAY(&request->msg_stats.conn_setup_done); smtp_rewrite_generic_internal(session->scratch2, request->sender); smtp_quote_821_address(session->scratch, vstring_str(session->scratch2)); vstring_sprintf(next_command, "MAIL FROM:<%s>", vstring_str(session->scratch)); /* XXX Don't announce SIZE if we're going to MIME downgrade. */ if (session->features & SMTP_FEATURE_SIZE /* RFC 1870 */ && !SMTP_MIME_DOWNGRADE(session, request)) vstring_sprintf_append(next_command, " SIZE=%lu", request->data_size); if (session->features & SMTP_FEATURE_8BITMIME) { /* RFC 1652 */ if (strcmp(request->encoding, MAIL_ATTR_ENC_8BIT) == 0) vstring_strcat(next_command, " BODY=8BITMIME"); else if (strcmp(request->encoding, MAIL_ATTR_ENC_7BIT) == 0) vstring_strcat(next_command, " BODY=7BIT"); else if (strcmp(request->encoding, MAIL_ATTR_ENC_NONE) != 0) msg_warn("%s: unknown content encoding: %s", request->queue_id, request->encoding); } if (session->features & SMTP_FEATURE_DSN) { if (request->dsn_envid[0]) { vstring_sprintf_append(next_command, " ENVID="); xtext_quote_append(next_command, request->dsn_envid, "+="); } if (request->dsn_ret) vstring_sprintf_append(next_command, " RET=%s", dsn_ret_str(request->dsn_ret)); } /* * Request SMTPUTF8 when the remote SMTP server supports SMTPUTF8 * and the sender requested SMTPUTF8 support. * * If the sender requested SMTPUTF8 but the remote SMTP server does * not support SMTPUTF8, then we have already determined earlier * that delivering this message without SMTPUTF8 will not break * the SMTPUTF8 promise that was made to the sender. */ if ((session->features & SMTP_FEATURE_SMTPUTF8) != 0 && (request->smtputf8 & SMTPUTF8_FLAG_REQUESTED) != 0) vstring_strcat(next_command, " SMTPUTF8"); /* * We authenticate the local MTA only, but not the sender. */ #ifdef USE_SASL_AUTH if (var_smtp_sasl_enable && var_smtp_dummy_mail_auth && (session->features & SMTP_FEATURE_AUTH)) vstring_strcat(next_command, " AUTH=<>"); #endif /* * CVE-2009-3555 (TLS renegotiation). Try to detect a mail * hijacking attack that prepends malicious EHLO/MAIL/RCPT/DATA * commands to our TLS session. * * For the attack to succeed, the remote SMTP server must reply to * the malicious EHLO/MAIL/RCPT/DATA commands after completing * TLS (re)negotiation, so that the replies arrive in our TLS * session (otherwise the Postfix SMTP client would time out * waiting for an answer). With some luck we can detect this * specific attack as a server MAIL reply that arrives before we * send our own MAIL command. * * We don't apply this test to the HELO command because the result * would be very timing sensitive, and we don't apply this test * to RCPT and DATA replies because these may be pipelined for * legitimate reasons. */ #ifdef USE_TLS if (var_smtp_tls_blk_early_mail_reply && (state->misc_flags & SMTP_MISC_FLAG_IN_STARTTLS) != 0 && (vstream_peek(session->stream) > 0 || peekfd(vstream_fileno(session->stream)) > 0)) session->features |= SMTP_FEATURE_EARLY_TLS_MAIL_REPLY; #endif /* * We now return to our regular broadcast. */ next_state = SMTP_STATE_RCPT; break; /* * Build one RCPT TO command before we have seen the MAIL FROM * response. */ case SMTP_STATE_RCPT: rcpt = request->rcpt_list.info + send_rcpt; smtp_rewrite_generic_internal(session->scratch2, rcpt->address); smtp_quote_821_address(session->scratch, vstring_str(session->scratch2)); vstring_sprintf(next_command, "RCPT TO:<%s>", vstring_str(session->scratch)); if (session->features & SMTP_FEATURE_DSN) { /* XXX DSN xtext encode address value not type. */ const char *orcpt_type_addr = rcpt->dsn_orcpt; /* Fix 20140706: don't use empty rcpt->orig_addr. */ if (orcpt_type_addr[0] == 0 && rcpt->orig_addr[0] != 0) { quote_822_local(session->scratch, rcpt->orig_addr); vstring_sprintf(session->scratch2, "%s;%s", /* Fix 20140707: sender must request SMTPUTF8. */ (request->smtputf8 != 0 && !allascii(vstring_str(session->scratch))) ? "utf-8" : "rfc822", vstring_str(session->scratch)); orcpt_type_addr = vstring_str(session->scratch2); } if (orcpt_type_addr[0] != 0) { /* Fix 20140706: don't send unquoted ORCPT. */ /* Fix 20140707: quoting method must match orcpt type. */ /* Fix 20140707: handle uxtext encoder errors. */ if (strncasecmp(orcpt_type_addr, "utf-8;", 6) == 0) { if (uxtext_quote(session->scratch, orcpt_type_addr, "+=") != 0) vstring_sprintf_append(next_command, " ORCPT=%s", vstring_str(session->scratch)); } else { xtext_quote(session->scratch, orcpt_type_addr, "="); vstring_sprintf_append(next_command, " ORCPT=%s", vstring_str(session->scratch)); } } if (rcpt->dsn_notify) vstring_sprintf_append(next_command, " NOTIFY=%s", dsn_notify_str(rcpt->dsn_notify)); } if ((next_rcpt = send_rcpt + 1) == SMTP_RCPT_LEFT(state)) next_state = (DEL_REQ_TRACE_ONLY(request->flags) && smtp_vrfy_tgt == SMTP_STATE_RCPT) ? SMTP_STATE_ABORT : SMTP_STATE_DATA; break; /* * Build the DATA command before we have seen all the RCPT TO * responses. */ case SMTP_STATE_DATA: vstring_strcpy(next_command, "DATA"); next_state = SMTP_STATE_DOT; break; /* * Build the "." command after we have seen the DATA response * (DATA is a protocol synchronization point). * * Changing the connection caching state here is safe because it * affects none of the not-yet processed replies to * already-generated commands. */ case SMTP_STATE_DOT: vstring_strcpy(next_command, "."); if (THIS_SESSION_IS_EXPIRED) DONT_CACHE_THIS_SESSION; next_state = THIS_SESSION_IS_CACHED ? SMTP_STATE_LAST : SMTP_STATE_QUIT; break; /* * The SMTP_STATE_ABORT sender state is entered by the sender * when it has verified all recipients; or it is entered by the * receiver when all recipients are verified or rejected, and is * then left before the bottom of the main loop. * * Changing the connection caching state here is safe because there * are no not-yet processed replies to already-generated * commands. */ case SMTP_STATE_ABORT: vstring_strcpy(next_command, "RSET"); if (THIS_SESSION_IS_EXPIRED) DONT_CACHE_THIS_SESSION; next_state = THIS_SESSION_IS_CACHED ? SMTP_STATE_LAST : SMTP_STATE_QUIT; break; /* * Build the RSET command. This is entered as initial state from * smtp_rset() and has its own dedicated state transitions. It is * used to find out the status of a cached session before * attempting mail delivery. */ case SMTP_STATE_RSET: vstring_strcpy(next_command, "RSET"); next_state = SMTP_STATE_LAST; break; /* * Build the QUIT command before we have seen the "." or RSET * response. This is entered as initial state from smtp_quit(), * or is reached near the end of any non-cached session. * * Changing the connection caching state here is safe. If this * command is pipelined together with a preceding command, then * connection caching was already turned off. Do not clobber the * "bad connection" flag. */ case SMTP_STATE_QUIT: vstring_strcpy(next_command, "QUIT"); next_state = SMTP_STATE_LAST; if (THIS_SESSION_IS_CACHED) DONT_CACHE_THIS_SESSION; break; /* * The final sender state has no action associated with it. */ case SMTP_STATE_LAST: VSTRING_RESET(next_command); break; } VSTRING_TERMINATE(next_command); /* * Process responses until the receiver has caught up. Vstreams * automatically flush buffered output when reading new data. * * Flush unsent output if command pipelining is off or if no I/O * happened for a while. This limits the accumulation of client-side * delays in pipelined sessions. * * The PIPELINING engine will flush the VSTREAM buffer if the sender * could otherwise produce more output than fits the PIPELINING * buffer. This generally works because we know exactly how much * output we produced since the last time that the sender and * receiver synchronized the SMTP state. However this logic is not * applicable after the sender enters the DATA phase, where it does * not synchronize with the receiver until the .. * Thus, the PIPELINING engine no longer knows how much data is * pending in the TCP send buffer. For this reason, if PIPELINING is * enabled, we always pipeline QUIT after .. This is * safe because once the receiver reads ., its TCP * stack either has already received the QUIT, or else it * acknowledges all bytes up to and including ., * making room in the sender's TCP stack for QUIT. */ #define CHECK_PIPELINING_BUFSIZE \ (recv_state != SMTP_STATE_DOT || send_state != SMTP_STATE_QUIT) if (SENDER_IN_WAIT_STATE || (SENDER_IS_AHEAD && ((session->features & SMTP_FEATURE_PIPELINING) == 0 || (CHECK_PIPELINING_BUFSIZE && (VSTRING_LEN(next_command) + 2 + vstream_bufstat(session->stream, VSTREAM_BST_OUT_PEND) > PIPELINING_BUFSIZE)) || time((time_t *) 0) - vstream_ftime(session->stream) > 10))) { while (SENDER_IS_AHEAD) { /* * Sanity check. */ if (recv_state < SMTP_STATE_XFORWARD_NAME_ADDR || recv_state > SMTP_STATE_QUIT) msg_panic("%s: bad receiver state %d (sender state %d)", myname, recv_state, send_state); /* * Receive the next server response. Use the proper timeout, * and log the proper client state in case of trouble. * * XXX If we lose the connection before sending end-of-data, * find out if the server sent a premature end-of-data reply. * If this read attempt fails, report "lost connection while * sending message body", not "lost connection while sending * end-of-data". * * "except" becomes zero just above the protocol loop, and stays * zero or triggers an early return from the loop. In just * one case: loss of the connection when sending the message * body, we record the exception, and keep processing in the * hope of detecting a premature 5XX. We must be careful to * not clobber this non-zero value once it is set. The * variable need not survive longjmp() calls, since the only * setjmp() which does not return early is the one sets this * condition, subsequent failures always return early. */ #define LOST_CONNECTION_INSIDE_DATA (except == SMTP_ERR_EOF) smtp_stream_setup(session->stream, *xfer_timeouts[recv_state], var_smtp_req_deadline, 0); if (LOST_CONNECTION_INSIDE_DATA) { if (vstream_setjmp(session->stream) != 0) RETURN(smtp_stream_except(state, SMTP_ERR_EOF, "sending message body")); } else { if ((except = vstream_setjmp(session->stream)) != 0) RETURN(SENDING_MAIL ? smtp_stream_except(state, except, xfer_states[recv_state]) : -1); } resp = smtp_chat_resp(session); /* * Process the response. */ switch (recv_state) { /* * Process the XFORWARD response. */ case SMTP_STATE_XFORWARD_NAME_ADDR: if (resp->code / 100 != 2) msg_warn("host %s said: %s (in reply to %s)", session->namaddrport, translit(resp->str, "\n", " "), xfer_request[SMTP_STATE_XFORWARD_NAME_ADDR]); if (session->send_proto_helo) recv_state = SMTP_STATE_XFORWARD_PROTO_HELO; else recv_state = SMTP_STATE_MAIL; break; case SMTP_STATE_XFORWARD_PROTO_HELO: if (resp->code / 100 != 2) msg_warn("host %s said: %s (in reply to %s)", session->namaddrport, translit(resp->str, "\n", " "), xfer_request[SMTP_STATE_XFORWARD_PROTO_HELO]); recv_state = SMTP_STATE_MAIL; break; /* * Process the MAIL FROM response. When the server * rejects the sender, set the mail_from_rejected flag so * that the receiver may apply a course correction. */ case SMTP_STATE_MAIL: if (resp->code / 100 != 2) { smtp_mesg_fail(state, STR(iter->host), resp, "host %s said: %s (in reply to %s)", session->namaddr, translit(resp->str, "\n", " "), xfer_request[SMTP_STATE_MAIL]); mail_from_rejected = 1; } /* * CVE-2009-3555 (TLS renegotiation). Whatever it was * that arrived before we sent our MAIL FROM command, it * was not a fatal-level TLS alert message. It could be a * warning-level TLS alert message, or a ChangeCipherSpec * message, but such messages are not normally sent in * the middle of a TLS session. We disconnect and try * again later. */ #ifdef USE_TLS if (var_smtp_tls_blk_early_mail_reply && (session->features & SMTP_FEATURE_EARLY_TLS_MAIL_REPLY)) { smtp_site_fail(state, DSN_BY_LOCAL_MTA, SMTP_RESP_FAKE(&fake, "4.7.0"), "unexpected server message"); msg_warn("server %s violates %s policy", session->namaddr, VAR_LMTP_SMTP(TLS_BLK_EARLY_MAIL_REPLY)); mail_from_rejected = 1; } #endif /* * We now return to our regular broadcast. */ recv_state = SMTP_STATE_RCPT; break; /* * Process one RCPT TO response. If MAIL FROM was * rejected, ignore RCPT TO responses: all recipients are * dead already. When all recipients are rejected the * receiver may apply a course correction. * * XXX 2821: Section 4.5.3.1 says that a 552 RCPT TO reply * must be treated as if the server replied with 452. * However, this causes "too much mail data" to be * treated as a recoverable error, which is wrong. I'll * stick with RFC 821. */ case SMTP_STATE_RCPT: if (!mail_from_rejected) { #ifdef notdef if (resp->code == 552) { resp->code = 452; resp->dsn[0] = '4'; } #endif rcpt = request->rcpt_list.info + recv_rcpt; if (resp->code / 100 == 2) { if (!smtp_mode) { if (survivors == 0) survivors = (int *) mymalloc(request->rcpt_list.len * sizeof(int)); survivors[nrcpt] = recv_rcpt; } ++nrcpt; /* If trace-only, mark the recipient done. */ if (DEL_REQ_TRACE_ONLY(request->flags) && smtp_vrfy_tgt == SMTP_STATE_RCPT) { translit(resp->str, "\n", " "); smtp_rcpt_done(state, resp, rcpt); } } else { smtp_rcpt_fail(state, rcpt, STR(iter->host), resp, "host %s said: %s (in reply to %s)", session->namaddr, translit(resp->str, "\n", " "), xfer_request[SMTP_STATE_RCPT]); } } /* If trace-only, send RSET instead of DATA. */ if (++recv_rcpt == SMTP_RCPT_LEFT(state)) recv_state = (DEL_REQ_TRACE_ONLY(request->flags) && smtp_vrfy_tgt == SMTP_STATE_RCPT) ? SMTP_STATE_ABORT : SMTP_STATE_DATA; /* XXX Also: record if non-delivering session. */ break; /* * Process the DATA response. When the server rejects * DATA, set nrcpt to a negative value so that the * receiver can apply a course correction. */ case SMTP_STATE_DATA: recv_state = SMTP_STATE_DOT; if (resp->code / 100 != 3) { if (nrcpt > 0) smtp_mesg_fail(state, STR(iter->host), resp, "host %s said: %s (in reply to %s)", session->namaddr, translit(resp->str, "\n", " "), xfer_request[SMTP_STATE_DATA]); nrcpt = -1; } /* * In the case of a successful address probe with target * equal to DATA, the remote server is now in the DATA * state, and therefore we must not make any further * attempt to send or receive on this connection. This * means that we cannot not reuse the general-purpose * course-correction logic below which sends RSET (and * perhaps QUIT). Instead we "jump" straight to the exit * and force an unceremonious disconnect. */ else if (DEL_REQ_TRACE_ONLY(request->flags) && smtp_vrfy_tgt == SMTP_STATE_DATA) { for (nrcpt = 0; nrcpt < recv_rcpt; nrcpt++) { rcpt = request->rcpt_list.info + nrcpt; if (!SMTP_RCPT_ISMARKED(rcpt)) { translit(resp->str, "\n", " "); SMTP_RESP_SET_DSN(resp, "2.0.0"); smtp_rcpt_done(state, resp, rcpt); } } DONT_CACHE_THIS_SESSION; send_state = recv_state = SMTP_STATE_LAST; } break; /* * Process the end of message response. Ignore the * response when no recipient was accepted: all * recipients are dead already, and the next receiver * state is SMTP_STATE_LAST/QUIT regardless. Otherwise, * if the message transfer fails, bounce all remaining * recipients, else cross off the recipients that were * delivered. */ case SMTP_STATE_DOT: GETTIMEOFDAY(&request->msg_stats.deliver_done); if (smtp_mode) { if (nrcpt > 0) { if (resp->code / 100 != 2) { smtp_mesg_fail(state, STR(iter->host), resp, "host %s said: %s (in reply to %s)", session->namaddr, translit(resp->str, "\n", " "), xfer_request[SMTP_STATE_DOT]); } else { for (nrcpt = 0; nrcpt < recv_rcpt; nrcpt++) { rcpt = request->rcpt_list.info + nrcpt; if (!SMTP_RCPT_ISMARKED(rcpt)) { translit(resp->str, "\n", " "); smtp_rcpt_done(state, resp, rcpt); } } } } } /* * With LMTP we have one response per accepted RCPT TO * command. Stay in the SMTP_STATE_DOT state until we * have collected all responses. */ else { if (nrcpt > 0) { rcpt = request->rcpt_list.info + survivors[recv_done++]; if (resp->code / 100 != 2) { smtp_rcpt_fail(state, rcpt, STR(iter->host), resp, "host %s said: %s (in reply to %s)", session->namaddr, translit(resp->str, "\n", " "), xfer_request[SMTP_STATE_DOT]); } else { translit(resp->str, "\n", " "); smtp_rcpt_done(state, resp, rcpt); } } if (msg_verbose) msg_info("%s: got %d of %d end-of-data replies", myname, recv_done, nrcpt); if (recv_done < nrcpt) break; } /* * XXX Do not change the connection caching state here, * even if the connection caching timer expired between * generating the command and processing the reply, * otherwise the sender and receiver loops get out of * sync. The caller will call smtp_quit() if appropriate. */ if (var_skip_quit_resp || THIS_SESSION_IS_CACHED || LOST_CONNECTION_INSIDE_DATA) recv_state = SMTP_STATE_LAST; else recv_state = SMTP_STATE_QUIT; break; /* * Receive the RSET response. * * The SMTP_STATE_ABORT sender state is entered by the * sender when it has verified all recipients; or it is * entered by the receiver when all recipients are * verified or rejected, and is then left before the * bottom of the main loop. * * XXX Do not change the connection caching state here, even * if the server rejected RSET or if the connection * caching timer expired between generating the command * and processing the reply, otherwise the sender and * receiver loops get out of sync. The caller will call * smtp_quit() if appropriate. */ case SMTP_STATE_ABORT: recv_state = (var_skip_quit_resp || THIS_SESSION_IS_CACHED ? SMTP_STATE_LAST : SMTP_STATE_QUIT); break; /* * This is the initial receiver state from smtp_rset(). * It is used to find out the status of a cached session * before attempting mail delivery. */ case SMTP_STATE_RSET: if (resp->code / 100 != 2) CANT_RSET_THIS_SESSION; recv_state = SMTP_STATE_LAST; break; /* * Receive, but otherwise ignore, the QUIT response. */ case SMTP_STATE_QUIT: recv_state = SMTP_STATE_LAST; break; } } /* * At this point, the sender and receiver are fully synchronized. */ /* * We know the server response to every command that was sent. * Apply a course correction if necessary: the sender wants to * send RCPT TO but MAIL FROM was rejected; the sender wants to * send DATA but all recipients were rejected; the sender wants * to deliver the message but DATA was rejected. */ if ((send_state == SMTP_STATE_RCPT && mail_from_rejected) || (send_state == SMTP_STATE_DATA && nrcpt == 0) || (send_state == SMTP_STATE_DOT && nrcpt < 0)) { send_state = recv_state = SMTP_STATE_ABORT; send_rcpt = recv_rcpt = 0; vstring_strcpy(next_command, "RSET"); if (THIS_SESSION_IS_EXPIRED) DONT_CACHE_THIS_SESSION; next_state = THIS_SESSION_IS_CACHED ? SMTP_STATE_LAST : SMTP_STATE_QUIT; /* XXX Also: record if non-delivering session. */ next_rcpt = 0; } } /* * Make the next sender state the current sender state. */ if (send_state == SMTP_STATE_LAST) continue; /* * Special case if the server accepted the DATA command. If the * server accepted at least one recipient send the entire message. * Otherwise, just send "." as per RFC 2197. * * XXX If there is a hard MIME error while downgrading to 7-bit mail, * disconnect ungracefully, because there is no other way to cancel a * transaction in progress. */ if (send_state == SMTP_STATE_DOT && nrcpt > 0) { smtp_stream_setup(session->stream, var_smtp_data1_tmout, var_smtp_req_deadline, var_smtp_min_data_rate); if ((except = vstream_setjmp(session->stream)) == 0) { if (vstream_fseek(state->src, request->data_offset, SEEK_SET) < 0) msg_fatal("seek queue file: %m"); downgrading = SMTP_MIME_DOWNGRADE(session, request); /* * XXX Don't downgrade just because generic_maps is turned * on. */ #define SMTP_ANY_CHECKS (smtp_header_checks || smtp_body_checks) if (downgrading || smtp_generic_maps || SMTP_ANY_CHECKS) session->mime_state = mime_state_alloc(downgrading ? MIME_OPT_DOWNGRADE | MIME_OPT_REPORT_NESTING : SMTP_ANY_CHECKS == 0 ? MIME_OPT_DISABLE_MIME : 0, smtp_generic_maps || smtp_header_checks ? smtp_header_rewrite : smtp_header_out, (MIME_STATE_ANY_END) 0, smtp_body_checks ? smtp_body_rewrite : smtp_text_out, (MIME_STATE_ANY_END) 0, (MIME_STATE_ERR_PRINT) 0, (void *) state); state->space_left = var_smtp_line_limit; if ((smtp_cli_attr.flags & SMTP_CLI_MASK_ADD_HEADERS) != 0 && smtp_out_add_headers(state) < 0) RETURN(0); while ((rec_type = rec_get(state->src, session->scratch, 0)) > 0) { if (rec_type != REC_TYPE_NORM && rec_type != REC_TYPE_CONT) break; if (smtp_out_raw_or_mime(state, rec_type, session->scratch) < 0) RETURN(0); prev_type = rec_type; } if (session->mime_state) { /* * The cleanup server normally ends MIME content with a * normal text record. The following code is needed to * flush an internal buffer when someone submits 8-bit * mail not ending in newline via /usr/sbin/sendmail * while MIME input processing is turned off, and MIME * 8bit->7bit conversion is requested upon delivery. * * Or some error while doing generic address mapping. */ mime_errs = mime_state_update(session->mime_state, rec_type, "", 0); if (mime_errs) { smtp_mime_fail(state, mime_errs); RETURN(0); } } else if (prev_type == REC_TYPE_CONT) /* missing newline */ smtp_fputs("", 0, session->stream); if (session->features & SMTP_FEATURE_PIX_DELAY_DOTCRLF) { smtp_flush(session->stream);/* hurts performance */ sleep(var_smtp_pix_delay); /* not to mention this */ } if (vstream_ferror(state->src)) msg_fatal("queue file read error"); if (rec_type != REC_TYPE_XTRA) { msg_warn("%s: bad record type: %d in message content", request->queue_id, rec_type); fail_status = smtp_mesg_fail(state, DSN_BY_LOCAL_MTA, SMTP_RESP_FAKE(&fake, "5.3.0"), "unreadable mail queue entry"); /* Bailing out, abort stream with prejudice */ (void) vstream_fpurge(session->stream, VSTREAM_PURGE_BOTH); DONT_USE_FORBIDDEN_SESSION; /* If bounce_append() succeeded, status is still 0 */ if (state->status == 0) (void) mark_corrupt(state->src); /* Don't override smtp_mesg_fail() here. */ RETURN(fail_status); } } else { if (!LOST_CONNECTION_INSIDE_DATA) RETURN(smtp_stream_except(state, except, "sending message body")); /* * We will clear the stream error flag to try and read a * premature 5XX response, so it is important to flush any * unwritten data. Otherwise, we will try to flush it again * before reading, which may incur an unnecessary delay and * will prevent the reading of any response that is not * already buffered (bundled with the DATA 354 response). * * Not much point in sending QUIT at this point, skip right to * SMTP_STATE_LAST. The read engine above will likewise avoid * looking for a QUIT response. */ (void) vstream_fpurge(session->stream, VSTREAM_PURGE_WRITE); next_state = SMTP_STATE_LAST; } } /* * Copy the next command to the buffer and update the sender state. */ if (except == 0) { smtp_chat_cmd(session, "%s", vstring_str(next_command)); } else { DONT_CACHE_THIS_SESSION; } send_state = next_state; send_rcpt = next_rcpt; } while (recv_state != SMTP_STATE_LAST); RETURN(0); } /* smtp_xfer - send a batch of envelope information and the message data */ int smtp_xfer(SMTP_STATE *state) { DELIVER_REQUEST *request = state->request; SMTP_SESSION *session = state->session; SMTP_RESP fake; int send_state; int recv_state; int send_name_addr; int result; /* * Sanity check. Recipients should be unmarked at this point. */ if (SMTP_RCPT_LEFT(state) <= 0) msg_panic("smtp_xfer: bad recipient count: %d", SMTP_RCPT_LEFT(state)); if (SMTP_RCPT_ISMARKED(request->rcpt_list.info)) msg_panic("smtp_xfer: bad recipient status: %d", request->rcpt_list.info->u.status); /* * See if we should even try to send this message at all. This code sits * here rather than in the EHLO processing code, because of SMTP * connection caching. */ if (session->size_limit > 0 && session->size_limit < request->data_size) { smtp_mesg_fail(state, DSN_BY_LOCAL_MTA, SMTP_RESP_FAKE(&fake, "5.3.4"), "message size %lu exceeds size limit %.0f of server %s", request->data_size, (double) session->size_limit, session->namaddr); /* Redundant. We abort this delivery attempt. */ state->misc_flags |= SMTP_MISC_FLAG_COMPLETE_SESSION; return (0); } /* * Use XFORWARD to forward the origin of this email message across an * SMTP-based content filter. Send client attribute information only if * it exists (i.e. remote submission). Local submissions have no client * attributes; the mail will appear to originate from the content filter * which is acceptable. */ send_name_addr = var_smtp_send_xforward && (((session->features & SMTP_FEATURE_XFORWARD_NAME) && CAN_FORWARD_CLIENT_NAME(request->client_name)) || ((session->features & SMTP_FEATURE_XFORWARD_ADDR) && CAN_FORWARD_CLIENT_ADDR(request->client_addr)) || ((session->features & SMTP_FEATURE_XFORWARD_PORT) && CAN_FORWARD_CLIENT_PORT(request->client_port))); session->send_proto_helo = var_smtp_send_xforward && (((session->features & SMTP_FEATURE_XFORWARD_PROTO) && CAN_FORWARD_PROTO_NAME(request->client_proto)) || ((session->features & SMTP_FEATURE_XFORWARD_HELO) && CAN_FORWARD_HELO_NAME(request->client_helo)) || ((session->features & SMTP_FEATURE_XFORWARD_IDENT) && CAN_FORWARD_IDENT_NAME(request->log_ident)) || ((session->features & SMTP_FEATURE_XFORWARD_DOMAIN) && CAN_FORWARD_RWR_CONTEXT(request->rewrite_context))); if (send_name_addr) recv_state = send_state = SMTP_STATE_XFORWARD_NAME_ADDR; else if (session->send_proto_helo) recv_state = send_state = SMTP_STATE_XFORWARD_PROTO_HELO; else recv_state = send_state = SMTP_STATE_MAIL; /* * Remember this session's "normal completion", even if the server 4xx-ed * some or all recipients. Connection or handshake errors with a later MX * host should not cause this destination be marked as unreachable. */ result = smtp_loop(state, send_state, recv_state); if (result == 0 /* Just in case */ && vstream_ferror(session->stream) == 0 && vstream_feof(session->stream) == 0) state->misc_flags |= SMTP_MISC_FLAG_COMPLETE_SESSION; return (result); } /* smtp_rset - send a lone RSET command */ int smtp_rset(SMTP_STATE *state) { /* * This works because SMTP_STATE_RSET is a dedicated sender/recipient * entry state, with SMTP_STATE_LAST as next sender/recipient state. */ return (smtp_loop(state, SMTP_STATE_RSET, SMTP_STATE_RSET)); } /* smtp_quit - send a lone QUIT command */ int smtp_quit(SMTP_STATE *state) { /* * This works because SMTP_STATE_QUIT is the last state with a sender * action, with SMTP_STATE_LAST as the next sender/recipient state. */ return (smtp_loop(state, SMTP_STATE_QUIT, var_skip_quit_resp ? SMTP_STATE_LAST : SMTP_STATE_QUIT)); }