diff options
author | Gilles Chehade <gilles@poolp.org> | 2020-04-30 02:06:46 +0200 |
---|---|---|
committer | Gilles Chehade <gilles@poolp.org> | 2020-04-30 02:06:46 +0200 |
commit | d4180f85d53f7381da9fcc7257cf61d0b29497a0 (patch) | |
tree | 928f5541f702f22aec334cd7262d611821ff44cf /smtpd/lka_session.c | |
parent | Merge remote-tracking branch 'portable/native' (diff) | |
download | OpenSMTPD-d4180f85d53f7381da9fcc7257cf61d0b29497a0.tar.xz OpenSMTPD-d4180f85d53f7381da9fcc7257cf61d0b29497a0.zip |
merge histories
Diffstat (limited to 'smtpd/lka_session.c')
-rw-r--r-- | smtpd/lka_session.c | 556 |
1 files changed, 556 insertions, 0 deletions
diff --git a/smtpd/lka_session.c b/smtpd/lka_session.c new file mode 100644 index 00000000..999e01d6 --- /dev/null +++ b/smtpd/lka_session.c @@ -0,0 +1,556 @@ +/* $OpenBSD: lka_session.c,v 1.93 2019/09/20 17:46:05 gilles Exp $ */ + +/* + * Copyright (c) 2011 Gilles Chehade <gilles@poolp.org> + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> +#include <sys/wait.h> + +#include <netinet/in.h> + +#include <ctype.h> +#include <errno.h> +#include <event.h> +#include <imsg.h> +#include <resolv.h> +#include <pwd.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <limits.h> + +#include "smtpd.h" +#include "log.h" + +#define EXPAND_DEPTH 10 + +#define F_WAITING 0x01 + +struct lka_session { + uint64_t id; /* given by smtp */ + + TAILQ_HEAD(, envelope) deliverylist; + struct expand expand; + + int flags; + int error; + const char *errormsg; + struct envelope envelope; + struct xnodes nodes; + /* waiting for fwdrq */ + struct rule *rule; + struct expandnode *node; +}; + +static void lka_expand(struct lka_session *, struct rule *, + struct expandnode *); +static void lka_submit(struct lka_session *, struct rule *, + struct expandnode *); +static void lka_resume(struct lka_session *); + +static int init; +static struct tree sessions; + +void +lka_session(uint64_t id, struct envelope *envelope) +{ + struct lka_session *lks; + struct expandnode xn; + + if (init == 0) { + init = 1; + tree_init(&sessions); + } + + lks = xcalloc(1, sizeof(*lks)); + lks->id = id; + RB_INIT(&lks->expand.tree); + TAILQ_INIT(&lks->deliverylist); + tree_xset(&sessions, lks->id, lks); + + lks->envelope = *envelope; + + TAILQ_INIT(&lks->nodes); + memset(&xn, 0, sizeof xn); + xn.type = EXPAND_ADDRESS; + xn.u.mailaddr = lks->envelope.rcpt; + lks->expand.parent = NULL; + lks->expand.rule = NULL; + lks->expand.queue = &lks->nodes; + expand_insert(&lks->expand, &xn); + lka_resume(lks); +} + +void +lka_session_forward_reply(struct forward_req *fwreq, int fd) +{ + struct lka_session *lks; + struct dispatcher *dsp; + struct rule *rule; + struct expandnode *xn; + int ret; + + lks = tree_xget(&sessions, fwreq->id); + xn = lks->node; + rule = lks->rule; + + lks->flags &= ~F_WAITING; + + switch (fwreq->status) { + case 0: + /* permanent failure while lookup ~/.forward */ + log_trace(TRACE_EXPAND, "expand: ~/.forward failed for user %s", + fwreq->user); + lks->error = LKA_PERMFAIL; + break; + case 1: + if (fd == -1) { + dsp = dict_get(env->sc_dispatchers, lks->rule->dispatcher); + if (dsp->u.local.forward_only) { + log_trace(TRACE_EXPAND, "expand: no .forward " + "for user %s on forward-only rule", fwreq->user); + lks->error = LKA_TEMPFAIL; + } + else if (dsp->u.local.expand_only) { + 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 { + dsp = dict_get(env->sc_dispatchers, rule->dispatcher); + + /* expand for the current user and rule */ + lks->expand.rule = rule; + lks->expand.parent = xn; + + /* forwards_get() will close the descriptor no matter what */ + ret = forwards_get(fd, &lks->expand); + if (ret == -1) { + log_trace(TRACE_EXPAND, "expand: temporary " + "forward error for user %s", fwreq->user); + lks->error = LKA_TEMPFAIL; + } + else if (ret == 0) { + if (dsp->u.local.forward_only) { + log_trace(TRACE_EXPAND, "expand: empty .forward " + "for user %s on forward-only rule", fwreq->user); + lks->error = LKA_TEMPFAIL; + } + else if (dsp->u.local.expand_only) { + 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; + default: + /* temporary failure while looking up ~/.forward */ + lks->error = LKA_TEMPFAIL; + } + + if (lks->error == LKA_TEMPFAIL && lks->errormsg == NULL) + lks->errormsg = "424 4.2.4 Mailing list expansion problem"; + if (lks->error == LKA_PERMFAIL && lks->errormsg == NULL) + lks->errormsg = "524 5.2.4 Mailing list expansion problem"; + + lka_resume(lks); +} + +static void +lka_resume(struct lka_session *lks) +{ + struct envelope *ep; + struct expandnode *xn; + + if (lks->error) + goto error; + + /* pop next node and expand it */ + while ((xn = TAILQ_FIRST(&lks->nodes))) { + TAILQ_REMOVE(&lks->nodes, xn, tq_entry); + lka_expand(lks, xn->rule, xn); + if (lks->flags & F_WAITING) + return; + if (lks->error) + goto error; + } + + /* delivery list is empty, reject */ + if (TAILQ_FIRST(&lks->deliverylist) == NULL) { + log_trace(TRACE_EXPAND, "expand: lka_done: expanded to empty " + "delivery list"); + lks->error = LKA_PERMFAIL; + lks->errormsg = "524 5.2.4 Mailing list expansion problem"; + } + error: + if (lks->error) { + m_create(p_pony, IMSG_SMTP_EXPAND_RCPT, 0, 0, -1); + m_add_id(p_pony, lks->id); + m_add_int(p_pony, lks->error); + + if (lks->errormsg) + m_add_string(p_pony, lks->errormsg); + else { + if (lks->error == LKA_PERMFAIL) + m_add_string(p_pony, "550 Invalid recipient"); + else if (lks->error == LKA_TEMPFAIL) + m_add_string(p_pony, "451 Temporary failure"); + } + + m_close(p_pony); + while ((ep = TAILQ_FIRST(&lks->deliverylist)) != NULL) { + TAILQ_REMOVE(&lks->deliverylist, ep, entry); + free(ep); + } + } + else { + /* Process the delivery list and submit envelopes to queue */ + while ((ep = TAILQ_FIRST(&lks->deliverylist)) != NULL) { + TAILQ_REMOVE(&lks->deliverylist, ep, entry); + m_create(p_queue, IMSG_LKA_ENVELOPE_SUBMIT, 0, 0, -1); + m_add_id(p_queue, lks->id); + m_add_envelope(p_queue, ep); + m_close(p_queue); + free(ep); + } + + m_create(p_queue, IMSG_LKA_ENVELOPE_COMMIT, 0, 0, -1); + m_add_id(p_queue, lks->id); + m_close(p_queue); + } + + expand_clear(&lks->expand); + tree_xpop(&sessions, lks->id); + free(lks); +} + +static void +lka_expand(struct lka_session *lks, struct rule *rule, struct expandnode *xn) +{ + struct forward_req fwreq; + struct envelope ep; + struct expandnode node; + struct mailaddr maddr; + struct dispatcher *dsp; + struct table *userbase; + int r; + union lookup lk; + char *tag; + const char *srs_decoded; + + if (xn->depth >= EXPAND_DEPTH) { + log_trace(TRACE_EXPAND, "expand: lka_expand: node too deep."); + lks->error = LKA_PERMFAIL; + lks->errormsg = "524 5.2.4 Mailing list expansion problem"; + return; + } + + switch (xn->type) { + case EXPAND_INVALID: + case EXPAND_INCLUDE: + fatalx("lka_expand: unexpected type"); + break; + + case EXPAND_ADDRESS: + + log_trace(TRACE_EXPAND, "expand: lka_expand: address: %s@%s " + "[depth=%d]", + xn->u.mailaddr.user, xn->u.mailaddr.domain, xn->depth); + + + ep = lks->envelope; + ep.dest = xn->u.mailaddr; + if (xn->parent) /* nodes with parent are forward addresses */ + ep.flags |= EF_INTERNAL; + + /* handle SRS */ + if (env->sc_srs_key != NULL && + ep.sender.user[0] == '\0' && + (strncasecmp(ep.rcpt.user, "SRS0=", 5) == 0 || + strncasecmp(ep.rcpt.user, "SRS1=", 5) == 0)) { + srs_decoded = srs_decode(mailaddr_to_text(&ep.rcpt)); + if (srs_decoded && + text_to_mailaddr(&ep.rcpt, srs_decoded)) { + /* flag envelope internal and override rcpt */ + ep.flags |= EF_INTERNAL; + xn->u.mailaddr = ep.rcpt; + lks->envelope = ep; + } + else { + log_warn("SRS failed to decode: %s", + mailaddr_to_text(&ep.rcpt)); + } + } + + /* Pass the node through the ruleset */ + rule = ruleset_match(&ep); + if (rule == NULL || rule->reject) { + lks->error = (errno == EAGAIN) ? + LKA_TEMPFAIL : LKA_PERMFAIL; + break; + } + + dsp = dict_xget(env->sc_dispatchers, rule->dispatcher); + if (dsp->type == DISPATCHER_REMOTE) { + lka_submit(lks, rule, xn); + } + else if (dsp->u.local.table_virtual) { + /* expand */ + lks->expand.rule = rule; + lks->expand.parent = xn; + + /* temporary replace the mailaddr with a copy where + * we eventually strip the '+'-part before lookup. + */ + maddr = xn->u.mailaddr; + xlowercase(maddr.user, xn->u.mailaddr.user, + sizeof maddr.user); + r = aliases_virtual_get(&lks->expand, &maddr); + if (r == -1) { + lks->error = LKA_TEMPFAIL; + log_trace(TRACE_EXPAND, "expand: lka_expand: " + "error in virtual alias lookup"); + } + else if (r == 0) { + lks->error = LKA_PERMFAIL; + log_trace(TRACE_EXPAND, "expand: lka_expand: " + "no aliases for virtual"); + } + if (lks->error == LKA_TEMPFAIL && lks->errormsg == NULL) + lks->errormsg = "424 4.2.4 Mailing list expansion problem"; + if (lks->error == LKA_PERMFAIL && lks->errormsg == NULL) + lks->errormsg = "524 5.2.4 Mailing list expansion problem"; + } + else { + lks->expand.rule = rule; + lks->expand.parent = xn; + xn->rule = rule; + + memset(&node, 0, sizeof node); + node.type = EXPAND_USERNAME; + xlowercase(node.u.user, xn->u.mailaddr.user, + sizeof node.u.user); + expand_insert(&lks->expand, &node); + } + break; + + case EXPAND_USERNAME: + log_trace(TRACE_EXPAND, "expand: lka_expand: username: %s " + "[depth=%d, sameuser=%d]", + xn->u.user, xn->depth, xn->sameuser); + + /* expand aliases with the given rule */ + dsp = dict_xget(env->sc_dispatchers, rule->dispatcher); + + lks->expand.rule = rule; + lks->expand.parent = xn; + + if (!xn->sameuser && + (dsp->u.local.table_alias || dsp->u.local.table_virtual)) { + if (dsp->u.local.table_alias) + r = aliases_get(&lks->expand, xn->u.user); + if (dsp->u.local.table_virtual) + r = aliases_virtual_get(&lks->expand, &xn->u.mailaddr); + if (r == -1) { + log_trace(TRACE_EXPAND, "expand: lka_expand: " + "error in alias lookup"); + lks->error = LKA_TEMPFAIL; + if (lks->errormsg == NULL) + lks->errormsg = "424 4.2.4 Mailing list expansion problem"; + } + if (r) + break; + } + + /* gilles+hackers@ -> gilles@ */ + if ((tag = strchr(xn->u.user, *env->sc_subaddressing_delim)) != NULL) { + *tag++ = '\0'; + (void)strlcpy(xn->subaddress, tag, sizeof xn->subaddress); + } + + userbase = table_find(env, dsp->u.local.table_userbase); + r = table_lookup(userbase, K_USERINFO, xn->u.user, &lk); + if (r == -1) { + log_trace(TRACE_EXPAND, "expand: lka_expand: " + "backend error while searching user"); + lks->error = LKA_TEMPFAIL; + break; + } + if (r == 0) { + log_trace(TRACE_EXPAND, "expand: lka_expand: " + "user-part does not match system user"); + lks->error = LKA_PERMFAIL; + break; + } + xn->realuser = 1; + + if (xn->sameuser && xn->parent->forwarded) { + log_trace(TRACE_EXPAND, "expand: lka_expand: same " + "user, submitting"); + lka_submit(lks, rule, xn); + break; + } + + /* no aliases found, query forward file */ + lks->rule = rule; + lks->node = xn; + xn->forwarded = 1; + + memset(&fwreq, 0, sizeof(fwreq)); + fwreq.id = lks->id; + (void)strlcpy(fwreq.user, lk.userinfo.username, sizeof(fwreq.user)); + (void)strlcpy(fwreq.directory, lk.userinfo.directory, sizeof(fwreq.directory)); + fwreq.uid = lk.userinfo.uid; + fwreq.gid = lk.userinfo.gid; + + m_compose(p_parent, IMSG_LKA_OPEN_FORWARD, 0, 0, -1, + &fwreq, sizeof(fwreq)); + lks->flags |= F_WAITING; + break; + + case EXPAND_FILENAME: + dsp = dict_xget(env->sc_dispatchers, rule->dispatcher); + if (dsp->u.local.forward_only) { + 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: + dsp = dict_xget(env->sc_dispatchers, rule->dispatcher); + if (dsp->u.local.forward_only) { + 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') + lks->error = LKA_TEMPFAIL; + else if (xn->u.buffer[0] == '5') + lks->error = LKA_PERMFAIL; + lks->errormsg = xn->u.buffer; + break; + + case EXPAND_FILTER: + dsp = dict_xget(env->sc_dispatchers, rule->dispatcher); + if (dsp->u.local.forward_only) { + 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); + break; + } +} + +static struct expandnode * +lka_find_ancestor(struct expandnode *xn, enum expand_type type) +{ + while (xn && (xn->type != type)) + xn = xn->parent; + if (xn == NULL) { + log_warnx("warn: lka_find_ancestor: no ancestors of type %d", + type); + fatalx(NULL); + } + return (xn); +} + +static void +lka_submit(struct lka_session *lks, struct rule *rule, struct expandnode *xn) +{ + struct envelope *ep; + struct dispatcher *dsp; + const char *user; + const char *format; + + ep = xmemdup(&lks->envelope, sizeof *ep); + (void)strlcpy(ep->dispatcher, rule->dispatcher, sizeof ep->dispatcher); + + dsp = dict_xget(env->sc_dispatchers, ep->dispatcher); + + switch (dsp->type) { + case DISPATCHER_REMOTE: + if (xn->type != EXPAND_ADDRESS) + fatalx("lka_deliver: expect address"); + ep->type = D_MTA; + ep->dest = xn->u.mailaddr; + break; + + case DISPATCHER_BOUNCE: + case DISPATCHER_LOCAL: + if (xn->type != EXPAND_USERNAME && + xn->type != EXPAND_FILENAME && + xn->type != EXPAND_FILTER) + fatalx("lka_deliver: wrong type: %d", xn->type); + + ep->type = D_MDA; + ep->dest = lka_find_ancestor(xn, EXPAND_ADDRESS)->u.mailaddr; + if (xn->type == EXPAND_USERNAME) { + (void)strlcpy(ep->mda_user, xn->u.user, sizeof(ep->mda_user)); + (void)strlcpy(ep->mda_subaddress, xn->subaddress, sizeof(ep->mda_subaddress)); + } + else { + user = !xn->parent->realuser ? + SMTPD_USER : + xn->parent->u.user; + (void)strlcpy(ep->mda_user, user, sizeof (ep->mda_user)); + + /* this battle needs to be fought ... */ + if (xn->type == EXPAND_FILTER && + strcmp(ep->mda_user, SMTPD_USER) == 0) + log_warnx("commands executed from aliases " + "run with %s privileges", SMTPD_USER); + + if (xn->type == EXPAND_FILENAME) + format = PATH_LIBEXEC"/mail.mboxfile -f %%{mbox.from} %s"; + else if (xn->type == EXPAND_FILTER) + format = "%s"; + (void)snprintf(ep->mda_exec, sizeof(ep->mda_exec), + format, xn->u.buffer); + } + break; + } + + TAILQ_INSERT_TAIL(&lks->deliverylist, ep, entry); +} |