Esempio n. 1
0
void    smtpd_chat_query(SMTPD_STATE *state)
{
    int     last_char;

    last_char = smtp_get(state->buffer, state->client, var_line_limit);
    smtp_chat_append(state, "In:  ");
    if (last_char != '\n')
	msg_warn("%s[%s]: request longer than %d: %.30s...",
		 state->name, state->addr, var_line_limit,
		 printable(STR(state->buffer), '?'));

    if (msg_verbose)
	msg_info("< %s[%s]: %s", state->name, state->addr, STR(state->buffer));
}
Esempio n. 2
0
static RESPONSE *response(VSTREAM *stream, VSTRING *buf)
{
    static RESPONSE rdata;
    int     more;
    char   *cp;

    /*
     * Initialize the response data buffer. Defend against a denial of
     * service attack by limiting the amount of multi-line text that we are
     * willing to store.
     */
    if (rdata.buf == 0) {
        rdata.buf = vstring_alloc(100);
        vstring_ctl(rdata.buf, VSTRING_CTL_MAXLEN, (ssize_t) var_line_limit, 0);
    }

    /*
     * Censor out non-printable characters in server responses. Concatenate
     * multi-line server responses. Separate the status code from the text.
     * Leave further parsing up to the application.
     */
#define BUF ((char *) vstring_str(buf))
    VSTRING_RESET(rdata.buf);
    for (;;) {
        smtp_get(buf, stream, var_line_limit, SMTP_GET_FLAG_SKIP);
        for (cp = BUF; *cp != 0; cp++)
            if (!ISPRINT(*cp) && !ISSPACE(*cp))
                *cp = '?';
        cp = BUF;
        if (msg_verbose)
            msg_info("<<< %s", cp);
        while (ISDIGIT(*cp))
            cp++;
        rdata.code = (cp - BUF == 3 ? atoi(BUF) : 0);
        if ((more = (*cp == '-')) != 0)
            cp++;
        while (ISSPACE(*cp))
            cp++;
        vstring_strcat(rdata.buf, cp);
        if (more == 0)
            break;
        VSTRING_ADDCH(rdata.buf, '\n');
    }
    VSTRING_TERMINATE(rdata.buf);
    rdata.str = vstring_str(rdata.buf);
    return (&rdata);
}
Esempio n. 3
0
int     smtpd_peer_from_haproxy(SMTPD_STATE *state)
{
    const char *myname = "smtpd_peer_from_haproxy";
    MAI_HOSTADDR_STR smtp_client_addr;
    MAI_SERVPORT_STR smtp_client_port;
    MAI_HOSTADDR_STR smtp_server_addr;
    MAI_SERVPORT_STR smtp_server_port;
    const char *proxy_err;
    int     io_err;
    VSTRING *escape_buf;

    /*
     * Note: the haproxy_srvr_parse() routine performs address protocol
     * checks, address and port syntax checks, and converts IPv4-in-IPv6
     * address string syntax (:ffff::1.2.3.4) to IPv4 syntax where permitted
     * by the main.cf:inet_protocols setting, but logs no warnings.
     */
#define ENABLE_DEADLINE	1

    smtp_stream_setup(state->client, var_smtpd_uproxy_tmout, ENABLE_DEADLINE);
    switch (io_err = vstream_setjmp(state->client)) {
    default:
	msg_panic("%s: unhandled I/O error %d", myname, io_err);
    case SMTP_ERR_EOF:
	msg_warn("haproxy read: unexpected EOF");
	return (-1);
    case SMTP_ERR_TIME:
	msg_warn("haproxy read: timeout error");
	return (-1);
    case 0:
	if (smtp_get(state->buffer, state->client, HAPROXY_MAX_LEN,
		     SMTP_GET_FLAG_NONE) != '\n') {
	    msg_warn("haproxy read: line > %d characters", HAPROXY_MAX_LEN);
	    return (-1);
	}
	if ((proxy_err = haproxy_srvr_parse(STR(state->buffer),
				       &smtp_client_addr, &smtp_client_port,
			      &smtp_server_addr, &smtp_server_port)) != 0) {
	    escape_buf = vstring_alloc(HAPROXY_MAX_LEN + 2);
	    escape(escape_buf, STR(state->buffer), LEN(state->buffer));
	    msg_warn("haproxy read: %s: %s", proxy_err, STR(escape_buf));
	    vstring_free(escape_buf);
	    return (-1);
	}
	state->addr = mystrdup(smtp_client_addr.buf);
	if (strrchr(state->addr, ':') != 0) {
	    state->rfc_addr = concatenate(IPV6_COL, state->addr, (char *) 0);
	    state->addr_family = AF_INET6;
	} else {
	    state->rfc_addr = mystrdup(state->addr);
	    state->addr_family = AF_INET;
	}
	state->port = mystrdup(smtp_client_port.buf);

	/*
	 * Avoid surprises in the Dovecot authentication server.
	 */
	state->dest_addr = mystrdup(smtp_server_addr.buf);
	return (0);
    }
}
Esempio n. 4
0
SMTP_RESP *smtp_chat_resp(SMTP_SESSION *session)
{
    static SMTP_RESP rdata;
    char   *cp;
    int     last_char;
    int     three_digs = 0;
    size_t  len;
    const char *new_reply;
    int     chat_append_flag;
    int     chat_append_skipped = 0;

    /*
     * Initialize the response data buffer.
     */
    if (rdata.str_buf == 0) {
	rdata.dsn_buf = vstring_alloc(10);
	rdata.str_buf = vstring_alloc(100);
    }

    /*
     * Censor out non-printable characters in server responses. Concatenate
     * multi-line server responses. Separate the status code from the text.
     * Leave further parsing up to the application.
     * 
     * 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(rdata.str_buf);
    for (;;) {
	last_char = smtp_get(session->buffer, session->stream, var_line_limit,
			     SMTP_GET_FLAG_SKIP);
	/* XXX Update the per-line time limit. */
	printable(STR(session->buffer), '?');
	if (last_char != '\n')
	    msg_warn("%s: response longer than %d: %.30s...",
		session->namaddrport, var_line_limit, STR(session->buffer));
	if (msg_verbose)
	    msg_info("< %s: %.100s", session->namaddrport, STR(session->buffer));

	/*
	 * Defend against a denial of service attack by limiting the amount
	 * of multi-line text that we are willing to store.
	 */
	chat_append_flag = (LEN(rdata.str_buf) < var_line_limit);
	if (chat_append_flag)
	    smtp_chat_append(session, "In:  ", STR(session->buffer));
	else {
	    if (chat_append_skipped == 0)
		msg_warn("%s: multi-line response longer than %d %.30s...",
		  session->namaddrport, var_line_limit, STR(rdata.str_buf));
	    if (chat_append_skipped < INT_MAX)
		chat_append_skipped++;
	}

	/*
	 * Server reply substitution, for fault-injection testing, or for
	 * working around broken systems. Use with care.
	 */
	if (smtp_chat_resp_filter != 0) {
	    new_reply = dict_get(smtp_chat_resp_filter, STR(session->buffer));
	    if (new_reply != 0) {
		msg_info("%s: replacing server reply \"%s\" with \"%s\"",
		     session->namaddrport, STR(session->buffer), new_reply);
		vstring_strcpy(session->buffer, new_reply);
		if (chat_append_flag) {
		    smtp_chat_append(session, "Replaced-by: ", "");
		    smtp_chat_append(session, "     ", new_reply);
		}
	    } else if (smtp_chat_resp_filter->error != 0) {
		msg_warn("%s: table %s:%s lookup error for %s",
			 session->state->request->queue_id,
			 smtp_chat_resp_filter->type,
			 smtp_chat_resp_filter->name,
			 printable(STR(session->buffer), '?'));
		vstream_longjmp(session->stream, SMTP_ERR_DATA);
	    }
	}
	if (chat_append_flag) {
	    if (LEN(rdata.str_buf))
		VSTRING_ADDCH(rdata.str_buf, '\n');
	    vstring_strcat(rdata.str_buf, STR(session->buffer));
	}

	/*
	 * Parse into code and text. Do not ignore garbage (see below).
	 */
	for (cp = STR(session->buffer); *cp && ISDIGIT(*cp); cp++)
	     /* void */ ;
	if ((three_digs = (cp - STR(session->buffer) == 3)) != 0) {
	    if (*cp == '-')
		continue;
	    if (*cp == ' ' || *cp == 0)
		break;
	}

	/*
	 * XXX Do not simply ignore garbage in the server reply when ESMTP
	 * command pipelining is turned on.  For example, after sending
	 * ".<CR><LF>QUIT<CR><LF>" and receiving garbage followed by a
	 * legitimate 2XX reply, Postfix recognizes the server's QUIT reply
	 * as the END-OF-DATA reply after garbage, causing mail to be lost.
	 * 
	 * Without the ability to store per-domain status information in queue
	 * files, automatic workarounds are problematic:
	 * 
	 * - Automatically deferring delivery creates a "repeated delivery"
	 * problem when garbage arrives after the DATA stage. Without the
	 * workaround, Postfix delivers only once.
	 * 
	 * - Automatically deferring delivery creates a "no delivery" problem
	 * when the garbage arrives before the DATA stage. Without the
	 * workaround, mail might still get through.
	 * 
	 * - Automatically turning off pipelining for delayed mail affects
	 * deliveries to correctly implemented servers, and may also affect
	 * delivery of large mailing lists.
	 * 
	 * So we leave the decision with the administrator, but we don't force
	 * them to take action, like we would with automatic deferral.  If
	 * loss of mail is not acceptable then they can turn off pipelining
	 * for specific sites, or they can turn off pipelining globally when
	 * they find that there are just too many broken sites.
	 */
	session->error_mask |= MAIL_ERROR_PROTOCOL;
	if (session->features & SMTP_FEATURE_PIPELINING) {
	    msg_warn("%s: non-%s response from %s: %.100s",
		     session->state->request->queue_id,
		     smtp_mode ? "ESMTP" : "LMTP",
		     session->namaddrport, STR(session->buffer));
	    if (var_helpful_warnings)
		msg_warn("to prevent loss of mail, turn off command pipelining "
			 "for %s with the %s parameter",
			 STR(session->iterator->addr),
			 VAR_LMTP_SMTP(EHLO_DIS_MAPS));
	}
    }

    /*
     * Extract RFC 821 reply code and RFC 2034 detail. Use a default detail
     * code if none was given.
     * 
     * Ignore out-of-protocol enhanced status codes: codes that accompany 3XX
     * replies, or codes whose initial digit is out of sync with the reply
     * code.
     * 
     * XXX Potential stability problem. In order to save memory, the queue
     * manager stores DSNs in a compact manner:
     * 
     * - empty strings are represented by null pointers,
     * 
     * - the status and reason are required to be non-empty.
     * 
     * Other Postfix daemons inherit this behavior, because they use the same
     * DSN support code. This means that everything that receives DSNs must
     * cope with null pointers for the optional DSN attributes, and that
     * everything that provides DSN information must provide a non-empty
     * status and reason, otherwise the DSN support code wil panic().
     * 
     * Thus, when the remote server sends a malformed reply (or 3XX out of
     * context) we should not panic() in DSN_COPY() just because we don't
     * have a status. Robustness suggests that we supply a status here, and
     * that we leave it up to the down-stream code to override the
     * server-supplied status in case of an error we can't detect here, such
     * as an out-of-order server reply.
     */
    VSTRING_TERMINATE(rdata.str_buf);
    vstring_strcpy(rdata.dsn_buf, "5.5.0");	/* SAFETY! protocol error */
    if (three_digs != 0) {
	rdata.code = atoi(STR(session->buffer));
	if (strchr("245", STR(session->buffer)[0]) != 0) {
	    for (cp = STR(session->buffer) + 4; *cp == ' '; cp++)
		 /* void */ ;
	    if ((len = dsn_valid(cp)) > 0 && *cp == *STR(session->buffer)) {
		vstring_strncpy(rdata.dsn_buf, cp, len);
	    } else {
		vstring_strcpy(rdata.dsn_buf, "0.0.0");
		STR(rdata.dsn_buf)[0] = STR(session->buffer)[0];
	    }
	}
    } else {
	rdata.code = 0;
    }
    rdata.dsn = STR(rdata.dsn_buf);
    rdata.str = STR(rdata.str_buf);
    return (&rdata);
}
Esempio n. 5
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);
    }
}