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