/* 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; } }