diff options
author | Gilles Chehade <gilles@poolp.org> | 2018-12-21 23:10:25 +0100 |
---|---|---|
committer | Gilles Chehade <gilles@poolp.org> | 2018-12-21 23:10:25 +0100 |
commit | 179976e04d51e717308219c70b053ee6c3f0f72b (patch) | |
tree | 21d7be8ba6ceeff2b232e1eaa51f51b42970299b | |
parent | Merge pull request #882 from bsdsx/strndup_compat (diff) | |
parent | sync (diff) | |
download | OpenSMTPD-179976e04d51e717308219c70b053ee6c3f0f72b.tar.xz OpenSMTPD-179976e04d51e717308219c70b053ee6c3f0f72b.zip |
Merge branch 'master' into portable
-rw-r--r-- | smtpd/config.c | 16 | ||||
-rw-r--r-- | smtpd/lka.c | 40 | ||||
-rw-r--r-- | smtpd/lka_filter.c | 611 | ||||
-rw-r--r-- | smtpd/lka_proc.c | 47 | ||||
-rw-r--r-- | smtpd/lka_report.c | 184 | ||||
-rw-r--r-- | smtpd/mail.maildir.c | 6 | ||||
-rw-r--r-- | smtpd/mproc.c | 7 | ||||
-rw-r--r-- | smtpd/mta_session.c | 114 | ||||
-rw-r--r-- | smtpd/parse.y | 428 | ||||
-rw-r--r-- | smtpd/ruleset.c | 38 | ||||
-rw-r--r-- | smtpd/smtp_session.c | 162 | ||||
-rw-r--r-- | smtpd/smtpd.conf.5 | 47 | ||||
-rw-r--r-- | smtpd/smtpd.h | 62 | ||||
-rw-r--r-- | smtpd/ssl.c | 4 | ||||
-rw-r--r-- | smtpd/table.c | 10 |
15 files changed, 1382 insertions, 394 deletions
diff --git a/smtpd/config.c b/smtpd/config.c index 64ca6786..c1089104 100644 --- a/smtpd/config.c +++ b/smtpd/config.c @@ -1,4 +1,4 @@ -/* $OpenBSD: config.c,v 1.46 2018/11/30 15:33:40 gilles Exp $ */ +/* $OpenBSD: config.c,v 1.47 2018/12/21 14:33:52 gilles Exp $ */ /* * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> @@ -50,7 +50,6 @@ config_default(void) struct mta_limits *limits = NULL; struct table *t = NULL; char hostname[HOST_NAME_MAX+1]; - uint8_t i; if (getmailname(hostname, sizeof hostname) == -1) return NULL; @@ -92,9 +91,8 @@ config_default(void) conf->sc_limits_dict = calloc(1, sizeof(*conf->sc_limits_dict)); conf->sc_mda_wrappers = calloc(1, sizeof(*conf->sc_mda_wrappers)); conf->sc_processors_dict = calloc(1, sizeof(*conf->sc_processors_dict)); - conf->sc_smtp_reporters_dict = calloc(1, sizeof(*conf->sc_smtp_reporters_dict)); - conf->sc_mta_reporters_dict = calloc(1, sizeof(*conf->sc_mta_reporters_dict)); conf->sc_dispatcher_bounce = calloc(1, sizeof(*conf->sc_dispatcher_bounce)); + conf->sc_filters_dict = calloc(1, sizeof(*conf->sc_filters_dict)); limits = calloc(1, sizeof(*limits)); if (conf->sc_tables_dict == NULL || @@ -107,9 +105,8 @@ config_default(void) conf->sc_limits_dict == NULL || conf->sc_mda_wrappers == NULL || conf->sc_processors_dict == NULL || - conf->sc_smtp_reporters_dict == NULL|| - conf->sc_mta_reporters_dict == NULL || conf->sc_dispatcher_bounce == NULL || + conf->sc_filters_dict == NULL || limits == NULL) goto error; @@ -121,8 +118,6 @@ config_default(void) dict_init(conf->sc_tables_dict); dict_init(conf->sc_limits_dict); dict_init(conf->sc_processors_dict); - dict_init(conf->sc_smtp_reporters_dict); - dict_init(conf->sc_mta_reporters_dict); limit_mta_set_defaults(limits); @@ -131,8 +126,6 @@ config_default(void) TAILQ_INIT(conf->sc_listeners); TAILQ_INIT(conf->sc_rules); - for (i = 0; i < nitems(conf->sc_filter_rules); ++i) - TAILQ_INIT(&conf->sc_filter_rules[i]); /* bounce dispatcher */ conf->sc_dispatcher_bounce->type = DISPATCHER_BOUNCE; @@ -166,8 +159,7 @@ error: free(conf->sc_mda_wrappers); free(conf->sc_processors_dict); free(conf->sc_dispatcher_bounce); - free(conf->sc_smtp_reporters_dict); - free(conf->sc_mta_reporters_dict); + free(conf->sc_filters_dict); free(limits); free(conf); return NULL; diff --git a/smtpd/lka.c b/smtpd/lka.c index 4073350f..519fb1d7 100644 --- a/smtpd/lka.c +++ b/smtpd/lka.c @@ -1,4 +1,4 @@ -/* $OpenBSD: lka.c,v 1.227 2018/12/13 17:08:10 gilles Exp $ */ +/* $OpenBSD: lka.c,v 1.228 2018/12/21 14:33:52 gilles Exp $ */ /* * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> @@ -64,6 +64,10 @@ static int lka_X509_verify(struct ca_vrfy_req_msg *, const char *, const char *) static void lka_certificate_verify(enum imsg_type, struct ca_vrfy_req_msg *); static void lka_certificate_verify_resume(enum imsg_type, struct ca_vrfy_req_msg *); +static void proc_timeout(int fd, short event, void *p); + +struct event ev_proc_ready; + static void lka_imsg(struct mproc *p, struct imsg *imsg) { @@ -93,6 +97,7 @@ lka_imsg(struct mproc *p, struct imsg *imsg) const char *ciphers; const char *address; const char *heloname; + const char *filter_name; struct sockaddr_storage ss_src, ss_dest; int filter_response; int filter_phase; @@ -375,8 +380,11 @@ lka_imsg(struct mproc *p, struct imsg *imsg) NULL) == -1) err(1, "pledge"); - /* Start fulfilling requests */ - mproc_enable(p_pony); + /* setup proc registering task */ + evtimer_set(&ev_proc_ready, proc_timeout, &ev_proc_ready); + tv.tv_sec = 0; + tv.tv_usec = 10; + evtimer_add(&ev_proc_ready, &tv); return; case IMSG_LKA_OPEN_FORWARD: @@ -605,13 +613,14 @@ lka_imsg(struct mproc *p, struct imsg *imsg) case IMSG_FILTER_SMTP_BEGIN: m_msg(&m, imsg); m_get_id(&m, &reqid); + m_get_string(&m, &filter_name); m_get_sockaddr(&m, (struct sockaddr *)&ss_src); m_get_sockaddr(&m, (struct sockaddr *)&ss_dest); m_get_string(&m, &rdns); m_get_int(&m, &fcrdns); m_end(&m); - lka_filter_begin(reqid, &ss_src, &ss_dest, rdns, fcrdns); + lka_filter_begin(reqid, filter_name, &ss_src, &ss_dest, rdns, fcrdns); return; case IMSG_FILTER_SMTP_END: @@ -703,6 +712,9 @@ lka(void) /* Ignore them until we get our config */ mproc_disable(p_pony); + lka_report_init(); + lka_filter_init(); + /* proc & exec will be revoked before serving requests */ if (pledge("stdio rpath inet dns getpw recvfd sendfd proc exec", NULL) == -1) err(1, "pledge"); @@ -713,6 +725,26 @@ lka(void) return (0); } +static void +proc_timeout(int fd, short event, void *p) +{ + struct event *ev = p; + struct timeval tv; + + if (!lka_proc_ready()) + goto reset; + + lka_filter_ready(); + mproc_enable(p_pony); + return; + +reset: + tv.tv_sec = 0; + tv.tv_usec = 10; + evtimer_add(ev, &tv); +} + + static int lka_authenticate(const char *tablename, const char *user, const char *password) { diff --git a/smtpd/lka_filter.c b/smtpd/lka_filter.c index 48288d50..f33962db 100644 --- a/smtpd/lka_filter.c +++ b/smtpd/lka_filter.c @@ -1,4 +1,4 @@ -/* $OpenBSD: lka_filter.c,v 1.14 2018/12/11 13:40:30 gilles Exp $ */ +/* $OpenBSD: lka_filter.c,v 1.21 2018/12/21 20:38:42 gilles Exp $ */ /* * Copyright (c) 2018 Gilles Chehade <gilles@poolp.org> @@ -37,62 +37,256 @@ #include "smtpd.h" #include "log.h" -static void filter_proceed(uint64_t); -static void filter_rewrite(uint64_t, const char *); -static void filter_reject(uint64_t, const char *); -static void filter_disconnect(uint64_t, const char *); +#define PROTOCOL_VERSION 1 + +struct filter; +struct filter_session; +static void filter_protocol(uint64_t, enum filter_phase, const char *); +static void filter_protocol_next(uint64_t, uint64_t, const char *); +static void filter_protocol_query(struct filter *, uint64_t, uint64_t, const char *, const char *); -static void filter_data(uint64_t reqid, const char *line); +static void filter_data(uint64_t, const char *); +static void filter_data_next(uint64_t, uint64_t, const char *); +static void filter_data_query(struct filter *, uint64_t, uint64_t, const char *); -static void filter_write(const char *, uint64_t, const char *, const char *); -static void filter_write_dataline(const char *, uint64_t, const char *); +static int filter_builtins_notimpl(struct filter_session *, struct filter *, uint64_t, const char *); +static int filter_builtins_connect(struct filter_session *, struct filter *, uint64_t, const char *); +static int filter_builtins_helo(struct filter_session *, struct filter *, uint64_t, const char *); +static int filter_builtins_mail_from(struct filter_session *, struct filter *, uint64_t, const char *); +static int filter_builtins_rcpt_to(struct filter_session *, struct filter *, uint64_t, const char *); -static int filter_exec_notimpl(uint64_t, struct filter_rule *, const char *); -static int filter_exec_connected(uint64_t, struct filter_rule *, const char *); -static int filter_exec_helo(uint64_t, struct filter_rule *, const char *); -static int filter_exec_mail_from(uint64_t, struct filter_rule *, const char *); -static int filter_exec_rcpt_to(uint64_t, struct filter_rule *, const char *); +static void filter_result_proceed(uint64_t); +static void filter_result_rewrite(uint64_t, const char *); +static void filter_result_reject(uint64_t, const char *); +static void filter_result_disconnect(uint64_t, const char *); static void filter_session_io(struct io *, int, void *); int lka_filter_process_response(const char *, const char *); -static void filter_data_next(uint64_t, const char *, const char *); - -#define PROTOCOL_VERSION 1 -static struct filter_exec { - enum filter_phase phase; - const char *phase_name; - int (*func)(uint64_t, struct filter_rule *, const char *); -} filter_execs[] = { - { FILTER_AUTH, "auth", filter_exec_notimpl }, - { FILTER_CONNECTED, "connected", filter_exec_connected }, - { FILTER_DATA, "data", filter_exec_notimpl }, - { FILTER_EHLO, "ehlo", filter_exec_helo }, - { FILTER_HELO, "helo", filter_exec_helo }, - { FILTER_STARTTLS, "starttls", filter_exec_notimpl }, - { FILTER_MAIL_FROM, "mail-from", filter_exec_mail_from }, - { FILTER_NOOP, "noop", filter_exec_notimpl }, - { FILTER_QUIT, "quit", filter_exec_notimpl }, - { FILTER_RCPT_TO, "rcpt-to", filter_exec_rcpt_to }, - { FILTER_RSET, "rset", filter_exec_notimpl }, - { FILTER_COMMIT, "commit", filter_exec_notimpl }, -}; - -static struct tree sessions; -static int inited; struct filter_session { uint64_t id; struct io *io; + char *filter_name; struct sockaddr_storage ss_src; struct sockaddr_storage ss_dest; char *rdns; int fcrdns; + + enum filter_phase phase; +}; + +static struct filter_exec { + enum filter_phase phase; + const char *phase_name; + int (*func)(struct filter_session *, struct filter *, uint64_t, const char *); +} filter_execs[FILTER_PHASES_COUNT] = { + { FILTER_CONNECT, "connect", filter_builtins_connect }, + { FILTER_HELO, "helo", filter_builtins_helo }, + { FILTER_EHLO, "ehlo", filter_builtins_helo }, + { FILTER_STARTTLS, "starttls", filter_builtins_notimpl }, + { FILTER_AUTH, "auth", filter_builtins_notimpl }, + { FILTER_MAIL_FROM, "mail-from", filter_builtins_mail_from }, + { FILTER_RCPT_TO, "rcpt-to", filter_builtins_rcpt_to }, + { FILTER_DATA, "data", filter_builtins_notimpl }, + { FILTER_DATA_LINE, "data-line", filter_builtins_notimpl }, + { FILTER_RSET, "rset", filter_builtins_notimpl }, + { FILTER_QUIT, "quit", filter_builtins_notimpl }, + { FILTER_NOOP, "noop", filter_builtins_notimpl }, + { FILTER_HELP, "help", filter_builtins_notimpl }, + { FILTER_WIZ, "wiz", filter_builtins_notimpl }, + { FILTER_COMMIT, "commit", filter_builtins_notimpl }, +}; + +struct filter { + uint64_t id; + uint32_t phases; + const char *name; + const char *proc; + struct filter **chain; + size_t chain_size; + struct filter_config *config; +}; +static struct dict filters; + +struct filter_entry { + TAILQ_ENTRY(filter_entry) entries; + uint64_t id; + const char *name; }; +struct filter_chain { + TAILQ_HEAD(, filter_entry) chain[nitems(filter_execs)]; +}; + +static struct dict smtp_in; + +static struct tree sessions; +static int inited; + +static struct dict filter_chains; + +void +lka_filter_init(void) +{ + void *iter; + const char *name; + struct filter *filter; + struct filter_config *filter_config; + size_t i; + + dict_init(&filters); + dict_init(&filter_chains); + + iter = NULL; + while (dict_iter(env->sc_filters_dict, &iter, &name, (void **)&filter_config)) { + switch (filter_config->filter_type) { + case FILTER_TYPE_BUILTIN: + filter = xcalloc(1, sizeof(*filter)); + filter->name = name; + filter->phases |= (1<<filter_config->phase); + filter->config = filter_config; + dict_set(&filters, name, filter); + break; + + case FILTER_TYPE_PROC: + filter = xcalloc(1, sizeof(*filter)); + filter->name = name; + filter->proc = filter_config->proc; + filter->config = filter_config; + dict_set(&filters, name, filter); + break; + + case FILTER_TYPE_CHAIN: + break; + } + } + + iter = NULL; + while (dict_iter(env->sc_filters_dict, &iter, &name, (void **)&filter_config)) { + switch (filter_config->filter_type) { + case FILTER_TYPE_CHAIN: + filter = xcalloc(1, sizeof(*filter)); + filter->name = name; + filter->chain = xcalloc(filter_config->chain_size, sizeof(void **)); + filter->chain_size = filter_config->chain_size; + filter->config = filter_config; + for (i = 0; i < filter->chain_size; ++i) + filter->chain[i] = dict_xget(&filters, filter_config->chain[i]); + dict_set(&filters, name, filter); + break; + + case FILTER_TYPE_BUILTIN: + case FILTER_TYPE_PROC: + break; + } + } +} + +void +lka_filter_register_hook(const char *name, const char *hook) +{ + struct dict *subsystem; + struct filter *filter; + const char *filter_name; + void *iter; + size_t i; + + if (strncasecmp(hook, "smtp-in|", 8) == 0) { + subsystem = &smtp_in; + hook += 8; + } + else + return; + + for (i = 0; i < nitems(filter_execs); i++) + if (strcmp(hook, filter_execs[i].phase_name) == 0) + break; + if (i == nitems(filter_execs)) + return; + + iter = NULL; + while (dict_iter(&filters, &iter, &filter_name, (void **)&filter)) + if (filter->proc && strcmp(name, filter->proc) == 0) + filter->phases |= (1<<filter_execs[i].phase); +} + +void +lka_filter_ready(void) +{ + struct filter *filter; + struct filter *subfilter; + const char *filter_name; + struct filter_entry *filter_entry; + struct filter_chain *filter_chain; + void *iter; + size_t i; + size_t j; + + iter = NULL; + while (dict_iter(&filters, &iter, &filter_name, (void **)&filter)) { + filter_chain = xcalloc(1, sizeof *filter_chain); + for (i = 0; i < nitems(filter_execs); i++) + TAILQ_INIT(&filter_chain->chain[i]); + dict_set(&filter_chains, filter_name, filter_chain); + + if (filter->chain) { + for (i = 0; i < filter->chain_size; i++) { + subfilter = filter->chain[i]; + for (j = 0; j < nitems(filter_execs); ++j) { + if (subfilter->phases & (1<<j)) { + filter_entry = xcalloc(1, sizeof *filter_entry); + filter_entry->id = generate_uid(); + filter_entry->name = subfilter->name; + TAILQ_INSERT_TAIL(&filter_chain->chain[j], + filter_entry, entries); + } + } + } + continue; + } + + for (i = 0; i < nitems(filter_execs); ++i) { + if (filter->phases & (1<<i)) { + filter_entry = xcalloc(1, sizeof *filter_entry); + filter_entry->id = generate_uid(); + filter_entry->name = filter_name; + TAILQ_INSERT_TAIL(&filter_chain->chain[i], + filter_entry, entries); + } + } + } +} + +int +lka_filter_proc_in_session(uint64_t reqid, const char *proc) +{ + struct filter_session *fs; + struct filter *filter; + size_t i; + + if ((fs = tree_get(&sessions, reqid)) == NULL) + return 0; + + filter = dict_get(&filters, fs->filter_name); + if (filter->proc == NULL && filter->chain == NULL) + return 0; + + if (filter->proc) + return strcmp(filter->proc, proc) == 0 ? 1 : 0; + + for (i = 0; i < filter->chain_size; i++) + if (filter->chain[i]->proc && + strcmp(filter->chain[i]->proc, proc) == 0) + return 1; + + return 0; +} + void lka_filter_begin(uint64_t reqid, + const char *filter_name, const struct sockaddr_storage *ss_src, const struct sockaddr_storage *ss_dest, const char *rdns, @@ -107,6 +301,7 @@ lka_filter_begin(uint64_t reqid, fs = xcalloc(1, sizeof (struct filter_session)); fs->id = reqid; + fs->filter_name = xstrdup(filter_name); fs->ss_src = *ss_src; fs->ss_dest = *ss_dest; fs->rdns = xstrdup(rdns); @@ -135,7 +330,8 @@ lka_filter_data_begin(uint64_t reqid) if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sp) == -1) goto end; - + io_set_nonblocking(sp[0]); + io_set_nonblocking(sp[1]); fd = sp[0]; fs->io = io_new(); io_set_fd(fs->io, sp[1]); @@ -154,8 +350,10 @@ lka_filter_data_end(uint64_t reqid) struct filter_session *fs; fs = tree_xget(&sessions, reqid); - io_free(fs->io); - fs->io = NULL; + if (fs->io) { + io_free(fs->io); + fs->io = NULL; + } } static void @@ -179,6 +377,11 @@ filter_session_io(struct io *io, int evt, void *arg) filter_data(fs->id, line); goto nextline; + + case IO_DISCONNECTED: + io_free(fs->io); + fs->io = NULL; + break; } } @@ -186,6 +389,7 @@ int lka_filter_process_response(const char *name, const char *line) { uint64_t reqid; + uint64_t token; char buffer[LINE_MAX]; char *ep = NULL; char *kind = NULL; @@ -199,9 +403,23 @@ lka_filter_process_response(const char *name, const char *line) *ep = 0; kind = buffer; + if (strcmp(kind, "register") == 0) + return 1; + if (strcmp(kind, "filter-result") != 0 && strcmp(kind, "filter-dataline") != 0) - return 1; + return 0; + + qid = ep+1; + if ((ep = strchr(qid, '|')) == NULL) + return 0; + *ep = 0; + + token = strtoull(qid, &ep, 16); + if (qid[0] == '\0' || *ep != '\0') + return 0; + if (errno == ERANGE && token == ULONG_MAX) + return 0; qid = ep+1; if ((ep = strchr(qid, '|')) == NULL) @@ -221,7 +439,7 @@ lka_filter_process_response(const char *name, const char *line) } if (strcmp(kind, "filter-dataline") == 0) { - filter_data_next(reqid, name, response); + filter_data_next(token, reqid, response); return 1; } @@ -239,72 +457,162 @@ lka_filter_process_response(const char *name, const char *line) parameter == NULL) return 0; - return lka_filter_response(reqid, response, parameter); + if (strcmp(response, "rewrite") == 0) { + filter_result_rewrite(reqid, parameter); + return 1; + } + + if (strcmp(response, "reject") == 0) { + filter_result_reject(reqid, parameter); + return 1; + } + + if (strcmp(response, "disconnect") == 0) { + filter_result_disconnect(reqid, parameter); + return 1; + } + + filter_protocol_next(token, reqid, parameter); + return 1; } void lka_filter_protocol(uint64_t reqid, enum filter_phase phase, const char *param) { - struct filter_rule *rule; - uint8_t i; + filter_protocol(reqid, phase, param); +} + +void +filter_protocol(uint64_t reqid, enum filter_phase phase, const char *param) +{ + struct filter_session *fs; + struct filter_chain *filter_chain; + struct filter_entry *filter_entry; + struct filter *filter; + uint8_t i; + + fs = tree_xget(&sessions, reqid); + filter_chain = dict_get(&filter_chains, fs->filter_name); for (i = 0; i < nitems(filter_execs); ++i) if (phase == filter_execs[i].phase) break; if (i == nitems(filter_execs)) goto proceed; + if (TAILQ_EMPTY(&filter_chain->chain[i])) + goto proceed; - TAILQ_FOREACH(rule, &env->sc_filter_rules[phase], entry) { - if (rule->proc) { - filter_write(rule->proc, reqid, + fs->phase = phase; + TAILQ_FOREACH(filter_entry, &filter_chain->chain[i], entries) { + filter = dict_get(&filters, filter_entry->name); + if (filter->proc) { + filter_protocol_query(filter, filter_entry->id, reqid, filter_execs[i].phase_name, param); - return; /* deferred */ + return; /* deferred */ } - if (filter_execs[i].func(reqid, rule, param)) { - if (rule->rewrite) - filter_rewrite(reqid, rule->rewrite); - else if (rule->disconnect) - filter_disconnect(reqid, rule->disconnect); + if (filter_execs[i].func(fs, filter, reqid, param)) { + if (filter->config->rewrite) + filter_result_rewrite(reqid, filter->config->rewrite); + else if (filter->config->disconnect) + filter_result_disconnect(reqid, filter->config->disconnect); else - filter_reject(reqid, rule->reject); + filter_result_reject(reqid, filter->config->reject); return; } } proceed: - filter_proceed(reqid); + filter_result_proceed(reqid); } static void +filter_protocol_next(uint64_t token, uint64_t reqid, const char *param) +{ + struct filter_session *fs; + struct filter_chain *filter_chain; + struct filter_entry *filter_entry; + struct filter *filter; + + /* client session may have disappeared while we were in proc */ + if ((fs = tree_xget(&sessions, reqid)) == NULL) + return; + + filter_chain = dict_get(&filter_chains, fs->filter_name); + TAILQ_FOREACH(filter_entry, &filter_chain->chain[fs->phase], entries) + if (filter_entry->id == token) + break; + + while ((filter_entry = TAILQ_NEXT(filter_entry, entries))) { + filter = dict_get(&filters, filter_entry->name); + if (filter->proc) { + filter_protocol_query(filter, filter_entry->id, reqid, + filter_execs[fs->phase].phase_name, param); + return; /* deferred */ + } + + if (filter_execs[fs->phase].func(fs, filter, reqid, param)) { + if (filter->config->rewrite) + filter_result_rewrite(reqid, filter->config->rewrite); + else if (filter->config->disconnect) + filter_result_disconnect(reqid, filter->config->disconnect); + else + filter_result_reject(reqid, filter->config->reject); + return; + } + } + + filter_result_proceed(reqid); +} + + +static void filter_data(uint64_t reqid, const char *line) { - struct filter_session *fs; - struct filter_rule *rule; + struct filter_session *fs; + struct filter_chain *filter_chain; + struct filter_entry *filter_entry; + struct filter *filter; fs = tree_xget(&sessions, reqid); - rule = TAILQ_FIRST(&env->sc_filter_rules[FILTER_DATA_LINE]); - filter_write_dataline(rule->proc, reqid, line); + fs->phase = FILTER_DATA_LINE; + filter_chain = dict_get(&filter_chains, fs->filter_name); + filter_entry = TAILQ_FIRST(&filter_chain->chain[fs->phase]); + if (filter_entry == NULL) { + io_printf(fs->io, "%s\r\n", line); + return; + } + + filter = dict_get(&filters, filter_entry->name); + filter_data_query(filter, filter_entry->id, reqid, line); } static void -filter_data_next(uint64_t reqid, const char *name, const char *line) +filter_data_next(uint64_t token, uint64_t reqid, const char *line) { - struct filter_session *fs; - struct filter_rule *rule; + struct filter_session *fs; + struct filter_chain *filter_chain; + struct filter_entry *filter_entry; + struct filter *filter; - fs = tree_xget(&sessions, reqid); + /* client session may have disappeared while we were in proc */ + if ((fs = tree_get(&sessions, reqid)) == NULL) + return; - TAILQ_FOREACH(rule, &env->sc_filter_rules[FILTER_DATA_LINE], entry) { - if (strcmp(rule->proc, name) == 0) - break; + filter_chain = dict_get(&filter_chains, fs->filter_name); + + TAILQ_FOREACH(filter_entry, &filter_chain->chain[fs->phase], entries) + if (filter_entry->id == token) + break; + + if ((filter_entry = TAILQ_NEXT(filter_entry, entries))) { + filter = dict_get(&filters, filter_entry->name); + filter_data_query(filter, filter_entry->id, reqid, line); + return; } - if ((rule = TAILQ_NEXT(rule, entry)) == NULL) - io_printf(fs->io, "%s\r\n", line); - else - filter_write_dataline(rule->proc, reqid, line); + io_printf(fs->io, "%s\r\n", line); } @@ -312,20 +620,20 @@ int lka_filter_response(uint64_t reqid, const char *response, const char *param) { if (strcmp(response, "proceed") == 0) - filter_proceed(reqid); + filter_result_proceed(reqid); else if (strcmp(response, "rewrite") == 0) - filter_rewrite(reqid, param); + filter_result_rewrite(reqid, param); else if (strcmp(response, "reject") == 0) - filter_reject(reqid, param); + filter_result_reject(reqid, param); else if (strcmp(response, "disconnect") == 0) - filter_disconnect(reqid, param); + filter_result_disconnect(reqid, param); else return 0; return 1; } static void -filter_write(const char *name, uint64_t reqid, const char *phase, const char *param) +filter_protocol_query(struct filter *filter, uint64_t token, uint64_t reqid, const char *phase, const char *param) { int n; time_t tm; @@ -333,40 +641,40 @@ filter_write(const char *name, uint64_t reqid, const char *phase, const char *pa fs = tree_xget(&sessions, reqid); time(&tm); - if (strcmp(phase, "connected") == 0) - n = io_printf(lka_proc_get_io(name), - "filter|%d|%zd|smtp-in|%s|%016"PRIx64"|%s|%s\n", + if (strcmp(phase, "connect") == 0) + n = io_printf(lka_proc_get_io(filter->proc), + "filter|%d|%zd|smtp-in|%s|%016"PRIx64"|%016"PRIx64"|%s|%s\n", PROTOCOL_VERSION, tm, - phase, reqid, fs->rdns, param); + phase, token, reqid, fs->rdns, param); else - n = io_printf(lka_proc_get_io(name), - "filter|%d|%zd|smtp-in|%s|%016"PRIx64"|%s\n", + n = io_printf(lka_proc_get_io(filter->proc), + "filter|%d|%zd|smtp-in|%s|%016"PRIx64"|%016"PRIx64"|%s\n", PROTOCOL_VERSION, tm, - phase, reqid, param); + phase, token, reqid, param); if (n == -1) fatalx("failed to write to processor"); } static void -filter_write_dataline(const char *name, uint64_t reqid, const char *line) +filter_data_query(struct filter *filter, uint64_t token, uint64_t reqid, const char *line) { int n; time_t tm; time(&tm); - n = io_printf(lka_proc_get_io(name), + n = io_printf(lka_proc_get_io(filter->proc), "filter|%d|%zd|smtp-in|data-line|" - "%016"PRIx64"|%s\n", + "%016"PRIx64"|%016"PRIx64"|%s\n", PROTOCOL_VERSION, - tm, reqid, line); + tm, token, reqid, line); if (n == -1) fatalx("failed to write to processor"); } static void -filter_proceed(uint64_t reqid) +filter_result_proceed(uint64_t reqid) { m_create(p_pony, IMSG_FILTER_SMTP_PROTOCOL, 0, 0, -1); m_add_id(p_pony, reqid); @@ -375,7 +683,7 @@ filter_proceed(uint64_t reqid) } static void -filter_rewrite(uint64_t reqid, const char *param) +filter_result_rewrite(uint64_t reqid, const char *param) { m_create(p_pony, IMSG_FILTER_SMTP_PROTOCOL, 0, 0, -1); m_add_id(p_pony, reqid); @@ -385,7 +693,7 @@ filter_rewrite(uint64_t reqid, const char *param) } static void -filter_reject(uint64_t reqid, const char *message) +filter_result_reject(uint64_t reqid, const char *message) { m_create(p_pony, IMSG_FILTER_SMTP_PROTOCOL, 0, 0, -1); m_add_id(p_pony, reqid); @@ -395,7 +703,7 @@ filter_reject(uint64_t reqid, const char *message) } static void -filter_disconnect(uint64_t reqid, const char *message) +filter_result_disconnect(uint64_t reqid, const char *message) { m_create(p_pony, IMSG_FILTER_SMTP_PROTOCOL, 0, 0, -1); m_add_id(p_pony, reqid); @@ -408,127 +716,124 @@ filter_disconnect(uint64_t reqid, const char *message) /* below is code for builtin filters */ static int -filter_check_table(struct filter_rule *rule, enum table_service kind, const char *key) +filter_check_rdns_table(struct filter *filter, enum table_service kind, const char *key) +{ + int ret = 0; + + if (filter->config->rdns_table) { + if (table_lookup(filter->config->rdns_table, NULL, key, kind, NULL) > 0) + ret = 1; + ret = filter->config->not_rdns_table < 0 ? !ret : ret; + } + return ret; +} + +static int +filter_check_rdns_regex(struct filter *filter, const char *key) { int ret = 0; - if (rule->table) { - if (table_lookup(rule->table, NULL, key, kind, NULL) > 0) + if (filter->config->rdns_regex) { + if (table_lookup(filter->config->rdns_regex, NULL, key, K_REGEX, NULL) > 0) ret = 1; - ret = rule->not_table < 0 ? !ret : ret; + ret = filter->config->not_rdns_regex < 0 ? !ret : ret; } return ret; } static int -filter_check_regex(struct filter_rule *rule, const char *key) +filter_check_src_table(struct filter *filter, enum table_service kind, const char *key) { int ret = 0; - if (rule->regex) { - if (table_lookup(rule->regex, NULL, key, K_REGEX, NULL) > 0) + if (filter->config->src_table) { + if (table_lookup(filter->config->src_table, NULL, key, kind, NULL) > 0) ret = 1; - ret = rule->not_regex < 0 ? !ret : ret; + ret = filter->config->not_src_table < 0 ? !ret : ret; } return ret; } static int -filter_check_fcrdns(struct filter_rule *rule, int fcrdns) +filter_check_src_regex(struct filter *filter, const char *key) { int ret = 0; - if (rule->fcrdns) { + if (filter->config->src_regex) { + if (table_lookup(filter->config->src_regex, NULL, key, K_REGEX, NULL) > 0) + ret = 1; + ret = filter->config->not_src_regex < 0 ? !ret : ret; + } + return ret; +} + +static int +filter_check_fcrdns(struct filter *filter, int fcrdns) +{ + int ret = 0; + + if (filter->config->fcrdns) { ret = fcrdns == 0; - ret = rule->not_fcrdns < 0 ? !ret : ret; + ret = filter->config->not_fcrdns < 0 ? !ret : ret; } return ret; } static int -filter_check_rdns(struct filter_rule *rule, const char *hostname) +filter_check_rdns(struct filter *filter, const char *hostname) { int ret = 0; struct netaddr netaddr; - if (rule->rdns) { + if (filter->config->rdns) { /* if text_to_netaddress succeeds, * we don't have an rDNS so the filter should match */ ret = text_to_netaddr(&netaddr, hostname); - ret = rule->not_rdns < 0 ? !ret : ret; + ret = filter->config->not_rdns < 0 ? !ret : ret; } return ret; } static int -filter_exec_notimpl(uint64_t reqid, struct filter_rule *rule, const char *param) +filter_builtins_notimpl(struct filter_session *fs, struct filter *filter, uint64_t reqid, const char *param) { return 0; } static int -filter_exec_connected(uint64_t reqid, struct filter_rule *rule, const char *param) +filter_builtins_global(struct filter_session *fs, struct filter *filter, uint64_t reqid, const char *param) { - struct filter_session *fs; - - fs = tree_xget(&sessions, reqid); - if (filter_check_table(rule, K_NETADDR, param) || - filter_check_regex(rule, param) || - filter_check_rdns(rule, fs->rdns) || - filter_check_fcrdns(rule, fs->fcrdns)) + if (filter_check_fcrdns(filter, fs->fcrdns) || + filter_check_rdns(filter, fs->rdns) || + filter_check_rdns_table(filter, K_DOMAIN, fs->rdns) || + filter_check_rdns_regex(filter, fs->rdns) || + filter_check_src_table(filter, K_NETADDR, ss_to_text(&fs->ss_src)) || + filter_check_src_regex(filter, ss_to_text(&fs->ss_src))) return 1; return 0; } static int -filter_exec_helo(uint64_t reqid, struct filter_rule *rule, const char *param) +filter_builtins_connect(struct filter_session *fs, struct filter *filter, uint64_t reqid, const char *param) { - struct filter_session *fs; - - fs = tree_xget(&sessions, reqid); - if (filter_check_table(rule, K_DOMAIN, param) || - filter_check_regex(rule, param) || - filter_check_rdns(rule, fs->rdns) || - filter_check_fcrdns(rule, fs->fcrdns)) - return 1; - return 0; + return filter_builtins_global(fs, filter, reqid, param); } static int -filter_exec_mail_from(uint64_t reqid, struct filter_rule *rule, const char *param) +filter_builtins_helo(struct filter_session *fs, struct filter *filter, uint64_t reqid, const char *param) { - char buffer[SMTPD_MAXMAILADDRSIZE]; - struct filter_session *fs; - - fs = tree_xget(&sessions, reqid); - (void)strlcpy(buffer, param+1, sizeof(buffer)); - buffer[strcspn(buffer, ">")] = '\0'; - param = buffer; - - if (filter_check_table(rule, K_MAILADDR, param) || - filter_check_regex(rule, param) || - filter_check_rdns(rule, fs->rdns) || - filter_check_fcrdns(rule, fs->fcrdns)) - return 1; - return 0; + return filter_builtins_global(fs, filter, reqid, param); } static int -filter_exec_rcpt_to(uint64_t reqid, struct filter_rule *rule, const char *param) +filter_builtins_mail_from(struct filter_session *fs, struct filter *filter, uint64_t reqid, const char *param) { - char buffer[SMTPD_MAXMAILADDRSIZE]; - struct filter_session *fs; + return filter_builtins_global(fs, filter, reqid, param); +} - fs = tree_xget(&sessions, reqid); - (void)strlcpy(buffer, param+1, sizeof(buffer)); - buffer[strcspn(buffer, ">")] = '\0'; - param = buffer; - - if (filter_check_table(rule, K_MAILADDR, param) || - filter_check_regex(rule, param) || - filter_check_rdns(rule, fs->rdns) || - filter_check_fcrdns(rule, fs->fcrdns)) - return 1; - return 0; +static int +filter_builtins_rcpt_to(struct filter_session *fs, struct filter *filter, uint64_t reqid, const char *param) +{ + return filter_builtins_global(fs, filter, reqid, param); } diff --git a/smtpd/lka_proc.c b/smtpd/lka_proc.c index fbf8b855..eafebbec 100644 --- a/smtpd/lka_proc.c +++ b/smtpd/lka_proc.c @@ -1,4 +1,4 @@ -/* $OpenBSD: lka_proc.c,v 1.4 2018/12/06 13:57:06 gilles Exp $ */ +/* $OpenBSD: lka_proc.c,v 1.6 2018/12/21 19:07:47 gilles Exp $ */ /* * Copyright (c) 2018 Gilles Chehade <gilles@poolp.org> @@ -40,15 +40,28 @@ static int inited = 0; static struct dict processors; - struct processor_instance { char *name; struct io *io; + int ready; }; static void processor_io(struct io *, int, void *); int lka_filter_process_response(const char *, const char *); +int +lka_proc_ready(void) +{ + void *iter; + struct processor_instance *pi; + + iter = NULL; + while (dict_iter(&processors, &iter, NULL, (void **)&pi)) + if (!pi->ready) + return 0; + return 1; +} + void lka_proc_forked(const char *name, int fd) { @@ -62,6 +75,9 @@ lka_proc_forked(const char *name, int fd) processor = xcalloc(1, sizeof *processor); processor->name = xstrdup(name); processor->io = io_new(); + + io_set_nonblocking(fd); + io_set_fd(processor->io, fd); io_set_callback(processor->io, processor_io, processor->name); dict_xset(&processors, name, processor); @@ -78,6 +94,29 @@ lka_proc_get_io(const char *name) } static void +processor_register(const char *name, const char *line) +{ + struct processor_instance *processor; + + processor = dict_xget(&processors, name); + + if (strcasecmp(line, "register|ready") == 0) { + processor->ready = 1; + return; + } + + if (strncasecmp(line, "register|report|", 16) == 0) { + lka_report_register_hook(name, line+16); + return; + } + + if (strncasecmp(line, "register|filter|", 16) == 0) { + lka_filter_register_hook(name, line+16); + return; + } +} + +static void processor_io(struct io *io, int evt, void *arg) { const char *name = arg; @@ -92,7 +131,9 @@ processor_io(struct io *io, int evt, void *arg) if (line == NULL) return; - if (! lka_filter_process_response(name, line)) + if (strncasecmp("register|", line, 9) == 0) + processor_register(name, line); + else if (! lka_filter_process_response(name, line)) fatalx("misbehaving filter"); goto nextline; diff --git a/smtpd/lka_report.c b/smtpd/lka_report.c index 3986cbb8..ddf7c7cf 100644 --- a/smtpd/lka_report.c +++ b/smtpd/lka_report.c @@ -1,4 +1,4 @@ -/* $OpenBSD: lka_report.c,v 1.15 2018/12/13 17:08:10 gilles Exp $ */ +/* $OpenBSD: lka_report.c,v 1.16 2018/12/21 14:33:52 gilles Exp $ */ /* * Copyright (c) 2018 Gilles Chehade <gilles@poolp.org> @@ -39,27 +39,127 @@ #define PROTOCOL_VERSION 1 +struct reporter_proc { + TAILQ_ENTRY(reporter_proc) entries; + const char *name; +}; +TAILQ_HEAD(reporters, reporter_proc); + +static struct dict smtp_in; +static struct dict smtp_out; + +static struct smtp_events { + const char *event; +} smtp_events[] = { + { "link-connect" }, + { "link-disconnect" }, + { "link-identify" }, + { "link-tls" }, + + { "tx-begin" }, + { "tx-mail" }, + { "tx-rcpt" }, + { "tx-envelope" }, + { "tx-data" }, + { "tx-commit" }, + { "tx-rollback" }, + + { "protocol-client" }, + { "protocol-server" }, + + { "filter-response" }, +}; + + +void +lka_report_init(void) +{ + struct reporters *tailq; + size_t i; + + dict_init(&smtp_in); + dict_init(&smtp_out); + + for (i = 0; i < nitems(smtp_events); ++i) { + tailq = xcalloc(1, sizeof (struct reporters *)); + TAILQ_INIT(tailq); + dict_xset(&smtp_in, smtp_events[i].event, tailq); + + tailq = xcalloc(1, sizeof (struct reporters *)); + TAILQ_INIT(tailq); + dict_xset(&smtp_out, smtp_events[i].event, tailq); + } +} + +void +lka_report_register_hook(const char *name, const char *hook) +{ + struct dict *subsystem; + struct reporter_proc *rp; + struct reporters *tailq; + void *iter; + size_t i; + + if (strncasecmp(hook, "smtp-in|", 8) == 0) { + subsystem = &smtp_in; + hook += 8; + } + else if (strncasecmp(hook, "smtp-out|", 9) == 0) { + subsystem = &smtp_out; + hook += 9; + } + else + return; + + if (strcmp(hook, "*") == 0) { + iter = NULL; + while (dict_iter(subsystem, &iter, NULL, (void **)&tailq)) { + rp = xcalloc(1, sizeof *rp); + rp->name = xstrdup(name); + TAILQ_INSERT_TAIL(tailq, rp, entries); + } + return; + } + + for (i = 0; i < nitems(smtp_events); i++) + if (strcmp(hook, smtp_events[i].event) == 0) + break; + if (i == nitems(smtp_events)) + return; + + tailq = dict_get(subsystem, hook); + rp = xcalloc(1, sizeof *rp); + rp->name = xstrdup(name); + TAILQ_INSERT_TAIL(tailq, rp, entries); +} + static void -report_smtp_broadcast(const char *direction, struct timeval *tv, const char *format, ...) +report_smtp_broadcast(uint64_t reqid, const char *direction, struct timeval *tv, const char *event, + const char *format, ...) { va_list ap; - void *hdl = NULL; - const char *reporter; struct dict *d; + struct reporters *tailq; + struct reporter_proc *rp; if (strcmp("smtp-in", direction) == 0) - d = env->sc_smtp_reporters_dict; + d = &smtp_in; + if (strcmp("smtp-out", direction) == 0) - d = env->sc_mta_reporters_dict; + d = &smtp_out; + + tailq = dict_xget(d, event); + TAILQ_FOREACH(rp, tailq, entries) { + if (!lka_filter_proc_in_session(reqid, rp->name)) + continue; - va_start(ap, format); - while (dict_iter(d, &hdl, &reporter, NULL)) { - if (io_printf(lka_proc_get_io(reporter), "report|%d|%lld.%06ld|%s|", - PROTOCOL_VERSION, tv->tv_sec, tv->tv_usec, direction) == -1 || - io_vprintf(lka_proc_get_io(reporter), format, ap) == -1) + va_start(ap, format); + if (io_printf(lka_proc_get_io(rp->name), "report|%d|%lld.%06ld|%s|%s|", + PROTOCOL_VERSION, tv->tv_sec, tv->tv_usec, direction, event) == -1 || + io_vprintf(lka_proc_get_io(rp->name), format, ap) == -1) fatalx("failed to write to processor"); + va_end(ap); } - va_end(ap); } void @@ -99,37 +199,37 @@ lka_report_smtp_link_connect(const char *direction, struct timeval *tv, uint64_t break; } - report_smtp_broadcast(direction, tv, - "link-connect|%016"PRIx64"|%s|%s|%s:%d|%s:%d\n", + report_smtp_broadcast(reqid, direction, tv, "link-connect", + "%016"PRIx64"|%s|%s|%s:%d|%s:%d\n", reqid, rdns, fcrdns_str, src, src_port, dest, dest_port); } void lka_report_smtp_link_disconnect(const char *direction, struct timeval *tv, uint64_t reqid) { - report_smtp_broadcast(direction, tv, - "link-disconnect|%016"PRIx64"\n", reqid); + report_smtp_broadcast(reqid, direction, tv, "link-disconnect", + "%016"PRIx64"\n", reqid); } void lka_report_smtp_link_identify(const char *direction, struct timeval *tv, uint64_t reqid, const char *heloname) { - report_smtp_broadcast(direction, tv, - "link-identify|%016"PRIx64"|%s\n", reqid, heloname); + report_smtp_broadcast(reqid, direction, tv, "link-identify", + "%016"PRIx64"|%s\n", reqid, heloname); } void lka_report_smtp_link_tls(const char *direction, struct timeval *tv, uint64_t reqid, const char *ciphers) { - report_smtp_broadcast(direction, tv, - "link-tls|%016"PRIx64"|%s\n", reqid, ciphers); + report_smtp_broadcast(reqid, direction, tv, "link-tls", + "%016"PRIx64"|%s\n", reqid, ciphers); } void lka_report_smtp_tx_begin(const char *direction, struct timeval *tv, uint64_t reqid, uint32_t msgid) { - report_smtp_broadcast(direction, tv, - "tx-begin|%016"PRIx64"|%08x\n", reqid, msgid); + report_smtp_broadcast(reqid, direction, tv, "tx-begin", + "%016"PRIx64"|%08x\n", reqid, msgid); } void @@ -148,8 +248,8 @@ lka_report_smtp_tx_mail(const char *direction, struct timeval *tv, uint64_t reqi result = "tempfail"; break; } - report_smtp_broadcast(direction, tv, - "tx-mail|%016"PRIx64"|%08x|%s|%s\n", reqid, msgid, address, result); + report_smtp_broadcast(reqid, direction, tv, "tx-mail", + "%016"PRIx64"|%08x|%s|%s\n", reqid, msgid, address, result); } void @@ -168,15 +268,15 @@ lka_report_smtp_tx_rcpt(const char *direction, struct timeval *tv, uint64_t reqi result = "tempfail"; break; } - report_smtp_broadcast(direction, tv, - "tx-rcpt|%016"PRIx64"|%08x|%s|%s\n", reqid, msgid, address, result); + report_smtp_broadcast(reqid, direction, tv, "tx-rcpt", + "%016"PRIx64"|%08x|%s|%s\n", reqid, msgid, address, result); } void lka_report_smtp_tx_envelope(const char *direction, struct timeval *tv, uint64_t reqid, uint32_t msgid, uint64_t evpid) { - report_smtp_broadcast(direction, tv, - "tx-envelope|%016"PRIx64"|%08x|%016"PRIx64"\n", + report_smtp_broadcast(reqid, direction, tv, "tx-envelope", + "%016"PRIx64"|%08x|%016"PRIx64"\n", reqid, msgid, evpid); } @@ -196,39 +296,39 @@ lka_report_smtp_tx_data(const char *direction, struct timeval *tv, uint64_t reqi result = "tempfail"; break; } - report_smtp_broadcast(direction, tv, - "tx-data|%016"PRIx64"|%08x|%s\n", reqid, msgid, result); + report_smtp_broadcast(reqid, direction, tv, "tx-data", + "%016"PRIx64"|%08x|%s\n", reqid, msgid, result); } void lka_report_smtp_tx_commit(const char *direction, struct timeval *tv, uint64_t reqid, uint32_t msgid, size_t msgsz) { - report_smtp_broadcast(direction, tv, - "tx-commit|%016"PRIx64"|%08x|%zd\n", + report_smtp_broadcast(reqid, direction, tv, "tx-commit", + "%016"PRIx64"|%08x|%zd\n", reqid, msgid, msgsz); } void lka_report_smtp_tx_rollback(const char *direction, struct timeval *tv, uint64_t reqid, uint32_t msgid) { - report_smtp_broadcast(direction, tv, - "tx-rollback|%016"PRIx64"|%08x\n", + report_smtp_broadcast(reqid, direction, tv, "tx-rollback", + "%016"PRIx64"|%08x\n", reqid, msgid); } void lka_report_smtp_protocol_client(const char *direction, struct timeval *tv, uint64_t reqid, const char *command) { - report_smtp_broadcast(direction, tv, - "protocol-client|%016"PRIx64"|%s\n", + report_smtp_broadcast(reqid, direction, tv, "protocol-client", + "%016"PRIx64"|%s\n", reqid, command); } void lka_report_smtp_protocol_server(const char *direction, struct timeval *tv, uint64_t reqid, const char *response) { - report_smtp_broadcast(direction, tv, - "protocol-server|%016"PRIx64"|%s\n", + report_smtp_broadcast(reqid, direction, tv, "protocol-server", + "%016"PRIx64"|%s\n", reqid, response); } @@ -240,7 +340,7 @@ lka_report_smtp_filter_response(const char *direction, struct timeval *tv, uint6 const char *response_name; switch (phase) { - case FILTER_CONNECTED: + case FILTER_CONNECT: phase_name = "connected"; break; case FILTER_HELO: @@ -306,7 +406,7 @@ lka_report_smtp_filter_response(const char *direction, struct timeval *tv, uint6 response_name = ""; } - report_smtp_broadcast(direction, tv, - "filter-response|%016"PRIx64"|%s|%s|%s\n", - reqid, phase_name, response_name, param ? param : ""); + report_smtp_broadcast(reqid, direction, tv, "filter-response", + "%016"PRIx64"|%s|%s%s%s\n", + reqid, phase_name, response_name, param ? "|" : "", param ? param : ""); } diff --git a/smtpd/mail.maildir.c b/smtpd/mail.maildir.c index a7b2e765..637d3f72 100644 --- a/smtpd/mail.maildir.c +++ b/smtpd/mail.maildir.c @@ -116,6 +116,7 @@ maildir_engine(const char *dirname, int junk) char extpath[PATH_MAX]; char subdir[PATH_MAX]; char filename[PATH_MAX]; + char hostname[HOST_NAME_MAX+1]; char tmp[PATH_MAX]; char new[PATH_MAX]; @@ -167,10 +168,13 @@ maildir_engine(const char *dirname, int junk) } } + if (gethostname(hostname, sizeof hostname) != 0) + (void)strlcpy(hostname, "localhost", sizeof hostname); + (void)snprintf(filename, sizeof filename, "%lld.%08x.%s", (long long int) time(NULL), arc4random(), - "localhost"); + hostname); (void)snprintf(tmp, sizeof tmp, "%s/tmp/%s", dirname, filename); diff --git a/smtpd/mproc.c b/smtpd/mproc.c index 53b2795d..e1bf324f 100644 --- a/smtpd/mproc.c +++ b/smtpd/mproc.c @@ -1,4 +1,4 @@ -/* $OpenBSD: mproc.c,v 1.31 2018/12/13 17:08:10 gilles Exp $ */ +/* $OpenBSD: mproc.c,v 1.32 2018/12/17 08:56:31 eric Exp $ */ /* * Copyright (c) 2012 Eric Faurot <eric@faurot.net> @@ -584,6 +584,11 @@ m_get_data(struct msg *m, const void **data, size_t *sz) { m_get_size(m, sz); + if (*sz == 0) { + *data = NULL; + return; + } + if (m->pos + *sz > m->end) m_error("msg too short"); diff --git a/smtpd/mta_session.c b/smtpd/mta_session.c index b366b37a..655c205f 100644 --- a/smtpd/mta_session.c +++ b/smtpd/mta_session.c @@ -1,4 +1,4 @@ -/* $OpenBSD: mta_session.c,v 1.113 2018/10/31 15:13:21 gilles Exp $ */ +/* $OpenBSD: mta_session.c,v 1.114 2018/12/17 11:14:56 eric Exp $ */ /* * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> @@ -152,6 +152,10 @@ static void mta_response(struct mta_session *, char *); static const char * mta_strstate(int); static void mta_start_tls(struct mta_session *); static int mta_verify_certificate(struct mta_session *); +static void mta_cert_init(struct mta_session *); +static void mta_cert_init_cb(void *, int, const char *, const void *, size_t); +static void mta_cert_verify(struct mta_session *); +static void mta_cert_verify_cb(void *, int); static void mta_tls_verified(struct mta_session *); static struct mta_session *mta_tree_pop(struct tree *, uint64_t); static const char * dsn_strret(enum dsn_ret); @@ -939,7 +943,7 @@ mta_response(struct mta_session *s, char *line) return; } - mta_start_tls(s); + mta_cert_init(s); break; case MTA_AUTH_PLAIN: @@ -1151,7 +1155,7 @@ mta_io(struct io *io, int evt, void *arg) if (s->use_smtps) { io_set_write(io); - mta_start_tls(s); + mta_cert_init(s); } else { mta_enter_state(s, MTA_BANNER); @@ -1164,12 +1168,7 @@ mta_io(struct io *io, int evt, void *arg) s->id, ssl_to_text(io_ssl(s->io))); s->flags |= MTA_TLS; - if (mta_verify_certificate(s)) { - io_pause(s->io, IO_IN); - break; - } - - mta_tls_verified(s); + mta_cert_verify(s); break; case IO_DATAIN: @@ -1658,6 +1657,103 @@ mta_verify_certificate(struct mta_session *s) } static void +mta_cert_init(struct mta_session *s) +{ + const char *name; + int fallback; + + if (s->relay->pki_name) { + name = s->relay->pki_name; + fallback = 0; + } + else { + name = s->helo; + fallback = 1; + } + + if (cert_init(name, fallback, mta_cert_init_cb, s)) { + tree_xset(&wait_ssl_init, s->id, s); + s->flags |= MTA_WAIT; + } +} + +static void +mta_cert_init_cb(void *arg, int status, const char *name, const void *cert, + size_t cert_len) +{ + struct mta_session *s = arg; + void *ssl; + char *xname = NULL, *xcert = NULL; + + if (s->flags & MTA_WAIT) + mta_tree_pop(&wait_ssl_init, s->id); + + if (status == CA_FAIL && s->relay->pki_name) { + log_info("%016"PRIx64" mta closing reason=ca-failure", s->id); + mta_free(s); + return; + } + + if (name) + xname = xstrdup(name); + if (cert) + xcert = xmemdup(cert, cert_len); + ssl = ssl_mta_init(xname, xcert, cert_len, env->sc_tls_ciphers); + free(xname); + free(xcert); + if (ssl == NULL) + fatal("mta: ssl_mta_init"); + io_start_tls(s->io, ssl); +} + +static void +mta_cert_verify(struct mta_session *s) +{ + const char *name; + int fallback; + + if (s->relay->ca_name) { + name = s->relay->ca_name; + fallback = 0; + } + else { + name = s->helo; + fallback = 1; + } + + if (cert_verify(io_ssl(s->io), name, fallback, mta_cert_verify_cb, s)) { + tree_xset(&wait_ssl_verify, s->id, s); + io_pause(s->io, IO_IN); + s->flags |= MTA_WAIT; + } +} + +static void +mta_cert_verify_cb(void *arg, int status) +{ + struct mta_session *s = arg; + int resume = 0; + + if (s->flags & MTA_WAIT) { + mta_tree_pop(&wait_ssl_verify, s->id); + resume = 1; + } + + if (status == CERT_OK) + s->flags |= MTA_TLS_VERIFIED; + else if (s->relay->flags & RELAY_TLS_VERIFY) { + errno = 0; + mta_error(s, "SSL certificate check failed"); + mta_free(s); + return; + } + + mta_tls_verified(s); + if (resume) + io_resume(s->io, IO_IN); +} + +static void mta_tls_verified(struct mta_session *s) { X509 *x; diff --git a/smtpd/parse.y b/smtpd/parse.y index d3c9b427..b778b76f 100644 --- a/smtpd/parse.y +++ b/smtpd/parse.y @@ -1,4 +1,4 @@ -/* $OpenBSD: parse.y,v 1.237 2018/12/13 14:43:31 gilles Exp $ */ +/* $OpenBSD: parse.y,v 1.241 2018/12/21 21:35:29 gilles Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org> @@ -112,7 +112,8 @@ static struct ca *sca; struct dispatcher *dispatcher; struct rule *rule; struct processor *processor; -struct filter_rule *filter_rule; +struct filter_config *filter_config; +static uint64_t last_dynproc_id = 1; enum listen_options { LO_FAMILY = 0x000001, @@ -178,9 +179,9 @@ typedef struct { %} %token ACTION ALIAS ANY ARROW AUTH AUTH_OPTIONAL -%token BACKUP BOUNCE -%token CA CERT CHROOT CIPHERS COMMIT COMPRESSION CONNECT -%token CHECK_FCRDNS CHECK_RDNS CHECK_REGEX CHECK_TABLE +%token BACKUP BOUNCE BUILTIN +%token CA CERT CHAIN CHROOT CIPHERS COMMIT COMPRESSION CONNECT +%token CHECK_FCRDNS CHECK_RDNS CHECK_RDNS_REGEX CHECK_RDNS_TABLE CHECK_SRC_REGEX CHECK_SRC_TABLE %token DATA DATA_LINE DHE DISCONNECT DOMAIN %token EHLO ENABLE ENCRYPTION ERROR EXPAND_ONLY %token FILTER FOR FORWARD_ONLY FROM @@ -193,9 +194,9 @@ typedef struct { %token MAIL_FROM MAILDIR MASK_SRC MASQUERADE MATCH MAX_MESSAGE_SIZE MAX_DEFERRED MBOX MDA MTA MX %token NO_DSN NO_VERIFY NOOP %token ON -%token PKI PORT PROC +%token PKI PORT PROC PROC_EXEC %token QUEUE QUIT -%token RCPT_TO RECIPIENT RECEIVEDAUTH RELAY REJECT REPORT REWRITE RSET +%token RCPT_TO RECIPIENT RECEIVEDAUTH REGEX RELAY REJECT REPORT REWRITE RSET %token SCHEDULER SENDER SENDERS SMTP SMTP_IN SMTP_OUT SMTPS SOCKET SRC SUB_ADDR_DELIM %token TABLE TAG TAGGED TLS TLS_REQUIRE TTL %token USER USERBASE @@ -913,6 +914,25 @@ negation TAG tables { rule->flag_tag = $1 ? -1 : 1; rule->table_tag = strdup(t->t_name); } +| +negation TAG REGEX tables { + struct table *t = $4; + + if (rule->flag_tag) { + yyerror("tag already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_REGEX)) { + yyerror("table \"%s\" may not be used for tag lookups", + t->t_name); + YYERROR; + } + + rule->flag_tag = $1 ? -1 : 1; + rule->flag_tag_regex = 1; + rule->table_tag = strdup(t->t_name); +} | negation HELO tables { struct table *t = $3; @@ -930,6 +950,24 @@ negation TAG tables { rule->flag_smtp_helo = $1 ? -1 : 1; rule->table_smtp_helo = strdup(t->t_name); } +| negation HELO REGEX tables { + struct table *t = $4; + + if (rule->flag_smtp_helo) { + yyerror("mail-helo already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_REGEX)) { + yyerror("table \"%s\" may not be used for helo lookups", + t->t_name); + YYERROR; + } + + rule->flag_smtp_helo = $1 ? -1 : 1; + rule->flag_smtp_helo_regex = 1; + rule->table_smtp_helo = strdup(t->t_name); +} | negation TLS { if (rule->flag_smtp_starttls) { yyerror("tls already specified for this rule"); @@ -961,6 +999,24 @@ negation TAG tables { rule->flag_smtp_auth = $1 ? -1 : 1; rule->table_smtp_auth = strdup(t->t_name); } +| negation AUTH REGEX tables { + struct table *t = $4; + + if (rule->flag_smtp_auth) { + yyerror("auth already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_REGEX)) { + yyerror("table \"%s\" may not be used for auth lookups", + t->t_name); + YYERROR; + } + + rule->flag_smtp_auth = $1 ? -1 : 1; + rule->flag_smtp_auth_regex = 1; + rule->table_smtp_auth = strdup(t->t_name); +} | negation MAIL_FROM tables { struct table *t = $3; @@ -978,6 +1034,24 @@ negation TAG tables { rule->flag_smtp_mail_from = $1 ? -1 : 1; rule->table_smtp_mail_from = strdup(t->t_name); } +| negation MAIL_FROM REGEX tables { + struct table *t = $4; + + if (rule->flag_smtp_mail_from) { + yyerror("mail-from already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_REGEX)) { + yyerror("table \"%s\" may not be used for mail-from lookups", + t->t_name); + YYERROR; + } + + rule->flag_smtp_mail_from = $1 ? -1 : 1; + rule->flag_smtp_mail_from_regex = 1; + rule->table_smtp_mail_from = strdup(t->t_name); +} | negation RCPT_TO tables { struct table *t = $3; @@ -995,6 +1069,24 @@ negation TAG tables { rule->flag_smtp_rcpt_to = $1 ? -1 : 1; rule->table_smtp_rcpt_to = strdup(t->t_name); } +| negation RCPT_TO REGEX tables { + struct table *t = $4; + + if (rule->flag_smtp_rcpt_to) { + yyerror("rcpt-to already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_REGEX)) { + yyerror("table \"%s\" may not be used for rcpt-to lookups", + t->t_name); + YYERROR; + } + + rule->flag_smtp_rcpt_to = $1 ? -1 : 1; + rule->flag_smtp_rcpt_to_regex = 1; + rule->table_smtp_rcpt_to = strdup(t->t_name); +} | negation FROM SOCKET { if (rule->flag_from) { @@ -1041,6 +1133,24 @@ negation TAG tables { rule->flag_from = $1 ? -1 : 1; rule->table_from = strdup(t->t_name); } +| negation FROM SRC REGEX tables { + struct table *t = $5; + + if (rule->flag_from) { + yyerror("from already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_REGEX)) { + yyerror("table \"%s\" may not be used for from lookups", + t->t_name); + YYERROR; + } + + rule->flag_from = $1 ? -1 : 1; + rule->flag_from_regex = 1; + rule->table_from = strdup(t->t_name); +} | negation FOR LOCAL { struct table *t = table_find(conf, "<localnames>", NULL); @@ -1079,6 +1189,24 @@ negation TAG tables { rule->flag_for = $1 ? -1 : 1; rule->table_for = strdup(t->t_name); } +| negation FOR DOMAIN REGEX tables { + struct table *t = $5; + + if (rule->flag_for) { + yyerror("for already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_REGEX)) { + yyerror("table \"%s\" may not be used for 'for' lookups", + t->t_name); + YYERROR; + } + + rule->flag_for = $1 ? -1 : 1; + rule->flag_for_regex = 1; + rule->table_for = strdup(t->t_name); +} ; match_options: @@ -1122,120 +1250,160 @@ MATCH { filter_action_builtin: REJECT STRING { - filter_rule->reject = $2; + filter_config->reject = $2; } | DISCONNECT STRING { - filter_rule->disconnect = $2; + filter_config->disconnect = $2; } ; -filter_phase_check_table: -negation CHECK_TABLE tables { - filter_rule->not_table = $1 ? -1 : 1; - filter_rule->table = $3; +filter_phase_check_fcrdns: +negation CHECK_FCRDNS { + filter_config->not_fcrdns = $1 ? -1 : 1; + filter_config->fcrdns = 1; } ; -filter_phase_check_regex: -negation CHECK_REGEX tables { - filter_rule->not_regex = $1 ? -1 : 1; - filter_rule->regex = $3; +filter_phase_check_rdns: +negation CHECK_RDNS { + filter_config->not_rdns = $1 ? -1 : 1; + filter_config->rdns = 1; } ; -filter_phase_check_fcrdns: -negation CHECK_FCRDNS { - filter_rule->not_fcrdns = $1 ? -1 : 1; - filter_rule->fcrdns = 1; +filter_phase_check_rdns_table: +negation CHECK_RDNS_TABLE tables { + filter_config->not_rdns_table = $1 ? -1 : 1; + filter_config->rdns_table = $3; +} +; +filter_phase_check_rdns_regex: +negation CHECK_RDNS_REGEX tables { + filter_config->not_rdns_regex = $1 ? -1 : 1; + filter_config->rdns_regex = $3; } ; -filter_phase_check_rdns: -negation CHECK_RDNS { - filter_rule->not_rdns = $1 ? -1 : 1; - filter_rule->rdns = 1; +filter_phase_check_src_table: +negation CHECK_SRC_TABLE tables { + filter_config->not_src_table = $1 ? -1 : 1; + filter_config->src_table = $3; +} +; +filter_phase_check_src_regex: +negation CHECK_SRC_REGEX tables { + filter_config->not_src_regex = $1 ? -1 : 1; + filter_config->src_regex = $3; } ; +filter_phase_global_options: +filter_phase_check_fcrdns | +filter_phase_check_rdns | +filter_phase_check_rdns_regex | +filter_phase_check_rdns_table | +filter_phase_check_src_regex | +filter_phase_check_src_table; + filter_phase_connect_options: -filter_phase_check_table | filter_phase_check_regex | filter_phase_check_fcrdns | filter_phase_check_rdns; +filter_phase_global_options; + +filter_phase_helo_options: +filter_phase_global_options; + +filter_phase_mail_from_options: +filter_phase_global_options; + +filter_phase_rcpt_to_options: +filter_phase_global_options; + +filter_phase_data_options: +filter_phase_global_options; + +filter_phase_quit_options: +filter_phase_global_options; + +filter_phase_rset_options: +filter_phase_global_options; + +filter_phase_noop_options: +filter_phase_global_options; + +filter_phase_commit_options: +filter_phase_global_options; + filter_phase_connect: CONNECT { - filter_rule->phase = FILTER_CONNECTED; + filter_config->phase = FILTER_CONNECT; } filter_phase_connect_options filter_action_builtin ; -filter_phase_helo_options: -filter_phase_check_table | filter_phase_check_regex | filter_phase_check_fcrdns | filter_phase_check_rdns; filter_phase_helo: HELO { - filter_rule->phase = FILTER_HELO; + filter_config->phase = FILTER_HELO; } filter_phase_helo_options filter_action_builtin ; filter_phase_ehlo: EHLO { - filter_rule->phase = FILTER_EHLO; + filter_config->phase = FILTER_EHLO; } filter_phase_helo_options filter_action_builtin ; -filter_phase_mail_from_options: -filter_phase_check_table | filter_phase_check_regex | filter_phase_check_fcrdns | filter_phase_check_rdns; - filter_phase_mail_from: MAIL_FROM { - filter_rule->phase = FILTER_MAIL_FROM; + filter_config->phase = FILTER_MAIL_FROM; } filter_phase_mail_from_options filter_action_builtin ; -filter_phase_rcpt_to_options: -filter_phase_check_table | filter_phase_check_regex | filter_phase_check_fcrdns | filter_phase_check_rdns; - filter_phase_rcpt_to: RCPT_TO { - filter_rule->phase = FILTER_RCPT_TO; + filter_config->phase = FILTER_RCPT_TO; } filter_phase_rcpt_to_options filter_action_builtin ; filter_phase_data: DATA { - filter_rule->phase = FILTER_DATA; -} filter_action_builtin + filter_config->phase = FILTER_DATA; +} filter_phase_data_options filter_action_builtin ; +/* filter_phase_data_line: DATA_LINE { - filter_rule->phase = FILTER_DATA_LINE; + filter_config->phase = FILTER_DATA_LINE; } filter_action_builtin ; +*/ filter_phase_quit: QUIT { - filter_rule->phase = FILTER_QUIT; -} filter_action_builtin + filter_config->phase = FILTER_QUIT; +} filter_phase_quit_options filter_action_builtin ; filter_phase_rset: RSET { - filter_rule->phase = FILTER_RSET; -} filter_action_builtin + filter_config->phase = FILTER_RSET; +} filter_phase_rset_options filter_action_builtin ; filter_phase_noop: NOOP { - filter_rule->phase = FILTER_NOOP; -} filter_action_builtin + filter_config->phase = FILTER_NOOP; +} filter_phase_noop_options filter_action_builtin ; filter_phase_commit: COMMIT { - filter_rule->phase = FILTER_COMMIT; -} filter_action_builtin + filter_config->phase = FILTER_COMMIT; +} filter_phase_commit_options filter_action_builtin ; + filter_phase: filter_phase_connect | filter_phase_helo @@ -1243,35 +1411,136 @@ filter_phase_connect | filter_phase_mail_from | filter_phase_rcpt_to | filter_phase_data -| filter_phase_data_line +/*| filter_phase_data_line*/ | filter_phase_quit | filter_phase_noop | filter_phase_rset | filter_phase_commit ; -filter: -FILTER SMTP_IN { - filter_rule = xcalloc(1, sizeof *filter_rule); -} filter_phase { - TAILQ_INSERT_TAIL(&conf->sc_filter_rules[filter_rule->phase], filter_rule, entry); - filter_rule = NULL; + +filterel: +STRING { + struct filter_config *fr; + size_t i; + + if ((fr = dict_get(conf->sc_filters_dict, $1)) == NULL) { + yyerror("no filter exist with that name: %s", $1); + free($1); + YYERROR; + } + if (fr->filter_type == FILTER_TYPE_CHAIN) { + yyerror("no filter chain allowed within a filter chain: %s", $1); + free($1); + YYERROR; + } + + for (i = 0; i < filter_config->chain_size; i++) { + if (strcmp(filter_config->chain[i], $1) == 0) { + yyerror("no filter allowed twice within a filter chain: %s", $1); + free($1); + YYERROR; + } + } + + if (fr->proc) { + if (dict_check(&filter_config->chain_procs, fr->proc)) { + yyerror("no proc allowed twice within a filter chain: %s", fr->proc); + free($1); + YYERROR; + } + dict_set(&filter_config->chain_procs, fr->proc, NULL); + } + + filter_config->chain_size += 1; + filter_config->chain = reallocarray(filter_config->chain, filter_config->chain_size, sizeof(char *)); + if (filter_config->chain == NULL) + err(1, NULL); + filter_config->chain[filter_config->chain_size - 1] = $1; } -| FILTER SMTP_IN ON STRING { +; + +filter_list: +filterel +| filterel comma filter_list +; + +filter: +FILTER STRING PROC STRING { + if (dict_get(conf->sc_filters_dict, $2)) { + yyerror("filter already exists with that name: %s", $2); + free($2); + free($4); + YYERROR; + } if (! dict_get(conf->sc_processors_dict, $4)) { yyerror("no processor exist with that name: %s", $4); free($4); YYERROR; } - dict_set(conf->sc_smtp_reporters_dict, $4, (void *)~0); + + filter_config = xcalloc(1, sizeof *filter_config); + filter_config->filter_type = FILTER_TYPE_PROC; + filter_config->name = $2; + filter_config->proc = $4; + dict_set(conf->sc_filters_dict, $2, filter_config); + filter_config = NULL; } -| FILTER SMTP_OUT ON STRING { - if (! dict_get(conf->sc_processors_dict, $4)) { - yyerror("no processor exist with that name: %s", $4); +| +FILTER STRING PROC_EXEC STRING { + char buffer[128]; + + do { + (void)snprintf(buffer, sizeof buffer, "<dynproc:%016"PRIx64">", last_dynproc_id++); + } while (dict_check(conf->sc_processors_dict, buffer)); + + if (dict_get(conf->sc_filters_dict, $2)) { + yyerror("filter already exists with that name: %s", $2); + free($2); free($4); YYERROR; } - dict_set(conf->sc_mta_reporters_dict, $4, (void *)~0); + + processor = xcalloc(1, sizeof *processor); + processor->command = $4; + + filter_config = xcalloc(1, sizeof *filter_config); + filter_config->filter_type = FILTER_TYPE_PROC; + filter_config->name = $2; + filter_config->proc = xstrdup(buffer); + dict_set(conf->sc_filters_dict, $2, filter_config); +} proc_params { + dict_set(conf->sc_processors_dict, filter_config->proc, processor); + processor = NULL; + filter_config = NULL; +} +| +FILTER STRING BUILTIN { + if (dict_get(conf->sc_filters_dict, $2)) { + yyerror("filter already exists with that name: %s", $2); + free($2); + YYERROR; + } + filter_config = xcalloc(1, sizeof *filter_config); + filter_config->name = $2; + filter_config->filter_type = FILTER_TYPE_BUILTIN; + dict_set(conf->sc_filters_dict, $2, filter_config); +} filter_phase { + filter_config = NULL; +} +| +FILTER STRING CHAIN { + if (dict_get(conf->sc_filters_dict, $2)) { + yyerror("filter already exists with that name: %s", $2); + free($2); + YYERROR; + } + filter_config = xcalloc(1, sizeof *filter_config); + filter_config->filter_type = FILTER_TYPE_CHAIN; + dict_init(&filter_config->chain_procs); +} '{' filter_list '}' { + dict_set(conf->sc_filters_dict, $2, filter_config); + filter_config = NULL; } ; @@ -1415,12 +1684,19 @@ limits_scheduler: opt_limit_scheduler limits_scheduler ; -opt_sock_listen : FILTER { +opt_sock_listen : FILTER STRING { if (listen_opts.options & LO_FILTER) { yyerror("filter already specified"); + free($2); + YYERROR; + } + if (dict_get(conf->sc_filters_dict, $2) == NULL) { + yyerror("no filter exist with that name: %s", $2); + free($2); YYERROR; } listen_opts.options |= LO_FILTER; + listen_opts.filtername = $2; } | MASK_SRC { if (config_lo_mask_source(&listen_opts)) { @@ -1476,12 +1752,18 @@ opt_if_listen : INET4 { } listen_opts.port = $2; } - | FILTER { + | FILTER STRING { if (listen_opts.options & LO_FILTER) { yyerror("filter already specified"); YYERROR; } + if (dict_get(conf->sc_filters_dict, $2) == NULL) { + yyerror("no filter exist with that name: %s", $2); + free($2); + YYERROR; + } listen_opts.options |= LO_FILTER; + listen_opts.filtername = $2; } | SMTPS { if (listen_opts.options & LO_SSL) { @@ -1824,12 +2106,16 @@ lookup(char *s) { "auth-optional", AUTH_OPTIONAL }, { "backup", BACKUP }, { "bounce", BOUNCE }, + { "builtin", BUILTIN }, { "ca", CA }, { "cert", CERT }, + { "chain", CHAIN }, { "check-fcrdns", CHECK_FCRDNS }, { "check-rdns", CHECK_RDNS }, - { "check-regex", CHECK_REGEX }, - { "check-table", CHECK_TABLE }, + { "check-rdns-regex", CHECK_RDNS_REGEX }, + { "check-rdns-table", CHECK_RDNS_TABLE }, + { "check-src-regex", CHECK_SRC_REGEX }, + { "check-src-table", CHECK_SRC_TABLE }, { "chroot", CHROOT }, { "ciphers", CIPHERS }, { "commit", COMMIT }, @@ -1880,11 +2166,13 @@ lookup(char *s) { "pki", PKI }, { "port", PORT }, { "proc", PROC }, + { "proc-exec", PROC_EXEC }, { "queue", QUEUE }, { "quit", QUIT }, { "rcpt-to", RCPT_TO }, { "received-auth", RECEIVEDAUTH }, { "recipient", RECIPIENT }, + { "regex", REGEX }, { "reject", REJECT }, { "relay", RELAY }, { "rset", RSET }, @@ -2462,8 +2750,12 @@ config_listener(struct listener *h, struct listen_opts *lo) if (lo->hostname == NULL) lo->hostname = conf->sc_hostname; - if (lo->options & LO_FILTER) + if (lo->options & LO_FILTER) { h->flags |= F_FILTERED; + (void)strlcpy(h->filter_name, + lo->filtername, + sizeof(h->filter_name)); + } h->pki_name[0] = '\0'; diff --git a/smtpd/ruleset.c b/smtpd/ruleset.c index 1ad5ee36..70a155ef 100644 --- a/smtpd/ruleset.c +++ b/smtpd/ruleset.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ruleset.c,v 1.36 2018/06/16 19:41:26 gilles Exp $ */ +/* $OpenBSD: ruleset.c,v 1.37 2018/12/21 21:35:29 gilles Exp $ */ /* * Copyright (c) 2009 Gilles Chehade <gilles@poolp.org> @@ -57,12 +57,16 @@ ruleset_match_tag(struct rule *r, const struct envelope *evp) { int ret; struct table *table; + enum table_service service = K_STRING; if (!r->flag_tag) return 1; + if (r->flag_tag_regex) + service = K_REGEX; + table = table_find(env, r->table_tag, NULL); - if ((ret = ruleset_match_table_lookup(table, evp->tag, K_STRING)) < 0) + if ((ret = ruleset_match_table_lookup(table, evp->tag, service)) < 0) return ret; return r->flag_tag < 0 ? !ret : ret; @@ -74,6 +78,7 @@ ruleset_match_from(struct rule *r, const struct envelope *evp) int ret; const char *key; struct table *table; + enum table_service service = K_NETADDR; if (!r->flag_from) return 1; @@ -89,8 +94,11 @@ ruleset_match_from(struct rule *r, const struct envelope *evp) else key = ss_to_text(&evp->ss); + if (r->flag_from_regex) + service = K_REGEX; + table = table_find(env, r->table_from, NULL); - if ((ret = ruleset_match_table_lookup(table, key, K_NETADDR)) < 0) + if ((ret = ruleset_match_table_lookup(table, key, service)) < 0) return -1; return r->flag_from < 0 ? !ret : ret; @@ -101,13 +109,17 @@ ruleset_match_to(struct rule *r, const struct envelope *evp) { int ret; struct table *table; + enum table_service service = K_DOMAIN; if (!r->flag_for) return 1; + if (r->flag_for_regex) + service = K_REGEX; + table = table_find(env, r->table_for, NULL); if ((ret = ruleset_match_table_lookup(table, evp->dest.domain, - K_DOMAIN)) < 0) + service)) < 0) return -1; return r->flag_for < 0 ? !ret : ret; @@ -118,12 +130,16 @@ ruleset_match_smtp_helo(struct rule *r, const struct envelope *evp) { int ret; struct table *table; + enum table_service service = K_DOMAIN; if (!r->flag_smtp_helo) return 1; + if (r->flag_smtp_helo_regex) + service = K_REGEX; + table = table_find(env, r->table_smtp_helo, NULL); - if ((ret = ruleset_match_table_lookup(table, evp->helo, K_DOMAIN)) < 0) + if ((ret = ruleset_match_table_lookup(table, evp->helo, service)) < 0) return -1; return r->flag_smtp_helo < 0 ? !ret : ret; @@ -171,15 +187,19 @@ ruleset_match_smtp_mail_from(struct rule *r, const struct envelope *evp) int ret; const char *key; struct table *table; + enum table_service service = K_MAILADDR; if (!r->flag_smtp_mail_from) return 1; + if (r->flag_smtp_mail_from_regex) + service = K_REGEX; + if ((key = mailaddr_to_text(&evp->sender)) == NULL) return -1; table = table_find(env, r->table_smtp_mail_from, NULL); - if ((ret = ruleset_match_table_lookup(table, key, K_MAILADDR)) < 0) + if ((ret = ruleset_match_table_lookup(table, key, service)) < 0) return -1; return r->flag_smtp_mail_from < 0 ? !ret : ret; @@ -191,15 +211,19 @@ ruleset_match_smtp_rcpt_to(struct rule *r, const struct envelope *evp) int ret; const char *key; struct table *table; + enum table_service service = K_MAILADDR; if (!r->flag_smtp_rcpt_to) return 1; + if (r->flag_smtp_rcpt_to_regex) + service = K_REGEX; + if ((key = mailaddr_to_text(&evp->dest)) == NULL) return -1; table = table_find(env, r->table_smtp_rcpt_to, NULL); - if ((ret = ruleset_match_table_lookup(table, key, K_MAILADDR)) < 0) + if ((ret = ruleset_match_table_lookup(table, key, service)) < 0) return -1; return r->flag_smtp_rcpt_to < 0 ? !ret : ret; diff --git a/smtpd/smtp_session.c b/smtpd/smtp_session.c index 4bbd325f..a166d8c9 100644 --- a/smtpd/smtp_session.c +++ b/smtpd/smtp_session.c @@ -1,4 +1,4 @@ -/* $OpenBSD: smtp_session.c,v 1.375 2018/12/14 09:18:03 eric Exp $ */ +/* $OpenBSD: smtp_session.c,v 1.379 2018/12/21 14:41:41 gilles Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org> @@ -108,6 +108,7 @@ enum smtp_command { struct smtp_rcpt { TAILQ_ENTRY(smtp_rcpt) entry; + uint64_t evpid; struct mailaddr maddr; size_t destcount; }; @@ -175,8 +176,7 @@ struct smtp_session { ((s)->listener->flags & F_FILTERED) #define SESSION_DATA_FILTERED(s) \ - (((s)->listener->flags & F_FILTERED) && \ - TAILQ_FIRST(&env->sc_filter_rules[FILTER_DATA_LINE])) + ((s)->listener->flags & F_FILTERED) static int smtp_mailaddr(struct mailaddr *, char *, int, char **, const char *); @@ -839,6 +839,7 @@ smtp_session_imsg(struct mproc *p, struct imsg *imsg) s = tree_xget(&wait_lka_rcpt, reqid); if (success) { m_get_evpid(&m, &evpid); + s->tx->evp.id = evpid; s->tx->destcount++; report_smtp_tx_envelope("smtp-in", s->id, s->tx->msgid, evpid); } @@ -867,6 +868,7 @@ smtp_session_imsg(struct mproc *p, struct imsg *imsg) } else { rcpt = xcalloc(1, sizeof(*rcpt)); + rcpt->evpid = s->tx->evp.id; rcpt->destcount = s->tx->destcount; rcpt->maddr = s->tx->evp.rcpt; TAILQ_INSERT_TAIL(&s->tx->rcpts, rcpt, entry); @@ -897,21 +899,24 @@ smtp_session_imsg(struct mproc *p, struct imsg *imsg) esc_code(ESC_STATUS_OK, ESC_OTHER_STATUS), s->tx->msgid); + log_info("%016"PRIx64" smtp message " + "msgid=%08x size=%zu nrcpt=%zu proto=%s", + s->id, + s->tx->msgid, + s->tx->odatalen, + s->tx->rcptcount, + s->flags & SF_EHLO ? "ESMTP" : "SMTP"); TAILQ_FOREACH(rcpt, &s->tx->rcpts, entry) { - log_info("%016"PRIx64" smtp message address=%s host=%s " - "msgid=%08x from=<%s%s%s> to=<%s%s%s> size=%zu ndest=%zu proto=%s", + log_info("%016"PRIx64" smtp envelope " + "evpid=%016"PRIx64" from=<%s%s%s> to=<%s%s%s>", s->id, - ss_to_text(&s->ss), s->hostname, - s->tx->msgid, + rcpt->evpid, s->tx->evp.sender.user, s->tx->evp.sender.user[0] == '\0' ? "" : "@", s->tx->evp.sender.domain, rcpt->maddr.user, rcpt->maddr.user[0] == '\0' ? "" : "@", - rcpt->maddr.domain, - s->tx->odatalen, - rcpt->destcount, - s->flags & SF_EHLO ? "ESMTP" : "SMTP"); + rcpt->maddr.domain); } smtp_tx_free(s->tx); s->mailcount++; @@ -928,26 +933,26 @@ smtp_session_imsg(struct mproc *p, struct imsg *imsg) strnvis(user, s->username, sizeof user, VIS_WHITE | VIS_SAFE); if (success == LKA_OK) { log_info("%016"PRIx64" smtp " - "authentication user=%s address=%s " - "host=%s result=ok", - s->id, user, ss_to_text(&s->ss), s->hostname); + "authentication user=%s " + "result=ok", + s->id, user); s->flags |= SF_AUTHENTICATED; smtp_reply(s, "235 %s: Authentication succeeded", esc_code(ESC_STATUS_OK, ESC_OTHER_STATUS)); } else if (success == LKA_PERMFAIL) { log_info("%016"PRIx64" smtp " - "authentication user=%s address=%s " - "host=%s result=permfail", - s->id, user, ss_to_text(&s->ss), s->hostname); + "authentication user=%s " + "result=permfail", + s->id, user); smtp_auth_failure_pause(s); return; } else if (success == LKA_TEMPFAIL) { log_info("%016"PRIx64" smtp " - "authentication user=%s address=%s " - "host=%s result=tempfail", - s->id, user, ss_to_text(&s->ss), s->hostname); + "authentication user=%s " + "result=tempfail", + s->id, user); smtp_reply(s, "421 %s: Temporary failure", esc_code(ESC_STATUS_TEMPFAIL, ESC_OTHER_MAIL_SYSTEM_STATUS)); } @@ -962,9 +967,9 @@ smtp_session_imsg(struct mproc *p, struct imsg *imsg) s = tree_xpop(&wait_ssl_init, resp_ca_cert->reqid); if (resp_ca_cert->status == CA_FAIL) { - log_info("%016"PRIx64" smtp disconnected address=%s host=%s " + log_info("%016"PRIx64" smtp disconnected " "reason=ca-failure", - s->id, ss_to_text(&s->ss), s->hostname); + s->id); smtp_free(s, "CA failure"); return; } @@ -989,8 +994,8 @@ smtp_session_imsg(struct mproc *p, struct imsg *imsg) s->flags |= SF_VERIFIED; else if (s->listener->flags & F_TLS_VERIFY) { log_info("%016"PRIx64" smtp " - "disconnected address=%s host=%s reason=cert-check-failed", - s->id, ss_to_text(&s->ss), s->hostname); + "disconnected reason=cert-check-failed", + s->id); smtp_free(s, "SSL certificate check failed"); return; } @@ -1038,7 +1043,7 @@ smtp_session_imsg(struct mproc *p, struct imsg *imsg) report_smtp_filter_response("smtp-in", s->id, s->filter_phase, filter_response, filter_param == s->filter_param ? NULL : filter_param); - if (s->filter_phase == FILTER_CONNECTED) { + if (s->filter_phase == FILTER_CONNECT) { smtp_proceed_connected(s); return; } @@ -1068,8 +1073,8 @@ smtp_tls_verified(struct smtp_session *s) x = SSL_get_peer_certificate(io_ssl(s->io)); if (x) { log_info("%016"PRIx64" smtp " - "client-cert-check address=%s host=%s result=\"%s\"", - s->id, ss_to_text(&s->ss), s->hostname, + "client-cert-check result=\"%s\"", + s->id, (s->flags & SF_VERIFIED) ? "success" : "failure"); X509_free(x); } @@ -1099,8 +1104,8 @@ smtp_io(struct io *io, int evt, void *arg) switch (evt) { case IO_TLSREADY: - log_info("%016"PRIx64" smtp tls address=%s host=%s ciphers=\"%s\"", - s->id, ss_to_text(&s->ss), s->hostname, ssl_to_text(io_ssl(s->io))); + log_info("%016"PRIx64" smtp tls ciphers=%s", + s->id, ssl_to_text(io_ssl(s->io))); report_smtp_link_tls("smtp-in", s->id, ssl_to_text(io_ssl(s->io))); @@ -1170,9 +1175,9 @@ smtp_io(struct io *io, int evt, void *arg) case IO_LOWAT: if (s->state == STATE_QUIT) { - log_info("%016"PRIx64" smtp disconnected address=%s host=%s " + log_info("%016"PRIx64" smtp disconnected " "reason=quit", - s->id, ss_to_text(&s->ss), s->hostname); + s->id); smtp_free(s, "done"); break; } @@ -1187,23 +1192,23 @@ smtp_io(struct io *io, int evt, void *arg) break; case IO_TIMEOUT: - log_info("%016"PRIx64" smtp disconnected address=%s host=%s " + log_info("%016"PRIx64" smtp disconnected " "reason=timeout", - s->id, ss_to_text(&s->ss), s->hostname); + s->id); smtp_free(s, "timeout"); break; case IO_DISCONNECTED: - log_info("%016"PRIx64" smtp disconnected address=%s host=%s " + log_info("%016"PRIx64" smtp disconnected " "reason=disconnect", - s->id, ss_to_text(&s->ss), s->hostname); + s->id); smtp_free(s, "disconnected"); break; case IO_ERROR: - log_info("%016"PRIx64" smtp disconnected address=%s host=%s " + log_info("%016"PRIx64" smtp disconnected " "reason=\"io-error: %s\"", - s->id, ss_to_text(&s->ss), s->hostname, io_error(io)); + s->id, io_error(io)); smtp_free(s, "IO error"); break; @@ -1320,18 +1325,26 @@ smtp_command(struct smtp_session *s, char *line) * ANY */ case CMD_QUIT: + if (!smtp_check_noparam(s, args)) + break; smtp_filter_phase(FILTER_QUIT, s, NULL); break; case CMD_NOOP: + if (!smtp_check_noparam(s, args)) + break; smtp_filter_phase(FILTER_NOOP, s, NULL); break; case CMD_HELP: + if (!smtp_check_noparam(s, args)) + break; smtp_proceed_help(s, NULL); break; case CMD_WIZ: + if (!smtp_check_noparam(s, args)) + break; smtp_proceed_wiz(s, NULL); break; @@ -1346,6 +1359,9 @@ smtp_command(struct smtp_session *s, char *line) static int smtp_check_rset(struct smtp_session *s, const char *args) { + if (!smtp_check_noparam(s, args)) + return 0; + if (s->helo[0] == '\0') { smtp_reply(s, "503 %s %s: Command not allowed at this point.", esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), @@ -1553,6 +1569,9 @@ smtp_check_rcpt_to(struct smtp_session *s, const char *args) static int smtp_check_data(struct smtp_session *s, const char *args) { + if (!smtp_check_noparam(s, args)) + return 0; + if (s->tx == NULL) { smtp_reply(s, "503 %s %s: Command not allowed at this point.", esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), @@ -1573,31 +1592,24 @@ smtp_check_data(struct smtp_session *s, const char *args) static int smtp_check_noparam(struct smtp_session *s, const char *args) { + if (args != NULL) { + smtp_reply(s, "500 %s %s: command does not accept arguments.", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND_ARGUMENTS), + esc_description(ESC_INVALID_COMMAND_ARGUMENTS)); + return 0; + } return 1; } static void smtp_query_filters(enum filter_phase phase, struct smtp_session *s, const char *args) { - uint8_t i; - - if (TAILQ_FIRST(&env->sc_filter_rules[phase])) { - m_create(p_lka, IMSG_FILTER_SMTP_PROTOCOL, 0, 0, -1); - m_add_id(p_lka, s->id); - m_add_int(p_lka, phase); - m_add_string(p_lka, args); - m_close(p_lka); - tree_xset(&wait_filters, s->id, s); - return; - } - - if (phase == FILTER_CONNECTED) { - smtp_proceed_connected(s); - return; - } - for (i = 0; i < nitems(commands); ++i) - if (commands[i].filter_phase == phase) - commands[i].proceed(s, args); + m_create(p_lka, IMSG_FILTER_SMTP_PROTOCOL, 0, 0, -1); + m_add_id(p_lka, s->id); + m_add_int(p_lka, phase); + m_add_string(p_lka, args); + m_close(p_lka); + tree_xset(&wait_filters, s->id, s); } static void @@ -1608,6 +1620,7 @@ smtp_filter_begin(struct smtp_session *s) m_create(p_lka, IMSG_FILTER_SMTP_BEGIN, 0, 0, -1); m_add_id(p_lka, s->id); + m_add_string(p_lka, s->listener->filter_name); m_add_sockaddr(p_lka, (struct sockaddr *)&s->ss); m_add_sockaddr(p_lka, (struct sockaddr *)&s->listener->ss); m_add_string(p_lka, s->hostname); @@ -1668,7 +1681,7 @@ smtp_filter_phase(enum filter_phase phase, struct smtp_session *s, const char *p return; } - if (s->filter_phase == FILTER_CONNECTED) { + if (s->filter_phase == FILTER_CONNECT) { smtp_proceed_connected(s); return; } @@ -1977,11 +1990,12 @@ smtp_connected(struct smtp_session *s) log_info("%016"PRIx64" smtp connected address=%s host=%s", s->id, ss_to_text(&s->ss), s->hostname); + smtp_filter_begin(s); + report_smtp_link_connect("smtp-in", s->id, s->hostname, s->fcrdns, &s->ss, &s->listener->ss); - smtp_filter_begin(s); - smtp_filter_phase(FILTER_CONNECTED, s, ss_to_text(&s->ss)); + smtp_filter_phase(FILTER_CONNECT, s, ss_to_text(&s->ss)); } static void @@ -2057,33 +2071,33 @@ smtp_reply(struct smtp_session *s, char *fmt, ...) if (s->flags & SF_BADINPUT) { log_info("%016"PRIx64" smtp " - "bad-input address=%s host=%s result=\"%.*s\"", - s->id, ss_to_text(&s->ss), s->hostname, n, buf); + "bad-input result=\"%.*s\"", + s->id, n, buf); } else if (s->state == STATE_AUTH_INIT) { log_info("%016"PRIx64" smtp " - "failed-command address=%s host=%s " + "failed-command " "command=\"AUTH PLAIN (...)\" result=\"%.*s\"", - s->id, ss_to_text(&s->ss), s->hostname, n, buf); + s->id, n, buf); } else if (s->state == STATE_AUTH_USERNAME) { log_info("%016"PRIx64" smtp " - "failed-command address=%s host=%s " + "failed-command " "command=\"AUTH LOGIN (username)\" result=\"%.*s\"", - s->id, ss_to_text(&s->ss), s->hostname, n, buf); + s->id, n, buf); } else if (s->state == STATE_AUTH_PASSWORD) { log_info("%016"PRIx64" smtp " - "failed-command address=%s host=%s " + "failed-command " "command=\"AUTH LOGIN (password)\" result=\"%.*s\"", - s->id, ss_to_text(&s->ss), s->hostname, n, buf); + s->id, n, buf); } else { strnvis(tmp, s->cmd, sizeof tmp, VIS_SAFE | VIS_CSTYLE); log_info("%016"PRIx64" smtp " - "failed-command address=%s host=%s command=\"%s\" " + "failed-command command=\"%s\" " "result=\"%.*s\"", - s->id, ss_to_text(&s->ss), s->hostname, tmp, n, buf); + s->id, tmp, n, buf); } break; } @@ -2339,9 +2353,9 @@ smtp_cert_init_cb(void *arg, int status, const char *name, const void *cert, tree_pop(&wait_ssl_init, s->id); if (status == CA_FAIL) { - log_info("%016"PRIx64" smtp disconnected address=%s host=%s " + log_info("%016"PRIx64" smtp disconnected " "reason=ca-failure", - s->id, ss_to_text(&s->ss), s->hostname); + s->id); smtp_free(s, "CA failure"); return; } @@ -2404,8 +2418,8 @@ smtp_cert_verify_cb(void *arg, int status) log_debug("smtp: %p: smtp_cert_verify_cb: %s", s, reason); if (!(s->flags & SF_VERIFIED) && (s->listener->flags & F_TLS_VERIFY)) { - log_info("%016"PRIx64" smtp disconnected address=%s host=%s " - " reason=%s", s->id, ss_to_text(&s->ss), s->hostname, + log_info("%016"PRIx64" smtp disconnected " + " reason=%s", s->id, reason); smtp_free(s, "SSL certificate check failed"); return; @@ -2869,7 +2883,7 @@ filter_session_io(struct io *io, int evt, void *arg) char*line = NULL; ssize_t len; - log_trace(TRACE_IO, "filter session: %p: %s %s", tx, io_strevent(evt), + log_trace(TRACE_IO, "filter session io (smtp): %p: %s %s", tx, io_strevent(evt), io_strio(io)); switch (evt) { diff --git a/smtpd/smtpd.conf.5 b/smtpd/smtpd.conf.5 index cb776224..8736d3db 100644 --- a/smtpd/smtpd.conf.5 +++ b/smtpd/smtpd.conf.5 @@ -1,4 +1,4 @@ -.\" $OpenBSD: smtpd.conf.5,v 1.207 2018/12/12 20:21:04 jmc Exp $ +.\" $OpenBSD: smtpd.conf.5,v 1.208 2018/12/21 21:35:29 gilles Exp $ .\" .\" Copyright (c) 2008 Janne Johansson <jj@openbsd.org> .\" Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> @@ -17,7 +17,7 @@ .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" .\" -.Dd $Mdocdate: December 12 2018 $ +.Dd $Mdocdate: December 21 2018 $ .Dt SMTPD.CONF 5 .Os .Sh NAME @@ -485,6 +485,13 @@ Specify that session may address the string or list table .Ar domain . .It Xo .Op Ic \&! +.Cm for domain regex +.Ar domain | Pf < Ar domain Ns > +.Xc +Specify that session may address the regex or regex table +.Ar domain . +.It Xo +.Op Ic \&! .Cm from any .Xc Specify that session may originate from any source. @@ -508,6 +515,14 @@ Specify that session may only originate from the local enqueuer. Specify that session may only originate from string or list table .Ar address which can be a specific address or a subnet expressed in CIDR-notation. +.It Xo +.Op Ic \&! +.Cm from src regex +.Ar address | Pf < Ar address Ns > +.Xc +Specify that session may only originate from regex or regex table +.Ar address +which can be a specific address or a subnet expressed in CIDR-notation. .El .Pp In addition, the following transaction options: @@ -526,6 +541,13 @@ Specify that session's HELO / EHLO should match the string or list table .Ar helo-name . .It Xo .Op Ic \&! +.Cm helo regex +.Ar helo-name | Pf < Ar helo-name Ns > +.Xc +Specify that session's HELO / EHLO should match the regex or regex table +.Ar helo-name . +.It Xo +.Op Ic \&! .Cm mail\-from .Ar sender | Pf < Ar sender Ns > .Xc @@ -533,6 +555,13 @@ Specify that transactions's MAIL FROM should match the string or list table .Ar sender . .It Xo .Op Ic \&! +.Cm mail\-from regex +.Ar sender | Pf < Ar sender Ns > +.Xc +Specify that transactions's MAIL FROM should match the regex or regex table +.Ar sender . +.It Xo +.Op Ic \&! .Cm rcpt\-to .Ar recipient | Pf < Ar recipient Ns > .Xc @@ -540,12 +569,26 @@ Specify that transaction's RCPT TO should match the string or list table .Ar recipient . .It Xo .Op Ic \&! +.Cm rcpt\-to regex +.Ar recipient | Pf < Ar recipient Ns > +.Xc +Specify that transaction's RCPT TO should match the regex or regex table +.Ar recipient . +.It Xo +.Op Ic \&! .Cm tag Ar tag .Xc Matches transactions tagged with the given .Ar tag . .It Xo .Op Ic \&! +.Cm tag regex Ar tag +.Xc +Matches transactions tagged with the given +.Ar tag +regex . +.It Xo +.Op Ic \&! .Cm tls .Xc Specify that transaction should take place in a TLS channel. diff --git a/smtpd/smtpd.h b/smtpd/smtpd.h index 18eba010..fafe7194 100644 --- a/smtpd/smtpd.h +++ b/smtpd/smtpd.h @@ -1,4 +1,4 @@ -/* $OpenBSD: smtpd.h,v 1.594 2018/12/13 17:08:10 gilles Exp $ */ +/* $OpenBSD: smtpd.h,v 1.597 2018/12/21 21:35:29 gilles Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org> @@ -436,7 +436,7 @@ enum expand_type { }; enum filter_phase { - FILTER_CONNECTED = 0, + FILTER_CONNECT, FILTER_HELO, FILTER_EHLO, FILTER_STARTTLS, @@ -552,6 +552,7 @@ struct listener { in_port_t port; struct timeval timeout; struct event ev; + char filter_name[PATH_MAX]; char pki_name[PATH_MAX]; char ca_name[PATH_MAX]; char tag[SMTPD_TAG_SIZE]; @@ -608,8 +609,6 @@ struct smtpd { size_t sc_scheduler_max_schedule; struct dict *sc_processors_dict; - struct dict *sc_smtp_reporters_dict; - struct dict *sc_mta_reporters_dict; int sc_ttl; #define MAX_BOUNCE_WARN 4 @@ -626,8 +625,9 @@ struct smtpd { TAILQ_HEAD(listenerlist, listener) *sc_listeners; TAILQ_HEAD(rulelist, rule) *sc_rules; - TAILQ_HEAD(filterrules, filter_rule) sc_filter_rules[FILTER_PHASES_COUNT]; + + struct dict *sc_filters_dict; struct dict *sc_dispatchers; struct dispatcher *sc_dispatcher_bounce; @@ -1065,26 +1065,43 @@ struct processor { const char *chroot; }; -struct filter_rule { - TAILQ_ENTRY(filter_rule) entry; +enum filter_type { + FILTER_TYPE_BUILTIN, + FILTER_TYPE_PROC, + FILTER_TYPE_CHAIN, +}; +struct filter_config { + char *name; + enum filter_type filter_type; enum filter_phase phase; char *reject; char *disconnect; char *rewrite; char *proc; - int8_t not_table; - struct table *table; + const char **chain; + size_t chain_size; + struct dict chain_procs; - int8_t not_regex; - struct table *regex; + int8_t not_fcrdns; + int8_t fcrdns; int8_t not_rdns; int8_t rdns; - int8_t not_fcrdns; - int8_t fcrdns; + int8_t not_rdns_table; + struct table *rdns_table; + + int8_t not_rdns_regex; + struct table *rdns_regex; + + int8_t not_src_table; + struct table *src_table; + + int8_t not_src_regex; + struct table *src_regex; + }; enum filter_status { @@ -1212,12 +1229,22 @@ struct rule { int8_t flag_for; int8_t flag_from_socket; + int8_t flag_tag_regex; + int8_t flag_for_regex; + int8_t flag_from_regex; + int8_t flag_smtp_helo; int8_t flag_smtp_starttls; int8_t flag_smtp_auth; int8_t flag_smtp_mail_from; int8_t flag_smtp_rcpt_to; + int8_t flag_smtp_helo_regex; + int8_t flag_smtp_starttls_regex; + int8_t flag_smtp_auth_regex; + int8_t flag_smtp_mail_from_regex; + int8_t flag_smtp_rcpt_to_regex; + char *table_tag; char *table_from; @@ -1348,11 +1375,14 @@ int lka(void); /* lka_proc.c */ +int lka_proc_ready(void); void lka_proc_forked(const char *, int); struct io *lka_proc_get_io(const char *); /* lka_report.c */ +void lka_report_init(void); +void lka_report_register_hook(const char *, const char *); void lka_report_smtp_link_connect(const char *, struct timeval *, uint64_t, const char *, int, const struct sockaddr_storage *, const struct sockaddr_storage *); void lka_report_smtp_link_disconnect(const char *, struct timeval *, uint64_t); @@ -1372,7 +1402,11 @@ void lka_report_smtp_filter_response(const char *, struct timeval *, uint64_t, /* lka_filter.c */ -void lka_filter_begin(uint64_t, const struct sockaddr_storage *, const struct sockaddr_storage *, const char *, int); +void lka_filter_init(void); +void lka_filter_register_hook(const char *, const char *); +void lka_filter_ready(void); +int lka_filter_proc_in_session(uint64_t, const char *); +void lka_filter_begin(uint64_t, const char *, const struct sockaddr_storage *, const struct sockaddr_storage *, const char *, int); void lka_filter_end(uint64_t); void lka_filter_protocol(uint64_t, enum filter_phase, const char *); void lka_filter_data_begin(uint64_t); diff --git a/smtpd/ssl.c b/smtpd/ssl.c index b88360eb..74932247 100644 --- a/smtpd/ssl.c +++ b/smtpd/ssl.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssl.c,v 1.89 2017/05/17 14:00:06 deraadt Exp $ */ +/* $OpenBSD: ssl.c,v 1.90 2018/12/20 19:40:13 gilles Exp $ */ /* * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> @@ -330,7 +330,7 @@ ssl_to_text(const SSL *ssl) { static char buf[256]; - (void)snprintf(buf, sizeof buf, "version=%s, cipher=%s, bits=%d", + (void)snprintf(buf, sizeof buf, "%s:%s:%d", SSL_get_version(ssl), SSL_get_cipher_name(ssl), SSL_get_cipher_bits(ssl, NULL)); diff --git a/smtpd/table.c b/smtpd/table.c index fc04c4ce..37249abd 100644 --- a/smtpd/table.c +++ b/smtpd/table.c @@ -1,4 +1,4 @@ -/* $OpenBSD: table.c,v 1.32 2018/11/02 13:45:59 gilles Exp $ */ +/* $OpenBSD: table.c,v 1.33 2018/12/21 21:35:29 gilles Exp $ */ /* * Copyright (c) 2013 Eric Faurot <eric@openbsd.org> @@ -471,8 +471,14 @@ int table_regex_match(const char *string, const char *pattern) { regex_t preg; + int cflags = REG_EXTENDED|REG_NOSUB; - if (regcomp(&preg, pattern, REG_EXTENDED|REG_NOSUB) != 0) + if (strncmp(pattern, "(?i)", 4) == 0) { + cflags |= REG_ICASE; + pattern += 4; + } + + if (regcomp(&preg, pattern, cflags) != 0) return (0); if (regexec(&preg, string, 0, NULL, 0) != 0) |