/*++ /* NAME /* smtp_proto 3 /* SUMMARY /* client SMTP 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 /* 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 no response, or negative response, it /* turns off connection caching. /* /* 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. /* /* 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 /* /* 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 #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 /* Global library. */ #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", }; /* smtp_helo - perform initial handshake with SMTP server */ int smtp_helo(SMTP_STATE *state, NOCLOBBER int misc_flags) { char *myname = "smtp_helo"; SMTP_SESSION *session = state->session; DELIVER_REQUEST *request = state->request; SMTP_RESP *resp; int except; char *lines; char *words; char *word; int n; static NAME_CODE xforward_features[] = { XFORWARD_NAME, SMTP_FEATURE_XFORWARD_NAME, XFORWARD_ADDR, SMTP_FEATURE_XFORWARD_ADDR, XFORWARD_PROTO, SMTP_FEATURE_XFORWARD_PROTO, XFORWARD_HELO, SMTP_FEATURE_XFORWARD_HELO, XFORWARD_DOMAIN, SMTP_FEATURE_XFORWARD_DOMAIN, 0, 0, }; SOCKOPT_SIZE optlen; const char *ehlo_words; int discard_mask; #ifdef USE_TLS static int smtp_start_tls(SMTP_STATE *, int); int saved_features = session->features; #endif /* * If not recursing after STARTTLS, examine the server greeting banner * and decide if we are going to send EHLO as the next command. */ if ((misc_flags & SMTP_MISC_FLAG_IN_STARTTLS) == 0) { /* * Prepare for disaster. */ smtp_timeout_setup(state->session->stream, var_smtp_helo_tmout); if ((except = vstream_setjmp(state->session->stream)) != 0) return (smtp_stream_except(state, except, "receiving the initial SMTP greeting")); /* * Read and parse the server's SMTP greeting banner. */ switch ((resp = smtp_chat_resp(session))->code / 100) { case 2: break; case 5: if (var_smtp_skip_5xx_greeting) resp->code = 400; default: return (smtp_site_fail(state, resp->code, "host %s refused to talk to me: %s", session->namaddr, translit(resp->str, "\n", " "))); } /* * 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) session->features |= SMTP_FEATURE_MAYBEPIX; /* * 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 (misc_flags & SMTP_MISC_FLAG_LOOP_DETECT) msg_warn("host %s greeted me with my own hostname %s", session->namaddr, var_myhostname); } else if (strcasecmp(word, "ESMTP") == 0) session->features |= SMTP_FEATURE_ESMTP; } if (var_smtp_always_ehlo && (session->features & SMTP_FEATURE_MAYBEPIX) == 0) session->features |= SMTP_FEATURE_ESMTP; if (var_smtp_never_ehlo || (session->features & SMTP_FEATURE_MAYBEPIX) != 0) 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 (session->features & SMTP_FEATURE_ESMTP) { smtp_chat_cmd(session, "EHLO %s", var_smtp_helo_name); if ((resp = smtp_chat_resp(session))->code / 100 != 2) session->features &= ~SMTP_FEATURE_ESMTP; } if ((session->features & SMTP_FEATURE_ESMTP) == 0) { smtp_chat_cmd(session, "HELO %s", var_smtp_helo_name); if ((resp = smtp_chat_resp(session))->code / 100 != 2) return (smtp_site_fail(state, resp->code, "host %s refused to talk to me: %s", session->namaddr, translit(resp->str, "\n", " "))); return (0); } /* * 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, state->session->addr, 0)) == 0) ehlo_words = var_smtp_ehlo_dis_words; 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; while ((words = mystrtok(&lines, "\n")) != 0) { if (mystrtok(&words, "- ") && (word = mystrtok(&words, " \t=")) != 0) { 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->namaddr); 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, var_myhostname) == 0) { if (misc_flags & SMTP_MISC_FLAG_LOOP_DETECT) { msg_warn("host %s replied to HELO/EHLO with my own hostname %s", session->namaddr, var_myhostname); return (smtp_site_fail(state, (session->features & SMTP_FEATURE_BEST_MX) ? 550 : 450, "mail for %s loops back to myself", request->nexthop)); } } } } if (msg_verbose) msg_info("server features: 0x%x size %.0f", session->features, (double) session->size_limit); /* * 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 not available 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. */ if (session->features & SMTP_FEATURE_PIPELINING) { optlen = sizeof(session->sndbufsize); if (getsockopt(vstream_fileno(session->stream), SOL_SOCKET, SO_SNDBUF, (char *) &session->sndbufsize, &optlen) < 0) msg_fatal("%s: getsockopt: %m", myname); if (session->sndbufsize > VSTREAM_BUFSIZE) session->sndbufsize = VSTREAM_BUFSIZE; if (session->sndbufsize == 0) { session->sndbufsize = VSTREAM_BUFSIZE; if (setsockopt(vstream_fileno(session->stream), SOL_SOCKET, SO_SNDBUF, (char *) &session->sndbufsize, optlen) < 0) msg_fatal("%s: setsockopt: %m", myname); } if (msg_verbose) msg_info("Using ESMTP PIPELINING, TCP send buffer size is %d", session->sndbufsize); } else { session->sndbufsize = 0; } #ifdef USE_TLS /* * Skip this part if we already sent STARTTLS. */ if ((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) && (!(session->tls_enforce_tls || session->tls_use_tls))) msg_info("Host offered STARTTLS: [%s]", session->host); /* * Decide whether or not to send STARTTLS. */ if ((session->features & SMTP_FEATURE_STARTTLS) != 0 && smtp_tls_ctx != 0 && (session->tls_use_tls || session->tls_enforce_tls)) { /* * Prepare for disaster. */ smtp_timeout_setup(state->session->stream, var_smtp_starttls_tmout); 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. * * XXX Reset the SASL mechanism list to avoid spurious warnings. We * need a routine to reset the list instead of groping data here. * * XXX Should not there be an smtp_sasl_tls_security_options feature * to allow different mechanisms across TLS tunnels than across * plain-text connections? */ smtp_chat_cmd(session, "STARTTLS"); if ((resp = smtp_chat_resp(session))->code / 100 == 2) { #ifdef USE_SASL_AUTH if (session->sasl_mechanism_list) { myfree(session->sasl_mechanism_list); session->sasl_mechanism_list = 0; } #endif session->features = saved_features; misc_flags |= SMTP_MISC_FLAG_IN_STARTTLS; return (smtp_start_tls(state, misc_flags)); } /* * 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 (session->tls_enforce_tls) return (smtp_site_fail(state, resp->code, "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 (session->tls_enforce_tls) { if (!(session->features & SMTP_FEATURE_STARTTLS)) { return (smtp_site_fail(state, 450, "TLS is required, but was not offered by host %s", session->namaddr)); } else if (smtp_tls_ctx == 0) { return (smtp_site_fail(state, 450, "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, 450, "TLS is required, but not available")); } } } #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, int misc_flags) { SMTP_SESSION *session = state->session; /* * Turn off SMTP connection caching. When the TLS handshake succeeds, we * can't reuse the SMTP connection. Reason: we can't turn off TLS in one * process, save the connection to the 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. */ session->reuse_count = 0; /* * The actual TLS handshake may succeed, but tls_client_start() may fail * anyway, for example because server certificate verification is * required but failed, or because enforce_peername is required but the * names listed in the server certificate don't match the peer hostname. * * 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. * * XXX Other tests for specific combinations of required/failed behavior * follow below AFTER the tls_client_start() call. These tests should be * done inside tls_client_start() or its call-backs, to keep the SMTP * client code clean (as it is in the SMTP server). */ session->tls_context = tls_client_start(smtp_tls_ctx, session->stream, var_smtp_starttls_tmout, session->tls_enforce_peername, session->host, &(session->tls_info)); if (session->tls_context == 0) return (smtp_site_fail(state, 450, "Cannot start TLS: handshake failure")); /* * Give up when TLS is required, we can parse the server certificate's * CommonName field, but server certificate verification failed. * * In enforce_peername state, the handshake would already have been * terminated by the certificate verification call-back routine, so the * check here is for logging only. * * XXX It appears that the CommonName field is used as an indicator that a * server certificate is available. If the latter is what we want, then * we should test for that instead. */ if (session->tls_info.peer_CN != NULL) { if (!session->tls_info.peer_verified) { msg_info("Server certificate could not be verified"); if (session->tls_enforce_tls) { tls_client_stop(smtp_tls_ctx, session->stream, var_smtp_starttls_tmout, 1, &(session->tls_info)); return (smtp_site_fail(state, 450, "TLS failure: Cannot verify server certificate")); } } } /* * Give up when TLS is required but no server certificate is available * (or we could not parse the certificate's CommonName) field. * * XXX The test below is not accurate: the server hostname verification may * use the dNSNames instead of the CommonName. We really should be * testing if a certificate is available. */ else { if (session->tls_enforce_tls) { tls_client_stop(smtp_tls_ctx, session->stream, var_smtp_starttls_tmout, 1, &(session->tls_info)); return (smtp_site_fail(state, 450, "TLS failure: Cannot verify server hostname")); } } /* * At this point we have to re-negotiate the "EHLO" to reget the * feature-list. */ return (smtp_helo(state, misc_flags)); } #endif /* smtp_text_out - output one header/body record */ static void smtp_text_out(void *context, int rec_type, const char *text, int len, off_t unused_offset) { SMTP_STATE *state = (SMTP_STATE *) context; SMTP_SESSION *session = state->session; int 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 (var_smtp_line_limit > 0 && 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; } } 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, 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, 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; /* * 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 (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_sprintf(buf, "%s: ", header_info->name); tok822_externalize(buf, tree, TOK822_STR_HEAD); } myfree((char *) 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_loop - exercise the SMTP protocol engine */ static int smtp_loop(SMTP_STATE *state, NOCLOBBER int send_state, NOCLOBBER int recv_state) { char *myname = "smtp_loop"; DELIVER_REQUEST *request = state->request; SMTP_SESSION *session = state->session; SMTP_RESP *resp; RECIPIENT *rcpt; VSTRING *next_command = vstring_alloc(100); NOCLOBBER int next_state; NOCLOBBER int next_rcpt; NOCLOBBER int send_rcpt; NOCLOBBER int recv_rcpt; NOCLOBBER int nrcpt; int except; int rec_type; NOCLOBBER int prev_type = 0; NOCLOBBER int sndbuffree; NOCLOBBER int mail_from_rejected; NOCLOBBER int downgrading; int mime_errs; /* * Macros for readability. */ #define REWRITE_ADDRESS(dst, src) do { \ vstring_strcpy(dst, src); \ if (*(src) && smtp_generic_maps) \ smtp_map11_internal(dst, smtp_generic_maps, \ smtp_ext_prop_mask & EXT_PROP_GENERIC); \ } while (0) #define QUOTE_ADDRESS(dst, src) do { \ if (*(src) && var_smtp_quote_821_env) { \ quote_821_local(dst, src); \ } else { \ vstring_strcpy(dst, src); \ } \ } while (0) #define RETURN(x) do { \ vstring_free(next_command); \ 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 THIS_SESSION_IS_CACHED \ (session->reuse_count > 0) #define DONT_CACHE_THIS_SESSION \ (session->reuse_count = 0) #define CANT_RSET_THIS_SESSION \ (session->features |= SMTP_FEATURE_RSET_REJECTED) /* * Sanity check. We don't want smtp_chat() to inadvertently flush the * output buffer. That means someone broke pipelining support. */ if (session->sndbufsize > VSTREAM_BUFSIZE) msg_panic("bad sndbufsize %d > VSTREAM_BUFSIZE %d", session->sndbufsize, VSTREAM_BUFSIZE); /* * Miscellaneous initialization. Some of this might be done in * smtp_xfer() but that just complicates interfaces and data structures. */ sndbuffree = session->sndbufsize; /* * 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 = 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_timeout_setup(session->stream, *xfer_timeouts[send_state]); 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. */ case SMTP_STATE_XFORWARD_NAME_ADDR: vstring_strcpy(next_command, XFORWARD_CMD); if (session->features & SMTP_FEATURE_XFORWARD_NAME) vstring_sprintf_append(next_command, " %s=%s", XFORWARD_NAME, DEL_REQ_ATTR_AVAIL(request->client_name) ? request->client_name : XFORWARD_UNAVAILABLE); if (session->features & SMTP_FEATURE_XFORWARD_ADDR) vstring_sprintf_append(next_command, " %s=%s", XFORWARD_ADDR, DEL_REQ_ATTR_AVAIL(request->client_addr) ? request->client_addr : XFORWARD_UNAVAILABLE); 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) vstring_sprintf_append(next_command, " %s=%s", XFORWARD_PROTO, DEL_REQ_ATTR_AVAIL(request->client_proto) ? request->client_proto : XFORWARD_UNAVAILABLE); if (session->features & SMTP_FEATURE_XFORWARD_HELO) vstring_sprintf_append(next_command, " %s=%s", XFORWARD_HELO, DEL_REQ_ATTR_AVAIL(request->client_helo) ? request->client_helo : XFORWARD_UNAVAILABLE); if (session->features & SMTP_FEATURE_XFORWARD_DOMAIN) vstring_sprintf_append(next_command, " %s=%s", XFORWARD_DOMAIN, DEL_REQ_ATTR_AVAIL(request->rewrite_context) == 0 ? XFORWARD_UNAVAILABLE : 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: REWRITE_ADDRESS(session->scratch2, request->sender); QUOTE_ADDRESS(session->scratch, vstring_str(session->scratch2)); vstring_sprintf(next_command, "MAIL FROM:<%s>", vstring_str(session->scratch)); if (session->features & SMTP_FEATURE_SIZE) /* RFC 1870 */ 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); } /* * We authenticate the local MTA only, but not the sender. */ #ifdef USE_SASL_AUTH if (var_smtp_sasl_enable && (session->features & SMTP_FEATURE_AUTH)) vstring_strcat(next_command, " AUTH=<>"); #endif 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; REWRITE_ADDRESS(session->scratch2, rcpt->address); QUOTE_ADDRESS(session->scratch, vstring_str(session->scratch2)); vstring_sprintf(next_command, "RCPT TO:<%s>", vstring_str(session->scratch)); if ((next_rcpt = send_rcpt + 1) == SMTP_RCPT_LEFT(state)) next_state = DEL_REQ_TRACE_ONLY(request->flags) ? 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 before we have seen the DATA response. */ case SMTP_STATE_DOT: vstring_strcpy(next_command, "."); 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. */ case SMTP_STATE_ABORT: vstring_strcpy(next_command, "RSET"); 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. */ case SMTP_STATE_QUIT: vstring_strcpy(next_command, "QUIT"); next_state = SMTP_STATE_LAST; 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. */ if (SENDER_IN_WAIT_STATE || (SENDER_IS_AHEAD && (VSTRING_LEN(next_command) + 2 > sndbuffree || 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. */ smtp_timeout_setup(session->stream, *xfer_timeouts[recv_state]); 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->namaddr, 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->namaddr, 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, resp->code, "host %s said: %s (in reply to %s)", session->namaddr, translit(resp->str, "\n", " "), xfer_request[SMTP_STATE_MAIL]); mail_from_rejected = 1; } 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; #endif rcpt = request->rcpt_list.info + recv_rcpt; if (resp->code / 100 == 2) { ++nrcpt; /* If trace-only, mark the recipient done. */ if (DEL_REQ_TRACE_ONLY(request->flags)) smtp_rcpt_done(state, resp->str, rcpt); } else { smtp_rcpt_fail(state, resp->code, rcpt, "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_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: if (resp->code / 100 != 3) { if (nrcpt > 0) smtp_mesg_fail(state, resp->code, "host %s said: %s (in reply to %s)", session->namaddr, translit(resp->str, "\n", " "), xfer_request[SMTP_STATE_DATA]); nrcpt = -1; } recv_state = SMTP_STATE_DOT; 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: if (nrcpt > 0) { if (resp->code / 100 != 2) { smtp_mesg_fail(state, resp->code, "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)) smtp_rcpt_done(state, resp->str, rcpt); } } } recv_state = (var_skip_quit_resp || THIS_SESSION_IS_CACHED ? SMTP_STATE_LAST : SMTP_STATE_QUIT); break; /* * Receive the RSET response, and disable session caching * in case of failure. */ case SMTP_STATE_ABORT: if (resp->code / 100 != 2) DONT_CACHE_THIS_SESSION; 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, * so that the entire TCP send buffer becomes available again. */ sndbuffree = session->sndbufsize; /* * 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"); 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) { downgrading = (var_disable_mime_oconv == 0 && (session->features & SMTP_FEATURE_8BITMIME) == 0 && strcmp(request->encoding, MAIL_ATTR_ENC_7BIT) != 0); if (downgrading || smtp_generic_maps) session->mime_state = mime_state_alloc(MIME_OPT_DOWNGRADE | MIME_OPT_REPORT_NESTING, smtp_generic_maps ? smtp_header_rewrite : smtp_header_out, (MIME_STATE_ANY_END) 0, smtp_text_out, (MIME_STATE_ANY_END) 0, (MIME_STATE_ERR_PRINT) 0, (void *) state); state->space_left = var_smtp_line_limit; smtp_timeout_setup(session->stream, var_smtp_data1_tmout); if ((except = vstream_setjmp(session->stream)) != 0) RETURN(smtp_stream_except(state, except, "sending message body")); if (vstream_fseek(state->src, request->data_offset, SEEK_SET) < 0) msg_fatal("seek queue file: %m"); while ((rec_type = rec_get(state->src, session->scratch, 0)) > 0) { if (rec_type != REC_TYPE_NORM && rec_type != REC_TYPE_CONT) break; if (session->mime_state == 0) { smtp_text_out((void *) state, rec_type, vstring_str(session->scratch), VSTRING_LEN(session->scratch), (off_t) 0); } else { mime_errs = mime_state_update(session->mime_state, rec_type, vstring_str(session->scratch), VSTRING_LEN(session->scratch)); if (mime_errs) { smtp_mesg_fail(state, 554, "MIME 7-bit conversion failed: %s", mime_state_error(mime_errs)); 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. */ mime_errs = mime_state_update(session->mime_state, rec_type, "", 0); if (mime_errs) { smtp_mesg_fail(state, 554, "MIME 7-bit conversion failed: %s", mime_state_error(mime_errs)); RETURN(0); } } else if (prev_type == REC_TYPE_CONT) /* missing newline */ smtp_fputs("", 0, session->stream); if ((session->features & SMTP_FEATURE_MAYBEPIX) != 0 && request->arrival_time < vstream_ftime(session->stream) - var_smtp_pix_thresh) { msg_info("%s: enabling PIX . workaround for %s", request->queue_id, session->namaddr); 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); RETURN(mark_corrupt(state->src)); } } /* * Copy the next command to the buffer and update the sender state. */ if (sndbuffree > 0) sndbuffree -= VSTRING_LEN(next_command) + 2; smtp_chat_cmd(session, "%s", vstring_str(next_command)); 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; int send_state; int recv_state; int send_name_addr; /* * 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->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, 552, "message size %lu exceeds size limit %.0f of server %s", request->data_size, (double) session->size_limit, session->namaddr); return (0); } /* * Use the XFORWARD command to forward client attributes only when a * minimal amount of information is available. */ send_name_addr = var_smtp_send_xforward && (((session->features & SMTP_FEATURE_XFORWARD_NAME) && DEL_REQ_ATTR_AVAIL(request->client_name)) || ((session->features & SMTP_FEATURE_XFORWARD_ADDR) && DEL_REQ_ATTR_AVAIL(request->client_addr))); session->send_proto_helo = var_smtp_send_xforward && (((session->features & SMTP_FEATURE_XFORWARD_PROTO) && DEL_REQ_ATTR_AVAIL(request->client_proto)) || ((session->features & SMTP_FEATURE_XFORWARD_HELO) && DEL_REQ_ATTR_AVAIL(request->client_helo))); 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; return (smtp_loop(state, send_state, recv_state)); } /* 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)); }