/* $OpenBSD: lka_session.c,v 1.100 2024/02/02 23:33:42 gilles Exp $ */ /* * Copyright (c) 2011 Gilles Chehade * Copyright (c) 2012 Eric Faurot * * 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 #include #include #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_dispatcher, IMSG_SMTP_EXPAND_RCPT, 0, 0, -1); m_add_id(p_dispatcher, lks->id); m_add_int(p_dispatcher, lks->error); if (lks->errormsg) m_add_string(p_dispatcher, lks->errormsg); else { if (lks->error == LKA_PERMFAIL) m_add_string(p_dispatcher, "550 Invalid recipient"); else if (lks->error == LKA_TEMPFAIL) m_add_string(p_dispatcher, "451 Temporary failure"); } m_close(p_dispatcher); 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.dest.user, "SRS0=", 5) == 0 || strncasecmp(ep.dest.user, "SRS1=", 5) == 0)) { srs_decoded = srs_decode(mailaddr_to_text(&ep.dest)); if (srs_decoded && text_to_mailaddr(&ep.dest, srs_decoded)) { /* flag envelope internal and override dest */ ep.flags |= EF_INTERNAL; xn->u.mailaddr = ep.dest; lks->envelope = ep; } else { log_warn("SRS failed to decode: %s", mailaddr_to_text(&ep.dest)); } } /* 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; xn->realuser_uid = lk.userinfo.uid; if (xn->sameuser && xn->parent->forwarded) { log_trace(TRACE_EXPAND, "expand: lka_expand: same " "user, submitting"); lka_submit(lks, rule, xn); break; } /* when alternate delivery user is provided, * skip other users forward files. */ if (dsp->u.local.user) { if (strcmp(dsp->u.local.user, xn->u.user) != 0) { log_trace(TRACE_EXPAND, "expand: lka_expand: " "alternate delivery user mismatch recipient " "user, skip .forward, 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: if (xn->parent->realuser && xn->parent->realuser_uid == 0) { log_trace(TRACE_EXPAND, "expand: filename not allowed in root's forward"); lks->error = LKA_TEMPFAIL; break; } 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: if (xn->parent->realuser && xn->parent->realuser_uid == 0) { log_trace(TRACE_EXPAND, "expand: filter not allowed in root's forward"); lks->error = LKA_TEMPFAIL; break; } 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); format = "%s"; if (xn->type == EXPAND_FILENAME) format = "/usr/libexec/mail.mboxfile -f %%{mbox.from} %s"; (void)snprintf(ep->mda_exec, sizeof(ep->mda_exec), format, xn->u.buffer); } break; } TAILQ_INSERT_TAIL(&lks->deliverylist, ep, entry); }