Home | History | Annotate | Line # | Download | only in smtp
      1 /*	$NetBSD: smtp_trouble.c,v 1.4 2026/05/09 18:49:20 christos Exp $	*/
      2 
      3 /*++
      4 /* NAME
      5 /*	smtp_trouble 3
      6 /* SUMMARY
      7 /*	error handler policies
      8 /* SYNOPSIS
      9 /*	#include "smtp.h"
     10 /*
     11 /*	int	smtp_sess_fail(state)
     12 /*	SMTP_STATE *state;
     13 /*
     14 /*	int	smtp_site_fail(state, mta_name, resp, format, ...)
     15 /*	SMTP_STATE *state;
     16 /*	const char *mta_name;
     17 /*	SMTP_RESP *resp;
     18 /*	const char *format;
     19 /*
     20 /*	int	smtp_mesg_fail(state, mta_name, resp, format, ...)
     21 /*	SMTP_STATE *state;
     22 /*	const char *mta_name;
     23 /*	SMTP_RESP *resp;
     24 /*	const char *format;
     25 /*
     26 /*	void	smtp_rcpt_fail(state, recipient, mta_name, resp, format, ...)
     27 /*	SMTP_STATE *state;
     28 /*	RECIPIENT *recipient;
     29 /*	const char *mta_name;
     30 /*	SMTP_RESP *resp;
     31 /*	const char *format;
     32 /*
     33 /*	int	smtp_stream_except(state, exception, description)
     34 /*	SMTP_STATE *state;
     35 /*	int	exception;
     36 /*	const char *description;
     37 /* AUXILIARY FUNCTIONS
     38 /*	int	smtp_misc_fail(state, flags, mta_name, resp, format, ...)
     39 /*	SMTP_STATE *state;
     40 /*	int	flags;
     41 /*	const char *mta_name;
     42 /*	SMTP_RESP *resp;
     43 /*	const char *format;
     44 /* DESCRIPTION
     45 /*	This module handles all non-fatal errors that can happen while
     46 /*	attempting to deliver mail via SMTP, and implements the policy
     47 /*	of how to deal with the error. Depending on the nature of
     48 /*	the problem, delivery of a single message is deferred, delivery
     49 /*	of all messages to the same domain is deferred, or one or more
     50 /*	recipients are given up as non-deliverable and a bounce log is
     51 /*	updated. In any case, the recipient is marked as either KEEP
     52 /*	(try again with a backup host) or DROP (delete recipient from
     53 /*	delivery request).
     54 /*
     55 /*	In addition, when an unexpected response code is seen such
     56 /*	as 3xx where only 4xx or 5xx are expected, or any error code
     57 /*	that suggests a syntax error or something similar, the
     58 /*	protocol error flag is set so that the postmaster receives
     59 /*	a transcript of the session. No notification is generated for
     60 /*	what appear to be configuration errors - very likely, they
     61 /*	would suffer the same problem and just cause more trouble.
     62 /*
     63 /*	In case of a soft error, action depends on whether the error
     64 /*	qualifies for trying the request with other mail servers (log
     65 /*	an informational record only and try a backup server) or
     66 /*	whether this is the final server (log recipient delivery status
     67 /*	records and delete the recipient from the request).
     68 /*
     69 /*	smtp_sess_fail() takes a pre-formatted error report after
     70 /*	failure to complete some protocol handshake.  The policy is
     71 /*	as with smtp_site_fail().
     72 /*
     73 /*	smtp_site_fail() handles the case where the program fails to
     74 /*	complete the initial handshake: the server is not reachable,
     75 /*	is not running, does not want talk to us, or we talk to ourselves.
     76 /*	The \fIcode\fR gives an error status code; the \fIformat\fR
     77 /*	argument gives a textual description.
     78 /*	The policy is: soft error, non-final server: log an informational
     79 /*	record why the host is being skipped; soft error, final server:
     80 /*	defer delivery of all remaining recipients and mark the destination
     81 /*	as problematic; hard error: bounce all remaining recipients.
     82 /*	The session is marked as "do not cache".
     83 /*	The result is non-zero.
     84 /*
     85 /*	smtp_mesg_fail() handles the case where the smtp server
     86 /*	does not accept the sender address or the message data,
     87 /*	or when the local MTA is unable to convert the message data.
     88 /*	The policy is: soft error, non-final server: log an informational
     89 /*	record why the host is being skipped; soft error, final server:
     90 /*	defer delivery of all remaining recipients; hard error: bounce all
     91 /*	remaining recipients.
     92 /*	The result is non-zero.
     93 /*
     94 /*	smtp_misc_fail() provides a more detailed interface than
     95 /*	smtp_site_fail() and smtp_mesg_fail(), which are convenience
     96 /*	wrappers around smtp_misc_fail(). See the flags argument below.
     97 /*
     98 /*	smtp_rcpt_fail() handles the case where a recipient is not
     99 /*	accepted by the server for reasons other than that the server
    100 /*	recipient limit is reached.
    101 /*	The policy is: soft error, non-final server: log an informational
    102 /*	record why the recipient is being skipped; soft error, final server:
    103 /*	defer delivery of this recipient; hard error: bounce this
    104 /*	recipient.
    105 /*
    106 /*	smtp_stream_except() handles the exceptions generated by
    107 /*	the smtp_stream(3) module (i.e. timeouts and I/O errors).
    108 /*	The \fIexception\fR argument specifies the type of problem.
    109 /*	The \fIdescription\fR argument describes at what stage of
    110 /*	the SMTP dialog the problem happened.
    111 /*	The policy is: non-final server: log an informational record
    112 /*	with the reason why the host is being skipped; final server:
    113 /*	defer delivery of all remaining recipients.
    114 /*	Retry plaintext delivery after TLS post-handshake session
    115 /*	failure, provided that at least one recipient was not
    116 /*	deferred or rejected during the TLS phase, and that global
    117 /*	preconditions for plaintext fallback are met.
    118 /*	The session is marked as "do not cache".
    119 /*	The result is non-zero.
    120 /*
    121 /*	Arguments:
    122 /* .IP state
    123 /*	SMTP client state per delivery request.
    124 /* .IP flags
    125 /*	Either SMTP_MISC_FAIL_NONE or the bitwise OR of
    126 /* .RS
    127 /* .IP SMTP_MISC_FAIL_THROTTLE
    128 /*	Mark the destination as problematic.
    129 /* .IP SMTP_MISC_FAIL_SOFT_NON_FINAL
    130 /*	If the server was not the last one to try, treat a hard error
    131 /*	as a soft error.
    132 /* .IP SMTP_MISC_FAIL_DONT_CACHE
    133 /*	Do not save the connection to the cache. This flag is ignored
    134 /*	when SMTP_MISC_FAIL_THROTTLE is in effect.
    135 /* .IP resp
    136 /*	Server response including reply code and text.
    137 /* .IP recipient
    138 /*	Undeliverable recipient address information.
    139 /* .IP format
    140 /*	Human-readable description of why mail is not deliverable.
    141 /* DIAGNOSTICS
    142 /*	Panic: unknown exception code.
    143 /* SEE ALSO
    144 /*	smtp_proto(3) smtp high-level protocol
    145 /*	smtp_stream(3) smtp low-level protocol
    146 /*	defer(3) basic message defer interface
    147 /*	bounce(3) basic message bounce interface
    148 /* LICENSE
    149 /* .ad
    150 /* .fi
    151 /*	The Secure Mailer license must be distributed with this software.
    152 /* AUTHOR(S)
    153 /*	Wietse Venema
    154 /*	IBM T.J. Watson Research
    155 /*	P.O. Box 704
    156 /*	Yorktown Heights, NY 10598, USA
    157 /*
    158 /*	Wietse Venema
    159 /*	Google, Inc.
    160 /*	111 8th Avenue
    161 /*	New York, NY 10011, USA
    162 /*--*/
    163 
    164 /* System library. */
    165 
    166 #include <sys_defs.h>
    167 #include <stdlib.h>			/* 44BSD stdarg.h uses abort() */
    168 #include <stdarg.h>
    169 #include <string.h>
    170 
    171 /* Utility library. */
    172 
    173 #include <msg.h>
    174 #include <vstring.h>
    175 #include <stringops.h>
    176 
    177 /* Global library. */
    178 
    179 #include <smtp_stream.h>
    180 #include <deliver_request.h>
    181 #include <deliver_completed.h>
    182 #include <bounce.h>
    183 #include <defer.h>
    184 #include <mail_error.h>
    185 #include <dsn_buf.h>
    186 #include <dsn.h>
    187 #include <mail_params.h>
    188 
    189 /* Application-specific. */
    190 
    191 #include "smtp.h"
    192 #include "smtp_sasl.h"
    193 
    194 /* smtp_check_code - check response code */
    195 
    196 static void smtp_check_code(SMTP_SESSION *session, int code)
    197 {
    198 
    199     /*
    200      * The intention of this code is to alert the postmaster when the local
    201      * Postfix SMTP client screws up, protocol wise. RFC 821 says that x0z
    202      * replies "refer to syntax errors, syntactically correct commands that
    203      * don't fit any functional category, and unimplemented or superfluous
    204      * commands". Unfortunately, this also triggers postmaster notices when
    205      * remote servers screw up, protocol wise. This is becoming a common
    206      * problem now that response codes are configured manually as part of
    207      * anti-UCE systems, by people who aren't aware of RFC details.
    208      *
    209      * Fix 20190621: don't cache an SMTP session after an SMTP protocol error.
    210      * The protocol may be in a bad state. Disable caching here so that the
    211      * protocol engine will send QUIT.
    212      */
    213     if (code < 400 || code > 599
    214 	|| code == 555			/* RFC 1869, section 6.1. */
    215 	|| (code >= 500 && code < 510)) {
    216 	session->error_mask |= MAIL_ERROR_PROTOCOL;
    217 	DONT_CACHE_THIS_SESSION;
    218     }
    219 }
    220 
    221 /* smtp_bulk_fail - skip, defer or bounce recipients, maybe throttle queue */
    222 
    223 static int smtp_bulk_fail(SMTP_STATE *state, int flags)
    224 {
    225     DELIVER_REQUEST *request = state->request;
    226     SMTP_SESSION *session = state->session;
    227     DSN_BUF *why = state->why;
    228     RECIPIENT *rcpt;
    229     int     status;
    230     int     aggregate_status;
    231     int     soft_error = (STR(why->status)[0] == '4');
    232     int     soft_bounce_error = (STR(why->status)[0] == '5' && var_soft_bounce);
    233     int     throttle_queue = (flags & SMTP_MISC_FAIL_THROTTLE);
    234     int     dont_cache = (flags & SMTP_MISC_FAIL_DONT_CACHE);
    235     int     nrcpt;
    236 
    237     /*
    238      * Sanity check.
    239      */
    240     if ((flags & SMTP_MISC_FAIL_SOFT_NON_FINAL) != 0) {
    241 	if (soft_error) {
    242 	    msg_warn("smtp_bulk_fail: ignoring SMTP_MISC_FAIL_SOFT_NON_FINAL "
    243 		     "for a soft error");
    244 	} else {
    245 	    soft_error = (state->misc_flags & SMTP_MISC_FLAG_FINAL_SERVER) == 0;
    246 	}
    247     }
    248 
    249     /*
    250      * Don't defer the recipients just yet when this error qualifies them for
    251      * delivery to a backup server. Just log something informative to show
    252      * why we're skipping this host.
    253      */
    254     if ((soft_error || soft_bounce_error)
    255 	&& (state->misc_flags & SMTP_MISC_FLAG_FINAL_SERVER) == 0) {
    256 	msg_info("%s: %s", request->queue_id, STR(why->reason));
    257 	for (nrcpt = 0; nrcpt < SMTP_RCPT_LEFT(state); nrcpt++) {
    258 	    rcpt = request->rcpt_list.info + nrcpt;
    259 	    if (SMTP_RCPT_ISMARKED(rcpt))
    260 		continue;
    261 	    SMTP_RCPT_KEEP(state, rcpt);
    262 	}
    263     }
    264 
    265     /*
    266      * Defer or bounce all the remaining recipients, and delete them from the
    267      * delivery request. If a bounce fails, defer instead and do not qualify
    268      * the recipient for delivery to a backup server.
    269      */
    270     else {
    271 
    272 	/*
    273 	 * If we are still in the connection set-up phase, update the set-up
    274 	 * completion time here, otherwise the time spent in set-up latency
    275 	 * will be attributed as message transfer latency.
    276 	 *
    277 	 * All remaining recipients have failed at this point, so we update the
    278 	 * delivery completion time stamp so that multiple recipient status
    279 	 * records show the same delay values.
    280 	 */
    281 	if (request->msg_stats.conn_setup_done.tv_sec == 0) {
    282 	    GETTIMEOFDAY(&request->msg_stats.conn_setup_done);
    283 	    request->msg_stats.deliver_done =
    284 		request->msg_stats.conn_setup_done;
    285 	} else
    286 	    GETTIMEOFDAY(&request->msg_stats.deliver_done);
    287 
    288 	(void) DSN_FROM_DSN_BUF(why);
    289 	aggregate_status = 0;
    290 	for (nrcpt = 0; nrcpt < SMTP_RCPT_LEFT(state); nrcpt++) {
    291 	    rcpt = request->rcpt_list.info + nrcpt;
    292 	    if (SMTP_RCPT_ISMARKED(rcpt))
    293 		continue;
    294 	    status = (soft_error ? defer_append : bounce_append)
    295 		(DEL_REQ_TRACE_FLAGS(request->flags), request->queue_id,
    296 		 &request->msg_stats, rcpt,
    297 		 session ? session->namaddrport : "none", state->tls_stats,
    298 		 &why->dsn);
    299 	    if (status == 0)
    300 		deliver_completed(state->src, rcpt->offset);
    301 	    SMTP_RCPT_DROP(state, rcpt);
    302 	    aggregate_status |= status;
    303 	}
    304 	state->status |= aggregate_status;
    305 	if ((state->misc_flags & SMTP_MISC_FLAG_COMPLETE_SESSION) == 0
    306 	    && throttle_queue && aggregate_status
    307 	    && request->hop_status == 0)
    308 	    request->hop_status = DSN_COPY(&why->dsn);
    309     }
    310 
    311     /*
    312      * Don't cache this session. We can't talk to this server.
    313      */
    314     if (throttle_queue && session)
    315 	DONT_CACHE_THROTTLED_SESSION;
    316     else if (dont_cache && session)
    317 	DONT_CACHE_THIS_SESSION;
    318 
    319     return (-1);
    320 }
    321 
    322 /* smtp_sess_fail - skip site, defer or bounce all recipients */
    323 
    324 int     smtp_sess_fail(SMTP_STATE *state)
    325 {
    326 
    327     /*
    328      * We can't avoid copying copying lots of strings into VSTRING buffers,
    329      * because this error information is collected by a routine that
    330      * terminates BEFORE the error is reported.
    331      */
    332     return (smtp_bulk_fail(state, SMTP_MISC_FAIL_THROTTLE));
    333 }
    334 
    335 /* vsmtp_fill_dsn - fill in temporary DSN structure */
    336 
    337 static void vsmtp_fill_dsn(SMTP_STATE *state, const char *mta_name,
    338 			           const char *status, const char *reply,
    339 			           const char *format, va_list ap)
    340 {
    341     DSN_BUF *why = state->why;
    342 
    343     /*
    344      * We could avoid copying lots of strings into VSTRING buffers, because
    345      * this error information is given to us by a routine that terminates
    346      * AFTER the error is reported. However, this results in ugly kludges
    347      * when informal text needs to be formatted. So we maintain consistency
    348      * with other error reporting in the SMTP client even if we waste a few
    349      * cycles.
    350      *
    351      * Fix 20190621: don't cache an SMTP session after an SMTP protocol error.
    352      * The protocol may be in a bad state. Disable caching here so that the
    353      * protocol engine will send QUIT.
    354      */
    355     VSTRING_RESET(why->reason);
    356     if (mta_name && status && status[0] != '4' && status[0] != '5') {
    357 	SMTP_SESSION *session = state->session;
    358 
    359 	session->error_mask |= MAIL_ERROR_PROTOCOL;
    360 	DONT_CACHE_THIS_SESSION;
    361 	vstring_strcpy(why->reason, "Protocol error: ");
    362 	status = "5.5.0";
    363     }
    364     vstring_vsprintf_append(why->reason, format, ap);
    365     dsb_formal(why, status, DSB_DEF_ACTION,
    366 	       mta_name ? DSB_MTYPE_DNS : DSB_MTYPE_NONE, mta_name,
    367 	       reply ? DSB_DTYPE_SMTP : DSB_DTYPE_NONE, reply);
    368 }
    369 
    370 /* smtp_misc_fail - maybe throttle queue; skip/defer/bounce all recipients */
    371 
    372 int     smtp_misc_fail(SMTP_STATE *state, int flags, const char *mta_name,
    373 		               SMTP_RESP *resp, const char *format,...)
    374 {
    375     va_list ap;
    376 
    377     /*
    378      * Initialize.
    379      */
    380     va_start(ap, format);
    381     vsmtp_fill_dsn(state, mta_name, resp->dsn, resp->str, format, ap);
    382     va_end(ap);
    383 
    384     if (state->session && mta_name)
    385 	smtp_check_code(state->session, resp->code);
    386 
    387     /*
    388      * Skip, defer or bounce recipients, and throttle this queue.
    389      */
    390     return (smtp_bulk_fail(state, flags));
    391 }
    392 
    393 /* smtp_rcpt_fail - skip, defer, or bounce recipient */
    394 
    395 void    smtp_rcpt_fail(SMTP_STATE *state, RECIPIENT *rcpt, const char *mta_name,
    396 		               SMTP_RESP *resp, const char *format,...)
    397 {
    398     DELIVER_REQUEST *request = state->request;
    399     SMTP_SESSION *session = state->session;
    400     DSN_BUF *why = state->why;
    401     int     status;
    402     int     soft_error;
    403     int     soft_bounce_error;
    404     va_list ap;
    405 
    406     /*
    407      * Sanity check.
    408      */
    409     if (SMTP_RCPT_ISMARKED(rcpt))
    410 	msg_panic("smtp_rcpt_fail: recipient <%s> is marked", rcpt->address);
    411 
    412     /*
    413      * Initialize.
    414      */
    415     va_start(ap, format);
    416     vsmtp_fill_dsn(state, mta_name, resp->dsn, resp->str, format, ap);
    417     va_end(ap);
    418     soft_error = STR(why->status)[0] == '4';
    419     soft_bounce_error = (STR(why->status)[0] == '5' && var_soft_bounce);
    420 
    421     if (state->session && mta_name)
    422 	smtp_check_code(state->session, resp->code);
    423 
    424     /*
    425      * Don't defer this recipient record just yet when this error qualifies
    426      * for trying other mail servers. Just log something informative to show
    427      * why we're skipping this recipient now.
    428      */
    429     if ((soft_error || soft_bounce_error)
    430 	&& (state->misc_flags & SMTP_MISC_FLAG_FINAL_SERVER) == 0) {
    431 	msg_info("%s: %s", request->queue_id, STR(why->reason));
    432 	SMTP_RCPT_KEEP(state, rcpt);
    433     }
    434 
    435     /*
    436      * Defer or bounce this recipient, and delete from the delivery request.
    437      * If the bounce fails, defer instead and do not qualify the recipient
    438      * for delivery to a backup server.
    439      *
    440      * Note: we may still make an SMTP connection to deliver other recipients
    441      * that did qualify for delivery to a backup server.
    442      */
    443     else {
    444 	(void) DSN_FROM_DSN_BUF(state->why);
    445 	status = (soft_error ? defer_append : bounce_append)
    446 	    (DEL_REQ_TRACE_FLAGS(request->flags), request->queue_id,
    447 	     &request->msg_stats, rcpt,
    448 	     session ? session->namaddrport : "none", state->tls_stats,
    449 	     &why->dsn);
    450 	if (status == 0)
    451 	    deliver_completed(state->src, rcpt->offset);
    452 	SMTP_RCPT_DROP(state, rcpt);
    453 	state->status |= status;
    454     }
    455 }
    456 
    457 /* smtp_stream_except - defer domain after I/O problem */
    458 
    459 int     smtp_stream_except(SMTP_STATE *state, int code, const char *description)
    460 {
    461     SMTP_SESSION *session = state->session;
    462     DSN_BUF *why = state->why;
    463 
    464     /*
    465      * Sanity check.
    466      */
    467     if (session == 0)
    468 	msg_panic("smtp_stream_except: no session");
    469 
    470     /*
    471      * Initialize.
    472      */
    473     switch (code) {
    474     default:
    475 	msg_panic("smtp_stream_except: unknown exception %d", code);
    476     case SMTP_ERR_EOF:
    477 	dsb_simple(why, "4.4.2", "lost connection with %s while %s",
    478 		   session->namaddr, description);
    479 #ifdef USE_TLS
    480 	if (PLAINTEXT_FALLBACK_OK_AFTER_TLS_SESSION_FAILURE)
    481 	    RETRY_AS_PLAINTEXT;
    482 #endif
    483 	break;
    484     case SMTP_ERR_TIME:
    485 	dsb_simple(why, "4.4.2", "conversation with %s timed out while %s",
    486 		   session->namaddr, description);
    487 #ifdef USE_TLS
    488 	if (PLAINTEXT_FALLBACK_OK_AFTER_TLS_SESSION_FAILURE)
    489 	    RETRY_AS_PLAINTEXT;
    490 #endif
    491 	break;
    492     case SMTP_ERR_DATA:
    493 	session->error_mask |= MAIL_ERROR_DATA;
    494 	dsb_simple(why, "4.3.0", "local data error while talking to %s",
    495 		   session->namaddr);
    496     }
    497 
    498     /*
    499      * The smtp_bulk_fail() call below will not throttle the destination when
    500      * falling back to plaintext, because RETRY_AS_PLAINTEXT clears the
    501      * FINAL_SERVER flag.
    502      */
    503     return (smtp_bulk_fail(state, SMTP_MISC_FAIL_THROTTLE));
    504 }
    505