Esempio n. 1
0
static void smtp_connect_inet(SMTP_STATE *state, const char *nexthop,
			              char *def_service)
{
    DELIVER_REQUEST *request = state->request;
    SMTP_ITERATOR *iter = state->iterator;
    ARGV   *sites;
    char   *dest;
    char  **cpp;
    int     non_fallback_sites;
    int     retry_plain = 0;
    DSN_BUF *why = state->why;

    /*
     * For sanity, require that at least one of INET or INET6 is enabled.
     * Otherwise, we can't look up interface information, and we can't
     * convert names or addresses.
     */
    if (inet_proto_info()->ai_family_list[0] == 0) {
	dsb_simple(why, "4.4.4", "all network protocols are disabled");
	return;
    }

    /*
     * Future proofing: do a null destination sanity check in case we allow
     * the primary destination to be a list (it could be just separators).
     */
    sites = argv_alloc(1);
    argv_add(sites, nexthop, (char *) 0);
    if (sites->argc == 0)
	msg_panic("null destination: \"%s\"", nexthop);
    non_fallback_sites = sites->argc;
    argv_split_append(sites, var_fallback_relay, CHARS_COMMA_SP);

    /*
     * Don't give up after a hard host lookup error until we have tried the
     * fallback relay servers.
     * 
     * Don't bounce mail after a host lookup problem with a relayhost or with a
     * fallback relay.
     * 
     * Don't give up after a qualifying soft error until we have tried all
     * qualifying backup mail servers.
     * 
     * All this means that error handling and error reporting depends on whether
     * the error qualifies for trying to deliver to a backup mail server, or
     * whether we're looking up a relayhost or fallback relay. The challenge
     * then is to build this into the pre-existing SMTP client without
     * getting lost in the complexity.
     */
#define IS_FALLBACK_RELAY(cpp, sites, non_fallback_sites) \
	    (*(cpp) && (cpp) >= (sites)->argv + (non_fallback_sites))

    for (cpp = sites->argv, (state->misc_flags |= SMTP_MISC_FLAG_FIRST_NEXTHOP);
	 SMTP_RCPT_LEFT(state) > 0 && (dest = *cpp) != 0;
	 cpp++, (state->misc_flags &= ~SMTP_MISC_FLAG_FIRST_NEXTHOP)) {
	char   *dest_buf;
	char   *domain;
	unsigned port;
	DNS_RR *addr_list;
	DNS_RR *addr;
	DNS_RR *next;
	int     addr_count;
	int     sess_count;
	SMTP_SESSION *session;
	int     lookup_mx;
	unsigned domain_best_pref;
	MAI_HOSTADDR_STR hostaddr;

	if (cpp[1] == 0)
	    state->misc_flags |= SMTP_MISC_FLAG_FINAL_NEXTHOP;

	/*
	 * Parse the destination. If no TCP port is specified, use the port
	 * that is reserved for the protocol (SMTP or LMTP).
	 */
	dest_buf = smtp_parse_destination(dest, def_service, &domain, &port);
	if (var_helpful_warnings && var_smtp_tls_wrappermode == 0
	    && ntohs(port) == 465) {
	    msg_info("SMTPS wrappermode (TCP port 465) requires setting "
		     "\"%s = yes\", and \"%s = encrypt\" (or stronger)",
		     VAR_LMTP_SMTP(TLS_WRAPPER), VAR_LMTP_SMTP(TLS_LEVEL));
	}
#define NO_HOST	""				/* safety */
#define NO_ADDR	""				/* safety */

	SMTP_ITER_INIT(iter, dest, NO_HOST, NO_ADDR, port, state);

	/*
	 * Resolve an SMTP or LMTP server. In the case of SMTP, skip mail
	 * exchanger lookups when a quoted host is specified or when DNS
	 * lookups are disabled.
	 */
	if (msg_verbose)
	    msg_info("connecting to %s port %d", domain, ntohs(port));
	if (smtp_mode) {
	    if (ntohs(port) == IPPORT_SMTP)
		state->misc_flags |= SMTP_MISC_FLAG_LOOP_DETECT;
	    else
		state->misc_flags &= ~SMTP_MISC_FLAG_LOOP_DETECT;
	    lookup_mx = (smtp_dns_support != SMTP_DNS_DISABLED && *dest != '[');
	} else
	    lookup_mx = 0;
	if (!lookup_mx) {
	    addr_list = smtp_host_addr(domain, state->misc_flags, why);
	    /* XXX We could be an MX host for this destination... */
	} else {
	    int     i_am_mx = 0;

	    addr_list = smtp_domain_addr(domain, &iter->mx, state->misc_flags,
					 why, &i_am_mx);
	    /* If we're MX host, don't connect to non-MX backups. */
	    if (i_am_mx)
		state->misc_flags |= SMTP_MISC_FLAG_FINAL_NEXTHOP;
	}

	/*
	 * Don't try fall-back hosts if mail loops to myself. That would just
	 * make the problem worse.
	 */
	if (addr_list == 0 && SMTP_HAS_LOOP_DSN(why))
	    state->misc_flags |= SMTP_MISC_FLAG_FINAL_NEXTHOP;

	/*
	 * No early loop exit or we have a memory leak with dest_buf.
	 */
	if (addr_list)
	    domain_best_pref = addr_list->pref;

	/*
	 * When session caching is enabled, store the first good session for
	 * this delivery request under the next-hop destination name. All
	 * good sessions will be stored under their specific server IP
	 * address.
	 * 
	 * XXX smtp_session_cache_destinations specifies domain names without
	 * :port, because : is already used for maptype:mapname. Because of
	 * this limitation we use the bare domain without the optional [] or
	 * non-default TCP port.
	 * 
	 * Opportunistic (a.k.a. on-demand) session caching on request by the
	 * queue manager. This is turned temporarily when a destination has a
	 * high volume of mail in the active queue. When the surge reaches
	 * its end, the queue manager requests that connections be retrieved
	 * but not stored.
	 */
	if (addr_list && (state->misc_flags & SMTP_MISC_FLAG_FIRST_NEXTHOP)) {
	    smtp_cache_policy(state, domain);
	    if (state->misc_flags & SMTP_MISC_FLAG_CONN_CACHE_MASK)
		SET_NEXTHOP_STATE(state, dest);
	}

	/*
	 * Delete visited cached hosts from the address list.
	 * 
	 * Optionally search the connection cache by domain name or by primary
	 * MX address before we try to create new connections.
	 * 
	 * Enforce the MX session and MX address counts per next-hop or
	 * fall-back destination. smtp_reuse_session() will truncate the
	 * address list when either limit is reached.
	 */
	if (addr_list && (state->misc_flags & SMTP_MISC_FLAG_CONN_LOAD)) {
	    if (state->cache_used->used > 0)
		smtp_scrub_addr_list(state->cache_used, &addr_list);
	    sess_count = addr_count =
		smtp_reuse_session(state, &addr_list, domain_best_pref);
	} else
	    sess_count = addr_count = 0;

	/*
	 * Connect to an SMTP server: create primary MX connections, and
	 * reuse or create backup MX connections.
	 * 
	 * At the start of an SMTP session, all recipients are unmarked. In the
	 * course of an SMTP session, recipients are marked as KEEP (deliver
	 * to alternate mail server) or DROP (remove from recipient list). At
	 * the end of an SMTP session, weed out the recipient list. Unmark
	 * any left-over recipients and try to deliver them to a backup mail
	 * server.
	 * 
	 * Cache the first good session under the next-hop destination name.
	 * Cache all good sessions under their physical endpoint.
	 * 
	 * Don't query the session cache for primary MX hosts. We already did
	 * that in smtp_reuse_session(), and if any were found in the cache,
	 * they were already deleted from the address list.
	 * 
	 * Currently, we use smtp_reuse_addr() only for SASL-unauthenticated
	 * connections. Furthermore, we rely on smtp_reuse_addr() to look up
	 * an existing SASL-unauthenticated connection only when a new
	 * connection would be guaranteed not to require SASL authentication.
	 * 
	 * In addition, we rely on smtp_reuse_addr() to look up an existing
	 * plaintext connection only when a new connection would be
	 * guaranteed not to use TLS.
	 */
	for (addr = addr_list; SMTP_RCPT_LEFT(state) > 0 && addr; addr = next) {
	    next = addr->next;
	    if (++addr_count == var_smtp_mxaddr_limit)
		next = 0;
	    if (dns_rr_to_pa(addr, &hostaddr) == 0) {
		msg_warn("cannot convert type %s record to printable address",
			 dns_strtype(addr->type));
		/* XXX Assume there is no code at the end of this loop. */
		continue;
	    }
	    vstring_strcpy(iter->addr, hostaddr.buf);
	    vstring_strcpy(iter->host, SMTP_HNAME(addr));
	    iter->rr = addr;
#ifdef USE_TLS
	    if (!smtp_tls_policy_cache_query(why, state->tls, iter)) {
		msg_warn("TLS policy lookup for %s/%s: %s",
			 STR(iter->dest), STR(iter->host), STR(why->reason));
		continue;
		/* XXX Assume there is no code at the end of this loop. */
	    }
	    if (var_smtp_tls_wrappermode
		&& state->tls->level < TLS_LEV_ENCRYPT) {
		msg_warn("%s requires \"%s = encrypt\" (or stronger)",
		      VAR_LMTP_SMTP(TLS_WRAPPER), VAR_LMTP_SMTP(TLS_LEVEL));
		continue;
		/* XXX Assume there is no code at the end of this loop. */
	    }
	    /* Disable TLS when retrying after a handshake failure */
	    if (retry_plain) {
		state->tls->level = TLS_LEV_NONE;
		retry_plain = 0;
	    }
#endif
	    if ((state->misc_flags & SMTP_MISC_FLAG_CONN_LOAD) == 0
		|| addr->pref == domain_best_pref
		|| !(session = smtp_reuse_addr(state,
					  SMTP_KEY_MASK_SCACHE_ENDP_LABEL)))
		session = smtp_connect_addr(iter, why, state->misc_flags);
	    if ((state->session = session) != 0) {
		session->state = state;
#ifdef USE_TLS
		session->tls_nexthop = domain;
#endif
		if (addr->pref == domain_best_pref)
		    session->features |= SMTP_FEATURE_BEST_MX;
		/* Don't count handshake errors towards the session limit. */
		if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP)
		    && next == 0)
		    state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER;
		if ((session->features & SMTP_FEATURE_FROM_CACHE) == 0
		    && smtp_helo(state) != 0) {
#ifdef USE_TLS

		    /*
		     * When an opportunistic TLS handshake fails, try the
		     * same address again, with TLS disabled. See also the
		     * RETRY_AS_PLAINTEXT macro.
		     */
		    if ((retry_plain = session->tls_retry_plain) != 0) {
			--addr_count;
			next = addr;
		    }
#endif

		    /*
		     * When a TLS handshake fails, the stream is marked
		     * "dead" to avoid further I/O over a broken channel.
		     */
		    if (!THIS_SESSION_IS_FORBIDDEN
			&& vstream_ferror(session->stream) == 0
			&& vstream_feof(session->stream) == 0)
			smtp_quit(state);
		} else {
		    /* Do count delivery errors towards the session limit. */
		    if (++sess_count == var_smtp_mxsess_limit)
			next = 0;
		    if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP)
			&& next == 0)
			state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER;
		    smtp_xfer(state);
#ifdef USE_TLS

		    /*
		     * When opportunistic TLS fails after the STARTTLS
		     * handshake, try the same address again, with TLS
		     * disabled. See also the RETRY_AS_PLAINTEXT macro.
		     */
		    if ((retry_plain = session->tls_retry_plain) != 0) {
			--sess_count;
			--addr_count;
			next = addr;
		    }
#endif
		}
		smtp_cleanup_session(state);
	    } else {
		/* The reason already includes the IP address and TCP port. */
		msg_info("%s", STR(why->reason));
	    }
	    /* XXX Code above assumes there is no code at this loop ending. */
	}
	dns_rr_free(addr_list);
	if (iter->mx) {
	    dns_rr_free(iter->mx);
	    iter->mx = 0;			/* Just in case */
	}
	myfree(dest_buf);
	if (state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP)
	    break;
    }

    /*
     * We still need to deliver, bounce or defer some left-over recipients:
     * either mail loops or some backup mail server was unavailable.
     */
    if (SMTP_RCPT_LEFT(state) > 0) {

	/*
	 * In case of a "no error" indication we make up an excuse: we did
	 * find the host address, but we did not attempt to connect to it.
	 * This can happen when the fall-back relay was already tried via a
	 * cached connection, so that the address list scrubber left behind
	 * an empty list.
	 */
	if (!SMTP_HAS_DSN(why)) {
	    dsb_simple(why, "4.3.0",
		       "server unavailable or unable to receive mail");
	}

	/*
	 * Pay attention to what could be configuration problems, and pretend
	 * that these are recoverable rather than bouncing the mail.
	 */
	else if (!SMTP_HAS_SOFT_DSN(why)) {

	    /*
	     * The fall-back destination did not resolve as expected, or it
	     * is refusing to talk to us, or mail for it loops back to us.
	     */
	    if (IS_FALLBACK_RELAY(cpp, sites, non_fallback_sites)) {
		msg_warn("%s configuration problem", VAR_SMTP_FALLBACK);
		vstring_strcpy(why->status, "4.3.5");
		/* XXX Keep the diagnostic code and MTA. */
	    }

	    /*
	     * The next-hop relayhost did not resolve as expected, or it is
	     * refusing to talk to us, or mail for it loops back to us.
	     * 
	     * XXX There is no equivalent safety net for mis-configured
	     * sender-dependent relay hosts. The trivial-rewrite resolver
	     * would have to flag the result, and the queue manager would
	     * have to provide that information to delivery agents.
	     */
	    else if (smtp_mode && strcmp(sites->argv[0], var_relayhost) == 0) {
		msg_warn("%s configuration problem", VAR_RELAYHOST);
		vstring_strcpy(why->status, "4.3.5");
		/* XXX Keep the diagnostic code and MTA. */
	    }

	    /*
	     * Mail for the next-hop destination loops back to myself. Pass
	     * the mail to the best_mx_transport or bounce it.
	     */
	    else if (smtp_mode && SMTP_HAS_LOOP_DSN(why) && *var_bestmx_transp) {
		dsb_reset(why);			/* XXX */
		state->status = deliver_pass_all(MAIL_CLASS_PRIVATE,
						 var_bestmx_transp,
						 request);
		SMTP_RCPT_LEFT(state) = 0;	/* XXX */
	    }
	}
    }

    /*
     * Cleanup.
     */
    if (HAVE_NEXTHOP_STATE(state))
	FREE_NEXTHOP_STATE(state);
    argv_free(sites);
}
Esempio n. 2
0
static void smtp_connect_remote(SMTP_STATE *state, const char *nexthop,
				        char *def_service)
{
    DELIVER_REQUEST *request = state->request;
    ARGV   *sites;
    char   *dest;
    char  **cpp;
    int     non_fallback_sites;
    int     retry_plain = 0;
    DSN_BUF *why = state->why;

    /*
     * First try to deliver to the indicated destination, then try to deliver
     * to the optional fall-back relays.
     * 
     * Future proofing: do a null destination sanity check in case we allow the
     * primary destination to be a list (it could be just separators).
     */
    sites = argv_alloc(1);
    argv_add(sites, nexthop, (char *) 0);
    if (sites->argc == 0)
	msg_panic("null destination: \"%s\"", nexthop);
    non_fallback_sites = sites->argc;
    if ((state->misc_flags & SMTP_MISC_FLAG_USE_LMTP) == 0)
	argv_split_append(sites, var_fallback_relay, ", \t\r\n");

    /*
     * Don't give up after a hard host lookup error until we have tried the
     * fallback relay servers.
     * 
     * Don't bounce mail after a host lookup problem with a relayhost or with a
     * fallback relay.
     * 
     * Don't give up after a qualifying soft error until we have tried all
     * qualifying backup mail servers.
     * 
     * All this means that error handling and error reporting depends on whether
     * the error qualifies for trying to deliver to a backup mail server, or
     * whether we're looking up a relayhost or fallback relay. The challenge
     * then is to build this into the pre-existing SMTP client without
     * getting lost in the complexity.
     */
#define IS_FALLBACK_RELAY(cpp, sites, non_fallback_sites) \
	    (*(cpp) && (cpp) >= (sites)->argv + (non_fallback_sites))

    for (cpp = sites->argv, (state->misc_flags |= SMTP_MISC_FLAG_FIRST_NEXTHOP);
	 SMTP_RCPT_LEFT(state) > 0 && (dest = *cpp) != 0;
	 cpp++, (state->misc_flags &= ~SMTP_MISC_FLAG_FIRST_NEXTHOP)) {
	char   *dest_buf;
	char   *domain;
	unsigned port;
	DNS_RR *addr_list;
	DNS_RR *addr;
	DNS_RR *next;
	int     addr_count;
	int     sess_count;
	SMTP_SESSION *session;
	int     lookup_mx;
	unsigned domain_best_pref;
	MAI_HOSTADDR_STR hostaddr;

	if (cpp[1] == 0)
	    state->misc_flags |= SMTP_MISC_FLAG_FINAL_NEXTHOP;

	/*
	 * Parse the destination. Default is to use the SMTP port. Look up
	 * the address instead of the mail exchanger when a quoted host is
	 * specified, or when DNS lookups are disabled.
	 */
	dest_buf = smtp_parse_destination(dest, def_service, &domain, &port);
	if (var_helpful_warnings && ntohs(port) == 465) {
	    msg_info("CLIENT wrappermode (port smtps/465) is unimplemented");
	    msg_info("instead, send to (port submission/587) with STARTTLS");
	}

	/*
	 * Resolve an SMTP server. Skip mail exchanger lookups when a quoted
	 * host is specified, or when DNS lookups are disabled.
	 */
	if (msg_verbose)
	    msg_info("connecting to %s port %d", domain, ntohs(port));
	if ((state->misc_flags & SMTP_MISC_FLAG_USE_LMTP) == 0) {
	    if (ntohs(port) == IPPORT_SMTP)
		state->misc_flags |= SMTP_MISC_FLAG_LOOP_DETECT;
	    else
		state->misc_flags &= ~SMTP_MISC_FLAG_LOOP_DETECT;
	    lookup_mx = (var_disable_dns == 0 && *dest != '[');
	} else
	    lookup_mx = 0;
	if (!lookup_mx) {
	    addr_list = smtp_host_addr(domain, state->misc_flags, why);
	    /* XXX We could be an MX host for this destination... */
	} else {
	    int     i_am_mx = 0;

	    addr_list = smtp_domain_addr(domain, state->misc_flags,
					 why, &i_am_mx);
	    /* If we're MX host, don't connect to non-MX backups. */
	    if (i_am_mx)
		state->misc_flags |= SMTP_MISC_FLAG_FINAL_NEXTHOP;
	}

	/*
	 * Don't try fall-back hosts if mail loops to myself. That would just
	 * make the problem worse.
	 */
	if (addr_list == 0 && SMTP_HAS_LOOP_DSN(why))
	    state->misc_flags |= SMTP_MISC_FLAG_FINAL_NEXTHOP;

	/*
	 * No early loop exit or we have a memory leak with dest_buf.
	 */
	if (addr_list)
	    domain_best_pref = addr_list->pref;

	/*
	 * When session caching is enabled, store the first good session for
	 * this delivery request under the next-hop destination name. All
	 * good sessions will be stored under their specific server IP
	 * address.
	 * 
	 * XXX Replace sites->argv by (lookup_mx, domain, port) triples so we
	 * don't have to make clumsy ad-hoc copies and keep track of who
	 * free()s the memory.
	 * 
	 * XXX smtp_session_cache_destinations specifies domain names without
	 * :port, because : is already used for maptype:mapname. Because of
	 * this limitation we use the bare domain without the optional [] or
	 * non-default TCP port.
	 * 
	 * Opportunistic (a.k.a. on-demand) session caching on request by the
	 * queue manager. This is turned temporarily when a destination has a
	 * high volume of mail in the active queue.
	 * 
	 * XXX Disable connection caching when sender-dependent authentication
	 * is enabled. We must not send someone elses mail over an
	 * authenticated connection, and we must not send mail that requires
	 * authentication over a connection that wasn't authenticated.
	 */
	if (addr_list && (state->misc_flags & SMTP_MISC_FLAG_FIRST_NEXTHOP)) {
	    smtp_cache_policy(state, domain);
	    if (state->misc_flags & SMTP_MISC_FLAG_CONN_STORE)
		SET_NEXTHOP_STATE(state, lookup_mx, domain, port);
	}

	/*
	 * Delete visited cached hosts from the address list.
	 * 
	 * Optionally search the connection cache by domain name or by primary
	 * MX address before we try to create new connections.
	 * 
	 * Enforce the MX session and MX address counts per next-hop or
	 * fall-back destination. smtp_reuse_session() will truncate the
	 * address list when either limit is reached.
	 */
	if (addr_list && (state->misc_flags & SMTP_MISC_FLAG_CONN_LOAD)) {
	    if (state->cache_used->used > 0)
		smtp_scrub_addr_list(state->cache_used, &addr_list);
	    sess_count = addr_count =
		smtp_reuse_session(state, lookup_mx, domain, port,
				   &addr_list, domain_best_pref);
	} else
	    sess_count = addr_count = 0;

	/*
	 * Connect to an SMTP server: create primary MX connections, and
	 * reuse or create backup MX connections.
	 * 
	 * At the start of an SMTP session, all recipients are unmarked. In the
	 * course of an SMTP session, recipients are marked as KEEP (deliver
	 * to alternate mail server) or DROP (remove from recipient list). At
	 * the end of an SMTP session, weed out the recipient list. Unmark
	 * any left-over recipients and try to deliver them to a backup mail
	 * server.
	 * 
	 * Cache the first good session under the next-hop destination name.
	 * Cache all good sessions under their physical endpoint.
	 * 
	 * Don't query the session cache for primary MX hosts. We already did
	 * that in smtp_reuse_session(), and if any were found in the cache,
	 * they were already deleted from the address list.
	 */
	for (addr = addr_list; SMTP_RCPT_LEFT(state) > 0 && addr; addr = next) {
	    next = addr->next;
	    if (++addr_count == var_smtp_mxaddr_limit)
		next = 0;
	    if ((state->misc_flags & SMTP_MISC_FLAG_CONN_LOAD) == 0
		|| addr->pref == domain_best_pref
		|| dns_rr_to_pa(addr, &hostaddr) == 0
		|| !(session = smtp_reuse_addr(state, hostaddr.buf, port)))
		session = smtp_connect_addr(dest, addr, port, why,
					    state->misc_flags);
	    if ((state->session = session) != 0) {
		session->state = state;
		if (addr->pref == domain_best_pref)
		    session->features |= SMTP_FEATURE_BEST_MX;
		/* Don't count handshake errors towards the session limit. */
		if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP)
		    && next == 0)
		    state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER;
#ifdef USE_TLS
		/* Disable TLS when retrying after a handshake failure */
		if (retry_plain) {
		    if (session->tls_level >= TLS_LEV_ENCRYPT)
			msg_panic("Plain-text retry wrong for mandatory TLS");
		    session->tls_level = TLS_LEV_NONE;
		    retry_plain = 0;
		}
		session->tls_nexthop = domain;	/* for TLS_LEV_SECURE */
#endif
		if ((session->features & SMTP_FEATURE_FROM_CACHE) == 0
		    && smtp_helo(state) != 0) {
#ifdef USE_TLS

		    /*
		     * When an opportunistic TLS handshake fails, try the
		     * same address again, with TLS disabled. See also the
		     * RETRY_AS_PLAINTEXT macro.
		     */
		    if ((retry_plain = session->tls_retry_plain) != 0) {
			--addr_count;
			next = addr;
		    }
#endif

		    /*
		     * When a TLS handshake fails, the stream is marked
		     * "dead" to avoid further I/O over a broken channel.
		     */
		    if (!THIS_SESSION_IS_DEAD
			&& vstream_ferror(session->stream) == 0
			&& vstream_feof(session->stream) == 0)
			smtp_quit(state);
		} else {
		    /* Do count delivery errors towards the session limit. */
		    if (++sess_count == var_smtp_mxsess_limit)
			next = 0;
		    if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP)
			&& next == 0)
			state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER;
		    smtp_xfer(state);
		}
		smtp_cleanup_session(state);
	    } else {
		/* The reason already includes the IP address and TCP port. */
		msg_info("%s", STR(why->reason));
	    }
	    /* Insert: test if we must skip the remaining MX hosts. */
	}
	dns_rr_free(addr_list);
	myfree(dest_buf);
	if (state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP)
	    break;
    }

    /*
     * We still need to deliver, bounce or defer some left-over recipients:
     * either mail loops or some backup mail server was unavailable.
     */
    if (SMTP_RCPT_LEFT(state) > 0) {

	/*
	 * In case of a "no error" indication we make up an excuse: we did
	 * find the host address, but we did not attempt to connect to it.
	 * This can happen when the fall-back relay was already tried via a
	 * cached connection, so that the address list scrubber left behind
	 * an empty list.
	 */
	if (!SMTP_HAS_DSN(why)) {
	    dsb_simple(why, "4.3.0",
		       "server unavailable or unable to receive mail");
	}

	/*
	 * Pay attention to what could be configuration problems, and pretend
	 * that these are recoverable rather than bouncing the mail.
	 */
	else if (!SMTP_HAS_SOFT_DSN(why)
		 && (state->misc_flags & SMTP_MISC_FLAG_USE_LMTP) == 0) {

	    /*
	     * The fall-back destination did not resolve as expected, or it
	     * is refusing to talk to us, or mail for it loops back to us.
	     */
	    if (IS_FALLBACK_RELAY(cpp, sites, non_fallback_sites)) {
		msg_warn("%s configuration problem", VAR_SMTP_FALLBACK);
		vstring_strcpy(why->status, "4.3.5");
		/* XXX Keep the diagnostic code and MTA. */
	    }

	    /*
	     * The next-hop relayhost did not resolve as expected, or it is
	     * refusing to talk to us, or mail for it loops back to us.
	     */
	    else if (strcmp(sites->argv[0], var_relayhost) == 0) {
		msg_warn("%s configuration problem", VAR_RELAYHOST);
		vstring_strcpy(why->status, "4.3.5");
		/* XXX Keep the diagnostic code and MTA. */
	    }

	    /*
	     * Mail for the next-hop destination loops back to myself. Pass
	     * the mail to the best_mx_transport or bounce it.
	     */
	    else if (SMTP_HAS_LOOP_DSN(why) && *var_bestmx_transp) {
		dsb_reset(why);			/* XXX */
		state->status = deliver_pass_all(MAIL_CLASS_PRIVATE,
						 var_bestmx_transp,
						 request);
		SMTP_RCPT_LEFT(state) = 0;	/* XXX */
	    }
	}
    }

    /*
     * Cleanup.
     */
    if (HAVE_NEXTHOP_STATE(state))
	FREE_NEXTHOP_STATE(state);
    argv_free(sites);
}
Esempio n. 3
0
int     main(int argc, char **argv)
{
    static char *full_name = 0;		/* sendmail -F */
    struct stat st;
    char   *slash;
    char   *sender = 0;			/* sendmail -f */
    int     c;
    int     fd;
    int     mode;
    ARGV   *ext_argv;
    int     debug_me = 0;
    int     err;
    int     n;
    int     flags = SM_FLAG_DEFAULT;
    char   *site_to_flush = 0;
    char   *id_to_flush = 0;
    char   *encoding = 0;
    char   *qtime = 0;
    const char *errstr;
    uid_t   uid;
    const char *rewrite_context = MAIL_ATTR_RWR_LOCAL;
    int     dsn_notify = 0;
    int     dsn_ret = 0;
    const char *dsn_envid = 0;
    int     saved_optind;

    /*
     * Fingerprint executables and core dumps.
     */
    MAIL_VERSION_STAMP_ALLOCATE;

    /*
     * Be consistent with file permissions.
     */
    umask(022);

    /*
     * To minimize confusion, make sure that the standard file descriptors
     * are open before opening anything else. XXX Work around for 44BSD where
     * fstat can return EBADF on an open file descriptor.
     */
    for (fd = 0; fd < 3; fd++)
	if (fstat(fd, &st) == -1
	    && (close(fd), open("/dev/null", O_RDWR, 0)) != fd)
	    msg_fatal_status(EX_OSERR, "open /dev/null: %m");

    /*
     * The CDE desktop calendar manager leaks a parent file descriptor into
     * the child process. For the sake of sendmail compatibility we have to
     * close the file descriptor otherwise mail notification will hang.
     */
    for ( /* void */ ; fd < 100; fd++)
	(void) close(fd);

    /*
     * Process environment options as early as we can. We might be called
     * from a set-uid (set-gid) program, so be careful with importing
     * environment variables.
     */
    if (safe_getenv(CONF_ENV_VERB))
	msg_verbose = 1;
    if (safe_getenv(CONF_ENV_DEBUG))
	debug_me = 1;

    /*
     * Initialize. Set up logging, read the global configuration file and
     * extract configuration information. Set up signal handlers so that we
     * can clean up incomplete output.
     */
    if ((slash = strrchr(argv[0], '/')) != 0 && slash[1])
	argv[0] = slash + 1;
    msg_vstream_init(argv[0], VSTREAM_ERR);
    msg_cleanup(tempfail);
    msg_syslog_init(mail_task("sendmail"), LOG_PID, LOG_FACILITY);
    set_mail_conf_str(VAR_PROCNAME, var_procname = mystrdup(argv[0]));

    /*
     * Check the Postfix library version as soon as we enable logging.
     */
    MAIL_VERSION_CHECK;

    /*
     * Some sites mistakenly install Postfix sendmail as set-uid root. Drop
     * set-uid privileges only when root, otherwise some systems will not
     * reset the saved set-userid, which would be a security vulnerability.
     */
    if (geteuid() == 0 && getuid() != 0) {
	msg_warn("the Postfix sendmail command has set-uid root file permissions");
	msg_warn("or the command is run from a set-uid root process");
	msg_warn("the Postfix sendmail command must be installed without set-uid root file permissions");
	set_ugid(getuid(), getgid());
    }

    /*
     * Further initialization. Load main.cf first, so that command-line
     * options can override main.cf settings. Pre-scan the argument list so
     * that we load the right main.cf file.
     */
#define GETOPT_LIST "A:B:C:F:GIL:N:O:R:UV:X:b:ce:f:h:imno:p:r:q:tvx"

    saved_optind = optind;
    while (argv[OPTIND] != 0) {
	if (strcmp(argv[OPTIND], "-q") == 0) {	/* not getopt compatible */
	    optind++;
	    continue;
	}
	if ((c = GETOPT(argc, argv, GETOPT_LIST)) <= 0)
	    break;
	if (c == 'C') {
	    VSTRING *buf = vstring_alloc(1);

	    if (setenv(CONF_ENV_PATH,
		   strcmp(sane_basename(buf, optarg), MAIN_CONF_FILE) == 0 ?
		       sane_dirname(buf, optarg) : optarg, 1) < 0)
		msg_fatal_status(EX_UNAVAILABLE, "out of memory");
	    vstring_free(buf);
	}
    }
    optind = saved_optind;
    mail_conf_read();
    /* Re-evaluate mail_task() after reading main.cf. */
    msg_syslog_init(mail_task("sendmail"), LOG_PID, LOG_FACILITY);
    get_mail_conf_str_table(str_table);

    if (chdir(var_queue_dir))
	msg_fatal_status(EX_UNAVAILABLE, "chdir %s: %m", var_queue_dir);

    signal(SIGPIPE, SIG_IGN);

    /*
     * Optionally start the debugger on ourself. This must be done after
     * reading the global configuration file, because that file specifies
     * what debugger command to execute.
     */
    if (debug_me)
	debug_process();

    /*
     * The default mode of operation is determined by the process name. It
     * can, however, be changed via command-line options (for example,
     * "newaliases -bp" will show the mail queue).
     */
    if (strcmp(argv[0], "mailq") == 0) {
	mode = SM_MODE_MAILQ;
    } else if (strcmp(argv[0], "newaliases") == 0) {
	mode = SM_MODE_NEWALIAS;
    } else if (strcmp(argv[0], "smtpd") == 0) {
	mode = SM_MODE_DAEMON;
    } else {
	mode = SM_MODE_ENQUEUE;
    }

    /*
     * Parse JCL. Sendmail has been around for a long time, and has acquired
     * a large number of options in the course of time. Some options such as
     * -q are not parsable with GETOPT() and get special treatment.
     */
#define OPTIND  (optind > 0 ? optind : 1)

    while (argv[OPTIND] != 0) {
	if (strcmp(argv[OPTIND], "-q") == 0) {
	    if (mode == SM_MODE_DAEMON)
		msg_warn("ignoring -q option in daemon mode");
	    else
		mode = SM_MODE_FLUSHQ;
	    optind++;
	    continue;
	}
	if (strcmp(argv[OPTIND], "-V") == 0
	    && argv[OPTIND + 1] != 0 && strlen(argv[OPTIND + 1]) == 2) {
	    msg_warn("option -V is deprecated with Postfix 2.3; "
		     "specify -XV instead");
	    argv[OPTIND] = "-XV";
	}
	if (strncmp(argv[OPTIND], "-V", 2) == 0 && strlen(argv[OPTIND]) == 4) {
	    msg_warn("option %s is deprecated with Postfix 2.3; "
		     "specify -X%s instead",
		     argv[OPTIND], argv[OPTIND] + 1);
	    argv[OPTIND] = concatenate("-X", argv[OPTIND] + 1, (char *) 0);
	}
	if (strcmp(argv[OPTIND], "-XV") == 0) {
	    verp_delims = var_verp_delims;
	    optind++;
	    continue;
	}
	if ((c = GETOPT(argc, argv, GETOPT_LIST)) <= 0)
	    break;
	switch (c) {
	default:
	    if (msg_verbose)
		msg_info("-%c option ignored", c);
	    break;
	case 'n':
	    msg_fatal_status(EX_USAGE, "-%c option not supported", c);
	case 'B':
	    if (strcmp(optarg, "8BITMIME") == 0)/* RFC 1652 */
		encoding = MAIL_ATTR_ENC_8BIT;
	    else if (strcmp(optarg, "7BIT") == 0)	/* RFC 1652 */
		encoding = MAIL_ATTR_ENC_7BIT;
	    else
		msg_fatal_status(EX_USAGE, "-B option needs 8BITMIME or 7BIT");
	    break;
	case 'F':				/* full name */
	    full_name = optarg;
	    break;
	case 'G':				/* gateway submission */
	    rewrite_context = MAIL_ATTR_RWR_REMOTE;
	    break;
	case 'I':				/* newaliases */
	    mode = SM_MODE_NEWALIAS;
	    break;
	case 'N':
	    if ((dsn_notify = dsn_notify_mask(optarg)) == 0)
		msg_warn("bad -N option value -- ignored");
	    break;
	case 'R':
	    if ((dsn_ret = dsn_ret_code(optarg)) == 0)
		msg_warn("bad -R option value -- ignored");
	    break;
	case 'V':				/* DSN, was: VERP */
	    if (strlen(optarg) > 100)
		msg_warn("too long -V option value -- ignored");
	    else if (!allprint(optarg))
		msg_warn("bad syntax in -V option value -- ignored");
	    else
		dsn_envid = optarg;
	    break;
	case 'X':
	    switch (*optarg) {
	    default:
		msg_fatal_status(EX_USAGE, "unsupported: -%c%c", c, *optarg);
	    case 'V':				/* VERP */
		if (verp_delims_verify(optarg + 1) != 0)
		    msg_fatal_status(EX_USAGE, "-V requires two characters from %s",
				     var_verp_filter);
		verp_delims = optarg + 1;
		break;
	    }
	    break;
	case 'b':
	    switch (*optarg) {
	    default:
		msg_fatal_status(EX_USAGE, "unsupported: -%c%c", c, *optarg);
	    case 'd':				/* daemon mode */
	    case 'l':				/* daemon mode */
		if (mode == SM_MODE_FLUSHQ)
		    msg_warn("ignoring -q option in daemon mode");
		mode = SM_MODE_DAEMON;
		break;
	    case 'h':				/* print host status */
	    case 'H':				/* flush host status */
		mode = SM_MODE_IGNORE;
		break;
	    case 'i':				/* newaliases */
		mode = SM_MODE_NEWALIAS;
		break;
	    case 'm':				/* deliver mail */
		mode = SM_MODE_ENQUEUE;
		break;
	    case 'p':				/* mailq */
		mode = SM_MODE_MAILQ;
		break;
	    case 's':				/* stand-alone mode */
		mode = SM_MODE_USER;
		break;
	    case 'v':				/* expand recipients */
		flags |= DEL_REQ_FLAG_USR_VRFY;
		break;
	    }
	    break;
	case 'f':
	    sender = optarg;
	    break;
	case 'i':
	    flags &= ~SM_FLAG_AEOF;
	    break;
	case 'o':
	    switch (*optarg) {
	    default:
		if (msg_verbose)
		    msg_info("-%c%c option ignored", c, *optarg);
		break;
	    case 'A':
		if (optarg[1] == 0)
		    msg_fatal_status(EX_USAGE, "-oA requires pathname");
		myfree(var_alias_db_map);
		var_alias_db_map = mystrdup(optarg + 1);
		set_mail_conf_str(VAR_ALIAS_DB_MAP, var_alias_db_map);
		break;
	    case '7':
	    case '8':
		break;
	    case 'i':
		flags &= ~SM_FLAG_AEOF;
		break;
	    case 'm':
		break;
	    }
	    break;
	case 'r':				/* obsoleted by -f */
	    sender = optarg;
	    break;
	case 'q':
	    if (ISDIGIT(optarg[0])) {
		qtime = optarg;
	    } else if (optarg[0] == 'R') {
		site_to_flush = optarg + 1;
		if (*site_to_flush == 0)
		    msg_fatal_status(EX_USAGE, "specify: -qRsitename");
	    } else if (optarg[0] == 'I') {
		id_to_flush = optarg + 1;
		if (*id_to_flush == 0)
		    msg_fatal_status(EX_USAGE, "specify: -qIqueueid");
	    } else {
		msg_fatal_status(EX_USAGE, "-q%c is not implemented",
				 optarg[0]);
	    }
	    break;
	case 't':
	    flags |= SM_FLAG_XRCPT;
	    break;
	case 'v':
	    msg_verbose++;
	    break;
	case '?':
	    msg_fatal_status(EX_USAGE, "usage: %s [options]", argv[0]);
	}
    }

    /*
     * Look for conflicting options and arguments.
     */
    if ((flags & SM_FLAG_XRCPT) && mode != SM_MODE_ENQUEUE)
	msg_fatal_status(EX_USAGE, "-t can be used only in delivery mode");

    if (site_to_flush && mode != SM_MODE_ENQUEUE)
	msg_fatal_status(EX_USAGE, "-qR can be used only in delivery mode");

    if (id_to_flush && mode != SM_MODE_ENQUEUE)
	msg_fatal_status(EX_USAGE, "-qI can be used only in delivery mode");

    if (flags & DEL_REQ_FLAG_USR_VRFY) {
	if (flags & SM_FLAG_XRCPT)
	    msg_fatal_status(EX_USAGE, "-t option cannot be used with -bv");
	if (dsn_notify)
	    msg_fatal_status(EX_USAGE, "-N option cannot be used with -bv");
	if (dsn_ret)
	    msg_fatal_status(EX_USAGE, "-R option cannot be used with -bv");
	if (msg_verbose == 1)
	    msg_fatal_status(EX_USAGE, "-v option cannot be used with -bv");
    }

    /*
     * The -v option plays double duty. One requests verbose delivery, more
     * than one requests verbose logging.
     */
    if (msg_verbose == 1 && mode == SM_MODE_ENQUEUE) {
	msg_verbose = 0;
	flags |= DEL_REQ_FLAG_RECORD;
    }

    /*
     * Start processing. Everything is delegated to external commands.
     */
    if (qtime && mode != SM_MODE_DAEMON)
	exit(0);
    switch (mode) {
    default:
	msg_panic("unknown operation mode: %d", mode);
	/* NOTREACHED */
    case SM_MODE_ENQUEUE:
	if (site_to_flush) {
	    if (argv[OPTIND])
		msg_fatal_status(EX_USAGE, "flush site requires no recipient");
	    ext_argv = argv_alloc(2);
	    argv_add(ext_argv, "postqueue", "-s", site_to_flush, (char *) 0);
	    for (n = 0; n < msg_verbose; n++)
		argv_add(ext_argv, "-v", (char *) 0);
	    argv_terminate(ext_argv);
	    mail_run_replace(var_command_dir, ext_argv->argv);
	    /* NOTREACHED */
	} else if (id_to_flush) {
	    if (argv[OPTIND])
		msg_fatal_status(EX_USAGE, "flush queue_id requires no recipient");
	    ext_argv = argv_alloc(2);
	    argv_add(ext_argv, "postqueue", "-i", id_to_flush, (char *) 0);
	    for (n = 0; n < msg_verbose; n++)
		argv_add(ext_argv, "-v", (char *) 0);
	    argv_terminate(ext_argv);
	    mail_run_replace(var_command_dir, ext_argv->argv);
	    /* NOTREACHED */
	} else {
	    enqueue(flags, encoding, dsn_envid, dsn_ret, dsn_notify,
		    rewrite_context, sender, full_name, argv + OPTIND);
	    exit(0);
	    /* NOTREACHED */
	}
	break;
    case SM_MODE_MAILQ:
	if (argv[OPTIND])
	    msg_fatal_status(EX_USAGE,
			     "display queue mode requires no recipient");
	ext_argv = argv_alloc(2);
	argv_add(ext_argv, "postqueue", "-p", (char *) 0);
	for (n = 0; n < msg_verbose; n++)
	    argv_add(ext_argv, "-v", (char *) 0);
	argv_terminate(ext_argv);
	mail_run_replace(var_command_dir, ext_argv->argv);
	/* NOTREACHED */
    case SM_MODE_FLUSHQ:
	if (argv[OPTIND])
	    msg_fatal_status(EX_USAGE,
			     "flush queue mode requires no recipient");
	ext_argv = argv_alloc(2);
	argv_add(ext_argv, "postqueue", "-f", (char *) 0);
	for (n = 0; n < msg_verbose; n++)
	    argv_add(ext_argv, "-v", (char *) 0);
	argv_terminate(ext_argv);
	mail_run_replace(var_command_dir, ext_argv->argv);
	/* NOTREACHED */
    case SM_MODE_DAEMON:
	if (argv[OPTIND])
	    msg_fatal_status(EX_USAGE, "daemon mode requires no recipient");
	ext_argv = argv_alloc(2);
	argv_add(ext_argv, "postfix", (char *) 0);
	for (n = 0; n < msg_verbose; n++)
	    argv_add(ext_argv, "-v", (char *) 0);
	argv_add(ext_argv, "start", (char *) 0);
	argv_terminate(ext_argv);
	err = (mail_run_background(var_command_dir, ext_argv->argv) < 0);
	argv_free(ext_argv);
	exit(err);
	break;
    case SM_MODE_NEWALIAS:
	if (argv[OPTIND])
	    msg_fatal_status(EX_USAGE,
			 "alias initialization mode requires no recipient");
	if (*var_alias_db_map == 0)
	    return (0);
	ext_argv = argv_alloc(2);
	argv_add(ext_argv, "postalias", (char *) 0);
	for (n = 0; n < msg_verbose; n++)
	    argv_add(ext_argv, "-v", (char *) 0);
	argv_split_append(ext_argv, var_alias_db_map, CHARS_COMMA_SP);
	argv_terminate(ext_argv);
	mail_run_replace(var_command_dir, ext_argv->argv);
	/* NOTREACHED */
    case SM_MODE_USER:
	if (argv[OPTIND])
	    msg_fatal_status(EX_USAGE,
			     "stand-alone mode requires no recipient");
	/* The actual enforcement happens in the postdrop command. */
	if ((errstr = check_user_acl_byuid(VAR_SUBMIT_ACL, var_submit_acl,
					   uid = getuid())) != 0)
	    msg_fatal_status(EX_NOPERM,
			     "User %s(%ld) is not allowed to submit mail",
			     errstr, (long) uid);
	ext_argv = argv_alloc(2);
	argv_add(ext_argv, "smtpd", "-S", (char *) 0);
	for (n = 0; n < msg_verbose; n++)
	    argv_add(ext_argv, "-v", (char *) 0);
	argv_terminate(ext_argv);
	mail_run_replace(var_daemon_dir, ext_argv->argv);
	/* NOTREACHED */
    case SM_MODE_IGNORE:
	exit(0);
	/* NOTREACHED */
    }
}
Esempio n. 4
0
static int tls_policy_lookup_one(SMTP_SESSION *session, int *site_level,
				         const char *site_name,
				         const char *site_class)
{
    const char *lookup;
    char   *policy;
    char   *saved_policy;
    char   *tok;
    const char *err;
    char   *name;
    char   *val;
    static VSTRING *cbuf;

#undef FREE_RETURN
#define FREE_RETURN(x) do { myfree(saved_policy); return (x); } while (0)

    if ((lookup = maps_find(tls_policy, site_name, 0)) == 0) {
	if (tls_policy->error) {
	    msg_fatal("%s: %s lookup error for %s",
		      session->state->request->queue_id,
		      tls_policy->title, site_name);
	    /* XXX session->stream has no longjmp context yet. */
	}
	return (0);
    }
    if (cbuf == 0)
	cbuf = vstring_alloc(10);

#define WHERE \
    vstring_str(vstring_sprintf(cbuf, "TLS policy table, %s \"%s\"", \
		site_class, site_name))

    saved_policy = policy = mystrdup(lookup);

    if ((tok = mystrtok(&policy, "\t\n\r ,")) == 0) {
	msg_warn("%s: invalid empty policy", WHERE);
	*site_level = TLS_LEV_INVALID;
	FREE_RETURN(1);				/* No further lookups */
    }
    *site_level = tls_level_lookup(tok);
    if (*site_level == TLS_LEV_INVALID) {
	/* tls_level_lookup() logs no warning. */
	msg_warn("%s: invalid security level \"%s\"", WHERE, tok);
	FREE_RETURN(1);				/* No further lookups */
    }

    /*
     * Warn about ignored attributes when TLS is disabled.
     */
    if (*site_level < TLS_LEV_MAY) {
	while ((tok = mystrtok(&policy, "\t\n\r ,")) != 0)
	    msg_warn("%s: ignoring attribute \"%s\" with TLS disabled",
		     WHERE, tok);
	FREE_RETURN(1);
    }

    /*
     * Errors in attributes may have security consequences, don't ignore
     * errors that can degrade security.
     */
    while ((tok = mystrtok(&policy, "\t\n\r ,")) != 0) {
	if ((err = split_nameval(tok, &name, &val)) != 0) {
	    *site_level = TLS_LEV_INVALID;
	    msg_warn("%s: malformed attribute/value pair \"%s\": %s",
		     WHERE, tok, err);
	    break;
	}
	/* Only one instance per policy. */
	if (!strcasecmp(name, "ciphers")) {
	    if (*val == 0) {
		msg_warn("%s: attribute \"%s\" has empty value", WHERE, name);
		*site_level = TLS_LEV_INVALID;
		break;
	    }
	    if (session->tls_grade) {
		msg_warn("%s: attribute \"%s\" is specified multiple times",
			 WHERE, name);
		*site_level = TLS_LEV_INVALID;
		break;
	    }
	    session->tls_grade = mystrdup(val);
	    continue;
	}
	/* Only one instance per policy. */
	if (!strcasecmp(name, "protocols")) {
	    if (session->tls_protocols) {
		msg_warn("%s: attribute \"%s\" is specified multiple times",
			 WHERE, name);
		*site_level = TLS_LEV_INVALID;
		break;
	    }
	    session->tls_protocols = mystrdup(val);
	    continue;
	}
	/* Multiple instance(s) per policy. */
	if (!strcasecmp(name, "match")) {
	    char   *delim = *site_level == TLS_LEV_FPRINT ? "|" : ":";

	    if (*site_level <= TLS_LEV_ENCRYPT) {
		msg_warn("%s: attribute \"%s\" invalid at security level \"%s\"",
			 WHERE, name, policy_name(*site_level));
		*site_level = TLS_LEV_INVALID;
		break;
	    }
	    if (*val == 0) {
		msg_warn("%s: attribute \"%s\" has empty value", WHERE, name);
		*site_level = TLS_LEV_INVALID;
		break;
	    }
	    if (session->tls_matchargv == 0)
		session->tls_matchargv = argv_split(val, delim);
	    else
		argv_split_append(session->tls_matchargv, val, delim);
	    continue;
	}
	/* Only one instance per policy. */
	if (!strcasecmp(name, "exclude")) {
	    if (session->tls_exclusions) {
		msg_warn("%s: attribute \"%s\" is specified multiple times",
			 WHERE, name);
		*site_level = TLS_LEV_INVALID;
		break;
	    }
	    session->tls_exclusions = vstring_strcpy(vstring_alloc(10), val);
	    continue;
	} else {
	    msg_warn("%s: invalid attribute name: \"%s\"", WHERE, name);
	    *site_level = TLS_LEV_INVALID;
	    break;
	}
    }
    FREE_RETURN(1);
}
static void tls_policy_lookup_one(SMTP_TLS_POLICY *tls, int *site_level,
				          const char *site_name,
				          const char *site_class)
{
    const char *lookup;
    char   *policy;
    char   *saved_policy;
    char   *tok;
    const char *err;
    char   *name;
    char   *val;
    static VSTRING *cbuf;

#undef FREE_RETURN
#define FREE_RETURN do { myfree(saved_policy); return; } while (0)

#define INVALID_RETURN(why, levelp) do { \
	    MARK_INVALID((why), (levelp)); FREE_RETURN; } while (0)

