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