Current File : //root/postfix-3.2.0/src/smtpd/smtpd_proxy.c |
/*++
/* NAME
/* smtpd_proxy 3
/* SUMMARY
/* SMTP server pass-through proxy client
/* SYNOPSIS
/* #include <smtpd.h>
/* #include <smtpd_proxy.h>
/*
/* typedef struct {
/* .in +4
/* VSTREAM *stream; /* SMTP proxy or replay log */
/* VSTRING *buffer; /* last SMTP proxy response */
/* /* other fields... */
/* .in -4
/* } SMTPD_PROXY;
/*
/* int smtpd_proxy_create(state, flags, service, timeout,
/* ehlo_name, mail_from)
/* SMTPD_STATE *state;
/* int flags;
/* const char *service;
/* int timeout;
/* const char *ehlo_name;
/* const char *mail_from;
/*
/* int proxy->cmd(state, expect, format, ...)
/* SMTPD_PROXY *proxy;
/* SMTPD_STATE *state;
/* int expect;
/* const char *format;
/*
/* void smtpd_proxy_free(state)
/* SMTPD_STATE *state;
/*
/* int smtpd_proxy_parse_opts(param_name, param_val)
/* const char *param_name;
/* const char *param_val;
/* RECORD-LEVEL ROUTINES
/* int proxy->rec_put(proxy->stream, rec_type, data, len)
/* SMTPD_PROXY *proxy;
/* int rec_type;
/* const char *data;
/* ssize_t len;
/*
/* int proxy->rec_fprintf(proxy->stream, rec_type, format, ...)
/* SMTPD_PROXY *proxy;
/* int rec_type;
/* cont char *format;
/* DESCRIPTION
/* The functions in this module implement a pass-through proxy
/* client.
/*
/* In order to minimize the intrusiveness of pass-through
/* proxying, 1) the proxy server must support the same MAIL
/* FROM/RCPT syntax that Postfix supports, 2) the record-level
/* routines for message content proxying have the same interface
/* as the routines that are used for non-proxied mail.
/*
/* smtpd_proxy_create() takes a description of a before-queue
/* filter. Depending on flags, it either arranges to buffer
/* up commands and message content until the entire message
/* is received, or it immediately connects to the proxy service,
/* sends EHLO, sends client information with the XFORWARD
/* command if possible, sends the MAIL FROM command, and
/* receives the reply.
/* A non-zero result value means trouble: either the proxy is
/* unavailable, or it did not send the expected reply.
/* All results are reported via the proxy->buffer field in a
/* form that can be sent to the SMTP client. An unexpected
/* 2xx or 3xx proxy server response is replaced by a generic
/* error response to avoid support problems.
/* In case of error, smtpd_proxy_create() updates the
/* state->error_mask and state->err fields, and leaves the
/* SMTPD_PROXY handle in an unconnected state. Destroy the
/* handle after reporting the error reply in the proxy->buffer
/* field.
/*
/* proxy->cmd() formats and either buffers up the command and
/* expected response until the entire message is received, or
/* it immediately sends the specified command to the proxy
/* server, and receives the proxy server reply.
/* A non-zero result value means trouble: either the proxy is
/* unavailable, or it did not send the expected reply.
/* All results are reported via the proxy->buffer field in a
/* form that can be sent to the SMTP client. An unexpected
/* 2xx or 3xx proxy server response is replaced by a generic
/* error response to avoid support problems.
/* In case of error, proxy->cmd() updates the state->error_mask
/* and state->err fields.
/*
/* smtpd_proxy_free() destroys a proxy server handle and resets
/* the state->proxy field.
/*
/* smtpd_proxy_parse_opts() parses main.cf processing options.
/*
/* proxy->rec_put() is a rec_put() clone that either buffers
/* up arbitrary message content records until the entire message
/* is received, or that immediately sends it to the proxy
/* server.
/* All data is expected to be in SMTP dot-escaped form.
/* All errors are reported as a REC_TYPE_ERROR result value,
/* with the state->error_mask, state->err and proxy-buffer
/* fields given appropriate values.
/*
/* proxy->rec_fprintf() is a rec_fprintf() clone that formats
/* message content and either buffers up the record until the
/* entire message is received, or that immediately sends it
/* to the proxy server.
/* All data is expected to be in SMTP dot-escaped form.
/* All errors are reported as a REC_TYPE_ERROR result value,
/* with the state->error_mask, state->err and proxy-buffer
/* fields given appropriate values.
/*
/* Arguments:
/* .IP flags
/* Zero, or SMTPD_PROXY_FLAG_SPEED_ADJUST to buffer up the entire
/* message before contacting a before-queue content filter.
/* Note: when this feature is requested, the before-queue
/* filter MUST use the same 2xx, 4xx or 5xx reply code for all
/* recipients of a multi-recipient message.
/* .IP server
/* The SMTP proxy server host:port. The host or host: part is optional.
/* This argument is not duplicated.
/* .IP timeout
/* Time limit for connecting to the proxy server and for
/* sending and receiving proxy server commands and replies.
/* .IP ehlo_name
/* The EHLO Hostname that will be sent to the proxy server.
/* This argument is not duplicated.
/* .IP mail_from
/* The MAIL FROM command. This argument is not duplicated.
/* .IP state
/* SMTP server state.
/* .IP expect
/* Expected proxy server reply status code range. A warning is logged
/* when an unexpected reply is received. Specify one of the following:
/* .RS
/* .IP SMTPD_PROX_WANT_OK
/* The caller expects a reply in the 200 range.
/* .IP SMTPD_PROX_WANT_MORE
/* The caller expects a reply in the 300 range.
/* .IP SMTPD_PROX_WANT_ANY
/* The caller has no expectation. Do not warn for unexpected replies.
/* .IP SMTPD_PROX_WANT_NONE
/* Do not bother waiting for a reply.
/* .RE
/* .IP format
/* A format string.
/* .IP stream
/* Connection to proxy server.
/* .IP data
/* Pointer to the content of one message content record.
/* .IP len
/* The length of a message content record.
/* SEE ALSO
/* smtpd(8) Postfix smtp server
/* DIAGNOSTICS
/* Panic: internal API violations.
/*
/* Fatal errors: memory allocation problem.
/*
/* Warnings: unexpected response from proxy server, unable
/* to connect to proxy server, proxy server read/write error,
/* proxy speed-adjust buffer read/write error.
/* LICENSE
/* .ad
/* .fi
/* The Secure Mailer license must be distributed with this software.
/* AUTHOR(S)
/* Wietse Venema
/* IBM T.J. Watson Research
/* P.O. Box 704
/* Yorktown Heights, NY 10598, USA
/*--*/
/* System library. */
#include <sys_defs.h>
#include <ctype.h>
#include <unistd.h>
#ifdef STRCASECMP_IN_STRINGS_H
#include <strings.h>
#endif
/* Utility library. */
#include <msg.h>
#include <vstream.h>
#include <vstring.h>
#include <stringops.h>
#include <connect.h>
#include <name_code.h>
#include <mymalloc.h>
/* Global library. */
#include <mail_error.h>
#include <smtp_stream.h>
#include <cleanup_user.h>
#include <mail_params.h>
#include <rec_type.h>
#include <mail_proto.h>
#include <xtext.h>
#include <record.h>
#include <mail_queue.h>
/* Application-specific. */
#include <smtpd.h>
#include <smtpd_proxy.h>
/*
* XFORWARD server features, recognized by the pass-through proxy client.
*/
#define SMTPD_PROXY_XFORWARD_NAME (1<<0) /* client name */
#define SMTPD_PROXY_XFORWARD_ADDR (1<<1) /* client address */
#define SMTPD_PROXY_XFORWARD_PROTO (1<<2) /* protocol */
#define SMTPD_PROXY_XFORWARD_HELO (1<<3) /* client helo */
#define SMTPD_PROXY_XFORWARD_IDENT (1<<4) /* message identifier */
#define SMTPD_PROXY_XFORWARD_DOMAIN (1<<5) /* origin type */
#define SMTPD_PROXY_XFORWARD_PORT (1<<6) /* client port */
/*
* Spead-matching: we use an unlinked file for transient storage.
*/
static VSTREAM *smtpd_proxy_replay_stream;
/*
* Forward declarations.
*/
static void smtpd_proxy_fake_server_reply(SMTPD_STATE *, int);
static int smtpd_proxy_rdwr_error(SMTPD_STATE *, int);
static int PRINTFLIKE(3, 4) smtpd_proxy_cmd(SMTPD_STATE *, int, const char *,...);
static int smtpd_proxy_rec_put(VSTREAM *, int, const char *, ssize_t);
/*
* SLMs.
*/
#define STR(x) vstring_str(x)
#define LEN(x) VSTRING_LEN(x)
#define STREQ(x, y) (strcmp((x), (y)) == 0)
/* smtpd_proxy_xforward_flush - flush forwarding information */
static int smtpd_proxy_xforward_flush(SMTPD_STATE *state, VSTRING *buf)
{
int ret;
if (VSTRING_LEN(buf) > 0) {
ret = smtpd_proxy_cmd(state, SMTPD_PROX_WANT_OK,
XFORWARD_CMD "%s", STR(buf));
VSTRING_RESET(buf);
return (ret);
}
return (0);
}
/* smtpd_proxy_xforward_send - send forwarding information */
static int smtpd_proxy_xforward_send(SMTPD_STATE *state, VSTRING *buf,
const char *name,
int value_available,
const char *value)
{
size_t new_len;
int ret;
#define CONSTR_LEN(s) (sizeof(s) - 1)
#define PAYLOAD_LIMIT (512 - CONSTR_LEN("250 " XFORWARD_CMD "\r\n"))
if (!value_available)
value = XFORWARD_UNAVAILABLE;
/*
* Encode the attribute value.
*/
if (state->expand_buf == 0)
state->expand_buf = vstring_alloc(100);
xtext_quote(state->expand_buf, value, "");
/*
* How much space does this attribute need? SPACE name = value.
*/
new_len = strlen(name) + strlen(STR(state->expand_buf)) + 2;
if (new_len > PAYLOAD_LIMIT)
msg_warn("%s command payload %s=%.10s... exceeds SMTP protocol limit",
XFORWARD_CMD, name, value);
/*
* Flush the buffer if we need to, and store the attribute.
*/
if (VSTRING_LEN(buf) > 0 && VSTRING_LEN(buf) + new_len > PAYLOAD_LIMIT)
if ((ret = smtpd_proxy_xforward_flush(state, buf)) < 0)
return (ret);
vstring_sprintf_append(buf, " %s=%s", name, STR(state->expand_buf));
return (0);
}
/* smtpd_proxy_connect - open proxy connection */
static int smtpd_proxy_connect(SMTPD_STATE *state)
{
SMTPD_PROXY *proxy = state->proxy;
int fd;
char *lines;
char *words;
VSTRING *buf;
int bad;
char *word;
static const NAME_CODE known_xforward_features[] = {
XFORWARD_NAME, SMTPD_PROXY_XFORWARD_NAME,
XFORWARD_ADDR, SMTPD_PROXY_XFORWARD_ADDR,
XFORWARD_PORT, SMTPD_PROXY_XFORWARD_PORT,
XFORWARD_PROTO, SMTPD_PROXY_XFORWARD_PROTO,
XFORWARD_HELO, SMTPD_PROXY_XFORWARD_HELO,
XFORWARD_IDENT, SMTPD_PROXY_XFORWARD_IDENT,
XFORWARD_DOMAIN, SMTPD_PROXY_XFORWARD_DOMAIN,
0, 0,
};
int server_xforward_features;
int (*connect_fn) (const char *, int, int);
const char *endpoint;
/*
* Find connection method (default inet)
*/
if (strncasecmp("unix:", proxy->service_name, 5) == 0) {
endpoint = proxy->service_name + 5;
connect_fn = unix_connect;
} else {
if (strncasecmp("inet:", proxy->service_name, 5) == 0)
endpoint = proxy->service_name + 5;
else
endpoint = proxy->service_name;
connect_fn = inet_connect;
}
/*
* Connect to proxy.
*/
if ((fd = connect_fn(endpoint, BLOCKING, proxy->timeout)) < 0) {
msg_warn("connect to proxy filter %s: %m", proxy->service_name);
return (smtpd_proxy_rdwr_error(state, 0));
}
proxy->service_stream = vstream_fdopen(fd, O_RDWR);
/* Needed by our DATA-phase record emulation routines. */
vstream_control(proxy->service_stream,
CA_VSTREAM_CTL_CONTEXT((void *) state),
CA_VSTREAM_CTL_END);
/* Avoid poor performance when TCP MSS > VSTREAM_BUFSIZE. */
if (connect_fn == inet_connect)
vstream_tweak_tcp(proxy->service_stream);
smtp_timeout_setup(proxy->service_stream, proxy->timeout);
/*
* Get server greeting banner.
*
* If this fails then we have a problem because the proxy should always
* accept our connection. Make up our own response instead of passing
* back a negative greeting banner: the proxy open is delayed to the
* point that the client expects a MAIL FROM or RCPT TO reply.
*/
if (smtpd_proxy_cmd(state, SMTPD_PROX_WANT_OK, "%s", "")) {
smtpd_proxy_fake_server_reply(state, CLEANUP_STAT_PROXY);
smtpd_proxy_close(state);
return (-1);
}
/*
* Send our own EHLO command. If this fails then we have a problem
* because the proxy should always accept our EHLO command. Make up our
* own response instead of passing back a negative EHLO reply: the proxy
* open is delayed to the point that the remote SMTP client expects a
* MAIL FROM or RCPT TO reply.
*/
if (smtpd_proxy_cmd(state, SMTPD_PROX_WANT_OK, "EHLO %s",
proxy->ehlo_name)) {
smtpd_proxy_fake_server_reply(state, CLEANUP_STAT_PROXY);
smtpd_proxy_close(state);
return (-1);
}
/*
* Parse the EHLO reply and see if we can forward logging information.
*/
server_xforward_features = 0;
lines = STR(proxy->reply);
while ((words = mystrtok(&lines, "\n")) != 0) {
if (mystrtok(&words, "- ") && (word = mystrtok(&words, " \t")) != 0) {
if (strcasecmp(word, XFORWARD_CMD) == 0)
while ((word = mystrtok(&words, " \t")) != 0)
server_xforward_features |=
name_code(known_xforward_features,
NAME_CODE_FLAG_NONE, word);
}
}
/*
* Send XFORWARD attributes. For robustness, explicitly specify what SMTP
* session attributes are known and unknown. Make up our own response
* instead of passing back a negative XFORWARD reply: the proxy open is
* delayed to the point that the remote SMTP client expects a MAIL FROM
* or RCPT TO reply.
*/
if (server_xforward_features) {
buf = vstring_alloc(100);
bad =
(((server_xforward_features & SMTPD_PROXY_XFORWARD_NAME)
&& smtpd_proxy_xforward_send(state, buf, XFORWARD_NAME,
IS_AVAIL_CLIENT_NAME(FORWARD_NAME(state)),
FORWARD_NAME(state)))
|| ((server_xforward_features & SMTPD_PROXY_XFORWARD_ADDR)
&& smtpd_proxy_xforward_send(state, buf, XFORWARD_ADDR,
IS_AVAIL_CLIENT_ADDR(FORWARD_ADDR(state)),
FORWARD_ADDR(state)))
|| ((server_xforward_features & SMTPD_PROXY_XFORWARD_PORT)
&& smtpd_proxy_xforward_send(state, buf, XFORWARD_PORT,
IS_AVAIL_CLIENT_PORT(FORWARD_PORT(state)),
FORWARD_PORT(state)))
|| ((server_xforward_features & SMTPD_PROXY_XFORWARD_HELO)
&& smtpd_proxy_xforward_send(state, buf, XFORWARD_HELO,
IS_AVAIL_CLIENT_HELO(FORWARD_HELO(state)),
FORWARD_HELO(state)))
|| ((server_xforward_features & SMTPD_PROXY_XFORWARD_IDENT)
&& smtpd_proxy_xforward_send(state, buf, XFORWARD_IDENT,
IS_AVAIL_CLIENT_IDENT(FORWARD_IDENT(state)),
FORWARD_IDENT(state)))
|| ((server_xforward_features & SMTPD_PROXY_XFORWARD_PROTO)
&& smtpd_proxy_xforward_send(state, buf, XFORWARD_PROTO,
IS_AVAIL_CLIENT_PROTO(FORWARD_PROTO(state)),
FORWARD_PROTO(state)))
|| ((server_xforward_features & SMTPD_PROXY_XFORWARD_DOMAIN)
&& smtpd_proxy_xforward_send(state, buf, XFORWARD_DOMAIN, 1,
STREQ(FORWARD_DOMAIN(state), MAIL_ATTR_RWR_LOCAL) ?
XFORWARD_DOM_LOCAL : XFORWARD_DOM_REMOTE))
|| smtpd_proxy_xforward_flush(state, buf));
vstring_free(buf);
if (bad) {
smtpd_proxy_fake_server_reply(state, CLEANUP_STAT_PROXY);
smtpd_proxy_close(state);
return (-1);
}
}
/*
* Pass-through the remote SMTP client's MAIL FROM command. If this
* fails, then we have a problem because the proxy should always accept
* any MAIL FROM command that was accepted by us.
*/
if (smtpd_proxy_cmd(state, SMTPD_PROX_WANT_OK, "%s",
proxy->mail_from) != 0) {
/* NOT: smtpd_proxy_fake_server_reply(state, CLEANUP_STAT_PROXY); */
smtpd_proxy_close(state);
return (-1);
}
return (0);
}
/* smtpd_proxy_fake_server_reply - produce generic error response */
static void smtpd_proxy_fake_server_reply(SMTPD_STATE *state, int status)
{
const CLEANUP_STAT_DETAIL *detail;
/*
* Either we have no server reply (connection refused), or we have an
* out-of-protocol server reply, so we make up a generic server error
* response instead.
*/
detail = cleanup_stat_detail(status);
vstring_sprintf(state->proxy->reply,
"%d %s Error: %s",
detail->smtp, detail->dsn, detail->text);
}
/* smtpd_proxy_replay_rdwr_error - report replay log I/O error */
static int smtpd_proxy_replay_rdwr_error(SMTPD_STATE *state)
{
/*
* Log an appropriate warning message.
*/
msg_warn("proxy speed-adjust log I/O error: %m");
/*
* Set the appropriate flags and server reply.
*/
state->error_mask |= MAIL_ERROR_RESOURCE;
/* Update state->err in case we are past the client's DATA command. */
state->err |= CLEANUP_STAT_PROXY;
smtpd_proxy_fake_server_reply(state, CLEANUP_STAT_PROXY);
return (-1);
}
/* smtpd_proxy_rdwr_error - report proxy communication error */
static int smtpd_proxy_rdwr_error(SMTPD_STATE *state, int err)
{
const char *myname = "smtpd_proxy_rdwr_error";
SMTPD_PROXY *proxy = state->proxy;
/*
* Sanity check.
*/
if (err != 0 && err != SMTP_ERR_NONE && proxy == 0)
msg_panic("%s: proxy error %d without proxy handle", myname, err);
/*
* Log an appropriate warning message.
*/
switch (err) {
case 0:
case SMTP_ERR_NONE:
break;
case SMTP_ERR_EOF:
msg_warn("lost connection with proxy %s", proxy->service_name);
break;
case SMTP_ERR_TIME:
msg_warn("timeout talking to proxy %s", proxy->service_name);
break;
default:
msg_panic("%s: unknown proxy %s error %d",
myname, proxy->service_name, err);
}
/*
* Set the appropriate flags and server reply.
*/
state->error_mask |= MAIL_ERROR_SOFTWARE;
/* Update state->err in case we are past the client's DATA command. */
state->err |= CLEANUP_STAT_PROXY;
smtpd_proxy_fake_server_reply(state, CLEANUP_STAT_PROXY);
return (-1);
}
/* smtpd_proxy_replay_send - replay saved SMTP session from speed-match log */
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, "%s", 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);
}
}
}
/* smtpd_proxy_save_cmd - save SMTP command + expected response to replay log */
static int PRINTFLIKE(3, 4) 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.
*/
/*
* 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));
}
/* smtpd_proxy_cmd - send command to proxy, receive reply */
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));
}
/*
* Format the command.
*/
va_start(ap, fmt);
vstring_vsprintf(proxy->request, fmt, ap);
va_end(ap);
/*
* 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 (LEN(proxy->request) > 0) {
/*
* 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, LEN(proxy->request) == 0 ?
"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);
}
}
/* smtpd_proxy_save_rec_put - save message content to replay log */
static int smtpd_proxy_save_rec_put(VSTREAM *stream, int rec_type,
const char *data, ssize_t len)
{
const char *myname = "smtpd_proxy_save_rec_put";
int ret;
#define VSTREAM_TO_SMTPD_STATE(s) ((SMTPD_STATE *) vstream_context(s))
/*
* Sanity check.
*/
if (stream == 0)
msg_panic("%s: attempt to use closed stream", myname);
/*
* Send one content record. Errors and results must be as with rec_put().
*/
if (rec_type == REC_TYPE_NORM || rec_type == REC_TYPE_CONT)
ret = rec_put(stream, rec_type, data, len);
else
msg_panic("%s: need REC_TYPE_NORM or REC_TYPE_CONT", myname);
/*
* Errors last.
*/
if (ret != rec_type) {
(void) smtpd_proxy_replay_rdwr_error(VSTREAM_TO_SMTPD_STATE(stream));
return (REC_TYPE_ERROR);
}
return (rec_type);
}
/* smtpd_proxy_rec_put - send message content, rec_put() clone */
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);
}
/* smtpd_proxy_save_rec_fprintf - save message content to replay log */
static int smtpd_proxy_save_rec_fprintf(VSTREAM *stream, int rec_type,
const char *fmt,...)
{
const char *myname = "smtpd_proxy_save_rec_fprintf";
va_list ap;
int ret;
/*
* Sanity check.
*/
if (stream == 0)
msg_panic("%s: attempt to use closed stream", myname);
/*
* Save one content record. Errors and results must be as with
* rec_fprintf().
*/
va_start(ap, fmt);
if (rec_type == REC_TYPE_NORM)
ret = rec_vfprintf(stream, rec_type, fmt, ap);
else
msg_panic("%s: need REC_TYPE_NORM", myname);
va_end(ap);
/*
* Errors last.
*/
if (ret != rec_type) {
(void) smtpd_proxy_replay_rdwr_error(VSTREAM_TO_SMTPD_STATE(stream));
return (REC_TYPE_ERROR);
}
return (rec_type);
}
/* smtpd_proxy_rec_fprintf - send message content, rec_fprintf() clone */
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);
}
#ifndef NO_TRUNCATE
/* smtpd_proxy_replay_setup - prepare the replay logfile */
static int smtpd_proxy_replay_setup(SMTPD_STATE *state)
{
const char *myname = "smtpd_proxy_replay_setup";
off_t file_offs;
/*
* Where possible reuse an existing replay logfile, because creating a
* file is expensive compared to reading or writing. For security reasons
* we must truncate the file before reuse. For performance reasons we
* should truncate the file immediately after the end of a mail
* transaction. We enforce the security guarantee upon reuse, by
* requiring that no I/O happened since the file was truncated. This is
* less expensive than truncating the file redundantly.
*/
if (smtpd_proxy_replay_stream != 0) {
/* vstream_ftell() won't invoke the kernel, so all errors are mine. */
if ((file_offs = vstream_ftell(smtpd_proxy_replay_stream)) != 0)
msg_panic("%s: bad before-queue filter speed-adjust log offset %lu",
myname, (unsigned long) file_offs);
vstream_clearerr(smtpd_proxy_replay_stream);
if (msg_verbose)
msg_info("%s: reuse speed-adjust stream fd=%d", myname,
vstream_fileno(smtpd_proxy_replay_stream));
/* Here, smtpd_proxy_replay_stream != 0 */
}
/*
* Create a new replay logfile.
*/
if (smtpd_proxy_replay_stream == 0) {
smtpd_proxy_replay_stream = mail_queue_enter(MAIL_QUEUE_INCOMING, 0,
(struct timeval *) 0);
if (smtpd_proxy_replay_stream == 0)
return (smtpd_proxy_replay_rdwr_error(state));
if (unlink(VSTREAM_PATH(smtpd_proxy_replay_stream)) < 0)
msg_warn("remove before-queue filter speed-adjust log %s: %m",
VSTREAM_PATH(smtpd_proxy_replay_stream));
if (msg_verbose)
msg_info("%s: new speed-adjust stream fd=%d", myname,
vstream_fileno(smtpd_proxy_replay_stream));
}
/*
* Needed by our DATA-phase record emulation routines.
*/
vstream_control(smtpd_proxy_replay_stream,
CA_VSTREAM_CTL_CONTEXT((void *) state),
CA_VSTREAM_CTL_END);
return (0);
}
#endif
/* smtpd_proxy_create - set up smtpd proxy handle */
int smtpd_proxy_create(SMTPD_STATE *state, int flags, const char *service,
int timeout, const char *ehlo_name,
const char *mail_from)
{
SMTPD_PROXY *proxy;
/*
* When an operation has many arguments it is safer to use named
* parameters, and have the compiler enforce the argument count.
*/
#define SMTPD_PROXY_ALLOC(p, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12) \
((p) = (SMTPD_PROXY *) mymalloc(sizeof(*(p))), (p)->a1, (p)->a2, \
(p)->a3, (p)->a4, (p)->a5, (p)->a6, (p)->a7, (p)->a8, (p)->a9, \
(p)->a10, (p)->a11, (p)->a12, (p))
/*
* Sanity check.
*/
if (state->proxy != 0)
msg_panic("smtpd_proxy_create: handle still exists");
/*
* Connect to the before-queue filter immediately.
*/
if ((flags & SMTPD_PROXY_FLAG_SPEED_ADJUST) == 0) {
state->proxy =
SMTPD_PROXY_ALLOC(proxy, stream = 0, request = vstring_alloc(10),
reply = vstring_alloc(10),
cmd = smtpd_proxy_cmd,
rec_fprintf = smtpd_proxy_rec_fprintf,
rec_put = smtpd_proxy_rec_put,
flags = flags, service_stream = 0,
service_name = service, timeout = timeout,
ehlo_name = ehlo_name, mail_from = mail_from);
if (smtpd_proxy_connect(state) < 0) {
/* NOT: smtpd_proxy_free(state); we still need proxy->reply. */
return (-1);
}
proxy->stream = proxy->service_stream;
return (0);
}
/*
* Connect to the before-queue filter after we receive the entire
* message. Open the replay logfile early to simplify code. The file is
* reused for multiple mail transactions, so there is no need to minimize
* its life time.
*/
else {
#ifdef NO_TRUNCATE
msg_panic("smtpd_proxy_create: speed-adjust support is not available");
#else
if (smtpd_proxy_replay_setup(state) < 0)
return (-1);
state->proxy =
SMTPD_PROXY_ALLOC(proxy, stream = smtpd_proxy_replay_stream,
request = vstring_alloc(10),
reply = vstring_alloc(10),
cmd = smtpd_proxy_save_cmd,
rec_fprintf = smtpd_proxy_save_rec_fprintf,
rec_put = smtpd_proxy_save_rec_put,
flags = flags, service_stream = 0,
service_name = service, timeout = timeout,
ehlo_name = ehlo_name, mail_from = mail_from);
return (0);
#endif
}
}
/* smtpd_proxy_close - close proxy connection without destroying handle */
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;
}
}
/* smtpd_proxy_free - destroy smtpd proxy handle */
void smtpd_proxy_free(SMTPD_STATE *state)
{
SMTPD_PROXY *proxy = state->proxy;
/*
* Clean up.
*/
if (proxy->service_stream != 0)
(void) smtpd_proxy_close(state);
if (proxy->request != 0)
vstring_free(proxy->request);
if (proxy->reply != 0)
vstring_free(proxy->reply);
myfree((void *) proxy);
state->proxy = 0;
/*
* Reuse the replay logfile if possible. For security reasons we must
* truncate the replay logfile before reuse. For performance reasons we
* should truncate the replay logfile immediately after the end of a mail
* transaction. We truncate the file here, and enforce the security
* guarantee by requiring that no I/O happens before the file is reused.
*/
if (smtpd_proxy_replay_stream == 0)
return;
if (vstream_ferror(smtpd_proxy_replay_stream)) {
/* Errors are already reported. */
(void) vstream_fclose(smtpd_proxy_replay_stream);
smtpd_proxy_replay_stream = 0;
return;
}
/* Flush output from aborted transaction before truncating the file!! */
if (vstream_fseek(smtpd_proxy_replay_stream, (off_t) 0, SEEK_SET) < 0) {
msg_warn("seek before-queue filter speed-adjust log: %m");
(void) vstream_fclose(smtpd_proxy_replay_stream);
smtpd_proxy_replay_stream = 0;
return;
}
if (ftruncate(vstream_fileno(smtpd_proxy_replay_stream), (off_t) 0) < 0) {
msg_warn("truncate before-queue filter speed-adjust log: %m");
(void) vstream_fclose(smtpd_proxy_replay_stream);
smtpd_proxy_replay_stream = 0;
return;
}
}
/* smtpd_proxy_parse_opts - parse main.cf options */
int smtpd_proxy_parse_opts(const char *param_name, const char *param_val)
{
static const NAME_MASK proxy_opts_table[] = {
SMTPD_PROXY_NAME_SPEED_ADJUST, SMTPD_PROXY_FLAG_SPEED_ADJUST,
0, 0,
};
int flags;
/*
* The optional before-filter speed-adjust buffers use disk space.
* However, we don't know if they compete for storage space with the
* after-filter queue, so we can't simply bump up the free space
* requirement to 2.5 * message_size_limit.
*/
flags = name_mask(param_name, proxy_opts_table, param_val);
if (flags & SMTPD_PROXY_FLAG_SPEED_ADJUST) {
#ifdef NO_TRUNCATE
msg_warn("smtpd_proxy %s support is not available",
SMTPD_PROXY_NAME_SPEED_ADJUST);
flags &= ~SMTPD_PROXY_FLAG_SPEED_ADJUST;
#endif
}
return (flags);
}