diff options
author | Gilles Chehade <gilles@cvs.openbsd.org> | 2018-11-03 13:42:25 +0000 |
---|---|---|
committer | Gilles Chehade <gilles@cvs.openbsd.org> | 2018-11-03 13:42:25 +0000 |
commit | c452475d031c4151fabfe3172dcfb16754a9f747 (patch) | |
tree | 7ff871d39f4c927f4524f55b2c17690e429c85b2 /usr.sbin | |
parent | 103e498ba78e6e90657cb40d45cc39ce9280e1b2 (diff) |
bring plumbing for builtin filters
ok millert@, eric@, jung@
Diffstat (limited to 'usr.sbin')
-rw-r--r-- | usr.sbin/smtpd/config.c | 6 | ||||
-rw-r--r-- | usr.sbin/smtpd/lka.c | 14 | ||||
-rw-r--r-- | usr.sbin/smtpd/lka_filter.c | 221 | ||||
-rw-r--r-- | usr.sbin/smtpd/parse.y | 198 | ||||
-rw-r--r-- | usr.sbin/smtpd/pony.c | 3 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtp.c | 3 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtp_session.c | 339 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtpd.h | 50 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtpd/Makefile | 3 |
9 files changed, 735 insertions, 102 deletions
diff --git a/usr.sbin/smtpd/config.c b/usr.sbin/smtpd/config.c index 8882732b7ea..00da2891252 100644 --- a/usr.sbin/smtpd/config.c +++ b/usr.sbin/smtpd/config.c @@ -1,4 +1,4 @@ -/* $OpenBSD: config.c,v 1.44 2018/11/01 14:48:49 gilles Exp $ */ +/* $OpenBSD: config.c,v 1.45 2018/11/03 13:42:24 gilles Exp $ */ /* * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> @@ -48,6 +48,7 @@ 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; @@ -125,6 +126,9 @@ 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; diff --git a/usr.sbin/smtpd/lka.c b/usr.sbin/smtpd/lka.c index ac6713b7084..f38cb786536 100644 --- a/usr.sbin/smtpd/lka.c +++ b/usr.sbin/smtpd/lka.c @@ -1,4 +1,4 @@ -/* $OpenBSD: lka.c,v 1.211 2018/11/02 17:20:22 gilles Exp $ */ +/* $OpenBSD: lka.c,v 1.212 2018/11/03 13:42:24 gilles Exp $ */ /* * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> @@ -87,6 +87,8 @@ lka_imsg(struct mproc *p, struct imsg *imsg) const char *command, *response; const char *ciphers; struct sockaddr_storage ss_src, ss_dest; + int filter_phase; + const char *filter_param; if (imsg == NULL) lka_shutdown(); @@ -481,6 +483,16 @@ lka_imsg(struct mproc *p, struct imsg *imsg) lka_report_smtp_protocol_server(tm, reqid, response); return; + + case IMSG_SMTP_FILTER: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_int(&m, &filter_phase); + m_get_string(&m, &filter_param); + m_end(&m); + + lka_filter(reqid, filter_phase, filter_param); + return; } errx(1, "lka_imsg: unexpected %s imsg", imsg_to_str(imsg->hdr.type)); diff --git a/usr.sbin/smtpd/lka_filter.c b/usr.sbin/smtpd/lka_filter.c new file mode 100644 index 00000000000..7bbdddd0167 --- /dev/null +++ b/usr.sbin/smtpd/lka_filter.c @@ -0,0 +1,221 @@ +/* $OpenBSD: lka_filter.c,v 1.1 2018/11/03 13:42:24 gilles Exp $ */ + +/* + * Copyright (c) 2018 Gilles Chehade <gilles@poolp.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/socket.h> + +#include <netinet/in.h> + +#include <errno.h> +#include <event.h> +#include <imsg.h> +#include <inttypes.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "smtpd.h" +#include "log.h" + +static void filter_proceed(uint64_t, enum filter_phase, const char *); +static void filter_rewrite(uint64_t, enum filter_phase, const char *); +static void filter_reject(uint64_t, enum filter_phase, const char *); +static void filter_disconnect(uint64_t, enum filter_phase, 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 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 }, +}; + +void +lka_filter(uint64_t reqid, enum filter_phase phase, const char *param) +{ + struct filter_rule *rule; + uint8_t i; + + for (i = 0; i < nitems(filter_execs); ++i) + if (phase == filter_execs[i].phase) + break; + if (i == nitems(filter_execs)) + goto proceed; + + TAILQ_FOREACH(rule, &env->sc_filter_rules[phase], entry) { + if (! filter_execs[i].func(reqid, rule, param)) { + if (rule->rewrite) + filter_rewrite(reqid, phase, rule->rewrite); + else if (rule->disconnect) + filter_disconnect(reqid, phase, rule->disconnect); + else + filter_reject(reqid, phase, rule->reject); + return; + } + } + +proceed: + filter_proceed(reqid, phase, param); +} + +static void +filter_proceed(uint64_t reqid, enum filter_phase phase, const char *param) +{ + m_create(p_pony, IMSG_SMTP_FILTER, 0, 0, -1); + m_add_id(p_pony, reqid); + m_add_int(p_pony, phase); + m_add_int(p_pony, FILTER_PROCEED); + m_add_string(p_pony, param); + m_close(p_pony); +} + +static void +filter_rewrite(uint64_t reqid, enum filter_phase phase, const char *param) +{ + m_create(p_pony, IMSG_SMTP_FILTER, 0, 0, -1); + m_add_id(p_pony, reqid); + m_add_int(p_pony, phase); + m_add_int(p_pony, FILTER_REWRITE); + m_add_string(p_pony, param); + m_close(p_pony); +} + +static void +filter_reject(uint64_t reqid, enum filter_phase phase, const char *message) +{ + m_create(p_pony, IMSG_SMTP_FILTER, 0, 0, -1); + m_add_id(p_pony, reqid); + m_add_int(p_pony, phase); + m_add_int(p_pony, FILTER_REJECT); + m_add_string(p_pony, message); + m_close(p_pony); +} + +static void +filter_disconnect(uint64_t reqid, enum filter_phase phase, const char *message) +{ + m_create(p_pony, IMSG_SMTP_FILTER, 0, 0, -1); + m_add_id(p_pony, reqid); + m_add_int(p_pony, phase); + m_add_int(p_pony, FILTER_DISCONNECT); + m_add_string(p_pony, message); + m_close(p_pony); +} + + +/* below is code for builtin filters */ + +static int +filter_check_table(struct filter_rule *rule, enum table_service kind, const char *key) +{ + int ret = 0; + + if (rule->table) { + if (table_lookup(rule->table, NULL, key, kind, NULL) > 0) + ret = 1; + ret = rule->not_table < 0 ? !ret : ret; + } + return ret; +} + +static int +filter_check_regex(struct filter_rule *rule, const char *key) +{ + int ret = 0; + + if (rule->regex) { + if (table_lookup(rule->regex, NULL, key, K_REGEX, NULL) > 0) + ret = 1; + ret = rule->not_regex < 0 ? !ret : ret; + } + return ret; +} + +static int +filter_exec_notimpl(uint64_t reqid, struct filter_rule *rule, const char *param) +{ + return 1; +} + +static int +filter_exec_connected(uint64_t reqid, struct filter_rule *rule, const char *param) +{ + if (filter_check_table(rule, K_NETADDR, param) || + filter_check_regex(rule, param)) + return 0; + return 1; +} + +static int +filter_exec_helo(uint64_t reqid, struct filter_rule *rule, const char *param) +{ + if (filter_check_table(rule, K_DOMAIN, param) || + filter_check_regex(rule, param)) + return 0; + return 1; +} + +static int +filter_exec_mail_from(uint64_t reqid, struct filter_rule *rule, const char *param) +{ + char buffer[SMTPD_MAXMAILADDRSIZE]; + + (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)) + return 0; + return 1; +} + +static int +filter_exec_rcpt_to(uint64_t reqid, struct filter_rule *rule, const char *param) +{ + char buffer[SMTPD_MAXMAILADDRSIZE]; + + (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)) + return 0; + return 1; +} diff --git a/usr.sbin/smtpd/parse.y b/usr.sbin/smtpd/parse.y index 7f6b7ffde4f..e5b4a8c2582 100644 --- a/usr.sbin/smtpd/parse.y +++ b/usr.sbin/smtpd/parse.y @@ -1,4 +1,4 @@ -/* $OpenBSD: parse.y,v 1.225 2018/11/01 14:48:49 gilles Exp $ */ +/* $OpenBSD: parse.y,v 1.226 2018/11/03 13:42:24 gilles Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org> @@ -106,6 +106,7 @@ static struct ca *sca; struct dispatcher *dispatcher; struct rule *rule; struct processor *processor; +struct filter_rule *filter_rule; enum listen_options { LO_FAMILY = 0x000001, @@ -173,9 +174,10 @@ typedef struct { %token ACTION ALIAS ANY ARROW AUTH AUTH_OPTIONAL %token BACKUP BOUNCE -%token CA CERT CHROOT CIPHERS COMPRESSION -%token DHE DOMAIN -%token ENCRYPTION ERROR EXPAND_ONLY +%token CA CERT CHROOT CIPHERS COMPRESSION CONNECT +%token CHECK_REGEX CHECK_TABLE +%token DATA DHE DISCONNECT DOMAIN +%token EHLO ENABLE ENCRYPTION ERROR EXPAND_ONLY %token FILTER FOR FORWARD_ONLY FROM %token GROUP %token HELO HELO_SRC HOST HOSTNAME HOSTNAMES @@ -184,11 +186,11 @@ typedef struct { %token KEY %token LIMIT LISTEN LMTP LOCAL %token MAIL_FROM MAILDIR MASK_SRC MASQUERADE MATCH MAX_MESSAGE_SIZE MAX_DEFERRED MBOX MDA MTA MX -%token NO_DSN NO_VERIFY +%token NO_DSN NO_VERIFY NOOP %token ON %token PKI PORT PROC -%token QUEUE -%token RCPT_TO RECIPIENT RECEIVEDAUTH RELAY REJECT REPORT +%token QUEUE QUIT +%token RCPT_TO RECIPIENT RECEIVEDAUTH RELAY REJECT REPORT REWRITE RSET %token SCHEDULER SENDER SENDERS SMTP SMTPS SOCKET SRC SUB_ADDR_DELIM %token TABLE TAG TAGGED TLS TLS_REQUIRE TTL %token USER USERBASE @@ -220,6 +222,7 @@ grammar : /* empty */ | grammar table '\n' | grammar dispatcher '\n' | grammar match '\n' + | grammar filter '\n' | grammar error '\n' { file->errors++; } ; @@ -447,6 +450,7 @@ PROC STRING STRING { } ; + proc_params_opt: USER STRING { if (processor->user) { @@ -1129,6 +1133,176 @@ MATCH { } ; +/* +filter_action_proc: +ON STRING { + filter_rule->filter = $2; +} +; +*/ + +filter_action_builtin: +REJECT STRING { + filter_rule->reject = $2; +} +| DISCONNECT STRING { + filter_rule->disconnect = $2; +} +/* +| REWRITE STRING { + filter_rule->rewrite = $2; +} +*/ +; + +filter_phase_check_table: +negation CHECK_TABLE tables { + filter_rule->not_table = $1 ? -1 : 1; + filter_rule->table = $3; +} +; + +filter_phase_check_regex: +negation CHECK_REGEX tables { + filter_rule->not_regex = $1 ? -1 : 1; + filter_rule->regex = $3; +} +; + +filter_phase_connect_options: +filter_phase_check_table | filter_phase_check_regex; + +filter_phase_connect: +CONNECT { + filter_rule->phase = FILTER_CONNECTED; +} filter_phase_connect_options filter_action_builtin + /* +| CONNECT { + filter_rule->phase = FILTER_CONNECTED; +} filter_action_proc + */ +; + +filter_phase_helo_options: +filter_phase_check_table | filter_phase_check_regex; + +filter_phase_helo: +HELO { + filter_rule->phase = FILTER_HELO; +} filter_phase_helo_options filter_action_builtin + /* +| HELO { + filter_rule->phase = FILTER_HELO; +} filter_action_proc + */ +; + +filter_phase_ehlo: +EHLO { + filter_rule->phase = FILTER_EHLO; +} filter_phase_helo_options filter_action_builtin + /* +| EHLO { + filter_rule->phase = FILTER_EHLO; +} filter_action_proc + */ +; + +filter_phase_mail_from_options: +filter_phase_check_table | filter_phase_check_regex; + +filter_phase_mail_from: +MAIL_FROM { + filter_rule->phase = FILTER_MAIL_FROM; +} filter_phase_mail_from_options filter_action_builtin + /* +| MAIL_FROM { + filter_rule->phase = FILTER_MAIL_FROM; +} filter_action_proc + */ +; + +filter_phase_rcpt_to_options: +filter_phase_check_table | filter_phase_check_regex; + +filter_phase_rcpt_to: +RCPT_TO { + filter_rule->phase = FILTER_RCPT_TO; +} filter_phase_rcpt_to_options filter_action_builtin + /* +| RCPT_TO { + filter_rule->phase = FILTER_RCPT_TO; +} filter_action_proc + */ +; + +filter_phase_data: +DATA { + filter_rule->phase = FILTER_DATA; +} filter_action_builtin + /* +| DATA { + filter_rule->phase = FILTER_DATA; +} filter_action_proc + */ +; + +filter_phase_quit: +QUIT { + filter_rule->phase = FILTER_QUIT; +} filter_action_builtin + /* +| QUIT { + filter_rule->phase = FILTER_QUIT; +} filter_action_proc + */ +; + +filter_phase_rset: +RSET { + filter_rule->phase = FILTER_RSET; +} filter_action_builtin + /* +| RSET { + filter_rule->phase = FILTER_RSET; +} filter_action_proc + */ +; + +filter_phase_noop: +NOOP { + filter_rule->phase = FILTER_NOOP; +} filter_action_builtin + /* +| NOOP { + filter_rule->phase = FILTER_NOOP; +} filter_action_proc + */ +; + + + +filter_phase: +filter_phase_connect +| filter_phase_helo +| filter_phase_ehlo +| filter_phase_mail_from +| filter_phase_rcpt_to +| filter_phase_data +| filter_phase_quit +| filter_phase_noop +| filter_phase_rset +; + +filter: +FILTER SMTP { + 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; +} +; + size : NUMBER { if ($1 < 0) { yyerror("invalid size: %" PRId64, $1); @@ -1676,11 +1850,17 @@ lookup(char *s) { "bounce", BOUNCE }, { "ca", CA }, { "cert", CERT }, + { "check-regex", CHECK_REGEX }, + { "check-table", CHECK_TABLE }, { "chroot", CHROOT }, { "ciphers", CIPHERS }, { "compression", COMPRESSION }, + { "connect", CONNECT }, + { "data", DATA }, { "dhe", DHE }, + { "disconnect", DISCONNECT }, { "domain", DOMAIN }, + { "ehlo", EHLO }, { "encryption", ENCRYPTION }, { "expand-only", EXPAND_ONLY }, { "filter", FILTER }, @@ -1715,17 +1895,21 @@ lookup(char *s) { "mx", MX }, { "no-dsn", NO_DSN }, { "no-verify", NO_VERIFY }, + { "noop", NOOP }, { "on", ON }, { "pki", PKI }, { "port", PORT }, { "proc", PROC }, { "queue", QUEUE }, + { "quit", QUIT }, { "rcpt-to", RCPT_TO }, { "received-auth", RECEIVEDAUTH }, { "recipient", RECIPIENT }, { "reject", REJECT }, { "relay", RELAY }, { "report", REPORT }, + { "rewrite", REWRITE }, + { "rset", RSET }, { "scheduler", SCHEDULER }, { "senders", SENDERS }, { "smtp", SMTP }, diff --git a/usr.sbin/smtpd/pony.c b/usr.sbin/smtpd/pony.c index 7af7f014789..411fbfa6dca 100644 --- a/usr.sbin/smtpd/pony.c +++ b/usr.sbin/smtpd/pony.c @@ -1,4 +1,4 @@ -/* $OpenBSD: pony.c,v 1.21 2018/07/25 16:00:48 eric Exp $ */ +/* $OpenBSD: pony.c,v 1.22 2018/11/03 13:42:24 gilles Exp $ */ /* * Copyright (c) 2014 Gilles Chehade <gilles@poolp.org> @@ -91,6 +91,7 @@ pony_imsg(struct mproc *p, struct imsg *imsg) case IMSG_SMTP_MESSAGE_COMMIT: case IMSG_SMTP_MESSAGE_CREATE: case IMSG_SMTP_MESSAGE_OPEN: + case IMSG_SMTP_FILTER: case IMSG_QUEUE_ENVELOPE_SUBMIT: case IMSG_QUEUE_ENVELOPE_COMMIT: case IMSG_QUEUE_SMTP_SESSION: diff --git a/usr.sbin/smtpd/smtp.c b/usr.sbin/smtpd/smtp.c index 80fc099b672..4eab35df03d 100644 --- a/usr.sbin/smtpd/smtp.c +++ b/usr.sbin/smtpd/smtp.c @@ -1,4 +1,4 @@ -/* $OpenBSD: smtp.c,v 1.159 2018/07/25 16:00:48 eric Exp $ */ +/* $OpenBSD: smtp.c,v 1.160 2018/11/03 13:42:24 gilles Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org> @@ -68,6 +68,7 @@ smtp_imsg(struct mproc *p, struct imsg *imsg) case IMSG_SMTP_AUTHENTICATE: case IMSG_SMTP_TLS_INIT: case IMSG_SMTP_TLS_VERIFY: + case IMSG_SMTP_FILTER: smtp_session_imsg(p, imsg); return; diff --git a/usr.sbin/smtpd/smtp_session.c b/usr.sbin/smtpd/smtp_session.c index 155b5dbe1d1..1c165e09aad 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.345 2018/11/02 17:20:22 gilles Exp $ */ +/* $OpenBSD: smtp_session.c,v 1.346 2018/11/03 13:42:24 gilles Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org> @@ -191,40 +191,53 @@ static void smtp_message_fd(struct smtp_tx *, int); static void smtp_message_end(struct smtp_tx *); static int smtp_message_printf(struct smtp_tx *, const char *, ...); -static int smtp_check_rset(struct smtp_session *); -static int smtp_check_helo(struct smtp_session *, int, const char *); +static int smtp_check_rset(struct smtp_session *, const char *); +static int smtp_check_helo(struct smtp_session *, const char *); +static int smtp_check_ehlo(struct smtp_session *, const char *); static int smtp_check_auth(struct smtp_session *s, const char *); static int smtp_check_starttls(struct smtp_session *, const char *); -static int smtp_check_mail_from(struct smtp_session *); -static int smtp_check_rcpt_to(struct smtp_session *); -static int smtp_check_data(struct smtp_session *); - -static void smtp_proceed_rset(struct smtp_session *); -static void smtp_proceed_helo(struct smtp_session *, int, char *); -static void smtp_proceed_auth(struct smtp_session *, char *); -static void smtp_proceed_starttls(struct smtp_session *); -static void smtp_proceed_mail_from(struct smtp_session *, char *); -static void smtp_proceed_rcpt_to(struct smtp_session *, char *); -static void smtp_proceed_data(struct smtp_session *); -static void smtp_proceed_noop(struct smtp_session *); -static void smtp_proceed_help(struct smtp_session *); -static void smtp_proceed_wiz(struct smtp_session *); -static void smtp_proceed_quit(struct smtp_session *); - -static struct { int code; const char *cmd; } commands[] = { - { CMD_HELO, "HELO" }, - { CMD_EHLO, "EHLO" }, - { CMD_STARTTLS, "STARTTLS" }, - { CMD_AUTH, "AUTH" }, - { CMD_MAIL_FROM, "MAIL FROM" }, - { CMD_RCPT_TO, "RCPT TO" }, - { CMD_DATA, "DATA" }, - { CMD_RSET, "RSET" }, - { CMD_QUIT, "QUIT" }, - { CMD_HELP, "HELP" }, - { CMD_WIZ, "WIZ" }, - { CMD_NOOP, "NOOP" }, - { -1, NULL }, +static int smtp_check_mail_from(struct smtp_session *, const char *); +static int smtp_check_rcpt_to(struct smtp_session *, const char *); +static int smtp_check_data(struct smtp_session *, const char *); +static int smtp_check_noparam(struct smtp_session *, const char *); + +static void smtp_filter_phase(enum filter_phase, struct smtp_session *, const char *); + +static void smtp_proceed_connected(struct smtp_session *); +static void smtp_proceed_rset(struct smtp_session *, const char *); +static void smtp_proceed_helo(struct smtp_session *, const char *); +static void smtp_proceed_ehlo(struct smtp_session *, const char *); +static void smtp_proceed_auth(struct smtp_session *, const char *); +static void smtp_proceed_starttls(struct smtp_session *, const char *); +static void smtp_proceed_mail_from(struct smtp_session *, const char *); +static void smtp_proceed_rcpt_to(struct smtp_session *, const char *); +static void smtp_proceed_data(struct smtp_session *, const char *); +static void smtp_proceed_noop(struct smtp_session *, const char *); +static void smtp_proceed_help(struct smtp_session *, const char *); +static void smtp_proceed_wiz(struct smtp_session *, const char *); +static void smtp_proceed_quit(struct smtp_session *, const char *); + +static struct { + int code; + enum filter_phase filter_phase; + const char *cmd; + + int (*check)(struct smtp_session *, const char *); + void (*proceed)(struct smtp_session *, const char *); +} commands[] = { + { CMD_HELO, FILTER_HELO, "HELO", smtp_check_helo, smtp_proceed_helo }, + { CMD_EHLO, FILTER_EHLO, "EHLO", smtp_check_ehlo, smtp_proceed_ehlo }, + { CMD_STARTTLS, FILTER_STARTTLS, "STARTTLS", smtp_check_starttls, smtp_proceed_starttls }, + { CMD_AUTH, FILTER_AUTH, "AUTH", smtp_check_auth, smtp_proceed_auth }, + { CMD_MAIL_FROM, FILTER_MAIL_FROM, "MAIL FROM", smtp_check_mail_from, smtp_proceed_mail_from }, + { CMD_RCPT_TO, FILTER_RCPT_TO, "RCPT TO", smtp_check_rcpt_to, smtp_proceed_rcpt_to }, + { CMD_DATA, FILTER_DATA, "DATA", smtp_check_data, smtp_proceed_data }, + { CMD_RSET, FILTER_RSET, "RSET", smtp_check_rset, smtp_proceed_rset }, + { CMD_QUIT, FILTER_QUIT, "QUIT", smtp_check_noparam, smtp_proceed_quit }, + { CMD_NOOP, FILTER_NOOP, "NOOP", smtp_check_noparam, smtp_proceed_noop }, + { CMD_HELP, FILTER_HELP, "HELP", smtp_check_noparam, smtp_proceed_help }, + { CMD_WIZ, FILTER_WIZ, "WIZ", smtp_check_noparam, smtp_proceed_wiz }, + { -1, 0, NULL, NULL }, }; static struct tree wait_lka_helo; @@ -236,6 +249,7 @@ static struct tree wait_queue_fd; static struct tree wait_queue_commit; static struct tree wait_ssl_init; static struct tree wait_ssl_verify; +static struct tree wait_filters; static void header_append_domain_buffer(char *buffer, char *domain, size_t len) @@ -516,6 +530,7 @@ smtp_session_init(void) tree_init(&wait_queue_commit); tree_init(&wait_ssl_init); tree_init(&wait_ssl_verify); + tree_init(&wait_filters); init = 1; } } @@ -601,6 +616,10 @@ smtp_session_imsg(struct mproc *p, struct imsg *imsg) uint32_t msgid; int status, success; void *ssl_ctx; + enum filter_phase filter_phase; + int filter_response; + const char *filter_param; + uint8_t i; switch (imsg->hdr.type) { @@ -864,6 +883,49 @@ smtp_session_imsg(struct mproc *p, struct imsg *imsg) smtp_tls_verified(s); io_resume(s->io, IO_IN); return; + + case IMSG_SMTP_FILTER: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_int(&m, (int *)&filter_phase); + m_get_int(&m, &filter_response); + m_get_string(&m, &filter_param); + m_end(&m); + + s = tree_xpop(&wait_filters, reqid); + + switch (filter_response) { + case FILTER_REJECT: + case FILTER_DISCONNECT: + if (!valid_smtp_response(filter_param) || + (filter_param[0] != '4' && filter_param[0] != '5')) + filter_param = "421 Internal server error"; + if (!strncmp(filter_param, "421", 3)) + filter_response = FILTER_DISCONNECT; + + smtp_reply(s, "%s", filter_param); + + if (filter_response == FILTER_DISCONNECT) + smtp_enter_state(s, STATE_QUIT); + break; + + case FILTER_PROCEED: + case FILTER_REWRITE: + if (filter_phase == FILTER_CONNECTED) { + smtp_proceed_connected(s); + return; + } + for (i = 0; i < nitems(commands); ++i) + if (commands[i].filter_phase == filter_phase) { + if (filter_response == FILTER_REWRITE) + if (!commands[i].check(s, filter_param)) + break; + commands[i].proceed(s, filter_param); + break; + } + break; + } + return; } log_warnx("smtp_session_imsg: unexpected %s imsg", @@ -1082,70 +1144,77 @@ smtp_command(struct smtp_session *s, char *line) * INIT */ case CMD_HELO: + if (!smtp_check_helo(s, args)) + break; + smtp_filter_phase(FILTER_HELO, s, args); + break; + case CMD_EHLO: - if (!smtp_check_helo(s, cmd, args)) + if (!smtp_check_ehlo(s, args)) break; - smtp_proceed_helo(s, cmd, args); + smtp_filter_phase(FILTER_EHLO, s, args); break; + /* * SETUP */ case CMD_STARTTLS: if (!smtp_check_starttls(s, args)) break; - smtp_proceed_starttls(s); + + smtp_filter_phase(FILTER_STARTTLS, s, NULL); break; case CMD_AUTH: if (!smtp_check_auth(s, args)) break; - smtp_proceed_auth(s, args); + smtp_filter_phase(FILTER_AUTH, s, NULL); break; case CMD_MAIL_FROM: - if (!smtp_check_mail_from(s)) + if (!smtp_check_mail_from(s, args)) break; - smtp_proceed_mail_from(s, args); + smtp_filter_phase(FILTER_MAIL_FROM, s, args); break; /* * TRANSACTION */ case CMD_RCPT_TO: - if (!smtp_check_rcpt_to(s)) + if (!smtp_check_rcpt_to(s, args)) break; - smtp_proceed_rcpt_to(s, args); + smtp_filter_phase(FILTER_RCPT_TO, s, args); break; case CMD_RSET: - if (!smtp_check_rset(s)) + if (!smtp_check_rset(s, args)) break; - smtp_proceed_rset(s); + smtp_filter_phase(FILTER_RSET, s, NULL); break; case CMD_DATA: - if (!smtp_check_data(s)) + if (!smtp_check_data(s, args)) break; - smtp_proceed_data(s); + smtp_filter_phase(FILTER_DATA, s, NULL); break; /* * ANY */ case CMD_QUIT: - smtp_proceed_quit(s); + smtp_filter_phase(FILTER_QUIT, s, NULL); break; case CMD_NOOP: - smtp_proceed_noop(s); + smtp_filter_phase(FILTER_NOOP, s, NULL); break; case CMD_HELP: - smtp_proceed_help(s); + smtp_proceed_help(s, NULL); break; case CMD_WIZ: - smtp_proceed_wiz(s); + smtp_proceed_wiz(s, NULL); break; default: @@ -1157,7 +1226,7 @@ smtp_command(struct smtp_session *s, char *line) } static int -smtp_check_rset(struct smtp_session *s) +smtp_check_rset(struct smtp_session *s, const char *args) { if (s->helo[0] == '\0') { smtp_reply(s, "503 %s %s: Command not allowed at this point.", @@ -1169,8 +1238,15 @@ smtp_check_rset(struct smtp_session *s) } static int -smtp_check_helo(struct smtp_session *s, int cmd, const char *args) +smtp_check_helo(struct smtp_session *s, const char *args) { + if (!s->banner_sent) { + smtp_reply(s, "503 %s %s: Command not allowed at this point.", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + if (s->helo[0]) { smtp_reply(s, "503 %s %s: Already identified", esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), @@ -1179,10 +1255,43 @@ smtp_check_helo(struct smtp_session *s, int cmd, const char *args) } if (args == NULL) { - smtp_reply(s, "501 %s %s: %s requires domain name", + smtp_reply(s, "501 %s %s: HELO requires domain name", esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), - esc_description(ESC_INVALID_COMMAND), - (cmd == CMD_HELO) ? "HELO" : "EHLO"); + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (!valid_domainpart(args)) { + smtp_reply(s, "501 %s %s: Invalid domain name", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND_ARGUMENTS), + esc_description(ESC_INVALID_COMMAND_ARGUMENTS)); + return 0; + } + + return 1; +} + +static int +smtp_check_ehlo(struct smtp_session *s, const char *args) +{ + if (!s->banner_sent) { + smtp_reply(s, "503 %s %s: Command not allowed at this point.", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (s->helo[0]) { + smtp_reply(s, "503 %s %s: Already identified", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (args == NULL) { + smtp_reply(s, "501 %s %s: EHLO requires domain name", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); return 0; } @@ -1265,7 +1374,7 @@ smtp_check_starttls(struct smtp_session *s, const char *args) } static int -smtp_check_mail_from(struct smtp_session *s) +smtp_check_mail_from(struct smtp_session *s, const char *args) { if (s->helo[0] == '\0' || s->tx) { smtp_reply(s, "503 %s %s: Command not allowed at this point.", @@ -1311,7 +1420,7 @@ smtp_check_mail_from(struct smtp_session *s) } static int -smtp_check_rcpt_to(struct smtp_session *s) +smtp_check_rcpt_to(struct smtp_session *s, const char *args) { if (s->tx == NULL) { smtp_reply(s, "503 %s %s: Command not allowed at this point.", @@ -1324,7 +1433,7 @@ smtp_check_rcpt_to(struct smtp_session *s) } static int -smtp_check_data(struct smtp_session *s) +smtp_check_data(struct smtp_session *s, const char *args) { if (s->tx == NULL) { smtp_reply(s, "503 %s %s: Command not allowed at this point.", @@ -1343,9 +1452,44 @@ smtp_check_data(struct smtp_session *s) return 1; } +static int +smtp_check_noparam(struct smtp_session *s, const char *args) +{ + return 1; +} static void -smtp_proceed_rset(struct smtp_session *s) +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_SMTP_FILTER, 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); +} + +static void +smtp_filter_phase(enum filter_phase phase, struct smtp_session *s, const char *param) +{ + smtp_query_filters(phase, s, param ? param : ""); +} + +static void +smtp_proceed_rset(struct smtp_session *s, const char *args) { if (s->tx) { if (s->tx->msgid) @@ -1358,45 +1502,56 @@ smtp_proceed_rset(struct smtp_session *s) } static void -smtp_proceed_helo(struct smtp_session *s, int cmd, char *args) +smtp_proceed_helo(struct smtp_session *s, const char *args) { (void)strlcpy(s->helo, args, sizeof(s->helo)); s->flags &= SF_SECURE | SF_AUTHENTICATED | SF_VERIFIED; - if (cmd == CMD_EHLO) { - s->flags |= SF_EHLO; - s->flags |= SF_8BITMIME; - } smtp_enter_state(s, STATE_HELO); - smtp_reply(s, "250%c%s Hello %s [%s], pleased to meet you", - (s->flags & SF_EHLO) ? '-' : ' ', + smtp_reply(s, "250 %s Hello %s [%s], pleased to meet you", s->smtpname, s->helo, ss_to_text(&s->ss)); +} - if (s->flags & SF_EHLO) { - smtp_reply(s, "250-8BITMIME"); - smtp_reply(s, "250-ENHANCEDSTATUSCODES"); - smtp_reply(s, "250-SIZE %zu", env->sc_maxsize); - if (ADVERTISE_EXT_DSN(s)) - smtp_reply(s, "250-DSN"); - if (ADVERTISE_TLS(s)) - smtp_reply(s, "250-STARTTLS"); - if (ADVERTISE_AUTH(s)) - smtp_reply(s, "250-AUTH PLAIN LOGIN"); - smtp_reply(s, "250 HELP"); - } +static void +smtp_proceed_ehlo(struct smtp_session *s, const char *args) +{ + (void)strlcpy(s->helo, args, sizeof(s->helo)); + s->flags &= SF_SECURE | SF_AUTHENTICATED | SF_VERIFIED; + s->flags |= SF_EHLO; + s->flags |= SF_8BITMIME; + + smtp_enter_state(s, STATE_HELO); + smtp_reply(s, "250-%s Hello %s [%s], pleased to meet you", + s->smtpname, + s->helo, + ss_to_text(&s->ss)); + + smtp_reply(s, "250-8BITMIME"); + smtp_reply(s, "250-ENHANCEDSTATUSCODES"); + smtp_reply(s, "250-SIZE %zu", env->sc_maxsize); + if (ADVERTISE_EXT_DSN(s)) + smtp_reply(s, "250-DSN"); + if (ADVERTISE_TLS(s)) + smtp_reply(s, "250-STARTTLS"); + if (ADVERTISE_AUTH(s)) + smtp_reply(s, "250-AUTH PLAIN LOGIN"); + smtp_reply(s, "250 HELP"); } static void -smtp_proceed_auth(struct smtp_session *s, char *args) +smtp_proceed_auth(struct smtp_session *s, const char *args) { + char tmp[SMTP_LINE_MAX]; char *eom, *method; - method = args; - eom = strchr(args, ' '); + (void)strlcpy(tmp, args, sizeof tmp); + + method = tmp; + eom = strchr(tmp, ' '); if (eom == NULL) - eom = strchr(args, '\t'); + eom = strchr(tmp, '\t'); if (eom != NULL) *eom++ = '\0'; if (strcasecmp(method, "PLAIN") == 0) @@ -1411,7 +1566,7 @@ smtp_proceed_auth(struct smtp_session *s, char *args) } static void -smtp_proceed_starttls(struct smtp_session *s) +smtp_proceed_starttls(struct smtp_session *s, const char *args) { smtp_reply(s, "220 %s: Ready to start TLS", esc_code(ESC_STATUS_OK, ESC_OTHER_STATUS)); @@ -1419,25 +1574,25 @@ smtp_proceed_starttls(struct smtp_session *s) } static void -smtp_proceed_mail_from(struct smtp_session *s, char *args) +smtp_proceed_mail_from(struct smtp_session *s, const char *args) { smtp_tx_mail_from(s->tx, args); } static void -smtp_proceed_rcpt_to(struct smtp_session *s, char *args) +smtp_proceed_rcpt_to(struct smtp_session *s, const char *args) { smtp_tx_rcpt_to(s->tx, args); } static void -smtp_proceed_data(struct smtp_session *s) +smtp_proceed_data(struct smtp_session *s, const char *args) { smtp_tx_open_message(s->tx); } static void -smtp_proceed_quit(struct smtp_session *s) +smtp_proceed_quit(struct smtp_session *s, const char *args) { smtp_reply(s, "221 %s: Bye", esc_code(ESC_STATUS_OK, ESC_OTHER_STATUS)); @@ -1445,14 +1600,14 @@ smtp_proceed_quit(struct smtp_session *s) } static void -smtp_proceed_noop(struct smtp_session *s) +smtp_proceed_noop(struct smtp_session *s, const char *args) { smtp_reply(s, "250 %s: Ok", esc_code(ESC_STATUS_OK, ESC_OTHER_STATUS)); } static void -smtp_proceed_help(struct smtp_session *s) +smtp_proceed_help(struct smtp_session *s, const char *args) { smtp_reply(s, "214- This is " SMTPD_NAME); smtp_reply(s, "214- To report bugs in the implementation, " @@ -1463,7 +1618,7 @@ smtp_proceed_help(struct smtp_session *s) } static void -smtp_proceed_wiz(struct smtp_session *s) +smtp_proceed_wiz(struct smtp_session *s, const char *args) { smtp_reply(s, "500 %s %s: this feature is not supported yet ;-)", esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), @@ -1632,6 +1787,12 @@ smtp_connected(struct smtp_session *s) return; } + smtp_filter_phase(FILTER_CONNECTED, s, ss_to_text(&s->ss)); +} + +static void +smtp_proceed_connected(struct smtp_session *s) +{ smtp_send_banner(s); } diff --git a/usr.sbin/smtpd/smtpd.h b/usr.sbin/smtpd/smtpd.h index 7a41dc74fe7..e59dd9ac311 100644 --- a/usr.sbin/smtpd/smtpd.h +++ b/usr.sbin/smtpd/smtpd.h @@ -1,4 +1,4 @@ -/* $OpenBSD: smtpd.h,v 1.568 2018/11/02 17:20:22 gilles Exp $ */ +/* $OpenBSD: smtpd.h,v 1.569 2018/11/03 13:42:24 gilles Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org> @@ -315,6 +315,8 @@ enum imsg_type { IMSG_SMTP_REPORT_PROTOCOL_CLIENT, IMSG_SMTP_REPORT_PROTOCOL_SERVER, + IMSG_SMTP_FILTER, + IMSG_CA_PRIVENC, IMSG_CA_PRIVDEC }; @@ -390,6 +392,23 @@ enum expand_type { EXPAND_ERROR, }; +enum filter_phase { + FILTER_CONNECTED = 0, + FILTER_HELO, + FILTER_EHLO, + FILTER_STARTTLS, + FILTER_AUTH, + FILTER_MAIL_FROM, + FILTER_RCPT_TO, + FILTER_DATA, + FILTER_RSET, + FILTER_QUIT, + FILTER_NOOP, + FILTER_HELP, + FILTER_WIZ, + FILTER_PHASES_COUNT /* must be last */ +}; + struct expandnode { RB_ENTRY(expandnode) entry; TAILQ_ENTRY(expandnode) tq_entry; @@ -562,6 +581,8 @@ 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_dispatchers; struct dispatcher *sc_dispatcher_bounce; @@ -999,6 +1020,29 @@ struct processor { const char *chroot; }; +struct filter_rule { + TAILQ_ENTRY(filter_rule) entry; + + enum filter_phase phase; + char *reject; + char *disconnect; + char *rewrite; + char *filter; + + int8_t not_table; + struct table *table; + + int8_t not_regex; + struct table *regex; +}; + +enum filter_status { + FILTER_PROCEED, + FILTER_REWRITE, + FILTER_REJECT, + FILTER_DISCONNECT, +}; + enum ca_resp_status { CA_OK, CA_FAIL @@ -1259,6 +1303,10 @@ void lka_report_smtp_protocol_client(time_t, uint64_t, const char *); void lka_report_smtp_protocol_server(time_t, uint64_t, const char *); +/* lka_filter.c */ +void lka_filter(uint64_t, enum filter_phase, const char *); + + /* lka_session.c */ void lka_session(uint64_t, struct envelope *); void lka_session_forward_reply(struct forward_req *, int); diff --git a/usr.sbin/smtpd/smtpd/Makefile b/usr.sbin/smtpd/smtpd/Makefile index 96bad9f8264..f7d5e127a14 100644 --- a/usr.sbin/smtpd/smtpd/Makefile +++ b/usr.sbin/smtpd/smtpd/Makefile @@ -1,4 +1,4 @@ -# $OpenBSD: Makefile,v 1.95 2018/11/01 14:48:49 gilles Exp $ +# $OpenBSD: Makefile,v 1.96 2018/11/03 13:42:24 gilles Exp $ .PATH: ${.CURDIR}/.. @@ -22,6 +22,7 @@ SRCS+= iobuf.c SRCS+= ioev.c SRCS+= limit.c SRCS+= lka.c +SRCS+= lka_filter.c SRCS+= lka_proc.c SRCS+= lka_report.c SRCS+= lka_session.c |