#include <sys_defs.h>
#include <ctype.h>
#ifdef STRCASECMP_IN_STRINGS_H
#include <strings.h>
#endif
#include <msg.h>
#include <vstream.h>
#include <vstring.h>
#include <stringops.h>
#include <connect.h>
#include <name_code.h>
#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 <mail_params.h>
#include <xtext.h>
#include <smtpd.h>
#include <smtpd_proxy.h>
#define SMTPD_PROXY_XFORWARD_NAME (1<<0)
#define SMTPD_PROXY_XFORWARD_ADDR (1<<1)
#define SMTPD_PROXY_XFORWARD_PROTO (1<<2)
#define SMTPD_PROXY_XFORWARD_HELO (1<<3)
#define SMTPD_PROXY_XFORWARD_IDENT (1<<4)
#define SMTPD_PROXY_XFORWARD_DOMAIN (1<<5)
#define SMTPD_PROXY_XFORWARD_PORT (1<<6)
#define STR(x) vstring_str(x)
#define LEN(x) VSTRING_LEN(x)
#define SMTPD_PROXY_CONNECT null_format_string
#define STREQ(x, y) (strcmp((x), (y)) == 0)
static int smtpd_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);
}
static int smtpd_xforward(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;
if (state->expand_buf == 0)
state->expand_buf = vstring_alloc(100);
xtext_quote(state->expand_buf, 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);
if (VSTRING_LEN(buf) > 0 && VSTRING_LEN(buf) + new_len > PAYLOAD_LIMIT)
if ((ret = smtpd_xforward_flush(state, buf)) < 0)
return (ret);
vstring_sprintf_append(buf, " %s=%s", name, STR(state->expand_buf));
return (0);
}
int smtpd_proxy_open(SMTPD_STATE *state, const char *service,
int timeout, const char *ehlo_name,
const char *mail_from)
{
int fd;
char *lines;
char *words;
VSTRING *buf;
int bad;
char *word;
static const NAME_CODE 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_DOMAIN, SMTPD_PROXY_XFORWARD_DOMAIN,
0, 0,
};
const CLEANUP_STAT_DETAIL *detail;
int (*connect_fn) (const char *, int, int);
const char *endpoint;
if (state->proxy_buffer == 0)
state->proxy_buffer = vstring_alloc(10);
if (strncasecmp("unix:", service, 5) == 0) {
endpoint = service + 5;
connect_fn = unix_connect;
} else {
if (strncasecmp("inet:", service, 5) == 0)
endpoint = service + 5;
else
endpoint = service;
connect_fn = inet_connect;
}
if ((fd = connect_fn(endpoint, BLOCKING, timeout)) < 0) {
state->error_mask |= MAIL_ERROR_SOFTWARE;
state->err |= CLEANUP_STAT_PROXY;
msg_warn("connect to proxy service %s: %m", service);
detail = cleanup_stat_detail(CLEANUP_STAT_PROXY);
vstring_sprintf(state->proxy_buffer,
"%d %s Error: %s",
detail->smtp, detail->dsn, detail->text);
return (-1);
}
state->proxy = vstream_fdopen(fd, O_RDWR);
vstream_control(state->proxy, VSTREAM_CTL_PATH, service, VSTREAM_CTL_END);
smtp_timeout_setup(state->proxy, timeout);
if (smtpd_proxy_cmd(state, SMTPD_PROX_WANT_OK, SMTPD_PROXY_CONNECT) != 0) {
detail = cleanup_stat_detail(CLEANUP_STAT_PROXY);
vstring_sprintf(state->proxy_buffer,
"%d %s Error: %s",
detail->smtp, detail->dsn, detail->text);
smtpd_proxy_close(state);
return (-1);
}
if (smtpd_proxy_cmd(state, SMTPD_PROX_WANT_OK, "EHLO %s", ehlo_name) != 0) {
detail = cleanup_stat_detail(CLEANUP_STAT_PROXY);
vstring_sprintf(state->proxy_buffer,
"%d %s Error: %s",
detail->smtp, detail->dsn, detail->text);
smtpd_proxy_close(state);
return (-1);
}
state->proxy_xforward_features = 0;
lines = STR(state->proxy_buffer);
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)
state->proxy_xforward_features |=
name_code(xforward_features, NAME_CODE_FLAG_NONE, word);
}
}
if (state->proxy_xforward_features) {
buf = vstring_alloc(100);
bad = 0;
if (state->proxy_xforward_features & SMTPD_PROXY_XFORWARD_NAME)
bad = smtpd_xforward(state, buf, XFORWARD_NAME,
IS_AVAIL_CLIENT_NAME(FORWARD_NAME(state)),
FORWARD_NAME(state));
if (bad == 0
&& (state->proxy_xforward_features & SMTPD_PROXY_XFORWARD_ADDR))
bad = smtpd_xforward(state, buf, XFORWARD_ADDR,
IS_AVAIL_CLIENT_ADDR(FORWARD_ADDR(state)),
FORWARD_ADDR(state));
if (bad == 0
&& (state->proxy_xforward_features & SMTPD_PROXY_XFORWARD_PORT))
bad = smtpd_xforward(state, buf, XFORWARD_PORT,
IS_AVAIL_CLIENT_PORT(FORWARD_PORT(state)),
FORWARD_PORT(state));
if (bad == 0
&& (state->proxy_xforward_features & SMTPD_PROXY_XFORWARD_HELO))
bad = smtpd_xforward(state, buf, XFORWARD_HELO,
IS_AVAIL_CLIENT_HELO(FORWARD_HELO(state)),
FORWARD_HELO(state));
if (bad == 0
&& (state->proxy_xforward_features & SMTPD_PROXY_XFORWARD_PROTO))
bad = smtpd_xforward(state, buf, XFORWARD_PROTO,
IS_AVAIL_CLIENT_PROTO(FORWARD_PROTO(state)),
FORWARD_PROTO(state));
if (bad == 0
&& (state->proxy_xforward_features & SMTPD_PROXY_XFORWARD_DOMAIN))
bad = smtpd_xforward(state, buf, XFORWARD_DOMAIN, 1,
STREQ(FORWARD_DOMAIN(state), MAIL_ATTR_RWR_LOCAL) ?
XFORWARD_DOM_LOCAL : XFORWARD_DOM_REMOTE);
if (bad == 0)
bad = smtpd_xforward_flush(state, buf);
vstring_free(buf);
if (bad) {
detail = cleanup_stat_detail(CLEANUP_STAT_PROXY);
vstring_sprintf(state->proxy_buffer,
"%d %s Error: %s",
detail->smtp, detail->dsn, detail->text);
smtpd_proxy_close(state);
return (-1);
}
}
if (smtpd_proxy_cmd(state, SMTPD_PROX_WANT_OK, "%s", mail_from) != 0) {
smtpd_proxy_close(state);
return (-1);
}
return (0);
}
static int smtpd_proxy_rdwr_error(VSTREAM *stream, int err)
{
switch (err) {
case SMTP_ERR_EOF:
msg_warn("lost connection with proxy %s", VSTREAM_PATH(stream));
return (err);
case SMTP_ERR_TIME:
msg_warn("timeout talking to proxy %s", VSTREAM_PATH(stream));
return (err);
default:
msg_panic("smtpd_proxy_rdwr_error: unknown proxy %s stream error %d",
VSTREAM_PATH(stream), err);
}
}
static void smtpd_proxy_cmd_error(SMTPD_STATE *state, const char *fmt,
va_list ap)
{
VSTRING *buf;
buf = vstring_alloc(100);
vstring_vsprintf(buf, fmt == SMTPD_PROXY_CONNECT ?
"connection request" : fmt, ap);
msg_warn("proxy %s rejected \"%s\": \"%s\"", VSTREAM_PATH(state->proxy),
STR(buf), STR(state->proxy_buffer));
vstring_free(buf);
}
int smtpd_proxy_cmd(SMTPD_STATE *state, int expect, const char *fmt,...)
{
va_list ap;
char *cp;
int last_char;
int err = 0;
static VSTRING *buffer = 0;
const CLEANUP_STAT_DETAIL *detail;
if (vstream_ferror(state->proxy)
|| vstream_feof(state->proxy)
|| ((err = vstream_setjmp(state->proxy)) != 0
&& smtpd_proxy_rdwr_error(state->proxy, err))) {
state->error_mask |= MAIL_ERROR_SOFTWARE;
state->err |= CLEANUP_STAT_PROXY;
detail = cleanup_stat_detail(CLEANUP_STAT_PROXY);
vstring_sprintf(state->proxy_buffer,
"%d %s Error: %s",
detail->smtp, detail->dsn, detail->text);
return (-1);
}
if (fmt != SMTPD_PROXY_CONNECT) {
va_start(ap, fmt);
vstring_vsprintf(state->proxy_buffer, fmt, ap);
va_end(ap);
if (msg_verbose)
msg_info("> %s: %s", VSTREAM_PATH(state->proxy),
STR(state->proxy_buffer));
smtp_fputs(STR(state->proxy_buffer), LEN(state->proxy_buffer),
state->proxy);
}
if (expect == SMTPD_PROX_WANT_NONE)
return (0);
VSTRING_RESET(state->proxy_buffer);
if (buffer == 0)
buffer = vstring_alloc(10);
for (;;) {
last_char = smtp_get(buffer, state->proxy, var_line_limit);
printable(STR(buffer), '?');
if (last_char != '\n')
msg_warn("%s: response longer than %d: %.30s...",
VSTREAM_PATH(state->proxy), var_line_limit,
STR(buffer));
if (msg_verbose)
msg_info("< %s: %.100s", VSTREAM_PATH(state->proxy),
STR(buffer));
if (LEN(state->proxy_buffer) < var_line_limit) {
if (VSTRING_LEN(state->proxy_buffer))
VSTRING_ADDCH(state->proxy_buffer, '\n');
vstring_strcat(state->proxy_buffer, STR(buffer));
}
for (cp = STR(buffer); *cp && ISDIGIT(*cp); cp++)
;
if (cp - STR(buffer) == 3) {
if (*cp == '-')
continue;
if (*cp == ' ' || *cp == 0)
break;
}
msg_warn("received garbage from proxy %s: %.100s",
VSTREAM_PATH(state->proxy), STR(buffer));
}
if (expect != SMTPD_PROX_WANT_ANY && expect != *STR(state->proxy_buffer)) {
va_start(ap, fmt);
smtpd_proxy_cmd_error(state, fmt, ap);
va_end(ap);
return (-1);
} else {
return (0);
}
}
int smtpd_proxy_rec_put(VSTREAM *stream, int rec_type,
const char *data, ssize_t len)
{
int err;
if (vstream_ferror(stream) || vstream_feof(stream))
return (REC_TYPE_ERROR);
if ((err = vstream_setjmp(stream)) != 0)
return (smtpd_proxy_rdwr_error(stream, err), REC_TYPE_ERROR);
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("smtpd_proxy_rec_put: need REC_TYPE_NORM or REC_TYPE_CONT");
return (rec_type);
}
int smtpd_proxy_rec_fprintf(VSTREAM *stream, int rec_type,
const char *fmt,...)
{
va_list ap;
int err;
if (vstream_ferror(stream) || vstream_feof(stream))
return (REC_TYPE_ERROR);
if ((err = vstream_setjmp(stream)) != 0)
return (smtpd_proxy_rdwr_error(stream, err), REC_TYPE_ERROR);
va_start(ap, fmt);
if (rec_type == REC_TYPE_NORM)
smtp_vprintf(stream, fmt, ap);
else
msg_panic("smtpd_proxy_rec_fprintf: need REC_TYPE_NORM");
va_end(ap);
return (rec_type);
}
void smtpd_proxy_close(SMTPD_STATE *state)
{
(void) vstream_fclose(state->proxy);
state->proxy = 0;
}