1 /*	$NetBSD: dotforward.c,v 1.1.1.1 2009/06/23 10:08:48 tron 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 <been_here.h>
80 #include <mail_params.h>
81 #include <mail_conf.h>
82 #include <ext_prop.h>
83 #include <sent.h>
84 #include <dsn_mask.h>
85 #include <trace.h>
86 
87 /* Application-specific. */
88 
89 #include "local.h"
90 
91 #define NO	0
92 #define YES	1
93 
94 /* deliver_dotforward - expand contents of .forward file */
95 
96 int     deliver_dotforward(LOCAL_STATE state, USER_ATTR usr_attr, int *statusp)
97 {
98     const char *myname = "deliver_dotforward";
99     struct stat st;
100     VSTRING *path;
101     struct mypasswd *mypwd;
102     int     fd;
103     VSTREAM *fp;
104     int     status;
105     int     forward_found = NO;
106     int     lookup_status;
107     int     addr_count;
108     char   *saved_forward_path;
109     char   *lhs;
110     char   *next;
111     int     expand_status;
112     int     saved_notify;
113 
114     /*
115      * Make verbose logging easier to understand.
116      */
117     state.level++;
118     if (msg_verbose)
119 	MSG_LOG_STATE(myname, state);
120 
121     /*
122      * Skip this module if per-user forwarding is disabled.
123      */
124     if (*var_forward_path == 0)
125 	return (NO);
126 
127     /*
128      * Skip non-existing users. The mailbox delivery routine will catch the
129      * error.
130      */
131     if ((mypwd = mypwnam(state.msg_attr.user)) == 0)
132 	return (NO);
133 
134     /*
135      * From here on no early returns or we have a memory leak.
136      */
137 
138     /*
139      * EXTERNAL LOOP CONTROL
140      *
141      * Set the delivered message attribute to the recipient, so that this
142      * message will list the correct forwarding address.
143      */
144     if (var_frozen_delivered == 0)
145 	state.msg_attr.delivered = state.msg_attr.rcpt.address;
146 
147     /*
148      * DELIVERY RIGHTS
149      *
150      * Do not inherit rights from the .forward file owner. Instead, use the
151      * recipient's rights, and insist that the .forward file is owned by the
152      * recipient. This is a small but significant difference. Use the
153      * recipient's rights for all /file and |command deliveries, and pass on
154      * these rights to command/file destinations in included files. When
155      * these are the rights of root, the /file and |command delivery routines
156      * will use unprivileged default rights instead. Better safe than sorry.
157      */
158     SET_USER_ATTR(usr_attr, mypwd, state.level);
159 
160     /*
161      * DELIVERY POLICY
162      *
163      * Update the expansion type attribute so that we can decide if deliveries
164      * to |command and /file/name are allowed at all.
165      */
166     state.msg_attr.exp_type = EXPAND_TYPE_FWD;
167 
168     /*
169      * WHERE TO REPORT DELIVERY PROBLEMS
170      *
171      * Set the owner attribute so that 1) include files won't set the sender to
172      * be this user and 2) mail forwarded to other local users will be
173      * resubmitted as a new queue file.
174      */
175     state.msg_attr.owner = state.msg_attr.user;
176 
177     /*
178      * Search the forward_path for an existing forward file.
179      *
180      * If unmatched extensions should never be propagated, or if a forward file
181      * name includes the address extension, don't propagate the extension to
182      * the recipient addresses.
183      */
184     status = 0;
185     path = vstring_alloc(100);
186     saved_forward_path = mystrdup(var_forward_path);
187     next = saved_forward_path;
188     lookup_status = -1;
189 
190     while ((lhs = mystrtok(&next, ", \t\r\n")) != 0) {
191 	expand_status = local_expand(path, lhs, &state, &usr_attr,
192 				     var_fwd_exp_filter);
193 	if ((expand_status & (MAC_PARSE_ERROR | MAC_PARSE_UNDEF)) == 0) {
194 	    lookup_status =
195 		lstat_as(STR(path), &st, usr_attr.uid, usr_attr.gid);
196 	    if (msg_verbose)
197 		msg_info("%s: path %s expand_status %d look_status %d", myname,
198 			 STR(path), expand_status, lookup_status);
199 	    if (lookup_status >= 0) {
200 		if ((expand_status & LOCAL_EXP_EXTENSION_MATCHED) != 0
201 		    || (local_ext_prop_mask & EXT_PROP_FORWARD) == 0)
202 		    state.msg_attr.unmatched = 0;
203 		break;
204 	    }
205 	}
206     }
207 
208     /*
209      * Process the forward file.
210      *
211      * Assume that usernames do not have file system meta characters. Open the
212      * .forward file as the user. Ignore files that aren't regular files,
213      * files that are owned by the wrong user, or files that have world write
214      * permission enabled.
215      *
216      * DUPLICATE/LOOP ELIMINATION
217      *
218      * If this user includes (an alias of) herself in her own .forward file,
219      * deliver to the user instead.
220      */
221     if (lookup_status >= 0) {
222 
223 	/*
224 	 * Don't expand a verify-only request.
225 	 */
226 	if (state.request->flags & DEL_REQ_FLAG_MTA_VRFY) {
227 	    dsb_simple(state.msg_attr.why, "2.0.0",
228 		       "forward via file: %s", STR(path));
229 	    *statusp = sent(BOUNCE_FLAGS(state.request),
230 			    SENT_ATTR(state.msg_attr));
231 	    forward_found = YES;
232 	} else if (been_here(state.dup_filter, "forward %s", STR(path)) == 0) {
233 	    state.msg_attr.exp_from = state.msg_attr.local;
234 	    if (S_ISREG(st.st_mode) == 0) {
235 		msg_warn("file %s is not a regular file", STR(path));
236 	    } else if (st.st_uid != 0 && st.st_uid != usr_attr.uid) {
237 		msg_warn("file %s has bad owner uid %ld",
238 			 STR(path), (long) st.st_uid);
239 	    } else if (st.st_mode & 002) {
240 		msg_warn("file %s is world writable", STR(path));
241 	    } else if ((fd = open_as(STR(path), O_RDONLY, 0, usr_attr.uid, usr_attr.gid)) < 0) {
242 		msg_warn("cannot open file %s: %m", STR(path));
243 	    } else {
244 
245 		/*
246 		 * XXX DSN. When delivering to an alias (i.e. the envelope
247 		 * sender address is not replaced) any ENVID, RET, or ORCPT
248 		 * parameters are propagated to all forwarding addresses
249 		 * associated with that alias.  The NOTIFY parameter is
250 		 * propagated to the forwarding addresses, except that any
251 		 * SUCCESS keyword is removed.
252 		 */
253 		close_on_exec(fd, CLOSE_ON_EXEC);
254 		addr_count = 0;
255 		fp = vstream_fdopen(fd, O_RDONLY);
256 		saved_notify = state.msg_attr.rcpt.dsn_notify;
257 		state.msg_attr.rcpt.dsn_notify =
258 		    (saved_notify == DSN_NOTIFY_SUCCESS ?
259 		     DSN_NOTIFY_NEVER : saved_notify & ~DSN_NOTIFY_SUCCESS);
260 		status = deliver_token_stream(state, usr_attr, fp, &addr_count);
261 		if (vstream_fclose(fp))
262 		    msg_warn("close file %s: %m", STR(path));
263 		if (addr_count > 0) {
264 		    forward_found = YES;
265 		    been_here(state.dup_filter, "forward-done %s", STR(path));
266 
267 		    /*
268 		     * XXX DSN. When delivering to an alias (i.e. the
269 		     * envelope sender address is not replaced) and the
270 		     * original NOTIFY parameter for the alias contained the
271 		     * SUCCESS keyword, an "expanded" DSN is issued for the
272 		     * alias.
273 		     */
274 		    if (status == 0 && (saved_notify & DSN_NOTIFY_SUCCESS)) {
275 			state.msg_attr.rcpt.dsn_notify = saved_notify;
276 			dsb_update(state.msg_attr.why, "2.0.0", "expanded",
277 				   DSB_SKIP_RMTA, DSB_SKIP_REPLY,
278 				   "alias expanded");
279 			(void) trace_append(BOUNCE_FLAG_NONE,
280 					    SENT_ATTR(state.msg_attr));
281 		    }
282 		}
283 	    }
284 	} else if (been_here_check(state.dup_filter, "forward-done %s", STR(path)) != 0)
285 	    forward_found = YES;		/* else we're recursive */
286     }
287 
288     /*
289      * Clean up.
290      */
291     vstring_free(path);
292     myfree(saved_forward_path);
293     mypwfree(mypwd);
294 
295     *statusp = status;
296     return (forward_found);
297 }
298