1 /*        $NetBSD: smtp_trouble.c,v 1.3 2020/03/18 19:05: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, throttle, mta_name, resp, format, ...)
39 /*        SMTP_STATE *state;
40 /*        int       throttle;
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(). The throttle argument
97 /*        is either SMTP_THROTTLE or SMTP_NOTHROTTLE; it is used only
98 /*        in the "soft error, final server" policy, and determines
99 /*        whether a destination will be marked as problematic.
100 /*
101 /*        smtp_rcpt_fail() handles the case where a recipient is not
102 /*        accepted by the server for reasons other than that the server
103 /*        recipient limit is reached.
104 /*        The policy is: soft error, non-final server: log an informational
105 /*        record why the recipient is being skipped; soft error, final server:
106 /*        defer delivery of this recipient; hard error: bounce this
107 /*        recipient.
108 /*
109 /*        smtp_stream_except() handles the exceptions generated by
110 /*        the smtp_stream(3) module (i.e. timeouts and I/O errors).
111 /*        The \fIexception\fR argument specifies the type of problem.
112 /*        The \fIdescription\fR argument describes at what stage of
113 /*        the SMTP dialog the problem happened.
114 /*        The policy is: non-final server: log an informational record
115 /*        with the reason why the host is being skipped; final server:
116 /*        defer delivery of all remaining recipients.
117 /*        Retry plaintext delivery after TLS post-handshake session
118 /*        failure, provided that at least one recipient was not
119 /*        deferred or rejected during the TLS phase, and that global
120 /*        preconditions for plaintext fallback are met.
121 /*        The session is marked as "do not cache".
122 /*        The result is non-zero.
123 /*
124 /*        Arguments:
125 /* .IP state
126 /*        SMTP client state per delivery request.
127 /* .IP resp
128 /*        Server response including reply code and text.
129 /* .IP recipient
130 /*        Undeliverable recipient address information.
131 /* .IP format
132 /*        Human-readable description of why mail is not deliverable.
133 /* DIAGNOSTICS
134 /*        Panic: unknown exception code.
135 /* SEE ALSO
136 /*        smtp_proto(3) smtp high-level protocol
137 /*        smtp_stream(3) smtp low-level protocol
138 /*        defer(3) basic message defer interface
139 /*        bounce(3) basic message bounce interface
140 /* LICENSE
141 /* .ad
142 /* .fi
143 /*        The Secure Mailer license must be distributed with this software.
144 /* AUTHOR(S)
145 /*        Wietse Venema
146 /*        IBM T.J. Watson Research
147 /*        P.O. Box 704
148 /*        Yorktown Heights, NY 10598, USA
149 /*
150 /*        Wietse Venema
151 /*        Google, Inc.
152 /*        111 8th Avenue
153 /*        New York, NY 10011, USA
154 /*--*/
155 
156 /* System library. */
157 
158 #include <sys_defs.h>
159 #include <stdlib.h>                     /* 44BSD stdarg.h uses abort() */
160 #include <stdarg.h>
161 #include <string.h>
162 
163 /* Utility library. */
164 
165 #include <msg.h>
166 #include <vstring.h>
167 #include <stringops.h>
168 
169 /* Global library. */
170 
171 #include <smtp_stream.h>
172 #include <deliver_request.h>
173 #include <deliver_completed.h>
174 #include <bounce.h>
175 #include <defer.h>
176 #include <mail_error.h>
177 #include <dsn_buf.h>
178 #include <dsn.h>
179 #include <mail_params.h>
180 
181 /* Application-specific. */
182 
183 #include "smtp.h"
184 #include "smtp_sasl.h"
185 
186 /* smtp_check_code - check response code */
187 
smtp_check_code(SMTP_SESSION * session,int code)188 static void smtp_check_code(SMTP_SESSION *session, int code)
189 {
190 
191     /*
192      * The intention of this code is to alert the postmaster when the local
193      * Postfix SMTP client screws up, protocol wise. RFC 821 says that x0z
194      * replies "refer to syntax errors, syntactically correct commands that
195      * don't fit any functional category, and unimplemented or superfluous
196      * commands". Unfortunately, this also triggers postmaster notices when
197      * remote servers screw up, protocol wise. This is becoming a common
198      * problem now that response codes are configured manually as part of
199      * anti-UCE systems, by people who aren't aware of RFC details.
200      *
201      * Fix 20190621: don't cache an SMTP session after an SMTP protocol error.
202      * The protocol may be in a bad state. Disable caching here so that the
203      * protocol engine will send QUIT.
204      */
205     if (code < 400 || code > 599
206           || code == 555                          /* RFC 1869, section 6.1. */
207           || (code >= 500 && code < 510)) {
208           session->error_mask |= MAIL_ERROR_PROTOCOL;
209           DONT_CACHE_THIS_SESSION;
210     }
211 }
212 
213 /* smtp_bulk_fail - skip, defer or bounce recipients, maybe throttle queue */
214 
smtp_bulk_fail(SMTP_STATE * state,int throttle_queue)215 static int smtp_bulk_fail(SMTP_STATE *state, int throttle_queue)
216 {
217     DELIVER_REQUEST *request = state->request;
218     SMTP_SESSION *session = state->session;
219     DSN_BUF *why = state->why;
220     RECIPIENT *rcpt;
221     int     status;
222     int     aggregate_status;
223     int     soft_error = (STR(why->status)[0] == '4');
224     int     soft_bounce_error = (STR(why->status)[0] == '5' && var_soft_bounce);
225     int     nrcpt;
226 
227     /*
228      * Don't defer the recipients just yet when this error qualifies them for
229      * delivery to a backup server. Just log something informative to show
230      * why we're skipping this host.
231      */
232     if ((soft_error || soft_bounce_error)
233           && (state->misc_flags & SMTP_MISC_FLAG_FINAL_SERVER) == 0) {
234           msg_info("%s: %s", request->queue_id, STR(why->reason));
235           for (nrcpt = 0; nrcpt < SMTP_RCPT_LEFT(state); nrcpt++) {
236               rcpt = request->rcpt_list.info + nrcpt;
237               if (SMTP_RCPT_ISMARKED(rcpt))
238                     continue;
239               SMTP_RCPT_KEEP(state, rcpt);
240           }
241     }
242 
243     /*
244      * Defer or bounce all the remaining recipients, and delete them from the
245      * delivery request. If a bounce fails, defer instead and do not qualify
246      * the recipient for delivery to a backup server.
247      */
248     else {
249 
250           /*
251            * If we are still in the connection set-up phase, update the set-up
252            * completion time here, otherwise the time spent in set-up latency
253            * will be attributed as message transfer latency.
254            *
255            * All remaining recipients have failed at this point, so we update the
256            * delivery completion time stamp so that multiple recipient status
257            * records show the same delay values.
258            */
259           if (request->msg_stats.conn_setup_done.tv_sec == 0) {
260               GETTIMEOFDAY(&request->msg_stats.conn_setup_done);
261               request->msg_stats.deliver_done =
262                     request->msg_stats.conn_setup_done;
263           } else
264               GETTIMEOFDAY(&request->msg_stats.deliver_done);
265 
266           (void) DSN_FROM_DSN_BUF(why);
267           aggregate_status = 0;
268           for (nrcpt = 0; nrcpt < SMTP_RCPT_LEFT(state); nrcpt++) {
269               rcpt = request->rcpt_list.info + nrcpt;
270               if (SMTP_RCPT_ISMARKED(rcpt))
271                     continue;
272               status = (soft_error ? defer_append : bounce_append)
273                     (DEL_REQ_TRACE_FLAGS(request->flags), request->queue_id,
274                      &request->msg_stats, rcpt,
275                      session ? session->namaddrport : "none", &why->dsn);
276               if (status == 0)
277                     deliver_completed(state->src, rcpt->offset);
278               SMTP_RCPT_DROP(state, rcpt);
279               aggregate_status |= status;
280           }
281           state->status |= aggregate_status;
282           if ((state->misc_flags & SMTP_MISC_FLAG_COMPLETE_SESSION) == 0
283               && throttle_queue && aggregate_status
284               && request->hop_status == 0)
285               request->hop_status = DSN_COPY(&why->dsn);
286     }
287 
288     /*
289      * Don't cache this session. We can't talk to this server.
290      */
291     if (throttle_queue && session)
292           DONT_CACHE_THROTTLED_SESSION;
293 
294     return (-1);
295 }
296 
297 /* smtp_sess_fail - skip site, defer or bounce all recipients */
298 
smtp_sess_fail(SMTP_STATE * state)299 int     smtp_sess_fail(SMTP_STATE *state)
300 {
301 
302     /*
303      * We can't avoid copying copying lots of strings into VSTRING buffers,
304      * because this error information is collected by a routine that
305      * terminates BEFORE the error is reported.
306      */
307     return (smtp_bulk_fail(state, SMTP_THROTTLE));
308 }
309 
310 /* vsmtp_fill_dsn - fill in temporary DSN structure */
311 
vsmtp_fill_dsn(SMTP_STATE * state,const char * mta_name,const char * status,const char * reply,const char * format,va_list ap)312 static void vsmtp_fill_dsn(SMTP_STATE *state, const char *mta_name,
313                                          const char *status, const char *reply,
314                                          const char *format, va_list ap)
315 {
316     DSN_BUF *why = state->why;
317 
318     /*
319      * We could avoid copying lots of strings into VSTRING buffers, because
320      * this error information is given to us by a routine that terminates
321      * AFTER the error is reported. However, this results in ugly kludges
322      * when informal text needs to be formatted. So we maintain consistency
323      * with other error reporting in the SMTP client even if we waste a few
324      * cycles.
325      *
326      * Fix 20190621: don't cache an SMTP session after an SMTP protocol error.
327      * The protocol may be in a bad state. Disable caching here so that the
328      * protocol engine will send QUIT.
329      */
330     VSTRING_RESET(why->reason);
331     if (mta_name && status && status[0] != '4' && status[0] != '5') {
332           SMTP_SESSION *session = state->session;
333 
334           session->error_mask |= MAIL_ERROR_PROTOCOL;
335           DONT_CACHE_THIS_SESSION;
336           vstring_strcpy(why->reason, "Protocol error: ");
337           status = "5.5.0";
338     }
339     vstring_vsprintf_append(why->reason, format, ap);
340     dsb_formal(why, status, DSB_DEF_ACTION,
341                  mta_name ? DSB_MTYPE_DNS : DSB_MTYPE_NONE, mta_name,
342                  reply ? DSB_DTYPE_SMTP : DSB_DTYPE_NONE, reply);
343 }
344 
345 /* smtp_misc_fail - maybe throttle queue; skip/defer/bounce all recipients */
346 
smtp_misc_fail(SMTP_STATE * state,int throttle,const char * mta_name,SMTP_RESP * resp,const char * format,...)347 int     smtp_misc_fail(SMTP_STATE *state, int throttle, const char *mta_name,
348                                    SMTP_RESP *resp, const char *format,...)
349 {
350     va_list ap;
351 
352     /*
353      * Initialize.
354      */
355     va_start(ap, format);
356     vsmtp_fill_dsn(state, mta_name, resp->dsn, resp->str, format, ap);
357     va_end(ap);
358 
359     if (state->session && mta_name)
360           smtp_check_code(state->session, resp->code);
361 
362     /*
363      * Skip, defer or bounce recipients, and throttle this queue.
364      */
365     return (smtp_bulk_fail(state, throttle));
366 }
367 
368 /* smtp_rcpt_fail - skip, defer, or bounce recipient */
369 
smtp_rcpt_fail(SMTP_STATE * state,RECIPIENT * rcpt,const char * mta_name,SMTP_RESP * resp,const char * format,...)370 void    smtp_rcpt_fail(SMTP_STATE *state, RECIPIENT *rcpt, const char *mta_name,
371                                    SMTP_RESP *resp, const char *format,...)
372 {
373     DELIVER_REQUEST *request = state->request;
374     SMTP_SESSION *session = state->session;
375     DSN_BUF *why = state->why;
376     int     status;
377     int     soft_error;
378     int     soft_bounce_error;
379     va_list ap;
380 
381     /*
382      * Sanity check.
383      */
384     if (SMTP_RCPT_ISMARKED(rcpt))
385           msg_panic("smtp_rcpt_fail: recipient <%s> is marked", rcpt->address);
386 
387     /*
388      * Initialize.
389      */
390     va_start(ap, format);
391     vsmtp_fill_dsn(state, mta_name, resp->dsn, resp->str, format, ap);
392     va_end(ap);
393     soft_error = STR(why->status)[0] == '4';
394     soft_bounce_error = (STR(why->status)[0] == '5' && var_soft_bounce);
395 
396     if (state->session && mta_name)
397           smtp_check_code(state->session, resp->code);
398 
399     /*
400      * Don't defer this recipient record just yet when this error qualifies
401      * for trying other mail servers. Just log something informative to show
402      * why we're skipping this recipient now.
403      */
404     if ((soft_error || soft_bounce_error)
405           && (state->misc_flags & SMTP_MISC_FLAG_FINAL_SERVER) == 0) {
406           msg_info("%s: %s", request->queue_id, STR(why->reason));
407           SMTP_RCPT_KEEP(state, rcpt);
408     }
409 
410     /*
411      * Defer or bounce this recipient, and delete from the delivery request.
412      * If the bounce fails, defer instead and do not qualify the recipient
413      * for delivery to a backup server.
414      *
415      * Note: we may still make an SMTP connection to deliver other recipients
416      * that did qualify for delivery to a backup server.
417      */
418     else {
419           (void) DSN_FROM_DSN_BUF(state->why);
420           status = (soft_error ? defer_append : bounce_append)
421               (DEL_REQ_TRACE_FLAGS(request->flags), request->queue_id,
422                &request->msg_stats, rcpt,
423                session ? session->namaddrport : "none", &why->dsn);
424           if (status == 0)
425               deliver_completed(state->src, rcpt->offset);
426           SMTP_RCPT_DROP(state, rcpt);
427           state->status |= status;
428     }
429 }
430 
431 /* smtp_stream_except - defer domain after I/O problem */
432 
smtp_stream_except(SMTP_STATE * state,int code,const char * description)433 int     smtp_stream_except(SMTP_STATE *state, int code, const char *description)
434 {
435     SMTP_SESSION *session = state->session;
436     DSN_BUF *why = state->why;
437 
438     /*
439      * Sanity check.
440      */
441     if (session == 0)
442           msg_panic("smtp_stream_except: no session");
443 
444     /*
445      * Initialize.
446      */
447     switch (code) {
448     default:
449           msg_panic("smtp_stream_except: unknown exception %d", code);
450     case SMTP_ERR_EOF:
451           dsb_simple(why, "4.4.2", "lost connection with %s while %s",
452                        session->namaddr, description);
453 #ifdef USE_TLS
454           if (PLAINTEXT_FALLBACK_OK_AFTER_TLS_SESSION_FAILURE)
455               RETRY_AS_PLAINTEXT;
456 #endif
457           break;
458     case SMTP_ERR_TIME:
459           dsb_simple(why, "4.4.2", "conversation with %s timed out while %s",
460                        session->namaddr, description);
461 #ifdef USE_TLS
462           if (PLAINTEXT_FALLBACK_OK_AFTER_TLS_SESSION_FAILURE)
463               RETRY_AS_PLAINTEXT;
464 #endif
465           break;
466     case SMTP_ERR_DATA:
467           session->error_mask |= MAIL_ERROR_DATA;
468           dsb_simple(why, "4.3.0", "local data error while talking to %s",
469                        session->namaddr);
470     }
471 
472     /*
473      * The smtp_bulk_fail() call below will not throttle the destination when
474      * falling back to plaintext, because RETRY_AS_PLAINTEXT clears the
475      * FINAL_SERVER flag.
476      */
477     return (smtp_bulk_fail(state, SMTP_THROTTLE));
478 }
479