xref: /netbsd-src/external/ibm-public/postfix/dist/src/local/dotforward.c (revision e89934bbf778a6d6d6894877c4da59d0c7835b0f)
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 
deliver_dotforward(LOCAL_STATE state,USER_ATTR usr_attr,int * statusp)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