/* This file is part of Mailfromd. Copyright (C) 2005, 2006, 2007, 2008, 2009 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 const char *ctx_getsym(void *data, const char *str); static int ctx_setreply(void *data, char *code, char *xcode, char *message); static void ctx_msgmod(void *data, struct msgmod_closure *cmd); /* 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_msgmod, 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]) { 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; } 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_msgmod_closure(void *item) { struct msgmod_closure *cmd = item; free(cmd->name); free(cmd->value); } int priv_store_msgmod_closure(SMFICTX *ctx, struct msgmod_closure *cmd) { 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_msgmod_closure); } debug4(10, "adding msgmod_closure: %s \"%s\" %s %u", msgmod_opcode_str(cmd->opcode), cmd->name, SP(cmd->value), cmd->idx); mu_list_append(md->hdr, cmd); return 0; } /* 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; } static void ctx_msgmod(void *data, struct msgmod_closure *cmd) { priv_store_msgmod_closure(data, cmd); } /* 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, "", ""); set_last_poll_helo(env, ""); set_last_poll_greeting(env, ""); 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; } set_last_poll_greeting(env, smtp_last_received(&io)); 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; } 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; } } set_last_poll_helo(env, smtp_last_received(&io)); 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_recv(&io); 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: case mf_failure: case mf_not_found: rc = mxstat; 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)) { int mxcount; mf_status mx_stat; mx_stat = check_mx_records(env, email, client_addr, ehlo, mailfrom, &mxcount); if (!(mx_stat == mf_not_found && mxcount == 0) && (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_last_poll_helo(env, ""); set_last_poll_greeting(env, ""); 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_last_poll_helo(env, ""); set_last_poll_greeting(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(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); debug2(70, "Processing xxfi_envfrom: %s %s", 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); debug2(70, "Processing xxfi_envrcpt: %s %s", 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); 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); /* 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 (mu_opool_create(&pool, 0)) { 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, 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; debug4(50, "%s %s: %s %u", msgmod_opcode_str(hdr->opcode), 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)); } 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_msgmod, ctx); } clear_rcpt_count(md->env); mu_list_destroy(&md->hdr); 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; } static int milter_conn_accept (gacopyz_conn_t conn, int fd, const milter_sockaddr_t *msa, int len) { mu_acl_result_t res; int rc; char *p; if (!mailfromd_acl) return 0; rc = mu_acl_check_sockaddr (mailfromd_acl, &msa->sa, len, &res); if (rc) { p = mu_sockaddr_to_astr (&msa->sa, len); mu_error (_("Access from %s blocked: cannot check ACLs: %s"), p, mu_strerror (rc)); free (p); return 1; } switch (res) { case mu_acl_result_undefined: p = mu_sockaddr_to_astr (&msa->sa, len); mu_diag_output (MU_DIAG_INFO, _("%s: undefined ACL result; access allowed"), p); free (p); break; case mu_acl_result_accept: break; case mu_acl_result_deny: p = mu_sockaddr_to_astr (&msa->sa, len); mu_error (_("Access from %s blocked."), p); free (p); return 1; } return 0; } 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; debug4(70, "Negotiate: mta_actions=%#lx, mta_capa=%#lx, " "filter_actions=%#lx, filter_capa=%#lx", mta_actions, mta_capa, *filter_actions, *filter_capa); 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; } struct smfiDesc smfilter = { "MailfromFilter", SMFI_VERSION, SMFI_V2_ACTS, /* 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 */ milter_conn_accept /* Accept connection 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': %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(_("%s started"), program_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(mailfromd_umask); mu_daemon_create_pidfile(pidfile); rc = smfi_main(); } else { umask(mailfromd_umask); 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); }