diff options
-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; |