static int smtpd_proxy_rec_put(VSTREAM *stream, int rec_type, const char *data, ssize_t len) { const char *myname = "smtpd_proxy_rec_put"; int err = 0; /* * Errors first. */ if (vstream_ferror(stream) || vstream_feof(stream) || (err = vstream_setjmp(stream)) != 0) { (void) smtpd_proxy_rdwr_error(VSTREAM_TO_SMTPD_STATE(stream), err); return (REC_TYPE_ERROR); } /* * Send one content record. Errors and results must be as with rec_put(). */ if (rec_type == REC_TYPE_NORM) smtp_fputs(data, len, stream); else if (rec_type == REC_TYPE_CONT) smtp_fwrite(data, len, stream); else msg_panic("%s: need REC_TYPE_NORM or REC_TYPE_CONT", myname); return (rec_type); }
static int smtpd_proxy_rec_fprintf(VSTREAM *stream, int rec_type, const char *fmt,...) { const char *myname = "smtpd_proxy_rec_fprintf"; va_list ap; int err = 0; /* * Errors first. */ if (vstream_ferror(stream) || vstream_feof(stream) || (err = vstream_setjmp(stream)) != 0) { (void) smtpd_proxy_rdwr_error(VSTREAM_TO_SMTPD_STATE(stream), err); return (REC_TYPE_ERROR); } /* * Send one content record. Errors and results must be as with * rec_fprintf(). */ va_start(ap, fmt); if (rec_type == REC_TYPE_NORM) smtp_vprintf(stream, fmt, ap); else msg_panic("%s: need REC_TYPE_NORM", myname); va_end(ap); return (rec_type); }
int main(int unused_argc, char **unused_argv) { VSTRING *buf = vstring_alloc(100); VSTRING *result = vstring_alloc(100); char *cp; char *name; char *value; HTABLE *table; int stat; while (!vstream_feof(VSTREAM_IN)) { table = htable_create(0); /* * Read a block of definitions, terminated with an empty line. */ while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) { vstream_printf("<< %s\n", vstring_str(buf)); vstream_fflush(VSTREAM_OUT); if (VSTRING_LEN(buf) == 0) break; cp = vstring_str(buf); name = mystrtok(&cp, " \t\r\n="); value = mystrtok(&cp, " \t\r\n="); htable_enter(table, name, value ? mystrdup(value) : 0); } /* * Read a block of patterns, terminated with an empty line or EOF. */ while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) { vstream_printf("<< %s\n", vstring_str(buf)); vstream_fflush(VSTREAM_OUT); if (VSTRING_LEN(buf) == 0) break; cp = vstring_str(buf); VSTRING_RESET(result); stat = mac_expand(result, vstring_str(buf), MAC_EXP_FLAG_NONE, (char *) 0, lookup, (char *) table); vstream_printf("stat=%d result=%s\n", stat, vstring_str(result)); vstream_fflush(VSTREAM_OUT); } htable_free(table, myfree); vstream_printf("\n"); } /* * Clean up. */ vstring_free(buf); vstring_free(result); exit(0); }
void smtpd_proxy_close(SMTPD_STATE *state) { SMTPD_PROXY *proxy = state->proxy; /* * Specify SMTPD_PROX_WANT_NONE so that the server reply will not clobber * the END-OF-DATA reply. */ if (proxy->service_stream != 0) { if (vstream_feof(proxy->service_stream) == 0 && vstream_ferror(proxy->service_stream) == 0) (void) smtpd_proxy_cmd(state, SMTPD_PROX_WANT_NONE, SMTPD_CMD_QUIT); (void) vstream_fclose(proxy->service_stream); if (proxy->stream == proxy->service_stream) proxy->stream = 0; proxy->service_stream = 0; } }
static int smtpd_proxy_save_cmd(SMTPD_STATE *state, int expect, const char *fmt,...) { va_list ap; /* * Errors first. */ if (vstream_ferror(smtpd_proxy_replay_stream) || vstream_feof(smtpd_proxy_replay_stream)) return (smtpd_proxy_replay_rdwr_error(state)); /* * Save the expected reply first, so that the replayer can safely * overwrite the input buffer with the command. */ rec_fprintf(smtpd_proxy_replay_stream, REC_TYPE_RCPT, "%d", expect); /* * The command can be omitted at the start of an SMTP session. This is * not documented as part of the official interface because it is used * only internally to this module. Use an explicit null string in case * the SMTPD_PROXY_CONN_FMT implementation details change. */ if (fmt == SMTPD_PROXY_CONN_FMT) fmt = ""; /* * Save the command to the replay log, and send it to the before-queue * filter after we have received the entire message. */ va_start(ap, fmt); rec_vfprintf(smtpd_proxy_replay_stream, REC_TYPE_FROM, fmt, ap); va_end(ap); /* * If we just saved the "." command, replay the log. */ return (strcmp(fmt, ".") ? 0 : smtpd_proxy_replay_send(state)); }
void smtpd_proxy_close(SMTPD_STATE *state) { SMTPD_PROXY *proxy = state->proxy; /* * XXX We can't send QUIT if the stream is still good, because that would * overwrite the last server reply in proxy->buffer. We probably should * just bite the bullet and allocate separate buffers for sending and * receiving. */ if (proxy->service_stream != 0) { #if 0 if (vstream_feof(proxy->service_stream) == 0 && vstream_ferror(proxy->service_stream) == 0) (void) smtpd_proxy_cmd(state, SMTPD_PROX_WANT_NONE, SMTPD_CMD_QUIT); #endif (void) vstream_fclose(proxy->service_stream); if (proxy->stream == proxy->service_stream) proxy->stream = 0; proxy->service_stream = 0; } }
int recv_pass_attr(int fd, HTABLE **attr, int timeout, ssize_t bufsize) { VSTREAM *fp; int stream_err; /* * Set up a temporary VSTREAM to receive the attributes. * * XXX We use one-character reads to simplify the implementation. */ fp = vstream_fdopen(fd, O_RDWR); vstream_control(fp, VSTREAM_CTL_BUFSIZE, bufsize, VSTREAM_CTL_TIMEOUT, timeout, VSTREAM_CTL_START_DEADLINE, VSTREAM_CTL_END); (void) attr_scan(fp, ATTR_FLAG_NONE, ATTR_TYPE_HASH, *attr = htable_create(1), ATTR_TYPE_END); stream_err = (vstream_feof(fp) || vstream_ferror(fp)); vstream_fdclose(fp); /* * Error reporting and recovery. */ if (stream_err) { htable_free(*attr, myfree); *attr = 0; return (-1); } else { if ((*attr)->used == 0) { htable_free(*attr, myfree); *attr = 0; } return (0); } }
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 void smtp_connect_local(SMTP_STATE *state, const char *path) { const char *myname = "smtp_connect_local"; SMTP_ITERATOR *iter = state->iterator; SMTP_SESSION *session; DSN_BUF *why = state->why; /* * Do not silently ignore an unused setting. */ if (*var_fallback_relay) msg_warn("ignoring \"%s = %s\" setting for non-TCP connections", VAR_LMTP_FALLBACK, var_fallback_relay); /* * It's too painful to weave this code into the SMTP connection * management routine. * * Connection cache management is based on the UNIX-domain pathname, without * the "unix:" prefix. */ smtp_cache_policy(state, path); /* * Here we ensure that the iter->addr member refers to a copy of the * UNIX-domain pathname, so that smtp_save_session() will cache the * connection using the pathname as the physical endpoint name. * * We set dest=path for backwards compatibility. */ #define NO_PORT 0 SMTP_ITER_INIT(iter, path, var_myhostname, path, NO_PORT, state); /* * Opportunistic TLS for unix domain sockets does not make much sense, * since the channel is private, mere encryption without authentication * is just wasted cycles and opportunity for breakage. Since we are not * willing to retry after TLS handshake failures here, we downgrade "may" * no "none". Nothing is lost, and much waste is avoided. * * We don't know who is authenticating whom, so if a client cert is * available, "encrypt" may be a sensible policy. Otherwise, we also * downgrade "encrypt" to "none", this time just to avoid waste. * * We use smtp_reuse_nexthop() instead of smtp_reuse_addr(), so that we can * reuse a SASL-authenticated connection (however unlikely this scenario * may be). The smtp_reuse_addr() interface currently supports only reuse * of SASL-unauthenticated connections. */ #ifdef USE_TLS if (!smtp_tls_policy_cache_query(why, state->tls, iter)) { msg_warn("TLS policy lookup error for %s/%s: %s", STR(iter->host), STR(iter->addr), STR(why->reason)); return; } #endif if ((state->misc_flags & SMTP_MISC_FLAG_CONN_LOAD) == 0 || (session = smtp_reuse_nexthop(state, SMTP_KEY_MASK_SCACHE_DEST_LABEL)) == 0) session = smtp_connect_unix(iter, why, state->misc_flags); if ((state->session = session) != 0) { session->state = state; #ifdef USE_TLS session->tls_nexthop = var_myhostname; /* for TLS_LEV_SECURE */ if (state->tls->level == TLS_LEV_MAY) { msg_warn("%s: opportunistic TLS encryption is not appropriate " "for unix-domain destinations.", myname); state->tls->level = TLS_LEV_NONE; } #endif /* All delivery errors bounce or defer. */ state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER; /* * When a TLS handshake fails, the stream is marked "dead" to avoid * further I/O over a broken channel. */ if ((session->features & SMTP_FEATURE_FROM_CACHE) == 0 && smtp_helo(state) != 0) { if (!THIS_SESSION_IS_FORBIDDEN && vstream_ferror(session->stream) == 0 && vstream_feof(session->stream) == 0) smtp_quit(state); } else { smtp_xfer(state); } /* * With opportunistic TLS disabled we don't expect to be asked to * retry connections without TLS, and so we expect the final server * flag to stay on. */ if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_SERVER) == 0) msg_panic("%s: unix-domain destination not final!", myname); smtp_cleanup_session(state); } }
static void smtp_cleanup_session(SMTP_STATE *state) { DELIVER_REQUEST *request = state->request; SMTP_SESSION *session = state->session; int throttled; /* * Inform the postmaster of trouble. * * XXX Don't send notifications about errors while sending notifications. */ #define POSSIBLE_NOTIFICATION(sender) \ (*sender == 0 || strcmp(sender, mail_addr_double_bounce()) == 0) if (session->history != 0 && (session->error_mask & name_mask(VAR_NOTIFY_CLASSES, mail_error_masks, var_notify_classes)) != 0 && POSSIBLE_NOTIFICATION(request->sender) == 0) smtp_chat_notify(session); /* * When session caching is enabled, cache the first good session for this * delivery request under the next-hop destination, and cache all good * sessions under their server network address (destroying the session in * the process). * * Caching under the next-hop destination name (rather than the fall-back * destination) allows us to skip over non-responding primary or backup * hosts. In fact, this is the only benefit of caching logical to * physical bindings; caching a session under its own hostname provides * no performance benefit, given the way smtp_connect() works. */ throttled = THIS_SESSION_IS_THROTTLED; /* smtp_quit() may fail */ if (THIS_SESSION_IS_EXPIRED) smtp_quit(state); /* also disables caching */ if (THIS_SESSION_IS_CACHED /* Redundant tests for safety... */ && vstream_ferror(session->stream) == 0 && vstream_feof(session->stream) == 0) { smtp_save_session(state, SMTP_KEY_MASK_SCACHE_DEST_LABEL, SMTP_KEY_MASK_SCACHE_ENDP_LABEL); } else { smtp_session_free(session); } state->session = 0; /* * If this session was good, reset the logical next-hop state, so that we * won't cache connections to alternate servers under the logical * next-hop destination. Otherwise we could end up skipping over the * available and more preferred servers. */ if (HAVE_NEXTHOP_STATE(state) && !throttled) FREE_NEXTHOP_STATE(state); /* * Clean up the lists with todo and dropped recipients. */ smtp_rcpt_cleanup(state); /* * Reset profiling info. * * XXX When one delivery request results in multiple sessions, the set-up * and transmission latencies of the earlier sessions will count as * connection set-up time for the later sessions. * * XXX On the other hand, when we first try to connect to one or more dead * hosts before we reach a good host, then all that time must be counted * as connection set-up time for the session with the good host. * * XXX So this set-up attribution problem exists only when we actually * engage in a session, spend a lot of time delivering a message, find * that it fails, and then connect to an alternate host. */ memset((void *) &request->msg_stats.conn_setup_done, 0, sizeof(request->msg_stats.conn_setup_done)); memset((void *) &request->msg_stats.deliver_done, 0, sizeof(request->msg_stats.deliver_done)); request->msg_stats.reuse_count = 0; }
static void smtp_connect_remote(SMTP_STATE *state, const char *nexthop, char *def_service) { DELIVER_REQUEST *request = state->request; ARGV *sites; char *dest; char **cpp; int non_fallback_sites; int retry_plain = 0; DSN_BUF *why = state->why; /* * First try to deliver to the indicated destination, then try to deliver * to the optional fall-back relays. * * 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; if ((state->misc_flags & SMTP_MISC_FLAG_USE_LMTP) == 0) argv_split_append(sites, var_fallback_relay, ", \t\r\n"); /* * 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. Default is to use the SMTP port. Look up * the address instead of the mail exchanger when a quoted host is * specified, or when DNS lookups are disabled. */ dest_buf = smtp_parse_destination(dest, def_service, &domain, &port); if (var_helpful_warnings && ntohs(port) == 465) { msg_info("CLIENT wrappermode (port smtps/465) is unimplemented"); msg_info("instead, send to (port submission/587) with STARTTLS"); } /* * Resolve an SMTP server. 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 ((state->misc_flags & SMTP_MISC_FLAG_USE_LMTP) == 0) { if (ntohs(port) == IPPORT_SMTP) state->misc_flags |= SMTP_MISC_FLAG_LOOP_DETECT; else state->misc_flags &= ~SMTP_MISC_FLAG_LOOP_DETECT; lookup_mx = (var_disable_dns == 0 && *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, 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 Replace sites->argv by (lookup_mx, domain, port) triples so we * don't have to make clumsy ad-hoc copies and keep track of who * free()s the memory. * * 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. * * XXX Disable connection caching when sender-dependent authentication * is enabled. We must not send someone elses mail over an * authenticated connection, and we must not send mail that requires * authentication over a connection that wasn't authenticated. */ if (addr_list && (state->misc_flags & SMTP_MISC_FLAG_FIRST_NEXTHOP)) { smtp_cache_policy(state, domain); if (state->misc_flags & SMTP_MISC_FLAG_CONN_STORE) SET_NEXTHOP_STATE(state, lookup_mx, domain, port); } /* * 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, lookup_mx, domain, port, &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. */ 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 ((state->misc_flags & SMTP_MISC_FLAG_CONN_LOAD) == 0 || addr->pref == domain_best_pref || dns_rr_to_pa(addr, &hostaddr) == 0 || !(session = smtp_reuse_addr(state, hostaddr.buf, port))) session = smtp_connect_addr(dest, addr, port, why, state->misc_flags); if ((state->session = session) != 0) { session->state = state; 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; #ifdef USE_TLS /* Disable TLS when retrying after a handshake failure */ if (retry_plain) { if (session->tls_level >= TLS_LEV_ENCRYPT) msg_panic("Plain-text retry wrong for mandatory TLS"); session->tls_level = TLS_LEV_NONE; retry_plain = 0; } session->tls_nexthop = domain; /* for TLS_LEV_SECURE */ #endif 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_DEAD && 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); } smtp_cleanup_session(state); } else { /* The reason already includes the IP address and TCP port. */ msg_info("%s", STR(why->reason)); } /* Insert: test if we must skip the remaining MX hosts. */ } dns_rr_free(addr_list); 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) && (state->misc_flags & SMTP_MISC_FLAG_USE_LMTP) == 0) { /* * 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. */ else if (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_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 void smtp_connect_local(SMTP_STATE *state, const char *path) { const char *myname = "smtp_connect_local"; SMTP_SESSION *session; DSN_BUF *why = state->why; /* * It's too painful to weave this code into the SMTP connection * management routine. * * Connection cache management is based on the UNIX-domain pathname, without * the "unix:" prefix. */ smtp_cache_policy(state, path); /* * XXX We assume that the session->addr member refers to a copy of the * UNIX-domain pathname, so that smtp_save_session() will cache the * connection using the pathname as the physical endpoint name. */ #define NO_PORT 0 /* * Opportunistic TLS for unix domain sockets does not make much sense, * since the channel is private, mere encryption without authentication * is just wasted cycles and opportunity for breakage. Since we are not * willing to retry after TLS handshake failures here, we downgrade "may" * no "none". Nothing is lost, and much waste is avoided. * * We don't know who is authenticating whom, so if a client cert is * available, "encrypt" may be a sensible policy. Otherwise, we also * downgrade "encrypt" to "none", this time just to avoid waste. */ if ((state->misc_flags & SMTP_MISC_FLAG_CONN_LOAD) == 0 || (session = smtp_reuse_addr(state, path, NO_PORT)) == 0) session = smtp_connect_unix(path, why, state->misc_flags); if ((state->session = session) != 0) { session->state = state; #ifdef USE_TLS session->tls_nexthop = var_myhostname; /* for TLS_LEV_SECURE */ if (session->tls_level == TLS_LEV_MAY) { msg_warn("%s: opportunistic TLS encryption is not appropriate " "for unix-domain destinations.", myname); session->tls_level = TLS_LEV_NONE; } #endif /* All delivery errors bounce or defer. */ state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER; /* * When a TLS handshake fails, the stream is marked "dead" to avoid * further I/O over a broken channel. */ if ((session->features & SMTP_FEATURE_FROM_CACHE) == 0 && smtp_helo(state) != 0) { if (!THIS_SESSION_IS_DEAD && vstream_ferror(session->stream) == 0 && vstream_feof(session->stream) == 0) smtp_quit(state); } else { smtp_xfer(state); } /* * With opportunistic TLS disabled we don't expect to be asked to * retry connections without TLS, and so we expect the final server * flag to stay on. */ if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_SERVER) == 0) msg_panic("%s: unix-domain destination not final!", myname); smtp_cleanup_session(state); } }
static int smtpd_proxy_cmd(SMTPD_STATE *state, int expect, const char *fmt,...) { SMTPD_PROXY *proxy = state->proxy; va_list ap; char *cp; int last_char; int err = 0; static VSTRING *buffer = 0; /* * Errors first. Be prepared for delayed errors from the DATA phase. */ if (vstream_ferror(proxy->service_stream) || vstream_feof(proxy->service_stream) || (err = vstream_setjmp(proxy->service_stream)) != 0) { return (smtpd_proxy_rdwr_error(state, err)); } /* * The command can be omitted at the start of an SMTP session. This is * not documented as part of the official interface because it is used * only internally to this module. */ if (fmt != SMTPD_PROXY_CONN_FMT) { /* * Format the command. */ va_start(ap, fmt); vstring_vsprintf(proxy->request, fmt, ap); va_end(ap); /* * Optionally log the command first, so that we can see in the log * what the program is trying to do. */ if (msg_verbose) msg_info("> %s: %s", proxy->service_name, STR(proxy->request)); /* * Send the command to the proxy server. Since we're going to read a * reply immediately, there is no need to flush buffers. */ smtp_fputs(STR(proxy->request), LEN(proxy->request), proxy->service_stream); } /* * Early return if we don't want to wait for a server reply (such as * after sending QUIT). */ if (expect == SMTPD_PROX_WANT_NONE) return (0); /* * Censor out non-printable characters in server responses and save * complete multi-line responses if possible. * * 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(proxy->reply); if (buffer == 0) buffer = vstring_alloc(10); for (;;) { last_char = smtp_get(buffer, proxy->service_stream, var_line_limit, SMTP_GET_FLAG_SKIP); printable(STR(buffer), '?'); if (last_char != '\n') msg_warn("%s: response longer than %d: %.30s...", proxy->service_name, var_line_limit, STR(buffer)); if (msg_verbose) msg_info("< %s: %.100s", proxy->service_name, STR(buffer)); /* * Defend against a denial of service attack by limiting the amount * of multi-line text that we are willing to store. */ if (LEN(proxy->reply) < var_line_limit) { if (VSTRING_LEN(proxy->reply)) vstring_strcat(proxy->reply, "\r\n"); vstring_strcat(proxy->reply, STR(buffer)); } /* * Parse the response into code and text. Ignore unrecognized * garbage. This means that any character except space (or end of * line) will have the same effect as the '-' line continuation * character. */ for (cp = STR(buffer); *cp && ISDIGIT(*cp); cp++) /* void */ ; if (cp - STR(buffer) == 3) { if (*cp == '-') continue; if (*cp == ' ' || *cp == 0) break; } msg_warn("received garbage from proxy %s: %.100s", proxy->service_name, STR(buffer)); } /* * Log a warning in case the proxy does not send the expected response. * Silently accept any response when the client expressed no expectation. * * Starting with Postfix 2.6 we don't pass through unexpected 2xx or 3xx * proxy replies. They are a source of support problems, so we replace * them by generic server error replies. */ if (expect != SMTPD_PROX_WANT_ANY && expect != *STR(proxy->reply)) { msg_warn("proxy %s rejected \"%s\": \"%s\"", proxy->service_name, fmt == SMTPD_PROXY_CONN_FMT ? "connection request" : STR(proxy->request), STR(proxy->reply)); if (*STR(proxy->reply) == SMTPD_PROX_WANT_OK || *STR(proxy->reply) == SMTPD_PROX_WANT_MORE) { smtpd_proxy_rdwr_error(state, 0); } return (-1); } else { return (0); } }
static int smtpd_proxy_replay_send(SMTPD_STATE *state) { const char *myname = "smtpd_proxy_replay_send"; static VSTRING *replay_buf = 0; SMTPD_PROXY *proxy = state->proxy; int rec_type; int expect = SMTPD_PROX_WANT_BAD; /* * Sanity check. */ if (smtpd_proxy_replay_stream == 0) msg_panic("%s: no before-queue filter speed-adjust log", myname); /* * Errors first. */ if (vstream_ferror(smtpd_proxy_replay_stream) || vstream_feof(smtpd_proxy_replay_stream) || rec_put(smtpd_proxy_replay_stream, REC_TYPE_END, "", 0) != REC_TYPE_END || vstream_fflush(smtpd_proxy_replay_stream)) /* NOT: fsync(vstream_fileno(smtpd_proxy_replay_stream)) */ return (smtpd_proxy_replay_rdwr_error(state)); /* * Delayed connection to the before-queue filter. */ if (smtpd_proxy_connect(state) < 0) return (-1); /* * Replay the speed-match log. We do sanity check record content, but we * don't implement a protocol state engine here, since we are reading * from a file that we just wrote ourselves. * * This is different than the MailChannels patented solution that * multiplexes a large number of slowed-down inbound connections over a * small number of fast connections to a local MTA. * * - MailChannels receives mail directly from the Internet. It uses one * connection to the local MTA to reject invalid recipients before * receiving the entire email message at reduced bit rates, and then uses * a different connection to quickly deliver the message to the local * MTA. * * - Postfix receives mail directly from the Internet. The Postfix SMTP * server rejects invalid recipients before receiving the entire message * over the Internet, and then delivers the message quickly to a local * SMTP-based content filter. */ if (replay_buf == 0) replay_buf = vstring_alloc(100); if (vstream_fseek(smtpd_proxy_replay_stream, (off_t) 0, SEEK_SET) < 0) return (smtpd_proxy_replay_rdwr_error(state)); for (;;) { switch (rec_type = rec_get(smtpd_proxy_replay_stream, replay_buf, REC_FLAG_NONE)) { /* * Message content. */ case REC_TYPE_NORM: case REC_TYPE_CONT: if (smtpd_proxy_rec_put(proxy->service_stream, rec_type, STR(replay_buf), LEN(replay_buf)) < 0) return (-1); break; /* * Expected server reply type. */ case REC_TYPE_RCPT: if (!alldig(STR(replay_buf)) || (expect = atoi(STR(replay_buf))) == SMTPD_PROX_WANT_BAD) msg_panic("%s: malformed server reply type: %s", myname, STR(replay_buf)); break; /* * Client command, or void. Bail out on the first negative proxy * response. This is OK, because the filter must use the same * reply code for all recipients of a multi-recipient message. */ case REC_TYPE_FROM: if (expect == SMTPD_PROX_WANT_BAD) msg_panic("%s: missing server reply type", myname); if (smtpd_proxy_cmd(state, expect, *STR(replay_buf) ? "%s" : SMTPD_PROXY_CONN_FMT, STR(replay_buf)) < 0) return (-1); expect = SMTPD_PROX_WANT_BAD; break; /* * Explicit end marker, instead of implicit EOF. */ case REC_TYPE_END: return (0); /* * Errors. */ case REC_TYPE_ERROR: return (smtpd_proxy_replay_rdwr_error(state)); default: msg_panic("%s: unexpected record type; %d", myname, rec_type); } } }
static int smtpd_proxy_replay_send(SMTPD_STATE *state) { const char *myname = "smtpd_proxy_replay_send"; static VSTRING *replay_buf = 0; SMTPD_PROXY *proxy = state->proxy; int rec_type; int expect = SMTPD_PROX_WANT_BAD; /* * Sanity check. */ if (smtpd_proxy_replay_stream == 0) msg_panic("%s: no before-queue filter speed-adjust log", myname); /* * Errors first. */ if (vstream_ferror(smtpd_proxy_replay_stream) || vstream_feof(smtpd_proxy_replay_stream) || rec_put(smtpd_proxy_replay_stream, REC_TYPE_END, "", 0) != REC_TYPE_END || vstream_fflush(smtpd_proxy_replay_stream)) /* NOT: fsync(vstream_fileno(smtpd_proxy_replay_stream)) */ return (smtpd_proxy_replay_rdwr_error(state)); /* * Delayed connection to the before-queue filter. */ if (smtpd_proxy_connect(state) < 0) return (-1); /* * Replay the speed-match log. We do sanity check record content, but we * don't implement a protocol state engine here, since we are reading * from a file that we just wrote ourselves. */ if (replay_buf == 0) replay_buf = vstring_alloc(100); if (vstream_fseek(smtpd_proxy_replay_stream, (off_t) 0, SEEK_SET) < 0) return (smtpd_proxy_replay_rdwr_error(state)); for (;;) { switch (rec_type = rec_get(smtpd_proxy_replay_stream, replay_buf, REC_FLAG_NONE)) { /* * Message content. */ case REC_TYPE_NORM: case REC_TYPE_CONT: if (smtpd_proxy_rec_put(proxy->service_stream, rec_type, STR(replay_buf), LEN(replay_buf)) < 0) return (-1); break; /* * Expected server reply type. */ case REC_TYPE_RCPT: if (!alldig(STR(replay_buf)) || (expect = atoi(STR(replay_buf))) == SMTPD_PROX_WANT_BAD) msg_panic("%s: malformed server reply type: %s", myname, STR(replay_buf)); break; /* * Client command, or void. Bail out on the first negative proxy * response. This is OK, because the filter must use the same * reply code for all recipients of a multi-recipient message. */ case REC_TYPE_FROM: if (expect == SMTPD_PROX_WANT_BAD) msg_panic("%s: missing server reply type", myname); if (smtpd_proxy_cmd(state, expect, *STR(replay_buf) ? "%s" : SMTPD_PROXY_CONN_FMT, STR(replay_buf)) < 0) return (-1); expect = SMTPD_PROX_WANT_BAD; break; /* * Explicit end marker, instead of implicit EOF. */ case REC_TYPE_END: return (0); /* * Errors. */ case REC_TYPE_ERROR: return (smtpd_proxy_replay_rdwr_error(state)); default: msg_panic("%s: unexpected record type; %d", myname, rec_type); } } }
int smtp_get(VSTRING *vp, VSTREAM *stream, int bound) { int last_char; int next_char; /* * It's painful to do I/O with records that may span multiple buffers. * Allow for partial long lines (we will read the remainder later) and * allow for lines ending in bare LF. The idea is to be liberal in what * we accept, strict in what we send. * * XXX 2821: Section 4.1.1.4 says that an SMTP server must not recognize * bare LF as record terminator. */ smtp_timeout_reset(stream); last_char = (bound == 0 ? vstring_get(vp, stream) : vstring_get_bound(vp, stream, bound)); switch (last_char) { /* * Do some repair in the rare case that we stopped reading in the * middle of the CRLF record terminator. */ case '\r': if ((next_char = VSTREAM_GETC(stream)) == '\n') { VSTRING_ADDCH(vp, '\n'); last_char = '\n'; /* FALLTRHOUGH */ } else { if (next_char != VSTREAM_EOF) vstream_ungetc(stream, next_char); break; } /* * Strip off the record terminator: either CRLF or just bare LF. * * XXX RFC 2821 disallows sending bare CR everywhere. We remove bare CR * if received before CRLF, and leave it alone otherwise. */ case '\n': vstring_truncate(vp, VSTRING_LEN(vp) - 1); while (VSTRING_LEN(vp) > 0 && vstring_end(vp)[-1] == '\r') vstring_truncate(vp, VSTRING_LEN(vp) - 1); VSTRING_TERMINATE(vp); /* * Partial line: just read the remainder later. If we ran into EOF, * the next test will deal with it. */ default: break; } smtp_timeout_detect(stream); /* * EOF is bad, whether or not it happens in the middle of a record. Don't * allow data that was truncated because of EOF. */ if (vstream_feof(stream) || vstream_ferror(stream)) { if (msg_verbose) msg_info("smtp_get: EOF"); vstream_longjmp(stream, SMTP_ERR_EOF); } return (last_char); }