Example #1
0
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);
}
Example #2
0
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);
}