diff options
author | Eric Faurot <eric@cvs.openbsd.org> | 2012-09-27 18:57:26 +0000 |
---|---|---|
committer | Eric Faurot <eric@cvs.openbsd.org> | 2012-09-27 18:57:26 +0000 |
commit | dea6a45ba08769fac83771a96811e7fb20fc6269 (patch) | |
tree | 4079fb55d1ffc2dbbf91a7dab610569cbd9e44e8 /usr.sbin/smtpd/lka_session.c | |
parent | bad24a20111a04542ef10df25e711086eeee0039 (diff) |
clarify the alias expansion code.
The session manages a list of nodes to process. A node has a link to the
parent node from which it has been expanded, and a link to the rule that
led to its creation. Depending on its type and the associated rule, each
node is either "expanded" to create new nodes or "submitted" to create a
final envelope. Nodes which have already been seen, either processed or
not, are discarded to avoid loops.
The expansion process is bootstrapped by creating an EXPAND_ADDRESS node
from the original dest, with no rule and no parent. It is done when all
nodes have been expanded or if an error occurs before. The expand depth
is limited 5 levels. The whole expansion fails if the limit is reached.
While there, make sure that only one .forward file is queried at a time,
and only append the subfolder tag in the maildir case.
Fixe issues with some virtual map setups where the dest would get mixed
up, and make the whole expansion process generally easier to follow.
ok chl@ gilles@
Diffstat (limited to 'usr.sbin/smtpd/lka_session.c')
-rw-r--r-- | usr.sbin/smtpd/lka_session.c | 628 |
1 files changed, 257 insertions, 371 deletions
diff --git a/usr.sbin/smtpd/lka_session.c b/usr.sbin/smtpd/lka_session.c index 2b5cf095a82..9afa1bb731f 100644 --- a/usr.sbin/smtpd/lka_session.c +++ b/usr.sbin/smtpd/lka_session.c @@ -1,7 +1,8 @@ -/* $OpenBSD: lka_session.c,v 1.35 2012/09/26 19:52:20 eric Exp $ */ +/* $OpenBSD: lka_session.c,v 1.36 2012/09/27 18:57:25 eric Exp $ */ /* * Copyright (c) 2011 Gilles Chehade <gilles@openbsd.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 @@ -39,42 +40,40 @@ #include "smtpd.h" #include "log.h" -enum lka_session_flags { - F_ERROR = 0x1 -}; +#define F_ERROR 0x01 +#define F_WAITING 0x02 struct lka_session { - SPLAY_ENTRY(lka_session) nodes; - uint64_t id; + uint64_t id; + + TAILQ_HEAD(, envelope) deliverylist; + struct expand expand; + + int flags; + struct submit_status ss; - TAILQ_HEAD(, envelope) deliverylist; - struct expand expand; + struct envelope envelope; - uint8_t iterations; - uint32_t pending; - enum lka_session_flags flags; - struct submit_status ss; + struct xnodes nodes; + /* waiting for fwdrq */ + struct rule *rule; + struct expandnode *node; }; -static void lka_session_fail(struct lka_session *); -static void lka_session_destroy(struct lka_session *); -static void lka_session_pickup(struct lka_session *, struct envelope *); -static int lka_session_envelope_expand(struct lka_session *, struct envelope *); -static int lka_session_resume(struct lka_session *, struct envelope *); -static void lka_session_done(struct lka_session *); -static size_t lka_session_expand_format(char *, size_t, struct envelope *); -static void lka_session_request_forwardfile(struct lka_session *, - struct envelope *, char *); -static void lka_session_deliver(struct lka_session *, struct envelope *); -static int lka_session_resolve_node(struct envelope *, struct expandnode *); -static int lka_session_rcpt_action(struct envelope *); +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 size_t lka_expand_format(char *, size_t, const struct envelope *); +static void mailaddr_to_username(const struct mailaddr *, char *, size_t); +static const char * mailaddr_tag(const struct mailaddr *); static struct tree sessions = SPLAY_INITIALIZER(&sessions); void lka_session(struct submit_status *ss) { - struct lka_session *lks; + struct lka_session *lks; + struct expandnode xn; lks = xcalloc(1, sizeof(*lks), "lka_session"); lks->id = generate_uid(); @@ -84,256 +83,103 @@ lka_session(struct submit_status *ss) TAILQ_INIT(&lks->deliverylist); tree_xset(&sessions, lks->id, lks); - if (! lka_session_envelope_expand(lks, &ss->envelope)) - lka_session_fail(lks); - else - lka_session_pickup(lks, &ss->envelope); -} - -static int -lka_session_envelope_expand(struct lka_session *lks, struct envelope *ep) -{ - char *user; - char *tag; - struct user_backend *ub; - struct mta_user u; - char username[MAX_LOCALPART_SIZE]; - - /* remote delivery, no need to process further */ - if (ep->type == D_MTA) { - lka_session_deliver(lks, ep); - return 1; - } - - switch (ep->rule.r_condition.c_type) { - case C_ALL: - case C_DOM: - if (ep->agent.mda.user[0] == '\0') - user = ep->dest.user; - else - user = ep->agent.mda.user; - xlowercase(username, user, sizeof(username)); - - /* gilles+hackers@ -> gilles@ */ - if ((tag = strchr(username, '+')) != NULL) { - *tag++ = '\0'; - - /* skip dots after the '+' */ - while (*tag == '.') - tag++; - } - - if (aliases_get(ep->rule.r_amap, &lks->expand, username)) - return 1; - - bzero(&u, sizeof (u)); - ub = user_backend_lookup(USER_PWD); - if (! ub->getbyname(&u, username)) - return 0; - - (void)strlcpy(ep->agent.mda.user, u.username, - sizeof (ep->agent.mda.user)); - - ep->type = D_MDA; - switch (ep->rule.r_action) { - case A_MBOX: - ep->agent.mda.method = A_MBOX; - (void)strlcpy(ep->agent.mda.user, - u.username, - sizeof (ep->agent.mda.user)); - break; - case A_MAILDIR: - case A_FILENAME: - case A_MDA: - ep->agent.mda.method = ep->rule.r_action; - (void)strlcpy(ep->agent.mda.buffer, - ep->rule.r_value.buffer, - sizeof (ep->agent.mda.buffer)); - - if (tag && *tag) { - (void)strlcat(ep->agent.mda.buffer, "/.", - sizeof (ep->agent.mda.buffer)); - (void)strlcat(ep->agent.mda.buffer, tag, - sizeof (ep->agent.mda.buffer)); - } - break; - default: - fatalx("lka_session_envelope_expand: unexpected rule action"); - return 0; - } - - lka_session_request_forwardfile(lks, ep, u.username); - return 1; - - case C_VDOM: - if (aliases_virtual_get(ep->rule.r_condition.c_map, - &lks->expand, &ep->dest)) - return 1; - - return 0; - - default: - fatalx("lka_session_envelope_expand: unexpected type"); - return 0; - } + lks->envelope = ss->envelope; - return 0; + TAILQ_INIT(&lks->nodes); + bzero(&xn, sizeof xn); + xn.type = EXPAND_ADDRESS; + xn.u.mailaddr = lks->envelope.dest; /* XXX we should only have rcpt */ + 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 envelope *ep; + struct lka_session *lks; + struct rule *rule; + struct expandnode *xn; lks = tree_xget(&sessions, fwreq->id); - lks->pending--; - - ep = &fwreq->envelope; + xn = lks->node; + rule = lks->rule; - if (fd != -1) { - /* opened .forward okay */ - strlcpy(lks->expand.user, fwreq->as_user, - sizeof lks->expand.user); - if (! forwards_get(fd, &lks->expand)) { - lks->ss.code = 530; - lks->flags |= F_ERROR; - } - close(fd); - lka_session_pickup(lks, ep); - return; - } + lks->flags &= ~F_WAITING; - if (fwreq->status) { - /* .forward not present */ - lka_session_deliver(lks, ep); - lka_session_pickup(lks, ep); - return; + if (fd == -1 && fwreq->status) { + /* no .forward, just deliver to local user */ + log_debug("lka: no .forward for user %s, just deliver", + fwreq->as_user), + lka_submit(lks, rule, xn); } - - /* opening .forward failed */ - lks->ss.code = 530; - lks->flags |= F_ERROR; - lka_session_pickup(lks, ep); -} - -static void -lka_session_pickup(struct lka_session *lks, struct envelope *ep) -{ - int ret; - - /* we want to do five iterations of lka_session_resume() but - * we need to be interruptible in case lka_session_resume() - * has sent an imsg and expects an answer. - */ - while (! (lks->flags & F_ERROR) && - ! lks->pending && lks->iterations < 5) { - ++lks->iterations; - ret = lka_session_resume(lks, ep); - if (ret == -1) { + else if (fd == -1) { + log_debug("lka: opening .forward failed for user %s", + fwreq->as_user), + lks->ss.code = 530; + lks->flags |= F_ERROR; + } + else { + /* expand for the current user and rule */ + lks->expand.rule = rule; + lks->expand.parent = xn; + if (forwards_get(fd, &lks->expand) == 0) { + /* no aliases */ lks->ss.code = 530; lks->flags |= F_ERROR; } - - if (lks->pending || ret <= 0) - break; + close(fd); } - - if (lks->pending) - return; - - lka_session_done(lks); + lka_resume(lks); } -static int -lka_session_resume(struct lka_session *lks, struct envelope *ep) +static void +lka_resume(struct lka_session *lks) { - struct expandnode *xn; - uint8_t done = 1; - - RB_FOREACH(xn, expandtree, &lks->expand.tree) { - - /* this node has already been expanded, skip */ - if (xn->done) - continue; - done = 0; - - switch (lka_session_resolve_node(ep, xn)) { - case 0: - if (! lka_session_envelope_expand(lks, ep)) - return -1; - break; - case 1: - lka_session_deliver(lks, ep); - break; - default: - return -1; - } + struct envelope *ep; + struct expandnode *xn; - xn->done = 1; + if (lks->flags & F_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->flags & F_ERROR) + goto error; } - /* still not done after 5 iterations ? loop detected ... reject */ - if (!done && lks->iterations == 5) - return -1; - - /* we're done expanding, no need for another iteration */ - if (RB_ROOT(&lks->expand.tree) == NULL || done) - return 0; - - return 1; -} - -static void -lka_session_done(struct lka_session *lks) -{ - struct envelope *ep; - - /* delivery list is empty OR expansion led to an error, reject */ + /* delivery list is empty, reject */ if (TAILQ_FIRST(&lks->deliverylist) == NULL) { - log_info("lka_session_done: expansion led to empty delivery list"); + log_info("lka_done: expansion led to empty delivery list"); lks->flags |= F_ERROR; } - if (lks->flags & F_ERROR) - goto done; - - /* process the delivery list and submit envelopes to queue */ - while ((ep = TAILQ_FIRST(&lks->deliverylist)) != NULL) { - imsg_compose_event(env->sc_ievs[PROC_QUEUE], - IMSG_QUEUE_SUBMIT_ENVELOPE, 0, 0, -1, ep, sizeof *ep); - TAILQ_REMOVE(&lks->deliverylist, ep, entry); - free(ep); - } - ep = &lks->ss.envelope; - imsg_compose_event(env->sc_ievs[PROC_QUEUE], - IMSG_QUEUE_COMMIT_ENVELOPES, 0, 0, -1, ep, sizeof *ep); - -done: + error: if (lks->flags & F_ERROR) { lks->ss.code = 530; imsg_compose_event(env->sc_ievs[PROC_MFA], IMSG_LKA_RCPT, 0, 0, -1, &lks->ss, sizeof(struct submit_status)); + while ((ep = TAILQ_FIRST(&lks->deliverylist)) != NULL) { + TAILQ_REMOVE(&lks->deliverylist, ep, entry); + free(ep); + } } - lka_session_destroy(lks); -} - -static void -lka_session_fail(struct lka_session *lks) -{ - lks->ss.code = 530; - imsg_compose_event(env->sc_ievs[PROC_MFA], IMSG_LKA_RCPT, 0, 0, -1, - &lks->ss, sizeof(lks->ss)); - lka_session_destroy(lks); -} - -static void -lka_session_destroy(struct lka_session *lks) -{ - struct envelope *ep; - - 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); + imsg_compose_event(env->sc_ievs[PROC_QUEUE], + IMSG_QUEUE_SUBMIT_ENVELOPE, 0, 0, -1, + ep, sizeof *ep); + free(ep); + } + ep = &lks->ss.envelope; + imsg_compose_event(env->sc_ievs[PROC_QUEUE], + IMSG_QUEUE_COMMIT_ENVELOPES, 0, 0, -1, ep, sizeof *ep); } expand_free(&lks->expand); @@ -342,126 +188,180 @@ lka_session_destroy(struct lka_session *lks) } static void -lka_session_deliver(struct lka_session *lks, struct envelope *ep) -{ - struct envelope *new_ep; - - new_ep = xmemdup(ep, sizeof *ep, "lka_session_deliver"); - if (new_ep->type == D_MDA) { - switch (new_ep->agent.mda.method) { - case A_MAILDIR: - case A_FILENAME: - case A_MDA: - if (! lka_session_expand_format( - new_ep->agent.mda.buffer, - sizeof(new_ep->agent.mda.buffer), new_ep)) - lks->flags |= F_ERROR; - default: - break; - } - } - else if (new_ep->type == D_MTA) { - new_ep->agent.mta.relay = ep->rule.r_value.relayhost; - if (ep->rule.r_as) { - if (ep->rule.r_as->user[0]) { - strlcpy(new_ep->sender.user, - ep->rule.r_as->user, - sizeof new_ep->sender.user); - } - if (ep->rule.r_as->domain[0]) { - strlcpy(new_ep->sender.domain, - ep->rule.r_as->domain, - sizeof new_ep->sender.domain); - } - } - } - TAILQ_INSERT_TAIL(&lks->deliverylist, new_ep, entry); -} - -static int -lka_session_resolve_node(struct envelope *ep, struct expandnode *xn) +lka_expand(struct lka_session *lks, struct rule *rule, struct expandnode *xn) { - struct envelope oldep; + struct forward_req fwreq; + struct envelope ep; + struct expandnode node; - memcpy(&oldep, ep, sizeof (*ep)); - bzero(&ep->agent, sizeof (ep->agent)); + if (xn->depth >= 5) { + log_debug("lka_expand: node too deep."); + lks->flags |= F_ERROR; + lks->ss.code = 530; + return; + } switch (xn->type) { case EXPAND_INVALID: case EXPAND_INCLUDE: - fatalx("lka_session_resolve_node: unexpected type"); + fatalx("lka_expand: unexpected type"); break; case EXPAND_ADDRESS: - log_debug("lka_resolve_node: node is address: %s@%s", + log_debug("lka_expand: expanding address: %s@%s", xn->u.mailaddr.user, xn->u.mailaddr.domain); - ep->dest = xn->u.mailaddr; - /* evaluation of ruleset assumes local source - * since we're expanding on already accepted - * source. - */ - ep->flags |= DF_INTERNAL; - if (! lka_session_rcpt_action(ep)) - return -1; - return 0; + /* Pass the node through the ruleset */ + ep = lks->envelope; + ep.dest = xn->u.mailaddr; + if (xn->parent) /* nodes with parent are forward addresses */ + ep.flags |= DF_INTERNAL; + rule = ruleset_match(&ep); + if (rule == NULL) { + lks->flags |= F_ERROR; + lks->ss.code = 530; + break; /* no rule for address */ + } + if (rule->r_action == A_RELAY || rule->r_action == A_RELAYVIA) { + lka_submit(lks, rule, xn); + } + else if (rule->r_condition.c_type == C_VDOM) { + /* expand */ + lks->expand.rule = rule; + lks->expand.parent = xn; + if (aliases_virtual_get(rule->r_condition.c_map, + &lks->expand, &xn->u.mailaddr) == 0) { + log_debug("lka_expand: no aliases for virtual"); + lks->flags |= F_ERROR; + lks->ss.code = 530; + } + } + else { + lks->expand.rule = rule; + lks->expand.parent = xn; + node.type = EXPAND_USERNAME; + mailaddr_to_username(&xn->u.mailaddr, node.u.user, + sizeof node.u.user); + expand_insert(&lks->expand, &node); + } + break; case EXPAND_USERNAME: - log_debug("lka_resolve_node: node is local username: %s", - xn->u.user); - ep->type = D_MDA; - strlcpy(ep->agent.mda.user, xn->u.user, - sizeof ep->agent.mda.user); - - /* overwrite the initial condition before we expand the - * envelope again. if we came from a C_VDOM, not doing - * so would lead to a VDOM loop causing recipient to be - * rejected. - * - * i'll find a more elegant solution later, for now it - * fixes an annoying bug. - */ - ep->rule.r_condition.c_type = C_DOM; - - /* if expansion of a user results in same user ... deliver */ - if (strcmp(xn->u.user, xn->as_user) == 0) { - ep->agent.mda.method = oldep.agent.mda.method; + log_debug("lka_expand: expanding username: %s", xn->u.user); + + if (xn->sameuser) { + log_debug("lka_expand: same user, submitting"); + lka_submit(lks, rule, xn); break; } - /* otherwise rewrite delivery user with expansion result */ - (void)strlcpy(ep->agent.mda.user, xn->u.user, - sizeof (ep->agent.mda.user)); - return 0; + /* expand aliases with the given rule */ + + lks->expand.rule = rule; + lks->expand.parent = xn; + if (rule->r_amap && + aliases_get(rule->r_amap, &lks->expand, xn->u.user)) + break; + + /* no aliases found, query forward file */ + lks->rule = rule; + lks->node = xn; + fwreq.id = lks->id; + (void)strlcpy(fwreq.as_user, xn->u.user, sizeof(fwreq.as_user)); + imsg_compose_event(env->sc_ievs[PROC_PARENT], + IMSG_PARENT_FORWARD_OPEN, 0, 0, -1, &fwreq, sizeof(fwreq)); + lks->flags |= F_WAITING; + break; case EXPAND_FILENAME: - log_debug("lka_resolve_node: node is filename: %s", - xn->u.buffer); - ep->type = D_MDA; - ep->agent.mda.method = A_FILENAME; - (void)strlcpy(ep->agent.mda.buffer, xn->u.buffer, - sizeof (ep->agent.mda.buffer)); - (void)strlcpy(ep->agent.mda.user, xn->as_user, - sizeof (ep->agent.mda.user)); + log_debug("lka_expand: expanding filename: %s", xn->u.buffer); + lka_submit(lks, rule, xn); break; case EXPAND_FILTER: - log_debug("lka_resolve_node: node is filter: %s", - xn->u.buffer); - ep->type = D_MDA; - ep->agent.mda.method = A_MDA; - (void)strlcpy(ep->agent.mda.buffer, xn->u.buffer, - sizeof (ep->agent.mda.buffer)); - (void)strlcpy(ep->agent.mda.user, xn->as_user, + log_debug("lka_expand: expanding filter: %s", xn->u.buffer); + lka_submit(lks, rule, xn); + break; + } +} + +static void +lka_submit(struct lka_session *lks, struct rule *rule, struct expandnode *xn) +{ + struct envelope *ep; + struct expandnode *xn2; + const char *tag; + + ep = xmemdup(&lks->envelope, sizeof *ep, "lka_submit"); + + switch (rule->r_action) { + case A_RELAY: + case A_RELAYVIA: + ep->type = D_MTA; + ep->agent.mta.relay = rule->r_value.relayhost; + if (xn->type != EXPAND_ADDRESS) + fatalx("lka_deliver: expect address"); + ep->dest = xn->u.mailaddr; + break; + case A_MBOX: + case A_MAILDIR: + case A_FILENAME: + case A_MDA: + ep->type = D_MDA; + + /* set username */ + xn2 = xn; + while(xn2 && (xn2->type != EXPAND_USERNAME)) + xn2 = xn2->parent; + if (xn2 == NULL) + fatalx("no user in node ancestry"); + (void)strlcpy(ep->agent.mda.user, xn2->u.user, sizeof (ep->agent.mda.user)); + + /* set dest */ + xn2 = xn; + while(xn2 && (xn2->type != EXPAND_ADDRESS)) + xn2 = xn2->parent; + if (xn2 == NULL) + fatalx("no address in node ancestry"); + ep->dest = xn2->u.mailaddr; + + if (xn->type == EXPAND_FILENAME) { + ep->agent.mda.method = A_FILENAME; + strlcpy(ep->agent.mda.buffer, xn->u.buffer, + sizeof ep->agent.mda.buffer); + } + else if (xn->type == EXPAND_FILTER) { + ep->agent.mda.method = A_MDA; + strlcpy(ep->agent.mda.buffer, xn->u.buffer, + sizeof ep->agent.mda.buffer); + } + else if (xn->type == EXPAND_USERNAME) { + ep->agent.mda.method = rule->r_action; + strlcpy(ep->agent.mda.buffer, rule->r_value.buffer, + sizeof ep->agent.mda.buffer); + tag = mailaddr_tag(&ep->dest); + if (rule->r_action == A_MAILDIR && tag && tag[0]) { + strlcat(ep->agent.mda.buffer, "/.", + sizeof (ep->agent.mda.buffer)); + strlcat(ep->agent.mda.buffer, tag, + sizeof (ep->agent.mda.buffer)); + } + } else + fatalx("lka_deliver: bad node type"); + + lka_expand_format(ep->agent.mda.buffer, + sizeof(ep->agent.mda.buffer), ep); break; + default: + fatalx("lka_submit: bad rule action"); } - return 1; + TAILQ_INSERT_TAIL(&lks->deliverylist, ep, entry); } static size_t -lka_session_expand_format(char *buf, size_t len, struct envelope *ep) +lka_expand_format(char *buf, size_t len, const struct envelope *ep) { char *p, *pbuf; size_t ret, lret = 0; @@ -517,7 +417,7 @@ lka_session_expand_format(char *buf, size_t len, struct envelope *ep) } } if (*p == '%') { - char *string, *tmp = p + 1; + const char *string, *tmp = p + 1; int digit = 0; if (isdigit((int)*tmp)) { @@ -578,42 +478,28 @@ copy: return ret; } -static int -lka_session_rcpt_action(struct envelope *ep) +static void +mailaddr_to_username(const struct mailaddr *maddr, char *dst, size_t len) { - struct rule *r; - - r = ruleset_match(ep); - if (r == NULL) { - ep->type = D_MTA; - return 0; - } + char *tag; - ep->rule = *r; - switch (ep->rule.r_action) { - case A_MBOX: - case A_MAILDIR: - case A_FILENAME: - case A_MDA: - ep->type = D_MDA; - break; - default: - ep->type = D_MTA; - } + xlowercase(dst, maddr->user, len); - return 1; + /* gilles+hackers@ -> gilles@ */ + if ((tag = strchr(dst, '+')) != NULL) + *tag++ = '\0'; } -static void -lka_session_request_forwardfile(struct lka_session *lks, - struct envelope *ep, char *as_user) +static const char * +mailaddr_tag(const struct mailaddr *maddr) { - struct forward_req fwreq; - - fwreq.id = lks->id; - fwreq.envelope = *ep; - (void)strlcpy(fwreq.as_user, as_user, sizeof(fwreq.as_user)); - imsg_compose_event(env->sc_ievs[PROC_PARENT], - IMSG_PARENT_FORWARD_OPEN, 0, 0, -1, &fwreq, sizeof(fwreq)); - ++lks->pending; + const char *tag; + + if ((tag = strchr(maddr->user, '+'))) { + tag++; + while (*tag == '.') + tag++; + } + + return (tag); } |