/* $OpenBSD: mfa.c,v 1.74 2012/11/23 13:54:12 eric Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include <sys/types.h> #include <sys/wait.h> #include <sys/queue.h> #include <sys/tree.h> #include <sys/param.h> #include <sys/socket.h> #include <err.h> #include <errno.h> #include <event.h> #include <imsg.h> #include <pwd.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include "smtpd.h" #include "log.h" static void mfa_imsg(struct imsgev *, struct imsg *); static void mfa_shutdown(void); static void mfa_sig_handler(int, short, void *); static void mfa_test_connect(struct envelope *); static void mfa_test_helo(struct envelope *); static void mfa_test_mail(struct envelope *); static void mfa_test_rcpt(struct envelope *); static void mfa_test_rcpt_resume(struct submit_status *); static void mfa_test_dataline(struct submit_status *); static void mfa_test_quit(struct envelope *); static void mfa_test_close(struct envelope *); static void mfa_test_rset(struct envelope *); static int mfa_strip_source_route(char *, size_t); static int mfa_fork_filter(struct filter *); static void mfa_imsg(struct imsgev *iev, struct imsg *imsg) { struct filter *filter; if (iev->proc == PROC_SMTP) { switch (imsg->hdr.type) { case IMSG_MFA_CONNECT: mfa_test_connect(imsg->data); return; case IMSG_MFA_HELO: mfa_test_helo(imsg->data); return; case IMSG_MFA_MAIL: mfa_test_mail(imsg->data); return; case IMSG_MFA_RCPT: mfa_test_rcpt(imsg->data); return; case IMSG_MFA_DATALINE: mfa_test_dataline(imsg->data); return; case IMSG_MFA_QUIT: mfa_test_quit(imsg->data); return; case IMSG_MFA_CLOSE: mfa_test_close(imsg->data); return; case IMSG_MFA_RSET: mfa_test_rset(imsg->data); return; } } if (iev->proc == PROC_LKA) { switch (imsg->hdr.type) { case IMSG_LKA_MAIL: imsg_compose_event(env->sc_ievs[PROC_SMTP], IMSG_MFA_MAIL, 0, 0, -1, imsg->data, sizeof(struct submit_status)); return; case IMSG_LKA_RCPT: imsg_compose_event(env->sc_ievs[PROC_SMTP], IMSG_MFA_RCPT, 0, 0, -1, imsg->data, sizeof(struct submit_status)); return; case IMSG_LKA_RULEMATCH: mfa_test_rcpt_resume(imsg->data); return; } } if (iev->proc == PROC_PARENT) { switch (imsg->hdr.type) { case IMSG_CONF_START: env->sc_filters = xcalloc(1, sizeof *env->sc_filters, "mfa_imsg"); TAILQ_INIT(env->sc_filters); return; case IMSG_CONF_FILTER: filter = xmemdup(imsg->data, sizeof *filter, "mfa_imsg"); TAILQ_INSERT_TAIL(env->sc_filters, filter, f_entry); return; case IMSG_CONF_END: TAILQ_FOREACH(filter, env->sc_filters, f_entry) { log_info("info: Forking filter: %s", filter->name); if (! mfa_fork_filter(filter)) fatalx("could not fork filter"); } return; case IMSG_CTL_VERBOSE: log_verbose(*(int *)imsg->data); return; } } errx(1, "mfa_imsg: unexpected %s imsg", imsg_to_str(imsg->hdr.type)); } static void mfa_sig_handler(int sig, short event, void *p) { switch (sig) { case SIGINT: case SIGTERM: mfa_shutdown(); break; case SIGCHLD: fatalx("unexpected SIGCHLD"); break; default: fatalx("mfa_sig_handler: unexpected signal"); } } static void mfa_shutdown(void) { pid_t pid; struct filter *filter; TAILQ_FOREACH(filter, env->sc_filters, f_entry) { kill(filter->pid, SIGTERM); } do { pid = waitpid(WAIT_MYPGRP, NULL, 0); } while (pid != -1 || (pid == -1 && errno == EINTR)); log_info("info: mail filter exiting"); _exit(0); } pid_t mfa(void) { pid_t pid; struct passwd *pw; struct event ev_sigint; struct event ev_sigterm; struct event ev_sigchld; struct peer peers[] = { { PROC_PARENT, imsg_dispatch }, { PROC_SMTP, imsg_dispatch }, { PROC_LKA, imsg_dispatch }, { PROC_CONTROL, imsg_dispatch } }; switch (pid = fork()) { case -1: fatal("mfa: cannot fork"); case 0: break; default: return (pid); } purge_config(PURGE_EVERYTHING); if ((env->sc_pw = getpwnam(SMTPD_FILTER_USER)) == NULL) if ((env->sc_pw = getpwnam(SMTPD_USER)) == NULL) fatalx("unknown user " SMTPD_FILTER_USER); pw = env->sc_pw; smtpd_process = PROC_MFA; setproctitle("%s", env->sc_title[smtpd_process]); if (setgroups(1, &pw->pw_gid) || setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) fatal("mfa: cannot drop privileges"); imsg_callback = mfa_imsg; event_init(); SPLAY_INIT(&env->mfa_sessions); TAILQ_INIT(env->sc_filters); signal_set(&ev_sigint, SIGINT, mfa_sig_handler, NULL); signal_set(&ev_sigterm, SIGTERM, mfa_sig_handler, NULL); signal_set(&ev_sigchld, SIGCHLD, mfa_sig_handler, NULL); signal_add(&ev_sigint, NULL); signal_add(&ev_sigterm, NULL); signal_add(&ev_sigchld, NULL); signal(SIGPIPE, SIG_IGN); signal(SIGHUP, SIG_IGN); config_pipes(peers, nitems(peers)); config_peers(peers, nitems(peers)); if (event_dispatch() < 0) fatal("event_dispatch"); mfa_shutdown(); return (0); } static void mfa_test_connect(struct envelope *e) { struct submit_status ss; ss.id = e->session_id; ss.code = 530; ss.envelope = *e; mfa_session(&ss, S_CONNECTED); } static void mfa_test_helo(struct envelope *e) { struct submit_status ss; ss.id = e->session_id; ss.code = 530; ss.envelope = *e; mfa_session(&ss, S_HELO); } static void mfa_test_mail(struct envelope *e) { struct submit_status ss; ss.id = e->session_id; ss.code = 530; ss.u.maddr = e->sender; if (mfa_strip_source_route(ss.u.maddr.user, sizeof(ss.u.maddr.user))) goto refuse; if (! valid_localpart(ss.u.maddr.user) || ! valid_domainpart(ss.u.maddr.domain)) { /* * "MAIL FROM:<>" is the exception we allow. */ if (!(ss.u.maddr.user[0] == '\0' && ss.u.maddr.domain[0] == '\0')) goto refuse; } mfa_session(&ss, S_MAIL_MFA); return; refuse: imsg_compose_event(env->sc_ievs[PROC_SMTP], IMSG_MFA_MAIL, 0, 0, -1, &ss, sizeof(ss)); return; } static void mfa_test_rcpt(struct envelope *e) { struct submit_status ss; ss.id = e->session_id; ss.code = 530; ss.u.maddr = e->rcpt; ss.ss = e->ss; ss.envelope = *e; ss.envelope.dest = e->rcpt; ss.flags = e->flags; mfa_strip_source_route(ss.u.maddr.user, sizeof(ss.u.maddr.user)); if (! valid_localpart(ss.u.maddr.user) || ! valid_domainpart(ss.u.maddr.domain)) goto refuse; mfa_session(&ss, S_RCPT_MFA); return; refuse: imsg_compose_event(env->sc_ievs[PROC_SMTP], IMSG_MFA_RCPT, 0, 0, -1, &ss, sizeof(ss)); } static void mfa_test_rcpt_resume(struct submit_status *ss) { if (ss->code != 250) { imsg_compose_event(env->sc_ievs[PROC_SMTP], IMSG_MFA_RCPT, 0, 0, -1, ss, sizeof(*ss)); return; } ss->envelope.dest = ss->u.maddr; imsg_compose_event(env->sc_ievs[PROC_LKA], IMSG_LKA_RCPT, 0, 0, -1, ss, sizeof(*ss)); } static void mfa_test_dataline(struct submit_status *ss) { ss->code = 250; mfa_session(ss, S_DATACONTENT); } static void mfa_test_quit(struct envelope *e) { struct submit_status ss; ss.id = e->session_id; ss.code = 530; ss.envelope = *e; mfa_session(&ss, S_QUIT); } static void mfa_test_close(struct envelope *e) { struct submit_status ss; ss.id = e->session_id; ss.code = 530; ss.envelope = *e; mfa_session(&ss, S_CLOSE); } static void mfa_test_rset(struct envelope *e) { struct submit_status ss; ss.id = e->session_id; ss.code = 530; ss.envelope = *e; mfa_session(&ss, S_RSET); } static int mfa_strip_source_route(char *buf, size_t len) { char *p; p = strchr(buf, ':'); if (p != NULL) { p++; memmove(buf, p, strlen(p) + 1); return 1; } return 0; } static int mfa_fork_filter(struct filter *filter) { pid_t pid; int sockpair[2]; if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sockpair) < 0) return 0; session_socket_blockmode(sockpair[0], BM_NONBLOCK); session_socket_blockmode(sockpair[1], BM_NONBLOCK); filter->ibuf = calloc(1, sizeof(struct imsgbuf)); if (filter->ibuf == NULL) goto err; pid = fork(); if (pid == -1) goto err; if (pid == 0) { /* filter */ dup2(sockpair[0], STDIN_FILENO); if (closefrom(STDERR_FILENO + 1) < 0) exit(1); execl(filter->path, filter->name, NULL); exit(1); } /* in parent */ close(sockpair[0]); imsg_init(filter->ibuf, sockpair[1]); return 1; err: free(filter->ibuf); close(sockpair[0]); close(sockpair[1]); return 0; }