#define WHERE \
    STR(vstring_sprintf(cbuf, "%s, %s \"%s\"", \
		tls_policy->title, site_class, site_name))

    if (cbuf == 0)
	cbuf = vstring_alloc(10);

    if ((lookup = maps_find(tls_policy, site_name, 0)) == 0) {
	if (tls_policy->error) {
	    msg_warn("%s: policy table lookup error", WHERE);
	    MARK_INVALID(tls->why, site_level);
	}
	return;
    }
    saved_policy = policy = mystrdup(lookup);

    if ((tok = mystrtok(&policy, "\t\n\r ,")) == 0) {
	msg_warn("%s: invalid empty policy", WHERE);
	INVALID_RETURN(tls->why, site_level);
    }
    *site_level = tls_level_lookup(tok);
    if (*site_level == TLS_LEV_INVALID) {
	/* tls_level_lookup() logs no warning. */
	msg_warn("%s: invalid security level \"%s\"", WHERE, tok);
	INVALID_RETURN(tls->why, site_level);
    }

    /*
     * Warn about ignored attributes when TLS is disabled.
     */
    if (*site_level < TLS_LEV_MAY) {
	while ((tok = mystrtok(&policy, "\t\n\r ,")) != 0)
	    msg_warn("%s: ignoring attribute \"%s\" with TLS disabled",
		     WHERE, tok);
	FREE_RETURN;
    }

    /*
     * Errors in attributes may have security consequences, don't ignore
     * errors that can degrade security.
     */
    while ((tok = mystrtok(&policy, "\t\n\r ,")) != 0) {
	if ((err = split_nameval(tok, &name, &val)) != 0) {
	    msg_warn("%s: malformed attribute/value pair \"%s\": %s",
		     WHERE, tok, err);
	    INVALID_RETURN(tls->why, site_level);
	}
	/* Only one instance per policy. */
	if (!strcasecmp(name, "ciphers")) {
	    if (*val == 0) {
		msg_warn("%s: attribute \"%s\" has empty value", WHERE, name);
		INVALID_RETURN(tls->why, site_level);
	    }
	    if (tls->grade) {
		msg_warn("%s: attribute \"%s\" is specified multiple times",
			 WHERE, name);
		INVALID_RETURN(tls->why, site_level);
	    }
	    tls->grade = mystrdup(val);
	    continue;
	}
	/* Only one instance per policy. */
	if (!strcasecmp(name, "protocols")) {
	    if (tls->protocols) {
		msg_warn("%s: attribute \"%s\" is specified multiple times",
			 WHERE, name);
		INVALID_RETURN(tls->why, site_level);
	    }
	    tls->protocols = mystrdup(val);
	    continue;
	}
	/* Multiple instances per policy. */
	if (!strcasecmp(name, "match")) {
	    if (*val == 0) {
		msg_warn("%s: attribute \"%s\" has empty value", WHERE, name);
		INVALID_RETURN(tls->why, site_level);
	    }
	    switch (*site_level) {
	    default:
		msg_warn("%s: attribute \"%s\" invalid at security level "
			 "\"%s\"", WHERE, name, policy_name(*site_level));
		INVALID_RETURN(tls->why, site_level);
		break;
	    case TLS_LEV_FPRINT:
		if (!tls->dane)
		    tls->dane = tls_dane_alloc();
		tls_dane_add_ee_digests(tls->dane,
					var_smtp_tls_fpt_dgst, val, "|");
		break;
	    case TLS_LEV_VERIFY:
	    case TLS_LEV_SECURE:
		if (tls->matchargv == 0)
		    tls->matchargv = argv_split(val, ":");
		else
		    argv_split_append(tls->matchargv, val, ":");
		break;
	    }
	    continue;
	}
	/* Only one instance per policy. */
	if (!strcasecmp(name, "exclude")) {
	    if (tls->exclusions) {
		msg_warn("%s: attribute \"%s\" is specified multiple times",
			 WHERE, name);
		INVALID_RETURN(tls->why, site_level);
	    }
	    tls->exclusions = vstring_strcpy(vstring_alloc(10), val);
	    continue;
	}
	/* Multiple instances per policy. */
	if (!strcasecmp(name, "tafile")) {
	    /* Only makes sense if we're using CA-based trust */
	    if (!TLS_MUST_PKIX(*site_level)) {
		msg_warn("%s: attribute \"%s\" invalid at security level"
			 " \"%s\"", WHERE, name, policy_name(*site_level));
		INVALID_RETURN(tls->why, site_level);
	    }
	    if (*val == 0) {
		msg_warn("%s: attribute \"%s\" has empty value", WHERE, name);
		INVALID_RETURN(tls->why, site_level);
	    }
	    if (!tls->dane)
		tls->dane = tls_dane_alloc();
	    if (!tls_dane_load_trustfile(tls->dane, val)) {
		INVALID_RETURN(tls->why, site_level);
	    }
	    continue;
	}
	msg_warn("%s: invalid attribute name: \"%s\"", WHERE, name);
	INVALID_RETURN(tls->why, site_level);
    }

    FREE_RETURN;
}
Esempio n. 6
0
DICT   *dict_ldap_open(const char *ldapsource, int dummy, int dict_flags)
{
    char   *myname = "dict_ldap_open";
    DICT_LDAP *dict_ldap;
    VSTRING *url_list;
    char   *s;
    char   *h;
    char   *server_host;
    char   *domainlist;
    char   *scope;
    char   *attr;
    int     tmp;

    if (msg_verbose)
	msg_info("%s: Using LDAP source %s", myname, ldapsource);

    dict_ldap = (DICT_LDAP *) dict_alloc(DICT_TYPE_LDAP, ldapsource,
					 sizeof(*dict_ldap));
    dict_ldap->dict.lookup = dict_ldap_lookup;
    dict_ldap->dict.close = dict_ldap_close;
    dict_ldap->dict.flags = dict_flags | DICT_FLAG_FIXED;

    dict_ldap->ld = NULL;
    dict_ldap->parser = cfg_parser_alloc(ldapsource);
    dict_ldap->ldapsource = mystrdup(ldapsource);

    server_host = cfg_get_str(dict_ldap->parser, "server_host",
			      "localhost", 1, 0);

    /*
     * get configured value of "server_port"; default to LDAP_PORT (389)
     */
    dict_ldap->server_port =
	cfg_get_int(dict_ldap->parser, "server_port", LDAP_PORT, 0, 0);

    /*
     * Define LDAP Version.
     */
    dict_ldap->version = cfg_get_int(dict_ldap->parser, "version", 2, 2, 0);
    switch (dict_ldap->version) {
    case 2:
	dict_ldap->version = LDAP_VERSION2;
	break;
    case 3:
	dict_ldap->version = LDAP_VERSION3;
	break;
    default:
	msg_warn("%s: %s Unknown version %d.", myname, ldapsource,
		 dict_ldap->version);
	dict_ldap->version = LDAP_VERSION2;
    }

#if defined(LDAP_API_FEATURE_X_OPENLDAP)
    dict_ldap->ldap_ssl = 0;
#endif

    url_list = vstring_alloc(32);
    s = server_host;
    while ((h = mystrtok(&s, " \t\n\r,")) != NULL) {
#if defined(LDAP_API_FEATURE_X_OPENLDAP)

	/*
	 * Convert (host, port) pairs to LDAP URLs
	 */
	if (ldap_is_ldap_url(h)) {
	    LDAPURLDesc *url_desc;
	    int     rc;

	    if ((rc = ldap_url_parse(h, &url_desc)) != 0) {
		msg_error("%s: error parsing URL %s: %d: %s; skipping", myname,
			  h, rc, ldap_err2string(rc));
		continue;
	    }
	    if (strcasecmp(url_desc->lud_scheme, "ldap") != 0 &&
		dict_ldap->version != LDAP_VERSION3) {
		msg_warn("%s: URL scheme %s requires protocol version 3", myname,
			 url_desc->lud_scheme);
		dict_ldap->version = LDAP_VERSION3;
	    }
	    if (strcasecmp(url_desc->lud_scheme, "ldaps") == 0)
		dict_ldap->ldap_ssl = 1;
	    ldap_free_urldesc(url_desc);
	    vstring_sprintf_append(url_list, " %s", h);
	} else {
	    if (strrchr(h, ':'))
		vstring_sprintf_append(url_list, " ldap://%s", h);
	    else
		vstring_sprintf_append(url_list, " ldap://%s:%d", h,
				       dict_ldap->server_port);
	}
#else
	vstring_sprintf_append(url_list, " %s", h);
#endif
    }
    dict_ldap->server_host =
	mystrdup(VSTRING_LEN(url_list) > 0 ? vstring_str(url_list) + 1 : "");

#if defined(LDAP_API_FEATURE_X_OPENLDAP)

    /*
     * With URL scheme, clear port to normalize connection cache key
     */
    dict_ldap->server_port = LDAP_PORT;
    if (msg_verbose)
	msg_info("%s: %s server_host URL is %s", myname, ldapsource,
		 dict_ldap->server_host);
#endif
    myfree(server_host);
    vstring_free(url_list);

    /*
     * Scope handling thanks to Carsten Hoeger of SuSE.
     */
    scope = cfg_get_str(dict_ldap->parser, "scope", "sub", 1, 0);

    if (strcasecmp(scope, "one") == 0) {
	dict_ldap->scope = LDAP_SCOPE_ONELEVEL;
    } else if (strcasecmp(scope, "base") == 0) {
	dict_ldap->scope = LDAP_SCOPE_BASE;
    } else if (strcasecmp(scope, "sub") == 0) {
	dict_ldap->scope = LDAP_SCOPE_SUBTREE;
    } else {
	msg_warn("%s: %s: Unrecognized value %s specified for scope; using sub",
		 myname, ldapsource, scope);
	dict_ldap->scope = LDAP_SCOPE_SUBTREE;
    }

    myfree(scope);

    dict_ldap->search_base = cfg_get_str(dict_ldap->parser, "search_base",
					 "", 0, 0);

    domainlist = cfg_get_str(dict_ldap->parser, "domain", "", 0, 0);
    if (*domainlist) {
#ifdef MATCH_FLAG_NONE
	dict_ldap->domain = match_list_init(MATCH_FLAG_NONE,
					    domainlist, 1, match_string);
#else
	dict_ldap->domain = match_list_init(domainlist, 1, match_string);
#endif
	if (dict_ldap->domain == NULL)
	    msg_warn("%s: domain match list creation using \"%s\" failed, will continue without it",
		     myname, domainlist);
	if (msg_verbose)
	    msg_info("%s: domain list created using \"%s\"", myname,
		     domainlist);
    } else {
	dict_ldap->domain = NULL;
    }
    myfree(domainlist);

    /*
     * get configured value of "timeout"; default to 10 seconds
     * 
     * Thanks to Manuel Guesdon for spotting that this wasn't really getting
     * set.
     */
    dict_ldap->timeout = cfg_get_int(dict_ldap->parser, "timeout",
				     10, 0, 0);

    dict_ldap->query_filter =
	cfg_get_str(dict_ldap->parser, "query_filter",
		    "(mailacceptinggeneralid=%s)", 0, 0);

    dict_ldap->result_filter =
	cfg_get_str(dict_ldap->parser, "result_filter", "%s", 0, 0);

    if (strcmp(dict_ldap->result_filter, "%s") == 0) {
	myfree(dict_ldap->result_filter);
	dict_ldap->result_filter = NULL;
    }
    attr = cfg_get_str(dict_ldap->parser, "result_attribute",
		       "maildrop", 0, 0);
    dict_ldap->result_attributes = argv_split(attr, " ,\t\r\n");
    dict_ldap->num_attributes = dict_ldap->result_attributes->argc;
    myfree(attr);

    attr = cfg_get_str(dict_ldap->parser, "special_result_attribute",
		       "", 0, 0);
    if (*attr) {
	argv_split_append(dict_ldap->result_attributes, attr, " ,\t\r\n");
    }
    myfree(attr);

    /*
     * get configured value of "bind"; default to true
     */
    dict_ldap->bind = cfg_get_bool(dict_ldap->parser, "bind", 1);

    /*
     * get configured value of "bind_dn"; default to ""
     */
    dict_ldap->bind_dn = cfg_get_str(dict_ldap->parser, "bind_dn", "", 0, 0);

    /*
     * get configured value of "bind_pw"; default to ""
     */
    dict_ldap->bind_pw = cfg_get_str(dict_ldap->parser, "bind_pw", "", 0, 0);

    /*
     * get configured value of "cache"; default to false
     */
    tmp = cfg_get_bool(dict_ldap->parser, "cache", 0);
    if (tmp)
	msg_warn("%s: %s ignoring cache", myname, ldapsource);

    /*
     * get configured value of "cache_expiry"; default to 30 seconds
     */
    tmp = cfg_get_int(dict_ldap->parser, "cache_expiry", -1, 0, 0);
    if (tmp >= 0)
	msg_warn("%s: %s ignoring cache_expiry", myname, ldapsource);

    /*
     * get configured value of "cache_size"; default to 32k
     */
    tmp = cfg_get_int(dict_ldap->parser, "cache_size", -1, 0, 0);
    if (tmp >= 0)
	msg_warn("%s: %s ignoring cache_size", myname, ldapsource);

    /*
     * get configured value of "recursion_limit"; default to 1000
     */
    dict_ldap->recursion_limit = cfg_get_int(dict_ldap->parser,
					     "recursion_limit", 1000, 1, 0);

    /*
     * get configured value of "expansion_limit"; default to 0
     */
    dict_ldap->expansion_limit = cfg_get_int(dict_ldap->parser,
					     "expansion_limit", 0, 0, 0);

    /*
     * get configured value of "size_limit"; default to expansion_limit
     */
    dict_ldap->size_limit = cfg_get_int(dict_ldap->parser, "size_limit",
					dict_ldap->expansion_limit,
					0, 0);

    /*
     * Alias dereferencing suggested by Mike Mattice.
     */
    dict_ldap->dereference = cfg_get_int(dict_ldap->parser, "dereference",
					 0, 0, 0);
    if (dict_ldap->dereference < 0 || dict_ldap->dereference > 3) {
	msg_warn("%s: %s Unrecognized value %d specified for dereference; using 0",
		 myname, ldapsource, dict_ldap->dereference);
	dict_ldap->dereference = 0;
    }
    /* Referral chasing */
    dict_ldap->chase_referrals = cfg_get_bool(dict_ldap->parser,
					      "chase_referrals", 0);

#ifdef LDAP_API_FEATURE_X_OPENLDAP

    /*
     * TLS options
     */
    /* get configured value of "start_tls"; default to no */
    dict_ldap->start_tls = cfg_get_bool(dict_ldap->parser, "start_tls", 0);
    if (dict_ldap->start_tls && dict_ldap->version < LDAP_VERSION3) {
	msg_warn("%s: %s start_tls requires protocol version 3",
		 myname, ldapsource);
	dict_ldap->version = LDAP_VERSION3;
    }
    /* get configured value of "tls_require_cert"; default to no */
    dict_ldap->tls_require_cert = cfg_get_bool(dict_ldap->parser,
					       "tls_require_cert", 0);

    /* get configured value of "tls_ca_cert_file"; default "" */
    dict_ldap->tls_ca_cert_file = cfg_get_str(dict_ldap->parser,
					      "tls_ca_cert_file", "", 0, 0);

    /* get configured value of "tls_ca_cert_dir"; default "" */
    dict_ldap->tls_ca_cert_dir = cfg_get_str(dict_ldap->parser,
					     "tls_ca_cert_dir", "", 0, 0);

    /* get configured value of "tls_cert"; default "" */
    dict_ldap->tls_cert = cfg_get_str(dict_ldap->parser, "tls_cert",
				      "", 0, 0);

    /* get configured value of "tls_key"; default "" */
    dict_ldap->tls_key = cfg_get_str(dict_ldap->parser, "tls_key",
				     "", 0, 0);

    /* get configured value of "tls_random_file"; default "" */
    dict_ldap->tls_random_file = cfg_get_str(dict_ldap->parser,
					     "tls_random_file", "", 0, 0);

    /* get configured value of "tls_cipher_suite"; default "" */
    dict_ldap->tls_cipher_suite = cfg_get_str(dict_ldap->parser,
					      "tls_cipher_suite", "", 0, 0);
#endif

    /*
     * Debug level.
     */
#if defined(LDAP_OPT_DEBUG_LEVEL) && defined(LBER_OPT_LOG_PRINT_FN)
    dict_ldap->debuglevel = cfg_get_int(dict_ldap->parser, "debuglevel",
					0, 0, 0);
#endif

    /*
     * Find or allocate shared LDAP connection container.
     */
    dict_ldap_conn_find(dict_ldap);

    /*
     * Return the new dict_ldap structure.
     */
    return (DICT_DEBUG (&dict_ldap->dict));
}