Home | History | Annotate | Line # | Download | only in local
      1 /*	$NetBSD: dotforward.c,v 1.2 2017/02/14 01:16:45 christos Exp $	*/
      2 
      3 /*++
      4 /* NAME
      5 /*	dotforward 3
      6 /* SUMMARY
      7 /*	$HOME/.forward file expansion
      8 /* SYNOPSIS
      9 /*	#include "local.h"
     10 /*
     11 /*	int	deliver_dotforward(state, usr_attr, statusp)
     12 /*	LOCAL_STATE state;
     13 /*	USER_ATTR usr_attr;
     14 /*	int	*statusp;
     15 /* DESCRIPTION
     16 /*	deliver_dotforward() delivers a message to the destinations
     17 /*	listed in a recipient's .forward file(s) as specified through
     18 /*	the forward_path configuration parameter.  The result is
     19 /*	zero when no acceptable .forward file was found, or when
     20 /*	a recipient is listed in her own .forward file. Expansions
     21 /*	are scrutinized with the forward_expansion_filter parameter.
     22 /*
     23 /*	Arguments:
     24 /* .IP state
     25 /*	Message delivery attributes (sender, recipient etc.).
     26 /*	Attributes describing alias, include or forward expansion.
     27 /*	A table with the results from expanding aliases or lists.
     28 /*	A table with delivered-to: addresses taken from the message.
     29 /* .IP usr_attr
     30 /*	Attributes describing user rights and environment.
     31 /* .IP statusp
     32 /*	Message delivery status. See below.
     33 /* DIAGNOSTICS
     34 /*	Fatal errors: out of memory. Warnings: bad $HOME/.forward
     35 /*	file type, permissions or ownership.  The message delivery
     36 /*	status is non-zero when delivery should be tried again.
     37 /* SEE ALSO
     38 /*	include(3) include file processor.
     39 /* LICENSE
     40 /* .ad
     41 /* .fi
     42 /*	The Secure Mailer license must be distributed with this software.
     43 /* AUTHOR(S)
     44 /*	Wietse Venema
     45 /*	IBM T.J. Watson Research
     46 /*	P.O. Box 704
     47 /*	Yorktown Heights, NY 10598, USA
     48 /*--*/
     49 
     50 /* System library. */
     51 
     52 #include <sys_defs.h>
     53 #include <sys/stat.h>
     54 #include <unistd.h>
     55 #include <errno.h>
     56 #include <fcntl.h>
     57 #ifdef USE_PATHS_H
     58 #include <paths.h>
     59 #endif
     60 #include <string.h>
     61 
     62 /* Utility library. */
     63 
     64 #include <msg.h>
     65 #include <vstring.h>
     66 #include <vstream.h>
     67 #include <htable.h>
     68 #include <open_as.h>
     69 #include <lstat_as.h>
     70 #include <iostuff.h>
     71 #include <stringops.h>
     72 #include <mymalloc.h>
     73 #include <mac_expand.h>
     74 
     75 /* Global library. */
     76 
     77 #include <mypwd.h>
     78 #include <bounce.h>
     79 #include <defer.h>
     80 #include <been_here.h>
     81 #include <mail_params.h>
     82 #include <mail_conf.h>
     83 #include <ext_prop.h>
     84 #include <sent.h>
     85 #include <dsn_mask.h>
     86 #include <trace.h>
     87 
     88 /* Application-specific. */
     89 
     90 #include "local.h"
     91 
     92 #define NO	0
     93 #define YES	1
     94 
     95 /* deliver_dotforward - expand contents of .forward file */
     96 
     97 int     deliver_dotforward(LOCAL_STATE state, USER_ATTR usr_attr, int *statusp)
     98 {
     99     const char *myname = "deliver_dotforward";
    100     struct stat st;
    101     VSTRING *path;
    102     struct mypasswd *mypwd;
    103     int     fd;
    104     VSTREAM *fp;
    105     int     status;
    106     int     forward_found = NO;
    107     int     lookup_status;
    108     int     addr_count;
    109     char   *saved_forward_path;
    110     char   *lhs;
    111     char   *next;
    112     int     expand_status;
    113     int     saved_notify;
    114 
    115     /*
    116      * Make verbose logging easier to understand.
    117      */
    118     state.level++;
    119     if (msg_verbose)
    120 	MSG_LOG_STATE(myname, state);
    121 
    122     /*
    123      * Skip this module if per-user forwarding is disabled.
    124      */
    125     if (*var_forward_path == 0)
    126 	return (NO);
    127 
    128     /*
    129      * Skip non-existing users. The mailbox delivery routine will catch the
    130      * error.
    131      */
    132     if ((errno = mypwnam_err(state.msg_attr.user, &mypwd)) != 0) {
    133 	msg_warn("error looking up passwd info for %s: %m",
    134 		 state.msg_attr.user);
    135 	dsb_simple(state.msg_attr.why, "4.0.0", "user lookup error");
    136 	*statusp = defer_append(BOUNCE_FLAGS(state.request),
    137 				BOUNCE_ATTR(state.msg_attr));
    138 	return (YES);
    139     }
    140     if (mypwd == 0)
    141 	return (NO);
    142 
    143     /*
    144      * From here on no early returns or we have a memory leak.
    145      */
    146 
    147     /*
    148      * EXTERNAL LOOP CONTROL
    149      *
    150      * Set the delivered message attribute to the recipient, so that this
    151      * message will list the correct forwarding address.
    152      */
    153     if (var_frozen_delivered == 0)
    154 	state.msg_attr.delivered = state.msg_attr.rcpt.address;
    155 
    156     /*
    157      * DELIVERY RIGHTS
    158      *
    159      * Do not inherit rights from the .forward file owner. Instead, use the
    160      * recipient's rights, and insist that the .forward file is owned by the
    161      * recipient. This is a small but significant difference. Use the
    162      * recipient's rights for all /file and |command deliveries, and pass on
    163      * these rights to command/file destinations in included files. When
    164      * these are the rights of root, the /file and |command delivery routines
    165      * will use unprivileged default rights instead. Better safe than sorry.
    166      */
    167     SET_USER_ATTR(usr_attr, mypwd, state.level);
    168 
    169     /*
    170      * DELIVERY POLICY
    171      *
    172      * Update the expansion type attribute so that we can decide if deliveries
    173      * to |command and /file/name are allowed at all.
    174      */
    175     state.msg_attr.exp_type = EXPAND_TYPE_FWD;
    176 
    177     /*
    178      * WHERE TO REPORT DELIVERY PROBLEMS
    179      *
    180      * Set the owner attribute so that 1) include files won't set the sender to
    181      * be this user and 2) mail forwarded to other local users will be
    182      * resubmitted as a new queue file.
    183      */
    184     state.msg_attr.owner = state.msg_attr.user;
    185 
    186     /*
    187      * Search the forward_path for an existing forward file.
    188      *
    189      * If unmatched extensions should never be propagated, or if a forward file
    190      * name includes the address extension, don't propagate the extension to
    191      * the recipient addresses.
    192      */
    193     status = 0;
    194     path = vstring_alloc(100);
    195     saved_forward_path = mystrdup(var_forward_path);
    196     next = saved_forward_path;
    197     lookup_status = -1;
    198 
    199     while ((lhs = mystrtok(&next, CHARS_COMMA_SP)) != 0) {
    200 	expand_status = local_expand(path, lhs, &state, &usr_attr,
    201 				     var_fwd_exp_filter);
    202 	if ((expand_status & (MAC_PARSE_ERROR | MAC_PARSE_UNDEF)) == 0) {
    203 	    lookup_status =
    204 		lstat_as(STR(path), &st, usr_attr.uid, usr_attr.gid);
    205 	    if (msg_verbose)
    206 		msg_info("%s: path %s expand_status %d look_status %d", myname,
    207 			 STR(path), expand_status, lookup_status);
    208 	    if (lookup_status >= 0) {
    209 		if ((expand_status & LOCAL_EXP_EXTENSION_MATCHED) != 0
    210 		    || (local_ext_prop_mask & EXT_PROP_FORWARD) == 0)
    211 		    state.msg_attr.unmatched = 0;
    212 		break;
    213 	    }
    214 	}
    215     }
    216 
    217     /*
    218      * Process the forward file.
    219      *
    220      * Assume that usernames do not have file system meta characters. Open the
    221      * .forward file as the user. Ignore files that aren't regular files,
    222      * files that are owned by the wrong user, or files that have world write
    223      * permission enabled.
    224      *
    225      * DUPLICATE/LOOP ELIMINATION
    226      *
    227      * If this user includes (an alias of) herself in her own .forward file,
    228      * deliver to the user instead.
    229      */
    230     if (lookup_status >= 0) {
    231 
    232 	/*
    233 	 * Don't expand a verify-only request.
    234 	 */
    235 	if (state.request->flags & DEL_REQ_FLAG_MTA_VRFY) {
    236 	    dsb_simple(state.msg_attr.why, "2.0.0",
    237 		       "forward via file: %s", STR(path));
    238 	    *statusp = sent(BOUNCE_FLAGS(state.request),
    239 			    SENT_ATTR(state.msg_attr));
    240 	    forward_found = YES;
    241 	} else if (been_here(state.dup_filter, "forward %s", STR(path)) == 0) {
    242 	    state.msg_attr.exp_from = state.msg_attr.local;
    243 	    if (S_ISREG(st.st_mode) == 0) {
    244 		msg_warn("file %s is not a regular file", STR(path));
    245 	    } else if (st.st_uid != 0 && st.st_uid != usr_attr.uid) {
    246 		msg_warn("file %s has bad owner uid %ld",
    247 			 STR(path), (long) st.st_uid);
    248 	    } else if (st.st_mode & 002) {
    249 		msg_warn("file %s is world writable", STR(path));
    250 	    } else if ((fd = open_as(STR(path), O_RDONLY, 0, usr_attr.uid, usr_attr.gid)) < 0) {
    251 		msg_warn("cannot open file %s: %m", STR(path));
    252 	    } else {
    253 
    254 		/*
    255 		 * XXX DSN. When delivering to an alias (i.e. the envelope
    256 		 * sender address is not replaced) any ENVID, RET, or ORCPT
    257 		 * parameters are propagated to all forwarding addresses
    258 		 * associated with that alias.  The NOTIFY parameter is
    259 		 * propagated to the forwarding addresses, except that any
    260 		 * SUCCESS keyword is removed.
    261 		 */
    262 		close_on_exec(fd, CLOSE_ON_EXEC);
    263 		addr_count = 0;
    264 		fp = vstream_fdopen(fd, O_RDONLY);
    265 		saved_notify = state.msg_attr.rcpt.dsn_notify;
    266 		state.msg_attr.rcpt.dsn_notify =
    267 		    (saved_notify == DSN_NOTIFY_SUCCESS ?
    268 		     DSN_NOTIFY_NEVER : saved_notify & ~DSN_NOTIFY_SUCCESS);
    269 		status = deliver_token_stream(state, usr_attr, fp, &addr_count);
    270 		if (vstream_fclose(fp))
    271 		    msg_warn("close file %s: %m", STR(path));
    272 		if (addr_count > 0) {
    273 		    forward_found = YES;
    274 		    been_here(state.dup_filter, "forward-done %s", STR(path));
    275 
    276 		    /*
    277 		     * XXX DSN. When delivering to an alias (i.e. the
    278 		     * envelope sender address is not replaced) and the
    279 		     * original NOTIFY parameter for the alias contained the
    280 		     * SUCCESS keyword, an "expanded" DSN is issued for the
    281 		     * alias.
    282 		     */
    283 		    if (status == 0 && (saved_notify & DSN_NOTIFY_SUCCESS)) {
    284 			state.msg_attr.rcpt.dsn_notify = saved_notify;
    285 			dsb_update(state.msg_attr.why, "2.0.0", "expanded",
    286 				   DSB_SKIP_RMTA, DSB_SKIP_REPLY,
    287 				   "alias expanded");
    288 			(void) trace_append(BOUNCE_FLAG_NONE,
    289 					    SENT_ATTR(state.msg_attr));
    290 		    }
    291 		}
    292 	    }
    293 	} else if (been_here_check(state.dup_filter, "forward-done %s", STR(path)) != 0)
    294 	    forward_found = YES;		/* else we're recursive */
    295     }
    296 
    297     /*
    298      * Clean up.
    299      */
    300     vstring_free(path);
    301     myfree(saved_forward_path);
    302     mypwfree(mypwd);
    303 
    304     *statusp = status;
    305     return (forward_found);
    306 }
    307