/*++ /* NAME /* dotforward 3 /* SUMMARY /* $HOME/.forward file expansion /* SYNOPSIS /* #include "local.h" /* /* int deliver_dotforward(state, usr_attr, statusp) /* LOCAL_STATE state; /* USER_ATTR usr_attr; /* int *statusp; /* DESCRIPTION /* deliver_dotforward() delivers a message to the destinations /* listed in a recipient's .forward file(s) as specified through /* the forward_path configuration parameter. The result is /* zero when no acceptable .forward file was found, or when /* a recipient is listed in her own .forward file. Expansions /* are scrutinized with the forward_expansion_filter parameter. /* /* Arguments: /* .IP state /* Message delivery attributes (sender, recipient etc.). /* Attributes describing alias, include or forward expansion. /* A table with the results from expanding aliases or lists. /* A table with delivered-to: addresses taken from the message. /* .IP usr_attr /* Attributes describing user rights and environment. /* .IP statusp /* Message delivery status. See below. /* DIAGNOSTICS /* Fatal errors: out of memory. Warnings: bad $HOME/.forward /* file type, permissions or ownership. The message delivery /* status is non-zero when delivery should be tried again. /* SEE ALSO /* include(3) include file processor. /* LICENSE /* .ad /* .fi /* The Secure Mailer license must be distributed with this software. /* AUTHOR(S) /* Wietse Venema /* IBM T.J. Watson Research /* P.O. Box 704 /* Yorktown Heights, NY 10598, USA /*--*/ /* System library. */ #include #include #include #include #include #ifdef USE_PATHS_H #include #endif #include /* Utility library. */ #include #include #include #include #include #include #include #include #include #include /* Global library. */ #include #include #include #include #include #include #include #include #include #include /* Application-specific. */ #include "local.h" #define NO 0 #define YES 1 /* deliver_dotforward - expand contents of .forward file */ int deliver_dotforward(LOCAL_STATE state, USER_ATTR usr_attr, int *statusp) { const char *myname = "deliver_dotforward"; struct stat st; VSTRING *path; struct mypasswd *mypwd; int fd; VSTREAM *fp; int status; int forward_found = NO; int lookup_status; int addr_count; char *saved_forward_path; char *lhs; char *next; int expand_status; int saved_notify; /* * Make verbose logging easier to understand. */ state.level++; if (msg_verbose) MSG_LOG_STATE(myname, state); /* * Skip this module if per-user forwarding is disabled. */ if (*var_forward_path == 0) return (NO); /* * Skip non-existing users. The mailbox delivery routine will catch the * error. */ if ((errno = mypwnam_err(state.msg_attr.user, &mypwd)) != 0) { msg_warn("error looking up passwd info for %s: %m", state.msg_attr.user); dsb_simple(state.msg_attr.why, "4.0.0", "user lookup error"); *statusp = defer_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr)); return (YES); } if (mypwd == 0) return (NO); /* * From here on no early returns or we have a memory leak. */ /* * EXTERNAL LOOP CONTROL * * Set the delivered message attribute to the recipient, so that this * message will list the correct forwarding address. */ if (var_frozen_delivered == 0) state.msg_attr.delivered = state.msg_attr.rcpt.address; /* * DELIVERY RIGHTS * * Do not inherit rights from the .forward file owner. Instead, use the * recipient's rights, and insist that the .forward file is owned by the * recipient. This is a small but significant difference. Use the * recipient's rights for all /file and |command deliveries, and pass on * these rights to command/file destinations in included files. When * these are the rights of root, the /file and |command delivery routines * will use unprivileged default rights instead. Better safe than sorry. */ SET_USER_ATTR(usr_attr, mypwd, state.level); /* * DELIVERY POLICY * * Update the expansion type attribute so that we can decide if deliveries * to |command and /file/name are allowed at all. */ state.msg_attr.exp_type = EXPAND_TYPE_FWD; /* * WHERE TO REPORT DELIVERY PROBLEMS * * Set the owner attribute so that 1) include files won't set the sender to * be this user and 2) mail forwarded to other local users will be * resubmitted as a new queue file. */ state.msg_attr.owner = state.msg_attr.user; /* * Search the forward_path for an existing forward file. * * If unmatched extensions should never be propagated, or if a forward file * name includes the address extension, don't propagate the extension to * the recipient addresses. */ status = 0; path = vstring_alloc(100); saved_forward_path = mystrdup(var_forward_path); next = saved_forward_path; lookup_status = -1; while ((lhs = mystrtok(&next, CHARS_COMMA_SP)) != 0) { expand_status = local_expand(path, lhs, &state, &usr_attr, var_fwd_exp_filter); if ((expand_status & (MAC_PARSE_ERROR | MAC_PARSE_UNDEF)) == 0) { lookup_status = lstat_as(STR(path), &st, usr_attr.uid, usr_attr.gid); if (msg_verbose) msg_info("%s: path %s expand_status %d look_status %d", myname, STR(path), expand_status, lookup_status); if (lookup_status >= 0) { if ((expand_status & LOCAL_EXP_EXTENSION_MATCHED) != 0 || (local_ext_prop_mask & EXT_PROP_FORWARD) == 0) state.msg_attr.unmatched = 0; break; } } } /* * Process the forward file. * * Assume that usernames do not have file system meta characters. Open the * .forward file as the user. Ignore files that aren't regular files, * files that are owned by the wrong user, or files that have world write * permission enabled. * * DUPLICATE/LOOP ELIMINATION * * If this user includes (an alias of) herself in her own .forward file, * deliver to the user instead. */ if (lookup_status >= 0) { /* * Don't expand a verify-only request. */ if (state.request->flags & DEL_REQ_FLAG_MTA_VRFY) { dsb_simple(state.msg_attr.why, "2.0.0", "forward via file: %s", STR(path)); *statusp = sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr)); forward_found = YES; } else if (been_here(state.dup_filter, "forward %s", STR(path)) == 0) { state.msg_attr.exp_from = state.msg_attr.local; if (S_ISREG(st.st_mode) == 0) { msg_warn("file %s is not a regular file", STR(path)); } else if (st.st_uid != 0 && st.st_uid != usr_attr.uid) { msg_warn("file %s has bad owner uid %ld", STR(path), (long) st.st_uid); } else if (st.st_mode & 002) { msg_warn("file %s is world writable", STR(path)); } else if ((fd = open_as(STR(path), O_RDONLY, 0, usr_attr.uid, usr_attr.gid)) < 0) { msg_warn("cannot open file %s: %m", STR(path)); } else { /* * XXX DSN. When delivering to an alias (i.e. the envelope * sender address is not replaced) any ENVID, RET, or ORCPT * parameters are propagated to all forwarding addresses * associated with that alias. The NOTIFY parameter is * propagated to the forwarding addresses, except that any * SUCCESS keyword is removed. */ close_on_exec(fd, CLOSE_ON_EXEC); addr_count = 0; fp = vstream_fdopen(fd, O_RDONLY); saved_notify = state.msg_attr.rcpt.dsn_notify; state.msg_attr.rcpt.dsn_notify = (saved_notify == DSN_NOTIFY_SUCCESS ? DSN_NOTIFY_NEVER : saved_notify & ~DSN_NOTIFY_SUCCESS); status = deliver_token_stream(state, usr_attr, fp, &addr_count); if (vstream_fclose(fp)) msg_warn("close file %s: %m", STR(path)); if (addr_count > 0) { forward_found = YES; been_here(state.dup_filter, "forward-done %s", STR(path)); /* * XXX DSN. When delivering to an alias (i.e. the * envelope sender address is not replaced) and the * original NOTIFY parameter for the alias contained the * SUCCESS keyword, an "expanded" DSN is issued for the * alias. */ if (status == 0 && (saved_notify & DSN_NOTIFY_SUCCESS)) { state.msg_attr.rcpt.dsn_notify = saved_notify; dsb_update(state.msg_attr.why, "2.0.0", "expanded", DSB_SKIP_RMTA, DSB_SKIP_REPLY, "alias expanded"); (void) trace_append(BOUNCE_FLAG_NONE, SENT_ATTR(state.msg_attr)); } } } } else if (been_here_check(state.dup_filter, "forward-done %s", STR(path)) != 0) forward_found = YES; /* else we're recursive */ } /* * Clean up. */ vstring_free(path); myfree(saved_forward_path); mypwfree(mypwd); *statusp = status; return (forward_found); }