Ejemplo n.º 1
0
const char *check_user_acl_byuid(char *acl, uid_t uid)
{
    struct mypasswd *mypwd;
    STRING_LIST *list;
    static VSTRING *who = 0;
    int     matched;
    const char *name;

    /*
     * Optimize for the most common case. This also makes Postfix a little
     * more robust in the face of local infrastructure failures. Note that we
     * only need to match the "static:" substring, not the result value.
     */
    if (strncmp(acl, DICT_TYPE_STATIC ":", sizeof(DICT_TYPE_STATIC)) == 0)
	return (0);

    /*
     * XXX: Substitute "unknown" for UIDs without username, so that
     * static:anyone results in "permit" even when the uid is not found in
     * the password file, and so that a pattern of !unknown can be used to
     * block non-existent accounts.
     * 
     * The alternative is to use the UID as a surrogate lookup key for
     * non-existent accounts. There are several reasons why this is not a
     * good idea. 1) An ACL with a numerical UID should work regardless of
     * whether or not an account has a password file entry. Therefore we
     * would always have search on the numerical UID whenever the username
     * fails to produce a match. 2) The string-list infrastructure is not
     * really suitable for mixing numerical and non-numerical user
     * information, because the numerical match is done in a separate pass
     * from the non-numerical match. This breaks when the ! operator is used.
     * 
     * XXX To avoid waiting until the lookup completes (e.g., LDAP or NIS down)
     * invoke mypwuid_err(), and either change the user_acl() API to
     * propagate the error to the caller, or treat lookup errors as fatal.
     */
    if ((mypwd = mypwuid(uid)) == 0) {
	name = "unknown";
    } else {
	name = mypwd->pw_name;
    }

    list = string_list_init(MATCH_FLAG_NONE, acl);
    if ((matched = string_list_match(list, name)) == 0) {
	if (!who)
	    who = vstring_alloc(10);
	vstring_strcpy(who, name);
    }
    string_list_free(list);
    if (mypwd)
	mypwfree(mypwd);

    return (matched ? 0 : vstring_str(who));
}
Ejemplo n.º 2
0
int     mypwuid_err(uid_t uid, struct mypasswd ** result)
{
    struct passwd *pwd;
    struct mypasswd *mypwd;

    /*
     * See if this is the same user as last time.
     */
    if (last_pwd != 0) {
	if (last_pwd->pw_uid != uid) {
	    mypwfree(last_pwd);
	    last_pwd = 0;
	} else {
	    *result = mypwd = last_pwd;
	    mypwd->refcount++;
	    return (0);
	}
    }

    /*
     * Find the info in the cache or in the password database.
     */
    if ((mypwd = (struct mypasswd *)
	 binhash_find(mypwcache_uid, (char *) &uid, sizeof(uid))) == 0) {
#ifdef HAVE_POSIX_GETPW_R
	char    pwstore[GETPW_R_BUFSIZ];
	struct passwd pwbuf;
	int     err;

	err = getpwuid_r(uid, &pwbuf, pwstore, sizeof(pwstore), &pwd);
	if (err != 0)
	    return (err);
	if (pwd == 0) {
	    *result = 0;
	    return (0);
	}
#else
	if ((pwd = getpwuid(uid)) == 0) {
	    *result = 0;
	    return (0);
	}
#endif
	mypwd = mypwenter(pwd);
    }
    *result = last_pwd = mypwd;
    mypwd->refcount += 2;
    return (0);
}
Ejemplo n.º 3
0
int     mypwnam_err(const char *name, struct mypasswd ** result)
{
    struct passwd *pwd;
    struct mypasswd *mypwd;

    /*
     * See if this is the same user as last time.
     */
    if (last_pwd != 0) {
	if (strcmp(last_pwd->pw_name, name) != 0) {
	    mypwfree(last_pwd);
	    last_pwd = 0;
	} else {
	    *result = mypwd = last_pwd;
	    mypwd->refcount++;
	    return (0);
	}
    }

    /*
     * Find the info in the cache or in the password database.
     */
    if ((mypwd = (struct mypasswd *) htable_find(mypwcache_name, name)) == 0) {
#ifdef HAVE_POSIX_GETPW_R
	char    pwstore[GETPW_R_BUFSIZ];
	struct passwd pwbuf;
	int     err;

	err = getpwnam_r(name, &pwbuf, pwstore, sizeof(pwstore), &pwd);
	if (err != 0)
	    return (err);
	if (pwd == 0) {
	    *result = 0;
	    return (0);
	}
#else
	if ((pwd = getpwnam(name)) == 0) {
	    *result = 0;
	    return (0);
	}
#endif
	mypwd = mypwenter(pwd);
    }
    *result = last_pwd = mypwd;
    mypwd->refcount += 2;
    return (0);
}
Ejemplo n.º 4
0
int     main(int argc, char **argv)
{
    struct mypasswd **mypwd;
    int     i;

    msg_vstream_init(argv[0], VSTREAM_ERR);
    if (argc == 1)
	msg_fatal("usage: %s name or uid ...", argv[0]);

    mypwd = (struct mypasswd **) mymalloc((argc + 2) * sizeof(*mypwd));

    /*
     * Do a sequence of lookups.
     */
    for (i = 1; i < argc; i++) {
	if (ISDIGIT(argv[i][0]))
	    mypwd[i] = mypwuid(atoi(argv[i]));
	else
	    mypwd[i] = mypwnam(argv[i]);
	if (mypwd[i] == 0)
	    msg_fatal("%s: not found", argv[i]);
	msg_info("lookup %s %s/%d refcount=%d name_cache=%d uid_cache=%d",
		 argv[i], mypwd[i]->pw_name, mypwd[i]->pw_uid,
	     mypwd[i]->refcount, mypwcache_name->used, mypwcache_uid->used);
    }
    mypwd[argc] = last_pwd;

    /*
     * The following should free all entries.
     */
    for (i = 1; i < argc + 1; i++) {
	msg_info("free %s/%d refcount=%d name_cache=%d uid_cache=%d",
		 mypwd[i]->pw_name, mypwd[i]->pw_uid, mypwd[i]->refcount,
		 mypwcache_name->used, mypwcache_uid->used);
	mypwfree(mypwd[i]);
    }
    msg_info("name_cache=%d uid_cache=%d",
	     mypwcache_name->used, mypwcache_uid->used);

    myfree((char *) mypwd);
    return (0);
}
Ejemplo n.º 5
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);
}
Ejemplo n.º 6
0
int     deliver_alias(LOCAL_STATE state, USER_ATTR usr_attr,
		              char *name, int *statusp)
{
    char   *myname = "deliver_alias";
    const char *alias_result;
    char   *expansion;
    char   *owner;
    char  **cpp;
    uid_t   alias_uid;
    struct mypasswd *alias_pwd;
    VSTRING *canon_owner;
    DICT   *dict;
    const char *owner_rhs;		/* owner alias, RHS */
    int     alias_count;

    /*
     * Make verbose logging easier to understand.
     */
    state.level++;
    if (msg_verbose)
	MSG_LOG_STATE(myname, state);

    /*
     * DUPLICATE/LOOP ELIMINATION
     * 
     * We cannot do duplicate elimination here. Sendmail compatibility requires
     * that we allow multiple deliveries to the same alias, even recursively!
     * For example, we must deliver to mailbox any messags that are addressed
     * to the alias of a user that lists that same alias in her own .forward
     * file. Yuck! This is just an example of some really perverse semantics
     * that people will expect Postfix to implement just like sendmail.
     * 
     * We can recognize one special case: when an alias includes its own name,
     * deliver to the user instead, just like sendmail. Otherwise, we just
     * bail out when nesting reaches some unreasonable depth, and blame it on
     * a possible alias loop.
     */
    if (state.msg_attr.exp_from != 0
	&& strcasecmp(state.msg_attr.exp_from, name) == 0)
	return (NO);
    if (state.level > 100) {
	msg_warn("possible alias database loop for %s", name);
	*statusp = bounce_append(BOUNCE_FLAGS(state.request),
				 BOUNCE_ATTR(state.msg_attr),
			       "possible alias database loop for %s", name);
	return (YES);
    }
    state.msg_attr.exp_from = name;

    /*
     * There are a bunch of roles that we're trying to keep track of.
     * 
     * First, there's the issue of whose rights should be used when delivering
     * to "|command" or to /file/name. With alias databases, the rights are
     * those of who owns the alias, i.e. the database owner. With aliases
     * owned by root, a default user is used instead. When an alias with
     * default rights references an include file owned by an ordinary user,
     * we must use the rights of the include file owner, otherwise the
     * include file owner could take control of the default account.
     * 
     * Secondly, there's the question of who to notify of delivery problems.
     * With aliases that have an owner- alias, the latter is used to set the
     * sender and owner attributes. Otherwise, the owner attribute is reset
     * (the alias is globally visible and could be sent to by anyone).
     */
    for (cpp = alias_maps->argv->argv; *cpp; cpp++) {
	if ((dict = dict_handle(*cpp)) == 0)
	    msg_panic("%s: dictionary not found: %s", myname, *cpp);
	if ((alias_result = dict_get(dict, name)) != 0) {
	    if (msg_verbose)
		msg_info("%s: %s: %s = %s", myname, *cpp, name, alias_result);

	    /*
	     * Don't expand a verify-only request.
	     */
	    if (state.request->flags & DEL_REQ_FLAG_VERIFY) {
		*statusp = sent(BOUNCE_FLAGS(state.request),
				SENT_ATTR(state.msg_attr),
				"aliased to %s", alias_result);
		return (YES);
	    }

	    /*
	     * DELIVERY POLICY
	     * 
	     * Update the expansion type attribute, so we can decide if
	     * deliveries to |command and /file/name are allowed at all.
	     */
	    state.msg_attr.exp_type = EXPAND_TYPE_ALIAS;

	    /*
	     * DELIVERY RIGHTS
	     * 
	     * What rights to use for |command and /file/name deliveries? The
	     * command and file code will use default rights when the alias
	     * database is owned by root, otherwise it will use the rights of
	     * the alias database owner.
	     */
	    if ((alias_uid = dict_owner(*cpp)) == 0) {
		alias_pwd = 0;
		RESET_USER_ATTR(usr_attr, state.level);
	    } else {
		if ((alias_pwd = mypwuid(alias_uid)) == 0) {
		    msg_warn("cannot find alias database owner for %s", *cpp);
		    *statusp = defer_append(BOUNCE_FLAGS(state.request),
					    BOUNCE_ATTR(state.msg_attr),
					"cannot find alias database owner");
		    return (YES);
		}
		SET_USER_ATTR(usr_attr, alias_pwd, state.level);
	    }

	    /*
	     * WHERE TO REPORT DELIVERY PROBLEMS.
	     * 
	     * Use the owner- alias if one is specified, otherwise reset the
	     * owner attribute and use the include file ownership if we can.
	     * Save the dict_lookup() result before something clobbers it.
	     * 
	     * Don't match aliases that are based on regexps.
	     */
#define STR(x)	vstring_str(x)
#define OWNER_ASSIGN(own) \
	    (own = (var_ownreq_special == 0 ? 0 : \
	    concatenate("owner-", name, (char *) 0)))

	    expansion = mystrdup(alias_result);
	    if (OWNER_ASSIGN(owner) != 0
		&& (owner_rhs = maps_find(alias_maps, owner, DICT_FLAG_NONE)) != 0) {
		canon_owner = canon_addr_internal(vstring_alloc(10),
				     var_exp_own_alias ? owner_rhs : owner);
		SET_OWNER_ATTR(state.msg_attr, STR(canon_owner), state.level);
	    } else {
		canon_owner = 0;
		RESET_OWNER_ATTR(state.msg_attr, state.level);
	    }

	    /*
	     * EXTERNAL LOOP CONTROL
	     * 
	     * Set the delivered message attribute to the recipient, so that
	     * this message will list the correct forwarding address.
	     */
	    state.msg_attr.delivered = state.msg_attr.recipient;

	    /*
	     * Deliver.
	     */
	    alias_count = 0;
	    *statusp =
		(dict_errno ?
		 defer_append(BOUNCE_FLAGS(state.request),
			      BOUNCE_ATTR(state.msg_attr),
			      "alias database unavailable") :
	    deliver_token_string(state, usr_attr, expansion, &alias_count));
#if 0
	    if (var_ownreq_special
		&& strncmp("owner-", state.msg_attr.sender, 6) != 0
		&& alias_count > 10)
		msg_warn("mailing list \"%s\" needs an \"owner-%s\" alias",
			 name, name);
#endif
	    if (alias_count < 1) {
		msg_warn("no recipient in alias lookup result for %s", name);
		*statusp = defer_append(BOUNCE_FLAGS(state.request),
					BOUNCE_ATTR(state.msg_attr),
					"alias database unavailable");
	    }
	    myfree(expansion);
	    if (owner)
		myfree(owner);
	    if (canon_owner)
		vstring_free(canon_owner);
	    if (alias_pwd)
		mypwfree(alias_pwd);
	    return (YES);
	}

	/*
	 * If the alias database was inaccessible for some reason, defer
	 * further delivery for the current top-level recipient.
	 */
	if (dict_errno != 0) {
	    *statusp = defer_append(BOUNCE_FLAGS(state.request),
				    BOUNCE_ATTR(state.msg_attr),
				    "alias database unavailable");
	    return (YES);
	} else {
	    if (msg_verbose)
		msg_info("%s: %s: %s not found", myname, *cpp, name);
	}
    }

    /*
     * Try delivery to a local user instead.
     */
    return (NO);
}
Ejemplo n.º 7
0
int     deliver_alias(LOCAL_STATE state, USER_ATTR usr_attr,
		              char *name, int *statusp)
{
    const char *myname = "deliver_alias";
    const char *alias_result;
    char   *saved_alias_result;
    char   *owner;
    char  **cpp;
    uid_t   alias_uid;
    struct mypasswd *alias_pwd;
    VSTRING *canon_owner;
    DICT   *dict;
    const char *owner_rhs;		/* owner alias, RHS */
    int     alias_count;
    int     dsn_notify;
    char   *dsn_envid;
    int     dsn_ret;
    const char *dsn_orcpt;

    /*
     * Make verbose logging easier to understand.
     */
    state.level++;
    if (msg_verbose)
	MSG_LOG_STATE(myname, state);

    /*
     * DUPLICATE/LOOP ELIMINATION
     * 
     * We cannot do duplicate elimination here. Sendmail compatibility requires
     * that we allow multiple deliveries to the same alias, even recursively!
     * For example, we must deliver to mailbox any messags that are addressed
     * to the alias of a user that lists that same alias in her own .forward
     * file. Yuck! This is just an example of some really perverse semantics
     * that people will expect Postfix to implement just like sendmail.
     * 
     * We can recognize one special case: when an alias includes its own name,
     * deliver to the user instead, just like sendmail. Otherwise, we just
     * bail out when nesting reaches some unreasonable depth, and blame it on
     * a possible alias loop.
     */
    if (state.msg_attr.exp_from != 0
	&& strcasecmp(state.msg_attr.exp_from, name) == 0)
	return (NO);
    if (state.level > 100) {
	msg_warn("alias database loop for %s", name);
	dsb_simple(state.msg_attr.why, "5.4.6",
		   "alias database loop for %s", name);
	*statusp = bounce_append(BOUNCE_FLAGS(state.request),
				 BOUNCE_ATTR(state.msg_attr));
	return (YES);
    }
    state.msg_attr.exp_from = name;

    /*
     * There are a bunch of roles that we're trying to keep track of.
     * 
     * First, there's the issue of whose rights should be used when delivering
     * to "|command" or to /file/name. With alias databases, the rights are
     * those of who owns the alias, i.e. the database owner. With aliases
     * owned by root, a default user is used instead. When an alias with
     * default rights references an include file owned by an ordinary user,
     * we must use the rights of the include file owner, otherwise the
     * include file owner could take control of the default account.
     * 
     * Secondly, there's the question of who to notify of delivery problems.
     * With aliases that have an owner- alias, the latter is used to set the
     * sender and owner attributes. Otherwise, the owner attribute is reset
     * (the alias is globally visible and could be sent to by anyone).
     */
    for (cpp = alias_maps->argv->argv; *cpp; cpp++) {
	if ((dict = dict_handle(*cpp)) == 0)
	    msg_panic("%s: dictionary not found: %s", myname, *cpp);
	if ((alias_result = dict_get(dict, name)) != 0) {
	    if (msg_verbose)
		msg_info("%s: %s: %s = %s", myname, *cpp, name, alias_result);

	    /*
	     * 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",
			   "aliased to %s", alias_result);
		*statusp = sent(BOUNCE_FLAGS(state.request),
				SENT_ATTR(state.msg_attr));
		return (YES);
	    }

	    /*
	     * DELIVERY POLICY
	     * 
	     * Update the expansion type attribute, so we can decide if
	     * deliveries to |command and /file/name are allowed at all.
	     */
	    state.msg_attr.exp_type = EXPAND_TYPE_ALIAS;

	    /*
	     * DELIVERY RIGHTS
	     * 
	     * What rights to use for |command and /file/name deliveries? The
	     * command and file code will use default rights when the alias
	     * database is owned by root, otherwise it will use the rights of
	     * the alias database owner.
	     */
	    if ((alias_uid = dict_owner(*cpp)) == 0) {
		alias_pwd = 0;
		RESET_USER_ATTR(usr_attr, state.level);
	    } else {
		if ((alias_pwd = mypwuid(alias_uid)) == 0) {
		    msg_warn("cannot find alias database owner for %s", *cpp);
		    dsb_simple(state.msg_attr.why, "4.3.0",
			       "cannot find alias database owner");
		    *statusp = defer_append(BOUNCE_FLAGS(state.request),
					    BOUNCE_ATTR(state.msg_attr));
		    return (YES);
		}
		SET_USER_ATTR(usr_attr, alias_pwd, state.level);
	    }

	    /*
	     * WHERE TO REPORT DELIVERY PROBLEMS.
	     * 
	     * Use the owner- alias if one is specified, otherwise reset the
	     * owner attribute and use the include file ownership if we can.
	     * Save the dict_lookup() result before something clobbers it.
	     * 
	     * Don't match aliases that are based on regexps.
	     */
#define OWNER_ASSIGN(own) \
	    (own = (var_ownreq_special == 0 ? 0 : \
	    concatenate("owner-", name, (char *) 0)))

	    saved_alias_result = mystrdup(alias_result);
	    if (OWNER_ASSIGN(owner) != 0
		&& (owner_rhs = maps_find(alias_maps, owner, DICT_FLAG_NONE)) != 0) {
		canon_owner = canon_addr_internal(vstring_alloc(10),
				     var_exp_own_alias ? owner_rhs : owner);
		/* Set envelope sender and owner attribute. */
		SET_OWNER_ATTR(state.msg_attr, STR(canon_owner), state.level);
	    } else {
		canon_owner = 0;
		/* Note: this does not reset the envelope sender. */
		if (var_reset_owner_attr)
		    RESET_OWNER_ATTR(state.msg_attr, state.level);
	    }

	    /*
	     * 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;

	    /*
	     * Deliver.
	     */
	    alias_count = 0;
	    if (dict_errno != 0) {
		dsb_simple(state.msg_attr.why, "4.3.0",
			   "alias database unavailable");
		*statusp = defer_append(BOUNCE_FLAGS(state.request),
					BOUNCE_ATTR(state.msg_attr));
	    } else {

		/*
		 * XXX DSN
		 * 
		 * When delivering to a mailing list (i.e. the envelope sender
		 * is replaced) the ENVID, NOTIFY, RET, and ORCPT parameters
		 * which accompany the redistributed message MUST NOT be
		 * derived from those of the original message.
		 * 
		 * When delivering to an alias (i.e. the envelope sender 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.
		 */
#define DSN_SAVE_UPDATE(saved, old, new) do { \
	saved = old; \
	old = new; \
    } while (0)

		DSN_SAVE_UPDATE(dsn_notify, state.msg_attr.rcpt.dsn_notify,
				dsn_notify == DSN_NOTIFY_SUCCESS ?
				DSN_NOTIFY_NEVER :
				dsn_notify & ~DSN_NOTIFY_SUCCESS);
		if (canon_owner != 0) {
		    DSN_SAVE_UPDATE(dsn_envid, state.msg_attr.dsn_envid, "");
		    DSN_SAVE_UPDATE(dsn_ret, state.msg_attr.dsn_ret, 0);
		    DSN_SAVE_UPDATE(dsn_orcpt, state.msg_attr.rcpt.dsn_orcpt, "");
		    state.msg_attr.rcpt.orig_addr = "";
		}
		*statusp =
		    deliver_token_string(state, usr_attr, saved_alias_result,
					 &alias_count);
#if 0
		if (var_ownreq_special
		    && strncmp("owner-", state.msg_attr.sender, 6) != 0
		    && alias_count > 10)
		    msg_warn("mailing list \"%s\" needs an \"owner-%s\" alias",
			     name, name);
#endif
		if (alias_count < 1) {
		    msg_warn("no recipient in alias lookup result for %s", name);
		    dsb_simple(state.msg_attr.why, "4.3.0",
			       "alias database unavailable");
		    *statusp = defer_append(BOUNCE_FLAGS(state.request),
					    BOUNCE_ATTR(state.msg_attr));
		} else {

		    /*
		     * XXX DSN
		     * 
		     * When delivering to a mailing list (i.e. the envelope
		     * sender address is replaced) and NOTIFY=SUCCESS was
		     * specified, report a DSN of "delivered".
		     * 
		     * When delivering to an alias (i.e. the envelope sender
		     * address is not replaced) and NOTIFY=SUCCESS was
		     * specified, report a DSN of "expanded".
		     */
		    if (dsn_notify & DSN_NOTIFY_SUCCESS) {
			state.msg_attr.rcpt.dsn_notify = dsn_notify;
			if (canon_owner != 0) {
			    state.msg_attr.dsn_envid = dsn_envid;
			    state.msg_attr.dsn_ret = dsn_ret;
			    state.msg_attr.rcpt.dsn_orcpt = dsn_orcpt;
			}
			dsb_update(state.msg_attr.why, "2.0.0", canon_owner ?
				   "delivered" : "expanded",
				   DSB_SKIP_RMTA, DSB_SKIP_REPLY,
				   "alias expanded");
			(void) trace_append(BOUNCE_FLAG_NONE,
					    SENT_ATTR(state.msg_attr));
		    }
		}
	    }
	    myfree(saved_alias_result);
	    if (owner)
		myfree(owner);
	    if (canon_owner)
		vstring_free(canon_owner);
	    if (alias_pwd)
		mypwfree(alias_pwd);
	    return (YES);
	}

	/*
	 * If the alias database was inaccessible for some reason, defer
	 * further delivery for the current top-level recipient.
	 */
	if (dict_errno != 0) {
	    dsb_simple(state.msg_attr.why, "4.3.0",
		       "alias database unavailable");
	    *statusp = defer_append(BOUNCE_FLAGS(state.request),
				    BOUNCE_ATTR(state.msg_attr));
	    return (YES);
	} else {
	    if (msg_verbose)
		msg_info("%s: %s: %s not found", myname, *cpp, name);
	}
    }

    /*
     * Try delivery to a local user instead.
     */
    return (NO);
}
Ejemplo n.º 8
0
int     deliver_mailbox(LOCAL_STATE state, USER_ATTR usr_attr, int *statusp)
{
    const char *myname = "deliver_mailbox";
    int     status;
    struct mypasswd *mbox_pwd;
    char   *path;
    static MAPS *transp_maps;
    const char *map_transport;
    static MAPS *cmd_maps;
    const char *map_command;

    /*
     * Make verbose logging easier to understand.
     */
    state.level++;
    if (msg_verbose)
	MSG_LOG_STATE(myname, state);

    /*
     * DUPLICATE ELIMINATION
     * 
     * Don't come here more than once, whether or not the recipient exists.
     */
    if (been_here(state.dup_filter, "mailbox %s", state.msg_attr.local))
	return (YES);

    /*
     * Delegate mailbox delivery to another message transport.
     */
    if (*var_mbox_transp_maps && transp_maps == 0)
	transp_maps = maps_create(VAR_MBOX_TRANSP_MAPS, var_mbox_transp_maps,
				  DICT_FLAG_LOCK | DICT_FLAG_NO_REGSUB);
    /* The -1 is a hint for the down-stream deliver_completed() function. */
    if (transp_maps
	&& (map_transport = maps_find(transp_maps, state.msg_attr.user,
				      DICT_FLAG_NONE)) != 0) {
	state.msg_attr.rcpt.offset = -1L;
	*statusp = deliver_pass(MAIL_CLASS_PRIVATE, map_transport,
				state.request, &state.msg_attr.rcpt);
	return (YES);
    } else if (transp_maps && transp_maps->error != 0) {
	/* Details in the logfile. */
	dsb_simple(state.msg_attr.why, "4.3.0", "table lookup failure");
	*statusp = defer_append(BOUNCE_FLAGS(state.request),
				BOUNCE_ATTR(state.msg_attr));
	return (YES);
    }
    if (*var_mailbox_transport) {
	state.msg_attr.rcpt.offset = -1L;
	*statusp = deliver_pass(MAIL_CLASS_PRIVATE, var_mailbox_transport,
				state.request, &state.msg_attr.rcpt);
	return (YES);
    }

    /*
     * Skip delivery when this recipient does not exist.
     */
    if ((errno = mypwnam_err(state.msg_attr.user, &mbox_pwd)) != 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 (mbox_pwd == 0)
	return (NO);

    /*
     * No early returns or we have a memory leak.
     */

    /*
     * DELIVERY RIGHTS
     * 
     * Use the rights of the recipient user.
     */
    SET_USER_ATTR(usr_attr, mbox_pwd, state.level);

    /*
     * Deliver to mailbox, maildir or to external command.
     */
#define LAST_CHAR(s) (s[strlen(s) - 1])

    if (*var_mailbox_cmd_maps && cmd_maps == 0)
	cmd_maps = maps_create(VAR_MAILBOX_CMD_MAPS, var_mailbox_cmd_maps,
			       DICT_FLAG_LOCK | DICT_FLAG_PARANOID);

    if (cmd_maps && (map_command = maps_find(cmd_maps, state.msg_attr.user,
				    DICT_FLAG_NONE)) != 0) {
	status = deliver_command(state, usr_attr, map_command);
    } else if (cmd_maps && cmd_maps->error != 0) {
	/* Details in the logfile. */
	dsb_simple(state.msg_attr.why, "4.3.0", "table lookup failure");
	status = defer_append(BOUNCE_FLAGS(state.request),
			      BOUNCE_ATTR(state.msg_attr));
    } else if (*var_mailbox_command) {
	status = deliver_command(state, usr_attr, var_mailbox_command);
    } else if (*var_home_mailbox && LAST_CHAR(var_home_mailbox) == '/') {
	path = concatenate(usr_attr.home, "/", var_home_mailbox, (char *) 0);
	status = deliver_maildir(state, usr_attr, path);
	myfree(path);
    } else if (*var_mail_spool_dir && LAST_CHAR(var_mail_spool_dir) == '/') {
	path = concatenate(var_mail_spool_dir, state.msg_attr.user,
			   "/", (char *) 0);
	status = deliver_maildir(state, usr_attr, path);
	myfree(path);
    } else
	status = deliver_mailbox_file(state, usr_attr);

    /*
     * Cleanup.
     */
    mypwfree(mbox_pwd);
    *statusp = status;
    return (YES);
}
Ejemplo n.º 9
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.
     */
    mypwd = 0;
    if (var_stat_home_dir
	&& (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");
	return (defer_append(BOUNCE_FLAGS(state.request),
			     BOUNCE_ATTR(state.msg_attr)));
    }
    if (mypwd != 0) {
	if (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);
	    mypwfree(mypwd);
	    return (defer_append(BOUNCE_FLAGS(state.request),
				 BOUNCE_ATTR(state.msg_attr)));
	}
	mypwfree(mypwd);
    }
    if (deliver_dotforward(state, usr_attr, &status) == 0
	&& deliver_mailbox(state, usr_attr, &status) == 0)
	status = deliver_unknown(state, usr_attr);
    return (status);
}
Ejemplo n.º 10
0
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, ", \t\r\n")) != 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);
}