Пример #1
0
CFG_PARSER *cfg_parser_alloc(const char *pname)
{
    const char *myname = "cfg_parser_alloc";
    CFG_PARSER *parser;
    DICT   *dict;

    if (pname == 0 || *pname == 0)
	msg_fatal("%s: null parser name", myname);
    parser = (CFG_PARSER *) mymalloc(sizeof(*parser));
    parser->name = mystrdup(pname);
    if (*parser->name == '/' || *parser->name == '.') {
	if (dict_load_file_xt(parser->name, parser->name) == 0) {
	    myfree(parser->name);
	    myfree((void *) parser);
	    return (0);
	}
	parser->get_str = get_dict_str;
	parser->get_int = get_dict_int;
	parser->get_bool = get_dict_bool;
	dict = dict_handle(parser->name);
    } else {
	parser->get_str = get_main_str;
	parser->get_int = get_main_int;
	parser->get_bool = get_main_bool;
	dict = dict_handle(CONFIG_DICT);	/* XXX Use proper API */
    }
    if (dict == 0)
	msg_panic("%s: dict_handle failed", myname);
    parser->owner = dict->owner;
    return (parser);
}
Пример #2
0
int     match_string(MATCH_LIST *list, const char *string, const char *pattern)
{
    const char *myname = "match_string";
    DICT   *dict;

    if (msg_verbose)
	msg_info("%s: %s ~? %s", myname, string, pattern);

    /*
     * Try dictionary lookup: exact match.
     */
    if (MATCH_DICTIONARY(pattern)) {
	if ((dict = dict_handle(pattern)) == 0)
	    msg_panic("%s: unknown dictionary: %s", myname, pattern);
	if (dict_get(dict, string) != 0)
	    return (1);
	if ((list->error = dict->error) != 0)
	    return (match_error(list, "%s:%s: table lookup problem",
				dict->type, dict->name));
	return (0);
    }

    /*
     * Try an exact string match.
     */
    if (strcasecmp(string, pattern) == 0) {
	return (1);
    }

    /*
     * No match found.
     */
    return (0);
}
Пример #3
0
static const char *dict_union_lookup(DICT *dict, const char *query)
{
    static const char myname[] = "dict_union_lookup";
    DICT_UNION *dict_union = (DICT_UNION *) dict;
    DICT   *map;
    char  **cpp;
    char   *dict_type_name;
    const char *result = 0;

    /*
     * After Roel van Meer, postfix-users mailing list, Sept 2014.
     */
    VSTRING_RESET(dict_union->re_buf);
    for (cpp = dict_union->map_union->argv; (dict_type_name = *cpp) != 0; cpp++) {
	if ((map = dict_handle(dict_type_name)) == 0)
	    msg_panic("%s: dictionary \"%s\" not found", myname, dict_type_name);
	if ((result = dict_get(map, query)) == 0)
	    continue;
	if (VSTRING_LEN(dict_union->re_buf) > 0)
	    VSTRING_ADDCH(dict_union->re_buf, ',');
	vstring_strcat(dict_union->re_buf, result);
    }
    DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE,
			VSTRING_LEN(dict_union->re_buf) > 0 ?
			STR(dict_union->re_buf) : 0);
}
Пример #4
0
static DICT *proxy_map_find(const char *map_type_name, int request_flags,
                            int *statp)
{
    DICT   *dict;

#define PROXY_COLON	DICT_TYPE_PROXY ":"
#define PROXY_COLON_LEN	(sizeof(PROXY_COLON) - 1)
#define READ_OPEN_FLAGS	O_RDONLY
#define WRITE_OPEN_FLAGS (O_RDWR | O_CREAT)

    /*
     * Canonicalize the map name. If the map is not on the approved list,
     * deny the request.
     */
#define PROXY_MAP_FIND_ERROR_RETURN(x)  { *statp = (x); return (0); }

    while (strncmp(map_type_name, PROXY_COLON, PROXY_COLON_LEN) == 0)
        map_type_name += PROXY_COLON_LEN;
    if (strchr(map_type_name, ':') == 0)
        PROXY_MAP_FIND_ERROR_RETURN(PROXY_STAT_BAD);
    if (htable_locate(proxy_auth_maps, map_type_name) == 0) {
        msg_warn("request for unapproved table: \"%s\"", map_type_name);
        msg_warn("to approve this table for %s access, list %s:%s in %s:%s",
                 proxy_writer == 0 ? "read-only" : "read-write",
                 DICT_TYPE_PROXY, map_type_name, MAIN_CONF_FILE,
                 proxy_writer == 0 ? VAR_PROXY_READ_MAPS :
                 VAR_PROXY_WRITE_MAPS);
        PROXY_MAP_FIND_ERROR_RETURN(PROXY_STAT_DENY);
    }

    /*
     * Open one instance of a map for each combination of name+flags.
     *
     * Assume that a map instance can be shared among clients with different
     * paranoia flag settings and with different map lookup flag settings.
     *
     * XXX The open() flags are passed implicitly, via the selection of the
     * service name. For a more sophisticated interface, appropriate subsets
     * of open() flags should be received directly from the client.
     */
    vstring_sprintf(map_type_name_flags, "%s:%s", map_type_name,
                    dict_flags_str(request_flags & DICT_FLAG_INST_MASK));
    if (msg_verbose)
        msg_info("proxy_map_find: %s", STR(map_type_name_flags));
    if ((dict = dict_handle(STR(map_type_name_flags))) == 0) {
        dict = dict_open(map_type_name, proxy_writer ?
                         WRITE_OPEN_FLAGS : READ_OPEN_FLAGS,
                         request_flags);
        if (dict == 0)
            msg_panic("proxy_map_find: dict_open null result");
        dict_register(STR(map_type_name_flags), dict);
    }
    dict->error = 0;
    return (dict);
}
Пример #5
0
CFG_PARSER *cfg_parser_free(CFG_PARSER *parser)
{
    const char *myname = "cfg_parser_free";

    if (parser->name == 0 || *parser->name == 0)
	msg_panic("%s: null parser name", myname);
    if (*parser->name == '/' || *parser->name == '.') {
	if (dict_handle(parser->name))
	    dict_unregister(parser->name);
    }
    myfree(parser->name);
    myfree((void *) parser);
    return (0);
}
Пример #6
0
const char *maps_find(MAPS *maps, const char *name, int flags)
{
    const char *myname = "maps_find";
    char  **map_name;
    const char *expansion;
    DICT   *dict;

    /*
     * In case of return without map lookup (empty name or no maps).
     */
    maps->error = 0;

    /*
     * Temp. workaround, for buggy callers that pass zero-length keys when
     * given partial addresses.
     */
    if (*name == 0)
	return (0);

    for (map_name = maps->argv->argv; *map_name; map_name++) {
	if ((dict = dict_handle(*map_name)) == 0)
	    msg_panic("%s: dictionary not found: %s", myname, *map_name);
	if (flags != 0 && (dict->flags & flags) == 0)
	    continue;
	if ((expansion = dict_get(dict, name)) != 0) {
	    if (*expansion == 0) {
		msg_warn("%s lookup of %s returns an empty string result",
			 maps->title, name);
		msg_warn("%s should return NO RESULT in case of NOT FOUND",
			 maps->title);
		maps->error = DICT_ERR_RETRY;
		return (0);
	    }
	    if (msg_verbose)
		msg_info("%s: %s: %s: %s = %s", myname, maps->title,
			 *map_name, name, expansion);
	    return (expansion);
	} else if ((maps->error = dict->error) != 0) {
	    msg_warn("%s:%s lookup error for \"%.100s\"",
		     dict->type, dict->name, name);
	    break;
	}
    }
    if (msg_verbose)
	msg_info("%s: %s: %s: %s", myname, maps->title, name, maps->error ?
		 "search aborted" : "not found");
    return (0);
}
Пример #7
0
MAPS   *maps_create(const char *title, const char *map_names, int dict_flags)
{
    const char *myname = "maps_create";
    char   *temp;
    char   *bufp;
    static char sep[] = CHARS_COMMA_SP;
    static char parens[] = CHARS_BRACE;
    MAPS   *maps;
    char   *map_type_name;
    VSTRING *map_type_name_flags;
    DICT   *dict;

    /*
     * Initialize.
     */
    maps = (MAPS *) mymalloc(sizeof(*maps));
    maps->title = mystrdup(title);
    maps->argv = argv_alloc(2);
    maps->error = 0;

    /*
     * For each specified type:name pair, either register a new dictionary,
     * or increment the reference count of an existing one.
     */
    if (*map_names) {
	bufp = temp = mystrdup(map_names);
	map_type_name_flags = vstring_alloc(10);

#define OPEN_FLAGS	O_RDONLY

	while ((map_type_name = mystrtokq(&bufp, sep, parens)) != 0) {
	    vstring_sprintf(map_type_name_flags, "%s(%o,%s)",
			    map_type_name, OPEN_FLAGS,
			    dict_flags_str(dict_flags));
	    if ((dict = dict_handle(vstring_str(map_type_name_flags))) == 0)
		dict = dict_open(map_type_name, OPEN_FLAGS, dict_flags);
	    if ((dict->flags & dict_flags) != dict_flags)
		msg_panic("%s: map %s has flags 0%o, want flags 0%o",
			  myname, map_type_name, dict->flags, dict_flags);
	    dict_register(vstring_str(map_type_name_flags), dict);
	    argv_add(maps->argv, vstring_str(map_type_name_flags), ARGV_END);
	}
	myfree(temp);
	vstring_free(map_type_name_flags);
    }
    return (maps);
}
Пример #8
0
static uid_t dict_owner(char *table)
{
    char   *myname = "dict_owner";
    DICT   *dict;
    struct stat st;

    /*
     * This code sits here for now, but we may want to move it to the library
     * some time.
     */
    if ((dict = dict_handle(table)) == 0)
	msg_panic("%s: can't find dictionary: %s", myname, table);
    if (dict->stat_fd < 0)
	return (0);
    if (fstat(dict->stat_fd, &st) < 0)
	msg_fatal("%s: fstat dictionary %s: %m", myname, table);
    return (st.st_uid);
}
Пример #9
0
SERVER_ACL *server_acl_parse(const char *extern_acl, const char *origin)
{
    char   *saved_acl = mystrdup(extern_acl);
    SERVER_ACL *intern_acl = argv_alloc(1);
    char   *bp = saved_acl;
    char   *acl;

#define STREQ(x,y) ((*x) == (*y) && strcasecmp((x), (y)) == 0)
#define STRNE(x,y) ((*x) != (*y) || strcasecmp((x), (y)) != 0)

    /*
     * Nested tables are not allowed. Tables are opened before entering the
     * chroot jail, while access lists are evaluated after entering the
     * chroot jail.
     */
    while ((acl = mystrtok(&bp, SERVER_ACL_SEPARATORS)) != 0) {
	if (strchr(acl, ':') != 0) {
	    if (strchr(origin, ':') != 0) {
		msg_warn("table %s: lookup result \"%s\" is not allowed"
			 " -- ignoring remainder of access list",
			 origin, acl);
		argv_add(intern_acl, SERVER_ACL_NAME_DUNNO, (char *) 0);
		break;
	    } else {
		if (dict_handle(acl) == 0)
		    dict_register(acl, dict_open(acl, O_RDONLY, DICT_FLAG_LOCK
						 | DICT_FLAG_FOLD_FIX));
	    }
	}
	argv_add(intern_acl, acl, (char *) 0);
    }
    argv_terminate(intern_acl);

    /*
     * Cleanup.
     */
    myfree(saved_acl);
    return (intern_acl);
}
Пример #10
0
void    mail_conf_flush(void)
{
    if (dict_handle(CONFIG_DICT) != 0)
	dict_unregister(CONFIG_DICT);
}
Пример #11
0
int     server_acl_eval(const char *client_addr, SERVER_ACL * intern_acl,
			        const char *origin)
{
    const char *myname = "server_acl_eval";
    char  **cpp;
    DICT   *dict;
    SERVER_ACL *argv;
    const char *acl;
    const char *dict_val;
    int     ret;

    for (cpp = intern_acl->argv; (acl = *cpp) != 0; cpp++) {
	if (msg_verbose)
	    msg_info("source=%s address=%s acl=%s",
		     origin, client_addr, acl);
	if (STREQ(acl, SERVER_ACL_NAME_REJECT)) {
	    return (SERVER_ACL_ACT_REJECT);
	} else if (STREQ(acl, SERVER_ACL_NAME_PERMIT)) {
	    return (SERVER_ACL_ACT_PERMIT);
	} else if (STREQ(acl, SERVER_ACL_NAME_WL_MYNETWORKS)) {
	    if (addr_match_list_match(server_acl_mynetworks, client_addr))
		return (SERVER_ACL_ACT_PERMIT);
	    if (server_acl_mynetworks->error != 0) {
		msg_warn("%s: %s: mynetworks lookup error -- ignoring the "
			 "remainder of this access list", origin, acl);
		return (SERVER_ACL_ACT_ERROR);
	    }
	} else if (strchr(acl, ':') != 0) {
	    if ((dict = dict_handle(acl)) == 0)
		msg_panic("%s: unexpected dictionary: %s", myname, acl);
	    if ((dict_val = dict_get(dict, client_addr)) != 0) {
		/* Fake up an ARGV to avoid lots of mallocs and frees. */
		if (dict_val[strcspn(dict_val, ":" SERVER_ACL_SEPARATORS)] == 0) {
		    ARGV_FAKE_BEGIN(fake_argv, dict_val);
		    ret = server_acl_eval(client_addr, &fake_argv, acl);
		    ARGV_FAKE_END;
		} else {
		    argv = server_acl_parse(dict_val, acl);
		    ret = server_acl_eval(client_addr, argv, acl);
		    argv_free(argv);
		}
		if (ret != SERVER_ACL_ACT_DUNNO)
		    return (ret);
	    } else if (dict->error != 0) {
		msg_warn("%s: %s: table lookup error -- ignoring the remainder "
			 "of this access list", origin, acl);
		return (SERVER_ACL_ACT_ERROR);
	    }
	} else if (STREQ(acl, SERVER_ACL_NAME_DUNNO)) {
	    return (SERVER_ACL_ACT_DUNNO);
	} else {
	    msg_warn("%s: unknown command: %s -- ignoring the remainder "
		     "of this access list", origin, acl);
	    return (SERVER_ACL_ACT_ERROR);
	}
    }
    if (msg_verbose)
	msg_info("source=%s address=%s - no match",
		 origin, client_addr);
    return (SERVER_ACL_ACT_DUNNO);
}
Пример #12
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);
}
Пример #13
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);
}
Пример #14
0
static ARGV *match_list_parse(ARGV *list, char *string, int init_match)
{
    const char *myname = "match_list_parse";
    VSTRING *buf = vstring_alloc(10);
    VSTREAM *fp;
    const char *delim = " ,\t\r\n";
    char   *bp = string;
    char   *start;
    char   *item;
    char   *map_type_name_flags;
    int     match;

#define OPEN_FLAGS	O_RDONLY
#define DICT_FLAGS	(DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX)
#define STR(x)		vstring_str(x)

    /*
     * /filename contents are expanded in-line. To support !/filename we
     * prepend the negation operator to each item from the file.
     */
    while ((start = mystrtokq(&bp, delim, "{}")) != 0) {
	if (*start == '#') {
	    msg_warn("%s: comment at end of line is not supported: %s %s",
		     myname, start, bp);
	    break;
	}
	for (match = init_match, item = start; *item == '!'; item++)
	    match = !match;
	if (*item == 0)
	    msg_fatal("%s: no pattern after '!'", myname);
	if (*item == '/') {			/* /file/name */
	    if ((fp = vstream_fopen(item, O_RDONLY, 0)) == 0) {
		vstring_sprintf(buf, "%s:%s", DICT_TYPE_NOFILE, item);
		/* XXX Should increment existing map refcount. */
		if (dict_handle(STR(buf)) == 0)
		    dict_register(STR(buf),
				  dict_surrogate(DICT_TYPE_NOFILE, item,
						 OPEN_FLAGS, DICT_FLAGS,
						 "open file %s: %m", item));
		argv_add(list, STR(buf), (char *) 0);
	    } else {
		while (vstring_fgets(buf, fp))
		    if (vstring_str(buf)[0] != '#')
			list = match_list_parse(list, vstring_str(buf), match);
		if (vstream_fclose(fp))
		    msg_fatal("%s: read file %s: %m", myname, item);
	    }
	} else if (MATCH_DICTIONARY(item)) {	/* type:table */
	    vstring_sprintf(buf, "%s%s(%o,%s)", match ? "" : "!",
			    item, OPEN_FLAGS, dict_flags_str(DICT_FLAGS));
	    map_type_name_flags = STR(buf) + (match == 0);
	    /* XXX Should increment existing map refcount. */
	    if (dict_handle(map_type_name_flags) == 0)
		dict_register(map_type_name_flags,
			      dict_open(item, OPEN_FLAGS, DICT_FLAGS));
	    argv_add(list, STR(buf), (char *) 0);
	} else {				/* other pattern */
	    argv_add(list, match ? item :
		     STR(vstring_sprintf(buf, "!%s", item)), (char *) 0);
	}
    }
    vstring_free(buf);
    return (list);
}
Пример #15
0
DICT   *dict_union_open(const char *name, int open_flags, int dict_flags)
{
    static const char myname[] = "dict_union_open";
    DICT_UNION *dict_union;
    char   *saved_name = 0;
    char   *dict_type_name;
    ARGV   *argv = 0;
    char  **cpp;
    DICT   *dict;
    int     match_flags = 0;
    struct DICT_OWNER aggr_owner;
    size_t  len;

    /*
     * Clarity first. Let the optimizer worry about redundant code.
     */
#define DICT_UNION_RETURN(x) do { \
	      if (saved_name != 0) \
	          myfree(saved_name); \
	      if (argv != 0) \
	          argv_free(argv); \
	      return (x); \
	  } while (0)

    /*
     * Sanity checks.
     */
    if (open_flags != O_RDONLY)
	DICT_UNION_RETURN(dict_surrogate(DICT_TYPE_UNION, name,
					 open_flags, dict_flags,
				  "%s:%s map requires O_RDONLY access mode",
					 DICT_TYPE_UNION, name));

    /*
     * Split the table name into its constituent parts.
     */
    if ((len = balpar(name, CHARS_BRACE)) == 0 || name[len] != 0
	|| *(saved_name = mystrndup(name + 1, len - 2)) == 0
	|| ((argv = argv_splitq(saved_name, CHARS_COMMA_SP, CHARS_BRACE)),
	    (argv->argc == 0)))
	DICT_UNION_RETURN(dict_surrogate(DICT_TYPE_UNION, name,
					 open_flags, dict_flags,
					 "bad syntax: \"%s:%s\"; "
					 "need \"%s:{type:name...}\"",
					 DICT_TYPE_UNION, name,
					 DICT_TYPE_UNION));

    /*
     * The least-trusted table in the set determines the over-all trust
     * level. The first table determines the pattern-matching flags.
     */
    DICT_OWNER_AGGREGATE_INIT(aggr_owner);
    for (cpp = argv->argv; (dict_type_name = *cpp) != 0; cpp++) {
	if (msg_verbose)
	    msg_info("%s: %s", myname, dict_type_name);
	if (strchr(dict_type_name, ':') == 0)
	    DICT_UNION_RETURN(dict_surrogate(DICT_TYPE_UNION, name,
					     open_flags, dict_flags,
					     "bad syntax: \"%s:%s\"; "
					     "need \"%s:{type:name...}\"",
					     DICT_TYPE_UNION, name,
					     DICT_TYPE_UNION));
	if ((dict = dict_handle(dict_type_name)) == 0)
	    dict = dict_open(dict_type_name, open_flags, dict_flags);
	dict_register(dict_type_name, dict);
	DICT_OWNER_AGGREGATE_UPDATE(aggr_owner, dict->owner);
	if (cpp == argv->argv)
	    match_flags = dict->flags & (DICT_FLAG_FIXED | DICT_FLAG_PATTERN);
    }

    /*
     * Bundle up the result.
     */
    dict_union =
	(DICT_UNION *) dict_alloc(DICT_TYPE_UNION, name, sizeof(*dict_union));
    dict_union->dict.lookup = dict_union_lookup;
    dict_union->dict.close = dict_union_close;
    dict_union->dict.flags = dict_flags | match_flags;
    dict_union->dict.owner = aggr_owner;
    dict_union->re_buf = vstring_alloc(100);
    dict_union->map_union = argv;
    argv = 0;
    DICT_UNION_RETURN(DICT_DEBUG (&dict_union->dict));
}
Пример #16
0
int     match_hostaddr(MATCH_LIST *list, const char *addr, const char *pattern)
{
    const char *myname = "match_hostaddr";
    char   *saved_patt;
    CIDR_MATCH match_info;
    DICT   *dict;
    VSTRING *err;
    int     rc;

    if (msg_verbose)
	msg_info("%s: %s ~? %s", myname, addr, pattern);

#define V4_ADDR_STRING_CHARS	"01234567890."
#define V6_ADDR_STRING_CHARS	V4_ADDR_STRING_CHARS "abcdefABCDEF:"

    if (addr[strspn(addr, V6_ADDR_STRING_CHARS)] != 0)
	return (0);

    /*
     * Try dictionary lookup. This can be case insensitive.
     */
    if (MATCH_DICTIONARY(pattern)) {
	if ((dict = dict_handle(pattern)) == 0)
	    msg_panic("%s: unknown dictionary: %s", myname, pattern);
	if (dict_get(dict, addr) != 0)
	    return (1);
	if ((list->error = dict->error) != 0)
	    return (match_error(list, "%s:%s: table lookup problem",
				dict->type, dict->name));
	return (0);
    }

    /*
     * Try an exact match with the host address.
     */
    if (pattern[0] != '[') {
	if (strcasecmp(addr, pattern) == 0)
	    return (1);
    } else {
	size_t  addr_len = strlen(addr);

	if (strncasecmp(addr, pattern + 1, addr_len) == 0
	    && strcmp(pattern + 1 + addr_len, "]") == 0)
	    return (1);
    }

    /*
     * Light-weight tests before we get into expensive operations.
     * 
     * - Don't bother matching IPv4 against IPv6. Postfix transforms
     * IPv4-in-IPv6 to native IPv4 form when IPv4 support is enabled in
     * Postfix; if not, then Postfix has no business dealing with IPv4
     * addresses anyway.
     * 
     * - Don't bother unless the pattern is either an IPv6 address or net/mask.
     * 
     * We can safely skip IPv4 address patterns because their form is
     * unambiguous and they did not match in the strcasecmp() calls above.
     * 
     * XXX We MUST skip (parent) domain names, which may appear in NAMADR_LIST
     * input, to avoid triggering false cidr_match_parse() errors.
     * 
     * The last two conditions below are for backwards compatibility with
     * earlier Postfix versions: don't abort with fatal errors on junk that
     * was silently ignored (principle of least astonishment).
     */
    if (!strchr(addr, ':') != !strchr(pattern, ':')
	|| pattern[strcspn(pattern, ":/")] == 0
	|| pattern[strspn(pattern, V4_ADDR_STRING_CHARS)] == 0
	|| pattern[strspn(pattern, V6_ADDR_STRING_CHARS "[]/")] != 0)
	return (0);

    /*
     * No escape from expensive operations: either we have a net/mask
     * pattern, or we have an address that can have multiple valid
     * representations (e.g., 0:0:0:0:0:0:0:1 versus ::1, etc.). The only way
     * to find out if the address matches the pattern is to transform
     * everything into to binary form, and to do the comparison there.
     */
    saved_patt = mystrdup(pattern);
    err = cidr_match_parse(&match_info, saved_patt, (VSTRING *) 0);
    myfree(saved_patt);
    if (err != 0) {
	list->error = DICT_ERR_RETRY;
	rc = match_error(list, "%s", vstring_str(err));
	vstring_free(err);
	return (rc);
    }
    return (cidr_match_execute(&match_info, addr) != 0);
}
Пример #17
0
int     match_hostname(MATCH_LIST *list, const char *name, const char *pattern)
{
    const char *myname = "match_hostname";
    const char *pd;
    const char *entry;
    const char *next;
    int     match;
    DICT   *dict;

    if (msg_verbose)
	msg_info("%s: %s ~? %s", myname, name, pattern);

    /*
     * Try dictionary lookup: exact match and parent domains.
     * 
     * Don't look up parent domain substrings with regexp maps etc.
     */
    if (MATCH_DICTIONARY(pattern)) {
	if ((dict = dict_handle(pattern)) == 0)
	    msg_panic("%s: unknown dictionary: %s", myname, pattern);
	match = 0;
	for (entry = name; *entry != 0; entry = next) {
	    if (entry == name || (dict->flags & DICT_FLAG_FIXED)) {
		match = (dict_get(dict, entry) != 0);
		if (msg_verbose > 1)
		    msg_info("%s: lookup %s:%s %s: %s",
			     myname, dict->type, dict->name, entry,
			     match ? "found" : "notfound");
		if (match != 0)
		    break;
		if ((list->error = dict->error) != 0)
		    return (match_error(list, "%s:%s: table lookup problem",
					dict->type, dict->name));
	    }
	    if ((next = strchr(entry + 1, '.')) == 0)
		break;
	    if (list->flags & MATCH_FLAG_PARENT)
		next += 1;
	}
	return (match);
    }

    /*
     * Try an exact match with the host name.
     */
    if (strcasecmp(name, pattern) == 0) {
	return (1);
    }

    /*
     * See if the pattern is a parent domain of the hostname.
     */
    else {
	if (list->flags & MATCH_FLAG_PARENT) {
	    pd = name + strlen(name) - strlen(pattern);
	    if (pd > name && pd[-1] == '.' && strcasecmp(pd, pattern) == 0)
		return (1);
	} else if (pattern[0] == '.') {
	    pd = name + strlen(name) - strlen(pattern);
	    if (pd > name && strcasecmp(pd, pattern) == 0)
		return (1);
	}
    }
    return (0);
}