/*++ /* NAME /* smtpd_proxy 3 /* SUMMARY /* SMTP server pass-through proxy client /* SYNOPSIS /* #include /* #include /* /* 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 #include #include #ifdef STRCASECMP_IN_STRINGS_H #include #endif /* Utility library. */ #include #include #include #include #include #include #include /* Global library. */ #include #include #include #include #include #include #include #include #include /* Application-specific. */ #include #include /* * 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, "\r\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); }