Beispiel #1
0
static int smtpd_proxy_rec_put(VSTREAM *stream, int rec_type,
			               const char *data, ssize_t len)
{
    const char *myname = "smtpd_proxy_rec_put";
    int     err = 0;

    /*
     * Errors first.
     */
    if (vstream_ferror(stream) || vstream_feof(stream)
	|| (err = vstream_setjmp(stream)) != 0) {
	(void) smtpd_proxy_rdwr_error(VSTREAM_TO_SMTPD_STATE(stream), err);
	return (REC_TYPE_ERROR);
    }

    /*
     * Send one content record. Errors and results must be as with rec_put().
     */
    if (rec_type == REC_TYPE_NORM)
	smtp_fputs(data, len, stream);
    else if (rec_type == REC_TYPE_CONT)
	smtp_fwrite(data, len, stream);
    else
	msg_panic("%s: need REC_TYPE_NORM or REC_TYPE_CONT", myname);
    return (rec_type);
}
Beispiel #2
0
static int smtpd_proxy_rec_fprintf(VSTREAM *stream, int rec_type,
				           const char *fmt,...)
{
    const char *myname = "smtpd_proxy_rec_fprintf";
    va_list ap;
    int     err = 0;

    /*
     * Errors first.
     */
    if (vstream_ferror(stream) || vstream_feof(stream)
	|| (err = vstream_setjmp(stream)) != 0) {
	(void) smtpd_proxy_rdwr_error(VSTREAM_TO_SMTPD_STATE(stream), err);
	return (REC_TYPE_ERROR);
    }

    /*
     * Send one content record. Errors and results must be as with
     * rec_fprintf().
     */
    va_start(ap, fmt);
    if (rec_type == REC_TYPE_NORM)
	smtp_vprintf(stream, fmt, ap);
    else
	msg_panic("%s: need REC_TYPE_NORM", myname);
    va_end(ap);
    return (rec_type);
}
Beispiel #3
0
int     main(int unused_argc, char **unused_argv)
{
    VSTRING *buf = vstring_alloc(100);
    VSTRING *result = vstring_alloc(100);
    char   *cp;
    char   *name;
    char   *value;
    HTABLE *table;
    int     stat;

    while (!vstream_feof(VSTREAM_IN)) {

	table = htable_create(0);

	/*
	 * Read a block of definitions, terminated with an empty line.
	 */
	while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) {
	    vstream_printf("<< %s\n", vstring_str(buf));
	    vstream_fflush(VSTREAM_OUT);
	    if (VSTRING_LEN(buf) == 0)
		break;
	    cp = vstring_str(buf);
	    name = mystrtok(&cp, " \t\r\n=");
	    value = mystrtok(&cp, " \t\r\n=");
	    htable_enter(table, name, value ? mystrdup(value) : 0);
	}

	/*
	 * Read a block of patterns, terminated with an empty line or EOF.
	 */
	while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) {
	    vstream_printf("<< %s\n", vstring_str(buf));
	    vstream_fflush(VSTREAM_OUT);
	    if (VSTRING_LEN(buf) == 0)
		break;
	    cp = vstring_str(buf);
	    VSTRING_RESET(result);
	    stat = mac_expand(result, vstring_str(buf), MAC_EXP_FLAG_NONE,
			      (char *) 0, lookup, (char *) table);
	    vstream_printf("stat=%d result=%s\n", stat, vstring_str(result));
	    vstream_fflush(VSTREAM_OUT);
	}
	htable_free(table, myfree);
	vstream_printf("\n");
    }

    /*
     * Clean up.
     */
    vstring_free(buf);
    vstring_free(result);
    exit(0);
}
Beispiel #4
0
void    smtpd_proxy_close(SMTPD_STATE *state)
{
    SMTPD_PROXY *proxy = state->proxy;

    /*
     * Specify SMTPD_PROX_WANT_NONE so that the server reply will not clobber
     * the END-OF-DATA reply.
     */
    if (proxy->service_stream != 0) {
	if (vstream_feof(proxy->service_stream) == 0
	    && vstream_ferror(proxy->service_stream) == 0)
	    (void) smtpd_proxy_cmd(state, SMTPD_PROX_WANT_NONE,
				   SMTPD_CMD_QUIT);
	(void) vstream_fclose(proxy->service_stream);
	if (proxy->stream == proxy->service_stream)
	    proxy->stream = 0;
	proxy->service_stream = 0;
    }
}
Beispiel #5
0
static int smtpd_proxy_save_cmd(SMTPD_STATE *state, int expect, const char *fmt,...)
{
    va_list ap;

    /*
     * Errors first.
     */
    if (vstream_ferror(smtpd_proxy_replay_stream)
	|| vstream_feof(smtpd_proxy_replay_stream))
	return (smtpd_proxy_replay_rdwr_error(state));

    /*
     * Save the expected reply first, so that the replayer can safely
     * overwrite the input buffer with the command.
     */
    rec_fprintf(smtpd_proxy_replay_stream, REC_TYPE_RCPT, "%d", expect);

    /*
     * The command can be omitted at the start of an SMTP session. This is
     * not documented as part of the official interface because it is used
     * only internally to this module. Use an explicit null string in case
     * the SMTPD_PROXY_CONN_FMT implementation details change.
     */
    if (fmt == SMTPD_PROXY_CONN_FMT)
	fmt = "";

    /*
     * Save the command to the replay log, and send it to the before-queue
     * filter after we have received the entire message.
     */
    va_start(ap, fmt);
    rec_vfprintf(smtpd_proxy_replay_stream, REC_TYPE_FROM, fmt, ap);
    va_end(ap);

    /*
     * If we just saved the "." command, replay the log.
     */
    return (strcmp(fmt, ".") ? 0 : smtpd_proxy_replay_send(state));
}
Beispiel #6
0
void    smtpd_proxy_close(SMTPD_STATE *state)
{
    SMTPD_PROXY *proxy = state->proxy;

    /*
     * XXX We can't send QUIT if the stream is still good, because that would
     * overwrite the last server reply in proxy->buffer. We probably should
     * just bite the bullet and allocate separate buffers for sending and
     * receiving.
     */
    if (proxy->service_stream != 0) {
#if 0
	if (vstream_feof(proxy->service_stream) == 0
	    && vstream_ferror(proxy->service_stream) == 0)
	    (void) smtpd_proxy_cmd(state, SMTPD_PROX_WANT_NONE,
				   SMTPD_CMD_QUIT);
#endif
	(void) vstream_fclose(proxy->service_stream);
	if (proxy->stream == proxy->service_stream)
	    proxy->stream = 0;
	proxy->service_stream = 0;
    }
}
Beispiel #7
0
int     recv_pass_attr(int fd, HTABLE **attr, int timeout, ssize_t bufsize)
{
    VSTREAM *fp;
    int     stream_err;

    /*
     * Set up a temporary VSTREAM to receive the attributes.
     * 
     * XXX We use one-character reads to simplify the implementation.
     */
    fp = vstream_fdopen(fd, O_RDWR);
    vstream_control(fp,
		    VSTREAM_CTL_BUFSIZE, bufsize,
		    VSTREAM_CTL_TIMEOUT, timeout,
		    VSTREAM_CTL_START_DEADLINE,
		    VSTREAM_CTL_END);
    (void) attr_scan(fp, ATTR_FLAG_NONE,
		     ATTR_TYPE_HASH, *attr = htable_create(1),
		     ATTR_TYPE_END);
    stream_err = (vstream_feof(fp) || vstream_ferror(fp));
    vstream_fdclose(fp);

    /*
     * Error reporting and recovery.
     */
    if (stream_err) {
	htable_free(*attr, myfree);
	*attr = 0;
	return (-1);
    } else {
	if ((*attr)->used == 0) {
	    htable_free(*attr, myfree);
	    *attr = 0;
	}
	return (0);
    }
}
Beispiel #8
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);
}
Beispiel #9
0
static void smtp_connect_local(SMTP_STATE *state, const char *path)
{
    const char *myname = "smtp_connect_local";
    SMTP_ITERATOR *iter = state->iterator;
    SMTP_SESSION *session;
    DSN_BUF *why = state->why;

    /*
     * Do not silently ignore an unused setting.
     */
    if (*var_fallback_relay)
	msg_warn("ignoring \"%s = %s\" setting for non-TCP connections",
		 VAR_LMTP_FALLBACK, var_fallback_relay);

    /*
     * It's too painful to weave this code into the SMTP connection
     * management routine.
     * 
     * Connection cache management is based on the UNIX-domain pathname, without
     * the "unix:" prefix.
     */
    smtp_cache_policy(state, path);

    /*
     * Here we ensure that the iter->addr member refers to a copy of the
     * UNIX-domain pathname, so that smtp_save_session() will cache the
     * connection using the pathname as the physical endpoint name.
     * 
     * We set dest=path for backwards compatibility.
     */
#define NO_PORT	0

    SMTP_ITER_INIT(iter, path, var_myhostname, path, NO_PORT, state);

    /*
     * Opportunistic TLS for unix domain sockets does not make much sense,
     * since the channel is private, mere encryption without authentication
     * is just wasted cycles and opportunity for breakage. Since we are not
     * willing to retry after TLS handshake failures here, we downgrade "may"
     * no "none". Nothing is lost, and much waste is avoided.
     * 
     * We don't know who is authenticating whom, so if a client cert is
     * available, "encrypt" may be a sensible policy. Otherwise, we also
     * downgrade "encrypt" to "none", this time just to avoid waste.
     * 
     * We use smtp_reuse_nexthop() instead of smtp_reuse_addr(), so that we can
     * reuse a SASL-authenticated connection (however unlikely this scenario
     * may be). The smtp_reuse_addr() interface currently supports only reuse
     * of SASL-unauthenticated connections.
     */
#ifdef USE_TLS
    if (!smtp_tls_policy_cache_query(why, state->tls, iter)) {
	msg_warn("TLS policy lookup error for %s/%s: %s",
		 STR(iter->host), STR(iter->addr), STR(why->reason));
	return;
    }
#endif
    if ((state->misc_flags & SMTP_MISC_FLAG_CONN_LOAD) == 0
	|| (session = smtp_reuse_nexthop(state,
				     SMTP_KEY_MASK_SCACHE_DEST_LABEL)) == 0)
	session = smtp_connect_unix(iter, why, state->misc_flags);
    if ((state->session = session) != 0) {
	session->state = state;
#ifdef USE_TLS
	session->tls_nexthop = var_myhostname;	/* for TLS_LEV_SECURE */
	if (state->tls->level == TLS_LEV_MAY) {
	    msg_warn("%s: opportunistic TLS encryption is not appropriate "
		     "for unix-domain destinations.", myname);
	    state->tls->level = TLS_LEV_NONE;
	}
#endif
	/* All delivery errors bounce or defer. */
	state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER;

	/*
	 * When a TLS handshake fails, the stream is marked "dead" to avoid
	 * further I/O over a broken channel.
	 */
	if ((session->features & SMTP_FEATURE_FROM_CACHE) == 0
	    && smtp_helo(state) != 0) {
	    if (!THIS_SESSION_IS_FORBIDDEN
		&& vstream_ferror(session->stream) == 0
		&& vstream_feof(session->stream) == 0)
		smtp_quit(state);
	} else {
	    smtp_xfer(state);
	}

	/*
	 * With opportunistic TLS disabled we don't expect to be asked to
	 * retry connections without TLS, and so we expect the final server
	 * flag to stay on.
	 */
	if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_SERVER) == 0)
	    msg_panic("%s: unix-domain destination not final!", myname);
	smtp_cleanup_session(state);
    }
}
Beispiel #10
0
static void smtp_cleanup_session(SMTP_STATE *state)
{
    DELIVER_REQUEST *request = state->request;
    SMTP_SESSION *session = state->session;
    int     throttled;

    /*
     * Inform the postmaster of trouble.
     * 
     * XXX Don't send notifications about errors while sending notifications.
     */
#define POSSIBLE_NOTIFICATION(sender) \
	(*sender == 0 || strcmp(sender, mail_addr_double_bounce()) == 0)

    if (session->history != 0
	&& (session->error_mask & name_mask(VAR_NOTIFY_CLASSES,
					    mail_error_masks,
					    var_notify_classes)) != 0
	&& POSSIBLE_NOTIFICATION(request->sender) == 0)
	smtp_chat_notify(session);

    /*
     * When session caching is enabled, cache the first good session for this
     * delivery request under the next-hop destination, and cache all good
     * sessions under their server network address (destroying the session in
     * the process).
     * 
     * Caching under the next-hop destination name (rather than the fall-back
     * destination) allows us to skip over non-responding primary or backup
     * hosts. In fact, this is the only benefit of caching logical to
     * physical bindings; caching a session under its own hostname provides
     * no performance benefit, given the way smtp_connect() works.
     */
    throttled = THIS_SESSION_IS_THROTTLED;	/* smtp_quit() may fail */
    if (THIS_SESSION_IS_EXPIRED)
	smtp_quit(state);			/* also disables caching */
    if (THIS_SESSION_IS_CACHED
    /* Redundant tests for safety... */
	&& vstream_ferror(session->stream) == 0
	&& vstream_feof(session->stream) == 0) {
	smtp_save_session(state, SMTP_KEY_MASK_SCACHE_DEST_LABEL,
			  SMTP_KEY_MASK_SCACHE_ENDP_LABEL);
    } else {
	smtp_session_free(session);
    }
    state->session = 0;

    /*
     * If this session was good, reset the logical next-hop state, so that we
     * won't cache connections to alternate servers under the logical
     * next-hop destination. Otherwise we could end up skipping over the
     * available and more preferred servers.
     */
    if (HAVE_NEXTHOP_STATE(state) && !throttled)
	FREE_NEXTHOP_STATE(state);

    /*
     * Clean up the lists with todo and dropped recipients.
     */
    smtp_rcpt_cleanup(state);

    /*
     * Reset profiling info.
     * 
     * XXX When one delivery request results in multiple sessions, the set-up
     * and transmission latencies of the earlier sessions will count as
     * connection set-up time for the later sessions.
     * 
     * XXX On the other hand, when we first try to connect to one or more dead
     * hosts before we reach a good host, then all that time must be counted
     * as connection set-up time for the session with the good host.
     * 
     * XXX So this set-up attribution problem exists only when we actually
     * engage in a session, spend a lot of time delivering a message, find
     * that it fails, and then connect to an alternate host.
     */
    memset((void *) &request->msg_stats.conn_setup_done, 0,
	   sizeof(request->msg_stats.conn_setup_done));
    memset((void *) &request->msg_stats.deliver_done, 0,
	   sizeof(request->msg_stats.deliver_done));
    request->msg_stats.reuse_count = 0;
}
static void smtp_connect_remote(SMTP_STATE *state, const char *nexthop,
				        char *def_service)
{
    DELIVER_REQUEST *request = state->request;
    ARGV   *sites;
    char   *dest;
    char  **cpp;
    int     non_fallback_sites;
    int     retry_plain = 0;
    DSN_BUF *why = state->why;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    /*
     * Cleanup.
     */
    if (HAVE_NEXTHOP_STATE(state))
	FREE_NEXTHOP_STATE(state);
    argv_free(sites);
}
static void smtp_connect_local(SMTP_STATE *state, const char *path)
{
    const char *myname = "smtp_connect_local";
    SMTP_SESSION *session;
    DSN_BUF *why = state->why;

    /*
     * It's too painful to weave this code into the SMTP connection
     * management routine.
     * 
     * Connection cache management is based on the UNIX-domain pathname, without
     * the "unix:" prefix.
     */
    smtp_cache_policy(state, path);

    /*
     * XXX We assume that the session->addr member refers to a copy of the
     * UNIX-domain pathname, so that smtp_save_session() will cache the
     * connection using the pathname as the physical endpoint name.
     */
#define NO_PORT	0

    /*
     * Opportunistic TLS for unix domain sockets does not make much sense,
     * since the channel is private, mere encryption without authentication
     * is just wasted cycles and opportunity for breakage. Since we are not
     * willing to retry after TLS handshake failures here, we downgrade "may"
     * no "none". Nothing is lost, and much waste is avoided.
     * 
     * We don't know who is authenticating whom, so if a client cert is
     * available, "encrypt" may be a sensible policy. Otherwise, we also
     * downgrade "encrypt" to "none", this time just to avoid waste.
     */
    if ((state->misc_flags & SMTP_MISC_FLAG_CONN_LOAD) == 0
	|| (session = smtp_reuse_addr(state, path, NO_PORT)) == 0)
	session = smtp_connect_unix(path, why, state->misc_flags);
    if ((state->session = session) != 0) {
	session->state = state;
#ifdef USE_TLS
	session->tls_nexthop = var_myhostname;	/* for TLS_LEV_SECURE */
	if (session->tls_level == TLS_LEV_MAY) {
	    msg_warn("%s: opportunistic TLS encryption is not appropriate "
		     "for unix-domain destinations.", myname);
	    session->tls_level = TLS_LEV_NONE;
	}
#endif
	/* All delivery errors bounce or defer. */
	state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER;

	/*
	 * When a TLS handshake fails, the stream is marked "dead" to avoid
	 * further I/O over a broken channel.
	 */
	if ((session->features & SMTP_FEATURE_FROM_CACHE) == 0
	    && smtp_helo(state) != 0) {
	    if (!THIS_SESSION_IS_DEAD
		&& vstream_ferror(session->stream) == 0
		&& vstream_feof(session->stream) == 0)
		smtp_quit(state);
	} else {
	    smtp_xfer(state);
	}

	/*
	 * With opportunistic TLS disabled we don't expect to be asked to
	 * retry connections without TLS, and so we expect the final server
	 * flag to stay on.
	 */
	if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_SERVER) == 0)
	    msg_panic("%s: unix-domain destination not final!", myname);
	smtp_cleanup_session(state);
    }
}
Beispiel #13
0
static int smtpd_proxy_cmd(SMTPD_STATE *state, int expect, const char *fmt,...)
{
    SMTPD_PROXY *proxy = state->proxy;
    va_list ap;
    char   *cp;
    int     last_char;
    int     err = 0;
    static VSTRING *buffer = 0;

    /*
     * Errors first. Be prepared for delayed errors from the DATA phase.
     */
    if (vstream_ferror(proxy->service_stream)
	|| vstream_feof(proxy->service_stream)
	|| (err = vstream_setjmp(proxy->service_stream)) != 0) {
	return (smtpd_proxy_rdwr_error(state, err));
    }

    /*
     * The command can be omitted at the start of an SMTP session. This is
     * not documented as part of the official interface because it is used
     * only internally to this module.
     */
    if (fmt != SMTPD_PROXY_CONN_FMT) {

	/*
	 * Format the command.
	 */
	va_start(ap, fmt);
	vstring_vsprintf(proxy->request, fmt, ap);
	va_end(ap);

	/*
	 * Optionally log the command first, so that we can see in the log
	 * what the program is trying to do.
	 */
	if (msg_verbose)
	    msg_info("> %s: %s", proxy->service_name, STR(proxy->request));

	/*
	 * Send the command to the proxy server. Since we're going to read a
	 * reply immediately, there is no need to flush buffers.
	 */
	smtp_fputs(STR(proxy->request), LEN(proxy->request),
		   proxy->service_stream);
    }

    /*
     * Early return if we don't want to wait for a server reply (such as
     * after sending QUIT).
     */
    if (expect == SMTPD_PROX_WANT_NONE)
	return (0);

    /*
     * Censor out non-printable characters in server responses and save
     * complete multi-line responses if possible.
     * 
     * We can't parse or store input that exceeds var_line_limit, so we just
     * skip over it to simplify the remainder of the code below.
     */
    VSTRING_RESET(proxy->reply);
    if (buffer == 0)
	buffer = vstring_alloc(10);
    for (;;) {
	last_char = smtp_get(buffer, proxy->service_stream, var_line_limit,
			     SMTP_GET_FLAG_SKIP);
	printable(STR(buffer), '?');
	if (last_char != '\n')
	    msg_warn("%s: response longer than %d: %.30s...",
		     proxy->service_name, var_line_limit,
		     STR(buffer));
	if (msg_verbose)
	    msg_info("< %s: %.100s", proxy->service_name, STR(buffer));

	/*
	 * Defend against a denial of service attack by limiting the amount
	 * of multi-line text that we are willing to store.
	 */
	if (LEN(proxy->reply) < var_line_limit) {
	    if (VSTRING_LEN(proxy->reply))
		vstring_strcat(proxy->reply, "\r\n");
	    vstring_strcat(proxy->reply, STR(buffer));
	}

	/*
	 * Parse the response into code and text. Ignore unrecognized
	 * garbage. This means that any character except space (or end of
	 * line) will have the same effect as the '-' line continuation
	 * character.
	 */
	for (cp = STR(buffer); *cp && ISDIGIT(*cp); cp++)
	     /* void */ ;
	if (cp - STR(buffer) == 3) {
	    if (*cp == '-')
		continue;
	    if (*cp == ' ' || *cp == 0)
		break;
	}
	msg_warn("received garbage from proxy %s: %.100s",
		 proxy->service_name, STR(buffer));
    }

    /*
     * Log a warning in case the proxy does not send the expected response.
     * Silently accept any response when the client expressed no expectation.
     * 
     * Starting with Postfix 2.6 we don't pass through unexpected 2xx or 3xx
     * proxy replies. They are a source of support problems, so we replace
     * them by generic server error replies.
     */
    if (expect != SMTPD_PROX_WANT_ANY && expect != *STR(proxy->reply)) {
	msg_warn("proxy %s rejected \"%s\": \"%s\"",
		 proxy->service_name, fmt == SMTPD_PROXY_CONN_FMT ?
		 "connection request" : STR(proxy->request),
		 STR(proxy->reply));
	if (*STR(proxy->reply) == SMTPD_PROX_WANT_OK
	    || *STR(proxy->reply) == SMTPD_PROX_WANT_MORE) {
	    smtpd_proxy_rdwr_error(state, 0);
	}
	return (-1);
    } else {
	return (0);
    }
}
Beispiel #14
0
static int smtpd_proxy_replay_send(SMTPD_STATE *state)
{
    const char *myname = "smtpd_proxy_replay_send";
    static VSTRING *replay_buf = 0;
    SMTPD_PROXY *proxy = state->proxy;
    int     rec_type;
    int     expect = SMTPD_PROX_WANT_BAD;

    /*
     * Sanity check.
     */
    if (smtpd_proxy_replay_stream == 0)
	msg_panic("%s: no before-queue filter speed-adjust log", myname);

    /*
     * Errors first.
     */
    if (vstream_ferror(smtpd_proxy_replay_stream)
	|| vstream_feof(smtpd_proxy_replay_stream)
	|| rec_put(smtpd_proxy_replay_stream, REC_TYPE_END, "", 0) != REC_TYPE_END
	|| vstream_fflush(smtpd_proxy_replay_stream))
	/* NOT: fsync(vstream_fileno(smtpd_proxy_replay_stream)) */
	return (smtpd_proxy_replay_rdwr_error(state));

    /*
     * Delayed connection to the before-queue filter.
     */
    if (smtpd_proxy_connect(state) < 0)
	return (-1);

    /*
     * Replay the speed-match log. We do sanity check record content, but we
     * don't implement a protocol state engine here, since we are reading
     * from a file that we just wrote ourselves.
     * 
     * This is different than the MailChannels patented solution that
     * multiplexes a large number of slowed-down inbound connections over a
     * small number of fast connections to a local MTA.
     * 
     * - MailChannels receives mail directly from the Internet. It uses one
     * connection to the local MTA to reject invalid recipients before
     * receiving the entire email message at reduced bit rates, and then uses
     * a different connection to quickly deliver the message to the local
     * MTA.
     * 
     * - Postfix receives mail directly from the Internet. The Postfix SMTP
     * server rejects invalid recipients before receiving the entire message
     * over the Internet, and then delivers the message quickly to a local
     * SMTP-based content filter.
     */
    if (replay_buf == 0)
	replay_buf = vstring_alloc(100);
    if (vstream_fseek(smtpd_proxy_replay_stream, (off_t) 0, SEEK_SET) < 0)
	return (smtpd_proxy_replay_rdwr_error(state));

    for (;;) {
	switch (rec_type = rec_get(smtpd_proxy_replay_stream, replay_buf,
				   REC_FLAG_NONE)) {

	    /*
	     * Message content.
	     */
	case REC_TYPE_NORM:
	case REC_TYPE_CONT:
	    if (smtpd_proxy_rec_put(proxy->service_stream, rec_type,
				    STR(replay_buf), LEN(replay_buf)) < 0)
		return (-1);
	    break;

	    /*
	     * Expected server reply type.
	     */
	case REC_TYPE_RCPT:
	    if (!alldig(STR(replay_buf))
		|| (expect = atoi(STR(replay_buf))) == SMTPD_PROX_WANT_BAD)
		msg_panic("%s: malformed server reply type: %s",
			  myname, STR(replay_buf));
	    break;

	    /*
	     * Client command, or void. Bail out on the first negative proxy
	     * response. This is OK, because the filter must use the same
	     * reply code for all recipients of a multi-recipient message.
	     */
	case REC_TYPE_FROM:
	    if (expect == SMTPD_PROX_WANT_BAD)
		msg_panic("%s: missing server reply type", myname);
	    if (smtpd_proxy_cmd(state, expect, *STR(replay_buf) ? "%s" :
				SMTPD_PROXY_CONN_FMT, STR(replay_buf)) < 0)
		return (-1);
	    expect = SMTPD_PROX_WANT_BAD;
	    break;

	    /*
	     * Explicit end marker, instead of implicit EOF.
	     */
	case REC_TYPE_END:
	    return (0);

	    /*
	     * Errors.
	     */
	case REC_TYPE_ERROR:
	    return (smtpd_proxy_replay_rdwr_error(state));
	default:
	    msg_panic("%s: unexpected record type; %d", myname, rec_type);
	}
    }
}
Beispiel #15
0
static int smtpd_proxy_replay_send(SMTPD_STATE *state)
{
    const char *myname = "smtpd_proxy_replay_send";
    static VSTRING *replay_buf = 0;
    SMTPD_PROXY *proxy = state->proxy;
    int     rec_type;
    int     expect = SMTPD_PROX_WANT_BAD;

    /*
     * Sanity check.
     */
    if (smtpd_proxy_replay_stream == 0)
	msg_panic("%s: no before-queue filter speed-adjust log", myname);

    /*
     * Errors first.
     */
    if (vstream_ferror(smtpd_proxy_replay_stream)
	|| vstream_feof(smtpd_proxy_replay_stream)
	|| rec_put(smtpd_proxy_replay_stream, REC_TYPE_END, "", 0) != REC_TYPE_END
	|| vstream_fflush(smtpd_proxy_replay_stream))
	/* NOT: fsync(vstream_fileno(smtpd_proxy_replay_stream)) */
	return (smtpd_proxy_replay_rdwr_error(state));

    /*
     * Delayed connection to the before-queue filter.
     */
    if (smtpd_proxy_connect(state) < 0)
	return (-1);

    /*
     * Replay the speed-match log. We do sanity check record content, but we
     * don't implement a protocol state engine here, since we are reading
     * from a file that we just wrote ourselves.
     */
    if (replay_buf == 0)
	replay_buf = vstring_alloc(100);
    if (vstream_fseek(smtpd_proxy_replay_stream, (off_t) 0, SEEK_SET) < 0)
	return (smtpd_proxy_replay_rdwr_error(state));

    for (;;) {
	switch (rec_type = rec_get(smtpd_proxy_replay_stream, replay_buf,
				   REC_FLAG_NONE)) {

	    /*
	     * Message content.
	     */
	case REC_TYPE_NORM:
	case REC_TYPE_CONT:
	    if (smtpd_proxy_rec_put(proxy->service_stream, rec_type,
				    STR(replay_buf), LEN(replay_buf)) < 0)
		return (-1);
	    break;

	    /*
	     * Expected server reply type.
	     */
	case REC_TYPE_RCPT:
	    if (!alldig(STR(replay_buf))
		|| (expect = atoi(STR(replay_buf))) == SMTPD_PROX_WANT_BAD)
		msg_panic("%s: malformed server reply type: %s",
			  myname, STR(replay_buf));
	    break;

	    /*
	     * Client command, or void. Bail out on the first negative proxy
	     * response. This is OK, because the filter must use the same
	     * reply code for all recipients of a multi-recipient message.
	     */
	case REC_TYPE_FROM:
	    if (expect == SMTPD_PROX_WANT_BAD)
		msg_panic("%s: missing server reply type", myname);
	    if (smtpd_proxy_cmd(state, expect, *STR(replay_buf) ? "%s" :
				SMTPD_PROXY_CONN_FMT, STR(replay_buf)) < 0)
		return (-1);
	    expect = SMTPD_PROX_WANT_BAD;
	    break;

	    /*
	     * Explicit end marker, instead of implicit EOF.
	     */
	case REC_TYPE_END:
	    return (0);

	    /*
	     * Errors.
	     */
	case REC_TYPE_ERROR:
	    return (smtpd_proxy_replay_rdwr_error(state));
	default:
	    msg_panic("%s: unexpected record type; %d", myname, rec_type);
	}
    }
}
Beispiel #16
0
int     smtp_get(VSTRING *vp, VSTREAM *stream, int bound)
{
    int     last_char;
    int     next_char;

    /*
     * It's painful to do I/O with records that may span multiple buffers.
     * Allow for partial long lines (we will read the remainder later) and
     * allow for lines ending in bare LF. The idea is to be liberal in what
     * we accept, strict in what we send.
     * 
     * XXX 2821: Section 4.1.1.4 says that an SMTP server must not recognize
     * bare LF as record terminator.
     */
    smtp_timeout_reset(stream);
    last_char = (bound == 0 ? vstring_get(vp, stream) :
		 vstring_get_bound(vp, stream, bound));

    switch (last_char) {

	/*
	 * Do some repair in the rare case that we stopped reading in the
	 * middle of the CRLF record terminator.
	 */
    case '\r':
	if ((next_char = VSTREAM_GETC(stream)) == '\n') {
	    VSTRING_ADDCH(vp, '\n');
	    last_char = '\n';
	    /* FALLTRHOUGH */
	} else {
	    if (next_char != VSTREAM_EOF)
		vstream_ungetc(stream, next_char);
	    break;
	}

	/*
	 * Strip off the record terminator: either CRLF or just bare LF.
	 * 
	 * XXX RFC 2821 disallows sending bare CR everywhere. We remove bare CR
	 * if received before CRLF, and leave it alone otherwise.
	 */
    case '\n':
	vstring_truncate(vp, VSTRING_LEN(vp) - 1);
	while (VSTRING_LEN(vp) > 0 && vstring_end(vp)[-1] == '\r')
	    vstring_truncate(vp, VSTRING_LEN(vp) - 1);
	VSTRING_TERMINATE(vp);

	/*
	 * Partial line: just read the remainder later. If we ran into EOF,
	 * the next test will deal with it.
	 */
    default:
	break;
    }
    smtp_timeout_detect(stream);

    /*
     * EOF is bad, whether or not it happens in the middle of a record. Don't
     * allow data that was truncated because of EOF.
     */
    if (vstream_feof(stream) || vstream_ferror(stream)) {
	if (msg_verbose)
	    msg_info("smtp_get: EOF");
	vstream_longjmp(stream, SMTP_ERR_EOF);
    }
    return (last_char);
}