int deliver_include(LOCAL_STATE state, USER_ATTR usr_attr, char *path) { const char *myname = "deliver_include"; struct stat st; struct mypasswd *file_pwd = 0; int status; VSTREAM *fp; int fd; /* * Make verbose logging easier to understand. */ state.level++; if (msg_verbose) MSG_LOG_STATE(myname, state); /* * DUPLICATE ELIMINATION * * Don't process this include file more than once as this particular user. */ if (been_here(state.dup_filter, "include %ld %s", (long) usr_attr.uid, path)) return (0); state.msg_attr.exp_from = state.msg_attr.local; /* * Can of worms. Allow this include file to be symlinked, but disallow * inclusion of special files or of files with world write permission * enabled. */ if (*path != '/') { msg_warn(":include:%s uses a relative path", path); dsb_simple(state.msg_attr.why, "5.3.5", "mail system configuration error"); return (bounce_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr))); } if (stat_as(path, &st, usr_attr.uid, usr_attr.gid) < 0) { msg_warn("unable to lookup :include: file %s: %m", path); dsb_simple(state.msg_attr.why, "5.3.5", "mail system configuration error"); return (bounce_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr))); } if (S_ISREG(st.st_mode) == 0) { msg_warn(":include: file %s is not a regular file", path); dsb_simple(state.msg_attr.why, "5.3.5", "mail system configuration error"); return (bounce_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr))); } if (st.st_mode & S_IWOTH) { msg_warn(":include: file %s is world writable", path); dsb_simple(state.msg_attr.why, "5.3.5", "mail system configuration error"); return (bounce_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr))); } /* * DELIVERY POLICY * * Set the expansion type attribute so that we can decide if destinations * such as /file/name and |command are allowed at all. */ state.msg_attr.exp_type = EXPAND_TYPE_INCL; /* * DELIVERY RIGHTS * * When a non-root include file is listed in a root-owned alias, use the * rights of the include file owner. We do not want to give the include * file owner control of the default account. * * When an include file is listed in a user-owned alias or .forward file, * leave the delivery rights alone. Users should not be able to make * things happen with someone else's rights just by including some file * that is owned by their victim. */ if (usr_attr.uid == 0) { if ((errno = mypwuid_err(st.st_uid, &file_pwd)) != 0 || file_pwd == 0) { msg_warn(errno ? "cannot find username for uid %ld: %m" : "cannot find username for uid %ld", (long) st.st_uid); msg_warn("%s: cannot find :include: file owner username", path); dsb_simple(state.msg_attr.why, "4.3.5", "mail system configuration error"); return (defer_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr))); } if (file_pwd->pw_uid != 0) SET_USER_ATTR(usr_attr, file_pwd, state.level); } /* * MESSAGE FORWARDING * * When no owner attribute is set (either via an owner- alias, or as part of * .forward file processing), set the owner attribute, to disable direct * delivery of local recipients. By now it is clear that the owner * attribute should have been called forwarder instead. */ if (state.msg_attr.owner == 0) state.msg_attr.owner = state.msg_attr.rcpt.address; /* * From here on no early returns or we have a memory leak. * * FILE OPEN RIGHTS * * Use the delivery rights to open the include file. When no delivery rights * were established sofar, the file containing the :include: is owned by * root, so it should be OK to open any file that is accessible to root. * The command and file delivery routines are responsible for setting the * proper delivery rights. These are the rights of the default user, in * case the :include: is in a root-owned alias. * * Don't propagate unmatched extensions unless permitted to do so. */ #define FOPEN_AS(p,u,g) ((fd = open_as(p,O_RDONLY,0,u,g)) >= 0 ? \ vstream_fdopen(fd,O_RDONLY) : 0) if ((fp = FOPEN_AS(path, usr_attr.uid, usr_attr.gid)) == 0) { msg_warn("cannot open include file %s: %m", path); dsb_simple(state.msg_attr.why, "5.3.5", "mail system configuration error"); status = bounce_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr)); } else { if ((local_ext_prop_mask & EXT_PROP_INCLUDE) == 0) state.msg_attr.unmatched = 0; close_on_exec(vstream_fileno(fp), CLOSE_ON_EXEC); status = deliver_token_stream(state, usr_attr, fp, (int *) 0); if (vstream_fclose(fp)) msg_warn("close %s: %m", path); } /* * Cleanup. */ if (file_pwd) mypwfree(file_pwd); return (status); }
static int deliver_switch(LOCAL_STATE state, USER_ATTR usr_attr) { const char *myname = "deliver_switch"; int status = 0; struct stat st; struct mypasswd *mypwd; /* * Make verbose logging easier to understand. */ state.level++; if (msg_verbose) MSG_LOG_STATE(myname, state); /* * \user is special: it means don't do any alias or forward expansion. * * XXX This code currently does not work due to revision of the RFC822 * address parser. \user should be permitted only in locally specified * aliases, includes or forward files. * * XXX Should test for presence of user home directory. */ if (state.msg_attr.rcpt.address[0] == '\\') { state.msg_attr.rcpt.address++, state.msg_attr.local++, state.msg_attr.user++; if (deliver_mailbox(state, usr_attr, &status) == 0) status = deliver_unknown(state, usr_attr); return (status); } /* * Otherwise, alias expansion has highest precedence. First look up the * full localpart, then the bare user. Obey the address extension * propagation policy. */ state.msg_attr.unmatched = 0; if (deliver_alias(state, usr_attr, state.msg_attr.local, &status)) return (status); if (state.msg_attr.extension != 0) { if (local_ext_prop_mask & EXT_PROP_ALIAS) state.msg_attr.unmatched = state.msg_attr.extension; if (deliver_alias(state, usr_attr, state.msg_attr.user, &status)) return (status); state.msg_attr.unmatched = state.msg_attr.extension; } /* * Special case for mail locally forwarded or aliased to a different * local address. Resubmit the message via the cleanup service, so that * each recipient gets a separate delivery queue file status record in * the new queue file. The downside of this approach is that mutually * recursive .forward files cause a mail forwarding loop. Fortunately, * the loop can be broken by the use of the Delivered-To: message header. * * The code below must not trigger on mail sent to an alias that has no * owner- companion, so that mail for an alias first.last->username is * delivered directly, instead of going through username->first.last * canonical mappings in the cleanup service. The downside of this * approach is that recipients in the expansion of an alias without * owner- won't have separate delivery queue file status records, because * for them, the message won't be resubmitted as a new queue file. * * Do something sensible on systems that receive mail for multiple domains, * such as primary.name and secondary.name. Don't resubmit the message * when mail for `[email protected]' is delivered to a .forward file * that lists `user' or `[email protected]'. We already know that the * recipient domain is local, so we only have to compare local parts. */ if (state.msg_attr.owner != 0 && strcasecmp(state.msg_attr.owner, state.msg_attr.user) != 0) return (deliver_indirect(state)); /* * Always forward recipients in :include: files. */ if (state.msg_attr.exp_type == EXPAND_TYPE_INCL) return (deliver_indirect(state)); /* * Delivery to local user. First try expansion of the recipient's * $HOME/.forward file, then mailbox delivery. Back off when the user's * home directory does not exist. */ if (var_stat_home_dir && (mypwd = mypwnam(state.msg_attr.user)) != 0 && stat_as(mypwd->pw_dir, &st, mypwd->pw_uid, mypwd->pw_gid) < 0) { dsb_simple(state.msg_attr.why, "4.3.0", "cannot access home directory %s: %m", mypwd->pw_dir); return (defer_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr))); } if (deliver_dotforward(state, usr_attr, &status) == 0 && deliver_mailbox(state, usr_attr, &status) == 0) status = deliver_unknown(state, usr_attr); return (status); }