diff options
author | 2013-11-06 10:01:29 +0000 | |
---|---|---|
committer | 2013-11-06 10:01:29 +0000 | |
commit | cc81b7c6f67d5cabbb19cc962bd15a6b2c39d383 (patch) | |
tree | 6637e2071fc49e42803636d0cb9fed6ebfe97007 | |
parent | dont leak ccbs in mpii_sas_remove_device. detaching lots (like, 1000) of (diff) | |
download | wireguard-openbsd-cc81b7c6f67d5cabbb19cc962bd15a6b2c39d383.tar.xz wireguard-openbsd-cc81b7c6f67d5cabbb19cc962bd15a6b2c39d383.zip |
Much much improved config parser and related changes.
Simplify code and do not impose an order on conditions and rule options.
Format changes that may require smtpd.conf update for some setups:
- SSL certificates are no longer automatically loaded, but must be
explicitely declared using the "pki" keyword.
- "certificate" option becomes "pki" in listener and accept rules.
- "ssl://" becomes "secure://" in relay via rules.
- "helo" becomes "hostnames" in relay rules
New features:
- accept rules do not need an explicit action, in which case alias table
or .forward must provide one.
- new "forward-only" action to force relaying and reject rcpts that expand
as local delivery.
- "!" (negation) modifier on rule matching conditions.
- new "recipient" rule matching condition.
- new "verify" option on listeners and relay rules to reject invalid
certificates.
Other changes:
- remember the helo name advertised on incoming mail and use it for sending
bounces.
- bump envelope version (existing envelopes are updated on-the-fly).
-rw-r--r-- | usr.sbin/smtpd/bounce.c | 48 | ||||
-rw-r--r-- | usr.sbin/smtpd/envelope.c | 122 | ||||
-rw-r--r-- | usr.sbin/smtpd/lka.c | 30 | ||||
-rw-r--r-- | usr.sbin/smtpd/lka_session.c | 54 | ||||
-rw-r--r-- | usr.sbin/smtpd/mta.c | 4 | ||||
-rw-r--r-- | usr.sbin/smtpd/mta_session.c | 55 | ||||
-rw-r--r-- | usr.sbin/smtpd/parse.y | 1043 | ||||
-rw-r--r-- | usr.sbin/smtpd/ruleset.c | 50 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtp.c | 7 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtp_session.c | 114 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtpd.c | 77 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtpd.conf.5 | 322 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtpd.h | 41 | ||||
-rw-r--r-- | usr.sbin/smtpd/ssl.c | 76 | ||||
-rw-r--r-- | usr.sbin/smtpd/ssl.h | 17 | ||||
-rw-r--r-- | usr.sbin/smtpd/to.c | 41 |
16 files changed, 1348 insertions, 753 deletions
diff --git a/usr.sbin/smtpd/bounce.c b/usr.sbin/smtpd/bounce.c index 39779b6e17e..0daf1bd2bc9 100644 --- a/usr.sbin/smtpd/bounce.c +++ b/usr.sbin/smtpd/bounce.c @@ -1,4 +1,4 @@ -/* $OpenBSD: bounce.c,v 1.58 2013/10/26 12:27:58 eric Exp $ */ +/* $OpenBSD: bounce.c,v 1.59 2013/11/06 10:01:29 eric Exp $ */ /* * Copyright (c) 2009 Gilles Chehade <gilles@poolp.org> @@ -65,12 +65,14 @@ struct bounce_message { TAILQ_ENTRY(bounce_message) entry; uint32_t msgid; struct delivery_bounce bounce; + char *smtpname; char *to; time_t timeout; TAILQ_HEAD(, bounce_envelope) envelopes; }; struct bounce_session { + char *smtpname; struct bounce_message *msg; FILE *msgfp; int state; @@ -139,19 +141,21 @@ bounce_add(uint64_t evpid) key.msgid = evpid_to_msgid(evpid); key.bounce = evp.agent.bounce; + key.smtpname = evp.smtpname; msg = SPLAY_FIND(bounce_message_tree, &messages, &key); if (msg == NULL) { msg = xcalloc(1, sizeof(*msg), "bounce_add"); msg->msgid = key.msgid; msg->bounce = key.bounce; - SPLAY_INSERT(bounce_message_tree, &messages, msg); TAILQ_INIT(&msg->envelopes); + msg->smtpname = xstrdup(evp.smtpname, "bounce_add"); snprintf(buf, sizeof(buf), "%s@%s", evp.sender.user, evp.sender.domain); msg->to = xstrdup(buf, "bounce_add"); nmessage += 1; + SPLAY_INSERT(bounce_message_tree, &messages, msg); log_debug("debug: bounce: new message %08" PRIx32, msg->msgid); stat_increment("bounce.message", 1); @@ -168,8 +172,8 @@ bounce_add(uint64_t evpid) be->id = evpid; be->report = xstrdup(buf, "bounce_add"); TAILQ_INSERT_TAIL(&msg->envelopes, be, entry); - log_debug("debug: bounce: adding report %16"PRIx64": %s", be->id, - be->report); + buf[strcspn(buf, "\n")] = '\0'; + log_debug("debug: bounce: adding report %16"PRIx64": %s", be->id, buf); msg->timeout = time(NULL) + 1; TAILQ_INSERT_TAIL(&pending, msg, entry); @@ -182,16 +186,23 @@ void bounce_fd(int fd) { struct bounce_session *s; + struct bounce_message *msg; log_debug("debug: bounce: got enqueue socket %d", fd); - if (fd == -1) { + if (fd == -1 || TAILQ_EMPTY(&pending)) { + log_debug("debug: bounce: cancelling"); + if (fd != -1) + close(fd); running -= 1; bounce_drain(); return; } + msg = TAILQ_FIRST(&pending); + s = xcalloc(1, sizeof(*s), "bounce_fd"); + s->smtpname = xstrdup(msg->smtpname, "bounce_fd"); s->state = BOUNCE_EHLO; iobuf_xinit(&s->iobuf, 0, 0, "bounce_run"); io_init(&s->io, fd, s, bounce_io, &s->iobuf); @@ -323,11 +334,20 @@ bounce_next_message(struct bounce_session *s) struct bounce_message *msg; char buf[SMTPD_MAXLINESIZE]; int fd; + time_t now; again: - msg = TAILQ_FIRST(&pending); - if (msg == NULL || msg->timeout > time(NULL)) + now = time(NULL); + + TAILQ_FOREACH(msg, &pending, entry) { + if (msg->timeout > now) + continue; + if (strcmp(msg->smtpname, s->smtpname)) + continue; + break; + } + if (msg == NULL) return (0); TAILQ_REMOVE(&pending, msg, entry); @@ -360,7 +380,7 @@ bounce_next(struct bounce_session *s) switch (s->state) { case BOUNCE_EHLO: - bounce_send(s, "EHLO %s", env->sc_hostname); + bounce_send(s, "EHLO %s", s->smtpname); s->state = BOUNCE_MAIL; break; @@ -401,7 +421,7 @@ bounce_next(struct bounce_session *s) NOTICE_INTRO "\n", (s->msg->bounce.type == B_ERROR) ? "error" : "warning", - env->sc_hostname, + s->smtpname, s->msg->to, time_to_text(time(NULL))); @@ -515,6 +535,7 @@ bounce_delivery(struct bounce_message *msg, int delivery, const char *status) queue_envelope_delete(be->id); } TAILQ_REMOVE(&msg->envelopes, be, entry); + free(be->report); free(be); n += 1; } @@ -522,6 +543,8 @@ bounce_delivery(struct bounce_message *msg, int delivery, const char *status) nmessage -= 1; stat_decrement("bounce.message", 1); stat_decrement("bounce.envelope", n); + free(msg->smtpname); + free(msg->to); free(msg); } @@ -563,6 +586,8 @@ bounce_free(struct bounce_session *s) iobuf_clear(&s->iobuf); io_clear(&s->io); + + free(s->smtpname); free(s); running -= 1; @@ -648,10 +673,15 @@ static int bounce_message_cmp(const struct bounce_message *a, const struct bounce_message *b) { + int r; + if (a->msgid < b->msgid) return (-1); if (a->msgid > b->msgid) return (1); + if ((r = strcmp(a->smtpname, b->smtpname))) + return (r); + return memcmp(&a->bounce, &b->bounce, sizeof (a->bounce)); } diff --git a/usr.sbin/smtpd/envelope.c b/usr.sbin/smtpd/envelope.c index 92992cafa0e..aa884d97afc 100644 --- a/usr.sbin/smtpd/envelope.c +++ b/usr.sbin/smtpd/envelope.c @@ -1,4 +1,4 @@ -/* $OpenBSD: envelope.c,v 1.21 2013/10/26 20:32:48 eric Exp $ */ +/* $OpenBSD: envelope.c,v 1.22 2013/11/06 10:01:29 eric Exp $ */ /* * Copyright (c) 2013 Eric Faurot <eric@openbsd.org> @@ -44,6 +44,7 @@ #include "smtpd.h" #include "log.h" +static int envelope_upgrade_v1(struct dict *); static int envelope_ascii_load(struct envelope *, struct dict *); static void envelope_ascii_dump(const struct envelope *, char **, size_t *, const char *); @@ -139,8 +140,16 @@ envelope_load_buffer(struct envelope *ep, const char *ibuf, size_t buflen) switch (version) { case 1: - /* very very okd envelopes may still have this */ - dict_pop(&d, "msgid"); + log_debug("debug: upgrading envelope to version 1"); + if (!envelope_upgrade_v1(&d)) { + log_debug("debug: failed to upgrade envelope to version 1"); + goto end; + } + /* FALLTRHOUGH */ + case 2: + /* Can be missing in some v2 envelopes */ + if (dict_get(&d, "smtpname") == NULL) + dict_xset(&d, "smtpname", env->sc_hostname); break; default: log_debug("debug: bad envelope version %lld", version); @@ -165,6 +174,7 @@ envelope_dump_buffer(const struct envelope *ep, char *dest, size_t len) envelope_ascii_dump(ep, &dest, &len, "version"); envelope_ascii_dump(ep, &dest, &len, "tag"); envelope_ascii_dump(ep, &dest, &len, "type"); + envelope_ascii_dump(ep, &dest, &len, "smtpname"); envelope_ascii_dump(ep, &dest, &len, "helo"); envelope_ascii_dump(ep, &dest, &len, "hostname"); envelope_ascii_dump(ep, &dest, &len, "errorline"); @@ -190,7 +200,9 @@ envelope_dump_buffer(const struct envelope *ep, char *dest, size_t len) envelope_ascii_dump(ep, &dest, &len, "mta-relay"); envelope_ascii_dump(ep, &dest, &len, "mta-relay-auth"); envelope_ascii_dump(ep, &dest, &len, "mta-relay-cert"); - envelope_ascii_dump(ep, &dest, &len, "mta-relay-helo"); + envelope_ascii_dump(ep, &dest, &len, "mta-relay-flags"); + envelope_ascii_dump(ep, &dest, &len, "mta-relay-heloname"); + envelope_ascii_dump(ep, &dest, &len, "mta-relay-helotable"); envelope_ascii_dump(ep, &dest, &len, "mta-relay-source"); break; case D_BOUNCE: @@ -347,6 +359,23 @@ ascii_load_mta_relay_url(struct relayhost *relay, char *buf) } static int +ascii_load_mta_relay_flags(uint16_t *dest, char *buf) +{ + char *flag; + + while ((flag = strsep(&buf, " ,|")) != NULL) { + if (strcasecmp(flag, "verify") == 0) + *dest |= F_TLS_VERIFY; + else if (strcasecmp(flag, "tls") == 0) + *dest |= F_STARTTLS; + else + return 0; + } + + return 1; +} + +static int ascii_load_bounce_type(enum bounce_type *dest, char *buf) { if (strcasecmp(buf, "error") == 0) @@ -432,7 +461,14 @@ ascii_load_field(const char *field, struct envelope *ep, char *buf) return ascii_load_string(ep->agent.mta.relay.cert, buf, sizeof ep->agent.mta.relay.cert); - if (strcasecmp("mta-relay-helo", field) == 0) + if (strcasecmp("mta-relay-flags", field) == 0) + return ascii_load_mta_relay_flags(&ep->agent.mta.relay.flags, buf); + + if (strcasecmp("mta-relay-heloname", field) == 0) + return ascii_load_string(ep->agent.mta.relay.heloname, buf, + sizeof ep->agent.mta.relay.heloname); + + if (strcasecmp("mta-relay-helotable", field) == 0) return ascii_load_string(ep->agent.mta.relay.helotable, buf, sizeof ep->agent.mta.relay.helotable); @@ -449,6 +485,9 @@ ascii_load_field(const char *field, struct envelope *ep, char *buf) if (strcasecmp("sender", field) == 0) return ascii_load_mailaddr(&ep->sender, buf); + if (strcasecmp("smtpname", field) == 0) + return ascii_load_string(ep->smtpname, buf, sizeof(ep->smtpname)); + if (strcasecmp("sockaddr", field) == 0) return ascii_load_sockaddr(&ep->ss, buf); @@ -594,6 +633,28 @@ ascii_dump_mta_relay_url(const struct relayhost *relay, char *buf, size_t len) } static int +ascii_dump_mta_relay_flags(uint16_t flags, char *buf, size_t len) +{ + size_t cpylen = 0; + + buf[0] = '\0'; + if (flags) { + if (flags & F_TLS_VERIFY) { + if (buf[0] != '\0') + strlcat(buf, " ", len); + cpylen = strlcat(buf, "verify", len); + } + if (flags & F_STARTTLS) { + if (buf[0] != '\0') + strlcat(buf, " ", len); + cpylen = strlcat(buf, "tls", len); + } + } + + return cpylen < len ? 1 : 0; +} + +static int ascii_dump_bounce_type(enum bounce_type type, char *dest, size_t len) { char *p = NULL; @@ -683,7 +744,15 @@ ascii_dump_field(const char *field, const struct envelope *ep, return ascii_dump_string(ep->agent.mta.relay.cert, buf, len); - if (strcasecmp(field, "mta-relay-helo") == 0) + if (strcasecmp(field, "mta-relay-flags") == 0) + return ascii_dump_mta_relay_flags(ep->agent.mta.relay.flags, + buf, len); + + if (strcasecmp(field, "mta-relay-heloname") == 0) + return ascii_dump_string(ep->agent.mta.relay.heloname, + buf, len); + + if (strcasecmp(field, "mta-relay-helotable") == 0) return ascii_dump_string(ep->agent.mta.relay.helotable, buf, len); @@ -700,6 +769,9 @@ ascii_dump_field(const char *field, const struct envelope *ep, if (strcasecmp(field, "sender") == 0) return ascii_dump_mailaddr(&ep->sender, buf, len); + if (strcasecmp(field, "smtpname") == 0) + return ascii_dump_string(ep->smtpname, buf, len); + if (strcasecmp(field, "sockaddr") == 0) return ascii_dump_string(ss_to_text(&ep->ss), buf, len); @@ -740,3 +812,41 @@ envelope_ascii_dump(const struct envelope *ep, char **dest, size_t *len, const c err: *dest = NULL; } + +static int +envelope_upgrade_v1(struct dict *d) +{ + static char buf_relay[1024]; + char *val; + + /* + * very very old envelopes had a "msgid" field + */ + dict_pop(d, "msgid"); + + /* + * rename "mta-relay-helo" field to "mta-relay-helotable" + */ + if ((val = dict_get(d, "mta-relay-helo"))) { + dict_xset(d, "mta-relay-helotable", val); + dict_xpop(d, "mta-relay-helo"); + } + + /* + * "ssl" becomes "secure" in "mta-relay" scheme + */ + if ((val = dict_get(d, "mta-relay"))) { + if (strncasecmp("ssl://", val, 6) == 0) { + if (! bsnprintf(buf_relay, sizeof(buf_relay), "secure://%s", val+6)) + return (0); + dict_set(d, "mta-relay", buf_relay); + } + else if (strncasecmp("ssl+auth://", val, 11) == 0) { + if (! bsnprintf(buf_relay, sizeof(buf_relay), "secure+auth://%s", val+11)) + return (0); + dict_set(d, "mta-relay", buf_relay); + } + } + + return (1); +} diff --git a/usr.sbin/smtpd/lka.c b/usr.sbin/smtpd/lka.c index 7a550bbcfea..1f9153566ba 100644 --- a/usr.sbin/smtpd/lka.c +++ b/usr.sbin/smtpd/lka.c @@ -1,4 +1,4 @@ -/* $OpenBSD: lka.c,v 1.157 2013/10/28 17:02:08 eric Exp $ */ +/* $OpenBSD: lka.c,v 1.158 2013/11/06 10:01:29 eric Exp $ */ /* * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> @@ -105,6 +105,24 @@ lka_imsg(struct mproc *p, struct imsg *imsg) lka_session(reqid, &evp); return; + case IMSG_LKA_HELO: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_string(&m, &tablename); + m_get_sockaddr(&m, (struct sockaddr *)&ss); + m_end(&m); + + ret = lka_addrname(tablename, (struct sockaddr*)&ss, + &addrname); + + m_create(p, IMSG_LKA_HELO, 0, 0, -1); + m_add_id(p, reqid); + m_add_int(p, ret); + if (ret == LKA_OK) + m_add_string(p, addrname.name); + m_close(p); + return; + case IMSG_LKA_SSL_INIT: req_ca_cert = imsg->data; resp_ca_cert.reqid = req_ca_cert->reqid; @@ -431,6 +449,16 @@ lka_imsg(struct mproc *p, struct imsg *imsg) env->sc_tables_dict = tmp; return; + case IMSG_CONF_RULE_RECIPIENT: + rule = TAILQ_LAST(env->sc_rules_reload, rulelist); + tmp = env->sc_tables_dict; + env->sc_tables_dict = tables_dict; + rule->r_recipients = table_find(imsg->data, NULL); + if (rule->r_recipients == NULL) + fatalx("lka: tables inconsistency"); + env->sc_tables_dict = tmp; + return; + case IMSG_CONF_RULE_DESTINATION: rule = TAILQ_LAST(env->sc_rules_reload, rulelist); tmp = env->sc_tables_dict; diff --git a/usr.sbin/smtpd/lka_session.c b/usr.sbin/smtpd/lka_session.c index 4fc3ba044a5..5ebef4b854d 100644 --- a/usr.sbin/smtpd/lka_session.c +++ b/usr.sbin/smtpd/lka_session.c @@ -1,4 +1,4 @@ -/* $OpenBSD: lka_session.c,v 1.59 2013/10/28 09:14:58 eric Exp $ */ +/* $OpenBSD: lka_session.c,v 1.60 2013/11/06 10:01:29 eric Exp $ */ /* * Copyright (c) 2011 Gilles Chehade <gilles@poolp.org> @@ -142,9 +142,21 @@ lka_session_forward_reply(struct forward_req *fwreq, int fd) break; case 1: if (fd == -1) { - log_trace(TRACE_EXPAND, "expand: no .forward for " - "user %s, just deliver", fwreq->user); - lka_submit(lks, rule, xn); + if (lks->expand.rule->r_forwardonly) { + log_trace(TRACE_EXPAND, "expand: no .forward " + "for user %s on forward-only rule", fwreq->user); + lks->error = LKA_TEMPFAIL; + } + else if (lks->expand.rule->r_action == A_NONE) { + log_trace(TRACE_EXPAND, "expand: no .forward " + "for user %s and no default action on rule", fwreq->user); + lks->error = LKA_PERMFAIL; + } + else { + log_trace(TRACE_EXPAND, "expand: no .forward for " + "user %s, just deliver", fwreq->user); + lka_submit(lks, rule, xn); + } } else { /* expand for the current user and rule */ @@ -161,9 +173,21 @@ lka_session_forward_reply(struct forward_req *fwreq, int fd) lks->error = LKA_TEMPFAIL; } else if (ret == 0) { - log_trace(TRACE_EXPAND, "expand: empty .forward " - "for user %s, just deliver", fwreq->user); - lka_submit(lks, rule, xn); + if (lks->expand.rule->r_forwardonly) { + log_trace(TRACE_EXPAND, "expand: empty .forward " + "for user %s on forward-only rule", fwreq->user); + lks->error = LKA_TEMPFAIL; + } + else if (lks->expand.rule->r_action == A_NONE) { + log_trace(TRACE_EXPAND, "expand: empty .forward " + "for user %s and no default action on rule", fwreq->user); + lks->error = LKA_PERMFAIL; + } + else { + log_trace(TRACE_EXPAND, "expand: empty .forward " + "for user %s, just deliver", fwreq->user); + lka_submit(lks, rule, xn); + } } } break; @@ -394,12 +418,22 @@ lka_expand(struct lka_session *lks, struct rule *rule, struct expandnode *xn) break; case EXPAND_FILENAME: + if (rule->r_forwardonly) { + log_trace(TRACE_EXPAND, "expand: filename matched on forward-only rule"); + lks->error = LKA_TEMPFAIL; + break; + } log_trace(TRACE_EXPAND, "expand: lka_expand: filename: %s " "[depth=%d]", xn->u.buffer, xn->depth); lka_submit(lks, rule, xn); break; case EXPAND_ERROR: + if (rule->r_forwardonly) { + log_trace(TRACE_EXPAND, "expand: error matched on forward-only rule"); + lks->error = LKA_TEMPFAIL; + break; + } log_trace(TRACE_EXPAND, "expand: lka_expand: error: %s " "[depth=%d]", xn->u.buffer, xn->depth); if (xn->u.buffer[0] == '4') @@ -410,6 +444,11 @@ lka_expand(struct lka_session *lks, struct rule *rule, struct expandnode *xn) break; case EXPAND_FILTER: + if (rule->r_forwardonly) { + log_trace(TRACE_EXPAND, "expand: filter matched on forward-only rule"); + lks->error = LKA_TEMPFAIL; + break; + } log_trace(TRACE_EXPAND, "expand: lka_expand: filter: %s " "[depth=%d]", xn->u.buffer, xn->depth); lka_submit(lks, rule, xn); @@ -459,6 +498,7 @@ lka_submit(struct lka_session *lks, struct rule *rule, struct expandnode *xn) strlcpy(ep->sender.domain, rule->r_as->domain, sizeof ep->sender.domain); break; + case A_NONE: case A_MBOX: case A_MAILDIR: case A_FILENAME: diff --git a/usr.sbin/smtpd/mta.c b/usr.sbin/smtpd/mta.c index f1d30012db3..7c827ab675a 100644 --- a/usr.sbin/smtpd/mta.c +++ b/usr.sbin/smtpd/mta.c @@ -1,4 +1,4 @@ -/* $OpenBSD: mta.c,v 1.170 2013/10/30 21:37:48 eric Exp $ */ +/* $OpenBSD: mta.c,v 1.171 2013/11/06 10:01:29 eric Exp $ */ /* * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> @@ -1551,6 +1551,8 @@ mta_relay(struct envelope *e) key.flags |= RELAY_MX; } else { key.domain = mta_domain(e->dest.domain, 0); + if (!(e->agent.mta.relay.flags & RELAY_STARTTLS)) + key.flags |= RELAY_TLS_OPTIONAL; } key.flags |= e->agent.mta.relay.flags; diff --git a/usr.sbin/smtpd/mta_session.c b/usr.sbin/smtpd/mta_session.c index 5616b1d7c23..56442c807b8 100644 --- a/usr.sbin/smtpd/mta_session.c +++ b/usr.sbin/smtpd/mta_session.c @@ -1,4 +1,4 @@ -/* $OpenBSD: mta_session.c,v 1.45 2013/10/29 17:04:45 eric Exp $ */ +/* $OpenBSD: mta_session.c,v 1.46 2013/11/06 10:01:29 eric Exp $ */ /* * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> @@ -324,10 +324,19 @@ mta_session_imsg(struct mproc *p, struct imsg *imsg) return; if (resp_ca_cert->status == CA_FAIL) { - log_info("smtp-out: Disconnecting session %016"PRIx64 - ": CA failure", s->id); - mta_free(s); - return; + if (s->relay->cert) { + log_info("smtp-out: Disconnecting session %016"PRIx64 + ": CA failure", s->id); + mta_free(s); + return; + } + else { + ssl = ssl_mta_init(NULL, 0, NULL, 0); + if (ssl == NULL) + fatal("mta: ssl_mta_init"); + io_start_tls(&s->io, ssl); + return; + } } resp_ca_cert = xmemdup(imsg->data, sizeof *resp_ca_cert, @@ -360,6 +369,12 @@ mta_session_imsg(struct mproc *p, struct imsg *imsg) if (resp_ca_vrfy->status == CA_OK) s->flags |= MTA_VERIFIED; + else if (s->relay->flags & F_TLS_VERIFY) { + errno = 0; + mta_error(s, "SSL certificate check failed"); + mta_free(s); + return; + } mta_io(&s->io, IO_TLSVERIFIED); io_resume(&s->io, IO_PAUSE_IN); @@ -1495,22 +1510,20 @@ static void mta_start_tls(struct mta_session *s) { struct ca_cert_req_msg req_ca_cert; - void *ssl; - - if (s->relay->cert) { - req_ca_cert.reqid = s->id; - strlcpy(req_ca_cert.name, s->relay->cert, - sizeof req_ca_cert.name); - m_compose(p_lka, IMSG_LKA_SSL_INIT, 0, 0, -1, - &req_ca_cert, sizeof(req_ca_cert)); - tree_xset(&wait_ssl_init, s->id, s); - s->flags |= MTA_WAIT; - return; - } - ssl = ssl_mta_init(NULL, 0, NULL, 0); - if (ssl == NULL) - fatal("mta: ssl_mta_init"); - io_start_tls(&s->io, ssl); + const char *certname; + + if (s->relay->cert) + certname = s->relay->cert; + else + certname = s->helo; + + req_ca_cert.reqid = s->id; + strlcpy(req_ca_cert.name, certname, sizeof req_ca_cert.name); + m_compose(p_lka, IMSG_LKA_SSL_INIT, 0, 0, -1, + &req_ca_cert, sizeof(req_ca_cert)); + tree_xset(&wait_ssl_init, s->id, s); + s->flags |= MTA_WAIT; + return; } static int diff --git a/usr.sbin/smtpd/parse.y b/usr.sbin/smtpd/parse.y index 4b484a31720..83875ee924e 100644 --- a/usr.sbin/smtpd/parse.y +++ b/usr.sbin/smtpd/parse.y @@ -1,4 +1,4 @@ -/* $OpenBSD: parse.y,v 1.125 2013/10/27 11:01:47 eric Exp $ */ +/* $OpenBSD: parse.y,v 1.126 2013/11/06 10:01:29 eric Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org> @@ -50,7 +50,10 @@ #include <unistd.h> #include <util.h> +#include <openssl/ssl.h> + #include "smtpd.h" +#include "ssl.h" #include "log.h" TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files); @@ -93,15 +96,30 @@ struct table *table = NULL; struct rule *rule = NULL; struct listener l; struct mta_limits *limits; +static struct ssl *pki_ssl; + +static struct listen_opts { + char *ifx; + int family; + in_port_t port; + uint16_t ssl; + char *pki; + uint16_t auth; + struct table *authtable; + char *tag; + char *hostname; + struct table *hostnametable; + uint16_t flags; +} listen_opts; + +static void create_listener(struct listenerlist *, struct listen_opts *); +static void config_listener(struct listener *, struct listen_opts *); struct listener *host_v4(const char *, in_port_t); struct listener *host_v6(const char *, in_port_t); -int host_dns(const char *, const char *, const char *, - struct listenerlist *, int, in_port_t, uint8_t); -int host(const char *, const char *, const char *, - struct listenerlist *, int, in_port_t, const char *, uint8_t, const char *); -int interface(const char *, int, const char *, const char *, - struct listenerlist *, int, in_port_t, const char *, uint8_t, const char *); +int host_dns(struct listenerlist *, struct listen_opts *); +int host(struct listenerlist *, struct listen_opts *); +int interface(struct listenerlist *, struct listen_opts *); void set_localaddrs(void); int delaytonum(char *); int is_if_in_group(const char *, const char *); @@ -124,18 +142,17 @@ typedef struct { %} %token AS QUEUE COMPRESSION ENCRYPTION MAXMESSAGESIZE LISTEN ON ANY PORT EXPIRE -%token TABLE SSL SMTPS CERTIFICATE DOMAIN BOUNCEWARN LIMIT INET4 INET6 -%token RELAY BACKUP VIA DELIVER TO LMTP MAILDIR MBOX HOSTNAME HELO -%token ACCEPT REJECT INCLUDE ERROR MDA FROM FOR SOURCE MTA -%token ARROW AUTH TLS LOCAL VIRTUAL TAG TAGGED ALIAS FILTER FILTERCHAIN KEY -%token AUTH_OPTIONAL TLS_REQUIRE USERBASE SENDER +%token TABLE SECURE SMTPS CERTIFICATE DOMAIN BOUNCEWARN LIMIT INET4 INET6 +%token RELAY BACKUP VIA DELIVER TO LMTP MAILDIR MBOX HOSTNAME HOSTNAMES +%token ACCEPT REJECT INCLUDE ERROR MDA FROM FOR SOURCE MTA PKI +%token ARROW AUTH TLS LOCAL VIRTUAL TAG TAGGED ALIAS FILTER FILTERCHAIN KEY CA DHPARAMS +%token AUTH_OPTIONAL TLS_REQUIRE USERBASE SENDER MASK_SOURCE VERIFY FORWARDONLY RECIPIENT %token <v.string> STRING %token <v.number> NUMBER %type <v.table> table -%type <v.number> port auth ssl size expire address_family -%type <v.table> tables tablenew tableref destination alias virtual usermapping userbase credentials from sender -%type <v.maddr> relay_as -%type <v.string> certificate tag tagged relay_source listen_helo relay_helo relay_backup +%type <v.number> size negation +%type <v.table> tables tablenew tableref alias virtual userbase +%type <v.string> tagged %% grammar : /* empty */ @@ -203,7 +220,83 @@ size : NUMBER { } ; -port : PORT STRING { +tagged : TAGGED negation STRING { + if (strlcpy(rule->r_tag, $3, sizeof rule->r_tag) + >= sizeof rule->r_tag) { + yyerror("tag name too long: %s", $3); + free($3); + YYERROR; + } + free($3); + rule->r_nottag = $2; + } + ; + +bouncedelay : STRING { + time_t d; + int i; + + d = delaytonum($1); + if (d < 0) { + yyerror("invalid bounce delay: %s", $1); + free($1); + YYERROR; + } + free($1); + for (i = 0; i < MAX_BOUNCE_WARN; i++) { + if (conf->sc_bounce_warn[i] != 0) + continue; + conf->sc_bounce_warn[i] = d; + break; + } + } + +bouncedelays : bouncedelays ',' bouncedelay + | bouncedelay + | /* EMPTY */ + ; + +opt_limit : INET4 { + limits->family = AF_INET; + } + | INET6 { + limits->family = AF_INET6; + } + | STRING NUMBER { + if (!limit_mta_set(limits, $1, $2)) { + yyerror("invalid limit keyword"); + free($1); + YYERROR; + } + free($1); + } + ; + +limits : opt_limit limits + | /* empty */ + ; + +opt_pki : CERTIFICATE STRING { + pki_ssl->ssl_cert_file = $2; + } + | KEY STRING { + pki_ssl->ssl_key_file = $2; + } + | CA STRING { + pki_ssl->ssl_ca_file = $2; + } + | DHPARAMS STRING { + pki_ssl->ssl_dhparams_file = $2; + } + ; + +pki : opt_pki pki + | /* empty */ + ; + +opt_listen : INET4 { listen_opts.family = AF_INET; } + | INET6 { listen_opts.family = AF_INET6; } + | PORT STRING { struct servent *servent; servent = getservbyname($2, "tcp"); @@ -213,115 +306,141 @@ port : PORT STRING { YYERROR; } free($2); - $$ = ntohs(servent->s_port); + listen_opts.port = ntohs(servent->s_port); } | PORT NUMBER { if ($2 <= 0 || $2 >= (int)USHRT_MAX) { yyerror("invalid port: %" PRId64, $2); YYERROR; } - $$ = $2; + listen_opts.port = $2; + } + | SMTPS { listen_opts.ssl = F_SMTPS; } + | SMTPS VERIFY { listen_opts.ssl = F_SMTPS|F_TLS_VERIFY; } + | TLS { listen_opts.ssl = F_STARTTLS; } + | SECURE { listen_opts.ssl = F_SSL; } + | TLS_REQUIRE { listen_opts.ssl = F_STARTTLS|F_STARTTLS_REQUIRE; } + | TLS_REQUIRE VERIFY { listen_opts.ssl = F_STARTTLS|F_STARTTLS_REQUIRE|F_TLS_VERIFY; } + | PKI STRING { listen_opts.pki = $2; } + | AUTH { listen_opts.auth = F_AUTH|F_AUTH_REQUIRE; } + | AUTH_OPTIONAL { listen_opts.auth = F_AUTH; } + | AUTH tables { + listen_opts.authtable = $2; + listen_opts.auth = F_AUTH|F_AUTH_REQUIRE; } - | /* empty */ { - $$ = 0; + | AUTH_OPTIONAL tables { + listen_opts.authtable = $2; + listen_opts.auth = F_AUTH; } - ; - -certificate : CERTIFICATE STRING { - if (($$ = strdup($2)) == NULL) { - yyerror("strdup"); + | TAG STRING { + if (strlen($2) >= MAX_TAG_SIZE) { + yyerror("tag name too long"); free($2); YYERROR; } - free($2); + listen_opts.tag = $2; } - | /* empty */ { $$ = NULL; } + | HOSTNAME STRING { listen_opts.hostname = $2; } + | HOSTNAMES tables { + struct table *t = $2; + if (! table_check_use(t, T_DYNAMIC|T_HASH, K_ADDRNAME)) { + yyerror("invalid use of table \"%s\" as " + "HOSTNAMES parameter", t->t_name); + YYERROR; + } + listen_opts.hostnametable = t; + } + | MASK_SOURCE { listen_opts.flags |= F_MASK_SOURCE; } ; -ssl : SMTPS { $$ = F_SMTPS; } - | TLS { $$ = F_STARTTLS; } - | SSL { $$ = F_SSL; } - | TLS_REQUIRE { $$ = F_STARTTLS|F_STARTTLS_REQUIRE; } - | /* Empty */ { $$ = 0; } +listen : opt_listen listen + | /* empty */ ; -auth : AUTH { - $$ = F_AUTH|F_AUTH_REQUIRE; - } - | AUTH_OPTIONAL { - $$ = F_AUTH; - } - | AUTH tables { - strlcpy(l.authtable, ($2)->t_name, sizeof l.authtable); - $$ = F_AUTH|F_AUTH_REQUIRE; - } - | AUTH_OPTIONAL tables { - strlcpy(l.authtable, ($2)->t_name, sizeof l.authtable); - $$ = F_AUTH; - } - | /* empty */ { $$ = 0; } - ; +opt_relay_common: AS STRING { + struct mailaddr maddr, *maddrp; -tag : TAG STRING { - if (strlen($2) >= MAX_TAG_SIZE) { - yyerror("tag name too long"); + if (! text_to_mailaddr(&maddr, $2)) { + yyerror("invalid parameter to AS: %s", $2); free($2); YYERROR; } + free($2); - $$ = $2; + if (maddr.user[0] == '\0' && maddr.domain[0] == '\0') { + yyerror("invalid empty parameter to AS"); + YYERROR; + } + else if (maddr.domain[0] == '\0') { + if (strlcpy(maddr.domain, conf->sc_hostname, + sizeof (maddr.domain)) + >= sizeof (maddr.domain)) { + yyerror("hostname too long for AS parameter: %s", + conf->sc_hostname); + YYERROR; + } + } + rule->r_as = xmemdup(&maddr, sizeof (*maddrp), "parse relay_as: AS"); } - | /* empty */ { $$ = NULL; } - ; - -tagged : TAGGED STRING { - if (($$ = strdup($2)) == NULL) { - yyerror("strdup"); - free($2); + | SOURCE tables { + struct table *t = $2; + if (! table_check_use(t, T_DYNAMIC|T_LIST, K_SOURCE)) { + yyerror("invalid use of table \"%s\" as " + "SOURCE parameter", t->t_name); YYERROR; } + strlcpy(rule->r_value.relayhost.sourcetable, t->t_name, + sizeof rule->r_value.relayhost.sourcetable); + } + | HOSTNAME STRING { + strlcat(rule->r_value.relayhost.heloname, $2, + sizeof rule->r_value.relayhost.heloname); free($2); } - | /* empty */ { $$ = NULL; } - ; - -expire : EXPIRE STRING { - $$ = delaytonum($2); - if ($$ == -1) { - yyerror("invalid expire delay: %s", $2); - free($2); + | HOSTNAMES tables { + struct table *t = $2; + if (! table_check_use(t, T_DYNAMIC|T_HASH, K_ADDRNAME)) { + yyerror("invalid use of table \"%s\" as " + "HOSTNAMES parameter", t->t_name); YYERROR; } + strlcpy(rule->r_value.relayhost.helotable, t->t_name, + sizeof rule->r_value.relayhost.helotable); + } + | PKI STRING { + if (strlcpy(rule->r_value.relayhost.cert, $2, + sizeof(rule->r_value.relayhost.cert)) + >= sizeof(rule->r_value.relayhost.cert)) + fatal("certificate path too long"); free($2); } - | /* empty */ { $$ = conf->sc_qexpire; } ; -bouncedelay : STRING { - time_t d; - int i; - - d = delaytonum($1); - if (d < 0) { - yyerror("invalid bounce delay: %s", $1); - free($1); - YYERROR; - } - free($1); - for (i = 0; i < MAX_BOUNCE_WARN; i++) { - if (conf->sc_bounce_warn[i] != 0) - continue; - conf->sc_bounce_warn[i] = d; - break; - } +opt_relay : BACKUP STRING { + rule->r_value.relayhost.flags |= F_BACKUP; + strlcpy(rule->r_value.relayhost.hostname, $2, + sizeof (rule->r_value.relayhost.hostname)); } + | BACKUP { + rule->r_value.relayhost.flags |= F_BACKUP; + strlcpy(rule->r_value.relayhost.hostname, + conf->sc_hostname, + sizeof (rule->r_value.relayhost.hostname)); + } + | TLS { + rule->r_value.relayhost.flags |= F_STARTTLS; + } + | TLS VERIFY { + rule->r_value.relayhost.flags |= F_STARTTLS|F_TLS_VERIFY; + } + ; -bouncedelays : bouncedelays ',' bouncedelay - | bouncedelay - | /* EMPTY */ +relay : opt_relay_common relay + | opt_relay relay + | /* empty */ ; -credentials : AUTH tables { +opt_relay_via : AUTH tables { struct table *t = $2; if (! table_check_use(t, T_DYNAMIC|T_HASH, K_CREDENTIALS)) { @@ -329,38 +448,20 @@ credentials : AUTH tables { t->t_name); YYERROR; } - - $$ = t; + strlcpy(rule->r_value.relayhost.authtable, t->t_name, + sizeof(rule->r_value.relayhost.authtable)); } - | /* empty */ { $$ = 0; } - ; - -address_family : INET4 { $$ = AF_INET; } - | INET6 { $$ = AF_INET6; } - | /* empty */ { $$ = AF_UNSPEC; } - ; - -listen_helo : HOSTNAME STRING { $$ = $2; } - | /* empty */ { $$ = NULL; } - ; - -opt_limit : INET4 { - limits->family = AF_INET; - } - | INET6 { - limits->family = AF_INET6; - } - | STRING NUMBER { - if (!limit_mta_set(limits, $1, $2)) { - yyerror("invalid limit keyword"); - free($1); + | VERIFY { + if (!(rule->r_value.relayhost.flags & F_SSL)) { + yyerror("cannot \"verify\" with insecure protocol"); YYERROR; } - free($1); + rule->r_value.relayhost.flags |= F_TLS_VERIFY; } ; -limits : opt_limit limits +relay_via : opt_relay_common relay_via + | opt_relay_via relay_via | /* empty */ ; @@ -370,9 +471,45 @@ main : BOUNCEWARN { | QUEUE COMPRESSION { conf->sc_queue_flags |= QUEUE_COMPRESSION; } + | QUEUE ENCRYPTION { + char *password; + + password = getpass("queue key: "); + if (password == NULL) { + yyerror("getpass() error"); + YYERROR; + } + conf->sc_queue_key = strdup(password); + bzero(password, strlen(password)); + if (conf->sc_queue_key == NULL) { + yyerror("memory exhausted"); + YYERROR; + } + conf->sc_queue_flags |= QUEUE_ENCRYPTION; + + } | QUEUE ENCRYPTION KEY STRING { + char *buf; + char *lbuf; + size_t len; + + if (strcasecmp($4, "stdin") == 0 || + strcasecmp($4, "-") == 0) { + lbuf = NULL; + buf = fgetln(stdin, &len); + if (buf[len - 1] == '\n') { + lbuf = calloc(len, 1); + memcpy(lbuf, buf, len-1); + } + else { + lbuf = calloc(len+1, 1); + memcpy(lbuf, buf, len); + } + conf->sc_queue_key = lbuf; + } + else + conf->sc_queue_key = $4; conf->sc_queue_flags |= QUEUE_ENCRYPTION; - conf->sc_queue_key = $4; } | EXPIRE STRING { conf->sc_qexpire = delaytonum($2); @@ -403,58 +540,11 @@ main : BOUNCEWARN { } limits | LISTEN { bzero(&l, sizeof l); - } ON STRING address_family port ssl certificate auth tag listen_helo { - char *ifx = $4; - int family = $5; - in_port_t port = $6; - uint8_t ssl = $7; - char *cert = $8; - uint8_t auth = $9; - char *tag = $10; - char *helo = $11; - - if (port != 0 && ssl == F_SSL) { - yyerror("invalid listen option: tls/smtps on same port"); - YYERROR; - } - - if (auth != 0 && !ssl) { - yyerror("invalid listen option: auth requires tls/smtps"); - YYERROR; - } - - if (port == 0) { - if (ssl & F_SMTPS) { - if (! interface(ifx, family, tag, cert, conf->sc_listeners, - MAX_LISTEN, 465, l.authtable, F_SMTPS|auth, helo)) { - if (host(ifx, tag, cert, conf->sc_listeners, - MAX_LISTEN, 465, l.authtable, ssl|auth, helo) <= 0) { - yyerror("invalid virtual ip or interface: %s", ifx); - YYERROR; - } - } - } - if (! ssl || (ssl & ~F_SMTPS)) { - if (! interface(ifx, family, tag, cert, conf->sc_listeners, - MAX_LISTEN, 25, l.authtable, (ssl&~F_SMTPS)|auth, helo)) { - if (host(ifx, tag, cert, conf->sc_listeners, - MAX_LISTEN, 25, l.authtable, ssl|auth, helo) <= 0) { - yyerror("invalid virtual ip or interface: %s", ifx); - YYERROR; - } - } - } - } - else { - if (! interface(ifx, family, tag, cert, conf->sc_listeners, - MAX_LISTEN, port, l.authtable, ssl|auth, helo)) { - if (host(ifx, tag, cert, conf->sc_listeners, - MAX_LISTEN, port, l.authtable, ssl|auth, helo) <= 0) { - yyerror("invalid virtual ip or interface: %s", ifx); - YYERROR; - } - } - } + bzero(&listen_opts, sizeof listen_opts); + listen_opts.family = AF_UNSPEC; + } ON STRING listen { + listen_opts.ifx = $4; + create_listener(conf->sc_listeners, &listen_opts); } | FILTER STRING STRING { if (!create_filter($2, $3)) { @@ -472,6 +562,16 @@ main : BOUNCEWARN { } } filter_list ; + | PKI STRING { + pki_ssl = dict_get(conf->sc_ssl_dict, $2); + if (pki_ssl == NULL) { + pki_ssl = xcalloc(1, sizeof *pki_ssl, "parse:pki"); + xlowercase(pki_ssl->ssl_name, $2, sizeof pki_ssl->ssl_name); + dict_set(conf->sc_ssl_dict, pki_ssl->ssl_name, pki_ssl); + } + free($2); + } pki + ; table : TABLE STRING STRING { char *p, *backend, *config; @@ -610,332 +710,310 @@ virtual : VIRTUAL tables { t->t_name); YYERROR; } - $$ = t; } ; usermapping : alias { + if (rule->r_mapping) { + yyerror("alias specified multiple times"); + YYERROR; + } rule->r_desttype = DEST_DOM; - $$ = $1; + rule->r_mapping = $1; } | virtual { + if (rule->r_mapping) { + yyerror("virtual specified multiple times"); + YYERROR; + } rule->r_desttype = DEST_VDOM; - $$ = $1; - } - | /**/ { - rule->r_desttype = DEST_DOM; - $$ = 0; + rule->r_mapping = $1; } ; userbase : USERBASE tables { struct table *t = $2; - if (! table_check_use(t, T_DYNAMIC|T_HASH, K_USERINFO)) { - yyerror("invalid use of table \"%s\" as USERBASE parameter", - t->t_name); + if (rule->r_userbase) { + yyerror("userbase specified multiple times"); YYERROR; } - - $$ = t; - } - | /**/ { $$ = table_find("<getpwnam>", NULL); } - ; - - - - -destination : DOMAIN tables { - struct table *t = $2; - - if (! table_check_use(t, T_DYNAMIC|T_LIST, K_DOMAIN)) { - yyerror("invalid use of table \"%s\" as DOMAIN parameter", + if (! table_check_use(t, T_DYNAMIC|T_HASH, K_USERINFO)) { + yyerror("invalid use of table \"%s\" as USERBASE parameter", t->t_name); YYERROR; } - - $$ = t; - } - | LOCAL { $$ = table_find("<localnames>", NULL); } - | ANY { $$ = 0; } - ; - -relay_source : SOURCE tables { - struct table *t = $2; - if (! table_check_use(t, T_DYNAMIC|T_LIST, K_SOURCE)) { - yyerror("invalid use of table \"%s\" as " - "SOURCE parameter", t->t_name); - YYERROR; - } - $$ = t->t_name; + rule->r_userbase = t; } - | { $$ = NULL; } ; -relay_helo : HELO tables { - struct table *t = $2; - if (! table_check_use(t, T_DYNAMIC|T_HASH, K_ADDRNAME)) { - yyerror("invalid use of table \"%s\" as " - "HELO parameter", t->t_name); - YYERROR; - } - $$ = t->t_name; - } - | { $$ = NULL; } - ; - -relay_backup : BACKUP STRING { $$ = $2; } - | BACKUP { $$ = NULL; } - ; - -relay_as : AS STRING { - struct mailaddr maddr, *maddrp; - - if (! text_to_mailaddr(&maddr, $2)) { - yyerror("invalid parameter to AS: %s", $2); - free($2); - YYERROR; - } - free($2); - - if (maddr.user[0] == '\0' && maddr.domain[0] == '\0') { - yyerror("invalid empty parameter to AS"); - YYERROR; - } - else if (maddr.domain[0] == '\0') { - if (strlcpy(maddr.domain, conf->sc_hostname, - sizeof (maddr.domain)) - >= sizeof (maddr.domain)) { - yyerror("hostname too long for AS parameter: %s", - conf->sc_hostname); - YYERROR; - } - } - $$ = xmemdup(&maddr, sizeof (*maddrp), "parse relay_as: AS"); - } - | /* empty */ { $$ = NULL; } - ; - -action : userbase DELIVER TO MAILDIR { - rule->r_userbase = $1; +deliver_action : DELIVER TO MAILDIR { rule->r_action = A_MAILDIR; if (strlcpy(rule->r_value.buffer, "~/Maildir", sizeof(rule->r_value.buffer)) >= sizeof(rule->r_value.buffer)) fatal("pathname too long"); } - | userbase DELIVER TO MAILDIR STRING { - rule->r_userbase = $1; + | DELIVER TO MAILDIR STRING { rule->r_action = A_MAILDIR; - if (strlcpy(rule->r_value.buffer, $5, + if (strlcpy(rule->r_value.buffer, $4, sizeof(rule->r_value.buffer)) >= sizeof(rule->r_value.buffer)) fatal("pathname too long"); - free($5); + free($4); } - | userbase DELIVER TO LMTP STRING { - rule->r_userbase = $1; + | DELIVER TO LMTP STRING { rule->r_action = A_LMTP; - if (strchr($5, ':') || $5[0] == '/') { - if (strlcpy(rule->r_value.buffer, $5, + if (strchr($4, ':') || $4[0] == '/') { + if (strlcpy(rule->r_value.buffer, $4, sizeof(rule->r_value.buffer)) >= sizeof(rule->r_value.buffer)) fatal("lmtp destination too long"); } else fatal("invalid lmtp destination"); - free($5); + free($4); } - | userbase DELIVER TO MBOX { - rule->r_userbase = $1; + | DELIVER TO MBOX { rule->r_action = A_MBOX; if (strlcpy(rule->r_value.buffer, _PATH_MAILDIR "/%u", sizeof(rule->r_value.buffer)) >= sizeof(rule->r_value.buffer)) fatal("pathname too long"); } - | userbase DELIVER TO MDA STRING { - rule->r_userbase = $1; + | DELIVER TO MDA STRING { rule->r_action = A_MDA; - if (strlcpy(rule->r_value.buffer, $5, + if (strlcpy(rule->r_value.buffer, $4, sizeof(rule->r_value.buffer)) >= sizeof(rule->r_value.buffer)) fatal("command too long"); - free($5); + free($4); } - | RELAY relay_as relay_source relay_helo { - rule->r_action = A_RELAY; - rule->r_as = $2; - if ($3) - strlcpy(rule->r_value.relayhost.sourcetable, $3, - sizeof rule->r_value.relayhost.sourcetable); - if ($4) - strlcpy(rule->r_value.relayhost.helotable, $4, - sizeof rule->r_value.relayhost.helotable); - } - | RELAY relay_backup relay_as relay_source relay_helo { - rule->r_action = A_RELAY; - rule->r_as = $3; - rule->r_value.relayhost.flags |= F_BACKUP; - - if ($2) - strlcpy(rule->r_value.relayhost.hostname, $2, - sizeof (rule->r_value.relayhost.hostname)); - else - strlcpy(rule->r_value.relayhost.hostname, - env->sc_hostname, - sizeof (rule->r_value.relayhost.hostname)); - free($2); + ; - if ($4) - strlcpy(rule->r_value.relayhost.sourcetable, $4, - sizeof rule->r_value.relayhost.sourcetable); - if ($5) - strlcpy(rule->r_value.relayhost.helotable, $5, - sizeof rule->r_value.relayhost.helotable); +relay_action : RELAY relay { + rule->r_action = A_RELAY; } - | RELAY VIA STRING certificate credentials relay_as relay_source relay_helo { - struct table *t; - + | RELAY VIA STRING { rule->r_action = A_RELAYVIA; - rule->r_as = $6; - if (! text_to_relayhost(&rule->r_value.relayhost, $3)) { yyerror("error: invalid url: %s", $3); free($3); - free($4); - free($6); YYERROR; } free($3); - + } relay_via { /* no worries, F_AUTH cant be set without SSL */ if (rule->r_value.relayhost.flags & F_AUTH) { - if (! $5) { + if (rule->r_value.relayhost.authtable[0] == '\0') { yyerror("error: auth without auth table"); - free($4); - free($6); YYERROR; } - t = $5; - strlcpy(rule->r_value.relayhost.authtable, t->t_name, - sizeof(rule->r_value.relayhost.authtable)); } - - if ($4 != NULL) { - if (strlcpy(rule->r_value.relayhost.cert, $4, - sizeof(rule->r_value.relayhost.cert)) - >= sizeof(rule->r_value.relayhost.cert)) - fatal("certificate path too long"); - } - free($4); - - if ($7) - strlcpy(rule->r_value.relayhost.sourcetable, $7, - sizeof rule->r_value.relayhost.sourcetable); - if ($8) - strlcpy(rule->r_value.relayhost.helotable, $8, - sizeof rule->r_value.relayhost.helotable); } ; -from : FROM tables { - struct table *t = $2; +negation : '!' { $$ = 1; } + | /* empty */ { $$ = 0; } + ; +from : FROM negation SOURCE tables { + struct table *t = $4; + + if (rule->r_sources) { + yyerror("from specified multiple times"); + YYERROR; + } if (! table_check_use(t, T_DYNAMIC|T_LIST, K_NETADDR)) { yyerror("invalid use of table \"%s\" as FROM parameter", t->t_name); YYERROR; } - - $$ = t; + rule->r_notsources = $2; + rule->r_sources = t; + } + | FROM negation ANY { + if (rule->r_sources) { + yyerror("from specified multiple times"); + YYERROR; + } + rule->r_sources = table_find("<anyhost>", NULL); + rule->r_notsources = $2; + } + | FROM negation LOCAL { + if (rule->r_sources) { + yyerror("from specified multiple times"); + YYERROR; + } + rule->r_sources = table_find("<localhost>", NULL); + rule->r_notsources = $2; } - | FROM ANY { - $$ = table_find("<anyhost>", NULL); + ; + +for : FOR negation DOMAIN tables { + struct table *t = $4; + + if (rule->r_destination) { + yyerror("for specified multiple times"); + YYERROR; + } + if (! table_check_use(t, T_DYNAMIC|T_LIST, K_DOMAIN)) { + yyerror("invalid use of table \"%s\" as DOMAIN parameter", + t->t_name); + YYERROR; + } + rule->r_notdestination = $2; + rule->r_destination = t; } - | FROM LOCAL { - $$ = table_find("<localhost>", NULL); + | FOR negation ANY { + if (rule->r_destination) { + yyerror("for specified multiple times"); + YYERROR; + } + rule->r_notdestination = $2; + rule->r_destination = table_find("<anydestination>", NULL); } - | /* empty */ { - $$ = table_find("<localhost>", NULL); + | FOR negation LOCAL { + if (rule->r_destination) { + yyerror("for specified multiple times"); + YYERROR; + } + rule->r_notdestination = $2; + rule->r_destination = table_find("<localnames>", NULL); } ; -sender : SENDER tables { - struct table *t = $2; +sender : SENDER negation tables { + struct table *t = $3; + + if (rule->r_senders) { + yyerror("sender specified multiple times"); + YYERROR; + } if (! table_check_use(t, T_DYNAMIC|T_LIST, K_MAILADDR)) { yyerror("invalid use of table \"%s\" as SENDER parameter", t->t_name); YYERROR; } + rule->r_notsenders = $2; + rule->r_senders = t; + } + ; - $$ = t; +recipient : RECIPIENT negation tables { + struct table *t = $3; + + if (rule->r_recipients) { + yyerror("recipient specified multiple times"); + YYERROR; + } + + if (! table_check_use(t, T_DYNAMIC|T_LIST, K_MAILADDR)) { + yyerror("invalid use of table \"%s\" as RECIPIENT parameter", + t->t_name); + YYERROR; + } + rule->r_notrecipients = $2; + rule->r_recipients = t; } - | /* empty */ { $$ = NULL; } ; -rule : ACCEPT { - rule = xcalloc(1, sizeof(*rule), "parse rule: ACCEPT"); - } tagged from sender FOR destination usermapping action expire { +forwardonly : FORWARDONLY { + if (rule->r_forwardonly) { + yyerror("forward-only specified multiple times"); + YYERROR; + } + rule->r_forwardonly = 1; + } + ; - rule->r_decision = R_ACCEPT; - rule->r_sources = $4; - rule->r_senders = $5; - rule->r_destination = $7; - rule->r_mapping = $8; - if ($3) { - if (strlcpy(rule->r_tag, $3, sizeof rule->r_tag) - >= sizeof rule->r_tag) { - yyerror("tag name too long: %s", $3); - free($3); - YYERROR; - } - free($3); +expire : EXPIRE STRING { + if (rule->r_qexpire != -1) { + yyerror("expire specified multiple times"); + YYERROR; } - rule->r_qexpire = $10; + rule->r_qexpire = delaytonum($2); + if (rule->r_qexpire == -1) { + yyerror("invalid expire delay: %s", $2); + free($2); + YYERROR; + } + free($2); + } + ; + +opt_decision : sender + | recipient + | from + | for + | tagged + ; +decision : opt_decision decision + | + ; - if (rule->r_mapping && rule->r_desttype == DEST_VDOM) { - enum table_type type; +opt_lookup : userbase + | usermapping + ; +lookup : opt_lookup lookup + | + ; - switch (rule->r_action) { - case A_RELAY: - case A_RELAYVIA: - type = T_LIST; - break; - default: - type = T_HASH; - break; +action : deliver_action + | relay_action + | + ; + +opt_accept : expire + | forwardonly + ; + +accept_params : opt_accept accept_params + | + ; + +rule : ACCEPT { + rule = xcalloc(1, sizeof(*rule), "parse rule: ACCEPT"); + rule->r_action = A_NONE; + rule->r_decision = R_ACCEPT; + rule->r_desttype = DEST_DOM; + rule->r_qexpire = -1; + } decision lookup action accept_params { + if (! rule->r_sources) + rule->r_sources = table_find("<localhost>", NULL); + if (! rule->r_destination) + rule->r_destination = table_find("<localnames>", NULL); + if (! rule->r_userbase) + rule->r_userbase = table_find("<getpwnam>", NULL); + if (rule->r_qexpire == -1) + rule->r_qexpire = conf->sc_qexpire; + if (rule->r_action == A_RELAY || rule->r_action == A_RELAYVIA) { + if (rule->r_userbase != table_find("<getpwnam>", NULL)) { + yyerror("userbase may not be used with a relay rule"); + YYERROR; } - if (! table_check_service(rule->r_mapping, K_ALIAS) && - ! table_check_type(rule->r_mapping, type)) { - yyerror("invalid use of table \"%s\" as VIRTUAL parameter", - rule->r_mapping->t_name); + if (rule->r_mapping) { + yyerror("aliases/virtual may not be used with a relay rule"); YYERROR; } } - + if (rule->r_forwardonly && rule->r_action != A_NONE) { + yyerror("forward-only may not be used with a default action"); + YYERROR; + } TAILQ_INSERT_TAIL(conf->sc_rules, rule, r_entry); - rule = NULL; } | REJECT { rule = xcalloc(1, sizeof(*rule), "parse rule: REJECT"); - } tagged from sender FOR destination usermapping { rule->r_decision = R_REJECT; - rule->r_sources = $4; - rule->r_senders = $5; - rule->r_destination = $7; - rule->r_mapping = $8; - if ($3) { - if (strlcpy(rule->r_tag, $3, sizeof rule->r_tag) - >= sizeof rule->r_tag) { - yyerror("tag name too long: %s", $3); - free($3); - YYERROR; - } - free($3); - } + rule->r_desttype = DEST_DOM; + } decision { + if (! rule->r_sources) + rule->r_sources = table_find("<localhost>", NULL); + if (! rule->r_destination) + rule->r_destination = table_find("<localnames>", NULL); TAILQ_INSERT_TAIL(conf->sc_rules, rule, r_entry); rule = NULL; } @@ -982,18 +1060,21 @@ lookup(char *s) { "auth-optional", AUTH_OPTIONAL }, { "backup", BACKUP }, { "bounce-warn", BOUNCEWARN }, + { "ca", CA }, { "certificate", CERTIFICATE }, { "compression", COMPRESSION }, { "deliver", DELIVER }, + { "dhparams", DHPARAMS }, { "domain", DOMAIN }, { "encryption", ENCRYPTION }, { "expire", EXPIRE }, { "filter", FILTER }, { "filterchain", FILTERCHAIN }, { "for", FOR }, + { "forward-only", FORWARDONLY }, { "from", FROM }, - { "helo", HELO }, { "hostname", HOSTNAME }, + { "hostnames", HOSTNAMES }, { "include", INCLUDE }, { "inet4", INET4 }, { "inet6", INET6 }, @@ -1003,19 +1084,22 @@ lookup(char *s) { "lmtp", LMTP }, { "local", LOCAL }, { "maildir", MAILDIR }, + { "mask-source", MASK_SOURCE }, { "max-message-size", MAXMESSAGESIZE }, { "mbox", MBOX }, { "mda", MDA }, { "mta", MTA }, { "on", ON }, + { "pki", PKI }, { "port", PORT }, { "queue", QUEUE }, + { "recipient", RECIPIENT }, { "reject", REJECT }, { "relay", RELAY }, + { "secure", SECURE }, { "sender", SENDER }, { "smtps", SMTPS }, { "source", SOURCE }, - { "ssl", SSL }, { "table", TABLE }, { "tag", TAG }, { "tagged", TAGGED }, @@ -1023,6 +1107,7 @@ lookup(char *s) { "tls-require", TLS_REQUIRE }, { "to", TO }, { "userbase", USERBASE }, + { "verify", VERIFY }, { "via", VIA }, { "virtual", VIRTUAL }, }; @@ -1363,6 +1448,8 @@ parse_config(struct smtpd *x_conf, const char *filename, int opts) conf = x_conf; bzero(conf, sizeof(*conf)); + strlcpy(conf->sc_hostname, hostname, sizeof(conf->sc_hostname)); + conf->sc_maxsize = DEFAULT_MAX_BODY_SIZE; conf->sc_tables_dict = calloc(1, sizeof(*conf->sc_tables_dict)); @@ -1425,6 +1512,10 @@ parse_config(struct smtpd *x_conf, const char *filename, int opts) table_add(t, "localhost", NULL); table_add(t, hostname, NULL); + t = table_create("static", "<anydestination>", NULL, NULL); + t->t_type = T_LIST; + table_add(t, "*", NULL); + /* can't truncate here */ (void)strlcpy(hostname_copy, hostname, sizeof hostname_copy); @@ -1462,9 +1553,6 @@ parse_config(struct smtpd *x_conf, const char *filename, int opts) errors++; } - if (strlen(conf->sc_hostname) == 0) - strlcpy(conf->sc_hostname, hostname, sizeof conf->sc_hostname); - if (errors) { purge_config(PURGE_EVERYTHING); return (-1); @@ -1547,6 +1635,79 @@ symget(const char *nam) return (NULL); } +static void +create_listener(struct listenerlist *ll, struct listen_opts *lo) +{ + uint16_t flags; + + if (lo->port != 0 && lo->ssl == F_SSL) + errx(1, "invalid listen option: tls/smtps on same port"); + + if (lo->auth != 0 && !lo->ssl) + errx(1, "invalid listen option: auth requires tls/smtps"); + + if (lo->pki && !lo->ssl) + errx(1, "invalid listen option: pki requires tls/smtps"); + + if (lo->ssl && !lo->pki) + errx(1, "invalid listen option: tls/smtps requires pki"); + + flags = lo->flags; + + + if (lo->port) { + lo->flags = lo->ssl|lo->auth|flags; + lo->port = htons(lo->port); + if (! interface(ll, lo)) + if (host(ll, lo) <= 0) + errx(1, "invalid virtual ip or interface: %s", lo->ifx); + } + else { + if (lo->ssl & F_SMTPS) { + lo->port = htons(465); + lo->flags = F_SMTPS|lo->auth|flags; + if (! interface(ll, lo)) + if (host(ll, lo) <= 0) + errx(1, "invalid virtual ip or interface: %s", lo->ifx); + } + + if (! lo->ssl || (lo->ssl & F_STARTTLS)) { + lo->port = htons(25); + lo->flags = lo->auth|flags; + if (lo->ssl & F_STARTTLS) + lo->flags |= F_STARTTLS; + if (! interface(ll, lo)) + if (host(ll, lo) <= 0) + errx(1, "invalid virtual ip or interface: %s", lo->ifx); + } + } +} + +static void +config_listener(struct listener *h, struct listen_opts *lo) +{ + h->fd = -1; + h->port = lo->port; + h->flags = lo->flags; + + if (lo->hostname == NULL) + lo->hostname = conf->sc_hostname; + + h->ssl = NULL; + h->ssl_cert_name[0] = '\0'; + + if (lo->authtable != NULL) + (void)strlcpy(h->authtable, lo->authtable->t_name, sizeof(h->authtable)); + if (lo->pki != NULL) + (void)strlcpy(h->ssl_cert_name, lo->pki, sizeof(h->ssl_cert_name)); + if (lo->tag != NULL) + (void)strlcpy(h->tag, lo->tag, sizeof(h->tag)); + + (void)strlcpy(h->hostname, lo->hostname, sizeof(h->hostname)); + if (lo->hostnametable) + (void)strlcpy(h->hostnametable, lo->hostnametable->t_name, sizeof(h->hostnametable)); +} + struct listener * host_v4(const char *s, in_port_t port) { @@ -1590,8 +1751,7 @@ host_v6(const char *s, in_port_t port) } int -host_dns(const char *s, const char *tag, const char *cert, - struct listenerlist *al, int max, in_port_t port, uint8_t flags) +host_dns(struct listenerlist *al, struct listen_opts *lo) { struct addrinfo hints, *res0, *res; int error, cnt = 0; @@ -1602,107 +1762,74 @@ host_dns(const char *s, const char *tag, const char *cert, bzero(&hints, sizeof(hints)); hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; /* DUMMY */ - error = getaddrinfo(s, NULL, &hints, &res0); + error = getaddrinfo(lo->ifx, NULL, &hints, &res0); if (error == EAI_AGAIN || error == EAI_NODATA || error == EAI_NONAME) return (0); if (error) { - log_warnx("warn: host_dns: could not parse \"%s\": %s", s, + log_warnx("warn: host_dns: could not parse \"%s\": %s", lo->ifx, gai_strerror(error)); return (-1); } - for (res = res0; res && cnt < max; res = res->ai_next) { + for (res = res0; res; res = res->ai_next) { if (res->ai_family != AF_INET && res->ai_family != AF_INET6) continue; h = xcalloc(1, sizeof(*h), "host_dns"); - h->port = port; - h->flags = flags; h->ss.ss_family = res->ai_family; - h->ssl = NULL; - h->ssl_cert_name[0] = '\0'; - if (cert != NULL) - (void)strlcpy(h->ssl_cert_name, cert, sizeof(h->ssl_cert_name)); - if (tag != NULL) - (void)strlcpy(h->tag, tag, sizeof(h->tag)); - if (res->ai_family == AF_INET) { sain = (struct sockaddr_in *)&h->ss; sain->sin_len = sizeof(struct sockaddr_in); sain->sin_addr.s_addr = ((struct sockaddr_in *) res->ai_addr)->sin_addr.s_addr; - sain->sin_port = port; + sain->sin_port = lo->port; } else { sin6 = (struct sockaddr_in6 *)&h->ss; sin6->sin6_len = sizeof(struct sockaddr_in6); memcpy(&sin6->sin6_addr, &((struct sockaddr_in6 *) res->ai_addr)->sin6_addr, sizeof(struct in6_addr)); - sin6->sin6_port = port; + sin6->sin6_port = lo->port; } + config_listener(h, lo); + TAILQ_INSERT_HEAD(al, h, entry); cnt++; } - if (cnt == max && res) { - log_warnx("warn: host_dns: %s resolves to more than %d hosts", - s, max); - } + freeaddrinfo(res0); return (cnt); } int -host(const char *s, const char *tag, const char *cert, struct listenerlist *al, - int max, in_port_t port, const char *authtable, uint8_t flags, - const char *helo) +host(struct listenerlist *al, struct listen_opts *lo) { struct listener *h; - port = htons(port); - - h = host_v4(s, port); + h = host_v4(lo->ifx, lo->port); /* IPv6 address? */ if (h == NULL) - h = host_v6(s, port); + h = host_v6(lo->ifx, lo->port); if (h != NULL) { - h->port = port; - h->flags = flags; - if (h->flags & F_SSL) - if (cert == NULL) - cert = s; - h->ssl = NULL; - h->ssl_cert_name[0] = '\0'; - if (authtable != NULL) - (void)strlcpy(h->authtable, authtable, sizeof(h->authtable)); - if (cert != NULL) - (void)strlcpy(h->ssl_cert_name, cert, sizeof(h->ssl_cert_name)); - if (tag != NULL) - (void)strlcpy(h->tag, tag, sizeof(h->tag)); - if (helo != NULL) - (void)strlcpy(h->helo, helo, sizeof(h->helo)); - + config_listener(h, lo); TAILQ_INSERT_HEAD(al, h, entry); return (1); } - return (host_dns(s, tag, cert, al, max, port, flags)); + return (host_dns(al, lo)); } int -interface(const char *s, int family, const char *tag, const char *cert, - struct listenerlist *al, int max, in_port_t port, const char *authtable, uint8_t flags, - const char *helo) +interface(struct listenerlist *al, struct listen_opts *lo) { struct ifaddrs *ifap, *p; struct sockaddr_in *sain; struct sockaddr_in6 *sin6; struct listener *h; - int ret = 0; - - port = htons(port); + int ret = 0; if (getifaddrs(&ifap) == -1) fatal("getifaddrs"); @@ -1710,10 +1837,10 @@ interface(const char *s, int family, const char *tag, const char *cert, for (p = ifap; p != NULL; p = p->ifa_next) { if (p->ifa_addr == NULL) continue; - if (strcmp(p->ifa_name, s) != 0 && - ! is_if_in_group(p->ifa_name, s)) + if (strcmp(p->ifa_name, lo->ifx) != 0 && + ! is_if_in_group(p->ifa_name, lo->ifx)) continue; - if (family != AF_UNSPEC && family != p->ifa_addr->sa_family) + if (lo->family != AF_UNSPEC && lo->family != p->ifa_addr->sa_family) continue; h = xcalloc(1, sizeof(*h), "interface"); @@ -1723,14 +1850,14 @@ interface(const char *s, int family, const char *tag, const char *cert, sain = (struct sockaddr_in *)&h->ss; *sain = *(struct sockaddr_in *)p->ifa_addr; sain->sin_len = sizeof(struct sockaddr_in); - sain->sin_port = port; + sain->sin_port = lo->port; break; case AF_INET6: sin6 = (struct sockaddr_in6 *)&h->ss; *sin6 = *(struct sockaddr_in6 *)p->ifa_addr; sin6->sin6_len = sizeof(struct sockaddr_in6); - sin6->sin6_port = port; + sin6->sin6_port = lo->port; break; default: @@ -1738,22 +1865,7 @@ interface(const char *s, int family, const char *tag, const char *cert, continue; } - h->fd = -1; - h->port = port; - h->flags = flags; - if (h->flags & F_SSL) - if (cert == NULL) - cert = s; - h->ssl = NULL; - h->ssl_cert_name[0] = '\0'; - if (authtable != NULL) - (void)strlcpy(h->authtable, authtable, sizeof(h->authtable)); - if (cert != NULL) - (void)strlcpy(h->ssl_cert_name, cert, sizeof(h->ssl_cert_name)); - if (tag != NULL) - (void)strlcpy(h->tag, tag, sizeof(h->tag)); - if (helo != NULL) - (void)strlcpy(h->helo, helo, sizeof(h->helo)); + config_listener(h, lo); ret = 1; TAILQ_INSERT_HEAD(al, h, entry); } @@ -1894,7 +2006,6 @@ end: return ret; } - struct filter * create_filter(const char *name, const char *path) { diff --git a/usr.sbin/smtpd/ruleset.c b/usr.sbin/smtpd/ruleset.c index 7042ee090ca..9e8afbde20d 100644 --- a/usr.sbin/smtpd/ruleset.c +++ b/usr.sbin/smtpd/ruleset.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ruleset.c,v 1.28 2013/05/24 17:03:14 eric Exp $ */ +/* $OpenBSD: ruleset.c,v 1.29 2013/11/06 10:01:29 eric Exp $ */ /* * Copyright (c) 2009 Gilles Chehade <gilles@poolp.org> @@ -35,7 +35,7 @@ static int ruleset_check_source(struct table *, const struct sockaddr_storage *, int); -static int ruleset_check_sender(struct table *, const struct mailaddr *); +static int ruleset_check_mailaddr(struct table *, const struct mailaddr *); struct rule * ruleset_match(const struct envelope *evp) @@ -47,24 +47,39 @@ ruleset_match(const struct envelope *evp) TAILQ_FOREACH(r, env->sc_rules, r_entry) { - if (r->r_tag[0] != '\0' && strcmp(r->r_tag, evp->tag) != 0) - continue; + if (r->r_tag[0] != '\0') { + ret = strcmp(r->r_tag, evp->tag); + if (ret != 0 && !r->r_nottag) + continue; + if (ret == 0 && r->r_nottag) + continue; + } ret = ruleset_check_source(r->r_sources, ss, evp->flags); if (ret == -1) { errno = EAGAIN; return (NULL); } - if (ret == 0) + if ((ret == 0 && !r->r_notsources) || (ret != 0 && r->r_notsources)) continue; if (r->r_senders) { - ret = ruleset_check_sender(r->r_senders, &evp->sender); + ret = ruleset_check_mailaddr(r->r_senders, &evp->sender); + if (ret == -1) { + errno = EAGAIN; + return (NULL); + } + if ((ret == 0 && !r->r_notsenders) || (ret != 0 && r->r_notsenders)) + continue; + } + + if (r->r_recipients) { + ret = ruleset_check_mailaddr(r->r_recipients, &evp->dest); if (ret == -1) { errno = EAGAIN; return (NULL); } - if (ret == 0) + if ((ret == 0 && !r->r_notrecipients) || (ret != 0 && r->r_notrecipients)) continue; } @@ -75,19 +90,21 @@ ruleset_match(const struct envelope *evp) errno = EAGAIN; return NULL; } - if (ret) { - if (r->r_desttype == DEST_VDOM && - (r->r_action == A_RELAY || r->r_action == A_RELAYVIA)) { - if (! aliases_virtual_check(r->r_mapping, - &evp->rcpt)) { - return NULL; - } + if ((ret == 0 && !r->r_notdestination) || (ret != 0 && r->r_notdestination)) + continue; + + if (r->r_desttype == DEST_VDOM && + (r->r_action == A_RELAY || r->r_action == A_RELAYVIA)) { + if (! aliases_virtual_check(r->r_mapping, + &evp->rcpt)) { + return NULL; } - goto matched; } + goto matched; } errno = 0; + log_trace(TRACE_RULES, "no rule matched"); return (NULL); matched: @@ -120,7 +137,7 @@ ruleset_check_source(struct table *table, const struct sockaddr_storage *ss, } static int -ruleset_check_sender(struct table *table, const struct mailaddr *maddr) +ruleset_check_mailaddr(struct table *table, const struct mailaddr *maddr) { const char *key; @@ -138,6 +155,5 @@ ruleset_check_sender(struct table *table, const struct mailaddr *maddr) default: break; } - return 0; } diff --git a/usr.sbin/smtpd/smtp.c b/usr.sbin/smtpd/smtp.c index ed71a943fc4..2d4d8e6943d 100644 --- a/usr.sbin/smtpd/smtp.c +++ b/usr.sbin/smtpd/smtp.c @@ -1,4 +1,4 @@ -/* $OpenBSD: smtp.c,v 1.129 2013/10/27 11:01:47 eric Exp $ */ +/* $OpenBSD: smtp.c,v 1.130 2013/11/06 10:01:29 eric Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org> @@ -66,6 +66,7 @@ smtp_imsg(struct mproc *p, struct imsg *imsg) switch (imsg->hdr.type) { case IMSG_DNS_PTR: case IMSG_LKA_EXPAND_RCPT: + case IMSG_LKA_HELO: case IMSG_LKA_AUTHENTICATE: case IMSG_LKA_SSL_INIT: case IMSG_LKA_SSL_VERIFY: @@ -296,7 +297,7 @@ smtp_setup_events(void) TAILQ_FOREACH(l, env->sc_listeners, entry) { log_debug("debug: smtp: listen on %s port %d flags 0x%01x" - " cert \"%s\"", ss_to_text(&l->ss), ntohs(l->port), + " pki \"%s\"", ss_to_text(&l->ss), ntohs(l->port), l->flags, l->ssl_cert_name); session_socket_blockmode(l->fd, BM_NONBLOCK); @@ -361,6 +362,8 @@ smtp_enqueue(uid_t *euid) strlcpy(listener->tag, "local", sizeof(listener->tag)); listener->ss.ss_family = AF_LOCAL; listener->ss.ss_len = sizeof(struct sockaddr *); + strlcpy(listener->hostname, "localhost", + sizeof(listener->hostname)); } /* diff --git a/usr.sbin/smtpd/smtp_session.c b/usr.sbin/smtpd/smtp_session.c index bc59a6e0bf3..0e7563c7bba 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.187 2013/10/28 17:02:08 eric Exp $ */ +/* $OpenBSD: smtp_session.c,v 1.188 2013/11/06 10:01:29 eric Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org> @@ -114,6 +114,7 @@ struct smtp_session { struct listener *listener; struct sockaddr_storage ss; char hostname[SMTPD_MAXHOSTNAMELEN]; + char smtpname[SMTPD_MAXHOSTNAMELEN]; int flags; int phase; @@ -151,9 +152,10 @@ struct smtp_session { ((s)->listener->flags & F_AUTH && (s)->flags & SF_SECURE && \ !((s)->flags & SF_AUTHENTICATED)) -static int smtp_mailaddr(struct mailaddr *, char *, int, char **); +static int smtp_mailaddr(struct mailaddr *, char *, int, char **, const char *); static void smtp_session_init(void); static void smtp_connected(struct smtp_session *); +static void smtp_send_banner(struct smtp_session *); static void smtp_mfa_response(struct smtp_session *, int, uint32_t, const char *); static void smtp_io(struct io *, int); @@ -189,6 +191,7 @@ static struct { int code; const char *cmd; } commands[] = { }; static struct tree wait_lka_ptr; +static struct tree wait_lka_helo; static struct tree wait_lka_rcpt; static struct tree wait_mfa_response; static struct tree wait_mfa_data; @@ -206,6 +209,7 @@ smtp_session_init(void) if (!init) { tree_init(&wait_lka_ptr); + tree_init(&wait_lka_helo); tree_init(&wait_lka_rcpt); tree_init(&wait_mfa_response); tree_init(&wait_mfa_data); @@ -247,6 +251,8 @@ smtp_session(struct listener *listener, int sock, s->state = STATE_NEW; s->phase = PHASE_INIT; + strlcpy(s->smtpname, listener->hostname, sizeof(s->smtpname)); + /* For local enqueueing, the hostname is already set */ if (hostname) { s->flags |= SF_AUTHENTICATED; @@ -273,7 +279,7 @@ smtp_session_imsg(struct mproc *p, struct imsg *imsg) void *ssl; char user[SMTPD_MAXLOGNAME]; struct msg m; - const char *line; + const char *line, *helo; uint64_t reqid, evpid; uint32_t code, msgid; int status, success, dnserror; @@ -319,6 +325,20 @@ smtp_session_imsg(struct mproc *p, struct imsg *imsg) io_reload(&s->io); return; + case IMSG_LKA_HELO: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + s = tree_xpop(&wait_lka_helo, reqid); + m_get_int(&m, &status); + if (status == LKA_OK) { + m_get_string(&m, &helo); + strlcpy(s->smtpname, helo, sizeof(s->smtpname)); + } + m_end(&m); + smtp_reply(s, SMTPD_BANNER, s->smtpname, SMTPD_NAME); + io_reload(&s->io); + return; + case IMSG_MFA_SMTP_RESPONSE: m_msg(&m, imsg); m_get_id(&m, &reqid); @@ -369,13 +389,15 @@ smtp_session_imsg(struct mproc *p, struct imsg *imsg) return; } - fprintf(s->ofile, - "Received: from %s (%s [%s]);\n" - "\tby %s (%s) with %sSMTP%s%s id %08x;\n", - s->evp.helo, - s->hostname, - ss_to_text(&s->ss), - s->listener->helo[0] ? s->listener->helo : env->sc_hostname, + fprintf(s->ofile, "Received: "); + if (! (s->listener->flags & F_MASK_SOURCE)) { + fprintf(s->ofile, "from %s (%s [%s]);\n\t", + s->evp.helo, + s->hostname, + ss_to_text(&s->ss)); + } + fprintf(s->ofile, "by %s (%s) with %sSMTP%s%s id %08x;\n", + s->smtpname, SMTPD_NAME, s->flags & SF_EHLO ? "E" : "", s->flags & SF_SECURE ? "S" : "", @@ -576,7 +598,12 @@ smtp_session_imsg(struct mproc *p, struct imsg *imsg) if (resp_ca_vrfy->status == CA_OK) s->flags |= SF_VERIFIED; - + else if (s->listener->flags & F_TLS_VERIFY) { + log_info("smtp-in: Disconnecting session %016" PRIx64 + ": SSL certificate check failed", s->id); + smtp_free(s, "SSL certificate check failed"); + return; + } smtp_io(&s->io, IO_TLSVERIFIED); io_resume(&s->io, IO_PAUSE_IN); return; @@ -621,11 +648,7 @@ smtp_mfa_response(struct smtp_session *s, int status, uint32_t code, tree_xset(&wait_ssl_init, s->id, s); return; } - if (s->listener->helo[0]) - smtp_reply(s, SMTPD_BANNER, s->listener->helo, SMTPD_NAME); - else - smtp_reply(s, SMTPD_BANNER, env->sc_hostname, SMTPD_NAME); - io_reload(&s->io); + smtp_send_banner(s); return; case IMSG_MFA_REQ_HELO: @@ -640,7 +663,7 @@ smtp_mfa_response(struct smtp_session *s, int status, uint32_t code, smtp_enter_state(s, STATE_HELO); smtp_reply(s, "250%c%s Hello %s [%s], pleased to meet you", (s->flags & SF_EHLO) ? '-' : ' ', - env->sc_hostname, + s->smtpname, s->evp.helo, ss_to_text(&s->ss)); @@ -755,6 +778,13 @@ smtp_io(struct io *io, int evt) break; } + if (s->listener->flags & F_TLS_VERIFY) { + log_info("smtp-in: Disconnecting session %016" PRIx64 + ": client did not present certificate", s->id); + smtp_free(s, "client did not present certificate"); + return; + } + /* No verification required, cascade */ case IO_TLSVERIFIED: @@ -769,8 +799,8 @@ smtp_io(struct io *io, int evt) if (s->listener->flags & F_SMTPS) { stat_increment("smtp.smtps", 1); - smtp_reply(s, SMTPD_BANNER, env->sc_hostname, SMTPD_NAME); io_set_write(&s->io); + smtp_send_banner(s); } else { stat_increment("smtp.tls", 1); @@ -1066,7 +1096,8 @@ smtp_command(struct smtp_session *s, char *line) smtp_message_reset(s, 1); - if (smtp_mailaddr(&s->evp.sender, args, 1, &args) == 0) { + if (smtp_mailaddr(&s->evp.sender, args, 1, &args, + s->smtpname) == 0) { smtp_reply(s, "553 Sender address syntax error"); break; } @@ -1093,7 +1124,8 @@ smtp_command(struct smtp_session *s, char *line) break; } - if (smtp_mailaddr(&s->evp.rcpt, args, 0, &args) == 0) { + if (smtp_mailaddr(&s->evp.rcpt, args, 0, &args, + s->smtpname) == 0) { smtp_reply(s, "553 Recipient address syntax error"); break; @@ -1329,6 +1361,34 @@ smtp_connected(struct smtp_session *s) smtp_wait_mfa(s, IMSG_MFA_REQ_CONNECT); } +static void +smtp_send_banner(struct smtp_session *s) +{ + struct sockaddr_storage ss; + struct sockaddr *sa; + socklen_t sa_len; + + if (s->listener->hostnametable[0]) { + sa_len = sizeof(ss); + sa = (struct sockaddr *)&ss; + if (getsockname(s->io.sock, sa, &sa_len) == -1) { + log_warn("warn: getsockname()"); + } + else { + m_create(p_lka, IMSG_LKA_HELO, 0, 0, -1); + m_add_id(p_lka, s->id); + m_add_string(p_lka, s->listener->hostnametable); + m_add_sockaddr(p_lka, sa); + m_close(p_lka); + tree_xset(&wait_lka_helo, s->id, s); + return; + } + } + + smtp_reply(s, SMTPD_BANNER, s->smtpname, SMTPD_NAME); + io_reload(&s->io); +} + void smtp_enter_state(struct smtp_session *s, int newstate) { @@ -1416,6 +1476,7 @@ smtp_message_reset(struct smtp_session *s, int prepare) if (prepare) { s->evp.ss = s->ss; strlcpy(s->evp.tag, s->listener->tag, sizeof(s->evp.tag)); + strlcpy(s->evp.smtpname, s->smtpname, sizeof(s->evp.smtpname)); strlcpy(s->evp.hostname, s->hostname, sizeof s->evp.hostname); strlcpy(s->evp.helo, s->helo, sizeof s->evp.helo); @@ -1515,7 +1576,8 @@ smtp_free(struct smtp_session *s, const char * reason) } static int -smtp_mailaddr(struct mailaddr *maddr, char *line, int mailfrom, char **args) +smtp_mailaddr(struct mailaddr *maddr, char *line, int mailfrom, char **args, + const char *domain) { char *p, *e; @@ -1549,6 +1611,16 @@ smtp_mailaddr(struct mailaddr *maddr, char *line, int mailfrom, char **args) maddr->user[0] == '\0' && maddr->domain[0] == '\0') return (1); + + /* We accept empty domain for RCPT TO if user is postmaster */ + if (!mailfrom && + strcasecmp(maddr->user, "postmaster") == 0 && + maddr->domain[0] == '\0') { + (void)strlcpy(maddr->domain, domain, + sizeof(maddr->domain)); + return (1); + } + return (0); } diff --git a/usr.sbin/smtpd/smtpd.c b/usr.sbin/smtpd/smtpd.c index 4ed2631723d..97586931bd6 100644 --- a/usr.sbin/smtpd/smtpd.c +++ b/usr.sbin/smtpd/smtpd.c @@ -1,4 +1,4 @@ -/* $OpenBSD: smtpd.c,v 1.203 2013/10/30 21:37:48 eric Exp $ */ +/* $OpenBSD: smtpd.c,v 1.204 2013/11/06 10:01:29 eric Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org> @@ -75,7 +75,7 @@ static int offline_enqueue(char *); static void purge_task(int, short, void *); static void log_imsg(int, int, struct imsg *); static int parent_auth_user(const char *, const char *); -static void load_ssl_trees(void); +static void load_ssl_tree(void); enum child_type { CHILD_DAEMON, @@ -343,8 +343,6 @@ parent_send_config_smtp(void) m_compose(p_smtp, IMSG_CONF_START, 0, 0, -1, NULL, 0); while (dict_iter(env->sc_ssl_dict, &iter, NULL, (void **)&s)) { - if (!(s->flags & F_SCERT)) - continue; iov[0].iov_base = s; iov[0].iov_len = sizeof(*s); iov[1].iov_base = s->ssl_cert; @@ -455,6 +453,12 @@ parent_send_config_lka() &r->r_senders->t_name, sizeof(r->r_senders->t_name)); } + if (r->r_recipients) { + m_compose(p_lka, IMSG_CONF_RULE_RECIPIENT, + 0, 0, -1, + &r->r_recipients->t_name, + sizeof(r->r_recipients->t_name)); + } if (r->r_destination) { m_compose(p_lka, IMSG_CONF_RULE_DESTINATION, 0, 0, -1, @@ -711,7 +715,7 @@ main(int argc, char *argv[]) errx(1, "config file exceeds SMTPD_MAXPATHLEN"); if (env->sc_opts & SMTPD_OPT_NOACTION) { - load_ssl_trees(); + load_ssl_tree(); fprintf(stderr, "configuration OK\n"); exit(0); } @@ -760,7 +764,7 @@ main(int argc, char *argv[]) errx(1, "machine does not have a hostname set"); env->sc_uptime = time(NULL); - load_ssl_trees(); + load_ssl_tree(); fork_peers(); @@ -814,44 +818,33 @@ main(int argc, char *argv[]) } static void -load_ssl_trees(void) +load_ssl_tree(void) { - struct listener *l; struct ssl *ssl; - struct rule *r; - - log_debug("debug: init server-ssl tree"); - TAILQ_FOREACH(l, env->sc_listeners, entry) { - if (!(l->flags & F_SSL)) - continue; + void *iter_dict; + const char *k; - ssl = dict_get(env->sc_ssl_dict, l->ssl_cert_name); - if (ssl == NULL) { - if (! ssl_load_certfile(&ssl, "/etc/mail/certs", - l->ssl_cert_name, F_SCERT)) - errx(1, "cannot load certificate: %s", - l->ssl_cert_name); - dict_set(env->sc_ssl_dict, ssl->ssl_name, ssl); - } - } - - log_debug("debug: init client-ssl tree"); - TAILQ_FOREACH(r, env->sc_rules, r_entry) { - if (r->r_action != A_RELAY && r->r_action != A_RELAYVIA) - continue; - if (! r->r_value.relayhost.cert[0]) - continue; - - ssl = dict_get(env->sc_ssl_dict, r->r_value.relayhost.cert); - if (ssl) - ssl->flags |= F_CCERT; - else { - if (! ssl_load_certfile(&ssl, "/etc/mail/certs", - r->r_value.relayhost.cert, F_CCERT)) - errx(1, "cannot load certificate: %s", - r->r_value.relayhost.cert); - dict_set(env->sc_ssl_dict, ssl->ssl_name, ssl); - } + log_debug("debug: init ssl-tree"); + iter_dict = NULL; + while (dict_iter(env->sc_ssl_dict, &iter_dict, &k, (void **)&ssl)) { + log_debug("debug: loading pki information for %s", k); + + if (ssl->ssl_cert_file == NULL) + errx(1, "load_ssl_tree: missing certificate file for %s", k); + if (ssl->ssl_key_file == NULL) + errx(1, "load_ssl_tree: missing key file for %s", k); + + if (! ssl_load_certificate(ssl, ssl->ssl_cert_file)) + errx(1, "load_ssl_tree: failed to load certificate file for %s", k); + if (! ssl_load_keyfile(ssl, ssl->ssl_key_file)) + errx(1, "load_ssl_tree: failed to load certificate file for %s", k); + + if (ssl->ssl_ca_file) + if (! ssl_load_cafile(ssl, ssl->ssl_ca_file)) + errx(1, "load_ssl_tree: failed to load CA file for %s", k); + if (ssl->ssl_dhparams_file) + if (! ssl_load_dhparams(ssl, ssl->ssl_dhparams_file)) + errx(1, "load_ssl_tree: failed to load dhparams file for %s", k); } } @@ -1349,7 +1342,7 @@ static void log_imsg(int to, int from, struct imsg *imsg) { - if (to == PROC_CONTROL) + if (to == PROC_CONTROL && imsg->hdr.type == IMSG_STAT_SET) return; if (imsg->fd != -1) diff --git a/usr.sbin/smtpd/smtpd.conf.5 b/usr.sbin/smtpd/smtpd.conf.5 index a66250eb2f3..13a2c56dd27 100644 --- a/usr.sbin/smtpd/smtpd.conf.5 +++ b/usr.sbin/smtpd/smtpd.conf.5 @@ -1,4 +1,4 @@ -.\" $OpenBSD: smtpd.conf.5,v 1.106 2013/10/29 14:30:05 eric Exp $ +.\" $OpenBSD: smtpd.conf.5,v 1.107 2013/11/06 10:01:29 eric 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: October 29 2013 $ +.Dd $Mdocdate: November 6 2013 $ .Dt SMTPD.CONF 5 .Os .Sh NAME @@ -79,32 +79,50 @@ from first to last. The first matching rule decides what action is taken. If no rule matches the message, the default action is to reject the message. +An exclamation mark may be specified to perform a reverse match. .Pp Following the accept/reject -decision comes the client's IP address filter: +decision comes the optional tag matching: +.Bl -tag -width Ds +.It Xo +.Ic tagged +.Op Ic \! +.Ic tag +.Xc +If specified, the rule will only be matched if the client session was tagged with +.Ar tag . +.El +.Pp +After that the client's IP address filter is specified: .Bl -tag -width Ds .It Ic from any Make the rule match regardless of the IP of connecting client. -.It Ic from local +.It Xo +.Ic from +.Op Ic \! +.Ic local +.Xc The rule matches only locally originating connections. This is the default, and may be omitted. -.It Ic from Ar network -The rule matches if the connection is made from the specified -.Ar network , -specified in CIDR notation. -.It Ic from Aq Ar table +.It Xo +.Ic from +.Op Ic \! +.Ic source +.Ic table +.Xc The rule matches if the connection is made from a client whose address is declared in the table .Ar table . -.It Ic tagged Ar tag -If specified, the rule will only be matched if the client session was tagged -.Ar tag . .El .Pp In addition, finer filtering may be achieved on the sender if desired: .Bl -tag -width Ds -.It Ic sender Ar senders +.It Xo +.Ic sender +.Op Ic \! +.Ic senders +.Xc If specified, the rule will only be matched if the sender email address is found in the table .Ar senders . @@ -124,7 +142,13 @@ Make the rule match regardless of the domain it is sent to. The .Ar vmap table will be used as the virtual domain mapping. -.It Ic for domain Ar domain Op Ic alias Aq Ar aliases +.It Xo +.Ic for +.Op Ic \! +.Ic domain +.Ar domain +.Op Ic alias Aq Ar aliases +.Xc This rule applies to mail destined for the specified .Ar domain . This parameter supports the @@ -139,7 +163,13 @@ If specified, the table .Ar aliases is used for looking up alternative destinations for addresses in this .Ar domain . -.It Ic for domain Aq Ar domains Op Ic alias Aq Ar aliases +.It Xo +.Ic for +.Op Ic \! +.Ic domain +.Aq Ar domains +.Op Ic alias Aq Ar aliases +.Xc This rule applies to mail destined to domains which are part of the table .Ar domains . .Pp @@ -147,7 +177,13 @@ If specified, the table .Ar aliases is used for looking up alternative destinations for addresses in these .Ar domains . -.It Ic for domain Ar domain Ic virtual Aq Ar users +.It Xo +.Ic for +.Op Ic \! +.Ic domain +.Ar domain +.Ic virtual Aq Ar users +.Xc This rule applies to mail destined for the specified virtual .Ar domain . This parameter supports the @@ -166,7 +202,13 @@ For an example of how to configure the .Ar users table, see .Xr makemap 8 . -.It Ic for domain Ao Ar domains Ac Ic virtual Aq Ar users +.It Xo +.Ic for +.Op Ic \! +.Ic domain +.Ao Ar domains +.Ac Ic virtual Aq Ar users +.Xc This rule applies to mail destined for the virtual domains specified in the table .Ar domains . @@ -178,7 +220,12 @@ For an example of how to configure the .Ar users table, see .Xr makemap 8 . -.It Ic for local Op Ic alias Aq Ar aliases +.It Xo +.Ic for +.Op Ic \! +.Ic local +.Op Ic alias Aq Ar aliases +.Xc This rule applies to mail destined to .Dq localhost and to the default server name. @@ -187,7 +234,12 @@ See the entry for .Pa /etc/mail/mailname below for details of how the server name is determined. -.It Ic for local virtual Aq Ar vmap +.It Xo +.Ic for +.Op Ic \! +.Ic local +.Ic virtual Aq Ar vmap +.Xc This rule applies to mail destined to .Dq localhost and to the default server name. @@ -196,6 +248,21 @@ The table will be used as the virtual domain mapping. .El .Pp +Further filtering may be achieved on specific recipients if desired: +.Bl -tag -width Ds +.It Xo +.Ic recipient +.Op Ic \&! +.Ar recipients +.Xc +If specified, the rule will only be matched if the recipient email address +is found in the table +.Ar recipients . +The table may contain complete email addresses or apply to an entire +domain if prefixed with +.Sq @ . +.El +.Pp If the method of delivery is local, a user database may be specified to override the system database: .Bl -tag -width Ds @@ -241,7 +308,12 @@ This parameter may use conversion specifiers that are expanded before use .Op Ic backup Op Ar mx .Op Ic as Ar address .Op Ic source Ar source -.Op Ic helo Ar names +.Bk -words +.Op Ic hostname Ar name +.Op Ic hostnames Ar names +.Ek +.Op Ic pki Ar pkiname +.Op Ic tls | verify .Xc Mail is relayed. The routing decision is based on the DNS system. @@ -275,7 +347,7 @@ If the parameter is specified, .Xr smtpd 8 will explicitly bind to an address found in the table referenced by -.Ar table +.Ar source when connecting to the relay. If the table contains more than one address, they are picked in turn each time a new connection is opened. @@ -284,22 +356,61 @@ By default, when connecting to a remote server, .Xr smtpd 8 advertises its default server name. A -.Ic helo -parameter may be specified to advertise an alternate hostname. +.Ic hostname +parameter may be specified to advertise the alternate hostname +.Ar name . +If the +.Ic source +parameter is used, the +.Ic hostnames +parameter may be specified to advertise a hostname based on +the source address. Table .Ar names contains a mapping of IP addresses to hostnames and .Xr smtpd 8 will automatically select the name that matches its source address when connected to the remote server. +The +.Ic hostname +and +.Ic hostnames +parameters are mutually exclusive. +.Pp +When relaying, STARTTLS is always attempted if available on remote host +and OpenSMTPD will try to present a certificate matching the outgoing +hostname if one is registered in the pki. +If +.Ic pki +is specified, the certificate registered for +.Ar pkiname +is used instead. +.Pp +If +.Ic tls +is specified, OpenSMTPD will refuse to relay unless remote host provides +STARTTLS. +.Pp +If +.Ic verify +is specified, OpenSMTPD will refuse to relay unless remote host provides +STARTTLS and the certificate it presented has been verified. +.Pp +Note that the +.Ic tls +and +.Ic verify +options are mutually exclusive and should only be used in private networks +as they will prevent proper relaying on the Internet. .It Xo .Ic relay via .Ar host -.Op Ic certificate Ar name .Op Ic auth Aq Ar auth .Op Ic as Ar address .Op Ic source Ar source -.Op Ic helo Ar names +.Op Ic hostname Ar name +.Op Ic hostnames Ar names +.Op Ic pki Ar pkiname .Xc Mail is relayed through the specified .Ar host @@ -319,7 +430,7 @@ For example: .Bd -literal -offset indent tls://mx1.example.org # use TLS smtps://mx1.example.org # use SMTPS -ssl://mx1.example.org # try SMTPS and \e +secure://mx1.example.org # try SMTPS and \e # fallback to TLS .Ed .Pp @@ -327,20 +438,16 @@ In addition, credentials for authenticated relaying may be provided when using a secure schema. For example: .Bd -literal -offset indent -tls+auth://label@mx.example.org # over TLS -smtps+auth://label@mx.example.org # over SMTPS -ssl+auth://label@mx.example.org # over either \e - # SMTPS or TLS +tls+auth://label@mx.example.org # over TLS +smtps+auth://label@mx.example.org # over SMTPS +secure+auth://label@mx.example.org # over either \e + # SMTPS or TLS .Ed .Pp -If a certificate -.Ar name -is specified and exists in the -.Pa /etc/mail/certs -directory with a .crt extension, it will be used if the remote server -requests a client certificate. -Creation of certificates is documented in -.Xr starttls 8 . +If a pki entry exists for the outgoing hostname, or one is provided +with +.Ar pkiname , +the associated certificate will be sent to the remote server. .Pp If an SMTPAUTH session with .Ar host @@ -378,14 +485,26 @@ By default, when connecting to a remote server, .Xr smtpd 8 advertises its default server name. A -.Ic helo -parameter may be specified to advertise an alternate hostname. +.Ic hostname +parameter may be specified to advertise the alternate hostname +.Ar name . +If the +.Ic source +parameter is used, the +.Ic hostnames +parameter may be specified to advertise a hostname based on +the source address. Table .Ar names contains a mapping of IP addresses to hostnames and .Xr smtpd 8 will automatically select the name that matches its source address when connected to the remote server. +The +.Ic hostname +and +.Ic hostnames +parameters are mutually exclusive. .El .Pp Additional per-rule adjustments available: @@ -435,11 +554,14 @@ to MXs for this domain. .Ic listen on Ar interface .Op Ar family .Op Ic port Ar port -.Op Ic tls | tls-require | smtps -.Op Ic certificate Ar name +.Op Ic tls | tls-require | smtps | secure +.Op Ic pki Ar pkiname .Op Ic auth | auth-optional .Op Ic tag Ar tag .Op Ic hostname Ar hostname +.Op Ic hostnames Ar names +.Op Ic mask-source +.Op Ic verify .Ek .Xc Specify an @@ -467,36 +589,16 @@ by default on port 465. .Ic tls-require may be used to force clients to establish a secure connection before being allowed to start an SMTP transaction. +.Ic secure +may be specified to provide both STARTTLS and SMTPS services. Host certificates may be used for these connections, -and are searched for in the -.Pa /etc/mail/certs -directory. +and must be priorly declared using the pki directive. If -.Ic certificate -is specified, -a certificate -.Ao Ar name Ac Ns .crt , -a key -.Ao Ar name Ac Ns .key -and Diffie-Hellman parameters -.Ao Ar name Ac Ns .dh -are searched for. -A certificate authority may be appended to the .crt -file to create a certificate chain. -If no -.Ic certificate +.Ic pki is specified, -the default interface name is instead used, -for example -.Pa fxp0.crt , -.Pa fxp0.key , -.Pa fxp0.ca , -and -.Pa fxp0.dh . -If no DH parameters are provided, smtpd will use -built-in parameters. -Creation of certificates is documented in -.Xr starttls 8 . +a certificate matching +.Ic name +is searched for. .Pp If the .Ic auth @@ -525,6 +627,27 @@ If the .Ic hostname parameter is used, then it will be used in the greeting banner instead of the default server name. +.Pp +The +.Ic hostnames +parameter overrides the server name for specific addresses. +Table +.Ar names +contains a mapping of IP addresses to hostnames and +.Xr smtpd 8 +will use the hostname that matches the address on which the connection arrives +if it is found in the mapping. +.Pp +If the +.Ic mask-source +parameter is used, then the listener will skip the "from" part +when prepending the "Received" header. +.Pp +If the listener is configured to provide SMTPS or STARTTLS and the +.Ic verify +parameter is used, then clients will be required to present a +certificate that can be verified before a SMTP session can be +initiated. .It Ic max-message-size Ar n Specify a maximum message size of .Ar n @@ -532,6 +655,35 @@ bytes. The argument may contain a multiplier, as documented in .Xr scan_scaled 3 . The default maximum message size is 35MB if none is specified. +.It Ic pki Ar hostname Ic certificate Ar certfile +Associate the certificate located in +.Ar certfile +with +.Ar hostname . +.Pp +A certificate chain may be created by appending one or many certificates, +including a Certificate Authority certificate, +to +.Ar certfile . +.Pp +Creation of certificates is documented in +.Xr starttls 8 . +.It Ic pki Ar hostname Ic key Ar keyfile +Associate the key located in +.Ar keyfile +with +.Ar hostname . +.It Ic pki Ar hostname Ic dhparams Ar dhfile +Associate the Diffie-Hellman parameters located in +.Ar dhfile +with +.Ar hostname . +.Pp +The parameters are used for ephemeral key exchange. +If not specified, OpenSMTPD will use safely generated builtin parameters. +.Pp +Creation of Diffie-Hellman parameters is documented in +.Xr openssl 1 . .It Ic queue compression Enable transparent compression of envelopes and messages. The only supported algorithm at the moment is gzip. @@ -540,7 +692,7 @@ Envelopes and messages may be inspected using the or .Xr gzcat 1 utilities. -.It Ic queue encryption key Ar key +.It Ic queue encryption Op key Ar key Enable transparent encryption of envelopes and messages. .Ar key must be a 16-byte random key in hexadecimal representation. @@ -551,6 +703,15 @@ utility as follow: $ openssl rand -hex 16 .Ed .Pp +If the +.Ar key +parameter is not specified, it is read with +.Xr getpass 3 +at startup. +If +.Ar key +is "stdin", then it is read from the standard input at startup. +.Pp The only supported algorithm is AES-256 in GCM mode. Envelopes and messages may be inspected using the .Xr smtpctl 8 @@ -562,6 +723,10 @@ perform compression before encryption. Tables are used to provide additional configuration information for .Xr smtpd 8 in the form of lists or key-value mappings. +The format of the entries depends on what the table is used for. +Refer to +.Xr table 5 +for the exhaustive documentation. .Pp The table is identified using table name .Ar name ; @@ -712,19 +877,25 @@ The mail server listens on all interfaces the default route(s) point to. Mail with a local destination should be sent to an external mda. First, the RSA certificate is created: .Bd -literal -offset indent -# openssl genrsa -out /etc/mail/certs/mail.example.com.key 4096 -# openssl req -new -x509 -key /etc/mail/certs/mail.example.com.key \e - -out /etc/mail/certs/mail.example.com.crt -days 365 -# chmod 600 /etc/mail/certs/mail.example.com.* +# openssl genrsa -out /etc/ssl/private/mail.example.com.key 4096 +# openssl req -new -x509 -key /etc/ssl/private/mail.example.com.key \e + -out /etc/ssl/mail.example.com.crt -days 365 +# chmod 600 /etc/ssl/mail.example.com.crt +# chmod 600 /etc/ssl/private/mail.example.com.key .Ed .Pp In the example above, a certificate valid for one year was created. The configuration file would look like this: .Bd -literal -offset indent +pki mail.example.com certificate "/etc/ssl/mail.example.com.crt" +pki mail.example.com key "/etc/ssl/private/mail.example.com.key" + listen on lo0 -listen on egress tls certificate mail.example.com auth +listen on egress tls pki mail.example.com auth + table aliases db:/etc/mail/aliases.db + accept for local alias <aliases> deliver to mda "/path/to/mda -f -" accept from any for domain example.org \e deliver to mda "/path/to/mda -f -" @@ -732,6 +903,7 @@ accept for any relay .Ed .Sh SEE ALSO .Xr mailer.conf 5 , +.Xr table 5 , .Xr makemap 8 , .Xr smtpd 8 .Sh HISTORY diff --git a/usr.sbin/smtpd/smtpd.h b/usr.sbin/smtpd/smtpd.h index 3baddd8f243..782d7ffb71c 100644 --- a/usr.sbin/smtpd/smtpd.h +++ b/usr.sbin/smtpd/smtpd.h @@ -1,4 +1,4 @@ -/* $OpenBSD: smtpd.h,v 1.432 2013/10/30 21:37:48 eric Exp $ */ +/* $OpenBSD: smtpd.h,v 1.433 2013/11/06 10:01:29 eric Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org> @@ -30,24 +30,16 @@ #define CONF_FILE "/etc/mail/smtpd.conf" #define MAILNAME_FILE "/etc/mail/mailname" #define CA_FILE "/etc/ssl/cert.pem" -#define MAX_LISTEN 16 + #define PROC_COUNT 10 -#define MAX_NAME_SIZE 64 #define MAX_HOPS_COUNT 100 #define DEFAULT_MAX_BODY_SIZE (35*1024*1024) - #define MAX_TAG_SIZE 32 - -#define MAX_TABLE_BACKEND_SIZE 32 - -/* return and forward path size */ #define MAX_FILTER_NAME 32 #define EXPAND_BUFFER 1024 -#define SMTPD_QUEUE_INTERVAL (15 * 60) -#define SMTPD_QUEUE_MAXINTERVAL (4 * 60 * 60) #define SMTPD_QUEUE_EXPIRY (4 * 24 * 60 * 60) #define SMTPD_USER "_smtpd" #define SMTPD_QUEUE_USER "_smtpq" @@ -80,9 +72,8 @@ #define F_STARTTLS_REQUIRE 0x20 #define F_AUTH_REQUIRE 0x40 #define F_LMTP 0x80 - -#define F_SCERT 0x01 -#define F_CCERT 0x02 +#define F_MASK_SOURCE 0x100 +#define F_TLS_VERIFY 0x200 /* must match F_* for mta */ #define RELAY_STARTTLS 0x01 @@ -93,6 +84,7 @@ #define RELAY_BACKUP 0x10 /* XXX - MUST BE SYNC-ED WITH F_BACKUP */ #define RELAY_MX 0x20 #define RELAY_LMTP 0x80 +#define RELAY_TLS_VERIFY 0x200 struct userinfo { char username[SMTPD_MAXLOGNAME]; @@ -107,14 +99,14 @@ struct netaddr { }; struct relayhost { - uint8_t flags; + uint16_t flags; char hostname[SMTPD_MAXHOSTNAMELEN]; uint16_t port; char cert[SMTPD_MAXPATHLEN]; char authtable[SMTPD_MAXPATHLEN]; char authlabel[SMTPD_MAXPATHLEN]; char sourcetable[SMTPD_MAXPATHLEN]; - char heloname[SMTPD_MAXPATHLEN]; + char heloname[SMTPD_MAXHOSTNAMELEN]; char helotable[SMTPD_MAXPATHLEN]; }; @@ -325,6 +317,7 @@ enum dest_type { }; enum action_type { + A_NONE, A_RELAY, A_RELAYVIA, A_MAILDIR, @@ -342,10 +335,19 @@ enum decision { struct rule { TAILQ_ENTRY(rule) r_entry; enum decision r_decision; + uint8_t r_nottag; char r_tag[MAX_TAG_SIZE]; + + uint8_t r_notsources; struct table *r_sources; + + uint8_t r_notsenders; struct table *r_senders; + uint8_t r_notrecipients; + struct table *r_recipients; + + uint8_t r_notdestination; enum dest_type r_desttype; struct table *r_destination; @@ -359,6 +361,7 @@ struct rule { struct table *r_mapping; struct table *r_userbase; time_t r_qexpire; + uint8_t r_forwardonly; }; struct delivery_mda { @@ -424,7 +427,7 @@ struct expand { struct expandnode *parent; }; -#define SMTPD_ENVELOPE_VERSION 1 +#define SMTPD_ENVELOPE_VERSION 2 struct envelope { TAILQ_ENTRY(envelope) entry; @@ -434,6 +437,7 @@ struct envelope { uint64_t id; enum envelope_flags flags; + char smtpname[SMTPD_MAXHOSTNAMELEN]; char helo[SMTPD_MAXHOSTNAMELEN]; char hostname[SMTPD_MAXHOSTNAMELEN]; char errorline[SMTPD_MAXLINESIZE]; @@ -459,7 +463,7 @@ struct envelope { }; struct listener { - uint8_t flags; + uint16_t flags; int fd; struct sockaddr_storage ss; in_port_t port; @@ -470,7 +474,8 @@ struct listener { void *ssl_ctx; char tag[MAX_TAG_SIZE]; char authtable[SMTPD_MAXLINESIZE]; - char helo[SMTPD_MAXHOSTNAMELEN]; + char hostname[SMTPD_MAXHOSTNAMELEN]; + char hostnametable[SMTPD_MAXPATHLEN]; TAILQ_ENTRY(listener) entry; }; diff --git a/usr.sbin/smtpd/ssl.c b/usr.sbin/smtpd/ssl.c index f7559a5abab..7b7adb8fd54 100644 --- a/usr.sbin/smtpd/ssl.c +++ b/usr.sbin/smtpd/ssl.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssl.c,v 1.55 2013/10/26 12:27:59 eric Exp $ */ +/* $OpenBSD: ssl.c,v 1.56 2013/11/06 10:01:29 eric Exp $ */ /* * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> @@ -235,75 +235,45 @@ ssl_ctx_create(void) } int -ssl_load_certfile(struct ssl **sp, const char *path, const char *name, uint8_t flags) +ssl_load_certificate(struct ssl *s, const char *pathname) { - struct ssl *s; - char pathname[PATH_MAX]; - int ret; - - if ((s = calloc(1, sizeof(*s))) == NULL) - fatal(NULL); - - s->flags = flags; - (void)strlcpy(s->ssl_name, name, sizeof(s->ssl_name)); - - ret = snprintf(pathname, sizeof(pathname), "%s/%s.crt", - path ? path : "/etc/ssl", name); - if (ret == -1 || (size_t)ret >= sizeof pathname) - goto err; s->ssl_cert = ssl_load_file(pathname, &s->ssl_cert_len, 0755); if (s->ssl_cert == NULL) - goto err; + return 0; + return 1; +} - ret = snprintf(pathname, sizeof(pathname), "%s/%s.key", - path ? path : "/etc/ssl/private", name); - if (ret == -1 || (size_t)ret >= sizeof pathname) - goto err; +int +ssl_load_keyfile(struct ssl *s, const char *pathname) +{ s->ssl_key = ssl_load_file(pathname, &s->ssl_key_len, 0700); if (s->ssl_key == NULL) - goto err; + return 0; + return 1; +} - ret = snprintf(pathname, sizeof(pathname), "%s/%s.ca", - path ? path : "/etc/ssl", name); - if (ret == -1 || (size_t)ret >= sizeof pathname) - goto err; +int +ssl_load_cafile(struct ssl *s, const char *pathname) +{ s->ssl_ca = ssl_load_file(pathname, &s->ssl_ca_len, 0755); - if (s->ssl_ca == NULL) { - if (errno == EACCES) - goto err; - log_info("info: No CA found in %s", pathname); - } + if (s->ssl_ca == NULL) + return 0; + return 1; +} - ret = snprintf(pathname, sizeof(pathname), "%s/%s.dh", - path ? path : "/etc/ssl", name); - if (ret == -1 || (size_t)ret >= sizeof pathname) - goto err; +int +ssl_load_dhparams(struct ssl *s, const char *pathname) +{ s->ssl_dhparams = ssl_load_file(pathname, &s->ssl_dhparams_len, 0755); if (s->ssl_dhparams == NULL) { if (errno == EACCES) - goto err; + return 0; log_info("info: No DH parameters found in %s: " "using built-in parameters", pathname); } - - *sp = s; - return (1); - -err: - if (s->ssl_cert != NULL) - free(s->ssl_cert); - if (s->ssl_key != NULL) - free(s->ssl_key); - if (s->ssl_ca != NULL) - free(s->ssl_ca); - if (s->ssl_dhparams != NULL) - free(s->ssl_dhparams); - if (s != NULL) - free(s); - return (0); + return 1; } - const char * ssl_to_text(const SSL *ssl) { diff --git a/usr.sbin/smtpd/ssl.h b/usr.sbin/smtpd/ssl.h index 4cbec59458f..9d127beffac 100644 --- a/usr.sbin/smtpd/ssl.h +++ b/usr.sbin/smtpd/ssl.h @@ -1,4 +1,4 @@ -/* $OpenBSD: ssl.h,v 1.2 2013/07/19 09:04:07 eric Exp $ */ +/* $OpenBSD: ssl.h,v 1.3 2013/11/06 10:01:29 eric Exp $ */ /* * Copyright (c) 2013 Gilles Chehade <gilles@poolp.org> * @@ -21,22 +21,28 @@ struct ssl { char ssl_name[PATH_MAX]; + + char *ssl_ca_file; char *ssl_ca; off_t ssl_ca_len; + + char *ssl_cert_file; char *ssl_cert; off_t ssl_cert_len; + + char *ssl_key_file; char *ssl_key; off_t ssl_key_len; + + char *ssl_dhparams_file; char *ssl_dhparams; off_t ssl_dhparams_len; - uint8_t flags; }; /* ssl.c */ void ssl_init(void); int ssl_setup(SSL_CTX **, struct ssl *); SSL_CTX *ssl_ctx_create(void); -int ssl_load_certfile(struct ssl **, const char *, const char *, uint8_t); void *ssl_mta_init(char *, off_t, char *, off_t); void *ssl_smtp_init(void *, char *, off_t, char *, off_t); int ssl_cmp(struct ssl *, struct ssl *); @@ -51,6 +57,11 @@ char *ssl_load_key(const char *, off_t *, char *); const char *ssl_to_text(const SSL *); void ssl_error(const char *); +int ssl_load_certificate(struct ssl *, const char *); +int ssl_load_keyfile(struct ssl *, const char *); +int ssl_load_cafile(struct ssl *, const char *); +int ssl_load_dhparams(struct ssl *, const char *); + /* ssl_privsep.c */ int ssl_ctx_use_private_key(SSL_CTX *, char *, off_t); diff --git a/usr.sbin/smtpd/to.c b/usr.sbin/smtpd/to.c index 8e3a93f1bf9..b9da9be0274 100644 --- a/usr.sbin/smtpd/to.c +++ b/usr.sbin/smtpd/to.c @@ -1,4 +1,4 @@ -/* $OpenBSD: to.c,v 1.11 2013/10/28 10:32:17 eric Exp $ */ +/* $OpenBSD: to.c,v 1.12 2013/11/06 10:01:29 eric Exp $ */ /* * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> @@ -321,8 +321,12 @@ text_to_relayhost(struct relayhost *relay, const char *s) { static const struct schema { const char *name; - uint8_t flags; + uint16_t flags; } schemas [] = { + /* + * new schemas should be *appended* otherwise the default + * schema index needs to be updated later in this function. + */ { "smtp://", 0 }, { "lmtp://", F_LMTP }, { "smtp+tls://", F_TLS_OPTIONAL }, @@ -330,8 +334,8 @@ text_to_relayhost(struct relayhost *relay, const char *s) { "tls://", F_STARTTLS }, { "smtps+auth://", F_SMTPS|F_AUTH }, { "tls+auth://", F_STARTTLS|F_AUTH }, - { "ssl://", F_SMTPS|F_STARTTLS }, - { "ssl+auth://", F_SMTPS|F_STARTTLS|F_AUTH }, + { "secure://", F_SMTPS|F_STARTTLS }, + { "secure+auth://", F_SMTPS|F_STARTTLS|F_AUTH }, { "backup://", F_BACKUP } }; const char *errstr = NULL; @@ -414,10 +418,10 @@ relayhost_to_text(const struct relayhost *relay) bzero(buf, sizeof buf); switch (relay->flags) { case F_SMTPS|F_STARTTLS|F_AUTH: - strlcat(buf, "ssl+auth://", sizeof buf); + strlcat(buf, "secure+auth://", sizeof buf); break; case F_SMTPS|F_STARTTLS: - strlcat(buf, "ssl://", sizeof buf); + strlcat(buf, "secure://", sizeof buf); break; case F_STARTTLS|F_AUTH: strlcat(buf, "tls+auth://", sizeof buf); @@ -425,12 +429,18 @@ relayhost_to_text(const struct relayhost *relay) case F_SMTPS|F_AUTH: strlcat(buf, "smtps+auth://", sizeof buf); break; + case F_STARTTLS|F_TLS_VERIFY: + strlcat(buf, "tls://", sizeof buf); + break; case F_STARTTLS: strlcat(buf, "tls://", sizeof buf); break; case F_SMTPS: strlcat(buf, "smtps://", sizeof buf); break; + case F_SMTPS|F_TLS_VERIFY: + strlcat(buf, "smtps://", sizeof buf); + break; case F_BACKUP: strlcat(buf, "backup://", sizeof buf); break; @@ -501,19 +511,26 @@ rule_to_text(struct rule *r) bzero(buf, sizeof buf); strlcpy(buf, r->r_decision == R_ACCEPT ? "accept" : "reject", sizeof buf); if (r->r_tag[0]) { - strlcat(buf, " on ", sizeof buf); + strlcat(buf, " tagged ", sizeof buf); + if (r->r_nottag) + strlcat(buf, "! ", sizeof buf); strlcat(buf, r->r_tag, sizeof buf); } strlcat(buf, " from ", sizeof buf); + if (r->r_notsources) + strlcat(buf, "! ", sizeof buf); strlcat(buf, r->r_sources->t_name, sizeof buf); + strlcat(buf, " for ", sizeof buf); + if (r->r_notdestination) + strlcat(buf, "! ", sizeof buf); switch (r->r_desttype) { case DEST_DOM: if (r->r_destination == NULL) { - strlcat(buf, " for any", sizeof buf); + strlcat(buf, " any", sizeof buf); break; } - strlcat(buf, " for domain ", sizeof buf); + strlcat(buf, " domain ", sizeof buf); strlcat(buf, r->r_destination->t_name, sizeof buf); if (r->r_mapping) { strlcat(buf, " alias ", sizeof buf); @@ -522,11 +539,11 @@ rule_to_text(struct rule *r) break; case DEST_VDOM: if (r->r_destination == NULL) { - strlcat(buf, " for any virtual ", sizeof buf); + strlcat(buf, " any virtual ", sizeof buf); strlcat(buf, r->r_mapping->t_name, sizeof buf); break; } - strlcat(buf, " for domain ", sizeof buf); + strlcat(buf, " domain ", sizeof buf); strlcat(buf, r->r_destination->t_name, sizeof buf); strlcat(buf, " virtual ", sizeof buf); strlcat(buf, r->r_mapping->t_name, sizeof buf); @@ -564,6 +581,8 @@ rule_to_text(struct rule *r) strlcat(buf, r->r_value.buffer, sizeof buf); strlcat(buf, "\"", sizeof buf); break; + case A_NONE: + break; } return buf; |