static void smtp_connect_inet(SMTP_STATE *state, const char *nexthop, char *def_service) { DELIVER_REQUEST *request = state->request; SMTP_ITERATOR *iter = state->iterator; ARGV *sites; char *dest; char **cpp; int non_fallback_sites; int retry_plain = 0; DSN_BUF *why = state->why; /* * For sanity, require that at least one of INET or INET6 is enabled. * Otherwise, we can't look up interface information, and we can't * convert names or addresses. */ if (inet_proto_info()->ai_family_list[0] == 0) { dsb_simple(why, "4.4.4", "all network protocols are disabled"); return; } /* * Future proofing: do a null destination sanity check in case we allow * the primary destination to be a list (it could be just separators). */ sites = argv_alloc(1); argv_add(sites, nexthop, (char *) 0); if (sites->argc == 0) msg_panic("null destination: \"%s\"", nexthop); non_fallback_sites = sites->argc; argv_split_append(sites, var_fallback_relay, CHARS_COMMA_SP); /* * Don't give up after a hard host lookup error until we have tried the * fallback relay servers. * * Don't bounce mail after a host lookup problem with a relayhost or with a * fallback relay. * * Don't give up after a qualifying soft error until we have tried all * qualifying backup mail servers. * * All this means that error handling and error reporting depends on whether * the error qualifies for trying to deliver to a backup mail server, or * whether we're looking up a relayhost or fallback relay. The challenge * then is to build this into the pre-existing SMTP client without * getting lost in the complexity. */ #define IS_FALLBACK_RELAY(cpp, sites, non_fallback_sites) \ (*(cpp) && (cpp) >= (sites)->argv + (non_fallback_sites)) for (cpp = sites->argv, (state->misc_flags |= SMTP_MISC_FLAG_FIRST_NEXTHOP); SMTP_RCPT_LEFT(state) > 0 && (dest = *cpp) != 0; cpp++, (state->misc_flags &= ~SMTP_MISC_FLAG_FIRST_NEXTHOP)) { char *dest_buf; char *domain; unsigned port; DNS_RR *addr_list; DNS_RR *addr; DNS_RR *next; int addr_count; int sess_count; SMTP_SESSION *session; int lookup_mx; unsigned domain_best_pref; MAI_HOSTADDR_STR hostaddr; if (cpp[1] == 0) state->misc_flags |= SMTP_MISC_FLAG_FINAL_NEXTHOP; /* * Parse the destination. If no TCP port is specified, use the port * that is reserved for the protocol (SMTP or LMTP). */ dest_buf = smtp_parse_destination(dest, def_service, &domain, &port); if (var_helpful_warnings && var_smtp_tls_wrappermode == 0 && ntohs(port) == 465) { msg_info("SMTPS wrappermode (TCP port 465) requires setting " "\"%s = yes\", and \"%s = encrypt\" (or stronger)", VAR_LMTP_SMTP(TLS_WRAPPER), VAR_LMTP_SMTP(TLS_LEVEL)); } #define NO_HOST "" /* safety */ #define NO_ADDR "" /* safety */ SMTP_ITER_INIT(iter, dest, NO_HOST, NO_ADDR, port, state); /* * Resolve an SMTP or LMTP server. In the case of SMTP, skip mail * exchanger lookups when a quoted host is specified or when DNS * lookups are disabled. */ if (msg_verbose) msg_info("connecting to %s port %d", domain, ntohs(port)); if (smtp_mode) { if (ntohs(port) == IPPORT_SMTP) state->misc_flags |= SMTP_MISC_FLAG_LOOP_DETECT; else state->misc_flags &= ~SMTP_MISC_FLAG_LOOP_DETECT; lookup_mx = (smtp_dns_support != SMTP_DNS_DISABLED && *dest != '['); } else lookup_mx = 0; if (!lookup_mx) { addr_list = smtp_host_addr(domain, state->misc_flags, why); /* XXX We could be an MX host for this destination... */ } else { int i_am_mx = 0; addr_list = smtp_domain_addr(domain, &iter->mx, state->misc_flags, why, &i_am_mx); /* If we're MX host, don't connect to non-MX backups. */ if (i_am_mx) state->misc_flags |= SMTP_MISC_FLAG_FINAL_NEXTHOP; } /* * Don't try fall-back hosts if mail loops to myself. That would just * make the problem worse. */ if (addr_list == 0 && SMTP_HAS_LOOP_DSN(why)) state->misc_flags |= SMTP_MISC_FLAG_FINAL_NEXTHOP; /* * No early loop exit or we have a memory leak with dest_buf. */ if (addr_list) domain_best_pref = addr_list->pref; /* * When session caching is enabled, store the first good session for * this delivery request under the next-hop destination name. All * good sessions will be stored under their specific server IP * address. * * XXX smtp_session_cache_destinations specifies domain names without * :port, because : is already used for maptype:mapname. Because of * this limitation we use the bare domain without the optional [] or * non-default TCP port. * * Opportunistic (a.k.a. on-demand) session caching on request by the * queue manager. This is turned temporarily when a destination has a * high volume of mail in the active queue. When the surge reaches * its end, the queue manager requests that connections be retrieved * but not stored. */ if (addr_list && (state->misc_flags & SMTP_MISC_FLAG_FIRST_NEXTHOP)) { smtp_cache_policy(state, domain); if (state->misc_flags & SMTP_MISC_FLAG_CONN_CACHE_MASK) SET_NEXTHOP_STATE(state, dest); } /* * Delete visited cached hosts from the address list. * * Optionally search the connection cache by domain name or by primary * MX address before we try to create new connections. * * Enforce the MX session and MX address counts per next-hop or * fall-back destination. smtp_reuse_session() will truncate the * address list when either limit is reached. */ if (addr_list && (state->misc_flags & SMTP_MISC_FLAG_CONN_LOAD)) { if (state->cache_used->used > 0) smtp_scrub_addr_list(state->cache_used, &addr_list); sess_count = addr_count = smtp_reuse_session(state, &addr_list, domain_best_pref); } else sess_count = addr_count = 0; /* * Connect to an SMTP server: create primary MX connections, and * reuse or create backup MX connections. * * At the start of an SMTP session, all recipients are unmarked. In the * course of an SMTP session, recipients are marked as KEEP (deliver * to alternate mail server) or DROP (remove from recipient list). At * the end of an SMTP session, weed out the recipient list. Unmark * any left-over recipients and try to deliver them to a backup mail * server. * * Cache the first good session under the next-hop destination name. * Cache all good sessions under their physical endpoint. * * Don't query the session cache for primary MX hosts. We already did * that in smtp_reuse_session(), and if any were found in the cache, * they were already deleted from the address list. * * Currently, we use smtp_reuse_addr() only for SASL-unauthenticated * connections. Furthermore, we rely on smtp_reuse_addr() to look up * an existing SASL-unauthenticated connection only when a new * connection would be guaranteed not to require SASL authentication. * * In addition, we rely on smtp_reuse_addr() to look up an existing * plaintext connection only when a new connection would be * guaranteed not to use TLS. */ for (addr = addr_list; SMTP_RCPT_LEFT(state) > 0 && addr; addr = next) { next = addr->next; if (++addr_count == var_smtp_mxaddr_limit) next = 0; if (dns_rr_to_pa(addr, &hostaddr) == 0) { msg_warn("cannot convert type %s record to printable address", dns_strtype(addr->type)); /* XXX Assume there is no code at the end of this loop. */ continue; } vstring_strcpy(iter->addr, hostaddr.buf); vstring_strcpy(iter->host, SMTP_HNAME(addr)); iter->rr = addr; #ifdef USE_TLS if (!smtp_tls_policy_cache_query(why, state->tls, iter)) { msg_warn("TLS policy lookup for %s/%s: %s", STR(iter->dest), STR(iter->host), STR(why->reason)); continue; /* XXX Assume there is no code at the end of this loop. */ } if (var_smtp_tls_wrappermode && state->tls->level < TLS_LEV_ENCRYPT) { msg_warn("%s requires \"%s = encrypt\" (or stronger)", VAR_LMTP_SMTP(TLS_WRAPPER), VAR_LMTP_SMTP(TLS_LEVEL)); continue; /* XXX Assume there is no code at the end of this loop. */ } /* Disable TLS when retrying after a handshake failure */ if (retry_plain) { state->tls->level = TLS_LEV_NONE; retry_plain = 0; } #endif if ((state->misc_flags & SMTP_MISC_FLAG_CONN_LOAD) == 0 || addr->pref == domain_best_pref || !(session = smtp_reuse_addr(state, SMTP_KEY_MASK_SCACHE_ENDP_LABEL))) session = smtp_connect_addr(iter, why, state->misc_flags); if ((state->session = session) != 0) { session->state = state; #ifdef USE_TLS session->tls_nexthop = domain; #endif if (addr->pref == domain_best_pref) session->features |= SMTP_FEATURE_BEST_MX; /* Don't count handshake errors towards the session limit. */ if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP) && next == 0) state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER; if ((session->features & SMTP_FEATURE_FROM_CACHE) == 0 && smtp_helo(state) != 0) { #ifdef USE_TLS /* * When an opportunistic TLS handshake fails, try the * same address again, with TLS disabled. See also the * RETRY_AS_PLAINTEXT macro. */ if ((retry_plain = session->tls_retry_plain) != 0) { --addr_count; next = addr; } #endif /* * When a TLS handshake fails, the stream is marked * "dead" to avoid further I/O over a broken channel. */ if (!THIS_SESSION_IS_FORBIDDEN && vstream_ferror(session->stream) == 0 && vstream_feof(session->stream) == 0) smtp_quit(state); } else { /* Do count delivery errors towards the session limit. */ if (++sess_count == var_smtp_mxsess_limit) next = 0; if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP) && next == 0) state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER; smtp_xfer(state); #ifdef USE_TLS /* * When opportunistic TLS fails after the STARTTLS * handshake, try the same address again, with TLS * disabled. See also the RETRY_AS_PLAINTEXT macro. */ if ((retry_plain = session->tls_retry_plain) != 0) { --sess_count; --addr_count; next = addr; } #endif } smtp_cleanup_session(state); } else { /* The reason already includes the IP address and TCP port. */ msg_info("%s", STR(why->reason)); } /* XXX Code above assumes there is no code at this loop ending. */ } dns_rr_free(addr_list); if (iter->mx) { dns_rr_free(iter->mx); iter->mx = 0; /* Just in case */ } myfree(dest_buf); if (state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP) break; } /* * We still need to deliver, bounce or defer some left-over recipients: * either mail loops or some backup mail server was unavailable. */ if (SMTP_RCPT_LEFT(state) > 0) { /* * In case of a "no error" indication we make up an excuse: we did * find the host address, but we did not attempt to connect to it. * This can happen when the fall-back relay was already tried via a * cached connection, so that the address list scrubber left behind * an empty list. */ if (!SMTP_HAS_DSN(why)) { dsb_simple(why, "4.3.0", "server unavailable or unable to receive mail"); } /* * Pay attention to what could be configuration problems, and pretend * that these are recoverable rather than bouncing the mail. */ else if (!SMTP_HAS_SOFT_DSN(why)) { /* * The fall-back destination did not resolve as expected, or it * is refusing to talk to us, or mail for it loops back to us. */ if (IS_FALLBACK_RELAY(cpp, sites, non_fallback_sites)) { msg_warn("%s configuration problem", VAR_SMTP_FALLBACK); vstring_strcpy(why->status, "4.3.5"); /* XXX Keep the diagnostic code and MTA. */ } /* * The next-hop relayhost did not resolve as expected, or it is * refusing to talk to us, or mail for it loops back to us. * * XXX There is no equivalent safety net for mis-configured * sender-dependent relay hosts. The trivial-rewrite resolver * would have to flag the result, and the queue manager would * have to provide that information to delivery agents. */ else if (smtp_mode && strcmp(sites->argv[0], var_relayhost) == 0) { msg_warn("%s configuration problem", VAR_RELAYHOST); vstring_strcpy(why->status, "4.3.5"); /* XXX Keep the diagnostic code and MTA. */ } /* * Mail for the next-hop destination loops back to myself. Pass * the mail to the best_mx_transport or bounce it. */ else if (smtp_mode && SMTP_HAS_LOOP_DSN(why) && *var_bestmx_transp) { dsb_reset(why); /* XXX */ state->status = deliver_pass_all(MAIL_CLASS_PRIVATE, var_bestmx_transp, request); SMTP_RCPT_LEFT(state) = 0; /* XXX */ } } } /* * Cleanup. */ if (HAVE_NEXTHOP_STATE(state)) FREE_NEXTHOP_STATE(state); argv_free(sites); }
static SMTP_SESSION *smtp_connect_addr(SMTP_ITERATOR *iter, DSN_BUF *why, int sess_flags) { const char *myname = "smtp_connect_addr"; struct sockaddr_storage ss; /* remote */ struct sockaddr *sa = (struct sockaddr *) &ss; SOCKADDR_SIZE salen = sizeof(ss); MAI_HOSTADDR_STR hostaddr; DNS_RR *addr = iter->rr; unsigned port = iter->port; int sock; char *bind_addr; char *bind_var; dsb_reset(why); /* Paranoia */ /* * Sanity checks. */ if (dns_rr_to_sa(addr, port, sa, &salen) != 0) { msg_warn("%s: skip address type %s: %m", myname, dns_strtype(addr->type)); dsb_simple(why, "4.4.0", "network address conversion failed: %m"); return (0); } /* * Initialize. */ if ((sock = socket(sa->sa_family, SOCK_STREAM, 0)) < 0) msg_fatal("%s: socket: %m", myname); if (inet_windowsize > 0) set_inet_windowsize(sock, inet_windowsize); /* * Allow the sysadmin to specify the source address, for example, as "-o * smtp_bind_address=x.x.x.x" in the master.cf file. */ #ifdef HAS_IPV6 if (sa->sa_family == AF_INET6) { bind_addr = var_smtp_bind_addr6; bind_var = VAR_LMTP_SMTP(BIND_ADDR6); } else #endif if (sa->sa_family == AF_INET) { bind_addr = var_smtp_bind_addr; bind_var = VAR_LMTP_SMTP(BIND_ADDR); } else bind_var = bind_addr = ""; if (*bind_addr) { int aierr; struct addrinfo *res0; if ((aierr = hostaddr_to_sockaddr(bind_addr, (char *) 0, 0, &res0)) != 0) msg_fatal("%s: bad %s parameter: %s: %s", myname, bind_var, bind_addr, MAI_STRERROR(aierr)); if (bind(sock, res0->ai_addr, res0->ai_addrlen) < 0) msg_warn("%s: bind %s: %m", myname, bind_addr); else if (msg_verbose) msg_info("%s: bind %s", myname, bind_addr); freeaddrinfo(res0); } /* * When running as a virtual host, bind to the virtual interface so that * the mail appears to come from the "right" machine address. * * XXX The IPv6 patch expands the null host (as client endpoint) and uses * the result as the loopback address list. */ else { int count = 0; struct sockaddr *own_addr = 0; INET_ADDR_LIST *addr_list = own_inet_addr_list(); struct sockaddr_storage *s; for (s = addr_list->addrs; s < addr_list->addrs + addr_list->used; s++) { if (SOCK_ADDR_FAMILY(s) == sa->sa_family) { if (count++ > 0) break; own_addr = SOCK_ADDR_PTR(s); } } if (count == 1 && !sock_addr_in_loopback(own_addr)) { if (bind(sock, own_addr, SOCK_ADDR_LEN(own_addr)) < 0) { SOCKADDR_TO_HOSTADDR(own_addr, SOCK_ADDR_LEN(own_addr), &hostaddr, (MAI_SERVPORT_STR *) 0, 0); msg_warn("%s: bind %s: %m", myname, hostaddr.buf); } else if (msg_verbose) { SOCKADDR_TO_HOSTADDR(own_addr, SOCK_ADDR_LEN(own_addr), &hostaddr, (MAI_SERVPORT_STR *) 0, 0); msg_info("%s: bind %s", myname, hostaddr.buf); } } } /* * Connect to the server. */ if (msg_verbose) msg_info("%s: trying: %s[%s] port %d...", myname, STR(iter->host), STR(iter->addr), ntohs(port)); return (smtp_connect_sock(sock, sa, salen, iter, why, sess_flags)); }
static void pre_init(char *unused_name, char **unused_argv) { int use_tls; static const NAME_CODE addr_pref_map[] = { INET_PROTO_NAME_IPV6, SMTP_MISC_FLAG_PREF_IPV6, INET_PROTO_NAME_IPV4, SMTP_MISC_FLAG_PREF_IPV4, INET_PROTO_NAME_ANY, 0, 0, -1, }; /* * Turn on per-peer debugging. */ debug_peer_init(); /* * SASL initialization. */ if (var_smtp_sasl_enable) #ifdef USE_SASL_AUTH smtp_sasl_initialize(); #else msg_warn("%s is true, but SASL support is not compiled in", VAR_LMTP_SMTP(SASL_ENABLE)); #endif if (*var_smtp_tls_level != 0) switch (tls_level_lookup(var_smtp_tls_level)) { case TLS_LEV_SECURE: case TLS_LEV_VERIFY: case TLS_LEV_DANE_ONLY: case TLS_LEV_FPRINT: case TLS_LEV_ENCRYPT: var_smtp_use_tls = var_smtp_enforce_tls = 1; break; case TLS_LEV_DANE: case TLS_LEV_MAY: var_smtp_use_tls = 1; var_smtp_enforce_tls = 0; break; case TLS_LEV_NONE: var_smtp_use_tls = var_smtp_enforce_tls = 0; break; default: /* tls_level_lookup() logs no warning. */ /* session_tls_init() assumes that var_smtp_tls_level is sane. */ msg_fatal("Invalid TLS level \"%s\"", var_smtp_tls_level); } use_tls = (var_smtp_use_tls || var_smtp_enforce_tls); /* * Initialize the TLS data before entering the chroot jail */ if (use_tls || var_smtp_tls_per_site[0] || var_smtp_tls_policy[0]) { #ifdef USE_TLS TLS_CLIENT_INIT_PROPS props; /* * We get stronger type safety and a cleaner interface by combining * the various parameters into a single tls_client_props structure. * * Large parameter lists are error-prone, so we emulate a language * feature that C does not have natively: named parameter lists. */ smtp_tls_ctx = TLS_CLIENT_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), 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); smtp_tls_list_init(); #else msg_warn("TLS has been selected, but TLS support is not compiled in"); #endif } /* * Flush client. */ flush_init(); /* * Session cache domain list. */ if (*var_smtp_cache_dest) smtp_cache_dest = string_list_init(MATCH_FLAG_RETURN, var_smtp_cache_dest); /* * EHLO keyword filter. */ if (*var_smtp_ehlo_dis_maps) smtp_ehlo_dis_maps = maps_create(VAR_LMTP_SMTP(EHLO_DIS_MAPS), var_smtp_ehlo_dis_maps, DICT_FLAG_LOCK); /* * PIX bug workarounds. */ if (*var_smtp_pix_bug_maps) smtp_pix_bug_maps = maps_create(VAR_LMTP_SMTP(PIX_BUG_MAPS), var_smtp_pix_bug_maps, DICT_FLAG_LOCK); /* * Generic maps. */ if (*var_prop_extension) smtp_ext_prop_mask = ext_prop_mask(VAR_PROP_EXTENSION, var_prop_extension); if (*var_smtp_generic_maps) smtp_generic_maps = maps_create(VAR_LMTP_SMTP(GENERIC_MAPS), var_smtp_generic_maps, DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX); /* * Header/body checks. */ smtp_header_checks = hbc_header_checks_create( VAR_LMTP_SMTP(HEAD_CHKS), var_smtp_head_chks, VAR_LMTP_SMTP(MIME_CHKS), var_smtp_mime_chks, VAR_LMTP_SMTP(NEST_CHKS), var_smtp_nest_chks, smtp_hbc_callbacks); smtp_body_checks = hbc_body_checks_create( VAR_LMTP_SMTP(BODY_CHKS), var_smtp_body_chks, smtp_hbc_callbacks); /* * Server reply filter. */ if (*var_smtp_resp_filter) smtp_chat_resp_filter = dict_open(var_smtp_resp_filter, O_RDONLY, DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX); /* * Address family preference. */ if (*var_smtp_addr_pref) { smtp_addr_pref = name_code(addr_pref_map, NAME_CODE_FLAG_NONE, var_smtp_addr_pref); if (smtp_addr_pref < 0) msg_fatal("bad %s value: %s", VAR_LMTP_SMTP(ADDR_PREF), var_smtp_addr_pref); } }
SMTP_RESP *smtp_chat_resp(SMTP_SESSION *session) { static SMTP_RESP rdata; char *cp; int last_char; int three_digs = 0; size_t len; const char *new_reply; int chat_append_flag; int chat_append_skipped = 0; /* * Initialize the response data buffer. */ if (rdata.str_buf == 0) { rdata.dsn_buf = vstring_alloc(10); rdata.str_buf = vstring_alloc(100); } /* * Censor out non-printable characters in server responses. Concatenate * multi-line server responses. Separate the status code from the text. * Leave further parsing up to the application. * * We can't parse or store input that exceeds var_line_limit, so we just * skip over it to simplify the remainder of the code below. */ VSTRING_RESET(rdata.str_buf); for (;;) { last_char = smtp_get(session->buffer, session->stream, var_line_limit, SMTP_GET_FLAG_SKIP); /* XXX Update the per-line time limit. */ printable(STR(session->buffer), '?'); if (last_char != '\n') msg_warn("%s: response longer than %d: %.30s...", session->namaddrport, var_line_limit, STR(session->buffer)); if (msg_verbose) msg_info("< %s: %.100s", session->namaddrport, STR(session->buffer)); /* * Defend against a denial of service attack by limiting the amount * of multi-line text that we are willing to store. */ chat_append_flag = (LEN(rdata.str_buf) < var_line_limit); if (chat_append_flag) smtp_chat_append(session, "In: ", STR(session->buffer)); else { if (chat_append_skipped == 0) msg_warn("%s: multi-line response longer than %d %.30s...", session->namaddrport, var_line_limit, STR(rdata.str_buf)); if (chat_append_skipped < INT_MAX) chat_append_skipped++; } /* * Server reply substitution, for fault-injection testing, or for * working around broken systems. Use with care. */ if (smtp_chat_resp_filter != 0) { new_reply = dict_get(smtp_chat_resp_filter, STR(session->buffer)); if (new_reply != 0) { msg_info("%s: replacing server reply \"%s\" with \"%s\"", session->namaddrport, STR(session->buffer), new_reply); vstring_strcpy(session->buffer, new_reply); if (chat_append_flag) { smtp_chat_append(session, "Replaced-by: ", ""); smtp_chat_append(session, " ", new_reply); } } else if (smtp_chat_resp_filter->error != 0) { msg_warn("%s: table %s:%s lookup error for %s", session->state->request->queue_id, smtp_chat_resp_filter->type, smtp_chat_resp_filter->name, printable(STR(session->buffer), '?')); vstream_longjmp(session->stream, SMTP_ERR_DATA); } } if (chat_append_flag) { if (LEN(rdata.str_buf)) VSTRING_ADDCH(rdata.str_buf, '\n'); vstring_strcat(rdata.str_buf, STR(session->buffer)); } /* * Parse into code and text. Do not ignore garbage (see below). */ for (cp = STR(session->buffer); *cp && ISDIGIT(*cp); cp++) /* void */ ; if ((three_digs = (cp - STR(session->buffer) == 3)) != 0) { if (*cp == '-') continue; if (*cp == ' ' || *cp == 0) break; } /* * XXX Do not simply ignore garbage in the server reply when ESMTP * command pipelining is turned on. For example, after sending * ".<CR><LF>QUIT<CR><LF>" and receiving garbage followed by a * legitimate 2XX reply, Postfix recognizes the server's QUIT reply * as the END-OF-DATA reply after garbage, causing mail to be lost. * * Without the ability to store per-domain status information in queue * files, automatic workarounds are problematic: * * - Automatically deferring delivery creates a "repeated delivery" * problem when garbage arrives after the DATA stage. Without the * workaround, Postfix delivers only once. * * - Automatically deferring delivery creates a "no delivery" problem * when the garbage arrives before the DATA stage. Without the * workaround, mail might still get through. * * - Automatically turning off pipelining for delayed mail affects * deliveries to correctly implemented servers, and may also affect * delivery of large mailing lists. * * So we leave the decision with the administrator, but we don't force * them to take action, like we would with automatic deferral. If * loss of mail is not acceptable then they can turn off pipelining * for specific sites, or they can turn off pipelining globally when * they find that there are just too many broken sites. */ session->error_mask |= MAIL_ERROR_PROTOCOL; if (session->features & SMTP_FEATURE_PIPELINING) { msg_warn("%s: non-%s response from %s: %.100s", session->state->request->queue_id, smtp_mode ? "ESMTP" : "LMTP", session->namaddrport, STR(session->buffer)); if (var_helpful_warnings) msg_warn("to prevent loss of mail, turn off command pipelining " "for %s with the %s parameter", STR(session->iterator->addr), VAR_LMTP_SMTP(EHLO_DIS_MAPS)); } } /* * Extract RFC 821 reply code and RFC 2034 detail. Use a default detail * code if none was given. * * Ignore out-of-protocol enhanced status codes: codes that accompany 3XX * replies, or codes whose initial digit is out of sync with the reply * code. * * XXX Potential stability problem. In order to save memory, the queue * manager stores DSNs in a compact manner: * * - empty strings are represented by null pointers, * * - the status and reason are required to be non-empty. * * Other Postfix daemons inherit this behavior, because they use the same * DSN support code. This means that everything that receives DSNs must * cope with null pointers for the optional DSN attributes, and that * everything that provides DSN information must provide a non-empty * status and reason, otherwise the DSN support code wil panic(). * * Thus, when the remote server sends a malformed reply (or 3XX out of * context) we should not panic() in DSN_COPY() just because we don't * have a status. Robustness suggests that we supply a status here, and * that we leave it up to the down-stream code to override the * server-supplied status in case of an error we can't detect here, such * as an out-of-order server reply. */ VSTRING_TERMINATE(rdata.str_buf); vstring_strcpy(rdata.dsn_buf, "5.5.0"); /* SAFETY! protocol error */ if (three_digs != 0) { rdata.code = atoi(STR(session->buffer)); if (strchr("245", STR(session->buffer)[0]) != 0) { for (cp = STR(session->buffer) + 4; *cp == ' '; cp++) /* void */ ; if ((len = dsn_valid(cp)) > 0 && *cp == *STR(session->buffer)) { vstring_strncpy(rdata.dsn_buf, cp, len); } else { vstring_strcpy(rdata.dsn_buf, "0.0.0"); STR(rdata.dsn_buf)[0] = STR(session->buffer)[0]; } } } else { rdata.code = 0; } rdata.dsn = STR(rdata.dsn_buf); rdata.str = STR(rdata.str_buf); return (&rdata); }
static void post_init(char *unused_name, char **unused_argv) { static const NAME_MASK lookup_masks[] = { SMTP_HOST_LOOKUP_DNS, SMTP_HOST_FLAG_DNS, SMTP_HOST_LOOKUP_NATIVE, SMTP_HOST_FLAG_NATIVE, 0, }; static const NAME_MASK dns_res_opt_masks[] = { SMTP_DNS_RES_OPT_DEFNAMES, RES_DEFNAMES, SMTP_DNS_RES_OPT_DNSRCH, RES_DNSRCH, 0, }; static const NAME_CODE dns_support[] = { SMTP_DNS_SUPPORT_DISABLED, SMTP_DNS_DISABLED, SMTP_DNS_SUPPORT_ENABLED, SMTP_DNS_ENABLED, #if (RES_USE_DNSSEC != 0) && (RES_USE_EDNS0 != 0) SMTP_DNS_SUPPORT_DNSSEC, SMTP_DNS_DNSSEC, #endif 0, SMTP_DNS_INVALID, }; if (*var_smtp_dns_support == 0) { /* Backwards compatible empty setting */ smtp_dns_support = var_disable_dns ? SMTP_DNS_DISABLED : SMTP_DNS_ENABLED; } else { smtp_dns_support = name_code(dns_support, NAME_CODE_FLAG_NONE, var_smtp_dns_support); if (smtp_dns_support == SMTP_DNS_INVALID) msg_fatal("invalid %s: \"%s\"", VAR_LMTP_SMTP(DNS_SUPPORT), var_smtp_dns_support); var_disable_dns = (smtp_dns_support == SMTP_DNS_DISABLED); } /* * Select hostname lookup mechanisms. */ if (smtp_dns_support == SMTP_DNS_DISABLED) smtp_host_lookup_mask = SMTP_HOST_FLAG_NATIVE; else smtp_host_lookup_mask = name_mask(VAR_LMTP_SMTP(HOST_LOOKUP), lookup_masks, var_smtp_host_lookup); if (msg_verbose) msg_info("host name lookup methods: %s", str_name_mask(VAR_LMTP_SMTP(HOST_LOOKUP), lookup_masks, smtp_host_lookup_mask)); /* * Session cache instance. */ if (*var_smtp_cache_dest || var_smtp_cache_demand) #if 0 smtp_scache = scache_multi_create(); #else smtp_scache = scache_clnt_create(var_scache_service, var_scache_proto_tmout, var_ipc_idle_limit, var_ipc_ttl_limit); #endif /* * Select DNS query flags. */ smtp_dns_res_opt = name_mask(VAR_LMTP_SMTP(DNS_RES_OPT), dns_res_opt_masks, var_smtp_dns_res_opt); }