/*++
/* NAME
/* smtp_connect 3
/* SUMMARY
/* connect to SMTP/LMTP server and deliver
/* SYNOPSIS
/* #include "smtp.h"
/*
/* int smtp_connect(state)
/* SMTP_STATE *state;
/* DESCRIPTION
/* This module implements SMTP/LMTP connection management and controls
/* mail delivery.
/*
/* smtp_connect() attempts to establish an SMTP/LMTP session with a host
/* that represents the destination domain, or with an optional fallback
/* relay when {the destination cannot be found, or when all the
/* destination servers are unavailable}. It skips over IP addresses
/* that fail to complete the SMTP/LMTP handshake and tries to find
/* an alternate server when an SMTP/LMTP session fails to deliver.
/*
/* This layer also controls what connections are retrieved from
/* the connection cache, and what connections are saved to the cache.
/*
/* The destination is either a host (or domain) name or a numeric
/* address. Symbolic or numeric service port information may be
/* appended, separated by a colon (":"). In the case of LMTP,
/* destinations may be specified as "unix:pathname", "inet:host"
/* or "inet:host:port".
/*
/* With SMTP, or with SRV record lookup enabled, the Internet
/* domain name service is queried for mail
/* exchanger hosts. Quote the domain name with `[' and `]' to
/* suppress mail exchanger lookups.
/*
/* Numerical address information should always be quoted with `[]'.
/* DIAGNOSTICS
/* The delivery status is the result value.
/* SEE ALSO
/* smtp_proto(3) SMTP client protocol
/* 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
/*
/* Wietse Venema
/* Google, Inc.
/* 111 8th Avenue
/* New York, NY 10011, USA
/*
/* Connection caching in cooperation with:
/* Victor Duchovni
/* Morgan Stanley
/*--*/
/* System library. */
#include <sys_defs.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <netdb.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <ctype.h>
#ifndef IPPORT_SMTP
#define IPPORT_SMTP 25
#endif
/* Utility library. */
#include <msg.h>
#include <vstream.h>
#include <vstring.h>
#include <split_at.h>
#include <mymalloc.h>
#include <inet_addr_list.h>
#include <iostuff.h>
#include <timed_connect.h>
#include <stringops.h>
#include <host_port.h>
#include <sane_connect.h>
#include <myaddrinfo.h>
#include <sock_addr.h>
#include <inet_proto.h>
#include <known_tcp_ports.h>
/* Global library. */
#include <mail_params.h>
#include <own_inet_addr.h>
#include <deliver_pass.h>
#include <mail_error.h>
#include <dsn_buf.h>
#include <mail_addr.h>
#include <valid_hostname.h>
#include <sendopts.h>
/* DNS library. */
#include <dns.h>
/* Application-specific. */
#include <smtp.h>
#include <smtp_addr.h>
#include <smtp_reuse.h>
/*
* Forward declaration .
*/
static SMTP_SESSION *smtp_connect_sock(int , struct sockaddr *, int ,
SMTP_ITERATOR *, DSN_BUF *,
int );
/* smtp_connect_unix - connect to UNIX-domain address */
static SMTP_SESSION *smtp_connect_unix(SMTP_ITERATOR *iter, DSN_BUF *why,
int sess_flags)
{
const char *myname = "smtp_connect_unix" ;
struct sockaddr_un sock_un;
const char *addr = STR(iter->addr);
int len = strlen(addr);
int sock;
dsb_reset(why); /* Paranoia */
/*
* Sanity checks .
*/
if (len >= (int ) sizeof (sock_un.sun_path)) {
msg_warn("unix-domain name too long: %s" , addr);
dsb_simple(why, "4.3.5" , "Server configuration error" );
return (0 );
}
/*
* Initialize .
*/
memset((void *) &sock_un, 0 , sizeof (sock_un));
sock_un.sun_family = AF_UNIX;
#ifdef HAS_SUN_LEN
sock_un.sun_len = len + 1 ;
#endif
memcpy(sock_un.sun_path, addr, len + 1 );
/*
* Create a client socket .
*/
if ((sock = socket(AF_UNIX, SOCK_STREAM, 0 )) < 0 )
msg_fatal("%s: socket: %m" , myname);
/*
* Connect to the server .
*/
if (msg_verbose)
msg_info("%s: trying: %s..." , myname, addr);
return (smtp_connect_sock(sock, (struct sockaddr *) &sock_un,
sizeof (sock_un), iter, why, sess_flags));
}
/* smtp_connect_addr - connect to explicit address */
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;
char *saved_bind_addr = 0 ;
char *tail;
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);
#define RETURN_EARLY() do { \
if (saved_bind_addr) \
myfree(saved_bind_addr); \
(void ) close(sock); \
return (0 ); \
} while (0 )
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 (*bind_addr == '[' ) {
saved_bind_addr = mystrdup(bind_addr + 1 );
if ((tail = split_at(saved_bind_addr, ']' )) == 0 || *tail)
msg_fatal("%s: malformed %s parameter: %s" ,
myname, bind_var, bind_addr);
bind_addr = saved_bind_addr;
}
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);
if (var_smtp_bind_addr_enforce) {
freeaddrinfo(res0);
dsb_simple(why, "4.4.0" , "server configuration error" );
RETURN_EARLY();
}
} else if (msg_verbose)
msg_info("%s: bind %s" , myname, bind_addr);
if (saved_bind_addr)
myfree(saved_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));
}
/* smtp_connect_sock - connect a socket over some transport */
static SMTP_SESSION *smtp_connect_sock(int sock, struct sockaddr *sa,
int salen,
SMTP_ITERATOR *iter,
DSN_BUF *why,
int sess_flags)
{
int conn_stat;
int saved_errno;
VSTREAM *stream;
time_t start_time;
const char *name = STR(iter->host);
const char *addr = STR(iter->addr);
unsigned port = iter->port;
start_time = time((time_t *) 0 );
if (var_smtp_conn_tmout > 0 ) {
non_blocking(sock, NON_BLOCKING);
conn_stat = timed_connect(sock, sa, salen, var_smtp_conn_tmout);
saved_errno = errno;
non_blocking(sock, BLOCKING);
errno = saved_errno;
} else {
conn_stat = sane_connect(sock, sa, salen);
}
if (conn_stat < 0 ) {
if (port)
dsb_simple(why, "4.4.1" , "connect to %s[%s]:%d: %m" ,
name, addr, ntohs(port));
else
dsb_simple(why, "4.4.1" , "connect to %s[%s]: %m" , name, addr);
close(sock);
return (0 );
}
stream = vstream_fdopen(sock, O_RDWR);
/*
* Avoid poor performance when TCP MSS > VSTREAM_BUFSIZE .
*/
if (sa->sa_family == AF_INET
#ifdef AF_INET6
|| sa->sa_family == AF_INET6
#endif
)
vstream_tweak_tcp(stream);
/*
* Bundle up what we have into a nice SMTP_SESSION object .
*/
return (smtp_session_alloc(stream, iter, start_time, sess_flags));
}
/* smtp_parse_destination - parse host/port destination */
static char *smtp_parse_destination(char *destination, char *def_service,
char **hostp, char **servicep,
unsigned *portp)
{
char *buf = mystrdup(destination);
char *service;
struct servent *sp;
char *protocol = "tcp" ; /* XXX configurable? */
unsigned port;
const char *err;
if (msg_verbose)
msg_info("smtp_parse_destination: %s %s" , destination, def_service);
/*
* Parse the host / port information . We ' re working with a copy of the
* destination argument so the parsing can be destructive .
*/
if ((err = host_port(buf, hostp, (char *) 0 , servicep, def_service)) != 0 )
msg_fatal("%s in server description: %s" , err, destination);
/*
* Convert service to port number , network byte order .
*/
service = (char *) filter_known_tcp_port(*servicep);
if (alldig(service)) {
if ((port = atoi(service)) >= 65536 || port == 0 )
msg_fatal("bad network port: %s for destination: %s" ,
service, destination);
*portp = htons(port);
} else {
if ((sp = getservbyname(service, protocol)) == 0 )
msg_fatal("unknown service: %s/%s" , service, protocol);
*portp = sp->s_port;
}
return (buf);
}
/* smtp_cleanup_session - clean up after using a session */
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 scache next - hop destination , so
* that we won ' t cache connections to less - preferred servers under the
* same next - hop destination . Otherwise we could end up skipping over the
* available and more - preferred servers .
*/
if (HAVE_SCACHE_REQUEST_NEXTHOP(state) && !throttled)
CLEAR_SCACHE_REQUEST_NEXTHOP(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_cache_policy(SMTP_STATE *state, const char *dest)
{
DELIVER_REQUEST *request = state->request;
state->misc_flags &= ~SMTP_MISC_FLAG_CONN_CACHE_MASK;
if (smtp_cache_dest && string_list_match(smtp_cache_dest, dest)) {
state->misc_flags |= SMTP_MISC_FLAG_CONN_CACHE_MASK;
} else if (var_smtp_cache_demand) {
if (request->flags & DEL_REQ_FLAG_CONN_LOAD)
state->misc_flags |= SMTP_MISC_FLAG_CONN_LOAD;
if (request->flags & DEL_REQ_FLAG_CONN_STORE)
state->misc_flags |= SMTP_MISC_FLAG_CONN_STORE;
}
}
#ifdef USE_TLS
/* smtp_get_effective_tls_level - get the effective TLS security level */
static int smtp_get_effective_tls_level(DSN_BUF *why, SMTP_STATE *state)
{
SMTP_ITERATOR *iter = state->iterator;
SMTP_TLS_POLICY *tls = state->tls;
/*
* Prepare TLS feature status logging .
*/
if (state->tls_stats) {
pol_stats_revert(state->tls_stats);
if (state->reqtls_level > SMTP_REQTLS_POLICY_ACT_DISABLE)
smtp_tls_stat_activate_reqtls(state->tls_stats,
SMTP_TLS_STAT_NAME_REQTLS);
}
/*
* Determine the TLS level for this destination .
*/
if (!smtp_tls_policy_cache_query(why, tls, iter)) {
if (state->tls_stats)
smtp_tls_stat_activate_sec_unknown(state->tls_stats);
return (0 );
}
if (state->tls_stats)
smtp_tls_stat_activate_sec_level(state->tls_stats,
state->tls->level);
/*
* Skip this destination if its TLS policy cannot satisfy the REQUIRETLS
* policy for this destination ( REQUIRETLS Failure ) .
*
* Otherwise , log what would fail if REQUIRETLS was fully enforced
* ( REQUIRETLS Debug ) .
*
* Finally , skip this destination if its REQUIRETLS policy is bad .
*/
switch (state->reqtls_level) {
case SMTP_REQTLS_POLICY_ACT_ENFORCE:
if (TLS_MUST_MATCH(tls->level) == 0 ) {
if (state->tls_stats)
smtp_tls_stat_decide_reqtls(state->tls_stats,
SMTP_TLS_STAT_NAME_NOCMATCH,
POL_STAT_VIOLATION);
dsb_simple(why, "5.7.10" , "Sender requested REQUIRETLS, "
"but my configured TLS security level '%s' "
"disables certificate matching. The last "
"attempted server was %s" , str_tls_level(tls->level),
STR(iter->host));
return (0 );
}
break ;
case SMTP_REQTLS_POLICY_ACT_OPP_TLS:
if (tls->level == TLS_LEV_NONE) {
if (state->tls_stats)
smtp_tls_stat_decide_reqtls(state->tls_stats,
SMTP_TLS_STAT_NAME_NOTLS,
POL_STAT_VIOLATION);
dsb_simple(why, "5.7.10" , "Sender requested REQUIRETLS, "
"but my configured TLS security level '%s' "
"disables encryption. The last attempted "
"server was %s" , str_tls_level(tls->level),
STR(iter->host));
return (0 );
} else if (TLS_MUST_MATCH(tls->level) == 0 ) {
msg_info("%s: Sender requested REQUIRETLS, but my "
"configured TLS security level '%s' disables "
"certificate matching. The last attempted server "
"was %s" , state->request->queue_id,
str_tls_level(tls->level), STR(iter->host));
}
break ;
case SMTP_REQTLS_POLICY_ACT_OPPORTUNISTIC:
case SMTP_REQTLS_POLICY_ACT_DISABLE:
break ;
default :
dsb_simple(why, "4.7.10" , "REQUIRETLS policy configuration "
"error. The last attempted server was %s" ,
STR(iter->host));
return (0 );
}
/*
* Success .
*/
return (1 );
}
#endif
/* smtp_connect_local - connect to local server */
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);
if (state->misc_flags & SMTP_MISC_FLAG_CONN_CACHE_MASK)
SET_SCACHE_REQUEST_NEXTHOP(state, path);
/*
* REQUIRETLS policy selection is based on the same TLS net - hop name as
* with certificate matching . When var_reqtls_enable ! = 0 ,
* smtp_reqtls_policy must also be ! = 0 .
*/
#ifdef USE_TLS
if (STATE_REQTLS_IS_REQUESTED(var_reqtls_enable, state))
state->reqtls_level =
smtp_reqtls_policy_eval(smtp_reqtls_policy, var_myhostname);
else
state->reqtls_level = SMTP_REQTLS_POLICY_ACT_DISABLE;
#endif
/*
* 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 .
*
* If a client cert is available , " encrypt " may be a sensible policy .
* Without client cert , " encrypt " and " may " over UNIX - domain sockets are
* not useful .
*
* 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_get_effective_tls_level(why, state)) {
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);
}
/*
* Cleanup .
*/
if (HAVE_SCACHE_REQUEST_NEXTHOP(state))
CLEAR_SCACHE_REQUEST_NEXTHOP(state);
}
/* smtp_scrub_address_list - delete all cached addresses from list */
static void smtp_scrub_addr_list(HTABLE *cached_addr, DNS_RR **addr_list)
{
MAI_HOSTADDR_STR hostaddr;
DNS_RR *addr;
DNS_RR *next;
/*
* XXX Extend the DNS_RR structure with fields for the printable address
* and / or binary sockaddr representations , so that we can avoid repeated
* binary - > string transformations for the same address .
*/
for (addr = *addr_list; addr; addr = next) {
next = addr->next;
if (dns_rr_to_pa(addr, &hostaddr) == 0 ) {
msg_warn("cannot convert type %s record to printable address" ,
dns_strtype(addr->type));
continue ;
}
if (htable_locate(cached_addr, hostaddr.buf))
*addr_list = dns_rr_remove(*addr_list, addr);
}
}
/* smtp_update_addr_list - common address list update */
static void smtp_update_addr_list(DNS_RR **addr_list, const char *server_addr,
int session_count)
{
DNS_RR *addr;
DNS_RR *next;
int aierr;
struct addrinfo *res0;
if (*addr_list == 0 )
return ;
/*
* Truncate the address list if we are not going to use it anyway .
*/
if (session_count == var_smtp_mxsess_limit
|| session_count == var_smtp_mxaddr_limit) {
dns_rr_free(*addr_list);
*addr_list = 0 ;
return ;
}
/*
* Convert server address to internal form , and look it up in the address
* list .
*
* XXX smtp_reuse_session ( ) breaks if we remove two or more adjacent list
* elements but do not truncate the list to zero length .
*
* XXX Extend the SMTP_SESSION structure with sockaddr information so that
* we can avoid repeated string - > binary transformations for the same
* address .
*
* XXX SRV support : this should match the port , too , otherwise we may
* eliminate too many list entries .
*/
if ((aierr = hostaddr_to_sockaddr(server_addr, (char *) 0 , 0 , &res0)) != 0 ) {
msg_warn("hostaddr_to_sockaddr %s: %s" ,
server_addr, MAI_STRERROR(aierr));
} else {
for (addr = *addr_list; addr; addr = next) {
next = addr->next;
if (DNS_RR_EQ_SA(addr, (struct sockaddr *) res0->ai_addr)) {
*addr_list = dns_rr_remove(*addr_list, addr);
break ;
}
}
freeaddrinfo(res0);
}
}
/* smtp_reuse_session - try to use existing connection, return session count */
static int smtp_reuse_session(SMTP_STATE *state, DNS_RR **addr_list,
int domain_best_pref)
{
int session_count = 0 ;
DNS_RR *addr;
DNS_RR *next;
MAI_HOSTADDR_STR hostaddr;
SMTP_SESSION *session;
SMTP_ITERATOR *iter = state->iterator;
DSN_BUF *why = state->why;
/*
* This code is called after server address / port lookup , before
* iter - > host , iter - > addr , iter - > rr and iter - > mx are assigned concrete
* values , and while iter - > port still corresponds to the nexthop service ,
* or the default service configured with smtp_tcp_port or lmtp_tcp_port .
*
* When a connection is reused by nexthop / service or by server address / port ,
* iter - > host , iter - > addr and iter - > port are updated with actual values
* from the cached session . Additionally , when a connection is searched
* by nexthop / service , iter - > rr remains null , and when a connection is
* searched by server address / port , iter - > rr is updated with an actual
* server address / port before the search is made .
*
* First , search the cache by delivery request nexthop . We truncate the
* server address list when all the sessions for this destination are
* used up , to reduce the number of variables that need to be checked
* later .
*
* Note : connection reuse by delivery request nexthop restores the " best MX "
* bit .
*
* smtp_reuse_nexthop ( ) clobbers the iterators ' s " dest " attribute . We save
* and restore it here , so that subsequent connections will use the
* proper nexthop information .
*
* We don ' t use TLS level info for nexthop - based connection cache storage
* keys . The combination of ( service , nexthop , etc . ) should be stable
* over the time range of interest , and the policy is still enforced on
* an individual connection to an MX host , before that connection is
* stored under a nexthop - or host - based storage key .
*/
#ifdef USE_TLS
smtp_tls_policy_dummy(state->tls);
#endif
SMTP_ITER_SAVE_DEST(state->iterator);
if (*addr_list && SMTP_RCPT_LEFT(state) > 0
&& HAVE_SCACHE_REQUEST_NEXTHOP(state)
&& (session = smtp_reuse_nexthop(state, SMTP_KEY_MASK_SCACHE_DEST_LABEL)) != 0 ) {
session_count = 1 ;
smtp_update_addr_list(addr_list, STR(iter->addr), session_count);
if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP)
&& *addr_list == 0 )
state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER;
smtp_xfer(state);
smtp_cleanup_session(state);
}
SMTP_ITER_RESTORE_DEST(state->iterator);
/*
* Second , search the cache by primary MX address . Again , we use address
* list truncation so that we have to check fewer variables later .
*
* XXX This loop is safe because smtp_update_addr_list ( ) either truncates
* the list to zero length , or removes at most one list element .
*
* 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 more precise control over reuse , the iterator should look up SASL and
* TLS policy as it evaluates mail exchangers in order , instead of
* relying on duplicate lookup request code in smtp_reuse ( 3 ) and
* smtp_session ( 3 ) .
*/
for (addr = *addr_list; SMTP_RCPT_LEFT(state) > 0 && addr; addr = next) {
if (addr->pref != domain_best_pref)
break ;
next = addr->next;
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 ;
}
SMTP_ITER_UPDATE_HOST(iter, SMTP_HNAME(addr), hostaddr.buf, addr);
#ifdef USE_TLS
if (!smtp_get_effective_tls_level(why, state)) {
msg_warn("TLS policy lookup error 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. */
}
#endif
if ((session = smtp_reuse_addr(state,
SMTP_KEY_MASK_SCACHE_ENDP_LABEL)) != 0 ) {
session->features |= SMTP_FEATURE_BEST_MX;
session_count += 1 ;
smtp_update_addr_list(addr_list, STR(iter->addr), session_count);
if (*addr_list == 0 )
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);
}
}
return (session_count);
}
/* smtp_connect_inet - establish network connection */
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 ;
}
/*
* Do a null destination sanity check in case the primary destination is
* a list that consists of only separators .
*/
sites = argv_split(nexthop, CHARS_COMMA_SP);
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;
char *service;
DNS_RR *addr_list;
DNS_RR *addr;
DNS_RR *next;
int addr_count;
int sess_count;
SMTP_SESSION *session;
int lookup_mx;
int non_dns_or_literal;
int i_am_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 ) .
*
* The ' service ' variable corresponds to the remote service specified
* with the nexthop , or the default service configured with
* smtp_tcp_port or lmtp_tcp_port . The ' port ' variable and
* SMTP_ITERATOR . port initially correspond to that service . This
* determines what loop prevention will be in effect .
*
* The SMTP_ITERATOR . port will be overwritten after SRV record lookup .
* This guarantees that the connection cache key contains the correct
* port value when caching and retrieving a connection by its server
* address ( and port ) .
*
* By design , the connection cache key contains NO port information when
* caching or retrieving a connection by its nexthop destination .
* Instead , the cache key contains the master . cf service name ( a
* proxy for all the parameter settings including the default service
* from smtp_tcp_port or lmtp_tcp_port ) , together with the nexthop
* destination and sender - dependent info . This should be sufficient
* to avoid cross talk between mail streams that should be separated .
*/
dest_buf = smtp_parse_destination(dest, def_service, &domain,
&service, &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);
/*
* TODO ( wietse ) If the domain publishes a TLSRPT policy , they expect
* that clients use SMTP over TLS . Should we upgrade a TLS security
* level of " may " to " encrypt " ? This would disable falling back to
* plaintext , and could break interoperability with receivers that
* crank up security up to 11 .
*
* With " TLS - Required : no " in effect , the SMTP client ignores the
* recipient - side policy mechanism TLSRPT , in addition to the already
* ignored DANE and MTA - STS mechanisms . This prevents TLSRPT
* notifications for all SMTP deliveries that do not require TLS .
*/
#ifdef USE_TLSRPT
if (smtp_mode && var_smtp_tlsrpt_enable
&& STATE_TLS_NOT_REQUIRED(state) == 0
&& tls_level_lookup(var_smtp_tls_level) > TLS_LEV_NONE
&& !valid_hostaddr(domain, DONT_GRIPE))
smtp_tlsrpt_create_wrapper(state, domain);
else
state->tlsrpt = 0 ;
#endif /* USE_TLSRPT */
/*
* REQUIRETLS policy selection is based on the same TLS net - hop name
* as with certificate matching . When var_reqtls_enable ! = 0 ,
* smtp_reqtls_policy must also be ! = 0 .
*/
#ifdef USE_TLS
if (STATE_REQTLS_IS_REQUESTED(var_reqtls_enable, state))
state->reqtls_level =
smtp_reqtls_policy_eval(smtp_reqtls_policy, domain);
else
state->reqtls_level = SMTP_REQTLS_POLICY_ACT_DISABLE;
#endif
/*
* Resolve an SMTP or LMTP server . Skip MX or SRV lookups when a
* quoted domain is specified or when DNS lookups are disabled .
*/
if (msg_verbose)
msg_info("connecting to %s service %s" , domain, service);
non_dns_or_literal = (smtp_dns_support == SMTP_DNS_DISABLED
|| *dest == '[' );
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 = !non_dns_or_literal;
} else
lookup_mx = 0 ;
/*
* Look up SRV and address records and fall back to non - SRV lookups
* if permitted by configuration settings , or look up MX and address
* records , or look up address records only .
*/
i_am_mx = 0 ;
addr_list = 0 ;
if (!non_dns_or_literal && smtp_use_srv_lookup
&& string_list_match(smtp_use_srv_lookup, service)) {
if (lookup_mx)
state->misc_flags |= SMTP_MISC_FLAG_FALLBACK_SRV_TO_MX;
else
state->misc_flags &= ~SMTP_MISC_FLAG_FALLBACK_SRV_TO_MX;
addr_list = smtp_service_addr(domain, service, &iter->mx,
state->misc_flags, why, &i_am_mx);
} else if (!lookup_mx) {
/* Non-DNS, literal, or non-SMTP service */
addr_list = smtp_host_addr(domain, state->misc_flags, why);
/* XXX We could be an MX host for this destination... */
} else {
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 connection caching is enabled , store the first good
* connection for this delivery request under the delivery request
* next - hop name . Good connections will also 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_SCACHE_REQUEST_NEXTHOP(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 ;
}
SMTP_ITER_UPDATE_HOST(iter, SMTP_HNAME(addr), hostaddr.buf, addr);
#ifdef USE_TLS
if (!smtp_get_effective_tls_level(why, state)) {
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. */
}
/* Skip MX hosts that lack authorization. */
if (!smtp_tls_authorize_mx_hostname(state->tls, SMTP_HNAME(addr))) {
continue ;
/* XXX Assume there is no code at the end of this loop. */
}
/*
* Disable TLS when retrying after a handshake failure . This must
* never happen when TLS is required . See PLAINTEXT_FALLBACK_OK
* macros .
*
* By dropping the TLS level after smtp_get_effective_tls_level ( )
* and smtp_tls_stat_activate_ * ( ) , we will properly record the
* fallback for the TLS level etc . in TLS status logging .
*/
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;
/*
* Update TLSRPT state even if this is a reused SMTP
* connection . If for some unlikely reason we must report a
* problem , then we must report correct information .
*/
#ifdef USE_TLSRPT
if (state->tlsrpt) {
smtp_tlsrpt_set_tls_policy(state);
smtp_tlsrpt_set_tcp_connection(state);
}
#endif /* USE_TLSRPT */
#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_SCACHE_REQUEST_NEXTHOP(state))
CLEAR_SCACHE_REQUEST_NEXTHOP(state);
argv_free(sites);
}
/* smtp_connect - establish SMTP connection */
int smtp_connect(SMTP_STATE *state)
{
DELIVER_REQUEST *request = state->request;
char *destination = request->nexthop;
/*
* All deliveries proceed along the same lines , whether they are over TCP
* or UNIX - domain sockets , and whether they use SMTP or LMTP : get a
* connection from the cache or create a new connection ; deliver mail ;
* update the connection cache or disconnect .
*
* The major differences appear at a higher level : the expansion from
* destination to address list , and whether to stop before we reach the
* end of that list .
*/
/*
* With LMTP we have direct - to - host delivery only . The destination may
* have multiple IP addresses .
*/
if (!smtp_mode) {
if (strncmp(destination, "unix:" , 5 ) == 0 ) {
smtp_connect_local(state, destination + 5 );
} else {
if (strncmp(destination, "inet:" , 5 ) == 0 )
destination += 5 ;
smtp_connect_inet(state, destination, var_smtp_tcp_port);
}
}
/*
* XXX We don ' t add support for " unix : " or " inet : " prefixes in SMTP
* destinations , because that would break compatibility with existing
* Postfix configurations that have a host with such a name .
*/
else {
smtp_connect_inet(state, destination, var_smtp_tcp_port);
}
/*
* We still need to bounce or defer some left - over recipients : either
* ( SMTP ) mail loops or some server was unavailable .
*
* We could avoid this ( and the " final server " complexity ) by keeping one
* DSN structure per recipient in memory , by updating those in - memory
* structures with each delivery attempt , and by always flushing all
* deferred recipients at the end . We ' d probably still want to bounce
* recipients immediately , so we ' d end up with another chunk of code for
* defer logging only .
*/
if (SMTP_RCPT_LEFT(state) > 0 ) {
state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER; /* XXX */
smtp_sess_fail(state);
/*
* Sanity check . Don ' t silently lose recipients .
*/
smtp_rcpt_cleanup(state);
if (SMTP_RCPT_LEFT(state) > 0 )
msg_panic("smtp_connect: left-over recipients" );
}
return (state->status);
}
Messung V0.5 in Prozent C=76 H=94 G=85
¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.29Angebot
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-08-08)
¤
*Eine klare Vorstellung vom Zielzustand