/* This file is part of mailfromd.
Copyright (C) 2005, 2006, 2007 Sergey Poznyakoff
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see . */
#define MF_SOURCE_NAME MF_SOURCE_ENGINE
#ifdef HAVE_CONFIG_H
# include
#endif
#include
#include
#include
#include
#include
#include
#define obstack_chunk_alloc malloc
#define obstack_chunk_free free
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "mailfromd.h"
static char *ctx_getsym(void *data, char *str);
static int ctx_setreply(void *data, char *code, char *xcode, char *message);
static void ctx_setheader(void *data, struct old_header_node *hdr);
/* Per-message data */
struct smtp_io_data; /* Declared later on */
struct message_data {
eval_environ_t env; /* Evaluation environment */
struct smtp_io_data *io; /* I/O data for SMTP transactions */
mu_list_t hdr; /* List of header modifications */
char *helostr; /* Domain name obtained in HELO phase */
char msgid[64]; /* Message ID */
};
static struct message_data *
priv_get(SMFICTX *ctx)
{
static struct message_data *test_data;
struct message_data *md;
if (mode == MAILFROMD_TEST)
md = test_data;
else
md = (struct message_data*) gacopyz_getpriv(ctx);
if (!md) {
md = malloc(sizeof(*md));
if (!md)
mu_error(_("Not enough memory"));
else {
md->env = create_environment(ctx,
ctx_getsym,
ctx_setreply,
ctx_setheader,
ctx);
clear_rcpt_count(md->env);
md->hdr = NULL;
md->io = NULL;
md->helostr = NULL;
md->msgid[0] = 0;
if (mode == MAILFROMD_TEST)
test_data = md;
else
gacopyz_setpriv(ctx, md);
env_init(md->env);
xeval(md->env, smtp_state_begin);
}
}
if (!md->msgid[0]) {
char *p = gacopyz_getsymval(ctx, "i");
if (p) {
size_t len = strlen(p);
if (len > sizeof md->msgid - 3)
len = sizeof md->msgid - 3;
memcpy(md->msgid, p, len);
md->msgid[len++] = ':';
md->msgid[len++] = ' ';
md->msgid[len] = 0;
}
}
return md;
}
static int
priv_set_io(SMFICTX *ctx, struct smtp_io_data *io)
{
struct message_data *md = priv_get(ctx);
if (!md)
return 1;
md->io = io;
return 0;
}
const char *
mailfromd_msgid(SMFICTX *ctx)
{
struct message_data *md = priv_get(ctx);
return md->msgid;
}
char *
mailfromd_timestr(time_t timestamp, char *timebuf, size_t bufsize)
{
struct tm tm;
gmtime_r(×tamp, &tm);
strftime(timebuf, bufsize, "%c", &tm);
return timebuf;
}
void
destroy_old_header(void *item)
{
struct old_header_node *node = item;
free(node->value);
}
int
priv_store_header_command(SMFICTX *ctx, struct old_header_node *node)
{
struct message_data *md = priv_get(ctx);
if (!md)
return 1;
if (!md->hdr) {
mu_list_create(&md->hdr);
mu_list_set_destroy_item(md->hdr, destroy_old_header);
}
mu_list_append(md->hdr, node);
return 0;
}
/* Run-time execution */
static char *
ctx_getsym(void *data, char *str)
{
char *ret = gacopyz_getsymval(data, str);
if (!ret) {
struct message_data *md = priv_get(data);
if (strcmp (str, "s") == 0)
ret = md->helostr;
}
return ret;
}
static int
ctx_setreply(void *data, char *code, char *xcode, char *message)
{
if (code)
return gacopyz_setreply(data, code, xcode, message);
return 0;
}
static void
ctx_setheader(void *data, struct old_header_node *hdr)
{
priv_store_header_command(data, hdr);
}
/* Message capturing functions */
static int capture_enabled;
void
capture_on()
{
milter_enable_state(smtp_state_helo);
milter_enable_state(smtp_state_envfrom);
milter_enable_state(smtp_state_header);
milter_enable_state(smtp_state_eoh);
milter_enable_state(smtp_state_body);
milter_enable_state(smtp_state_eom);
capture_enabled = 1;
}
static void
capture_from(eval_environ_t env, const char *str)
{
if (capture_enabled) {
time_t t;
struct tm *tm;
char datebuf[26];
env_capture_start(env);
t = time(NULL);
tm = localtime(&t);
mu_strftime(datebuf, sizeof datebuf,
"%a %b %d %H:%M:%S %Y", tm);
env_capture_write_args(env,
"From ", str, " ", datebuf, "\n", NULL);
}
}
static void
capture_header(eval_environ_t env, const char *hf, const char *hv)
{
env_capture_write_args(env, hf, ": ", hv, "\n", NULL);
}
static void
capture_eoh(eval_environ_t env)
{
env_capture_write_args(env, "\n", NULL);
}
static void
capture_body(eval_environ_t env, unsigned char *bodyp, size_t len)
{
env_capture_write(env, bodyp, len);
}
static void
capture_eom(eval_environ_t env)
{
}
/* SMTP I/O functions */
#define SMTP_MAJOR(c) ((c)/100)
struct smtp_io_data {
SMFICTX *ctx; /* Milter context */
mu_stream_t stream; /* I/O stream */
size_t send_off; /* Send offset */
size_t recv_off; /* Receive offset */
struct obstack stk; /* Obstack for keeping commands/replies */
char *command; /* Last issued command */
char *reply; /* Last received reply */
char *start; /* First line of the reply, if it was multiline */
char buf[128]; /* Input buffer */
size_t level; /* Number of bytes in buf */
int code; /* Reply code */
};
void
transcript(struct smtp_io_data *io, char *prefix, const char *msg)
{
if (do_transcript) {
int len = strlen(msg);
if (msg[len-1] == '\n') {
--len;
if (len > 0 && msg[len-1] == '\r')
--len;
}
if (len)
logmsg(LOG_INFO, "%s%s %*.*s",
mailfromd_msgid(io->ctx),
prefix, len, len, msg);
}
}
int
smtp_io_data_init(struct smtp_io_data *dat, SMFICTX *ctx, mu_stream_t stream)
{
obstack_init(&dat->stk);
dat->send_off = dat->recv_off = 0;
dat->stream = stream;
dat->start = dat->command = dat->reply = NULL;
dat->level = 0;
dat->ctx = ctx;
if (ctx && priv_set_io(ctx, dat)) /* save the private data */
return 1;
return 0;
}
void
smtp_io_data_destroy(struct smtp_io_data *dat)
{
if (dat) {
mu_stream_close (dat->stream);
mu_stream_destroy(&dat->stream,
mu_stream_get_owner(dat->stream));
obstack_free(&dat->stk, NULL);
if (dat->ctx)
priv_set_io(dat->ctx, NULL);
}
}
struct timeout_ctl {
time_t start;
time_t timeout;
};
static void
init_timeout_ctl(struct timeout_ctl *tctl, time_t timeout)
{
tctl->start = time(NULL);
tctl->timeout = timeout;
}
#define UPDATE_TTW(t) do { \
time_t now = time(NULL); \
time_t delta = now - (t).start; \
if ((t).timeout > delta) \
(t).timeout -= delta; \
else \
(t).timeout = 0; \
(t).start = now; \
} while (0)
int
smtp_stream_wait(mu_stream_t stream, int flags, struct timeout_ctl *tctl)
{
int rc;
int oflags = flags;
struct timeval tv;
while (tctl->timeout) {
tv.tv_sec = tctl->timeout;
tv.tv_usec = 0;
rc = mu_stream_wait(stream, &oflags, &tv);
/* Correct the timeout */
UPDATE_TTW(*tctl);
switch (rc) {
case 0:
if (flags & oflags)
return 0;
/* FALLTHROUGH */
case EAGAIN:
case EINPROGRESS:
continue;
default:
return rc;
}
}
return ETIMEDOUT;
}
int
smtp_wait(struct smtp_io_data *dat, int flags, struct timeout_ctl *tctl)
{
return smtp_stream_wait(dat->stream, flags, tctl);
}
int
smtp_send(struct smtp_io_data *dat, char *command)
{
size_t len = strlen(command);
struct timeout_ctl tctl;
init_timeout_ctl (&tctl, io_timeout);
dat->reply = NULL; /* Clear reply for logging purposes */
transcript(dat, "SEND:", command);
do {
size_t nb;
int rc;
UPDATE_TTW(tctl);
rc = mu_stream_write(dat->stream, command, len,
dat->send_off, &nb);
if (rc == 0) {
if (nb == 0) {
mu_error(_("stream_write: wrote 0 bytes"));
return -1;
}
dat->send_off += nb;
len -= nb;
command += nb;
} else if (rc == EAGAIN) {
rc = smtp_wait(dat, MU_STREAM_READY_WR, &tctl);
if (rc) {
mu_error(_("smtp_wait failed: %s"),
mu_strerror(rc));
return -1;
}
continue;
} else {
mu_error("mu_stream_write: %s",
mu_strerror (rc));
return -1;
}
} while (len > 0);
return 0;
}
int
smtp_send2(struct smtp_io_data *dat, char *command, char *arg)
{
obstack_grow(&dat->stk, command, strlen(command));
if (arg)
obstack_grow(&dat->stk, arg, strlen(arg));
obstack_grow(&dat->stk, "\r\n", 2);
obstack_1grow(&dat->stk, 0);
dat->command = obstack_finish(&dat->stk);
return smtp_send(dat, dat->command);
}
int
smtp_send3(struct smtp_io_data *dat, char *command, char *arg1, char *arg2)
{
obstack_grow(&dat->stk, command, strlen(command));
obstack_grow(&dat->stk, arg1, strlen(arg1));
obstack_grow(&dat->stk, arg2, strlen(arg2));
obstack_grow(&dat->stk, "\r\n", 2);
obstack_1grow(&dat->stk, 0);
dat->command = obstack_finish(&dat->stk);
return smtp_send(dat, dat->command);
}
int
smtp_recvline(struct smtp_io_data *dat, time_t timeout)
{
struct timeout_ctl tctl;
init_timeout_ctl(&tctl, timeout);
for (;;) {
char *p;
UPDATE_TTW(tctl);
if (dat->level == 0) {
int rc = mu_stream_read(dat->stream,
dat->buf, sizeof dat->buf,
dat->recv_off, &dat->level);
if (rc == 0) {
if (dat->level == 0) {
mu_error(_("stream_read: read 0 bytes"));
return -1;
}
dat->recv_off += dat->level;
} else if (rc == EAGAIN) {
rc = smtp_wait(dat,
MU_STREAM_READY_RD, &tctl);
if (rc) {
mu_error(_("smtp_wait failed: %s"),
mu_strerror(rc));
return -1;
}
continue;
} else {
mu_error("mu_stream_read: %s",
mu_strerror (rc));
return -1;
}
}
p = memchr(dat->buf, '\n', dat->level);
if (!p) {
obstack_grow(&dat->stk, dat->buf, dat->level);
dat->level = 0;
continue;
} else {
size_t len = p - dat->buf + 1;
obstack_grow(&dat->stk, dat->buf, len);
obstack_1grow(&dat->stk, 0);
dat->reply = obstack_finish(&dat->stk);
dat->level -= len;
memmove(dat->buf, dat->buf + len, dat->level);
break;
}
}
return 0;
}
int
smtp_recv_tm(struct smtp_io_data *dat, time_t timeout)
{
char *p;
dat->start = NULL;
do {
int code;
int rc = smtp_recvline(dat, timeout);
if (rc)
return -1;
transcript(dat, "RECV:", dat->reply);
code = strtoul(dat->reply, &p, 0);
if (p - dat->reply != 3 || (*p != '-' && *p != ' ')) {
mu_error(_("Unexpected reply from server: %s"),
dat->reply);
return -1;
} else if (!dat->start) {
dat->start = dat->reply;
dat->code = code;
} else if (dat->code != code) {
mu_error(_("Unexpected reply code from server: %d"),
code);
return -1;
}
} while (*p == '-');
obstack_1grow (&dat->stk, 0);
obstack_finish (&dat->stk);
return 0;
}
int
smtp_recv(struct smtp_io_data *dat)
{
return smtp_recv_tm(dat, io_timeout);
}
const char *
smtp_last_sent(struct smtp_io_data *dat)
{
if (dat->command) {
size_t len = strcspn(dat->command, "\r\n");
dat->command[len] = 0;
return dat->command;
}
return "nothing";
}
const char *
smtp_last_received(struct smtp_io_data *dat)
{
if (dat->reply) {
size_t len = strcspn(dat->reply, "\r\n");
dat->reply[len] = 0;
return dat->reply;
}
return "nothing";
}
/* Milter-specific functions */
static mf_status
reset(struct smtp_io_data *io)
{
smtp_send(io, "RSET");
if (smtp_recv(io))
return mf_temp_failure;
else if (SMTP_MAJOR(io->code) != 2)
return SMTP_MAJOR(io->code) == 4 ?
mf_temp_failure : mf_failure;
return mf_success;
}
/* Verify whether EMAIL address is served by host CLIENT_ADDR. */
mf_status
check_on_host(eval_environ_t env, char *email, char *client_addr,
char *ehlo, char *mailfrom)
{
int rc;
mu_stream_t stream;
struct smtp_io_data io;
mf_status status = mf_success;
int count = 0;
SMFICTX *ctx = env_get_context(env);
struct timeout_ctl tctl;
debug2(10, "email = %s, client_addr = %s", email, client_addr);
set_last_poll_result(env, client_addr, "", "");
rc = mu_tcp_stream_create_with_source_ip (&stream, client_addr, 25,
source_address,
MU_STREAM_NONBLOCK);
if (rc) {
mu_error(_("%sCannot connect to `%s': %s"),
mailfromd_msgid(ctx), client_addr,
mu_strerror (rc));
return mf_temp_failure;
}
init_timeout_ctl(&tctl, connect_timeout);
while ((rc = mu_stream_open(stream))) {
if ((rc == EAGAIN || rc == EINPROGRESS)
&& tctl.timeout) {
rc = smtp_stream_wait(stream, MU_STREAM_READY_WR,
&tctl);
if (rc == 0) {
UPDATE_TTW(tctl);
continue;
}
}
mu_error("%sstream_open: %s, %d",
mailfromd_msgid(ctx), mu_strerror(rc), count);
mu_stream_destroy(&stream, mu_stream_get_owner(stream));
return mf_temp_failure;
}
debug(100,"stream opened");
if (smtp_io_data_init(&io, ctx, stream))
status = mf_temp_failure;
else {
mu_address_t addr;
size_t mailcount;
/* FIXME-MU: compensate for mailutils deficiency */
if (mailfrom[0] == 0)
mailfrom = "<>";
rc = mu_address_create(&addr, mailfrom);
if (rc) {
mu_error(_("Cannot create address `%s': %s"),
mailfrom, mu_strerror(rc));
return mf_temp_failure;
}
mu_address_get_count(addr, &mailcount);
do {
int i;
if (smtp_recv_tm(&io, response_timeout)) {
status = mf_temp_failure;
break;
} else if (SMTP_MAJOR(io.code) != 2) {
status = SMTP_MAJOR(io.code) == 4 ?
mf_temp_failure : mf_not_found;
break;
}
smtp_send2(&io, "HELO ", ehlo);
if (smtp_recv(&io)) {
status = mf_temp_failure;
break;
} else if (SMTP_MAJOR(io.code) == 5) {
/* Let's try EHLO, then */
smtp_send2(&io, "EHLO ", ehlo);
if (smtp_recv(&io)) {
status = mf_not_found;
break;
}
}
if (SMTP_MAJOR(io.code) != 2) {
status = SMTP_MAJOR(io.code) == 4 ?
mf_temp_failure : mf_not_found;
break;
}
for (i = 1; i <= mailcount; i++) {
char *fromaddr;
mu_address_aget_email(addr, i, &fromaddr);
smtp_send3(&io, "MAIL FROM:<", fromaddr, ">");
free(fromaddr);
if (smtp_recv(&io)) {
status = mf_temp_failure;
break;
} else if (SMTP_MAJOR(io.code) != 2) {
if (SMTP_MAJOR(io.code) == 4) {
if (reset(&io) != mf_success) {
/* RSET must always return 250
If it does not, there's no
use talking with this host
any more */
status = mf_failure;
break;
} else
status = mf_temp_failure;
} else
status = mf_not_found;
} else {
status = mf_success;
break;
}
}
if (status != mf_success)
break;
smtp_send3(&io, "RCPT TO:<", email, ">");
if (smtp_recv(&io)) {
status = mf_temp_failure;
break;
} else if (SMTP_MAJOR(io.code) != 2) {
status = SMTP_MAJOR(io.code) == 4 ?
mf_temp_failure : mf_not_found;
break;
}
} while (0);
mu_address_destroy(&addr);
set_last_poll_result(env,
client_addr,
smtp_last_sent(&io),
smtp_last_received(&io));
debug4(1,
"%spoll exited with status: %s; sent \"%s\", got \"%s\"",
mailfromd_msgid(ctx),
mf_status_str(status),
smtp_last_sent(&io),
smtp_last_received(&io));
smtp_send2(&io, "QUIT", NULL);
smtp_io_data_destroy(&io);
}
return status;
}
mf_status
check_mx_records(eval_environ_t env, char *email, char *client_addr,
char *ehlo, char *mailfrom, int *pcount)
{
int i;
mxbuf_t mxbuf;
mf_status rc, mxstat = getmx(client_addr, mxbuf);
if (pcount)
*pcount = 0;
switch (mxstat) {
case mf_temp_failure:
rc = mf_temp_failure;
break;
case mf_failure:
case mf_not_found:
rc = mf_not_found;
break;
case mf_success:
debug1(2,"Checking MX servers for %s", email);
rc = mf_not_found;
for (i = 0; i < MAXMXCOUNT && mxbuf[i]; i++) {
rc = check_on_host(env, email, mxbuf[i],
ehlo, mailfrom);
if (mf_resolved(rc))
break;
}
if (pcount) {
for (; i < MAXMXCOUNT && mxbuf[i]; i++)
;
*pcount = i;
}
dns_freemx(mxbuf);
break;
default:
abort();
}
return rc;
}
/* Method "strict". Verifies whether EMAIL is understood either by
host CLIENT_ADDR or one of MX servers of its domain */
mf_status
method_strict(eval_environ_t env, char *email, char *client_addr,
char *ehlo, char *mailfrom)
{
mf_status rc;
if (email[0] == 0)
return mf_success;
rc = cache_get2(email, client_addr);
if (!mf_resolved(rc)) {
set_cache_used(env, 0);
rc = check_on_host(env, email, client_addr, ehlo, mailfrom);
if (!mf_resolved(rc)) {
mf_status mx_stat;
mx_stat = check_mx_records(env, email, client_addr,
ehlo, mailfrom, NULL);
if (mf_resolved(mx_stat)
|| mx_stat == mf_temp_failure)
rc = mx_stat;
}
if (mf_resolved(rc))
cache_insert2(email, client_addr, rc);
} else {
set_last_poll_result(env, client_addr, "", "");
set_cache_used(env, 1);
}
return rc;
}
/* Method "standard". Verifies whether EMAIL is understood by
any of its MXs, or, if there are none, by the domain part host itself. */
mf_status
method_standard(eval_environ_t env, char *email, char *ehlo, char *mailfrom)
{
mf_status rc;
if (email[0] == 0)
return mf_success;
rc = cache_get(email);
if (!mf_resolved(rc)) {
char *p = strchr(email, '@');
set_cache_used(env, 0);
if (p == NULL) {
mu_error(_("Invalid address: %s"), email);
rc = mf_not_found;
} else {
int mxcount;
rc = check_mx_records(env, email, p+1, ehlo, mailfrom,
&mxcount);
if (rc != mf_success && mxcount == 0) {
mf_status host_stat;
host_stat = check_on_host(env, email, p+1,
ehlo, mailfrom);
if (mf_resolved(host_stat)
|| host_stat == mf_temp_failure)
rc = host_stat;
}
}
if (mf_resolved(rc))
cache_insert(email, rc);
} else {
set_last_poll_result(env, "", "", "");
set_cache_used(env, 1);
}
return rc;
}
mf_status
listens_on (const char *client_addr, int port)
{
unsigned count = 0;
mu_stream_t stream;
int rc;
struct timeout_ctl tctl;
if (port == 0)
port = 25;
debug2(10, "client_addr = %s, port = %d", client_addr, port);
rc = mu_tcp_stream_create_with_source_ip (&stream, client_addr, port,
source_address,
MU_STREAM_NONBLOCK);
if (rc) {
debug1 (10, "mu_tcp_stream_create_with_source_ip: %s",
mu_strerror (rc));
return mf_failure;
}
init_timeout_ctl(&tctl, connect_timeout);
while ((rc = mu_stream_open(stream))) {
if ((rc == EAGAIN || rc == EINPROGRESS) && tctl.timeout) {
rc = smtp_stream_wait(stream, MU_STREAM_READY_WR,
&tctl);
if (rc == 0) {
UPDATE_TTW(tctl);
continue;
}
}
break;
}
mu_stream_destroy(&stream, mu_stream_get_owner(stream));
debug3 (10, "mu_stream_open(%s): %s, %u", client_addr, mu_strerror(rc),
count);
return rc == 0 ? mf_success :
(rc == EAGAIN || rc == EINPROGRESS) ?
mf_temp_failure : mf_failure;
}
/* Cleanup functions */
void
filter_cleanup(SMFICTX *ctx)
{
struct message_data *md = priv_get(ctx);
debug(100,"cleaning up");
if (md) {
env_init(md->env);
xeval(md->env, smtp_state_end);
free(md->helostr);
destroy_environment(md->env);
smtp_io_data_destroy(md->io);
mu_list_destroy(&md->hdr);
free(md);
gacopyz_setpriv(ctx, NULL);
}
}
/* Milter interface functions */
int
xeval(eval_environ_t env, enum smtp_state tag)
{
int rc;
rc = eval_environment(env, entry_point[tag]);
if (rc)
mu_error(_("Execution of the filter program was not finished"));
if (tag == smtp_state_begin)
env_save_catches(env);
return rc;
}
sfsistat
mlfi_eval(SMFICTX *ctx, enum smtp_state tag)
{
int rc;
sfsistat status;
struct message_data *md = priv_get(ctx);
rc = xeval(md->env, tag);
if (rc == 0)
status = environment_get_status(md->env);
else {
gacopyz_setreply(ctx, "410", NULL,
"Local configuration error; please try again later");
status = SMFIS_TEMPFAIL;
}
log_status(status, ctx);
return status;
}
#define MFAM_STDIO 0
#define MFAM_UNIX 1
#define MFAM_INET 2
sfsistat
mlfi_connect(SMFICTX *ctx, char *hostname, _SOCK_ADDR *hostaddr)
{
sfsistat status;
struct message_data *md = priv_get(ctx);
int port;
char *addrstr;
int family;
if (!hostaddr) {
family = MFAM_STDIO;
port = 0;
addrstr = "";
} else switch (hostaddr->sa.sa_family) {
case PF_INET:
family = MFAM_INET;
port = ntohs(hostaddr->sin.sin_port);
addrstr = inet_ntoa(hostaddr->sin.sin_addr);
break;
case PF_UNIX:
family = MFAM_UNIX;
port = 0;
addrstr = hostaddr->sunix.sun_path;
break;
default:
mu_error(_("mlfi_connect: unsupported address family: %d"),
hostaddr->sa.sa_family);
gacopyz_setreply(ctx, "410", NULL,
"Local configuration error; please try again later");
return SMFIS_TEMPFAIL;
}
debug4(70, "Processing xxfi_connect: %s, %d, %u, %s",
hostname, family, port, addrstr);
env_init(md->env);
env_push_string(md->env, addrstr);
env_push_number(md->env, port);
env_push_number(md->env, family);
env_push_string(md->env, hostname);
env_make_frame(md->env);
status = mlfi_eval(ctx, smtp_state_connect);
env_leave_frame(md->env, 4);
return status;
}
sfsistat
mlfi_helo(SMFICTX *ctx, char *helohost)
{
sfsistat status;
struct message_data *md = priv_get(ctx);
debug1(70, "Processing xxfi_helo: %s", helohost);
md->helostr = strdup(helohost);
env_init(md->env);
env_push_string(md->env, md->helostr);
env_make_frame(md->env);
status = mlfi_eval(ctx, smtp_state_helo);
env_leave_frame(md->env, 1);
return status;
}
static char *
concat_args(const char *fname, char **argv)
{
size_t argc;
char *p = NULL;
for (argc = 0; argv[argc]; argc++)
;
if (mu_argcv_string(argc, argv, &p))
debug3(70, "Processing %s: %s%s",
fname, argv[0], argv[1] ? " (more)" : "");
else
debug2(70, "Processing %s: %s", fname, p);
return p;
}
sfsistat
mlfi_envfrom(SMFICTX *ctx, char **argv)
{
sfsistat status;
struct message_data *md = priv_get(ctx);
char *p = concat_args("xxfi_envfrom", argv);
env_init(md->env);
capture_from(md->env, argv[0]);
env_push_string(md->env, p);
free(p);
env_push_string(md->env, argv[0]);
env_make_frame(md->env);
status = mlfi_eval(ctx, smtp_state_envfrom);
env_leave_frame(md->env, 2);
return status;
}
sfsistat
mlfi_envrcpt(SMFICTX *ctx, char ** argv)
{
sfsistat status;
struct message_data *md = priv_get(ctx);
char *p = concat_args("xxfi_envrcpt", argv);
env_init(md->env);
env_push_string(md->env, p);
free(p);
env_push_string(md->env, argv[0]);
env_make_frame(md->env);
incr_rcpt_count(md->env);
status = mlfi_eval(ctx, smtp_state_envrcpt);
env_leave_frame(md->env, 2);
return status;
}
sfsistat
mlfi_data(SMFICTX *ctx)
{
sfsistat status;
struct message_data *md = priv_get(ctx);
debug(70, "Processing xxfi_data:");
env_init(md->env);
env_make_frame(md->env);
status = mlfi_eval(ctx, smtp_state_data);
env_leave_frame(md->env, 1);
return status;
}
sfsistat
mlfi_header(SMFICTX *ctx, char *headerf, char *headerv)
{
sfsistat status;
struct message_data *md = priv_get(ctx);
debug(70, "Processing xxfi_header:");
env_init(md->env);
capture_header(md->env, headerf, headerv);
env_push_string(md->env, headerv);
env_push_string(md->env, headerf);
env_make_frame(md->env);
status = mlfi_eval(ctx, smtp_state_header);
env_leave_frame(md->env, 2);
return status;
}
sfsistat
mlfi_eoh(SMFICTX *ctx)
{
sfsistat status;
struct message_data *md = priv_get(ctx);
debug(70, "Processing xxfi_eoh");
env_init(md->env);
capture_eoh(md->env);
env_make_frame(md->env);
status = mlfi_eval(ctx, smtp_state_eoh);
env_leave_frame(md->env, 0);
return status;
}
sfsistat
mlfi_body(SMFICTX *ctx, unsigned char *bodyp, size_t len)
{
sfsistat status;
struct message_data *md = priv_get(ctx);
debug1(70, "Processing xxfi_body: %lu", (unsigned long) len);
env_init(md->env);
capture_body(md->env, bodyp, len);
env_push_number(md->env, len);
/* FIXME: Push bodyp as number to avoid unnecessary stack allocation.
This, however, makes it impossible to pass $2 between invocations of
mlfi_body
*/
env_push_number(md->env, bodyp);
env_make_frame(md->env);
status = mlfi_eval(ctx, smtp_state_body);
env_leave_frame(md->env, 2);
return status;
}
static int
run_header_command(void *item, void *data)
{
struct old_header_node *hdr = item;
SMFICTX *ctx = data;
debug3(50,
"%s %s: %s",
header_command_str(hdr->opcode),
hdr->name, hdr->value);
switch (hdr->opcode) {
case header_add:
gacopyz_add_header(ctx, hdr->name, hdr->value);
break;
case header_replace:
gacopyz_change_header(ctx, 1, hdr->name, hdr->value);
break;
case header_delete:
gacopyz_change_header(ctx, 1, hdr->name, NULL);
break;
}
return 0;
}
sfsistat
mlfi_eom(SMFICTX *ctx)
{
sfsistat status;
struct message_data *md = priv_get(ctx);
debug(70, "Processing xxfi_eom");
env_init(md->env);
capture_eom(md->env);
env_make_frame(md->env);
status = mlfi_eval(ctx, smtp_state_eom);
env_leave_frame(md->env, 0);
if ((status == SMFIS_ACCEPT || status == SMFIS_CONTINUE) && md->hdr) {
debug(50,"executing header commands");
mu_list_do(md->hdr, run_header_command, ctx);
}
clear_rcpt_count(md->env);
return status;
}
sfsistat
mlfi_abort(SMFICTX *ctx)
{
struct message_data *md = priv_get(ctx);
debug(70, "Abort");
mu_list_destroy(&md->hdr);
md->hdr = NULL;
md->msgid[0] = 0;
return SMFIS_CONTINUE;
}
sfsistat
mlfi_close(SMFICTX *ctx)
{
debug(70, "Close");
filter_cleanup(ctx);
return SMFIS_CONTINUE;
}
static int
child_start()
{
signal(SIGPIPE, SIG_IGN);
signal(SIGALRM, SIG_IGN);
return 0;
}
struct smfiDesc smfilter =
{
"MailfromFilter",
SMFI_VERSION,
SMFIF_ADDHDRS|SMFIF_CHGHDRS,
NULL, /* connection info filter */
NULL, /* SMTP HELO command filter */
NULL, /* envelope sender filter */
NULL, /* envelope recipient filter */
NULL, /* header filter */
NULL, /* end of header */
NULL, /* body block filter */
mlfi_eom, /* end of message */
mlfi_abort, /* message aborted */
mlfi_close, /* connection cleanup */
#ifdef GACOPYZ_VERSION_MAJOR
NULL, /* unknown command handler */
NULL, /* data handler */
child_start, /* child start */
NULL, /* child finish */
NULL /* idle callback */
#endif
};
void
milter_enable_state(enum smtp_state state)
{
switch (state) {
case smtp_state_connect:
smfilter.xxfi_connect = mlfi_connect;
break;
case smtp_state_helo:
smfilter.xxfi_helo = mlfi_helo;
break;
case smtp_state_envfrom:
smfilter.xxfi_envfrom = mlfi_envfrom;
break;
case smtp_state_envrcpt:
smfilter.xxfi_envrcpt = mlfi_envrcpt;
break;
case smtp_state_data:
smfilter.xxfi_data = mlfi_data;
break;
case smtp_state_header:
smfilter.xxfi_header = mlfi_header;
break;
case smtp_state_eoh:
smfilter.xxfi_eoh = mlfi_eoh;
break;
case smtp_state_body:
smfilter.xxfi_body = mlfi_body;
break;
default:
break;
}
}
/* Check whether pidfile NAME exists and if so, whether its PID is still
active. Exit if it is. */
void
check_pidfile(char *name)
{
unsigned long pid;
FILE *fp = fopen(name, "r");
if (!fp) {
if (errno == ENOENT)
return;
mu_error(_("Cannot open pidfile `%s': %s"),
name, mu_strerror(errno));
exit(EX_TEMPFAIL);
}
if (fscanf(fp, "%lu", &pid) != 1) {
mu_error(_("Cannot get pid from pidfile `%s'"), name);
} else {
if (kill(pid, 0) == 0) {
mu_error(_("%s appears to run with pid %lu. If it does not, remove `%s' and retry."),
program_invocation_short_name,
pid,
name);
exit(EX_USAGE);
}
}
fclose(fp);
if (unlink(pidfile)) {
mu_error(_("Cannot unlink pidfile `%s'"),
name, mu_strerror(errno));
exit(EX_USAGE);
}
}
/* Check whether local socket P exists. Act in accordance with the settings
of --remove command line option. */
static void
check_local_portspec(char *p)
{
struct stat st;
if (stat(p, &st)) {
if (errno == ENOENT)
return; /* That's OK. The socket must not exist */
mu_error(_("Cannot stat file `%s': %s"),
p, mu_strerror(errno));
exit(EX_SOFTWARE);
} else if (!force_remove) {
mu_error(_("File `%s' already exists. Remove it or use --remove option."), p);
exit(EX_SOFTWARE);
}
if (unlink (p)) {
mu_error(_("Cannot unlink file `%s': %s"), p, mu_strerror(errno));
exit(EX_SOFTWARE);
}
}
void
check_portspec(char *portspec)
{
static struct spectab {
char *proto;
int skip;
void (*check)(char *);
} spectab[] = {
{ "/", 0, check_local_portspec },
{ "unix:", 1, check_local_portspec },
{ "local:", 1, check_local_portspec },
{ "inet:", 1, NULL },
{ "inet6:", 1, NULL },
};
struct spectab *sp;
for (sp = spectab; sp < spectab + sizeof(spectab)/sizeof(spectab[0]);
sp++) {
int len = strlen(sp->proto);
if (strlen(portspec) > len + 1
&& strncasecmp(portspec, sp->proto, len) == 0) {
if (sp->check)
sp->check(portspec + (sp->skip ? len : 0));
return;
}
}
mu_error(_("Unknown port specification: %s"), portspec);
}
static int x_argc; /* A copy of argc and ... */
static char **x_argv; /* ... argv */
static int restart; /* Set to 1 if restart is requested */
void
save_cmdline(int argc, char **argv)
{
int i;
if (argc == 0) {
/* Clear saved command line */
for (i = 0; i < x_argc; i++)
free(x_argv[i]);
free(x_argv);
x_argc = 0;
x_argv = NULL;
} else if (argv[0][0] != '/') {
/* Do not save if argv[0] is not a full file name.
An appropriate error message will be issued later,
if necessary. */
return;
}
x_argc = argc;
x_argv = xcalloc (x_argc + 1, sizeof x_argv[0]);
for (i = 0; i < x_argc; i++)
x_argv[i] = xstrdup (argv[i]);
x_argv[i] = NULL;
}
static RETSIGTYPE
sig_stop(int sig)
{
smfi_stop();
}
static RETSIGTYPE
sig_restart(int sig)
{
restart = 1;
smfi_stop();
}
void
mailfromd_daemon()
{
int rc;
mu_error(_("mailfromd version %s starting"), PACKAGE_VERSION);
priv_setup();
if (!foreground)
check_pidfile(pidfile);
check_portspec(portspec);
if (smfi_setconn(portspec) == MI_FAILURE) {
mu_error("smfi_setconn: %s", mu_strerror(errno));
exit(EX_SOFTWARE);
}
if (smfi_register(smfilter) == MI_FAILURE) {
mu_error(_("smfi_register failed"));
exit(EX_UNAVAILABLE);
}
smfi_set_foreground(single_process_option);
signal(SIGTERM, sig_stop);
signal(SIGQUIT, sig_stop);
if (x_argc == 0) {
if (!mtasim_option) {
if (script_file[0] != '/')
mu_error(_("Warning: script file is given "
"without full file name"));
else
mu_error(_("Warning: mailfromd started "
"without full file name"));
mu_error(_("Warning: restart (SIGHUP) will not work"));
}
signal(SIGHUP, sig_stop);
} else
signal(SIGHUP, sig_restart);
signal(SIGINT, sig_stop);
if (!foreground) {
if (daemon(0, 0) == -1) {
mu_error(_("Cannot become a daemon: %s"),
mu_strerror(errno));
exit(EX_OSERR);
}
umask(0117);
mu_daemon_create_pidfile(pidfile);
rc = smfi_main();
} else {
umask(0117);
rc = smfi_main();
}
if (rc)
mu_error(_("Exit code = %d, last error status: %s"),
rc, strerror (errno));
if (restart) {
char *s;
int i;
mu_error(_("mailfromd restarting"));
mu_daemon_remove_pidfile();
for (i = getmaxfd(); i > 2; i--)
close(i);
execv(x_argv[0], x_argv);
log_setup(0);
mu_error(_("Cannot restart: %s"), mu_strerror(errno));
if (mu_argcv_string(x_argc, x_argv, &s) == 0)
mu_error(_("Command line was: %s"), s);
} else
mu_error(_("mailfromd terminating"));
exit(rc);
}