1*68e5c67bSchristos /* $NetBSD: dotforward.c,v 1.2 2017/02/14 01:16:45 christos Exp $ */
241fbaed0Stron
341fbaed0Stron /*++
441fbaed0Stron /* NAME
541fbaed0Stron /* dotforward 3
641fbaed0Stron /* SUMMARY
741fbaed0Stron /* $HOME/.forward file expansion
841fbaed0Stron /* SYNOPSIS
941fbaed0Stron /* #include "local.h"
1041fbaed0Stron /*
1141fbaed0Stron /* int deliver_dotforward(state, usr_attr, statusp)
1241fbaed0Stron /* LOCAL_STATE state;
1341fbaed0Stron /* USER_ATTR usr_attr;
1441fbaed0Stron /* int *statusp;
1541fbaed0Stron /* DESCRIPTION
1641fbaed0Stron /* deliver_dotforward() delivers a message to the destinations
1741fbaed0Stron /* listed in a recipient's .forward file(s) as specified through
1841fbaed0Stron /* the forward_path configuration parameter. The result is
1941fbaed0Stron /* zero when no acceptable .forward file was found, or when
2041fbaed0Stron /* a recipient is listed in her own .forward file. Expansions
2141fbaed0Stron /* are scrutinized with the forward_expansion_filter parameter.
2241fbaed0Stron /*
2341fbaed0Stron /* Arguments:
2441fbaed0Stron /* .IP state
2541fbaed0Stron /* Message delivery attributes (sender, recipient etc.).
2641fbaed0Stron /* Attributes describing alias, include or forward expansion.
2741fbaed0Stron /* A table with the results from expanding aliases or lists.
2841fbaed0Stron /* A table with delivered-to: addresses taken from the message.
2941fbaed0Stron /* .IP usr_attr
3041fbaed0Stron /* Attributes describing user rights and environment.
3141fbaed0Stron /* .IP statusp
3241fbaed0Stron /* Message delivery status. See below.
3341fbaed0Stron /* DIAGNOSTICS
3441fbaed0Stron /* Fatal errors: out of memory. Warnings: bad $HOME/.forward
3541fbaed0Stron /* file type, permissions or ownership. The message delivery
3641fbaed0Stron /* status is non-zero when delivery should be tried again.
3741fbaed0Stron /* SEE ALSO
3841fbaed0Stron /* include(3) include file processor.
3941fbaed0Stron /* LICENSE
4041fbaed0Stron /* .ad
4141fbaed0Stron /* .fi
4241fbaed0Stron /* The Secure Mailer license must be distributed with this software.
4341fbaed0Stron /* AUTHOR(S)
4441fbaed0Stron /* Wietse Venema
4541fbaed0Stron /* IBM T.J. Watson Research
4641fbaed0Stron /* P.O. Box 704
4741fbaed0Stron /* Yorktown Heights, NY 10598, USA
4841fbaed0Stron /*--*/
4941fbaed0Stron
5041fbaed0Stron /* System library. */
5141fbaed0Stron
5241fbaed0Stron #include <sys_defs.h>
5341fbaed0Stron #include <sys/stat.h>
5441fbaed0Stron #include <unistd.h>
5541fbaed0Stron #include <errno.h>
5641fbaed0Stron #include <fcntl.h>
5741fbaed0Stron #ifdef USE_PATHS_H
5841fbaed0Stron #include <paths.h>
5941fbaed0Stron #endif
6041fbaed0Stron #include <string.h>
6141fbaed0Stron
6241fbaed0Stron /* Utility library. */
6341fbaed0Stron
6441fbaed0Stron #include <msg.h>
6541fbaed0Stron #include <vstring.h>
6641fbaed0Stron #include <vstream.h>
6741fbaed0Stron #include <htable.h>
6841fbaed0Stron #include <open_as.h>
6941fbaed0Stron #include <lstat_as.h>
7041fbaed0Stron #include <iostuff.h>
7141fbaed0Stron #include <stringops.h>
7241fbaed0Stron #include <mymalloc.h>
7341fbaed0Stron #include <mac_expand.h>
7441fbaed0Stron
7541fbaed0Stron /* Global library. */
7641fbaed0Stron
7741fbaed0Stron #include <mypwd.h>
7841fbaed0Stron #include <bounce.h>
79e694ac3bStron #include <defer.h>
8041fbaed0Stron #include <been_here.h>
8141fbaed0Stron #include <mail_params.h>
8241fbaed0Stron #include <mail_conf.h>
8341fbaed0Stron #include <ext_prop.h>
8441fbaed0Stron #include <sent.h>
8541fbaed0Stron #include <dsn_mask.h>
8641fbaed0Stron #include <trace.h>
8741fbaed0Stron
8841fbaed0Stron /* Application-specific. */
8941fbaed0Stron
9041fbaed0Stron #include "local.h"
9141fbaed0Stron
9241fbaed0Stron #define NO 0
9341fbaed0Stron #define YES 1
9441fbaed0Stron
9541fbaed0Stron /* deliver_dotforward - expand contents of .forward file */
9641fbaed0Stron
deliver_dotforward(LOCAL_STATE state,USER_ATTR usr_attr,int * statusp)9741fbaed0Stron int deliver_dotforward(LOCAL_STATE state, USER_ATTR usr_attr, int *statusp)
9841fbaed0Stron {
9941fbaed0Stron const char *myname = "deliver_dotforward";
10041fbaed0Stron struct stat st;
10141fbaed0Stron VSTRING *path;
10241fbaed0Stron struct mypasswd *mypwd;
10341fbaed0Stron int fd;
10441fbaed0Stron VSTREAM *fp;
10541fbaed0Stron int status;
10641fbaed0Stron int forward_found = NO;
10741fbaed0Stron int lookup_status;
10841fbaed0Stron int addr_count;
10941fbaed0Stron char *saved_forward_path;
11041fbaed0Stron char *lhs;
11141fbaed0Stron char *next;
11241fbaed0Stron int expand_status;
11341fbaed0Stron int saved_notify;
11441fbaed0Stron
11541fbaed0Stron /*
11641fbaed0Stron * Make verbose logging easier to understand.
11741fbaed0Stron */
11841fbaed0Stron state.level++;
11941fbaed0Stron if (msg_verbose)
12041fbaed0Stron MSG_LOG_STATE(myname, state);
12141fbaed0Stron
12241fbaed0Stron /*
12341fbaed0Stron * Skip this module if per-user forwarding is disabled.
12441fbaed0Stron */
12541fbaed0Stron if (*var_forward_path == 0)
12641fbaed0Stron return (NO);
12741fbaed0Stron
12841fbaed0Stron /*
12941fbaed0Stron * Skip non-existing users. The mailbox delivery routine will catch the
13041fbaed0Stron * error.
13141fbaed0Stron */
132e694ac3bStron if ((errno = mypwnam_err(state.msg_attr.user, &mypwd)) != 0) {
133e694ac3bStron msg_warn("error looking up passwd info for %s: %m",
134e694ac3bStron state.msg_attr.user);
135e694ac3bStron dsb_simple(state.msg_attr.why, "4.0.0", "user lookup error");
136e694ac3bStron *statusp = defer_append(BOUNCE_FLAGS(state.request),
137e694ac3bStron BOUNCE_ATTR(state.msg_attr));
138e694ac3bStron return (YES);
139e694ac3bStron }
140e694ac3bStron if (mypwd == 0)
14141fbaed0Stron return (NO);
14241fbaed0Stron
14341fbaed0Stron /*
14441fbaed0Stron * From here on no early returns or we have a memory leak.
14541fbaed0Stron */
14641fbaed0Stron
14741fbaed0Stron /*
14841fbaed0Stron * EXTERNAL LOOP CONTROL
14941fbaed0Stron *
15041fbaed0Stron * Set the delivered message attribute to the recipient, so that this
15141fbaed0Stron * message will list the correct forwarding address.
15241fbaed0Stron */
15341fbaed0Stron if (var_frozen_delivered == 0)
15441fbaed0Stron state.msg_attr.delivered = state.msg_attr.rcpt.address;
15541fbaed0Stron
15641fbaed0Stron /*
15741fbaed0Stron * DELIVERY RIGHTS
15841fbaed0Stron *
15941fbaed0Stron * Do not inherit rights from the .forward file owner. Instead, use the
16041fbaed0Stron * recipient's rights, and insist that the .forward file is owned by the
16141fbaed0Stron * recipient. This is a small but significant difference. Use the
16241fbaed0Stron * recipient's rights for all /file and |command deliveries, and pass on
16341fbaed0Stron * these rights to command/file destinations in included files. When
16441fbaed0Stron * these are the rights of root, the /file and |command delivery routines
16541fbaed0Stron * will use unprivileged default rights instead. Better safe than sorry.
16641fbaed0Stron */
16741fbaed0Stron SET_USER_ATTR(usr_attr, mypwd, state.level);
16841fbaed0Stron
16941fbaed0Stron /*
17041fbaed0Stron * DELIVERY POLICY
17141fbaed0Stron *
17241fbaed0Stron * Update the expansion type attribute so that we can decide if deliveries
17341fbaed0Stron * to |command and /file/name are allowed at all.
17441fbaed0Stron */
17541fbaed0Stron state.msg_attr.exp_type = EXPAND_TYPE_FWD;
17641fbaed0Stron
17741fbaed0Stron /*
17841fbaed0Stron * WHERE TO REPORT DELIVERY PROBLEMS
17941fbaed0Stron *
18041fbaed0Stron * Set the owner attribute so that 1) include files won't set the sender to
18141fbaed0Stron * be this user and 2) mail forwarded to other local users will be
18241fbaed0Stron * resubmitted as a new queue file.
18341fbaed0Stron */
18441fbaed0Stron state.msg_attr.owner = state.msg_attr.user;
18541fbaed0Stron
18641fbaed0Stron /*
18741fbaed0Stron * Search the forward_path for an existing forward file.
18841fbaed0Stron *
18941fbaed0Stron * If unmatched extensions should never be propagated, or if a forward file
19041fbaed0Stron * name includes the address extension, don't propagate the extension to
19141fbaed0Stron * the recipient addresses.
19241fbaed0Stron */
19341fbaed0Stron status = 0;
19441fbaed0Stron path = vstring_alloc(100);
19541fbaed0Stron saved_forward_path = mystrdup(var_forward_path);
19641fbaed0Stron next = saved_forward_path;
19741fbaed0Stron lookup_status = -1;
19841fbaed0Stron
199837e7c1aSchristos while ((lhs = mystrtok(&next, CHARS_COMMA_SP)) != 0) {
20041fbaed0Stron expand_status = local_expand(path, lhs, &state, &usr_attr,
20141fbaed0Stron var_fwd_exp_filter);
20241fbaed0Stron if ((expand_status & (MAC_PARSE_ERROR | MAC_PARSE_UNDEF)) == 0) {
20341fbaed0Stron lookup_status =
20441fbaed0Stron lstat_as(STR(path), &st, usr_attr.uid, usr_attr.gid);
20541fbaed0Stron if (msg_verbose)
20641fbaed0Stron msg_info("%s: path %s expand_status %d look_status %d", myname,
20741fbaed0Stron STR(path), expand_status, lookup_status);
20841fbaed0Stron if (lookup_status >= 0) {
20941fbaed0Stron if ((expand_status & LOCAL_EXP_EXTENSION_MATCHED) != 0
21041fbaed0Stron || (local_ext_prop_mask & EXT_PROP_FORWARD) == 0)
21141fbaed0Stron state.msg_attr.unmatched = 0;
21241fbaed0Stron break;
21341fbaed0Stron }
21441fbaed0Stron }
21541fbaed0Stron }
21641fbaed0Stron
21741fbaed0Stron /*
21841fbaed0Stron * Process the forward file.
21941fbaed0Stron *
22041fbaed0Stron * Assume that usernames do not have file system meta characters. Open the
22141fbaed0Stron * .forward file as the user. Ignore files that aren't regular files,
22241fbaed0Stron * files that are owned by the wrong user, or files that have world write
22341fbaed0Stron * permission enabled.
22441fbaed0Stron *
22541fbaed0Stron * DUPLICATE/LOOP ELIMINATION
22641fbaed0Stron *
22741fbaed0Stron * If this user includes (an alias of) herself in her own .forward file,
22841fbaed0Stron * deliver to the user instead.
22941fbaed0Stron */
23041fbaed0Stron if (lookup_status >= 0) {
23141fbaed0Stron
23241fbaed0Stron /*
23341fbaed0Stron * Don't expand a verify-only request.
23441fbaed0Stron */
23541fbaed0Stron if (state.request->flags & DEL_REQ_FLAG_MTA_VRFY) {
23641fbaed0Stron dsb_simple(state.msg_attr.why, "2.0.0",
23741fbaed0Stron "forward via file: %s", STR(path));
23841fbaed0Stron *statusp = sent(BOUNCE_FLAGS(state.request),
23941fbaed0Stron SENT_ATTR(state.msg_attr));
24041fbaed0Stron forward_found = YES;
24141fbaed0Stron } else if (been_here(state.dup_filter, "forward %s", STR(path)) == 0) {
24241fbaed0Stron state.msg_attr.exp_from = state.msg_attr.local;
24341fbaed0Stron if (S_ISREG(st.st_mode) == 0) {
24441fbaed0Stron msg_warn("file %s is not a regular file", STR(path));
24541fbaed0Stron } else if (st.st_uid != 0 && st.st_uid != usr_attr.uid) {
24641fbaed0Stron msg_warn("file %s has bad owner uid %ld",
24741fbaed0Stron STR(path), (long) st.st_uid);
24841fbaed0Stron } else if (st.st_mode & 002) {
24941fbaed0Stron msg_warn("file %s is world writable", STR(path));
25041fbaed0Stron } else if ((fd = open_as(STR(path), O_RDONLY, 0, usr_attr.uid, usr_attr.gid)) < 0) {
25141fbaed0Stron msg_warn("cannot open file %s: %m", STR(path));
25241fbaed0Stron } else {
25341fbaed0Stron
25441fbaed0Stron /*
25541fbaed0Stron * XXX DSN. When delivering to an alias (i.e. the envelope
25641fbaed0Stron * sender address is not replaced) any ENVID, RET, or ORCPT
25741fbaed0Stron * parameters are propagated to all forwarding addresses
25841fbaed0Stron * associated with that alias. The NOTIFY parameter is
25941fbaed0Stron * propagated to the forwarding addresses, except that any
26041fbaed0Stron * SUCCESS keyword is removed.
26141fbaed0Stron */
26241fbaed0Stron close_on_exec(fd, CLOSE_ON_EXEC);
26341fbaed0Stron addr_count = 0;
26441fbaed0Stron fp = vstream_fdopen(fd, O_RDONLY);
26541fbaed0Stron saved_notify = state.msg_attr.rcpt.dsn_notify;
26641fbaed0Stron state.msg_attr.rcpt.dsn_notify =
26741fbaed0Stron (saved_notify == DSN_NOTIFY_SUCCESS ?
26841fbaed0Stron DSN_NOTIFY_NEVER : saved_notify & ~DSN_NOTIFY_SUCCESS);
26941fbaed0Stron status = deliver_token_stream(state, usr_attr, fp, &addr_count);
27041fbaed0Stron if (vstream_fclose(fp))
27141fbaed0Stron msg_warn("close file %s: %m", STR(path));
27241fbaed0Stron if (addr_count > 0) {
27341fbaed0Stron forward_found = YES;
27441fbaed0Stron been_here(state.dup_filter, "forward-done %s", STR(path));
27541fbaed0Stron
27641fbaed0Stron /*
27741fbaed0Stron * XXX DSN. When delivering to an alias (i.e. the
27841fbaed0Stron * envelope sender address is not replaced) and the
27941fbaed0Stron * original NOTIFY parameter for the alias contained the
28041fbaed0Stron * SUCCESS keyword, an "expanded" DSN is issued for the
28141fbaed0Stron * alias.
28241fbaed0Stron */
28341fbaed0Stron if (status == 0 && (saved_notify & DSN_NOTIFY_SUCCESS)) {
28441fbaed0Stron state.msg_attr.rcpt.dsn_notify = saved_notify;
28541fbaed0Stron dsb_update(state.msg_attr.why, "2.0.0", "expanded",
28641fbaed0Stron DSB_SKIP_RMTA, DSB_SKIP_REPLY,
28741fbaed0Stron "alias expanded");
28841fbaed0Stron (void) trace_append(BOUNCE_FLAG_NONE,
28941fbaed0Stron SENT_ATTR(state.msg_attr));
29041fbaed0Stron }
29141fbaed0Stron }
29241fbaed0Stron }
29341fbaed0Stron } else if (been_here_check(state.dup_filter, "forward-done %s", STR(path)) != 0)
29441fbaed0Stron forward_found = YES; /* else we're recursive */
29541fbaed0Stron }
29641fbaed0Stron
29741fbaed0Stron /*
29841fbaed0Stron * Clean up.
29941fbaed0Stron */
30041fbaed0Stron vstring_free(path);
30141fbaed0Stron myfree(saved_forward_path);
30241fbaed0Stron mypwfree(mypwd);
30341fbaed0Stron
30441fbaed0Stron *statusp = status;
30541fbaed0Stron return (forward_found);
30641fbaed0Stron }
307