/*++ /* NAME /* smtp_trouble 3 /* SUMMARY /* error handler policies /* SYNOPSIS /* #include "smtp.h" /* /* int smtp_site_fail(state, code, format, ...) /* SMTP_STATE *state; /* int code; /* char *format; /* /* int smtp_mesg_fail(state, code, format, ...) /* SMTP_STATE *state; /* int code; /* char *format; /* /* void smtp_rcpt_fail(state, code, recipient, format, ...) /* SMTP_STATE *state; /* int code; /* RECIPIENT *recipient; /* char *format; /* /* int smtp_stream_except(state, exception, description) /* SMTP_STATE *state; /* int exception; /* char *description; /* DESCRIPTION /* This module handles all non-fatal errors that can happen while /* attempting to deliver mail via SMTP, and implements the policy /* of how to deal with the error. Depending on the nature of /* the problem, delivery of a single message is deferred, delivery /* of all messages to the same domain is deferred, or one or more /* recipients are given up as non-deliverable and a bounce log is /* updated. In any case, the recipient is marked as either KEEP /* (try again with a backup host) or DROP (delete recipient from /* delivery request). /* /* In addition, when an unexpected response code is seen such /* as 3xx where only 4xx or 5xx are expected, or any error code /* that suggests a syntax error or something similar, the /* protocol error flag is set so that the postmaster receives /* a transcript of the session. No notification is generated for /* what appear to be configuration errors - very likely, they /* would suffer the same problem and just cause more trouble. /* /* In case of a soft error, action depends on whether the error /* qualifies for trying the request with other mail servers (log /* an informational record only and try a backup server) or /* whether this is the final server (log recipient delivery status /* records and delete the recipient from the request). /* /* smtp_site_fail() handles the case where the program fails to /* complete the initial SMTP handshake: the server is not reachable, /* is not running, does not want talk to us, or we talk to ourselves. /* The \fIcode\fR gives an error status code; the \fIformat\fR /* argument gives a textual description. /* The policy is: soft error, non-final server: log an informational /* record why the host is being skipped; soft error, final server: /* defer delivery of all remaining recipients and mark the destination /* as problematic; hard error: bounce all remaining recipients. /* The session is marked as "do not cache". /* The result is non-zero. /* /* smtp_mesg_fail() handles the case where the smtp server /* does not accept the sender address or the message data. /* The policy is: soft error, non-final server: log an informational /* record why the host is being skipped; soft error, final server: /* defer delivery of all remaining recipients; hard error: bounce all /* remaining recipients. /* The result is non-zero. /* /* smtp_rcpt_fail() handles the case where a recipient is not /* accepted by the server for reasons other than that the server /* recipient limit is reached. /* The policy is: soft error, non-final server: log an informational /* record why the recipient is being skipped; soft error, final server: /* defer delivery of this recipient; hard error: bounce this /* recipient. /* /* smtp_stream_except() handles the exceptions generated by /* the smtp_stream(3) module (i.e. timeouts and I/O errors). /* The \fIexception\fR argument specifies the type of problem. /* The \fIdescription\fR argument describes at what stage of /* the SMTP dialog the problem happened. /* The policy is: non-final server: log an informational record /* with the reason why the host is being skipped; final server: /* defer delivery of all remaining recipients. /* The session is marked as "do not cache". /* The result is non-zero. /* DIAGNOSTICS /* Panic: unknown exception code. /* SEE ALSO /* smtp_proto(3) smtp high-level protocol /* smtp_stream(3) smtp low-level protocol /* defer(3) basic message defer interface /* bounce(3) basic message bounce interface /* 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 /*--*/ /* System library. */ #include #include /* 44BSD stdarg.h uses abort() */ #include /* Utility library. */ #include #include #include #include /* Global library. */ #include #include #include #include #include #include /* Application-specific. */ #include "smtp.h" #define SMTP_SOFT(code) (((code) / 100) == 4) #define SMTP_HARD(code) (((code) / 100) == 5) /* smtp_check_code - check response code */ static void smtp_check_code(SMTP_SESSION *session, int code) { /* * The intention of this stuff is to alert the postmaster when the local * Postfix SMTP client screws up, protocol wise. RFC 821 says that x0z * replies "refer to syntax errors, syntactically correct commands that * don't fit any functional category, and unimplemented or superfluous * commands". Unfortunately, this also triggers postmaster notices when * remote servers screw up, protocol wise. This is becoming a common * problem now that response codes are configured manually as part of * anti-UCE systems, by people who aren't aware of RFC details. */ if ((!SMTP_SOFT(code) && !SMTP_HARD(code)) || code == 555 /* RFC 1869, section 6.1. */ || (code >= 500 && code < 510)) session->error_mask |= MAIL_ERROR_PROTOCOL; } /* smtp_site_fail - skip site, defer or bounce all recipients */ int smtp_site_fail(SMTP_STATE *state, int code, char *format,...) { DELIVER_REQUEST *request = state->request; SMTP_SESSION *session = state->session; RECIPIENT *rcpt; int status; int nrcpt; int soft_error = SMTP_SOFT(code); va_list ap; VSTRING *why = vstring_alloc(100); /* * Initialize. */ va_start(ap, format); vstring_vsprintf(why, format, ap); va_end(ap); /* * Don't defer the recipients just yet when this error qualifies them for * delivery to a backup server. Just log something informative to show * why we're skipping this host. */ if (soft_error && state->final_server == 0) { msg_info("%s: %s", request->queue_id, vstring_str(why)); for (nrcpt = 0; nrcpt < SMTP_RCPT_LEFT(state); nrcpt++) { rcpt = request->rcpt_list.info + nrcpt; if (SMTP_RCPT_ISMARKED(rcpt)) continue; SMTP_RCPT_KEEP(state, rcpt); } } /* * Defer or bounce all the remaining recipients, and delete them from the * delivery request. If a bounce fails, defer instead and do not qualify * the recipient for delivery to a backup server. */ else { for (nrcpt = 0; nrcpt < SMTP_RCPT_LEFT(state); nrcpt++) { rcpt = request->rcpt_list.info + nrcpt; if (SMTP_RCPT_ISMARKED(rcpt)) continue; status = (soft_error ? defer_append : bounce_append) (DEL_REQ_TRACE_FLAGS(request->flags), request->queue_id, rcpt->orig_addr, rcpt->address, rcpt->offset, session ? session->namaddr : "none", request->arrival_time, "%s", vstring_str(why)); if (status == 0) deliver_completed(state->src, rcpt->offset); SMTP_RCPT_DROP(state, rcpt); state->status |= status; } /* XXX This assumes no fall-back relay. */ if (soft_error && request->hop_status == 0) request->hop_status = mystrdup(vstring_str(why)); } if (session) smtp_check_code(session, code); /* * Don't cache this session. We can't talk to this server. */ if (session) session->reuse_count = 0; /* * Cleanup. */ vstring_free(why); return (-1); } /* smtp_mesg_fail - skip site, defer all recipients, or bounce all recipients */ int smtp_mesg_fail(SMTP_STATE *state, int code, char *format,...) { DELIVER_REQUEST *request = state->request; SMTP_SESSION *session = state->session; RECIPIENT *rcpt; int status; int nrcpt; int soft_error = SMTP_SOFT(code); va_list ap; VSTRING *why = vstring_alloc(100); /* * Initialize. */ va_start(ap, format); vstring_vsprintf(why, format, ap); va_end(ap); /* * Don't defer the recipients just yet when this error qualifies them for * delivery to a backup server. Just log something informative to show * why we're skipping this host. */ if (soft_error && state->final_server == 0) { msg_info("%s: %s", request->queue_id, vstring_str(why)); for (nrcpt = 0; nrcpt < SMTP_RCPT_LEFT(state); nrcpt++) { rcpt = request->rcpt_list.info + nrcpt; if (SMTP_RCPT_ISMARKED(rcpt)) continue; SMTP_RCPT_KEEP(state, rcpt); } } /* * Defer or bounce all the remaining recipients, and delete them from the * delivery request. If a bounce fails, defer instead and do not qualify * the recipient for delivery to a backup server. */ else { for (nrcpt = 0; nrcpt < SMTP_RCPT_LEFT(state); nrcpt++) { rcpt = request->rcpt_list.info + nrcpt; if (SMTP_RCPT_ISMARKED(rcpt)) continue; status = (soft_error ? defer_append : bounce_append) (DEL_REQ_TRACE_FLAGS(request->flags), request->queue_id, rcpt->orig_addr, rcpt->address, rcpt->offset, session ? session->namaddr : "none", request->arrival_time, "%s", vstring_str(why)); if (status == 0) deliver_completed(state->src, rcpt->offset); SMTP_RCPT_DROP(state, rcpt); state->status |= status; } } if (session) smtp_check_code(session, code); /* * Cleanup. */ vstring_free(why); return (-1); } /* smtp_rcpt_fail - skip, defer, or bounce recipient */ void smtp_rcpt_fail(SMTP_STATE *state, int code, RECIPIENT *rcpt, char *format,...) { DELIVER_REQUEST *request = state->request; SMTP_SESSION *session = state->session; int status; int soft_error = SMTP_SOFT(code); va_list ap; /* * Sanity check. */ if (SMTP_RCPT_ISMARKED(rcpt)) msg_panic("smtp_rcpt_fail: recipient <%s> is marked", rcpt->address); /* * Don't defer this recipient record just yet when this error qualifies * for trying other mail servers. Just log something informative to show * why we're skipping this recipient now. */ if (soft_error && state->final_server == 0) { VSTRING *buf = vstring_alloc(100); va_start(ap, format); vstring_vsprintf(buf, format, ap); va_end(ap); msg_info("%s: %s", request->queue_id, vstring_str(buf)); SMTP_RCPT_KEEP(state, rcpt); vstring_free(buf); } /* * Defer or bounce this recipient, and delete from the delivery request. * If the bounce fails, defer instead and do not qualify the recipient * for delivery to a backup server. * * Note: we may still make an SMTP connection to deliver other recipients * that did qualify for delivery to a backup server. */ else { va_start(ap, format); status = (soft_error ? vdefer_append : vbounce_append) (DEL_REQ_TRACE_FLAGS(request->flags), request->queue_id, rcpt->orig_addr, rcpt->address, rcpt->offset, session ? session->namaddr : "none", request->arrival_time, format, ap); va_end(ap); if (status == 0) deliver_completed(state->src, rcpt->offset); SMTP_RCPT_DROP(state, rcpt); state->status |= status; } if (session) smtp_check_code(session, code); } /* smtp_stream_except - defer domain after I/O problem */ int smtp_stream_except(SMTP_STATE *state, int code, char *description) { DELIVER_REQUEST *request = state->request; SMTP_SESSION *session = state->session; RECIPIENT *rcpt; int nrcpt; VSTRING *why = vstring_alloc(100); /* * Sanity check. */ if (session == 0) msg_panic("smtp_stream_except: no session"); /* * Initialize. */ switch (code) { default: msg_panic("smtp_stream_except: unknown exception %d", code); case SMTP_ERR_EOF: vstring_sprintf(why, "lost connection with %s while %s", session->namaddr, description); break; case SMTP_ERR_TIME: vstring_sprintf(why, "conversation with %s timed out while %s", session->namaddr, description); break; } /* * Don't defer the recipients just yet when this error qualifies them for * delivery to a backup server. Just log something informative to show * why we're skipping this host. */ if (state->final_server == 0) { msg_info("%s: %s", request->queue_id, vstring_str(why)); for (nrcpt = 0; nrcpt < SMTP_RCPT_LEFT(state); nrcpt++) { rcpt = request->rcpt_list.info + nrcpt; if (SMTP_RCPT_ISMARKED(rcpt)) continue; SMTP_RCPT_KEEP(state, rcpt); } } /* * Defer all the remaining recipients and drop them from the delivery * request. */ else { for (nrcpt = 0; nrcpt < SMTP_RCPT_LEFT(state); nrcpt++) { rcpt = request->rcpt_list.info + nrcpt; if (SMTP_RCPT_ISMARKED(rcpt)) continue; state->status |= defer_append(DEL_REQ_TRACE_FLAGS(request->flags), request->queue_id, rcpt->orig_addr, rcpt->address, rcpt->offset, session->namaddr, request->arrival_time, "%s", vstring_str(why)); SMTP_RCPT_DROP(state, rcpt); } } /* * Don't attempt to cache this session. */ session->reuse_count = 0; /* * Cleanup. */ vstring_free(why); return (-1); }