diff options
Diffstat (limited to 'src/postconf/postconf_user.c')
-rw-r--r-- | src/postconf/postconf_user.c | 422 |
1 files changed, 422 insertions, 0 deletions
diff --git a/src/postconf/postconf_user.c b/src/postconf/postconf_user.c new file mode 100644 index 0000000..5942ec0 --- /dev/null +++ b/src/postconf/postconf_user.c @@ -0,0 +1,422 @@ +/*++ +/* NAME +/* postconf_user 3 +/* SUMMARY +/* support for user-defined main.cf parameter names +/* SYNOPSIS +/* #include <postconf.h> +/* +/* void pcf_register_user_parameters() +/* DESCRIPTION +/* Postfix has multiple parameter name spaces: the global +/* main.cf parameter name space, and the local parameter name +/* space of each master.cf entry. Parameters in local name +/* spaces take precedence over global parameters. +/* +/* There are three categories of known parameter names: built-in, +/* service-defined (see postconf_service.c), and valid +/* user-defined. +/* +/* There are two categories of valid user-defined parameter +/* names: +/* +/* - Parameters whose user-defined-name appears in the value +/* of smtpd_restriction_classes in main.cf or master.cf. +/* +/* - Parameters whose $user-defined-name appear in the value +/* of "name=value" entries in main.cf or master.cf. +/* +/* - In both cases the parameters must have a +/* "user-defined-name=value" entry in main.cf or master.cf. +/* +/* Other user-defined parameter names are flagged as "unused". +/* +/* pcf_register_user_parameters() scans the global and per-service +/* name spaces for user-defined parameters and flags parameters +/* as "valid" in the global name space (pcf_param_table) or +/* in the per-service name space (valid_params). +/* +/* This function also invokes pcf_register_dbms_parameters() to +/* to instantiate legacy per-dbms parameters, and to examine +/* per-dbms configuration files. This is limited to the content +/* of global and local, built-in and per-service, parameters. +/* DIAGNOSTICS +/* Problems are reported to the standard error stream. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <htable.h> +#include <mac_expand.h> +#include <stringops.h> + +/* Global library. */ + +#include <mail_conf.h> +#include <mail_params.h> + +/* Application-specific. */ + +#include <postconf.h> + + /* + * Hash with all user-defined names in the global smtpd_restriction_classes + * value. This is used when validating "-o user-defined-name=value" entries + * in master.cf. + */ +static HTABLE *pcf_rest_class_table; + + /* + * SLMs. + */ +#define STR(x) vstring_str(x) + + /* + * Macros to make code with obscure constants more readable. + */ +#define NO_SCAN_RESULT ((VSTRING *) 0) +#define NO_SCAN_FILTER ((char *) 0) + +/* SCAN_USER_PARAMETER_VALUE - examine macro names in parameter value */ + +#define SCAN_USER_PARAMETER_VALUE(value, class, scope) do { \ + PCF_PARAM_CTX _ctx; \ + _ctx.local_scope = (scope); \ + _ctx.param_class = (class); \ + (void) mac_expand(NO_SCAN_RESULT, (value), MAC_EXP_FLAG_SCAN, \ + NO_SCAN_FILTER, pcf_flag_user_parameter_wrapper, (void *) &_ctx); \ +} while (0) + +/* pcf_convert_user_parameter - get user-defined parameter string value */ + +static const char *pcf_convert_user_parameter(void *unused_ptr) +{ + return (""); /* can't happen */ +} + +/* pcf_flag_user_parameter - flag user-defined name "valid" if it has name=value */ + +static const char *pcf_flag_user_parameter(const char *mac_name, + int param_class, + PCF_MASTER_ENT *local_scope) +{ + const char *source = local_scope ? MASTER_CONF_FILE : MAIN_CONF_FILE; + int user_supplied = 0; + + /* + * If the name=value exists in the local (or global) name space, update + * the local (or global) "valid" parameter name table. + * + * Do not "validate" user-defined parameters whose name appears only as + * macro expansion; this is how Postfix historically implements backwards + * compatibility after a feature name change. + */ + if (local_scope && dict_get(local_scope->all_params, mac_name)) { + user_supplied = 1; + /* $name in master.cf references name=value in master.cf. */ + if (PCF_PARAM_TABLE_LOCATE(local_scope->valid_names, mac_name) == 0) { + PCF_PARAM_TABLE_ENTER(local_scope->valid_names, mac_name, + param_class, PCF_PARAM_NO_DATA, + pcf_convert_user_parameter); + if (msg_verbose) + msg_info("$%s in %s:%s validates %s=value in %s:%s", + mac_name, MASTER_CONF_FILE, + local_scope->name_space, + mac_name, MASTER_CONF_FILE, + local_scope->name_space); + } + } else if (mail_conf_lookup(mac_name) != 0) { + user_supplied = 1; + /* $name in main/master.cf references name=value in main.cf. */ + if (PCF_PARAM_TABLE_LOCATE(pcf_param_table, mac_name) == 0) { + PCF_PARAM_TABLE_ENTER(pcf_param_table, mac_name, param_class, + PCF_PARAM_NO_DATA, pcf_convert_user_parameter); + if (msg_verbose) { + if (local_scope) + msg_info("$%s in %s:%s validates %s=value in %s", + mac_name, MASTER_CONF_FILE, + local_scope->name_space, + mac_name, MAIN_CONF_FILE); + else + msg_info("$%s in %s validates %s=value in %s", + mac_name, MAIN_CONF_FILE, + mac_name, MAIN_CONF_FILE); + } + } + } + if (local_scope == 0) { + for (local_scope = pcf_master_table; local_scope->argv; local_scope++) { + if (local_scope->all_params != 0 + && dict_get(local_scope->all_params, mac_name) != 0) { + user_supplied = 1; + /* $name in main.cf references name=value in master.cf. */ + if (PCF_PARAM_TABLE_LOCATE(local_scope->valid_names, mac_name) == 0) { + PCF_PARAM_TABLE_ENTER(local_scope->valid_names, mac_name, + param_class, PCF_PARAM_NO_DATA, + pcf_convert_user_parameter); + if (msg_verbose) + msg_info("$%s in %s validates %s=value in %s:%s", + mac_name, MAIN_CONF_FILE, + mac_name, MASTER_CONF_FILE, + local_scope->name_space); + } + } + } + } + + /* + * Warn about a $name that has no user-supplied explicit value or + * Postfix-supplied default value. We don't enforce this for legacy DBMS + * parameters because they exist only for backwards compatibility, so we + * don't bother to figure out which parameters come without defaults. + */ + if (user_supplied == 0 && (param_class & PCF_PARAM_FLAG_DBMS) == 0 + && PCF_PARAM_TABLE_LOCATE(pcf_param_table, mac_name) == 0) + msg_warn("%s/%s: undefined parameter: %s", + var_config_dir, source, mac_name); + return (0); +} + +/* pcf_flag_user_parameter_wrapper - mac_expand call-back helper */ + +static const char *pcf_flag_user_parameter_wrapper(const char *mac_name, + int unused_mode, + void *context) +{ + PCF_PARAM_CTX *ctx = (PCF_PARAM_CTX *) context; + + return (pcf_flag_user_parameter(mac_name, ctx->param_class, ctx->local_scope)); +} + +/* pcf_lookup_eval - generalized mail_conf_lookup_eval */ + +static const char *pcf_lookup_eval(const char *dict_name, const char *name) +{ + const char *value; + +#define RECURSIVE 1 + + if ((value = dict_lookup(dict_name, name)) != 0) + value = dict_eval(dict_name, value, RECURSIVE); + return (value); +} + +/* pcf_scan_user_parameter_namespace - scan parameters in name space */ + +static void pcf_scan_user_parameter_namespace(const char *dict_name, + PCF_MASTER_ENT *local_scope) +{ + const char *myname = "pcf_scan_user_parameter_namespace"; + const char *class_list; + char *saved_class_list; + char *cp; + DICT *dict; + char *param_name; + int how; + const char *cparam_name; + const char *cparam_value; + PCF_PARAM_NODE *node; + const char *source = local_scope ? MASTER_CONF_FILE : MAIN_CONF_FILE; + + /* + * Flag parameter names in smtpd_restriction_classes as "valid", but only + * if they have a "name=value" entry. If we are in not in a local name + * space, update the global restriction class name table, so that we can + * query the global table from within a local master.cf name space. + */ + if ((class_list = pcf_lookup_eval(dict_name, VAR_REST_CLASSES)) != 0) { + cp = saved_class_list = mystrdup(class_list); + while ((param_name = mystrtok(&cp, CHARS_COMMA_SP)) != 0) { + if (local_scope == 0 + && htable_locate(pcf_rest_class_table, param_name) == 0) + htable_enter(pcf_rest_class_table, param_name, ""); + pcf_flag_user_parameter(param_name, PCF_PARAM_FLAG_USER, local_scope); + } + myfree(saved_class_list); + } + + /* + * For all "name=value" instances: a) if the name space is local and the + * name appears in the global restriction class table, flag the name as + * "valid" in the local name space; b) scan the value for macro + * expansions of unknown parameter names, and flag those parameter names + * as "valid" if they have a "name=value" entry. + * + * We delete name=value entries for read-only parameters, to maintain + * compatibility with Postfix programs that ignore such settings. + */ + if ((dict = dict_handle(dict_name)) == 0) + msg_panic("%s: parameter dictionary %s not found", + myname, dict_name); + if (dict->sequence == 0) + msg_panic("%s: parameter dictionary %s has no iterator", + myname, dict_name); + for (how = DICT_SEQ_FUN_FIRST; + dict->sequence(dict, how, &cparam_name, &cparam_value) == 0; + how = DICT_SEQ_FUN_NEXT) { + if (local_scope != 0 + && PCF_PARAM_TABLE_LOCATE(local_scope->valid_names, cparam_name) == 0 + && htable_locate(pcf_rest_class_table, cparam_name) != 0) + PCF_PARAM_TABLE_ENTER(local_scope->valid_names, cparam_name, + PCF_PARAM_FLAG_USER, PCF_PARAM_NO_DATA, + pcf_convert_user_parameter); + if ((node = PCF_PARAM_TABLE_FIND(pcf_param_table, cparam_name)) != 0) { + if (PCF_READONLY_PARAMETER(node)) { + msg_warn("%s/%s: read-only parameter assignment: %s=%s", + var_config_dir, source, cparam_name, cparam_value); + /* Can't use dict_del() with Postfix<2.10 htable_sequence(). */ + if (dict_del(dict, cparam_name) != 0) + msg_panic("%s: can't delete %s/%s parameter entry for %s", + myname, var_config_dir, source, cparam_name); + continue; + } + /* Re-label legacy parameter as user-defined, so it's printed. */ + if (PCF_LEGACY_PARAMETER(node)) + PCF_PARAM_CLASS_OVERRIDE(node, PCF_PARAM_FLAG_USER); + /* Skip "do not expand" parameters. */ + if (PCF_RAW_PARAMETER(node)) + continue; + } + SCAN_USER_PARAMETER_VALUE(cparam_value, PCF_PARAM_FLAG_USER, local_scope); +#ifdef LEGACY_DBMS_SUPPORT + + /* + * Scan global or local parameters that are built-in or per-service + * (when node == 0, the parameter doesn't exist in the global + * namespace and therefore it can't be built-in or per-service). + */ + if (node != 0 + && (PCF_BUILTIN_PARAMETER(node) || PCF_SERVICE_PARAMETER(node))) + pcf_register_dbms_parameters(cparam_value, pcf_flag_user_parameter, + local_scope); +#endif + } +} + +/* pcf_scan_default_parameter_values - scan parameters at implicit defaults */ + +static void pcf_scan_default_parameter_values(HTABLE *valid_params, + const char *dict_name, + PCF_MASTER_ENT *local_scope) +{ + const char *myname = "pcf_scan_default_parameter_values"; + PCF_PARAM_INFO **list; + PCF_PARAM_INFO **ht; + const char *param_value; + + list = PCF_PARAM_TABLE_LIST(valid_params); + for (ht = list; *ht; ht++) { + /* Skip "do not expand" parameters. */ + if (PCF_RAW_PARAMETER(PCF_PARAM_INFO_NODE(*ht))) + continue; + /* Skip parameters with a non-default value. */ + if (dict_lookup(dict_name, PCF_PARAM_INFO_NAME(*ht))) + continue; + if ((param_value = pcf_convert_param_node(PCF_SHOW_DEFS, PCF_PARAM_INFO_NAME(*ht), + PCF_PARAM_INFO_NODE(*ht))) == 0) + msg_panic("%s: parameter %s has no default value", + myname, PCF_PARAM_INFO_NAME(*ht)); + SCAN_USER_PARAMETER_VALUE(param_value, PCF_PARAM_FLAG_USER, local_scope); + /* No need to scan default values for legacy DBMS configuration. */ + } + myfree((void *) list); +} + +/* pcf_register_user_parameters - add parameters with user-defined names */ + +void pcf_register_user_parameters(void) +{ + const char *myname = "pcf_register_user_parameters"; + PCF_MASTER_ENT *masterp; + ARGV *argv; + char *arg; + char *aval; + int field; + char *saved_arg; + char *param_name; + char *param_value; + DICT *dict; + + /* + * Sanity checks. + */ + if (pcf_param_table == 0) + msg_panic("%s: global parameter table is not initialized", myname); + if (pcf_master_table == 0) + msg_panic("%s: master table is not initialized", myname); + if (pcf_rest_class_table != 0) + msg_panic("%s: restriction class table is already initialized", myname); + + /* + * Initialize the table with global restriction class names. + */ + pcf_rest_class_table = htable_create(1); + + /* + * Initialize the per-service parameter name spaces. + */ + for (masterp = pcf_master_table; (argv = masterp->argv) != 0; masterp++) { + for (field = PCF_MASTER_MIN_FIELDS; argv->argv[field] != 0; field++) { + arg = argv->argv[field]; + if (arg[0] != '-' || strcmp(arg, "--") == 0) + break; + if (strchr(pcf_daemon_options_expecting_value, arg[1]) == 0 + || (aval = argv->argv[field + 1]) == 0) + continue; + if (strcmp(arg, "-o") == 0) { + saved_arg = mystrdup(aval); + if (split_nameval(saved_arg, ¶m_name, ¶m_value) == 0) + dict_update(masterp->name_space, param_name, param_value); + myfree(saved_arg); + } + field += 1; + } + if ((dict = dict_handle(masterp->name_space)) != 0) { + masterp->all_params = dict; + masterp->valid_names = htable_create(1); + } + } + + /* + * Scan the "-o parameter=value" instances in each master.cf name space. + */ + for (masterp = pcf_master_table; masterp->argv != 0; masterp++) + if (masterp->all_params != 0) + pcf_scan_user_parameter_namespace(masterp->name_space, masterp); + + /* + * Scan parameter values that are left at their defaults in the global + * name space. Some defaults contain the $name of an obsolete parameter + * for backwards compatibility purposes. We might warn that an explicit + * name=value is obsolete, but we must not warn that the parameter is + * unused. + */ + pcf_scan_default_parameter_values(pcf_param_table, CONFIG_DICT, + (PCF_MASTER_ENT *) 0); + + /* + * Scan the explicit name=value entries in the global name space. + */ + pcf_scan_user_parameter_namespace(CONFIG_DICT, (PCF_MASTER_ENT *) 0); +} |