int smtp_stream_except(SMTP_STATE *state, int code, const char *description) { SMTP_SESSION *session = state->session; DSN_BUF *why = state->why; /* * Sanity check. */ if (session == 0) msg_panic("smtp_stream_except: no session"); /* * Initialize. */ switch (code) { default: msg_panic("smtp_stream_except: unknown exception %d", code); case SMTP_ERR_EOF: dsb_simple(why, "4.4.2", "lost connection with %s while %s", session->namaddr, description); break; case SMTP_ERR_TIME: dsb_simple(why, "4.4.2", "conversation with %s timed out while %s", session->namaddr, description); break; case SMTP_ERR_DATA: session->error_mask |= MAIL_ERROR_DATA; dsb_simple(why, "4.3.0", "local data error while talking to %s", session->namaddr); } return (smtp_bulk_fail(state, SMTP_THROTTLE)); }
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)); }
DNS_RR *smtp_host_addr(const char *host, int misc_flags, DSN_BUF *why) { DNS_RR *addr_list; dsb_reset(why); /* Paranoia */ /* * If the host is specified by numerical address, just convert the * address to internal form. Otherwise, the host is specified by name. */ #define PREF0 0 addr_list = smtp_addr_one((DNS_RR *) 0, host, PREF0, why); if (addr_list && (misc_flags & SMTP_MISC_FLAG_LOOP_DETECT) && smtp_find_self(addr_list) != 0) { dns_rr_free(addr_list); dsb_simple(why, "5.4.6", "mail for %s loops back to myself", host); return (0); } if (addr_list && addr_list->next) { if (var_smtp_rand_addr) addr_list = dns_rr_shuffle(addr_list); /* The following changes the order of equal-preference hosts. */ if (inet_proto_info()->ai_family_list[1] != 0) addr_list = dns_rr_sort(addr_list, SMTP_COMPARE_ADDR(misc_flags)); } if (msg_verbose) smtp_print_addr(host, addr_list); return (addr_list); }
int smtp_stream_except(SMTP_STATE *state, int code, const char *description) { SMTP_SESSION *session = state->session; DSN_BUF *why = state->why; /* * Sanity check. */ if (session == 0) msg_panic("smtp_stream_except: no session"); /* * Initialize. */ switch (code) { default: msg_panic("smtp_stream_except: unknown exception %d", code); case SMTP_ERR_EOF: dsb_simple(why, "4.4.2", "lost connection with %s while %s", session->namaddr, description); #ifdef USE_TLS if (PLAINTEXT_FALLBACK_OK_AFTER_TLS_SESSION_FAILURE) RETRY_AS_PLAINTEXT; #endif break; case SMTP_ERR_TIME: dsb_simple(why, "4.4.2", "conversation with %s timed out while %s", session->namaddr, description); #ifdef USE_TLS if (PLAINTEXT_FALLBACK_OK_AFTER_TLS_SESSION_FAILURE) RETRY_AS_PLAINTEXT; #endif break; case SMTP_ERR_DATA: session->error_mask |= MAIL_ERROR_DATA; dsb_simple(why, "4.3.0", "local data error while talking to %s", session->namaddr); } /* * The smtp_bulk_fail() call below will not throttle the destination when * falling back to plaintext, because RETRY_AS_PLAINTEXT clears the * FINAL_SERVER flag. */ return (smtp_bulk_fail(state, SMTP_THROTTLE)); }
static SMTP_SESSION *smtp_connect_unix(const char *addr, DSN_BUF *why, int sess_flags) { const char *myname = "smtp_connect_unix"; struct sockaddr_un sock_un; 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((char *) &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), var_myhostname, addr, 0, addr, why, sess_flags)); }
DNS_RR *smtp_host_addr(const char *host, int misc_flags, DSN_BUF *why) { DNS_RR *addr_list; int res_opt = 0; const char *ahost; dsb_reset(why); /* Paranoia */ if (smtp_dns_support == SMTP_DNS_DNSSEC) res_opt |= RES_USE_DNSSEC; /* * IDNA support. */ #ifndef NO_EAI if (!allascii(host) && (ahost = midna_domain_to_ascii(host)) != 0) { if (msg_verbose) msg_info("%s asciified to %s", host, ahost); } else #endif ahost = host; /* * If the host is specified by numerical address, just convert the * address to internal form. Otherwise, the host is specified by name. */ #define PREF0 0 addr_list = smtp_addr_one((DNS_RR *) 0, ahost, res_opt, PREF0, why); if (addr_list && (misc_flags & SMTP_MISC_FLAG_LOOP_DETECT) && smtp_find_self(addr_list) != 0) { dns_rr_free(addr_list); dsb_simple(why, "5.4.6", "mail for %s loops back to myself", host); return (0); } if (addr_list && addr_list->next) { if (var_smtp_rand_addr) addr_list = dns_rr_shuffle(addr_list); /* The following changes the order of equal-preference hosts. */ if (inet_proto_info()->ai_family_list[1] != 0) addr_list = dns_rr_sort(addr_list, SMTP_COMPARE_ADDR(misc_flags)); } if (msg_verbose) smtp_print_addr(host, addr_list); return (addr_list); }
static void tls_site_lookup(SMTP_TLS_POLICY *tls, int *site_level, const char *site_name, const char *site_class) { const char *lookup; /* * Look up a non-default policy. In case of multiple lookup results, the * precedence order is a permutation of the TLS enforcement level order: * VERIFY, ENCRYPT, NONE, MAY, NOTFOUND. I.e. we override MAY with a more * specific policy including NONE, otherwise we choose the stronger * enforcement level. */ if ((lookup = maps_find(tls_per_site, site_name, 0)) != 0) { if (!strcasecmp(lookup, "NONE")) { /* NONE overrides MAY or NOTFOUND. */ if (*site_level <= TLS_LEV_MAY) *site_level = TLS_LEV_NONE; } else if (!strcasecmp(lookup, "MAY")) { /* MAY overrides NOTFOUND but not NONE. */ if (*site_level < TLS_LEV_NONE) *site_level = TLS_LEV_MAY; } else if (!strcasecmp(lookup, "MUST_NOPEERMATCH")) { if (*site_level < TLS_LEV_ENCRYPT) *site_level = TLS_LEV_ENCRYPT; } else if (!strcasecmp(lookup, "MUST")) { if (*site_level < TLS_LEV_VERIFY) *site_level = TLS_LEV_VERIFY; } else { msg_warn("%s: unknown TLS policy '%s' for %s %s", tls_per_site->title, lookup, site_class, site_name); MARK_INVALID(tls->why, site_level); return; } } else if (tls_per_site->error) { msg_warn("%s: %s \"%s\": per-site table lookup error", tls_per_site->title, site_class, site_name); dsb_simple(tls->why, "4.3.0", "Temporary lookup error"); *site_level = TLS_LEV_INVALID; return; } return; }
int deliver_resolve_tree(LOCAL_STATE state, USER_ATTR usr_attr, TOK822 *addr) { const char *myname = "deliver_resolve_tree"; RESOLVE_REPLY reply; int status; ssize_t ext_len; char *ratsign; int rcpt_delim; /* * Make verbose logging easier to understand. */ state.level++; if (msg_verbose) MSG_LOG_STATE(myname, state); /* * Initialize. */ resolve_clnt_init(&reply); /* * Rewrite the address to canonical form, just like the cleanup service * does. Then, resolve the address to (transport, nexhop, recipient), * just like the queue manager does. The only part missing here is the * virtual address substitution. Message forwarding fixes most of that. */ tok822_rewrite(addr, REWRITE_CANON); tok822_resolve(addr, &reply); /* * First, a healthy portion of error handling. */ if (reply.flags & RESOLVE_FLAG_FAIL) { dsb_simple(state.msg_attr.why, "4.3.0", "address resolver failure"); status = defer_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr)); } else if (reply.flags & RESOLVE_FLAG_ERROR) { dsb_simple(state.msg_attr.why, "5.1.3", "bad recipient address syntax: %s", STR(reply.recipient)); status = bounce_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr)); } else { /* * Splice in the optional unmatched address extension. */ if (state.msg_attr.unmatched) { rcpt_delim = state.msg_attr.local[strlen(state.msg_attr.user)]; if ((ratsign = strrchr(STR(reply.recipient), '@')) == 0) { VSTRING_ADDCH(reply.recipient, rcpt_delim); vstring_strcat(reply.recipient, state.msg_attr.unmatched); } else { ext_len = strlen(state.msg_attr.unmatched); VSTRING_SPACE(reply.recipient, ext_len + 2); if ((ratsign = strrchr(STR(reply.recipient), '@')) == 0) msg_panic("%s: recipient @ botch", myname); memmove(ratsign + ext_len + 1, ratsign, strlen(ratsign) + 1); *ratsign = rcpt_delim; memcpy(ratsign + 1, state.msg_attr.unmatched, ext_len); VSTRING_SKIP(reply.recipient); } } state.msg_attr.rcpt.address = STR(reply.recipient); /* * Delivery to a local or non-local address. For a while there was * some ugly code to force local recursive alias expansions on a host * with no authority over the local domain, but that code was just * too unclean. */ if (strcmp(state.msg_attr.relay, STR(reply.transport)) == 0) { status = deliver_recipient(state, usr_attr); } else { status = deliver_indirect(state); } } /* * Cleanup. */ resolve_clnt_free(&reply); return (status); }
static int deliver_mailbox_file(LOCAL_STATE state, USER_ATTR usr_attr) { const char *myname = "deliver_mailbox_file"; DSN_BUF *why = state.msg_attr.why; MBOX *mp; int mail_copy_status; int deliver_status; int copy_flags; long end; struct stat st; /* * Make verbose logging easier to understand. */ state.level++; if (msg_verbose) MSG_LOG_STATE(myname, state); /* * Don't deliver trace-only requests. */ if (DEL_REQ_TRACE_ONLY(state.request->flags)) { dsb_simple(why, "2.0.0", "delivers to mailbox"); return (sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr))); } /* * Initialize. Assume the operation will fail. Set the delivered * attribute to reflect the final recipient. */ if (vstream_fseek(state.msg_attr.fp, state.msg_attr.offset, SEEK_SET) < 0) msg_fatal("seek message file %s: %m", VSTREAM_PATH(state.msg_attr.fp)); state.msg_attr.delivered = state.msg_attr.rcpt.address; mail_copy_status = MAIL_COPY_STAT_WRITE; /* * Lock the mailbox and open/create the mailbox file. * * Write the file as the recipient, so that file quota work. */ copy_flags = MAIL_COPY_MBOX; set_eugid(usr_attr.uid, usr_attr.gid); mp = mbox_open(usr_attr.mailbox, O_APPEND | O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR, &st, -1, -1, virtual_mbox_lock_mask, "4.2.0", why); if (mp != 0) { if (S_ISREG(st.st_mode) == 0) { vstream_fclose(mp->fp); msg_warn("recipient %s: destination %s is not a regular file", state.msg_attr.rcpt.address, usr_attr.mailbox); dsb_simple(why, "5.3.5", "mail system configuration error"); } else if (var_strict_mbox_owner && st.st_uid != usr_attr.uid) { vstream_fclose(mp->fp); dsb_simple(why, "4.2.0", "destination %s is not owned by recipient", usr_attr.mailbox); msg_warn("specify \"%s = no\" to ignore mailbox ownership mismatch", VAR_STRICT_MBOX_OWNER); } else { end = vstream_fseek(mp->fp, (off_t) 0, SEEK_END); mail_copy_status = mail_copy(COPY_ATTR(state.msg_attr), mp->fp, copy_flags, "\n", why); } mbox_release(mp); } set_eugid(var_owner_uid, var_owner_gid); /* * As the mail system, bounce, defer delivery, or report success. */ if (mail_copy_status & MAIL_COPY_STAT_CORRUPT) { deliver_status = DEL_STAT_DEFER; } else if (mail_copy_status != 0) { vstring_sprintf_prepend(why->reason, "delivery failed to mailbox %s: ", usr_attr.mailbox); deliver_status = (STR(why->status)[0] == '4' ? defer_append : bounce_append) (BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr)); } else { dsb_simple(why, "2.0.0", "delivered to mailbox"); deliver_status = sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr)); } return (deliver_status); }
DNS_RR *smtp_domain_addr(char *name, int misc_flags, DSN_BUF *why, int *found_myself) { DNS_RR *mx_names; DNS_RR *addr_list = 0; DNS_RR *self = 0; unsigned best_pref; unsigned best_found; dsb_reset(why); /* Paranoia */ /* * Preferences from DNS use 0..32767, fall-backs use 32768+. */ #define IMPOSSIBLE_PREFERENCE (~0) /* * Sanity check. */ if (var_disable_dns) msg_panic("smtp_domain_addr: DNS lookup is disabled"); /* * Look up the mail exchanger hosts listed for this name. Sort the * results by preference. Look up the corresponding host addresses, and * truncate the list so that it contains only hosts that are more * preferred than myself. When no MX resource records exist, look up the * addresses listed for this name. * * According to RFC 974: "It is possible that the list of MXs in the * response to the query will be empty. This is a special case. If the * list is empty, mailers should treat it as if it contained one RR, an * MX RR with a preference value of 0, and a host name of REMOTE. (I.e., * REMOTE is its only MX). In addition, the mailer should do no further * processing on the list, but should attempt to deliver the message to * REMOTE." * * Normally it is OK if an MX host cannot be found in the DNS; we'll just * use a backup one, and silently ignore the better MX host. However, if * the best backup that we can find in the DNS is the local machine, then * we must remember that the local machine is not the primary MX host, or * else we will claim that mail loops back. * * XXX Optionally do A lookups even when the MX lookup didn't complete. * Unfortunately with some DNS servers this is not a transient problem. * * XXX Ideally we would perform A lookups only as far as needed. But as long * as we're looking up all the hosts, it would be better to look up the * least preferred host first, so that DNS lookup error messages make * more sense. * * XXX 2821: RFC 2821 says that the sender must shuffle equal-preference MX * hosts, whereas multiple A records per hostname must be used in the * order as received. They make the bogus assumption that a hostname with * multiple A records corresponds to one machine with multiple network * interfaces. * * XXX 2821: Postfix recognizes the local machine by looking for its own IP * address in the list of mail exchangers. RFC 2821 says one has to look * at the mail exchanger hostname as well, making the bogus assumption * that an IP address is listed only under one hostname. However, looking * at hostnames provides a partial solution for MX hosts behind a NAT * gateway. */ switch (dns_lookup(name, T_MX, 0, &mx_names, (VSTRING *) 0, why->reason)) { default: dsb_status(why, "4.4.3"); if (var_ign_mx_lookup_err) addr_list = smtp_host_addr(name, misc_flags, why); break; case DNS_INVAL: dsb_status(why, "5.4.4"); if (var_ign_mx_lookup_err) addr_list = smtp_host_addr(name, misc_flags, why); break; case DNS_FAIL: dsb_status(why, "5.4.3"); if (var_ign_mx_lookup_err) addr_list = smtp_host_addr(name, misc_flags, why); break; case DNS_OK: mx_names = dns_rr_sort(mx_names, dns_rr_compare_pref_any); best_pref = (mx_names ? mx_names->pref : IMPOSSIBLE_PREFERENCE); addr_list = smtp_addr_list(mx_names, why); dns_rr_free(mx_names); if (addr_list == 0) { /* Text does not change. */ if (var_smtp_defer_mxaddr) { /* Don't clobber the null terminator. */ if (SMTP_HAS_HARD_DSN(why)) SMTP_SET_SOFT_DSN(why); /* XXX */ /* Require some error status. */ else if (!SMTP_HAS_SOFT_DSN(why)) msg_panic("smtp_domain_addr: bad status"); } msg_warn("no MX host for %s has a valid address record", name); break; } best_found = (addr_list ? addr_list->pref : IMPOSSIBLE_PREFERENCE); if (msg_verbose) smtp_print_addr(name, addr_list); if ((misc_flags & SMTP_MISC_FLAG_LOOP_DETECT) && (self = smtp_find_self(addr_list)) != 0) { addr_list = smtp_truncate_self(addr_list, self->pref); if (addr_list == 0) { if (best_pref != best_found) { dsb_simple(why, "4.4.4", "unable to find primary relay for %s", name); } else { dsb_simple(why, "5.4.6", "mail for %s loops back to myself", name); } } } #define SMTP_COMPARE_ADDR(flags) \ (((flags) & SMTP_MISC_FLAG_PREF_IPV6) ? dns_rr_compare_pref_ipv6 : \ ((flags) & SMTP_MISC_FLAG_PREF_IPV4) ? dns_rr_compare_pref_ipv4 : \ dns_rr_compare_pref_any) if (addr_list && addr_list->next && var_smtp_rand_addr) { addr_list = dns_rr_shuffle(addr_list); addr_list = dns_rr_sort(addr_list, SMTP_COMPARE_ADDR(misc_flags)); } break; case DNS_NOTFOUND: addr_list = smtp_host_addr(name, misc_flags, why); break; } /* * Clean up. */ *found_myself |= (self != 0); return (addr_list); }
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)); }
int deliver_recipient(LOCAL_STATE state, USER_ATTR usr_attr) { const char *myname = "deliver_recipient"; int rcpt_stat; /* * Make verbose logging easier to understand. */ state.level++; if (msg_verbose) MSG_LOG_STATE(myname, state); /* * Duplicate filter. */ if (been_here(state.dup_filter, "recipient %d %s", state.level, state.msg_attr.rcpt.address)) return (0); /* * With each level of recursion, detect and break external message * forwarding loops. * * If the looping recipient address has an owner- alias, send the error * report there instead. * * XXX A delivery agent cannot change the envelope sender address for * bouncing. As a workaround we use a one-recipient bounce procedure. * * The proper fix would be to record in the bounce logfile an error return * address for each individual recipient. This would also eliminate the * need for VERP specific bouncing code, at the cost of complicating the * normal bounce sending procedure, but would simplify the code below. */ if (delivered_hdr_find(state.loop_info, state.msg_attr.rcpt.address)) { dsb_simple(state.msg_attr.why, "5.4.6", "mail forwarding loop for %s", state.msg_attr.rcpt.address); /* Account for possible owner- sender address override. */ return (bounce_workaround(state)); } /* * Set up the recipient-specific attributes. If this is forwarded mail, * leave the delivered attribute alone, so that the forwarded message * will show the correct forwarding recipient. */ if (state.msg_attr.delivered == 0) state.msg_attr.delivered = state.msg_attr.rcpt.address; state.msg_attr.local = mystrdup(state.msg_attr.rcpt.address); lowercase(state.msg_attr.local); if ((state.msg_attr.domain = split_at_right(state.msg_attr.local, '@')) == 0) msg_warn("no @ in recipient address: %s", state.msg_attr.local); /* * Address extension management. */ state.msg_attr.user = mystrdup(state.msg_attr.local); if (*var_rcpt_delim) { state.msg_attr.extension = split_addr(state.msg_attr.user, *var_rcpt_delim); if (state.msg_attr.extension && strchr(state.msg_attr.extension, '/')) { msg_warn("%s: address with illegal extension: %s", state.msg_attr.queue_id, state.msg_attr.local); state.msg_attr.extension = 0; } } else state.msg_attr.extension = 0; state.msg_attr.unmatched = state.msg_attr.extension; /* * Do not allow null usernames. */ if (state.msg_attr.user[0] == 0) { dsb_simple(state.msg_attr.why, "5.1.3", "null username in \"%s\"", state.msg_attr.rcpt.address); return (bounce_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr))); } /* * Run the recipient through the delivery switch. */ if (msg_verbose) deliver_attr_dump(&state.msg_attr); rcpt_stat = deliver_switch(state, usr_attr); /* * Clean up. */ myfree(state.msg_attr.local); myfree(state.msg_attr.user); return (rcpt_stat); }
int deliver_unknown(LOCAL_STATE state, USER_ATTR usr_attr) { const char *myname = "deliver_unknown"; int status; VSTRING *expand_luser; static MAPS *transp_maps; const char *map_transport; /* * Make verbose logging easier to understand. */ state.level++; if (msg_verbose) MSG_LOG_STATE(myname, state); /* * DUPLICATE/LOOP ELIMINATION * * Don't deliver the same user twice. */ if (been_here(state.dup_filter, "%s %s", myname, state.msg_attr.local)) return (0); /* * The fall-back transport specifies a delivery machanism that handles * users not found in the aliases or UNIX passwd databases. */ if (*var_fbck_transp_maps && transp_maps == 0) transp_maps = maps_create(VAR_FBCK_TRANSP_MAPS, var_fbck_transp_maps, DICT_FLAG_LOCK | DICT_FLAG_NO_REGSUB); /* The -1 is a hint for the down-stream deliver_completed() function. */ if (transp_maps && (map_transport = maps_find(transp_maps, state.msg_attr.user, DICT_FLAG_NONE)) != 0) { state.msg_attr.rcpt.offset = -1L; return (deliver_pass(MAIL_CLASS_PRIVATE, map_transport, state.request, &state.msg_attr.rcpt)); } else if (transp_maps && transp_maps->error != 0) { /* Details in the logfile. */ dsb_simple(state.msg_attr.why, "4.3.0", "table lookup failure"); return (defer_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr))); } if (*var_fallback_transport) { state.msg_attr.rcpt.offset = -1L; return (deliver_pass(MAIL_CLASS_PRIVATE, var_fallback_transport, state.request, &state.msg_attr.rcpt)); } /* * Subject the luser_relay address to $name expansion, disable * propagation of unmatched address extension, and re-inject the address * into the delivery machinery. Do not give special treatment to "|stuff" * or /stuff. */ if (*var_luser_relay) { state.msg_attr.unmatched = 0; expand_luser = vstring_alloc(100); local_expand(expand_luser, var_luser_relay, &state, &usr_attr, (char *) 0); status = deliver_resolve_addr(state, usr_attr, STR(expand_luser)); vstring_free(expand_luser); return (status); } /* * If no alias was found for a required reserved name, toss the message * into the bit bucket, and issue a warning instead. */ #define STREQ(x,y) (strcasecmp(x,y) == 0) if (STREQ(state.msg_attr.local, MAIL_ADDR_MAIL_DAEMON) || STREQ(state.msg_attr.local, MAIL_ADDR_POSTMASTER)) { msg_warn("required alias not found: %s", state.msg_attr.local); dsb_simple(state.msg_attr.why, "2.0.0", "discarded"); return (sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr))); } /* * Bounce the message when no luser relay is specified. */ dsb_simple(state.msg_attr.why, "5.1.1", "unknown user: \"%s\"", state.msg_attr.local); return (bounce_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr))); }
int deliver_mailbox(LOCAL_STATE state, USER_ATTR usr_attr, int *statusp) { const char *myname = "deliver_mailbox"; int status; struct mypasswd *mbox_pwd; char *path; static MAPS *transp_maps; const char *map_transport; static MAPS *cmd_maps; const char *map_command; /* * Make verbose logging easier to understand. */ state.level++; if (msg_verbose) MSG_LOG_STATE(myname, state); /* * DUPLICATE ELIMINATION * * Don't come here more than once, whether or not the recipient exists. */ if (been_here(state.dup_filter, "mailbox %s", state.msg_attr.local)) return (YES); /* * Delegate mailbox delivery to another message transport. */ if (*var_mbox_transp_maps && transp_maps == 0) transp_maps = maps_create(VAR_MBOX_TRANSP_MAPS, var_mbox_transp_maps, DICT_FLAG_LOCK | DICT_FLAG_NO_REGSUB); /* The -1 is a hint for the down-stream deliver_completed() function. */ if (transp_maps && (map_transport = maps_find(transp_maps, state.msg_attr.user, DICT_FLAG_NONE)) != 0) { state.msg_attr.rcpt.offset = -1L; *statusp = deliver_pass(MAIL_CLASS_PRIVATE, map_transport, state.request, &state.msg_attr.rcpt); return (YES); } else if (transp_maps && transp_maps->error != 0) { /* Details in the logfile. */ dsb_simple(state.msg_attr.why, "4.3.0", "table lookup failure"); *statusp = defer_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr)); return (YES); } if (*var_mailbox_transport) { state.msg_attr.rcpt.offset = -1L; *statusp = deliver_pass(MAIL_CLASS_PRIVATE, var_mailbox_transport, state.request, &state.msg_attr.rcpt); return (YES); } /* * Skip delivery when this recipient does not exist. */ if ((errno = mypwnam_err(state.msg_attr.user, &mbox_pwd)) != 0) { msg_warn("error looking up passwd info for %s: %m", state.msg_attr.user); dsb_simple(state.msg_attr.why, "4.0.0", "user lookup error"); *statusp = defer_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr)); return (YES); } if (mbox_pwd == 0) return (NO); /* * No early returns or we have a memory leak. */ /* * DELIVERY RIGHTS * * Use the rights of the recipient user. */ SET_USER_ATTR(usr_attr, mbox_pwd, state.level); /* * Deliver to mailbox, maildir or to external command. */ #define LAST_CHAR(s) (s[strlen(s) - 1]) if (*var_mailbox_cmd_maps && cmd_maps == 0) cmd_maps = maps_create(VAR_MAILBOX_CMD_MAPS, var_mailbox_cmd_maps, DICT_FLAG_LOCK | DICT_FLAG_PARANOID); if (cmd_maps && (map_command = maps_find(cmd_maps, state.msg_attr.user, DICT_FLAG_NONE)) != 0) { status = deliver_command(state, usr_attr, map_command); } else if (cmd_maps && cmd_maps->error != 0) { /* Details in the logfile. */ dsb_simple(state.msg_attr.why, "4.3.0", "table lookup failure"); status = defer_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr)); } else if (*var_mailbox_command) { status = deliver_command(state, usr_attr, var_mailbox_command); } else if (*var_home_mailbox && LAST_CHAR(var_home_mailbox) == '/') { path = concatenate(usr_attr.home, "/", var_home_mailbox, (char *) 0); status = deliver_maildir(state, usr_attr, path); myfree(path); } else if (*var_mail_spool_dir && LAST_CHAR(var_mail_spool_dir) == '/') { path = concatenate(var_mail_spool_dir, state.msg_attr.user, "/", (char *) 0); status = deliver_maildir(state, usr_attr, path); myfree(path); } else status = deliver_mailbox_file(state, usr_attr); /* * Cleanup. */ mypwfree(mbox_pwd); *statusp = status; return (YES); }
int deliver_file(LOCAL_STATE state, USER_ATTR usr_attr, char *path) { const char *myname = "deliver_file"; struct stat st; MBOX *mp; DSN_BUF *why = state.msg_attr.why; int mail_copy_status = MAIL_COPY_STAT_WRITE; int deliver_status; int copy_flags; /* * Make verbose logging easier to understand. */ state.level++; if (msg_verbose) MSG_LOG_STATE(myname, state); /* * DUPLICATE ELIMINATION * * Skip this file if it was already delivered to as this user. */ if (been_here(state.dup_filter, "file %ld %s", (long) usr_attr.uid, path)) return (0); /* * DELIVERY POLICY * * Do we allow delivery to files? */ if ((local_file_deliver_mask & state.msg_attr.exp_type) == 0) { dsb_simple(why, "5.7.1", "mail to file is restricted"); /* Account for possible owner- sender address override. */ return (bounce_workaround(state)); } /* * Don't deliver trace-only requests. */ if (DEL_REQ_TRACE_ONLY(state.request->flags)) { dsb_simple(why, "2.0.0", "delivers to file: %s", path); return (sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr))); } /* * DELIVERY RIGHTS * * Use a default uid/gid when none are given. */ if (usr_attr.uid == 0 && (usr_attr.uid = var_default_uid) == 0) msg_panic("privileged default user id"); if (usr_attr.gid == 0 && (usr_attr.gid = var_default_gid) == 0) msg_panic("privileged default group id"); /* * If the name ends in /, use maildir-style delivery instead. */ if (path[strlen(path) - 1] == '/') return (deliver_maildir(state, usr_attr, path)); /* * Deliver. From here on, no early returns or we have a memory leak. */ if (msg_verbose) msg_info("deliver_file (%ld,%ld): %s", (long) usr_attr.uid, (long) usr_attr.gid, path); if (vstream_fseek(state.msg_attr.fp, state.msg_attr.offset, SEEK_SET) < 0) msg_fatal("seek queue file %s: %m", state.msg_attr.queue_id); /* * As the specified user, open or create the file, lock it, and append * the message. */ copy_flags = MAIL_COPY_MBOX; if ((local_deliver_hdr_mask & DELIVER_HDR_FILE) == 0) copy_flags &= ~MAIL_COPY_DELIVERED; set_eugid(usr_attr.uid, usr_attr.gid); mp = mbox_open(path, O_APPEND | O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR, &st, -1, -1, local_mbox_lock_mask | MBOX_DOT_LOCK_MAY_FAIL, "5.2.0", why); if (mp != 0) { if (S_ISREG(st.st_mode) && st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) { vstream_fclose(mp->fp); dsb_simple(why, "5.7.1", "file is executable"); } else { mail_copy_status = mail_copy(COPY_ATTR(state.msg_attr), mp->fp, S_ISREG(st.st_mode) ? copy_flags : (copy_flags & ~MAIL_COPY_TOFILE), "\n", why); } mbox_release(mp); } set_eugid(var_owner_uid, var_owner_gid); /* * As the mail system, bounce, defer delivery, or report success. */ if (mail_copy_status & MAIL_COPY_STAT_CORRUPT) { deliver_status = DEL_STAT_DEFER; } else if (mail_copy_status != 0) { vstring_sprintf_prepend(why->reason, "cannot append message to file %s: ", path); if (STR(why->status)[0] == '4') deliver_status = defer_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr)); else /* Account for possible owner- sender address override. */ deliver_status = bounce_workaround(state); } else { dsb_simple(why, "2.0.0", "delivered to file: %s", path); deliver_status = sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr)); } return (deliver_status); }
static void dane_init(SMTP_TLS_POLICY *tls, SMTP_ITERATOR *iter) { TLS_DANE *dane; if (!iter->port) { msg_warn("%s: the \"dane\" security level is invalid for delivery via" " unix-domain sockets", STR(iter->dest)); MARK_INVALID(tls->why, &tls->level); return; } if (!tls_dane_avail()) { dane_incompat(tls, iter, NONDANE_CONFIG, "%s: %s configured, but no requisite library support", STR(iter->dest), policy_name(tls->level)); return; } if (!(smtp_host_lookup_mask & SMTP_HOST_FLAG_DNS) || smtp_dns_support != SMTP_DNS_DNSSEC) { dane_incompat(tls, iter, NONDANE_CONFIG, "%s: %s configured with dnssec lookups disabled", STR(iter->dest), policy_name(tls->level)); return; } /* * If we ignore MX lookup errors, we also ignore DNSSEC security problems * and thus avoid any reasonable expectation that we get the right DANE * key material. */ if (smtp_mode && var_ign_mx_lookup_err) { dane_incompat(tls, iter, NONDANE_CONFIG, "%s: %s configured with MX lookup errors ignored", STR(iter->dest), policy_name(tls->level)); return; } /* * This is not optional, code in tls_dane.c assumes that the nexthop * qname is already an fqdn. If we're using these flags to go from qname * to rname, the assumption is invalid. Likewise we cannot add the qname * to certificate name checks, ... */ if (smtp_dns_res_opt & (RES_DEFNAMES | RES_DNSRCH)) { dane_incompat(tls, iter, NONDANE_CONFIG, "%s: dns resolver options incompatible with %s TLS", STR(iter->dest), policy_name(tls->level)); return; } /* When the MX name is present and insecure, DANE does not apply. */ if (iter->mx && !iter->mx->dnssec_valid) { dane_incompat(tls, iter, NONDANE_DEST, "non DNSSEC destination"); return; } /* When TLSA lookups fail, we defer the message */ if ((dane = tls_dane_resolve(iter->port, "tcp", iter->rr, var_smtp_tls_force_tlsa)) == 0) { tls->level = TLS_LEV_INVALID; dsb_simple(tls->why, "4.7.5", "TLSA lookup error for %s:%u", STR(iter->host), ntohs(iter->port)); return; } if (tls_dane_notfound(dane)) { dane_incompat(tls, iter, NONDANE_DEST, "no TLSA records found"); tls_dane_free(dane); return; } /* * Some TLSA records found, but none usable, per * * https://tools.ietf.org/html/draft-ietf-dane-srv-02#section-4 * * we MUST use TLS, and SHALL use full PKIX certificate checks. The latter * would be unwise for SMTP: no human present to "click ok" and risk of * non-delivery in most cases exceeds risk of interception. * * We also have a form of Goedel's incompleteness theorem in play: any list * of public root CA certs is either incomplete or inconsistent (for any * given verifier some of the CAs are surely not trustworthy). */ if (tls_dane_unusable(dane)) { dane_incompat(tls, iter, DANE_UNUSABLE, "TLSA records unusable"); tls_dane_free(dane); return; } /* * With DANE trust anchors, peername matching is not configurable. */ if (TLS_DANE_HASTA(dane)) { tls->matchargv = argv_alloc(2); argv_add(tls->matchargv, dane->base_domain, ARGV_END); if (iter->mx) { if (strcmp(iter->mx->qname, iter->mx->rname) == 0) argv_add(tls->matchargv, iter->mx->qname, ARGV_END); else argv_add(tls->matchargv, iter->mx->rname, iter->mx->qname, ARGV_END); } } else if (!TLS_DANE_HASEE(dane)) msg_panic("empty DANE match list"); tls->dane = dane; tls->level = TLS_LEV_DANE; return; }
static int deliver_message(DELIVER_REQUEST *request, char *service, char **argv) { const char *myname = "deliver_message"; static PIPE_PARAMS conf; static PIPE_ATTR attr; RECIPIENT_LIST *rcpt_list = &request->rcpt_list; DSN_BUF *why = dsb_create(); VSTRING *buf; ARGV *expanded_argv = 0; int deliver_status; int command_status; ARGV *export_env; const char *sender; #define DELIVER_MSG_CLEANUP() { \ dsb_free(why); \ if (expanded_argv) argv_free(expanded_argv); \ } if (msg_verbose) msg_info("%s: from <%s>", myname, request->sender); /* * Sanity checks. The get_service_params() and get_service_attr() * routines also do some sanity checks. Look up service attributes and * config information only once. This is safe since the information comes * from a trusted source, not from the delivery request. */ if (request->nexthop[0] == 0) msg_fatal("empty nexthop hostname"); if (rcpt_list->len <= 0) msg_fatal("recipient count: %d", rcpt_list->len); if (attr.command == 0) { get_service_params(&conf, service); get_service_attr(&attr, argv); } /* * The D flag cannot be specified for multi-recipient deliveries. */ if ((attr.flags & MAIL_COPY_DELIVERED) && (rcpt_list->len > 1)) { dsb_simple(why, "4.3.5", "mail system configuration error"); deliver_status = eval_command_status(PIPE_STAT_DEFER, service, request, &attr, why); msg_warn("pipe flag `D' requires %s_destination_recipient_limit = 1", service); DELIVER_MSG_CLEANUP(); return (deliver_status); } /* * The O flag cannot be specified for multi-recipient deliveries. */ if ((attr.flags & MAIL_COPY_ORIG_RCPT) && (rcpt_list->len > 1)) { dsb_simple(why, "4.3.5", "mail system configuration error"); deliver_status = eval_command_status(PIPE_STAT_DEFER, service, request, &attr, why); msg_warn("pipe flag `O' requires %s_destination_recipient_limit = 1", service); DELIVER_MSG_CLEANUP(); return (deliver_status); } /* * Check that this agent accepts messages this large. */ if (attr.size_limit != 0 && request->data_size > attr.size_limit) { if (msg_verbose) msg_info("%s: too big: size_limit = %ld, request->data_size = %ld", myname, (long) attr.size_limit, request->data_size); dsb_simple(why, "5.2.3", "message too large"); deliver_status = eval_command_status(PIPE_STAT_BOUNCE, service, request, &attr, why); DELIVER_MSG_CLEANUP(); return (deliver_status); } /* * Don't deliver a trace-only request. */ if (DEL_REQ_TRACE_ONLY(request->flags)) { RECIPIENT *rcpt; int status; int n; deliver_status = 0; dsb_simple(why, "2.0.0", "delivers to command: %s", attr.command[0]); (void) DSN_FROM_DSN_BUF(why); for (n = 0; n < request->rcpt_list.len; n++) { rcpt = request->rcpt_list.info + n; status = sent(DEL_REQ_TRACE_FLAGS(request->flags), request->queue_id, &request->msg_stats, rcpt, service, &why->dsn); if (status == 0 && (request->flags & DEL_REQ_FLAG_SUCCESS)) deliver_completed(request->fp, rcpt->offset); deliver_status |= status; } DELIVER_MSG_CLEANUP(); return (deliver_status); } /* * Report mail delivery loops. By definition, this requires * single-recipient delivery. Don't silently lose recipients. */ if (attr.flags & MAIL_COPY_DELIVERED) { DELIVERED_HDR_INFO *info; RECIPIENT *rcpt; int loop_found; if (request->rcpt_list.len > 1) msg_panic("%s: delivered-to enabled with multi-recipient request", myname); info = delivered_hdr_init(request->fp, request->data_offset, FOLD_ADDR_ALL); rcpt = request->rcpt_list.info; loop_found = delivered_hdr_find(info, rcpt->address); delivered_hdr_free(info); if (loop_found) { dsb_simple(why, "5.4.6", "mail forwarding loop for %s", rcpt->address); deliver_status = eval_command_status(PIPE_STAT_BOUNCE, service, request, &attr, why); DELIVER_MSG_CLEANUP(); return (deliver_status); } } /* * Deliver. Set the nexthop and sender variables, and expand the command * argument vector. Recipients will be expanded on the fly. XXX Rewrite * envelope and header addresses according to transport-specific * rewriting rules. */ if (vstream_fseek(request->fp, request->data_offset, SEEK_SET) < 0) msg_fatal("seek queue file %s: %m", VSTREAM_PATH(request->fp)); /* * A non-empty null sender replacement is subject to the 'q' flag. */ buf = vstring_alloc(10); sender = *request->sender ? request->sender : STR(attr.null_sender); if (*sender && (attr.flags & PIPE_OPT_QUOTE_LOCAL)) { quote_822_local(buf, sender); dict_update(PIPE_DICT_TABLE, PIPE_DICT_SENDER, STR(buf)); } else dict_update(PIPE_DICT_TABLE, PIPE_DICT_SENDER, sender); if (attr.flags & PIPE_OPT_FOLD_HOST) { vstring_strcpy(buf, request->nexthop); lowercase(STR(buf)); dict_update(PIPE_DICT_TABLE, PIPE_DICT_NEXTHOP, STR(buf)); } else dict_update(PIPE_DICT_TABLE, PIPE_DICT_NEXTHOP, request->nexthop); vstring_sprintf(buf, "%ld", (long) request->data_size); dict_update(PIPE_DICT_TABLE, PIPE_DICT_SIZE, STR(buf)); dict_update(PIPE_DICT_TABLE, PIPE_DICT_CLIENT_ADDR, request->client_addr); dict_update(PIPE_DICT_TABLE, PIPE_DICT_CLIENT_HELO, request->client_helo); dict_update(PIPE_DICT_TABLE, PIPE_DICT_CLIENT_NAME, request->client_name); dict_update(PIPE_DICT_TABLE, PIPE_DICT_CLIENT_PORT, request->client_port); dict_update(PIPE_DICT_TABLE, PIPE_DICT_CLIENT_PROTO, request->client_proto); dict_update(PIPE_DICT_TABLE, PIPE_DICT_SASL_METHOD, request->sasl_method); dict_update(PIPE_DICT_TABLE, PIPE_DICT_SASL_USERNAME, request->sasl_username); dict_update(PIPE_DICT_TABLE, PIPE_DICT_SASL_SENDER, request->sasl_sender); dict_update(PIPE_DICT_TABLE, PIPE_DICT_QUEUE_ID, request->queue_id); vstring_free(buf); if ((expanded_argv = expand_argv(service, attr.command, rcpt_list, attr.flags)) == 0) { dsb_simple(why, "4.3.5", "mail system configuration error"); deliver_status = eval_command_status(PIPE_STAT_DEFER, service, request, &attr, why); DELIVER_MSG_CLEANUP(); return (deliver_status); } export_env = argv_split(var_export_environ, ", \t\r\n"); command_status = pipe_command(request->fp, why, PIPE_CMD_UID, attr.uid, PIPE_CMD_GID, attr.gid, PIPE_CMD_SENDER, sender, PIPE_CMD_COPY_FLAGS, attr.flags, PIPE_CMD_ARGV, expanded_argv->argv, PIPE_CMD_TIME_LIMIT, conf.time_limit, PIPE_CMD_EOL, STR(attr.eol), PIPE_CMD_EXPORT, export_env->argv, PIPE_CMD_CWD, attr.exec_dir, PIPE_CMD_CHROOT, attr.chroot_dir, PIPE_CMD_ORIG_RCPT, rcpt_list->info[0].orig_addr, PIPE_CMD_DELIVERED, rcpt_list->info[0].address, PIPE_CMD_END); argv_free(export_env); deliver_status = eval_command_status(command_status, service, request, &attr, why); /* * Clean up. */ DELIVER_MSG_CLEANUP(); return (deliver_status); }
int deliver_dotforward(LOCAL_STATE state, USER_ATTR usr_attr, int *statusp) { const char *myname = "deliver_dotforward"; struct stat st; VSTRING *path; struct mypasswd *mypwd; int fd; VSTREAM *fp; int status; int forward_found = NO; int lookup_status; int addr_count; char *saved_forward_path; char *lhs; char *next; int expand_status; int saved_notify; /* * Make verbose logging easier to understand. */ state.level++; if (msg_verbose) MSG_LOG_STATE(myname, state); /* * Skip this module if per-user forwarding is disabled. */ if (*var_forward_path == 0) return (NO); /* * Skip non-existing users. The mailbox delivery routine will catch the * error. */ if ((errno = mypwnam_err(state.msg_attr.user, &mypwd)) != 0) { msg_warn("error looking up passwd info for %s: %m", state.msg_attr.user); dsb_simple(state.msg_attr.why, "4.0.0", "user lookup error"); *statusp = defer_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr)); return (YES); } if (mypwd == 0) return (NO); /* * From here on no early returns or we have a memory leak. */ /* * EXTERNAL LOOP CONTROL * * Set the delivered message attribute to the recipient, so that this * message will list the correct forwarding address. */ if (var_frozen_delivered == 0) state.msg_attr.delivered = state.msg_attr.rcpt.address; /* * DELIVERY RIGHTS * * Do not inherit rights from the .forward file owner. Instead, use the * recipient's rights, and insist that the .forward file is owned by the * recipient. This is a small but significant difference. Use the * recipient's rights for all /file and |command deliveries, and pass on * these rights to command/file destinations in included files. When * these are the rights of root, the /file and |command delivery routines * will use unprivileged default rights instead. Better safe than sorry. */ SET_USER_ATTR(usr_attr, mypwd, state.level); /* * DELIVERY POLICY * * Update the expansion type attribute so that we can decide if deliveries * to |command and /file/name are allowed at all. */ state.msg_attr.exp_type = EXPAND_TYPE_FWD; /* * WHERE TO REPORT DELIVERY PROBLEMS * * Set the owner attribute so that 1) include files won't set the sender to * be this user and 2) mail forwarded to other local users will be * resubmitted as a new queue file. */ state.msg_attr.owner = state.msg_attr.user; /* * Search the forward_path for an existing forward file. * * If unmatched extensions should never be propagated, or if a forward file * name includes the address extension, don't propagate the extension to * the recipient addresses. */ status = 0; path = vstring_alloc(100); saved_forward_path = mystrdup(var_forward_path); next = saved_forward_path; lookup_status = -1; while ((lhs = mystrtok(&next, ", \t\r\n")) != 0) { expand_status = local_expand(path, lhs, &state, &usr_attr, var_fwd_exp_filter); if ((expand_status & (MAC_PARSE_ERROR | MAC_PARSE_UNDEF)) == 0) { lookup_status = lstat_as(STR(path), &st, usr_attr.uid, usr_attr.gid); if (msg_verbose) msg_info("%s: path %s expand_status %d look_status %d", myname, STR(path), expand_status, lookup_status); if (lookup_status >= 0) { if ((expand_status & LOCAL_EXP_EXTENSION_MATCHED) != 0 || (local_ext_prop_mask & EXT_PROP_FORWARD) == 0) state.msg_attr.unmatched = 0; break; } } } /* * Process the forward file. * * Assume that usernames do not have file system meta characters. Open the * .forward file as the user. Ignore files that aren't regular files, * files that are owned by the wrong user, or files that have world write * permission enabled. * * DUPLICATE/LOOP ELIMINATION * * If this user includes (an alias of) herself in her own .forward file, * deliver to the user instead. */ if (lookup_status >= 0) { /* * Don't expand a verify-only request. */ if (state.request->flags & DEL_REQ_FLAG_MTA_VRFY) { dsb_simple(state.msg_attr.why, "2.0.0", "forward via file: %s", STR(path)); *statusp = sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr)); forward_found = YES; } else if (been_here(state.dup_filter, "forward %s", STR(path)) == 0) { state.msg_attr.exp_from = state.msg_attr.local; if (S_ISREG(st.st_mode) == 0) { msg_warn("file %s is not a regular file", STR(path)); } else if (st.st_uid != 0 && st.st_uid != usr_attr.uid) { msg_warn("file %s has bad owner uid %ld", STR(path), (long) st.st_uid); } else if (st.st_mode & 002) { msg_warn("file %s is world writable", STR(path)); } else if ((fd = open_as(STR(path), O_RDONLY, 0, usr_attr.uid, usr_attr.gid)) < 0) { msg_warn("cannot open file %s: %m", STR(path)); } else { /* * XXX DSN. When delivering to an alias (i.e. the envelope * sender address is not replaced) any ENVID, RET, or ORCPT * parameters are propagated to all forwarding addresses * associated with that alias. The NOTIFY parameter is * propagated to the forwarding addresses, except that any * SUCCESS keyword is removed. */ close_on_exec(fd, CLOSE_ON_EXEC); addr_count = 0; fp = vstream_fdopen(fd, O_RDONLY); saved_notify = state.msg_attr.rcpt.dsn_notify; state.msg_attr.rcpt.dsn_notify = (saved_notify == DSN_NOTIFY_SUCCESS ? DSN_NOTIFY_NEVER : saved_notify & ~DSN_NOTIFY_SUCCESS); status = deliver_token_stream(state, usr_attr, fp, &addr_count); if (vstream_fclose(fp)) msg_warn("close file %s: %m", STR(path)); if (addr_count > 0) { forward_found = YES; been_here(state.dup_filter, "forward-done %s", STR(path)); /* * XXX DSN. When delivering to an alias (i.e. the * envelope sender address is not replaced) and the * original NOTIFY parameter for the alias contained the * SUCCESS keyword, an "expanded" DSN is issued for the * alias. */ if (status == 0 && (saved_notify & DSN_NOTIFY_SUCCESS)) { state.msg_attr.rcpt.dsn_notify = saved_notify; dsb_update(state.msg_attr.why, "2.0.0", "expanded", DSB_SKIP_RMTA, DSB_SKIP_REPLY, "alias expanded"); (void) trace_append(BOUNCE_FLAG_NONE, SENT_ATTR(state.msg_attr)); } } } } else if (been_here_check(state.dup_filter, "forward-done %s", STR(path)) != 0) forward_found = YES; /* else we're recursive */ } /* * Clean up. */ vstring_free(path); myfree(saved_forward_path); mypwfree(mypwd); *statusp = status; return (forward_found); }
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 int deliver_mailbox_file(LOCAL_STATE state, USER_ATTR usr_attr) { const char *myname = "deliver_mailbox_file"; char *spool_dir; char *mailbox; DSN_BUF *why = state.msg_attr.why; MBOX *mp; int mail_copy_status; int deliver_status; int copy_flags; VSTRING *biff; long end; struct stat st; uid_t spool_uid; gid_t spool_gid; uid_t chown_uid; gid_t chown_gid; /* * Make verbose logging easier to understand. */ state.level++; if (msg_verbose) MSG_LOG_STATE(myname, state); /* * Don't deliver trace-only requests. */ if (DEL_REQ_TRACE_ONLY(state.request->flags)) { dsb_simple(why, "2.0.0", "delivers to mailbox"); return (sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr))); } /* * Initialize. Assume the operation will fail. Set the delivered * attribute to reflect the final recipient. */ if (vstream_fseek(state.msg_attr.fp, state.msg_attr.offset, SEEK_SET) < 0) msg_fatal("seek message file %s: %m", VSTREAM_PATH(state.msg_attr.fp)); if (var_frozen_delivered == 0) state.msg_attr.delivered = state.msg_attr.rcpt.address; mail_copy_status = MAIL_COPY_STAT_WRITE; if (*var_home_mailbox) { spool_dir = 0; mailbox = concatenate(usr_attr.home, "/", var_home_mailbox, (char *) 0); } else { spool_dir = var_mail_spool_dir; mailbox = concatenate(spool_dir, "/", state.msg_attr.user, (char *) 0); } /* * Mailbox delivery with least privilege. As long as we do not use root * privileges this code may also work over NFS. * * If delivering to the recipient's home directory, perform all operations * (including file locking) as that user (Mike Muuss, Army Research * Laboratory, USA). * * If delivering to the mail spool directory, and the spool directory is * world-writable, deliver as the recipient; if the spool directory is * group-writable, use the recipient user id and the mail spool group id. * * Otherwise, use root privileges and chown the mailbox. */ if (spool_dir == 0 || stat(spool_dir, &st) < 0 || (st.st_mode & S_IWOTH) != 0) { spool_uid = usr_attr.uid; spool_gid = usr_attr.gid; } else if ((st.st_mode & S_IWGRP) != 0) { spool_uid = usr_attr.uid; spool_gid = st.st_gid; } else { spool_uid = 0; spool_gid = 0; } if (spool_uid == usr_attr.uid) { chown_uid = -1; chown_gid = -1; } else { chown_uid = usr_attr.uid; chown_gid = usr_attr.gid; } if (msg_verbose) msg_info("spool_uid/gid %ld/%ld chown_uid/gid %ld/%ld", (long) spool_uid, (long) spool_gid, (long) chown_uid, (long) chown_gid); /* * Lock the mailbox and open/create the mailbox file. Depending on the * type of locking used, we lock first or we open first. * * Write the file as the recipient, so that file quota work. */ copy_flags = MAIL_COPY_MBOX; if ((local_deliver_hdr_mask & DELIVER_HDR_FILE) == 0) copy_flags &= ~MAIL_COPY_DELIVERED; set_eugid(spool_uid, spool_gid); mp = mbox_open(mailbox, O_APPEND | O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR, &st, chown_uid, chown_gid, local_mbox_lock_mask, "5.2.0", why); if (mp != 0) { if (spool_uid != usr_attr.uid || spool_gid != usr_attr.gid) set_eugid(usr_attr.uid, usr_attr.gid); if (S_ISREG(st.st_mode) == 0) { vstream_fclose(mp->fp); dsb_simple(why, "5.2.0", "destination %s is not a regular file", mailbox); } else if (var_strict_mbox_owner && st.st_uid != usr_attr.uid) { vstream_fclose(mp->fp); dsb_simple(why, "4.2.0", "destination %s is not owned by recipient", mailbox); msg_warn("specify \"%s = no\" to ignore mailbox ownership mismatch", VAR_STRICT_MBOX_OWNER); } else { end = vstream_fseek(mp->fp, (off_t) 0, SEEK_END); mail_copy_status = mail_copy(COPY_ATTR(state.msg_attr), mp->fp, copy_flags, "\n", why); } if (spool_uid != usr_attr.uid || spool_gid != usr_attr.gid) set_eugid(spool_uid, spool_gid); mbox_release(mp); } set_eugid(var_owner_uid, var_owner_gid); /* * As the mail system, bounce, defer delivery, or report success. */ if (mail_copy_status & MAIL_COPY_STAT_CORRUPT) { deliver_status = DEL_STAT_DEFER; } else if (mail_copy_status != 0) { vstring_sprintf_prepend(why->reason, "cannot update mailbox %s for user %s. ", mailbox, state.msg_attr.user); deliver_status = (STR(why->status)[0] == '4' ? defer_append : bounce_append) (BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr)); } else { dsb_simple(why, "2.0.0", "delivered to mailbox"); deliver_status = sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr)); if (var_biff) { biff = vstring_alloc(100); vstring_sprintf(biff, "%s@%ld", usr_attr.logname, (long) end); biff_notify(STR(biff), VSTRING_LEN(biff) + 1); vstring_free(biff); } } /* * Clean up. */ myfree(mailbox); return (deliver_status); }
int deliver_command(LOCAL_STATE state, USER_ATTR usr_attr, const char *command) { const char *myname = "deliver_command"; DSN_BUF *why = state.msg_attr.why; int cmd_status; int deliver_status; ARGV *env; int copy_flags; char **cpp; char *cp; ARGV *export_env; VSTRING *exec_dir; int expand_status; /* * Make verbose logging easier to understand. */ state.level++; if (msg_verbose) MSG_LOG_STATE(myname, state); /* * DUPLICATE ELIMINATION * * Skip this command if it was already delivered to as this user. */ if (been_here(state.dup_filter, "command %s:%ld %s", state.msg_attr.user, (long) usr_attr.uid, command)) return (0); /* * Don't deliver a trace-only request. */ if (DEL_REQ_TRACE_ONLY(state.request->flags)) { dsb_simple(why, "2.0.0", "delivers to command: %s", command); return (sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr))); } /* * DELIVERY RIGHTS * * Choose a default uid and gid when none have been selected (i.e. values * are still zero). */ if (usr_attr.uid == 0 && (usr_attr.uid = var_default_uid) == 0) msg_panic("privileged default user id"); if (usr_attr.gid == 0 && (usr_attr.gid = var_default_gid) == 0) msg_panic("privileged default group id"); /* * Deliver. */ copy_flags = MAIL_COPY_FROM | MAIL_COPY_RETURN_PATH | MAIL_COPY_ORIG_RCPT; if (local_deliver_hdr_mask & DELIVER_HDR_CMD) copy_flags |= MAIL_COPY_DELIVERED; if (vstream_fseek(state.msg_attr.fp, state.msg_attr.offset, SEEK_SET) < 0) msg_fatal("%s: seek queue file %s: %m", myname, VSTREAM_PATH(state.msg_attr.fp)); /* * Pass additional environment information. XXX This should be * configurable. However, passing untrusted information via environment * parameters opens up a whole can of worms. Lesson from web servers: * don't let any network data even near a shell. It causes trouble. */ env = argv_alloc(1); if (usr_attr.home) argv_add(env, "HOME", usr_attr.home, ARGV_END); argv_add(env, "LOGNAME", state.msg_attr.user, "USER", state.msg_attr.user, "SENDER", state.msg_attr.sender, "RECIPIENT", state.msg_attr.rcpt.address, "LOCAL", state.msg_attr.local, ARGV_END); if (usr_attr.shell) argv_add(env, "SHELL", usr_attr.shell, ARGV_END); if (state.msg_attr.domain) argv_add(env, "DOMAIN", state.msg_attr.domain, ARGV_END); if (state.msg_attr.extension) argv_add(env, "EXTENSION", state.msg_attr.extension, ARGV_END); if (state.msg_attr.rcpt.orig_addr && state.msg_attr.rcpt.orig_addr[0]) argv_add(env, "ORIGINAL_RECIPIENT", state.msg_attr.rcpt.orig_addr, ARGV_END); #define EXPORT_REQUEST(name, value) \ if ((value)[0]) argv_add(env, (name), (value), ARGV_END); EXPORT_REQUEST("CLIENT_HOSTNAME", state.msg_attr.request->client_name); EXPORT_REQUEST("CLIENT_ADDRESS", state.msg_attr.request->client_addr); EXPORT_REQUEST("CLIENT_HELO", state.msg_attr.request->client_helo); EXPORT_REQUEST("CLIENT_PROTOCOL", state.msg_attr.request->client_proto); EXPORT_REQUEST("SASL_METHOD", state.msg_attr.request->sasl_method); EXPORT_REQUEST("SASL_SENDER", state.msg_attr.request->sasl_sender); EXPORT_REQUEST("SASL_USERNAME", state.msg_attr.request->sasl_username); argv_terminate(env); /* * Censor out undesirable characters from exported data. */ for (cpp = env->argv; *cpp; cpp += 2) for (cp = cpp[1]; *(cp += strspn(cp, var_cmd_exp_filter)) != 0;) *cp++ = '_'; /* * Evaluate the command execution directory. Defer delivery if expansion * fails. */ export_env = mail_parm_split(VAR_EXPORT_ENVIRON, var_export_environ); exec_dir = vstring_alloc(10); expand_status = local_expand(exec_dir, var_exec_directory, &state, &usr_attr, var_exec_exp_filter); if (expand_status & MAC_PARSE_ERROR) { cmd_status = PIPE_STAT_DEFER; dsb_simple(why, "4.3.5", "mail system configuration error"); msg_warn("bad parameter value syntax for %s: %s", VAR_EXEC_DIRECTORY, var_exec_directory); } else { cmd_status = pipe_command(state.msg_attr.fp, why, PIPE_CMD_UID, usr_attr.uid, PIPE_CMD_GID, usr_attr.gid, PIPE_CMD_COMMAND, command, PIPE_CMD_COPY_FLAGS, copy_flags, PIPE_CMD_SENDER, state.msg_attr.sender, PIPE_CMD_ORIG_RCPT, state.msg_attr.rcpt.orig_addr, PIPE_CMD_DELIVERED, state.msg_attr.delivered, PIPE_CMD_TIME_LIMIT, var_command_maxtime, PIPE_CMD_ENV, env->argv, PIPE_CMD_EXPORT, export_env->argv, PIPE_CMD_SHELL, var_local_cmd_shell, PIPE_CMD_CWD, *STR(exec_dir) ? STR(exec_dir) : (char *) 0, PIPE_CMD_END); } vstring_free(exec_dir); argv_free(export_env); argv_free(env); /* * Depending on the result, bounce or defer the message. */ switch (cmd_status) { case PIPE_STAT_OK: dsb_simple(why, "2.0.0", "delivered to command: %s", command); deliver_status = sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr)); break; case PIPE_STAT_BOUNCE: case PIPE_STAT_DEFER: /* Account for possible owner- sender address override. */ deliver_status = bounce_workaround(state); break; case PIPE_STAT_CORRUPT: deliver_status = DEL_STAT_DEFER; break; default: msg_panic("%s: bad status %d", myname, cmd_status); /* NOTREACHED */ } return (deliver_status); }
int deliver_alias(LOCAL_STATE state, USER_ATTR usr_attr, char *name, int *statusp) { const char *myname = "deliver_alias"; const char *alias_result; char *saved_alias_result; char *owner; char **cpp; uid_t alias_uid; struct mypasswd *alias_pwd; VSTRING *canon_owner; DICT *dict; const char *owner_rhs; /* owner alias, RHS */ int alias_count; int dsn_notify; char *dsn_envid; int dsn_ret; const char *dsn_orcpt; /* * Make verbose logging easier to understand. */ state.level++; if (msg_verbose) MSG_LOG_STATE(myname, state); /* * DUPLICATE/LOOP ELIMINATION * * We cannot do duplicate elimination here. Sendmail compatibility requires * that we allow multiple deliveries to the same alias, even recursively! * For example, we must deliver to mailbox any messags that are addressed * to the alias of a user that lists that same alias in her own .forward * file. Yuck! This is just an example of some really perverse semantics * that people will expect Postfix to implement just like sendmail. * * We can recognize one special case: when an alias includes its own name, * deliver to the user instead, just like sendmail. Otherwise, we just * bail out when nesting reaches some unreasonable depth, and blame it on * a possible alias loop. */ if (state.msg_attr.exp_from != 0 && strcasecmp(state.msg_attr.exp_from, name) == 0) return (NO); if (state.level > 100) { msg_warn("alias database loop for %s", name); dsb_simple(state.msg_attr.why, "5.4.6", "alias database loop for %s", name); *statusp = bounce_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr)); return (YES); } state.msg_attr.exp_from = name; /* * There are a bunch of roles that we're trying to keep track of. * * First, there's the issue of whose rights should be used when delivering * to "|command" or to /file/name. With alias databases, the rights are * those of who owns the alias, i.e. the database owner. With aliases * owned by root, a default user is used instead. When an alias with * default rights references an include file owned by an ordinary user, * we must use the rights of the include file owner, otherwise the * include file owner could take control of the default account. * * Secondly, there's the question of who to notify of delivery problems. * With aliases that have an owner- alias, the latter is used to set the * sender and owner attributes. Otherwise, the owner attribute is reset * (the alias is globally visible and could be sent to by anyone). */ for (cpp = alias_maps->argv->argv; *cpp; cpp++) { if ((dict = dict_handle(*cpp)) == 0) msg_panic("%s: dictionary not found: %s", myname, *cpp); if ((alias_result = dict_get(dict, name)) != 0) { if (msg_verbose) msg_info("%s: %s: %s = %s", myname, *cpp, name, alias_result); /* * Don't expand a verify-only request. */ if (state.request->flags & DEL_REQ_FLAG_MTA_VRFY) { dsb_simple(state.msg_attr.why, "2.0.0", "aliased to %s", alias_result); *statusp = sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr)); return (YES); } /* * DELIVERY POLICY * * Update the expansion type attribute, so we can decide if * deliveries to |command and /file/name are allowed at all. */ state.msg_attr.exp_type = EXPAND_TYPE_ALIAS; /* * DELIVERY RIGHTS * * What rights to use for |command and /file/name deliveries? The * command and file code will use default rights when the alias * database is owned by root, otherwise it will use the rights of * the alias database owner. */ if ((alias_uid = dict_owner(*cpp)) == 0) { alias_pwd = 0; RESET_USER_ATTR(usr_attr, state.level); } else { if ((alias_pwd = mypwuid(alias_uid)) == 0) { msg_warn("cannot find alias database owner for %s", *cpp); dsb_simple(state.msg_attr.why, "4.3.0", "cannot find alias database owner"); *statusp = defer_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr)); return (YES); } SET_USER_ATTR(usr_attr, alias_pwd, state.level); } /* * WHERE TO REPORT DELIVERY PROBLEMS. * * Use the owner- alias if one is specified, otherwise reset the * owner attribute and use the include file ownership if we can. * Save the dict_lookup() result before something clobbers it. * * Don't match aliases that are based on regexps. */ #define OWNER_ASSIGN(own) \ (own = (var_ownreq_special == 0 ? 0 : \ concatenate("owner-", name, (char *) 0))) saved_alias_result = mystrdup(alias_result); if (OWNER_ASSIGN(owner) != 0 && (owner_rhs = maps_find(alias_maps, owner, DICT_FLAG_NONE)) != 0) { canon_owner = canon_addr_internal(vstring_alloc(10), var_exp_own_alias ? owner_rhs : owner); /* Set envelope sender and owner attribute. */ SET_OWNER_ATTR(state.msg_attr, STR(canon_owner), state.level); } else { canon_owner = 0; /* Note: this does not reset the envelope sender. */ if (var_reset_owner_attr) RESET_OWNER_ATTR(state.msg_attr, state.level); } /* * EXTERNAL LOOP CONTROL * * Set the delivered message attribute to the recipient, so that * this message will list the correct forwarding address. */ if (var_frozen_delivered == 0) state.msg_attr.delivered = state.msg_attr.rcpt.address; /* * Deliver. */ alias_count = 0; if (dict_errno != 0) { dsb_simple(state.msg_attr.why, "4.3.0", "alias database unavailable"); *statusp = defer_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr)); } else { /* * XXX DSN * * When delivering to a mailing list (i.e. the envelope sender * is replaced) the ENVID, NOTIFY, RET, and ORCPT parameters * which accompany the redistributed message MUST NOT be * derived from those of the original message. * * When delivering to an alias (i.e. the envelope sender is not * replaced) any ENVID, RET, or ORCPT parameters are * propagated to all forwarding addresses associated with * that alias. The NOTIFY parameter is propagated to the * forwarding addresses, except that any SUCCESS keyword is * removed. */ #define DSN_SAVE_UPDATE(saved, old, new) do { \ saved = old; \ old = new; \ } while (0) DSN_SAVE_UPDATE(dsn_notify, state.msg_attr.rcpt.dsn_notify, dsn_notify == DSN_NOTIFY_SUCCESS ? DSN_NOTIFY_NEVER : dsn_notify & ~DSN_NOTIFY_SUCCESS); if (canon_owner != 0) { DSN_SAVE_UPDATE(dsn_envid, state.msg_attr.dsn_envid, ""); DSN_SAVE_UPDATE(dsn_ret, state.msg_attr.dsn_ret, 0); DSN_SAVE_UPDATE(dsn_orcpt, state.msg_attr.rcpt.dsn_orcpt, ""); state.msg_attr.rcpt.orig_addr = ""; } *statusp = deliver_token_string(state, usr_attr, saved_alias_result, &alias_count); #if 0 if (var_ownreq_special && strncmp("owner-", state.msg_attr.sender, 6) != 0 && alias_count > 10) msg_warn("mailing list \"%s\" needs an \"owner-%s\" alias", name, name); #endif if (alias_count < 1) { msg_warn("no recipient in alias lookup result for %s", name); dsb_simple(state.msg_attr.why, "4.3.0", "alias database unavailable"); *statusp = defer_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr)); } else { /* * XXX DSN * * When delivering to a mailing list (i.e. the envelope * sender address is replaced) and NOTIFY=SUCCESS was * specified, report a DSN of "delivered". * * When delivering to an alias (i.e. the envelope sender * address is not replaced) and NOTIFY=SUCCESS was * specified, report a DSN of "expanded". */ if (dsn_notify & DSN_NOTIFY_SUCCESS) { state.msg_attr.rcpt.dsn_notify = dsn_notify; if (canon_owner != 0) { state.msg_attr.dsn_envid = dsn_envid; state.msg_attr.dsn_ret = dsn_ret; state.msg_attr.rcpt.dsn_orcpt = dsn_orcpt; } dsb_update(state.msg_attr.why, "2.0.0", canon_owner ? "delivered" : "expanded", DSB_SKIP_RMTA, DSB_SKIP_REPLY, "alias expanded"); (void) trace_append(BOUNCE_FLAG_NONE, SENT_ATTR(state.msg_attr)); } } } myfree(saved_alias_result); if (owner) myfree(owner); if (canon_owner) vstring_free(canon_owner); if (alias_pwd) mypwfree(alias_pwd); return (YES); } /* * If the alias database was inaccessible for some reason, defer * further delivery for the current top-level recipient. */ if (dict_errno != 0) { dsb_simple(state.msg_attr.why, "4.3.0", "alias database unavailable"); *statusp = defer_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr)); return (YES); } else { if (msg_verbose) msg_info("%s: %s: %s not found", myname, *cpp, name); } } /* * Try delivery to a local user instead. */ return (NO); }
static int deliver_switch(LOCAL_STATE state, USER_ATTR usr_attr) { const char *myname = "deliver_switch"; int status = 0; struct stat st; struct mypasswd *mypwd; /* * Make verbose logging easier to understand. */ state.level++; if (msg_verbose) MSG_LOG_STATE(myname, state); /* * \user is special: it means don't do any alias or forward expansion. * * XXX This code currently does not work due to revision of the RFC822 * address parser. \user should be permitted only in locally specified * aliases, includes or forward files. * * XXX Should test for presence of user home directory. */ if (state.msg_attr.rcpt.address[0] == '\\') { state.msg_attr.rcpt.address++, state.msg_attr.local++, state.msg_attr.user++; if (deliver_mailbox(state, usr_attr, &status) == 0) status = deliver_unknown(state, usr_attr); return (status); } /* * Otherwise, alias expansion has highest precedence. First look up the * full localpart, then the bare user. Obey the address extension * propagation policy. */ state.msg_attr.unmatched = 0; if (deliver_alias(state, usr_attr, state.msg_attr.local, &status)) return (status); if (state.msg_attr.extension != 0) { if (local_ext_prop_mask & EXT_PROP_ALIAS) state.msg_attr.unmatched = state.msg_attr.extension; if (deliver_alias(state, usr_attr, state.msg_attr.user, &status)) return (status); state.msg_attr.unmatched = state.msg_attr.extension; } /* * Special case for mail locally forwarded or aliased to a different * local address. Resubmit the message via the cleanup service, so that * each recipient gets a separate delivery queue file status record in * the new queue file. The downside of this approach is that mutually * recursive .forward files cause a mail forwarding loop. Fortunately, * the loop can be broken by the use of the Delivered-To: message header. * * The code below must not trigger on mail sent to an alias that has no * owner- companion, so that mail for an alias first.last->username is * delivered directly, instead of going through username->first.last * canonical mappings in the cleanup service. The downside of this * approach is that recipients in the expansion of an alias without * owner- won't have separate delivery queue file status records, because * for them, the message won't be resubmitted as a new queue file. * * Do something sensible on systems that receive mail for multiple domains, * such as primary.name and secondary.name. Don't resubmit the message * when mail for `[email protected]' is delivered to a .forward file * that lists `user' or `[email protected]'. We already know that the * recipient domain is local, so we only have to compare local parts. */ if (state.msg_attr.owner != 0 && strcasecmp(state.msg_attr.owner, state.msg_attr.user) != 0) return (deliver_indirect(state)); /* * Always forward recipients in :include: files. */ if (state.msg_attr.exp_type == EXPAND_TYPE_INCL) return (deliver_indirect(state)); /* * Delivery to local user. First try expansion of the recipient's * $HOME/.forward file, then mailbox delivery. Back off when the user's * home directory does not exist. */ if (var_stat_home_dir && (mypwd = mypwnam(state.msg_attr.user)) != 0 && stat_as(mypwd->pw_dir, &st, mypwd->pw_uid, mypwd->pw_gid) < 0) { dsb_simple(state.msg_attr.why, "4.3.0", "cannot access home directory %s: %m", mypwd->pw_dir); return (defer_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr))); } if (deliver_dotforward(state, usr_attr, &status) == 0 && deliver_mailbox(state, usr_attr, &status) == 0) status = deliver_unknown(state, usr_attr); return (status); }
int deliver_maildir(LOCAL_STATE state, USER_ATTR usr_attr) { const char *myname = "deliver_maildir"; char *newdir; char *tmpdir; char *curdir; char *tmpfile; char *newfile; DSN_BUF *why = state.msg_attr.why; VSTRING *buf; VSTREAM *dst; int mail_copy_status; int deliver_status; int copy_flags; struct stat st; struct timeval starttime; GETTIMEOFDAY(&starttime); /* * Make verbose logging easier to understand. */ state.level++; if (msg_verbose) MSG_LOG_STATE(myname, state); /* * Don't deliver trace-only requests. */ if (DEL_REQ_TRACE_ONLY(state.request->flags)) { dsb_simple(why, "2.0.0", "delivers to maildir"); return (sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr))); } /* * Initialize. Assume the operation will fail. Set the delivered * attribute to reflect the final recipient. */ if (vstream_fseek(state.msg_attr.fp, state.msg_attr.offset, SEEK_SET) < 0) msg_fatal("seek message file %s: %m", VSTREAM_PATH(state.msg_attr.fp)); state.msg_attr.delivered = state.msg_attr.rcpt.address; mail_copy_status = MAIL_COPY_STAT_WRITE; buf = vstring_alloc(100); copy_flags = MAIL_COPY_TOFILE | MAIL_COPY_RETURN_PATH | MAIL_COPY_DELIVERED | MAIL_COPY_ORIG_RCPT; newdir = concatenate(usr_attr.mailbox, "new/", (char *) 0); tmpdir = concatenate(usr_attr.mailbox, "tmp/", (char *) 0); curdir = concatenate(usr_attr.mailbox, "cur/", (char *) 0); /* * Create and write the file as the recipient, so that file quota work. * Create any missing directories on the fly. The file name is chosen * according to ftp://koobera.math.uic.edu/www/proto/maildir.html: * * "A unique name has three pieces, separated by dots. On the left is the * result of time(). On the right is the result of gethostname(). In the * middle is something that doesn't repeat within one second on a single * host. I fork a new process for each delivery, so I just use the * process ID. If you're delivering several messages from one process, * use starttime.pid_count.host, where starttime is the time that your * process started, and count is the number of messages you've * delivered." * * Well, that stopped working on fast machines, and on operating systems * that randomize process ID values. When creating a file in tmp/ we use * the process ID because it still is an exclusive resource. When moving * the file to new/ we use the device number and inode number. I do not * care if this breaks on a remote AFS file system, because people should * know better. * * On January 26, 2003, http://cr.yp.to/proto/maildir.html said: * * A unique name has three pieces, separated by dots. On the left is the * result of time() or the second counter from gettimeofday(). On the * right is the result of gethostname(). (To deal with invalid host * names, replace / with \057 and : with \072.) In the middle is a * delivery identifier, discussed below. * * [...] * * Modern delivery identifiers are created by concatenating enough of the * following strings to guarantee uniqueness: * * [...] * * In, where n is (in hexadecimal) the UNIX inode number of this file. * Unfortunately, inode numbers aren't always available through NFS. * * Vn, where n is (in hexadecimal) the UNIX device number of this file. * Unfortunately, device numbers aren't always available through NFS. * (Device numbers are also not helpful with the standard UNIX * filesystem: a maildir has to be within a single UNIX device for link() * and rename() to work.) * * Mn, where n is (in decimal) the microsecond counter from the same * gettimeofday() used for the left part of the unique name. * * Pn, where n is (in decimal) the process ID. * * [...] */ set_eugid(usr_attr.uid, usr_attr.gid); vstring_sprintf(buf, "%lu.P%d.%s", (unsigned long) starttime.tv_sec, var_pid, get_hostname()); tmpfile = concatenate(tmpdir, STR(buf), (char *) 0); newfile = 0; if ((dst = vstream_fopen(tmpfile, O_WRONLY | O_CREAT | O_EXCL, 0600)) == 0 && (errno != ENOENT || make_dirs(tmpdir, 0700) < 0 || (dst = vstream_fopen(tmpfile, O_WRONLY | O_CREAT | O_EXCL, 0600)) == 0)) { dsb_simple(why, mbox_dsn(errno, "4.2.0"), "create maildir file %s: %m", tmpfile); } else if (fstat(vstream_fileno(dst), &st) < 0) { /* * Coverity 200604: file descriptor leak in code that never executes. * Code replaced by msg_fatal(), as it is not worthwhile to continue * after an impossible error condition. */ msg_fatal("fstat %s: %m", tmpfile); } else { vstring_sprintf(buf, "%lu.V%lxI%lxM%lu.%s", (unsigned long) starttime.tv_sec, (unsigned long) st.st_dev, (unsigned long) st.st_ino, (unsigned long) starttime.tv_usec, get_hostname()); newfile = concatenate(newdir, STR(buf), (char *) 0); if ((mail_copy_status = mail_copy(COPY_ATTR(state.msg_attr), dst, copy_flags, "\n", why)) == 0) { if (sane_link(tmpfile, newfile) < 0 && (errno != ENOENT || (make_dirs(curdir, 0700), make_dirs(newdir, 0700)) < 0 || sane_link(tmpfile, newfile) < 0)) { dsb_simple(why, mbox_dsn(errno, "4.2.0"), "create maildir file %s: %m", newfile); mail_copy_status = MAIL_COPY_STAT_WRITE; } } if (unlink(tmpfile) < 0) msg_warn("remove %s: %m", tmpfile); } set_eugid(var_owner_uid, var_owner_gid); /* * The maildir location is controlled by the mail administrator. If * delivery fails, try again later. We would just bounce when the maildir * location possibly under user control. */ if (mail_copy_status & MAIL_COPY_STAT_CORRUPT) { deliver_status = DEL_STAT_DEFER; } else if (mail_copy_status != 0) { if (errno == EACCES) { msg_warn("maildir access problem for UID/GID=%lu/%lu: %s", (long) usr_attr.uid, (long) usr_attr.gid, STR(why->reason)); msg_warn("perhaps you need to create the maildirs in advance"); } vstring_sprintf_prepend(why->reason, "maildir delivery failed: "); deliver_status = (STR(why->status)[0] == '4' ? defer_append : bounce_append) (BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr)); } else { dsb_simple(why, "2.0.0", "delivered to maildir"); deliver_status = sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr)); } vstring_free(buf); myfree(newdir); myfree(tmpdir); myfree(curdir); myfree(tmpfile); if (newfile) myfree(newfile); return (deliver_status); }
int smtp_sasl_authenticate(SMTP_SESSION *session, DSN_BUF *why) { const char *myname = "smtp_sasl_authenticate"; SMTP_RESP *resp; const char *mechanism; int result; char *line; int steps = 0; /* * Sanity check. */ if (session->sasl_mechanism_list == 0) msg_panic("%s: no mechanism list", myname); if (msg_verbose) msg_info("%s: %s: SASL mechanisms %s", myname, session->namaddrport, session->sasl_mechanism_list); /* * Avoid repeated login failures after a recent 535 error. */ #ifdef HAVE_SASL_AUTH_CACHE if (smtp_sasl_auth_cache && smtp_sasl_auth_cache_find(smtp_sasl_auth_cache, session)) { char *resp_dsn = smtp_sasl_auth_cache_dsn(smtp_sasl_auth_cache); char *resp_str = smtp_sasl_auth_cache_text(smtp_sasl_auth_cache); if (var_smtp_sasl_auth_soft_bounce && resp_dsn[0] == '5') resp_dsn[0] = '4'; dsb_update(why, resp_dsn, DSB_DEF_ACTION, DSB_MTYPE_DNS, session->host, var_procname, resp_str, "SASL [CACHED] authentication failed; server %s said: %s", session->host, resp_str); return (0); } #endif /* * Start the client side authentication protocol. */ result = xsasl_client_first(session->sasl_client, session->sasl_mechanism_list, session->sasl_username, session->sasl_passwd, &mechanism, session->sasl_reply); if (result != XSASL_AUTH_OK) { dsb_update(why, "4.7.0", DSB_DEF_ACTION, DSB_SKIP_RMTA, DSB_DTYPE_SASL, STR(session->sasl_reply), "SASL authentication failed; " "cannot authenticate to server %s: %s", session->namaddr, STR(session->sasl_reply)); return (-1); } /* * Send the AUTH command and the optional initial client response. * sasl_encode64() produces four bytes for each complete or incomplete * triple of input bytes. Allocate an extra byte for string termination. */ if (LEN(session->sasl_reply) > 0) { smtp_chat_cmd(session, "AUTH %s %s", mechanism, STR(session->sasl_reply)); } else { smtp_chat_cmd(session, "AUTH %s", mechanism); } /* * Step through the authentication protocol until the server tells us * that we are done. */ while ((resp = smtp_chat_resp(session))->code / 100 == 3) { /* * Sanity check. */ if (++steps > 100) { dsb_simple(why, "4.3.0", "SASL authentication failed; " "authentication protocol loop with server %s", session->namaddr); return (-1); } /* * Process a server challenge. */ line = resp->str; (void) mystrtok(&line, "- \t\n"); /* skip over result code */ result = xsasl_client_next(session->sasl_client, line, session->sasl_reply); if (result != XSASL_AUTH_OK) { dsb_update(why, "4.7.0", DSB_DEF_ACTION, /* Fix 200512 */ DSB_SKIP_RMTA, DSB_DTYPE_SASL, STR(session->sasl_reply), "SASL authentication failed; " "cannot authenticate to server %s: %s", session->namaddr, STR(session->sasl_reply)); return (-1); /* Fix 200512 */ } /* * Send a client response. */ smtp_chat_cmd(session, "%s", STR(session->sasl_reply)); } /* * We completed the authentication protocol. */ if (resp->code / 100 != 2) { #ifdef HAVE_SASL_AUTH_CACHE /* Update the 535 authentication failure cache. */ if (smtp_sasl_auth_cache && resp->code == 535) smtp_sasl_auth_cache_store(smtp_sasl_auth_cache, session, resp); #endif if (var_smtp_sasl_auth_soft_bounce && resp->code / 100 == 5) STR(resp->dsn_buf)[0] = '4'; dsb_update(why, resp->dsn, DSB_DEF_ACTION, DSB_MTYPE_DNS, session->host, var_procname, resp->str, "SASL authentication failed; server %s said: %s", session->namaddr, resp->str); return (0); } return (1); }
int deliver_mailbox(LOCAL_STATE state, USER_ATTR usr_attr, int *statusp) { const char *myname = "deliver_mailbox"; const char *mailbox_res; const char *uid_res; const char *gid_res; DSN_BUF *why = state.msg_attr.why; long n; /* * Make verbose logging easier to understand. */ state.level++; if (msg_verbose) MSG_LOG_STATE(myname, state); /* * Sanity check. */ if (*var_virt_mailbox_base != '/') msg_fatal("do not specify relative pathname: %s = %s", VAR_VIRT_MAILBOX_BASE, var_virt_mailbox_base); /* * Look up the mailbox location. Bounce if not found, defer in case of * trouble. */ #define IGNORE_EXTENSION ((char **) 0) mailbox_res = mail_addr_find(virtual_mailbox_maps, state.msg_attr.user, IGNORE_EXTENSION); if (mailbox_res == 0) { if (virtual_mailbox_maps->error == 0) return (NO); msg_warn("table %s: lookup %s: %m", virtual_mailbox_maps->title, state.msg_attr.user); dsb_simple(why, "4.3.5", "mail system configuration error"); *statusp = defer_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr)); return (YES); } usr_attr.mailbox = concatenate(var_virt_mailbox_base, "/", mailbox_res, (char *) 0); #define RETURN(res) { myfree(usr_attr.mailbox); return (res); } /* * Look up the mailbox owner rights. Defer in case of trouble. */ uid_res = mail_addr_find(virtual_uid_maps, state.msg_attr.user, IGNORE_EXTENSION); if (uid_res == 0) { msg_warn("recipient %s: not found in %s", state.msg_attr.user, virtual_uid_maps->title); dsb_simple(why, "4.3.5", "mail system configuration error"); *statusp = defer_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr)); RETURN(YES); } if ((n = atol(uid_res)) < var_virt_minimum_uid) { msg_warn("recipient %s: bad uid %s in %s", state.msg_attr.user, uid_res, virtual_uid_maps->title); dsb_simple(why, "4.3.5", "mail system configuration error"); *statusp = defer_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr)); RETURN(YES); } usr_attr.uid = (uid_t) n; /* * Look up the mailbox group rights. Defer in case of trouble. */ gid_res = mail_addr_find(virtual_gid_maps, state.msg_attr.user, IGNORE_EXTENSION); if (gid_res == 0) { msg_warn("recipient %s: not found in %s", state.msg_attr.user, virtual_gid_maps->title); dsb_simple(why, "4.3.5", "mail system configuration error"); *statusp = defer_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr)); RETURN(YES); } if ((n = atol(gid_res)) <= 0) { msg_warn("recipient %s: bad gid %s in %s", state.msg_attr.user, gid_res, virtual_gid_maps->title); dsb_simple(why, "4.3.5", "mail system configuration error"); *statusp = defer_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr)); RETURN(YES); } usr_attr.gid = (gid_t) n; if (msg_verbose) msg_info("%s[%d]: set user_attr: %s, uid = %u, gid = %u", myname, state.level, usr_attr.mailbox, (unsigned) usr_attr.uid, (unsigned) usr_attr.gid); /* * Deliver to mailbox or to maildir. */ #define LAST_CHAR(s) (s[strlen(s) - 1]) if (LAST_CHAR(usr_attr.mailbox) == '/') *statusp = deliver_maildir(state, usr_attr); else *statusp = deliver_mailbox_file(state, usr_attr); /* * Cleanup. */ RETURN(YES); }
int deliver_include(LOCAL_STATE state, USER_ATTR usr_attr, char *path) { const char *myname = "deliver_include"; struct stat st; struct mypasswd *file_pwd = 0; int status; VSTREAM *fp; int fd; /* * Make verbose logging easier to understand. */ state.level++; if (msg_verbose) MSG_LOG_STATE(myname, state); /* * DUPLICATE ELIMINATION * * Don't process this include file more than once as this particular user. */ if (been_here(state.dup_filter, "include %ld %s", (long) usr_attr.uid, path)) return (0); state.msg_attr.exp_from = state.msg_attr.local; /* * Can of worms. Allow this include file to be symlinked, but disallow * inclusion of special files or of files with world write permission * enabled. */ if (*path != '/') { msg_warn(":include:%s uses a relative path", path); dsb_simple(state.msg_attr.why, "5.3.5", "mail system configuration error"); return (bounce_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr))); } if (stat_as(path, &st, usr_attr.uid, usr_attr.gid) < 0) { msg_warn("unable to lookup :include: file %s: %m", path); dsb_simple(state.msg_attr.why, "5.3.5", "mail system configuration error"); return (bounce_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr))); } if (S_ISREG(st.st_mode) == 0) { msg_warn(":include: file %s is not a regular file", path); dsb_simple(state.msg_attr.why, "5.3.5", "mail system configuration error"); return (bounce_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr))); } if (st.st_mode & S_IWOTH) { msg_warn(":include: file %s is world writable", path); dsb_simple(state.msg_attr.why, "5.3.5", "mail system configuration error"); return (bounce_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr))); } /* * DELIVERY POLICY * * Set the expansion type attribute so that we can decide if destinations * such as /file/name and |command are allowed at all. */ state.msg_attr.exp_type = EXPAND_TYPE_INCL; /* * DELIVERY RIGHTS * * When a non-root include file is listed in a root-owned alias, use the * rights of the include file owner. We do not want to give the include * file owner control of the default account. * * When an include file is listed in a user-owned alias or .forward file, * leave the delivery rights alone. Users should not be able to make * things happen with someone else's rights just by including some file * that is owned by their victim. */ if (usr_attr.uid == 0) { if ((errno = mypwuid_err(st.st_uid, &file_pwd)) != 0 || file_pwd == 0) { msg_warn(errno ? "cannot find username for uid %ld: %m" : "cannot find username for uid %ld", (long) st.st_uid); msg_warn("%s: cannot find :include: file owner username", path); dsb_simple(state.msg_attr.why, "4.3.5", "mail system configuration error"); return (defer_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr))); } if (file_pwd->pw_uid != 0) SET_USER_ATTR(usr_attr, file_pwd, state.level); } /* * MESSAGE FORWARDING * * When no owner attribute is set (either via an owner- alias, or as part of * .forward file processing), set the owner attribute, to disable direct * delivery of local recipients. By now it is clear that the owner * attribute should have been called forwarder instead. */ if (state.msg_attr.owner == 0) state.msg_attr.owner = state.msg_attr.rcpt.address; /* * From here on no early returns or we have a memory leak. * * FILE OPEN RIGHTS * * Use the delivery rights to open the include file. When no delivery rights * were established sofar, the file containing the :include: is owned by * root, so it should be OK to open any file that is accessible to root. * The command and file delivery routines are responsible for setting the * proper delivery rights. These are the rights of the default user, in * case the :include: is in a root-owned alias. * * Don't propagate unmatched extensions unless permitted to do so. */ #define FOPEN_AS(p,u,g) ((fd = open_as(p,O_RDONLY,0,u,g)) >= 0 ? \ vstream_fdopen(fd,O_RDONLY) : 0) if ((fp = FOPEN_AS(path, usr_attr.uid, usr_attr.gid)) == 0) { msg_warn("cannot open include file %s: %m", path); dsb_simple(state.msg_attr.why, "5.3.5", "mail system configuration error"); status = bounce_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr)); } else { if ((local_ext_prop_mask & EXT_PROP_INCLUDE) == 0) state.msg_attr.unmatched = 0; close_on_exec(vstream_fileno(fp), CLOSE_ON_EXEC); status = deliver_token_stream(state, usr_attr, fp, (int *) 0); if (vstream_fclose(fp)) msg_warn("close %s: %m", path); } /* * Cleanup. */ if (file_pwd) mypwfree(file_pwd); return (status); }
static DNS_RR *smtp_addr_one(DNS_RR *addr_list, const char *host, unsigned pref, DSN_BUF *why) { const char *myname = "smtp_addr_one"; DNS_RR *addr = 0; DNS_RR *rr; int aierr; struct addrinfo *res0; struct addrinfo *res; INET_PROTO_INFO *proto_info = inet_proto_info(); int found; if (msg_verbose) msg_info("%s: host %s", myname, host); /* * Interpret a numerical name as an address. */ if (hostaddr_to_sockaddr(host, (char *) 0, 0, &res0) == 0 && strchr((char *) proto_info->sa_family_list, res0->ai_family) != 0) { if ((addr = dns_sa_to_rr(host, pref, res0->ai_addr)) == 0) msg_fatal("host %s: conversion error for address family %d: %m", host, ((struct sockaddr *) (res0->ai_addr))->sa_family); addr_list = dns_rr_append(addr_list, addr); freeaddrinfo(res0); return (addr_list); } /* * Use DNS lookup, but keep the option open to use native name service. * * XXX A soft error dominates past and future hard errors. Therefore we * should not clobber a soft error text and status code. */ if (smtp_host_lookup_mask & SMTP_HOST_FLAG_DNS) { switch (dns_lookup_v(host, smtp_dns_res_opt, &addr, (VSTRING *) 0, why->reason, DNS_REQ_FLAG_NONE, proto_info->dns_atype_list)) { case DNS_OK: for (rr = addr; rr; rr = rr->next) rr->pref = pref; addr_list = dns_rr_append(addr_list, addr); return (addr_list); default: dsb_status(why, "4.4.3"); return (addr_list); case DNS_FAIL: dsb_status(why, SMTP_HAS_SOFT_DSN(why) ? "4.4.3" : "5.4.3"); return (addr_list); case DNS_INVAL: dsb_status(why, SMTP_HAS_SOFT_DSN(why) ? "4.4.4" : "5.4.4"); return (addr_list); case DNS_NOTFOUND: dsb_status(why, SMTP_HAS_SOFT_DSN(why) ? "4.4.4" : "5.4.4"); /* maybe native naming service will succeed */ break; } } /* * Use the native name service which also looks in /etc/hosts. * * XXX A soft error dominates past and future hard errors. Therefore we * should not clobber a soft error text and status code. */ #define RETRY_AI_ERROR(e) \ ((e) == EAI_AGAIN || (e) == EAI_MEMORY || (e) == EAI_SYSTEM) #ifdef EAI_NODATA #define DSN_NOHOST(e) \ ((e) == EAI_AGAIN || (e) == EAI_NODATA || (e) == EAI_NONAME) #else #define DSN_NOHOST(e) \ ((e) == EAI_AGAIN || (e) == EAI_NONAME) #endif if (smtp_host_lookup_mask & SMTP_HOST_FLAG_NATIVE) { if ((aierr = hostname_to_sockaddr(host, (char *) 0, 0, &res0)) != 0) { dsb_simple(why, (SMTP_HAS_SOFT_DSN(why) || RETRY_AI_ERROR(aierr)) ? (DSN_NOHOST(aierr) ? "4.4.4" : "4.3.0") : (DSN_NOHOST(aierr) ? "5.4.4" : "5.3.0"), "unable to look up host %s: %s", host, MAI_STRERROR(aierr)); } else { for (found = 0, res = res0; res != 0; res = res->ai_next) { if (strchr((char *) proto_info->sa_family_list, res->ai_family) == 0) { msg_info("skipping address family %d for host %s", res->ai_family, host); continue; } found++; if ((addr = dns_sa_to_rr(host, pref, res->ai_addr)) == 0) msg_fatal("host %s: conversion error for address family %d: %m", host, ((struct sockaddr *) (res0->ai_addr))->sa_family); addr_list = dns_rr_append(addr_list, addr); } freeaddrinfo(res0); if (found == 0) { dsb_simple(why, SMTP_HAS_SOFT_DSN(why) ? "4.4.4" : "5.4.4", "%s: host not found", host); } return (addr_list); } } /* * No further alternatives for host lookup. */ return (addr_list); }