7 /* $HOME/.forward file expansion
11 /* int deliver_dotforward(state, usr_attr, statusp)
13 /* USER_ATTR usr_attr;
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.
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.
30 /* Attributes describing user rights and environment.
32 /* Message delivery status. See below.
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.
38 /* include(3) include file processor.
42 /* The Secure Mailer license must be distributed with this software.
45 /* IBM T.J. Watson Research
47 /* Yorktown Heights, NY 10598, USA
62 /* Utility library. */
71 #include <stringops.h>
73 #include <mac_expand.h>
79 #include <been_here.h>
80 #include <mail_params.h>
81 #include <mail_conf.h>
87 /* Application-specific. */
94 /* deliver_dotforward - expand contents of .forward file */
96 int deliver_dotforward(LOCAL_STATE state
, USER_ATTR usr_attr
, int *statusp
)
98 const char *myname
= "deliver_dotforward";
101 struct mypasswd
*mypwd
;
105 int forward_found
= NO
;
108 char *saved_forward_path
;
115 * Make verbose logging easier to understand.
119 MSG_LOG_STATE(myname
, state
);
122 * Skip this module if per-user forwarding is disabled.
124 if (*var_forward_path
== 0)
128 * Skip non-existing users. The mailbox delivery routine will catch the
131 if ((mypwd
= mypwnam(state
.msg_attr
.user
)) == 0)
135 * From here on no early returns or we have a memory leak.
139 * EXTERNAL LOOP CONTROL
141 * Set the delivered message attribute to the recipient, so that this
142 * message will list the correct forwarding address.
144 if (var_frozen_delivered
== 0)
145 state
.msg_attr
.delivered
= state
.msg_attr
.rcpt
.address
;
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.
158 SET_USER_ATTR(usr_attr
, mypwd
, state
.level
);
163 * Update the expansion type attribute so that we can decide if deliveries
164 * to |command and /file/name are allowed at all.
166 state
.msg_attr
.exp_type
= EXPAND_TYPE_FWD
;
169 * WHERE TO REPORT DELIVERY PROBLEMS
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.
175 state
.msg_attr
.owner
= state
.msg_attr
.user
;
178 * Search the forward_path for an existing forward file.
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.
185 path
= vstring_alloc(100);
186 saved_forward_path
= mystrdup(var_forward_path
);
187 next
= saved_forward_path
;
190 while ((lhs
= mystrtok(&next
, ", \t\r\n")) != 0) {
191 expand_status
= local_expand(path
, lhs
, &state
, &usr_attr
,
193 if ((expand_status
& (MAC_PARSE_ERROR
| MAC_PARSE_UNDEF
)) == 0) {
195 lstat_as(STR(path
), &st
, usr_attr
.uid
, usr_attr
.gid
);
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;
209 * Process the forward file.
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.
216 * DUPLICATE/LOOP ELIMINATION
218 * If this user includes (an alias of) herself in her own .forward file,
219 * deliver to the user instead.
221 if (lookup_status
>= 0) {
224 * Don't expand a verify-only request.
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
));
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
));
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.
253 close_on_exec(fd
, CLOSE_ON_EXEC
);
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) {
265 been_here(state
.dup_filter
, "forward-done %s", STR(path
));
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
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
,
279 (void) trace_append(BOUNCE_FLAG_NONE
,
280 SENT_ATTR(state
.msg_attr
));
284 } else if (been_here_check(state
.dup_filter
, "forward-done %s", STR(path
)) != 0)
285 forward_found
= YES
; /* else we're recursive */
292 myfree(saved_forward_path
);
296 return (forward_found
);