/*++
/* 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
/*
/* Wietse Venema
/* porcupine.org
/*
/* 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 <sys_defs.h>
#include <sys/stat.h>
#include <sys/socket.h> /* shutdown(2) */
#include <netinet/in.h> /* ntohs() */
#include <string.h>
#include <unistd.h>
#include <stdlib.h> /* 44BSD stdarg.h uses abort() */
#include <stdarg.h>
#include <time.h>
#ifdef STRCASECMP_IN_STRINGS_H
#include <strings.h>
#endif
/* Utility library. */
#include <msg.h>
#include <vstring.h>
#include <vstream.h>
#include <vstring_vstream.h>
#include <stringops.h>
#include <mymalloc.h>
#include <iostuff.h>
#include <split_at.h>
#include <name_code.h>
#include <name_mask.h>
/* Global library. */
#include <mail_params.h>
#include <smtp_stream.h>
#include <mail_queue.h>
#include <recipient_list.h>
#include <deliver_request.h>
#include <defer.h>
#include <bounce.h>
#include <record.h>
#include <rec_type.h>
#include <off_cvt.h>
#include <mark_corrupt.h>
#include <quote_822_local.h>
#include <mail_proto.h>
#include <mime_state.h>
#include <ehlo_mask.h>
#include <maps.h>
#include <tok822.h>
#include <mail_addr_map.h>
#include <ext_prop.h>
#include <namadr_list.h>
#include <lex_822.h>
#include <dsn_mask.h>
#include <xtext.h>
#include <uxtext.h>
#include <smtputf8.h>
#if defined (USE_TLS) && defined (USE_TLSRPT)
#include <tlsrpt_wrapper.h>
#endif
/* 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 " . < CR > < LF > " 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;
}
#ifdef USE_TLSRPT
if (state->tlsrpt
&& (state->misc_flags & SMTP_MISC_FLAG_IN_STARTTLS) == 0 )
smtp_tlsrpt_set_ehlo_resp(state, resp->str);
#endif
}
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" , " " )));
#ifdef USE_TLSRPT
if (state->tlsrpt)
trw_set_ehlo_resp(state->tlsrpt, resp->str);
#endif
}
} 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;
} else if (strcasecmp(word, "REQUIRETLS" ) == 0 ) {
if ((discard_mask & EHLO_MASK_REQTLS) == 0
&& (state->misc_flags & SMTP_MISC_FLAG_IN_STARTTLS))
session->features |= SMTP_FEATURE_REQTLS;
#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);
/*
* Require that the server supports SMTPUTF8 when delivery requires
* SMTPUTF8 .
*
* Fix 20140706 : moved this before negotiating TLS , AUTH , and so on .
*
* Fix 20250824 : try multiple servers before giving up .
*
* Fix 20250911 : do not cache this session because it does not satisfy the
* requirement expressed in the cache storage key .
*/
if ((session->features & SMTP_FEATURE_SMTPUTF8) == 0
&& DELIVERY_REQUIRES_SMTPUTF8(request))
return (smtp_misc_fail(state, SMTP_MISC_FAIL_DONT_CACHE
| SMTP_MISC_FAIL_SOFT_NON_FINAL,
DSN_BY_LOCAL_MTA,
SMTP_RESP_FAKE(&fake, "5.6.7" ),
"message requires SMTPUTF8, but no "
"server was found that supports "
"SMTPUTF8. The last attempted server "
"was %s" , session->namaddr));
/*
* Fix 20140706 : don ' t do silly things when the remote server announces
* SMTPUTF8 but not 8 BITMIME 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;
}
/*
* Require that the server announces REQUIRETLS when the sender requested
* REQUIRETLS . Return the message as undeliverable only when there are no
* more alternative MX hosts . With opportunistic REQUIRETLS , only log
* that the server does not offer REQUIRETLS .
*/
#ifdef USE_TLS
if (state->reqtls_level > SMTP_REQTLS_POLICY_ACT_DISABLE
&& (state->misc_flags & SMTP_MISC_FLAG_IN_STARTTLS) != 0 ) {
if ((session->features & SMTP_FEATURE_REQTLS) != 0 ) {
if (state->tls_stats)
smtp_tls_stat_decide_reqtls(state->tls_stats,
TLS_CERT_IS_MATCHED(session->tls_context) ?
SMTP_TLS_STAT_NAME_REQTLS :
SMTP_TLS_STAT_NAME_NOCMATCH,
POL_STAT_COMPLIANT);
} else if (state->reqtls_level == SMTP_REQTLS_POLICY_ACT_ENFORCE) {
if (state->tls_stats)
smtp_tls_stat_decide_reqtls(state->tls_stats,
SMTP_TLS_STAT_NAME_NONE,
POL_STAT_VIOLATION);
return (smtp_misc_fail(state, SMTP_MISC_FAIL_DONT_CACHE
| SMTP_MISC_FAIL_SOFT_NON_FINAL,
DSN_BY_LOCAL_MTA,
SMTP_RESP_FAKE(&fake, "5.7.30" ),
"Sender requested REQUIRETLS, "
"but no server was found that "
"supports REQUIRETLS. The last "
"attempted server was %s" ,
session->namaddr));
} else {
if (state->tls_stats)
smtp_tls_stat_decide_reqtls(state->tls_stats,
SMTP_TLS_STAT_NAME_NONE,
POL_STAT_COMPLIANT);
msg_info("%s: Sender requested REQUIRETLS, but REQUIRETLS "
"support was not offered by host %s" ,
request->queue_id, session->namaddr);
}
}
/*
* TODO ( wietse ) Maybe log servers that announce REQUIRETLS and whether
* the connection is authenticated ?
*/
#endif
/*
* 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);
}
#ifdef USE_TLSRPT
if (state->tlsrpt)
trw_report_failure(state->tlsrpt,
TLSRPT_STARTTLS_NOT_SUPPORTED,
/* additional_info= */ (char *) 0,
/* failure_reason= */ (char *) 0);
#endif
/*
* Give up if we must use TLS but the server rejects STARTTLS
* although support for it was announced in the EHLO response .
*
* When the sender requested REQUIRETLS , and the REQUIRETLS policy
* requires TLS , return the message as undeliverable only when
* there are no more alternative MX hosts .
*/
session->features &= ~SMTP_FEATURE_STARTTLS;
if (TLS_REQUIRED_BY_SECURITY_LEVEL(state->tls->level)
|| TLS_REQUIRED_BY_REQTLS_POLICY(state->reqtls_level)) {
/* Before returning, decide all relevant policy status info. */
if (TLS_REQUIRED_BY_REQTLS_POLICY(state->reqtls_level)) {
if (state->tls_stats)
smtp_tls_stat_decide_reqtls(state->tls_stats,
SMTP_TLS_STAT_NAME_NOSTTLS,
POL_STAT_VIOLATION);
}
if (TLS_REQUIRED_BY_SECURITY_LEVEL(state->tls->level)) {
if (state->tls_stats)
smtp_tls_stat_decide_sec_level(state->tls_stats,
state->tls->level,
POL_STAT_VIOLATION);
}
/* Then, REQUIRETLS failure must take precedence over other. */
if (TLS_REQUIRED_BY_REQTLS_POLICY(state->reqtls_level)) {
return (smtp_misc_fail(state, SMTP_MISC_FAIL_DONT_CACHE
| SMTP_MISC_FAIL_SOFT_NON_FINAL,
DSN_BY_LOCAL_MTA,
SMTP_RESP_FAKE(&fake, "5.7.10" ),
"Sender requested REQUIRETLS, "
"but host %s refused to "
"start TLS: %s" , session->namaddr,
translit(resp->str, "\n" , " " )));
}
/* TLS_REQUIRED_BY_SECURITY_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 .
*
* When the sender requested REQUIRETLS , and the REQUIRETLS policy
* requires TLS , return the message as undeliverable only when there
* are no more alternative MX hosts .
*/
if (TLS_REQUIRED_BY_SECURITY_LEVEL(state->tls->level)
|| TLS_REQUIRED_BY_REQTLS_POLICY(state->reqtls_level)) {
if (!(session->features & SMTP_FEATURE_STARTTLS)) {
#ifdef USE_TLSRPT
if (state->tlsrpt)
trw_report_failure(state->tlsrpt,
TLSRPT_STARTTLS_NOT_SUPPORTED,
/* additional_info= */ (char *) 0,
/* failure_reason= */ (char *) 0);
#endif
/* Before returning, decide all relevant policy status info. */
if (TLS_REQUIRED_BY_REQTLS_POLICY(state->reqtls_level)) {
if (state->tls_stats)
smtp_tls_stat_decide_reqtls(state->tls_stats,
SMTP_TLS_STAT_NAME_NOSTTLS,
POL_STAT_VIOLATION);
}
if (TLS_REQUIRED_BY_SECURITY_LEVEL(state->tls->level))
if (state->tls_stats)
smtp_tls_stat_decide_sec_level(state->tls_stats,
state->tls->level,
POL_STAT_VIOLATION);
/* Then, REQUIRETLS failure must take precedence over other. */
if (TLS_REQUIRED_BY_REQTLS_POLICY(state->reqtls_level))
return (smtp_misc_fail(state, SMTP_MISC_FAIL_DONT_CACHE
| SMTP_MISC_FAIL_SOFT_NON_FINAL,
DSN_BY_LOCAL_MTA,
SMTP_RESP_FAKE(&fake, "5.7.30" ),
"Sender requested REQUIRETLS, "
"but TLS service was not "
"offered by host %s" ,
session->namaddr));
/* TLS_REQUIRED_BY_SECURITY_LEVEL */
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" ));
}
}
/* Continue in plain-text mode. */
if (state->tls_stats) {
smtp_tls_stat_decide_sec_level(state->tls_stats, TLS_LEV_NONE,
POL_STAT_COMPLIANT);
if (state->reqtls_level > SMTP_REQTLS_POLICY_ACT_DISABLE)
smtp_tls_stat_decide_reqtls(state->tls_stats,
SMTP_TLS_STAT_NAME_NONE,
POL_STAT_COMPLIANT);
}
}
#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 . Add the current TLS security level to
* account for TLS level overrides based on message content or envelope
* metadata .
*/
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
| SMTP_KEY_FLAG_TLS_LEVEL);
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,
enable_rpk = state->tls->enable_rpk,
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,
#ifdef USE_TLSRPT
tlsrpt = state->tlsrpt,
#else
tlsrpt = 0 ,
#endif
ffail_type = 0 ,
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,
enable_rpk = state->tls->enable_rpk,
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,
#ifdef USE_TLSRPT
tlsrpt = state->tlsrpt,
#else
tlsrpt = 0 ,
#endif
ffail_type = state->tls->ext_policy_failure,
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;
/* Leave all TLS feature policy status info as 'undecided'. */
return (smtp_misc_fail(state, state->tls->level == TLS_LEV_MAY ?
SMTP_MISC_FAIL_NONE : SMTP_MISC_FAIL_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)) {
int trusted = TLS_CERT_IS_TRUSTED(session->tls_context);
#ifdef USE_TLSRPT
/*
* Don ' t create a TLSRPT ' failure ' event here , if the TLS engine
* already reported a more specific reason .
*/
if (state->tlsrpt && session->tls_context->rpt_reported == 0 ) {
(void ) trw_report_failure(state->tlsrpt, trusted ?
TLSRPT_CERTIFICATE_HOST_MISMATCH :
TLSRPT_CERTIFICATE_NOT_TRUSTED,
/* additional_info= */ (char *) 0,
/* failure_reason= */ (char *) 0);
}
#endif
/* Finalize TLS feature policy status info before giving up. */
if (state->tls_stats)
smtp_tls_stat_decide_sec_level(state->tls_stats,
session->tls_context->level,
POL_STAT_VIOLATION);
/*
* When the sender requested REQUIRETLS , and REQUIRETLS is
* enforced , return the message as undeliverable only when there
* are no more alternative MX hosts .
*/
if (state->reqtls_level == SMTP_REQTLS_POLICY_ACT_ENFORCE) {
if (state->tls_stats)
smtp_tls_stat_decide_reqtls(state->tls_stats,
SMTP_TLS_STAT_NAME_NOCMATCH,
POL_STAT_VIOLATION);
return (smtp_misc_fail(state, SMTP_MISC_FAIL_DONT_CACHE
| SMTP_MISC_FAIL_SOFT_NON_FINAL,
DSN_BY_LOCAL_MTA,
SMTP_RESP_FAKE(&fake, "5.7.10" ),
"Sender requested REQUIRETLS, "
"but no %s server certificate "
"was found. The last attempted "
"server was %s" , trusted ?
"matching" : "trusted" ,
session->namaddr));
} else if (state->reqtls_level > SMTP_REQTLS_POLICY_ACT_DISABLE) {
if (state->tls_stats)
smtp_tls_stat_decide_reqtls(state->tls_stats,
SMTP_TLS_STAT_NAME_NOCMATCH,
POL_STAT_COMPLIANT);
}
return (smtp_site_fail(state, DSN_BY_LOCAL_MTA,
SMTP_RESP_FAKE(&fake, "4.7.5" ),
"Server certificate not verified" ));
}
}
/*
* Create a TLSRPT ' success ' event only if the TLS engine has not created
* a TLSRPT event . For example , The TLS engine will create a TLSRPT
* ' failure ' event when the TLS handshake was be successful , but the
* security level was downgraded from opportunistic " dane " to
* unauthenticated " encrypt " .
*/
#ifdef USE_TLSRPT
if (state->tlsrpt && session->tls_context->rpt_reported == 0 )
(void ) trw_report_success(state->tlsrpt);
#endif
/*
* Report relaxed enforcement if the initial TLS level was degraded .
*/
if (state->tls_stats)
smtp_tls_stat_decide_sec_level(state->tls_stats,
session->tls_context->level,
POL_STAT_COMPLIANT);
/*
* 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 %.200s: %s" ,
state->request->queue_id, action, where, content, text);
} else {
msg_info("%s: %s: %s %.200s" ,
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 <CR><LF>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, "+=" );
}
/* Fix 20250825: limit content exposure in bounce. */
#ifdef USE_TLS
if (state->reqtls_level > SMTP_REQTLS_POLICY_ACT_DISABLE
&& (session->features & SMTP_FEATURE_REQTLS) == 0 )
vstring_sprintf_append(next_command, " RET=%s" ,
dsn_ret_str(DSN_RET_HDRS));
else
#endif
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->sendopts & SMTPUTF8_FLAG_REQUESTED) != 0 )
vstring_strcat(next_command, " SMTPUTF8" );
/*
* Request REQUIRETLS when the remote SMTP server supports
* REQUIRETLS and the sender requested REQUIRETLS .
*/
#ifdef USE_TLS
if (state->reqtls_level > SMTP_REQTLS_POLICY_ACT_DISABLE) {
if ((session->features & SMTP_FEATURE_REQTLS) != 0 ) {
vstring_strcat(next_command, " REQUIRETLS" );
} else if (state->reqtls_level
== SMTP_REQTLS_POLICY_ACT_ENFORCE) {
msg_panic("Can't happen: must enforce REQUIRETLS, but "
"host %s did not announce REQUIRETLS support" ,
session->namaddr);
}
}
#endif
/*
* 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->sendopts & SMTPUTF8_FLAG_ALL)
&& !allascii(vstring_str(session->scratch))
&& valid_utf8_stringz(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 < CR > < LF > . < CR > < LF > .
* 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 < CR > < LF > . < CR > < LF > . This is
* safe because once the receiver reads < CR > < LF > . < CR > < LF > , its TCP
* stack either has already received the QUIT < CR > < LF > , or else it
* acknowledges all bytes up to and including < CR > < LF > . < CR > < LF > ,
* making room in the sender ' s TCP stack for QUIT < CR > < LF > .
*/
#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 5 XX . 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
* 8 bit - > 7 bit 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 5 XX 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 4 xx - 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));
}
Messung V0.5 in Prozent C=84 H=90 G=86
¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.83Angebot
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-08-09)
¤
*Eine klare Vorstellung vom Zielzustand