diff options
author | Gilles Chehade <gilles@cvs.openbsd.org> | 2011-08-27 22:32:43 +0000 |
---|---|---|
committer | Gilles Chehade <gilles@cvs.openbsd.org> | 2011-08-27 22:32:43 +0000 |
commit | e5d3ea52670446cab24de04bb472a473cf0de2a4 (patch) | |
tree | 9c0b8035e5bd6edf55a7449e16e4bafe381f9148 | |
parent | 1d2663c2aae172f35137c1fc6ca4f60fe6905c2e (diff) |
initial support for a session-time filtering API
currently only HELO/EHLO, MAIL, RCPT are supported, however ... I have
voluntarily disabled filters at smtpd.conf level so people don't play with
it until the API has stabilized a bit
discussed with several people in private, no one opposed the feature
-rw-r--r-- | usr.sbin/smtpd/filter.c | 252 | ||||
-rw-r--r-- | usr.sbin/smtpd/filter.h | 91 | ||||
-rw-r--r-- | usr.sbin/smtpd/lka_session.c | 3 | ||||
-rw-r--r-- | usr.sbin/smtpd/mfa.c | 139 | ||||
-rw-r--r-- | usr.sbin/smtpd/mfa_session.c | 308 | ||||
-rw-r--r-- | usr.sbin/smtpd/parse.y | 76 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtp.c | 7 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtp_session.c | 67 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtpd.c | 65 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtpd.h | 48 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtpd/Makefile | 4 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtpfilter.c | 125 |
12 files changed, 1055 insertions, 130 deletions
diff --git a/usr.sbin/smtpd/filter.c b/usr.sbin/smtpd/filter.c new file mode 100644 index 00000000000..45f6f4fa084 --- /dev/null +++ b/usr.sbin/smtpd/filter.c @@ -0,0 +1,252 @@ +/* $OpenBSD: filter.c,v 1.1 2011/08/27 22:32:41 gilles Exp $ */ + +/* + * Copyright (c) 2011 Gilles Chehade <gilles@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/queue.h> +#include <sys/uio.h> + +#include <err.h> +#include <event.h> +#include <fcntl.h> +#include <imsg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "filter.h" + +static struct filter_internals { + struct event ev; + struct imsgbuf ibuf; + + int (*helo_cb)(u_int64_t, struct filter_helo *, void *); + void *helo_cb_arg; + + int (*ehlo_cb)(u_int64_t, struct filter_helo *, void *); + void *ehlo_cb_arg; + + int (*mail_cb)(u_int64_t, struct filter_mail *, void *); + void *mail_cb_arg; + + int (*rcpt_cb)(u_int64_t, struct filter_rcpt *, void *); + void *rcpt_cb_arg; + + int (*data_cb)(u_int64_t, struct filter_data *, void *); + void *data_cb_arg; + + int (*quit_cb)(u_int64_t, struct filter_quit *, void *); + void *quit_cb_arg; +} fi; + +static void filter_handler(int, short, void *); +static void filter_register_callback(enum filter_type, void *, void *); + +void +filter_init(void) +{ + bzero(&fi, sizeof (fi)); + + imsg_init(&fi.ibuf, 0); + + event_init(); + event_set(&fi.ev, 0, EV_READ, filter_handler, (void *)&fi); + event_add(&fi.ev, NULL); +} + +void +filter_loop(void) +{ + if (event_dispatch() < 0) + errx(1, "event_dispatch"); +} + +void +filter_register_helo_callback(int (*cb)(u_int64_t, struct filter_helo *, void *), void *cb_arg) +{ + filter_register_callback(FILTER_HELO, cb, cb_arg); +} + +void +filter_register_ehlo_callback(int (*cb)(u_int64_t, struct filter_helo *, void *), void *cb_arg) +{ + filter_register_callback(FILTER_EHLO, cb, cb_arg); +} + +void +filter_register_mail_callback(int (*cb)(u_int64_t, struct filter_mail *, void *), void *cb_arg) +{ + filter_register_callback(FILTER_MAIL, cb, cb_arg); +} + +void +filter_register_rcpt_callback(int (*cb)(u_int64_t, struct filter_rcpt *, void *), void *cb_arg) +{ + filter_register_callback(FILTER_RCPT, cb, cb_arg); +} + +void +filter_register_data_callback(int (*cb)(u_int64_t, struct filter_data *, void *), void *cb_arg) +{ + filter_register_callback(FILTER_DATA, cb, cb_arg); +} + +void +filter_register_quit_callback(int (*cb)(u_int64_t, struct filter_quit *, void *), void *cb_arg) +{ + filter_register_callback(FILTER_QUIT, cb, cb_arg); +} + +static void +filter_register_callback(enum filter_type type, void *cb, void *cb_arg) +{ + switch (type) { + case FILTER_HELO: + fi.helo_cb = cb; + fi.helo_cb_arg = cb_arg; + break; + + case FILTER_EHLO: + fi.ehlo_cb = cb; + fi.ehlo_cb_arg = cb_arg; + break; + + case FILTER_MAIL: + fi.mail_cb = cb; + fi.mail_cb_arg = cb_arg; + break; + + case FILTER_RCPT: + fi.rcpt_cb = cb; + fi.rcpt_cb_arg = cb_arg; + break; + + case FILTER_DATA: + fi.data_cb = cb; + fi.data_cb_arg = cb_arg; + break; + + case FILTER_QUIT: + fi.quit_cb = cb; + fi.quit_cb_arg = cb_arg; + break; + } +} + +static void +filter_handler(int fd, short event, void *p) +{ + struct imsg imsg; + ssize_t n; + short evflags = EV_READ; + struct filter_msg fm; + + if (event & EV_READ) { + n = imsg_read(&fi.ibuf); + if (n == -1) + err(1, "imsg_read"); + if (n == 0) { + event_del(&fi.ev); + event_loopexit(NULL); + return; + } + } + + if (event & EV_WRITE) { + if (msgbuf_write(&fi.ibuf.w) == -1) + err(1, "msgbuf_write"); + if (fi.ibuf.w.queued) + evflags |= EV_WRITE; + } + + for (;;) { + n = imsg_get(&fi.ibuf, &imsg); + if (n == -1) + errx(1, "imsg_get"); + if (n == 0) + break; + + if ((imsg.hdr.len - IMSG_HEADER_SIZE) + != sizeof(fm)) + errx(1, "corrupted imsg"); + + memcpy(&fm, imsg.data, sizeof (fm)); + if (fm.version != FILTER_API_VERSION) + errx(1, "API version mismatch"); + + switch (imsg.hdr.type) { + case FILTER_HELO: + if (fi.helo_cb == NULL) + goto ignore; + fm.code = fi.helo_cb(fm.cl_id, &fm.u.helo, + fi.helo_cb_arg); + break; + case FILTER_EHLO: + if (fi.ehlo_cb == NULL) + goto ignore; + fm.code = fi.ehlo_cb(fm.cl_id, &fm.u.helo, + fi.ehlo_cb_arg); + break; + case FILTER_MAIL: + if (fi.mail_cb == NULL) + goto ignore; + fm.code = fi.mail_cb(fm.cl_id, &fm.u.mail, + fi.mail_cb_arg); + break; + case FILTER_RCPT: + if (fi.rcpt_cb == NULL) + goto ignore; + fm.code = fi.rcpt_cb(fm.cl_id, &fm.u.rcpt, + fi.rcpt_cb_arg); + break; + case FILTER_DATA: + if (fi.data_cb == NULL) + goto ignore; + fm.code = fi.data_cb(fm.cl_id, &fm.u.data, + fi.data_cb_arg); + break; + case FILTER_QUIT: + if (fi.quit_cb == NULL) + goto ignore; + fm.code = fi.quit_cb(fm.cl_id, &fm.u.quit, + fi.quit_cb_arg); + break; + default: + errx(1, "unsupported imsg"); + } + + if (! fm.code) + fm.code = -1; + + imsg_compose(&fi.ibuf, imsg.hdr.type, 0, 0, -1, &fm, sizeof fm); + evflags |= EV_WRITE; + imsg_free(&imsg); + } + + event_set(&fi.ev, 0, evflags, filter_handler, &fi); + event_add(&fi.ev, NULL); + return; + +ignore: + imsg_free(&imsg); + fm.code = 0; + imsg_compose(&fi.ibuf, imsg.hdr.type, 0, 0, -1, &fm, sizeof fm); + evflags |= EV_WRITE; + event_set(&fi.ev, 0, evflags, filter_handler, &fi); + event_add(&fi.ev, NULL); +} diff --git a/usr.sbin/smtpd/filter.h b/usr.sbin/smtpd/filter.h new file mode 100644 index 00000000000..bbd5cdc59ab --- /dev/null +++ b/usr.sbin/smtpd/filter.h @@ -0,0 +1,91 @@ +/* $OpenBSD: filter.h,v 1.1 2011/08/27 22:32:41 gilles Exp $ */ + +/* + * Copyright (c) 2011 Gilles Chehade <gilles@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. + */ + + +#define FILTER_API_VERSION 50 + +#if !defined(MAX_LINE_SIZE) +#define MAX_LINE_SIZE 1024 +#endif + +#if !defined(MAX_LOCALPART_SIZE) +#define MAX_LOCALPART_SIZE 128 +#endif + +#if !defined(MAX_DOMAINPART_SIZE) +#define MAX_DOMAINPART_SIZE (MAX_LINE_SIZE-MAX_LOCALPART_SIZE) +#endif + +enum filter_type { + FILTER_HELO, + FILTER_EHLO, + FILTER_MAIL, + FILTER_RCPT, + FILTER_DATA, + FILTER_QUIT, +}; + +struct filter_helo { + char buffer[1024]; +}; + +struct filter_mail { + char user[MAX_LOCALPART_SIZE]; + char domain[MAX_DOMAINPART_SIZE]; +}; + +struct filter_rcpt { + char user[MAX_LOCALPART_SIZE]; + char domain[MAX_DOMAINPART_SIZE]; +}; + +struct filter_data { + char data[4096]; +}; + +struct filter_quit { + char data[4096]; +}; + +union filter_union { + struct filter_helo helo; + struct filter_mail mail; + struct filter_rcpt rcpt; + struct filter_data data; + struct filter_quit quit; +}; + +struct filter_msg { + u_int64_t id; /* set by smtpd(8) */ + u_int64_t cl_id; /* set by smtpd(8) */ + int8_t code; + u_int8_t version; + enum filter_type type; + union filter_union u; +}; + +/**/ +void filter_init(void); +void filter_loop(void); + +void filter_register_helo_callback(int (*)(u_int64_t, struct filter_helo *, void *), void *); +void filter_register_ehlo_callback(int (*)(u_int64_t, struct filter_helo *, void *), void *); +void filter_register_mail_callback(int (*)(u_int64_t, struct filter_mail *, void *), void *); +void filter_register_rcpt_callback(int (*)(u_int64_t, struct filter_rcpt *, void *), void *); +void filter_register_data_callback(int (*)(u_int64_t, struct filter_data *, void *), void *); +void filter_register_quit_callback(int (*)(u_int64_t, struct filter_quit *, void *), void *); diff --git a/usr.sbin/smtpd/lka_session.c b/usr.sbin/smtpd/lka_session.c index 1f7c202040f..a3cd1a0ccd2 100644 --- a/usr.sbin/smtpd/lka_session.c +++ b/usr.sbin/smtpd/lka_session.c @@ -1,4 +1,4 @@ -/* $OpenBSD: lka_session.c,v 1.8 2011/07/04 19:44:31 gilles Exp $ */ +/* $OpenBSD: lka_session.c,v 1.9 2011/08/27 22:32:41 gilles Exp $ */ /* * Copyright (c) 2011 Gilles Chehade <gilles@openbsd.org> @@ -651,5 +651,4 @@ lka_session_cmp(struct lka_session *s1, struct lka_session *s2) return 0; } - SPLAY_GENERATE(lkatree, lka_session, nodes, lka_session_cmp); diff --git a/usr.sbin/smtpd/mfa.c b/usr.sbin/smtpd/mfa.c index a0ddf14cc20..c6696de8bb1 100644 --- a/usr.sbin/smtpd/mfa.c +++ b/usr.sbin/smtpd/mfa.c @@ -1,4 +1,4 @@ -/* $OpenBSD: mfa.c,v 1.59 2011/05/16 21:05:52 gilles Exp $ */ +/* $OpenBSD: mfa.c,v 1.60 2011/08/27 22:32:41 gilles Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> @@ -18,14 +18,17 @@ */ #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 <errno.h> #include <event.h> #include <imsg.h> #include <pwd.h> +#include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -37,20 +40,27 @@ 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_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 int mfa_strip_source_route(char *, size_t); +static int mfa_fork_filter(struct filter *); +void mfa_session(struct submit_status *, enum session_state); 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_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; @@ -74,6 +84,29 @@ mfa_imsg(struct imsgev *iev, struct imsg *imsg) if (iev->proc == PROC_PARENT) { switch (imsg->hdr.type) { + case IMSG_CONF_START: + env->sc_filters = calloc(1, sizeof *env->sc_filters); + if (env->sc_filters == NULL) + fatal(NULL); + TAILQ_INIT(env->sc_filters); + return; + + case IMSG_CONF_FILTER: + filter = calloc(1, sizeof *filter); + if (filter == NULL) + fatal(NULL); + memcpy(filter, (struct filter *)imsg->data, sizeof (*filter)); + TAILQ_INSERT_TAIL(env->sc_filters, filter, f_entry); + return; + + case IMSG_CONF_END: + TAILQ_FOREACH(filter, env->sc_filters, f_entry) { + log_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; @@ -91,6 +124,11 @@ mfa_sig_handler(int sig, short event, void *p) case SIGTERM: mfa_shutdown(); break; + + case SIGCHLD: + fatalx("unexpected SIGCHLD"); + break; + default: fatalx("mfa_sig_handler: unexpected signal"); } @@ -99,6 +137,17 @@ mfa_sig_handler(int sig, short event, void *p) 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("mail filter exiting"); _exit(0); } @@ -112,6 +161,7 @@ mfa(void) struct event ev_sigint; struct event ev_sigterm; + struct event ev_sigchld; struct peer peers[] = { { PROC_PARENT, imsg_dispatch }, @@ -131,13 +181,11 @@ mfa(void) 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; - if (chroot(pw->pw_dir) == -1) - fatal("mfa: chroot"); - if (chdir("/") == -1) - fatal("mfa: chdir(\"/\")"); - smtpd_process = PROC_MFA; setproctitle("%s", env->sc_title[smtpd_process]); @@ -149,10 +197,15 @@ mfa(void) 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); @@ -166,7 +219,20 @@ mfa(void) return (0); } -void +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); + return; +} + +static void mfa_test_mail(struct envelope *e) { struct submit_status ss; @@ -187,18 +253,13 @@ mfa_test_mail(struct envelope *e) goto refuse; } - /* Current policy is to allow all well-formed addresses. */ - goto accept; + 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; - -accept: - ss.code = 250; - imsg_compose_event(env->sc_ievs[PROC_LKA], IMSG_LKA_MAIL, 0, - 0, -1, &ss, sizeof(ss)); } static void @@ -220,10 +281,9 @@ mfa_test_rcpt(struct envelope *e) ! valid_domainpart(ss.u.maddr.domain)) goto refuse; - imsg_compose_event(env->sc_ievs[PROC_LKA], IMSG_LKA_RULEMATCH, 0, 0, -1, - &ss, sizeof(ss)); - + 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)); @@ -257,3 +317,46 @@ mfa_strip_source_route(char *buf, size_t len) 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], 0); + + closefrom(3); + + 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; +} diff --git a/usr.sbin/smtpd/mfa_session.c b/usr.sbin/smtpd/mfa_session.c new file mode 100644 index 00000000000..6b1228e6433 --- /dev/null +++ b/usr.sbin/smtpd/mfa_session.c @@ -0,0 +1,308 @@ +/* $OpenBSD: mfa_session.c,v 1.1 2011/08/27 22:32:41 gilles Exp $ */ + +/* + * Copyright (c) 2011 Gilles Chehade <gilles@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/queue.h> +#include <sys/tree.h> +#include <sys/param.h> +#include <sys/socket.h> +#include <sys/wait.h> + +#include <netinet/in.h> + +#include <ctype.h> +#include <errno.h> +#include <event.h> +#include <imsg.h> +#include <resolv.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "smtpd.h" +#include "log.h" + +void mfa_session(struct submit_status *, enum session_state); +struct mfa_session *mfa_session_init(struct submit_status *, enum session_state); +struct mfa_session *mfa_session_find(u_int64_t); +struct mfa_session *mfa_session_xfind(u_int64_t); +int mfa_session_proceed(struct mfa_session *); +void mfa_session_pickup(struct mfa_session *); +void mfa_session_fail(struct mfa_session *); +void mfa_session_destroy(struct mfa_session *); +void mfa_session_done(struct mfa_session *); + +void mfa_session_imsg(int, short, void *); + +void +mfa_session(struct submit_status *ss, enum session_state state) +{ + struct mfa_session *ms; + + ms = mfa_session_init(ss, state); + if (ms->filter == NULL) { + mfa_session_done(ms); + return; + } + if (! mfa_session_proceed(ms)) + mfa_session_fail(ms); +} + +struct mfa_session * +mfa_session_init(struct submit_status *ss, enum session_state state) +{ + struct mfa_session *ms; + + ms = calloc(1, sizeof(*ms)); + if (ms == NULL) + fatal("mfa_session_init: calloc"); + + ms->id = generate_uid(); + ms->ss = *ss; + ms->ss.code = 250; + ms->state = state; + ms->filter = TAILQ_FIRST(env->sc_filters); + + SPLAY_INSERT(mfatree, &env->mfa_sessions, ms); + + return ms; +} + + +int +mfa_session_proceed(struct mfa_session *ms) +{ + struct filter_msg fm; + + fm.id = ms->id; + fm.cl_id = ms->ss.id; + fm.version = FILTER_API_VERSION; + + switch (ms->state) { + case S_HELO: + fm.type = FILTER_HELO; + if (strlcpy(fm.u.helo.buffer, ms->ss.envelope.delivery.helo, + sizeof(fm.u.helo.buffer)) >= sizeof(fm.u.helo.buffer)) + fatalx("mfa_session_proceed: HELO: truncation"); + break; + + case S_MAIL_MFA: + fm.type = FILTER_MAIL; + if (strlcpy(fm.u.mail.user, ms->ss.u.maddr.user, + sizeof(ms->ss.u.maddr.user)) >= sizeof(ms->ss.u.maddr.user)) + fatalx("mfa_session_proceed: MAIL: user truncation"); + if (strlcpy(fm.u.mail.domain, ms->ss.u.maddr.domain, + sizeof(ms->ss.u.maddr.domain)) >= sizeof(ms->ss.u.maddr.domain)) + fatalx("mfa_session_proceed: MAIL: domain truncation"); + break; + + case S_RCPT_MFA: + fm.type = FILTER_RCPT; + if (strlcpy(fm.u.mail.user, ms->ss.u.maddr.user, + sizeof(ms->ss.u.maddr.user)) >= sizeof(ms->ss.u.maddr.user)) + fatalx("mfa_session_proceed: RCPT: user truncation"); + if (strlcpy(fm.u.mail.domain, ms->ss.u.maddr.domain, + sizeof(ms->ss.u.maddr.domain)) >= sizeof(ms->ss.u.maddr.domain)) + fatalx("mfa_session_proceed: RCPT: domain truncation"); + break; + + default: + fatalx("mfa_session_proceed: no such state"); + } + + imsg_compose(ms->filter->ibuf, fm.type, 0, 0, -1, + &fm, sizeof(fm)); + event_set(&ms->filter->ev, ms->filter->ibuf->fd, EV_READ|EV_WRITE, mfa_session_imsg, ms->filter); + event_add(&ms->filter->ev, NULL); + return 1; +} + +void +mfa_session_pickup(struct mfa_session *ms) +{ + if (ms->fm.code == -1) { + mfa_session_fail(ms); + return; + } + + ms->filter = TAILQ_NEXT(ms->filter, f_entry); + if (ms->filter == NULL) + mfa_session_done(ms); + else + mfa_session_proceed(ms); +} + +void +mfa_session_done(struct mfa_session *ms) +{ + enum imsg_type imsg_type; + + switch (ms->state) { + case S_HELO: + imsg_type = IMSG_MFA_HELO; + break; + case S_MAIL_MFA: + if (ms->ss.code != 530) { + imsg_compose_event(env->sc_ievs[PROC_LKA], IMSG_LKA_MAIL, 0, + 0, -1, &ms->ss, sizeof(ms->ss)); + mfa_session_destroy(ms); + return; + } + imsg_type = IMSG_MFA_MAIL; + break; + case S_RCPT_MFA: + if (ms->ss.code != 530) { + imsg_compose_event(env->sc_ievs[PROC_LKA], IMSG_LKA_RULEMATCH, + 0, 0, -1, &ms->ss, sizeof(ms->ss)); + mfa_session_destroy(ms); + return; + } + imsg_type = IMSG_MFA_RCPT; + break; + default: + fatalx("mfa_session_done: unsupported state"); + } + + imsg_compose_event(env->sc_ievs[PROC_SMTP], imsg_type, 0, 0, + -1, &ms->ss, sizeof(struct submit_status)); + mfa_session_destroy(ms); +} + +struct mfa_session * +mfa_session_find(u_int64_t id) +{ + struct mfa_session key; + + key.id = id; + return SPLAY_FIND(mfatree, &env->mfa_sessions, &key); +} + +struct mfa_session * +mfa_session_xfind(u_int64_t id) +{ + struct mfa_session *ms; + + ms = mfa_session_find(id); + if (ms == NULL) + fatalx("mfa_session_xfind: mfa session missing"); + + return ms; +} + +void +mfa_session_fail(struct mfa_session *ms) +{ + ms->ss.code = 530; + mfa_session_done(ms); +} + +void +mfa_session_destroy(struct mfa_session *ms) +{ + SPLAY_REMOVE(mfatree, &env->mfa_sessions, ms); + free(ms); +} + +void +mfa_session_imsg(int fd, short event, void *p) +{ + struct filter *filter = p; + struct mfa_session *ms; + struct imsg imsg; + ssize_t n; + struct filter_msg fm; + short evflags = EV_READ; + + if (event & EV_READ) { + n = imsg_read(filter->ibuf); + if (n == -1) + fatal("imsg_read"); + if (n == 0) { + event_del(&filter->ev); + event_loopexit(NULL); + return; + } + } + + if (event & EV_WRITE) { + if (msgbuf_write(&filter->ibuf->w) == -1) + fatal("msgbuf_write"); + if (filter->ibuf->w.queued) + evflags |= EV_WRITE; + } + + for (;;) { + n = imsg_get(filter->ibuf, &imsg); + if (n == -1) + fatalx("imsg_get"); + if (n == 0) + break; + + if ((imsg.hdr.len - IMSG_HEADER_SIZE) + != sizeof(fm)) + fatalx("corrupted imsg"); + + memcpy(&fm, imsg.data, sizeof (fm)); + if (fm.version != FILTER_API_VERSION) + fatalx("API version mismatch"); + + switch (imsg.hdr.type) { + case FILTER_HELO: + case FILTER_EHLO: + case FILTER_MAIL: + case FILTER_RCPT: + case FILTER_DATA: + ms = mfa_session_xfind(fm.id); + + /* overwrite filter code */ + ms->fm.code = fm.code; + + /* success, overwrite */ + if (fm.code == 1) + ms->fm = fm; + + mfa_session_pickup(ms); + break; + default: + fatalx("unsupported imsg"); + } + imsg_free(&imsg); + } + event_set(&filter->ev, filter->ibuf->fd, evflags, + mfa_session_imsg, filter); + event_add(&filter->ev, NULL); +} + +int +mfa_session_cmp(struct mfa_session *s1, struct mfa_session *s2) +{ + /* + * do not return u_int64_t's + */ + if (s1->id < s2->id) + return -1; + + if (s1->id > s2->id) + return 1; + + return 0; +} + +SPLAY_GENERATE(mfatree, mfa_session, nodes, mfa_session_cmp); diff --git a/usr.sbin/smtpd/parse.y b/usr.sbin/smtpd/parse.y index 4348e8ca63b..921d8027766 100644 --- a/usr.sbin/smtpd/parse.y +++ b/usr.sbin/smtpd/parse.y @@ -1,4 +1,4 @@ -/* $OpenBSD: parse.y,v 1.76 2011/06/09 17:41:52 gilles Exp $ */ +/* $OpenBSD: parse.y,v 1.77 2011/08/27 22:32:41 gilles Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> @@ -123,7 +123,7 @@ typedef struct { %token DNS DB PLAIN EXTERNAL DOMAIN CONFIG SOURCE %token RELAY VIA DELIVER TO MAILDIR MBOX HOSTNAME %token ACCEPT REJECT INCLUDE NETWORK ERROR MDA FROM FOR -%token ARROW ENABLE AUTH TLS LOCAL VIRTUAL TAG ALIAS +%token ARROW ENABLE AUTH TLS LOCAL VIRTUAL TAG ALIAS FILTER %token <v.string> STRING %token <v.number> NUMBER %type <v.map> map @@ -379,7 +379,39 @@ main : QUEUE INTERVAL interval { YYERROR; } free($2); + }/* + | FILTER STRING STRING { + struct filter *filter; + struct filter *tmp; + + filter = calloc(1, sizeof (*filter)); + if (filter == NULL || + strlcpy(filter->name, $2, sizeof (filter->name)) + >= sizeof (filter->name) || + strlcpy(filter->path, $3, sizeof (filter->path)) + >= sizeof (filter->path)) { + free(filter); + free($2); + free($3); + YYERROR; + } + + TAILQ_FOREACH(tmp, conf->sc_filters, f_entry) { + if (strcasecmp(filter->name, tmp->name) == 0) + break; + } + if (tmp == NULL) + TAILQ_INSERT_TAIL(conf->sc_filters, filter, f_entry); + else { + yyerror("ambiguous filter name: %s", filter->name); + free($2); + free($3); + YYERROR; + } + free($2); + free($3); } + */ ; maptype : SINGLE { map->m_type = T_SINGLE; } @@ -1195,6 +1227,7 @@ lookup(char *s) { "enable", ENABLE }, { "expire", EXPIRE }, { "external", EXTERNAL }, + { "filter", FILTER }, { "for", FOR }, { "from", FROM }, { "hash", HASH }, @@ -1561,34 +1594,26 @@ parse_config(struct smtpd *x_conf, const char *filename, int opts) conf->sc_maxsize = SIZE_MAX; - if ((conf->sc_maps = calloc(1, sizeof(*conf->sc_maps))) == NULL) { - log_warn("cannot allocate memory"); - return (-1); - } - if ((conf->sc_rules = calloc(1, sizeof(*conf->sc_rules))) == NULL) { - log_warn("cannot allocate memory"); - free(conf->sc_maps); - return (-1); - } - if ((conf->sc_listeners = calloc(1, sizeof(*conf->sc_listeners))) == NULL) { - log_warn("cannot allocate memory"); - free(conf->sc_maps); - free(conf->sc_rules); - return (-1); - } - if ((conf->sc_ssl = calloc(1, sizeof(*conf->sc_ssl))) == NULL) { - log_warn("cannot allocate memory"); - free(conf->sc_maps); - free(conf->sc_rules); - free(conf->sc_listeners); - return (-1); - } - if ((m = calloc(1, sizeof(*m))) == NULL) { + conf->sc_maps = calloc(1, sizeof(*conf->sc_maps)); + conf->sc_rules = calloc(1, sizeof(*conf->sc_rules)); + conf->sc_listeners = calloc(1, sizeof(*conf->sc_listeners)); + conf->sc_ssl = calloc(1, sizeof(*conf->sc_ssl)); + conf->sc_filters = calloc(1, sizeof(*conf->sc_filters)); + m = calloc(1, sizeof(*m)); + + if (conf->sc_maps == NULL || + conf->sc_rules == NULL || + conf->sc_listeners == NULL || + conf->sc_ssl == NULL || + conf->sc_filters == NULL || + m == NULL) { log_warn("cannot allocate memory"); free(conf->sc_maps); free(conf->sc_rules); free(conf->sc_listeners); free(conf->sc_ssl); + free(conf->sc_filters); + free(m); return (-1); } @@ -1601,6 +1626,7 @@ parse_config(struct smtpd *x_conf, const char *filename, int opts) TAILQ_INIT(conf->sc_listeners); TAILQ_INIT(conf->sc_maps); TAILQ_INIT(conf->sc_rules); + TAILQ_INIT(conf->sc_filters); SPLAY_INIT(conf->sc_ssl); SPLAY_INIT(&conf->sc_sessions); diff --git a/usr.sbin/smtpd/smtp.c b/usr.sbin/smtpd/smtp.c index b3821026cf6..d3c749f6fb8 100644 --- a/usr.sbin/smtpd/smtp.c +++ b/usr.sbin/smtpd/smtp.c @@ -1,4 +1,4 @@ -/* $OpenBSD: smtp.c,v 1.86 2011/05/16 21:05:52 gilles Exp $ */ +/* $OpenBSD: smtp.c,v 1.87 2011/08/27 22:32:41 gilles Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> @@ -79,9 +79,10 @@ smtp_imsg(struct imsgev *iev, struct imsg *imsg) if (iev->proc == PROC_MFA) { switch (imsg->hdr.type) { - case IMSG_MFA_RCPT: + case IMSG_MFA_HELO: case IMSG_MFA_MAIL: - log_debug("smtp: got imsg_mfa_mail/rcpt"); + case IMSG_MFA_RCPT: + log_debug("smtp: got imsg_mfa_helo/mail/rcpt/quit"); ss = imsg->data; s = session_lookup(ss->id); if (s == NULL) diff --git a/usr.sbin/smtpd/smtp_session.c b/usr.sbin/smtpd/smtp_session.c index ba904a04ea4..4351d98311d 100644 --- a/usr.sbin/smtpd/smtp_session.c +++ b/usr.sbin/smtpd/smtp_session.c @@ -1,4 +1,4 @@ -/* $OpenBSD: smtp_session.c,v 1.142 2011/05/16 21:05:52 gilles Exp $ */ +/* $OpenBSD: smtp_session.c,v 1.143 2011/08/27 22:32:41 gilles Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> @@ -329,12 +329,12 @@ session_rfc5321_helo_handler(struct session *s, char *args) return 1; } + s->s_msg.session_id = s->s_id; s->s_state = S_HELO; s->s_flags &= F_SECURE|F_AUTHENTICATED; - session_respond(s, "250 %s Hello %s [%s], pleased to meet you", - env->sc_hostname, args, ss_to_text(&s->s_ss)); - + session_imsg(s, PROC_MFA, IMSG_MFA_HELO, 0, 0, -1, &s->s_msg, + sizeof(s->s_msg)); return 1; } @@ -352,29 +352,14 @@ session_rfc5321_ehlo_handler(struct session *s, char *args) return 1; } + s->s_msg.session_id = s->s_id; s->s_state = S_HELO; s->s_flags &= F_SECURE|F_AUTHENTICATED; s->s_flags |= F_EHLO; s->s_flags |= F_8BITMIME; - session_respond(s, "250-%s Hello %s [%s], pleased to meet you", - env->sc_hostname, args, ss_to_text(&s->s_ss)); - - /* unconditionnal extensions go first */ - session_respond(s, "250-8BITMIME"); - session_respond(s, "250-ENHANCEDSTATUSCODES"); - - /* XXX - we also want to support reading SIZE from MAIL parameters */ - session_respond(s, "250-SIZE %zu", env->sc_maxsize); - - if (ADVERTISE_TLS(s)) - session_respond(s, "250-STARTTLS"); - - if (ADVERTISE_AUTH(s)) - session_respond(s, "250-AUTH PLAIN LOGIN"); - - session_respond(s, "250 HELP"); - + session_imsg(s, PROC_MFA, IMSG_MFA_HELO, 0, 0, -1, &s->s_msg, + sizeof(s->s_msg)); return 1; } @@ -416,7 +401,6 @@ session_rfc5321_mail_handler(struct session *s, char *args) s->rcptcount = 0; s->s_state = S_MAIL_MFA; - s->s_msg.session_id = s->s_id; s->s_msg.delivery.id = 0; s->s_msg.delivery.ss = s->s_ss; @@ -447,7 +431,6 @@ session_rfc5321_rcpt_handler(struct session *s, char *args) } s->s_state = S_RCPT_MFA; - session_imsg(s, PROC_MFA, IMSG_MFA_RCPT, 0, 0, -1, &s->s_msg, sizeof(s->s_msg)); return 1; @@ -456,10 +439,8 @@ session_rfc5321_rcpt_handler(struct session *s, char *args) static int session_rfc5321_quit_handler(struct session *s, char *args) { - session_respond(s, "221 2.0.0 %s Closing connection", env->sc_hostname); - s->s_flags |= F_QUIT; - + session_respond(s, "221 2.0.0 %s Closing connection", env->sc_hostname); return 1; } @@ -630,6 +611,36 @@ session_pickup(struct session *s, struct submit_status *ss) s->s_state = S_HELO; break; + case S_HELO: + if (ss == NULL) + fatalx("bad ss at S_HELO"); + if (ss->code != 250) { + s->s_state = S_GREETED; + session_respond(s, "%d Helo rejected", ss->code); + return; + } + + session_respond(s, "250%c%s Hello %s [%s], pleased to meet you", + (s->s_flags & F_EHLO) ? '-' : ' ', + env->sc_hostname, s->s_msg.delivery.helo, ss_to_text(&s->s_ss)); + + if (s->s_flags & F_EHLO) { + /* unconditionnal extensions go first */ + session_respond(s, "250-8BITMIME"); + session_respond(s, "250-ENHANCEDSTATUSCODES"); + + /* XXX - we also want to support reading SIZE from MAIL parameters */ + session_respond(s, "250-SIZE %zu", env->sc_maxsize); + + if (ADVERTISE_TLS(s)) + session_respond(s, "250-STARTTLS"); + + if (ADVERTISE_AUTH(s)) + session_respond(s, "250-AUTH PLAIN LOGIN"); + session_respond(s, "250 HELP"); + } + break; + case S_MAIL_MFA: if (ss == NULL) fatalx("bad ss at S_MAIL_MFA"); @@ -839,7 +850,6 @@ session_read_data(struct session *s, char *line) 0, 0, -1, &s->s_msg, sizeof(s->s_msg)); s->s_state = S_DONE; } - return; } @@ -1037,7 +1047,6 @@ session_readline(struct session *s) s->s_flags |= F_QUIT; free(line); free(line2); - return NULL; } diff --git a/usr.sbin/smtpd/smtpd.c b/usr.sbin/smtpd/smtpd.c index f98313ba1c1..5c5412818a7 100644 --- a/usr.sbin/smtpd/smtpd.c +++ b/usr.sbin/smtpd/smtpd.c @@ -1,4 +1,4 @@ -/* $OpenBSD: smtpd.c,v 1.127 2011/08/26 14:39:47 chl Exp $ */ +/* $OpenBSD: smtpd.c,v 1.128 2011/08/27 22:32:41 gilles Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> @@ -84,12 +84,10 @@ int __b64_pton(char const *, unsigned char *, size_t); static void parent_imsg(struct imsgev *iev, struct imsg *imsg) { - struct smtpd newenv, *saveenv; struct forward_req *fwreq; - struct reload *reload; struct auth *auth; struct auth_backend *auth_backend; - int fd, r; + int fd; if (iev->proc == PROC_SMTP) { switch (imsg->hdr.type) { @@ -147,33 +145,6 @@ parent_imsg(struct imsgev *iev, struct imsg *imsg) if (iev->proc == PROC_CONTROL) { switch (imsg->hdr.type) { - case IMSG_CONF_RELOAD: - reload = imsg->data; - reload->ret = 0; - saveenv = env; - env = &newenv; - r = parse_config(&newenv, saveenv->sc_conffile, 0); - env = saveenv; - if (r == 0) { - strlcpy(env->sc_hostname, newenv.sc_hostname, - sizeof env->sc_hostname); - env->sc_listeners = newenv.sc_listeners; - env->sc_maps = newenv.sc_maps; - env->sc_rules = newenv.sc_rules; - env->sc_rules = newenv.sc_rules; - env->sc_ssl = newenv.sc_ssl; - - parent_send_config_client_certs(); - parent_send_config_ruleset(PROC_MFA); - parent_send_config_ruleset(PROC_LKA); - imsg_compose_event(env->sc_ievs[PROC_SMTP], - IMSG_CONF_RELOAD, 0, 0, -1, NULL, 0); - reload->ret = 1; - } - imsg_compose_event(iev, IMSG_CONF_RELOAD, 0, 0, -1, - reload, sizeof *reload); - return; - case IMSG_CTL_VERBOSE: log_verbose(*(int *)imsg->data); @@ -230,6 +201,7 @@ parent_send_config(int fd, short event, void *p) { parent_send_config_listeners(); parent_send_config_client_certs(); + parent_send_config_ruleset(PROC_MFA); parent_send_config_ruleset(PROC_LKA); } @@ -317,25 +289,34 @@ parent_send_config_ruleset(int proc) struct rule *r; struct map *m; struct mapel *mapel; + struct filter *f; log_debug("parent_send_config_ruleset: reloading rules and maps"); imsg_compose_event(env->sc_ievs[proc], IMSG_CONF_START, 0, 0, -1, NULL, 0); - - TAILQ_FOREACH(m, env->sc_maps, m_entry) { - imsg_compose_event(env->sc_ievs[proc], IMSG_CONF_MAP, - 0, 0, -1, m, sizeof(*m)); - TAILQ_FOREACH(mapel, &m->m_contents, me_entry) { + + if (proc == PROC_MFA) { + TAILQ_FOREACH(f, env->sc_filters, f_entry) { + imsg_compose_event(env->sc_ievs[proc], IMSG_CONF_FILTER, + 0, 0, -1, f, sizeof(*f)); + } + } + else { + TAILQ_FOREACH(m, env->sc_maps, m_entry) { + imsg_compose_event(env->sc_ievs[proc], IMSG_CONF_MAP, + 0, 0, -1, m, sizeof(*m)); + TAILQ_FOREACH(mapel, &m->m_contents, me_entry) { imsg_compose_event(env->sc_ievs[proc], IMSG_CONF_MAP_CONTENT, 0, 0, -1, mapel, sizeof(*mapel)); + } } - } - TAILQ_FOREACH(r, env->sc_rules, r_entry) { - imsg_compose_event(env->sc_ievs[proc], IMSG_CONF_RULE, - 0, 0, -1, r, sizeof(*r)); - imsg_compose_event(env->sc_ievs[proc], IMSG_CONF_RULE_SOURCE, - 0, 0, -1, &r->r_sources->m_name, sizeof(r->r_sources->m_name)); + TAILQ_FOREACH(r, env->sc_rules, r_entry) { + imsg_compose_event(env->sc_ievs[proc], IMSG_CONF_RULE, + 0, 0, -1, r, sizeof(*r)); + imsg_compose_event(env->sc_ievs[proc], IMSG_CONF_RULE_SOURCE, + 0, 0, -1, &r->r_sources->m_name, sizeof(r->r_sources->m_name)); + } } imsg_compose_event(env->sc_ievs[proc], IMSG_CONF_END, diff --git a/usr.sbin/smtpd/smtpd.h b/usr.sbin/smtpd/smtpd.h index b0fc7d8bc6a..7e554503a52 100644 --- a/usr.sbin/smtpd/smtpd.h +++ b/usr.sbin/smtpd/smtpd.h @@ -1,4 +1,4 @@ -/* $OpenBSD: smtpd.h,v 1.233 2011/08/17 20:35:11 gilles Exp $ */ +/* $OpenBSD: smtpd.h,v 1.234 2011/08/27 22:32:41 gilles Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> @@ -21,6 +21,8 @@ #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) #endif +#include "filter.h" + #define IMSG_SIZE_CHECK(p) do { \ if (IMSG_DATA_SIZE(&imsg) != sizeof(*p)) \ fatalx("bad length imsg received"); \ @@ -35,13 +37,14 @@ #define MAX_HOPS_COUNT 100 -/* sizes include the tailing '\0' */ -#define MAX_LINE_SIZE 1024 -#define MAX_LOCALPART_SIZE 128 -#define MAX_DOMAINPART_SIZE MAXHOSTNAMELEN #define MAX_TAG_SIZE 32 +/* SYNC WITH filter.h */ +//#define MAX_LINE_SIZE 1024 +//#define MAX_LOCALPART_SIZE 128 +//#define MAX_DOMAINPART_SIZE MAXHOSTNAMELEN /* return and forward path size */ +#define MAX_FILTER_NAME 32 #define MAX_PATH_SIZE 256 #define MAX_RULEBUFFER_LEN 256 @@ -49,6 +52,7 @@ #define SMTPD_QUEUE_MAXINTERVAL (4 * 60 * 60) #define SMTPD_QUEUE_EXPIRY (4 * 24 * 60 * 60) #define SMTPD_USER "_smtpd" +#define SMTPD_FILTER_USER "_smtpdmfa" #define SMTPD_SOCKET "/var/run/smtpd.sock" #define SMTPD_BANNER "220 %s ESMTP OpenSMTPD" #define SMTPD_SESSION_TIMEOUT 300 @@ -130,6 +134,7 @@ enum imsg_type { IMSG_CONF_MAP_CONTENT, IMSG_CONF_RULE, IMSG_CONF_RULE_SOURCE, + IMSG_CONF_FILTER, IMSG_CONF_END, IMSG_CONF_RELOAD, IMSG_LKA_MAIL, @@ -138,8 +143,11 @@ enum imsg_type { IMSG_LKA_RULEMATCH, IMSG_MDA_SESS_NEW, IMSG_MDA_DONE, - IMSG_MFA_RCPT, + + IMSG_MFA_HELO, IMSG_MFA_MAIL, + IMSG_MFA_RCPT, + IMSG_MFA_DISCONNECT, IMSG_QUEUE_CREATE_MESSAGE, IMSG_QUEUE_SUBMIT_ENVELOPE, @@ -337,7 +345,6 @@ struct mailaddr { char domain[MAX_DOMAINPART_SIZE]; }; - enum delivery_type { D_INVALID = 0, D_MDA, @@ -453,7 +460,7 @@ enum child_type { CHILD_INVALID, CHILD_DAEMON, CHILD_MDA, - CHILD_ENQUEUE_OFFLINE + CHILD_ENQUEUE_OFFLINE, }; struct child { @@ -628,6 +635,8 @@ struct smtpd { struct ramqueue sc_rqueue; struct queue_backend *sc_queue; + TAILQ_HEAD(filterlist, filter) *sc_filters; + TAILQ_HEAD(listenerlist, listener) *sc_listeners; TAILQ_HEAD(maplist, map) *sc_maps, *sc_maps_reload; TAILQ_HEAD(rulelist, rule) *sc_rules, *sc_rules_reload; @@ -635,6 +644,7 @@ struct smtpd { SPLAY_HEAD(ssltree, ssl) *sc_ssl; SPLAY_HEAD(childtree, child) children; SPLAY_HEAD(lkatree, lka_session) lka_sessions; + SPLAY_HEAD(mfatree, mfa_session) mfa_sessions; SPLAY_HEAD(mtatree, mta_session) mta_sessions; LIST_HEAD(mdalist, mda_session) mda_sessions; @@ -819,6 +829,25 @@ struct lka_session { struct submit_status ss; }; +struct filter { + TAILQ_ENTRY(filter) f_entry; + pid_t pid; + struct event ev; + struct imsgbuf *ibuf; + char name[MAX_FILTER_NAME]; + char path[MAXPATHLEN]; +}; + +struct mfa_session { + SPLAY_ENTRY(mfa_session) nodes; + u_int64_t id; + + enum session_state state; + struct submit_status ss; + struct filter *filter; + struct filter_msg fm; +}; + enum mta_state { MTA_INVALID_STATE, MTA_INIT, @@ -1046,7 +1075,8 @@ pid_t mda(void); /* mfa.c */ pid_t mfa(void); - +int mfa_session_cmp(struct mfa_session *, struct mfa_session *); +SPLAY_PROTOTYPE(mfatree, mfa_session, nodes, mfa_session_cmp); /* mta.c */ pid_t mta(void); diff --git a/usr.sbin/smtpd/smtpd/Makefile b/usr.sbin/smtpd/smtpd/Makefile index 758e3c00dc8..0c4fab2109f 100644 --- a/usr.sbin/smtpd/smtpd/Makefile +++ b/usr.sbin/smtpd/smtpd/Makefile @@ -1,11 +1,11 @@ -# $OpenBSD: Makefile,v 1.28 2011/05/21 18:43:08 gilles Exp $ +# $OpenBSD: Makefile,v 1.29 2011/08/27 22:32:42 gilles Exp $ PROG= smtpd SRCS= aliases.c auth_backend.c bounce.c client.c \ config.c control.c dns.c expand.c forward.c \ lka.c lka_session.c log.c map.c map_backend.c \ map_backend_db.c map_backend_stdio.c \ - mda.c mfa.c mta.c parse.y \ + mda.c mfa.c mfa_session.c mta.c parse.y \ queue.c queue_shared.c ruleset.c runner.c smtp.c \ smtp_session.c smtpd.c ssl.c ssl_privsep.c util.c asr.c \ print.c pack.c dname.c res_random.c sockaddr.c \ diff --git a/usr.sbin/smtpd/smtpfilter.c b/usr.sbin/smtpd/smtpfilter.c new file mode 100644 index 00000000000..391d47d949e --- /dev/null +++ b/usr.sbin/smtpd/smtpfilter.c @@ -0,0 +1,125 @@ +/* $OpenBSD: smtpfilter.c,v 1.1 2011/08/27 22:32:41 gilles Exp $ */ + +/* + * Copyright (c) 2011 Gilles Chehade <gilles@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 <stdlib.h> + +#include "filter.h" + +int helo_cb(u_int64_t, struct filter_helo *, void *); +int ehlo_cb(u_int64_t, struct filter_helo *, void *); +int mail_cb(u_int64_t, struct filter_mail *, void *); +int rcpt_cb(u_int64_t, struct filter_rcpt *, void *); +int data_cb(u_int64_t, struct filter_data *, void *); + + +/* + * *ALL* of the callbacks follow the same principle: + * + * First parameter is a unique identifier that has been assigned to the + * client session when it connected. The same client session entering a + * second callback will enter it with the same id. + * + * Second parameter is a callback-specific structure filled with all of + * the information necessary to handle filtering at the callback stage. + * Some of the callbacks allow envelope or data rewriting. + * + * Third parameter is a filter-specific argument. It can be NULL, or it + * can be anything the developer wants, typically a structure allocated + * in main() to keep various states. + * A different paramter can be registered for each callback. + * + * RETURN VALUES: + * + * callbacks return 1 if the test succeesd, 0 if it fails. + * once a test fails, the entire envelope is rejected. + * + * + * -- gilles@ + * + */ + +int +helo_cb(u_int64_t id, struct filter_helo *helo, void *mystuff) +{ + return 1; +} + +int +mail_cb(u_int64_t id, struct filter_mail *mail, void *mystuff) +{ + return 1; +} + +int +rcpt_cb(u_int64_t id, struct filter_rcpt *rcpt, void *mystuff) +{ + if (rcpt->user[0] == 'a') + return 0; + return 1; +} + + +/* + * Not all callbacks need to be implemented ! + */ + +int +main(int argc, char *argv[]) +{ + void *mystuff; + + /* + * this MUST be the first think your filter does. + * do not do anything before that call: NOTHING. + */ + filter_init(); + + + /* + * NOW you can allocate your structures, read a configuration + * file or do anything required to prepare your filter before + * it starts handling requests. + */ + mystuff = malloc(42); + + + /* + * we only need to register for callbacks we're interested in + */ + filter_register_helo_callback(helo_cb, mystuff); + filter_register_mail_callback(mail_cb, mystuff); + filter_register_rcpt_callback(rcpt_cb, mystuff); + + /* + * filter_register_ehlo_callback(helo_cb, mystuff); + * filter_register_rcpt_callback(rcpt_cb, mystuff); + * filter_register_data_callback(data_cb, mystuff); + * filter_register_quit_callback(data_cb, mystuff); + */ + + /* + * finally, enter the filter_loop(). + * it will not return unless a critical failure happens. + * do not call exit() from a callback. + */ + filter_loop(); + + return 0; +} |