mirror of
https://github.com/openbsd/src.git
synced 2024-12-22 07:27:59 -08:00
a95ad3cfed
any recipient .forward file except that of the alternate delivery user. ok millert@
564 lines
15 KiB
C
564 lines
15 KiB
C
/* $OpenBSD: lka_session.c,v 1.100 2024/02/02 23:33:42 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 <errno.h>
|
|
#include <stdlib.h>
|
|
#include <string.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_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);
|
|
}
|