/* This file is part of Mailfromd.
Copyright (C) 2005-2020 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 . */
#ifdef HAVE_CONFIG_H
# include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "mailfromd.h"
#include "srvman.h"
#include "srvcfg.h"
static const char *ctx_getsym(void *data, const char *str);
static int ctx_setreply(void *data, char *code, char *xcode, char *message);
/* Per-message data */
struct message_data {
eval_environ_t env; /* Evaluation environment */
char *helostr; /* Domain name obtained in HELO phase */
char msgid[64]; /* Message ID */
};
static struct message_data *test_message_data;
void
test_message_data_init(eval_environ_t env)
{
test_message_data = mu_alloc(sizeof(*test_message_data));
test_message_data->env = env;
test_message_data->helostr = NULL;
test_message_data->msgid[0] = 0;
}
static struct message_data *
priv_get(SMFICTX *ctx)
{
struct message_data *md;
if (mode == MAILFROMD_TEST)
md = test_message_data;
else
md = (struct message_data*) gacopyz_getpriv(ctx);
if (!md) {
md = malloc(sizeof(*md));
if (!md)
mu_error(_("not enough memory"));
else {
milter_sockaddr_t addr;
socklen_t len = sizeof(addr);
md->env = create_environment(ctx,
ctx_getsym,
ctx_setreply,
NULL,
ctx);
clear_rcpt_count(md->env);
md->helostr = NULL;
md->msgid[0] = 0;
gacopyz_setpriv(ctx, md);
env_init(md->env);
if (gacopyz_server_sockname(ctx, &addr, &len) == 0)
set_milter_server_address(md->env, &addr, len);
set_milter_server_id(md->env, gacopyz_getclosure(ctx));
if (gacopyz_client_sockname(ctx, &addr, &len) == 0)
set_milter_client_address(md->env, &addr, len);
xeval(md->env, smtp_state_begin);
}
}
if (!md->msgid[0]) {
/* FIXME: Cannot use env_get_macro here, because it
would create a recursion. */
const 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;
}
const char *
mailfromd_msgid(SMFICTX *ctx)
{
struct message_data *md = priv_get(ctx);
return md->msgid;
}
/* Run-time execution */
static const char *
ctx_getsym(void *data, const char *str)
{
const 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;
}
/* 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, (char*) bodyp, len);
}
static void
capture_eom(eval_environ_t env)
{
}
/* Cleanup functions */
void
filter_cleanup(SMFICTX *ctx)
{
struct message_data *md = priv_get(ctx);
mu_debug(MF_SOURCE_ENGINE, MU_DEBUG_TRACE9, ("cleaning up"));
if (md) {
env_init(md->env);
xeval(md->env, smtp_state_end);
free(md->helostr);
destroy_environment(md->env);
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;
}
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;
#ifdef GACOPYZ_IPV6
case PF_INET6: {
char host[NI_MAXHOST];
family = MFAM_INET6;
port = ntohs(hostaddr->sin6.sin6_port);
if (getnameinfo(&hostaddr->sa, sizeof(hostaddr->sin6),
host, sizeof host,
NULL, 0,
NI_NUMERICHOST) == 0)
addrstr = host;
else
addrstr = "[unresolved]";
break;
}
#endif
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;
}
mu_debug(MF_SOURCE_ENGINE, MU_DEBUG_TRACE7,
("Processing xxfi_connect: %s, %d, %s, %u",
hostname, family, addrstr, port));
mf_proctitle_format("Connect: %s, %d, %s, %u",
hostname, family, addrstr, port);
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);
mu_debug(MF_SOURCE_ENGINE, MU_DEBUG_TRACE7,
("Processing xxfi_helo: %s", helohost));
mf_proctitle_format("%sHELO %s", md->msgid, helohost);
if (md->helostr)
free (md->helostr);
md->helostr = mu_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(char **argv)
{
size_t argc;
char *p = NULL;
for (argc = 0; argv[argc]; argc++)
;
mu_argcv_string(argc, argv, &p);
return p;
}
sfsistat
mlfi_envfrom(SMFICTX *ctx, char **argv)
{
sfsistat status;
struct message_data *md = priv_get(ctx);
char *p = concat_args(argv + 1);
mu_debug(MF_SOURCE_ENGINE, MU_DEBUG_TRACE7,
("Processing xxfi_envfrom: %s %s", argv[0], p));
mf_proctitle_format("%sMAIL FROM %s %s", md->msgid, argv[0], p);
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(argv + 1);
mu_debug(MF_SOURCE_ENGINE, MU_DEBUG_TRACE7,
("Processing xxfi_envrcpt: %s %s", argv[0], p));
mf_proctitle_format("%sRCPT TO %s %s", md->msgid, argv[0], p);
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);
mu_debug(MF_SOURCE_ENGINE, MU_DEBUG_TRACE7, ("Processing xxfi_data:"));
mf_proctitle_format("%sDATA", md->msgid);
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);
mu_debug(MF_SOURCE_ENGINE, MU_DEBUG_TRACE7,
("Processing xxfi_header:"));
mf_proctitle_format("%sHeader", md->msgid);
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);
mu_debug(MF_SOURCE_ENGINE, MU_DEBUG_TRACE7, ("Processing xxfi_eoh"));
mf_proctitle_format("%sEOH", md->msgid);
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);
mu_debug(MF_SOURCE_ENGINE, MU_DEBUG_TRACE7,
("Processing xxfi_body: %lu", (unsigned long) len));
mf_proctitle_format("%sBODY", md->msgid);
env_init(md->env);
capture_body(md->env, bodyp, len);
env_push_number(md->env, len);
/* Push bodyp as generic pointer to avoid unnecessary stack allocation.
User can then convert it to string using the body_string() call.
*/
env_push_pointer(md->env, bodyp);
env_make_frame(md->env);
status = mlfi_eval(ctx, smtp_state_body);
env_leave_frame(md->env, 2);
return status;
}
size_t
mem_search(const char *str, int c, size_t size)
{
const char *p;
if (size == 0)
return 0;
p = memchr(str, c, size);
if (p)
return p - str;
return size;
}
int
xlate_and_replace_body(SMFICTX *ctx, const char *value, size_t size)
{
int rc;
mu_opool_t pool;
if ((rc = mu_opool_create(&pool, MU_OPOOL_DEFAULT))) {
mu_error(_("cannot create opool: %s"), mu_strerror(rc));
return 1;
}
while (size) {
size_t n = mem_search(value, '\n', size);
size_t off;
if (value[n] == '\n') {
off = n + 1;
if (n > 0 && value[n-1] == '\r')
n--;
} else
off = n;
if ((rc = mu_opool_append(pool, value, n)) != 0
|| (rc = mu_opool_append(pool, "\r\n", 2)) != 0) {
mu_error(_("failed to append to opool: %s"),
mu_strerror(rc));
break;
}
value += off;
size -= off;
}
if (rc == 0) {
mu_iterator_t itr;
rc = mu_opool_get_iterator(pool, &itr);
if (rc) {
mu_error(_("%s failed: %s"),
"mu_opool_iterator_create",
mu_strerror(rc));
} else {
for (mu_iterator_first (itr);
!mu_iterator_is_done (itr);
mu_iterator_next (itr)) {
const char *ptr;
size_t len;
mu_iterator_current_kv (itr,
(const void**)&len,
(void **)&ptr);
rc = gacopyz_replace_body(ctx,
(const unsigned char*)ptr,
len);
if (rc) {
mu_error(_("%s failed: %s"),
"gacopyz_replace_body",
mu_strerror(errno));
break;
}
}
mu_iterator_destroy (&itr);
}
}
mu_opool_destroy (&pool);
return rc;
}
static int
run_msgmod(void *item, void *data)
{
struct msgmod_closure *hdr = item;
SMFICTX *ctx = data;
mu_debug(MF_SOURCE_ENGINE, MU_DEBUG_TRACE6,
("%s %s: %s %u",
msgmod_opcode_str(hdr->opcode),
SP(hdr->name), SP(hdr->value), hdr->idx));
switch (hdr->opcode) {
case header_add:
gacopyz_add_header(ctx, hdr->name, hdr->value);
break;
case header_replace:
gacopyz_change_header(ctx, hdr->idx, hdr->name, hdr->value);
break;
case header_delete:
gacopyz_change_header(ctx, hdr->idx, hdr->name, NULL);
break;
case header_insert:
gacopyz_insert_header(ctx, hdr->idx, hdr->name, hdr->value);
break;
case rcpt_add:
gacopyz_add_rcpt(ctx, hdr->name);
break;
case rcpt_delete:
gacopyz_del_rcpt(ctx, hdr->name);
break;
case quarantine:
gacopyz_quarantine(ctx, hdr->name);
break;
case body_repl:
xlate_and_replace_body(ctx, hdr->value, strlen(hdr->value));
break;
case body_repl_fd:
if (gacopyz_replace_body_fd(ctx, hdr->idx) != MI_SUCCESS) {
mu_error(_("%s failed: %s"),
"gacopyz_replace_body_fd",
mu_strerror(errno));
break;
}
break;
case set_from:
gacopyz_chgfrom(ctx, hdr->name, hdr->value);
break;
default:
abort();
}
return 0;
}
sfsistat
mlfi_eom(SMFICTX *ctx)
{
sfsistat status;
struct message_data *md = priv_get(ctx);
mu_debug(MF_SOURCE_ENGINE, MU_DEBUG_TRACE7, ("Processing xxfi_eom"));
mf_proctitle_format("%sEOM", md->msgid);
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)
&& env_msgmod_count(md->env) > 0) {
mu_debug(MF_SOURCE_ENGINE, MU_DEBUG_TRACE6,
("flushing message modification queue"));
env_msgmod_apply(md->env, run_msgmod, ctx);
}
mf_proctitle_format("%sfinished", md->msgid);
clear_rcpt_count(md->env);
env_msgmod_clear(md->env);
return status;
}
sfsistat
mlfi_abort(SMFICTX *ctx)
{
struct message_data *md = priv_get(ctx);
mu_debug(MF_SOURCE_ENGINE, MU_DEBUG_TRACE7, ("Abort"));
mf_proctitle_format("%saborting", md->msgid);
env_msgmod_clear(md->env);
md->msgid[0] = 0;
/* Note: the value of helostr is not discarded: RFC 2822,
section 4.1.1.5 states that RSET issued immediately
after EHLO is effectively equivalent to a NOOP. */
env_init_dataseg(md->env);
return SMFIS_CONTINUE;
}
sfsistat
mlfi_close(SMFICTX *ctx)
{
mu_debug(MF_SOURCE_ENGINE, MU_DEBUG_TRACE7, ("Close"));
mf_proctitle_format("closing");
filter_cleanup(ctx);
return SMFIS_CONTINUE;
}
static int
child_start()
{
signal(SIGPIPE, SIG_IGN);
signal(SIGALRM, SIG_IGN);
mf_proctitle_format("startup");
return 0;
}
enum smtp_state first_used_state = smtp_state_end;
sfsistat
mlfi_negotiate(SMFICTX *ctx,
unsigned long mta_actions,
unsigned long mta_capa,
unsigned long unused1,
unsigned long unused2,
unsigned long *filter_actions,
unsigned long *filter_capa,
unsigned long *unused4,
unsigned long *unused5)
{
enum gacopyz_stage i;
mu_debug(MF_SOURCE_ENGINE, MU_DEBUG_TRACE7,
("Negotiate: mta_actions=%#lx, mta_capa=%#lx, "
"filter_actions=%#lx, filter_capa=%#lx",
mta_actions, mta_capa, *filter_actions, *filter_capa));
if (first_used_state > smtp_state_first
&& first_used_state < smtp_state_end) {
mu_debug(MF_SOURCE_ENGINE, MU_DEBUG_TRACE6,
("Registering \"i\" macro for state %s",
state_to_string(first_used_state)));
register_macro(first_used_state, "i");
}
for (i = 0; i < gacopyz_stage_max; i++) {
char *str = get_stage_macro_string(i);
gacopyz_setsymlist(ctx, i, str);
free(str);
}
return SMFIS_CONTINUE;
}
static struct smfiDesc smfilter =
{
"MailfromFilter",
SMFI_VERSION,
SMFI_V2_ACTS|SMFIF_CHGFROM, /* FIXME: Add flags as needed */
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 */
mlfi_negotiate, /* negotiate */
child_start, /* child start */
NULL, /* child finish */
NULL, /* idle callback */
NULL /* Accept connection callback */
#endif
};
/* Milter server functions */
static void
setprocid(const char *id)
{
char *tag;
logger_close();
tag = mu_alloc(strlen(mu_log_tag) + 1 + strlen(id) + 1);
strcpy(tag, mu_log_tag);
strcat(tag, "#");
strcat(tag, id);
mu_log_tag = tag;
mf_server_log_setup();
}
int
milter_session_server(const char *id, int fd,
struct sockaddr const *sa, socklen_t len,
void *server_data, void *srvman_data)
{
setprocid(id);
gacopyz_context_loop(fd, &smfilter, (milter_sockaddr_t*) sa, len,
(void*) id);
return 0;
}
int
mfd_callout_session_server(const char *id, int fd,
struct sockaddr const *sa, socklen_t len,
void *server_data, void *srvman_data)
{
setprocid(id);
return callout_session_server(id, fd, sa, len,
server_data, srvman_data);
}
void
milter_setlogmask(int mask)
{
smfilter.logmask = mask;
}
void
milter_settimeout(time_t t)
{
smfilter.ctx_timeout.tv_sec = t;
smfilter.ctx_timeout.tv_usec = 0;
}
void
milter_enable_state(enum smtp_state state)
{
if (state > smtp_state_first && state < smtp_state_end
&& state < first_used_state)
first_used_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;
}
}