/* \fBpostconf\fR [\fB-fMv\fR] [\fB-c \fIconfig_dir\fR]
/* [\fIservice ...\fR]
/*
+/* \fBManaging bounce message templates:\fR
+/*
+/* \fBpostconf\fR [\fB-btv\fR] [\fB-c \fIconfig_dir\fR] [\fItemplate_file\fR]
+/*
/* \fBManaging other configuration:\fR
/*
/* \fBpostconf\fR [\fB-aAlmv\fR] [\fB-c \fIconfig_dir\fR]
-/*
-/* \fBpostconf\fR [\fB-btv\fR] [\fB-c \fIconfig_dir\fR] [\fItemplate_file\fR]
/* DESCRIPTION
/* By default, the \fBpostconf\fR(1) command displays the
/* values of \fBmain.cf\fR configuration parameters, and warns
/* readability.
/*
/* If \fIservice ...\fR is specified, only the matching services
-/* will be output. For example, a \fIservice\fB of \fBinet\fR
+/* will be output. For example, a \fIservice\fR of \fBinet\fR
/* will match all services that listen on the network.
/*
/* Specify zero or more arguments, each with a \fIservice-type\fR
/* This feature is available with Postfix 2.6 and later.
/* DIAGNOSTICS
/* Problems are reported to the standard error stream.
-/* BUGS
-/* \fBpostconf\fR(1) may log "unused parameter" warnings for
-/* \fBmaster.cf\fR entries with "-o user-defined-name=value".
-/* Addressing this limitation requires support for per-service
-/* parameter name spaces.
/* ENVIRONMENT
/* .ad
/* .fi
#define FOLD_LINE (1<<11) /* fold long *.cf entries */
/*
- * Lookup table for in-core parameter info.
+ * Lookup table for global parameter info.
*
* XXX Change the value pointers from table indices into pointers to parameter
* objects with print methods.
HTABLE *param_table;
/*
- * Lookup table for master.cf info.
- *
- * XXX Replace by data structures with per-entry hashes for "-o name=value", so
- * that we can properly handle name=value definitions in per-service name
- * spaces.
+ * Global restriction_classes hash.
*/
-static ARGV **master_table;
+HTABLE *rest_class_table;
+
+ /*
+ * Lookup table for master.cf info. The table is null-terminated.
+ */
+typedef struct {
+ char *name_space; /* service.type, parameter name space */
+ ARGV *argv; /* terminator, or master.cf fields */
+ DICT *all_params; /* all name=value entries */
+ HTABLE *valid_names; /* "blessed" parameters */
+} PC_MASTER_ENT;
+
+static PC_MASTER_ENT *master_table;
/*
* Support for built-in parameters: declarations generated by scanning
*
* There are three categories of known parameters: built-in, service-defined
* (see previous comment), and valid user-defined. In addition there are
- * multiple name spaces: the global main.cf name space, and the per-service
- * name space of each master.cf entry.
+ * multiple name spaces: the global main.cf name space, and the local name
+ * space of each master.cf entry.
*
* There are two categories of valid user-defined parameters:
*
- * - Parameters whose name appears in the value of smtpd_restriction_classes in
- * main.cf, and whose name has a "name=value" entry in main.cf (todo:
- * support for creating names with "-o smtpd_restriction_classes=name"
- * within a master.cf per-service name space).
+ * - Parameters whose user-defined-name appears in the value of
+ * smtpd_restriction_classes in main.cf or master.cf, and that have a
+ * "user-defined-name=value" entry in main.cf or master.cf.
*
- * - Parameters whose $name (macro expansion) appears in the value of a
- * "name=value" entry in main.cf or master.cf of a "known" parameter, and
- * whose name has a "name=value" entry in main.cf (todo: master.cf).
+ * - Parameters whose $user-defined-name appears in the value of a "name=value"
+ * entry in main.cf or master.cf, and whose user-defined-name has a
+ * "name=value" entry in main.cf or master.cf.
*
- * All other user-defined parameters are invalid. We currently log a warning
- * for "name=value" entries in main.cf or master.cf whose $name does not
- * appear in the value of a main.cf or master.cf "name=value" entry of a
- * "known" parameter.
+ * Other user-defined parameters are flagged as "unused".
+ */
+
+ /*
+ * Global-scope valid user-defined parameter names. The local-scope valid
+ * user-defined names are kept in the table with master.cf entries.
*/
static char **user_param_table;
static ssize_t user_param_tablen;
path = concatenate(var_config_dir, "/", MASTER_CONF_FILE, (char *) 0);
/*
- * We can't use the master_ent routines in their current form. They
- * convert everything to internal form, and they skip disabled services.
- * We need to be able to show default fields as "-", and we need to know
- * about all service names so that we can generate service-dependent
+ * We can't use the master daemon's master_ent routines in their current
+ * form. They convert everything to internal form, and they skip disabled
+ * services.
+ *
+ * The postconf command needs to show default fields as "-", and needs to
+ * know about all service names so that it can generate service-dependent
* parameter names (transport-dependent etc.).
*/
#define MASTER_BLANKS " \t\r\n" /* XXX */
/*
* Initialize the in-memory master table.
*/
- master_table = (ARGV **) mymalloc(sizeof(*master_table));
+ master_table = (PC_MASTER_ENT *) mymalloc(sizeof(*master_table));
/*
* Skip blank lines and comment lines.
if ((fp = vstream_fopen(path, O_RDONLY, 0)) == 0)
msg_fatal("open %s: %m", path);
while (readlline(buf, fp, &line_count) != 0) {
- master_table = (ARGV **) myrealloc((char *) master_table,
+ master_table = (PC_MASTER_ENT *) myrealloc((char *) master_table,
(entry_count + 2) * sizeof(*master_table));
argv = argv_split(STR(buf), MASTER_BLANKS);
if (argv->argc < MASTER_FIELD_COUNT)
msg_fatal("file %s: line %d: bad field count", path, line_count);
- master_table[entry_count++] = argv;
+ master_table[entry_count].name_space =
+ concatenate(argv->argv[0], ".", argv->argv[1], (char *) 0);
+ master_table[entry_count].argv = argv;
+ master_table[entry_count].valid_names = 0;
+ master_table[entry_count].all_params = 0;
+ entry_count += 1;
}
- master_table[entry_count] = 0;
+
+ /*
+ * Null-terminate the master table and clean up.
+ */
+ master_table[entry_count].argv = 0;
vstream_fclose(fp);
myfree(path);
vstring_free(buf);
const PC_STRING_NV *sp;
const char *progname;
const char *service;
- ARGV **argvp;
+ PC_MASTER_ENT *masterp;
ARGV *argv;
const PC_SERVICE_DEF *sd;
* Extract service names from master.cf and generate service parameter
* information.
*/
- for (argvp = master_table; (argv = *argvp) != 0; argvp++) {
+ for (masterp = master_table; (argv = masterp->argv) != 0; masterp++) {
/*
* Add service parameters for message delivery transports or spawn
htable_enter(param_table, sp->name, (char *) sp);
}
-/* add_user_parameter - add one user-defined parameter name */
-
-static void add_user_parameter(const char *name)
-{
- /* XXX Merge this with check_parameter_value() */
- user_param_table = (char **)
- myrealloc((char *) user_param_table,
- (user_param_tablen + 1) * sizeof(*user_param_table));
- user_param_table[user_param_tablen] = mystrdup(name);
- user_param_tablen += 1;
-}
-
-/* scan_user_parameter_value - extract macro names from parameter value */
+/* scan_user_parameter_value - examine macro names in parameter value */
#define NO_SCAN_RESULT ((VSTRING *) 0)
#define NO_SCAN_FILTER ((char *) 0)
#define NO_SCAN_MODE (0)
-#define NO_SCAN_CONTEXT ((char *) 0)
-#define scan_user_parameter_value(value) do { \
+#define scan_user_parameter_value(value, context) do { \
(void) mac_expand(NO_SCAN_RESULT, (value), MAC_EXP_FLAG_SCAN, \
- NO_SCAN_FILTER, check_user_parameter, NO_SCAN_CONTEXT); \
+ NO_SCAN_FILTER, check_user_parameter, (context)); \
} while (0)
-/* check_user_parameter - try to promote user-defined parameter */
+/* check_user_parameter - promote user-defined name if it has name=value */
static const char *check_user_parameter(const char *mac_name,
int unused_mode,
- char *unused_context)
+ char *context)
{
- const char *mac_value;
+ PC_MASTER_ENT *local_scope = (PC_MASTER_ENT *) context;
/*
* Promote only user-defined parameters with an explicit "name=value"
- * definition in main.cf (todo: master.cf). Do not promote parameters
- * whose name appears only as a macro expansion; this is how Postfix
- * implements backwards compatibility after a feature name change.
+ * definition. If the name=value exists in the local scope, update the
+ * local "valid" parameter name table, otherwise update the global one.
+ *
+ * Do not promote user-defined parameters whose name appears only as macro
+ * expansion; this is how Postfix implements backwards compatibility
+ * after a feature name change.
*
- * Skip parameters that are already in the param_table hash.
+ * Skip global names that are already in the param_table hash.
*/
- if (htable_locate(param_table, mac_name) == 0) {
- mac_value = mail_conf_lookup(mac_name);
- if (mac_value != 0) {
- add_user_parameter(mac_name);
- /* Promote parameter names recursively. */
- scan_user_parameter_value(mac_value);
+ if (local_scope && dict_get(local_scope->all_params, mac_name)) {
+ if (htable_locate(local_scope->valid_names, mac_name) == 0)
+ htable_enter(local_scope->valid_names, mac_name, "");
+ } else if (htable_locate(param_table, mac_name) == 0) {
+ if (mail_conf_lookup(mac_name) != 0) {
+ user_param_table = (char **)
+ myrealloc((char *) user_param_table,
+ (user_param_tablen + 1) * sizeof(*user_param_table));
+ user_param_table[user_param_tablen] = mystrdup(mac_name);
+ user_param_tablen += 1;
}
}
return (0);
}
-/* add_user_parameters - add parameters with user-defined names */
+/* pc_lookup_eval - generalized mail_conf_lookup_eval */
-static void add_user_parameters(void)
+static const char *pc_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);
+}
+
+/* scan_user_parameter_namespace - scan parameters in name space */
+
+static void scan_user_parameter_namespace(const char *dict_name,
+ PC_MASTER_ENT *local_scope)
+{
+ const char *myname = "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;
- HTABLE_INFO **ht_info;
- HTABLE_INFO **ht;
- ARGV **argvp;
+
+ /*
+ * Add parameters whose names are defined with smtpd_restriction_classes,
+ * but only if they have a "name=value" entry. If we are in the global
+ * scope, update the global restriction class name table, so that we can
+ * query the table from within a local master.cf name space.
+ */
+ if ((class_list = pc_lookup_eval(dict_name, VAR_REST_CLASSES)) != 0) {
+ cp = saved_class_list = mystrdup(class_list);
+ while ((param_name = mystrtok(&cp, ", \t\r\n")) != 0) {
+ if (local_scope == 0
+ && htable_locate(rest_class_table, param_name) == 0)
+ htable_enter(rest_class_table, param_name, "");
+ check_user_parameter(param_name, NO_SCAN_MODE,
+ (char *) local_scope);
+ }
+ myfree(saved_class_list);
+ }
+
+ /*
+ * For all "name=value" instances: a) if the scope is local and the name
+ * appears in the global restriction class table, flag the name as
+ * "valid" in the local scope; 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.
+ */
+ 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
+ && htable_locate(local_scope->valid_names, cparam_name) == 0
+ && htable_locate(rest_class_table, cparam_name) != 0)
+ htable_enter(local_scope->valid_names, cparam_name, "");
+ scan_user_parameter_value(cparam_value, (char *) local_scope);
+ }
+}
+
+/* add_user_parameters - add parameters with user-defined names */
+
+static void add_user_parameters(void)
+{
+ PC_MASTER_ENT *masterp;
ARGV *argv;
char *arg;
int field;
char *saved_arg;
char *param_name;
char *param_value;
+ DICT *dict;
char **up;
/*
- * Initialize the table with user-defined parameter names and values.
+ * Initialize the global table with user-defined parameter names, and the
+ * table with global restriction class names.
*/
user_param_table = (char **) mymalloc(1);
user_param_tablen = 0;
+ rest_class_table = htable_create(1);
/*
- * Add parameters whose names are defined with smtpd_restriction_classes,
- * but only if they have a "name=value" entry in main.cf.
- *
- * XXX It is possible that a user-defined parameter is defined in master.cf
- * with "-o smtpd_restriction_classes=name -o name=value". This requires
- * name space support for master.cf entries. Without this, we always log
- * "unused parameter" warnings for "-o user-defined-name=value" entries.
+ * Scan the explicit name=value entries in the global name space.
*/
- if ((class_list = mail_conf_lookup_eval(VAR_REST_CLASSES)) != 0) {
- cp = saved_class_list = mystrdup(class_list);
- while ((param_name = mystrtok(&cp, ", \t\r\n")) != 0)
- check_user_parameter(param_name, NO_SCAN_MODE, NO_SCAN_CONTEXT);
- myfree(saved_class_list);
- }
+ scan_user_parameter_namespace(CONFIG_DICT, (PC_MASTER_ENT *) 0);
/*
- * Parse the "name=value" instances in main.cf of built-in and service
- * parameters only, look for macro expansions of unknown parameter names,
- * and flag those parameter names as "known" if they have a "name=value"
- * entry in main.cf. Recursively apply the procedure to the values of
- * newly-flagged parameters.
+ * Scan the "-o parameter=value" instances in each master.cf name space.
*/
- for (ht_info = ht = htable_list(param_table); *ht; ht++)
- if ((cparam_value = mail_conf_lookup(ht[0]->key)) != 0)
- scan_user_parameter_value(cparam_value);
- myfree((char *) ht_info);
-
- /*
- * Parse all "-o parameter=value" instances in master.cf, look for macro
- * expansions of unknown parameter names, and flag those parameter names
- * as "known" if they have a "name=value" entry in main.cf (XXX todo: in
- * master.cf; without master.cf name space support we always log "unused
- * parameter" warnings for "-o user-defined-name=value" entries).
- */
- for (argvp = master_table; (argv = *argvp) != 0; argvp++) {
+ for (masterp = master_table; (argv = masterp->argv) != 0; masterp++) {
for (field = MASTER_FIELD_COUNT; argv->argv[field] != 0; field++) {
arg = argv->argv[field];
if (arg[0] != '-')
if (strcmp(arg, "-o") == 0 && (arg = argv->argv[field + 1]) != 0) {
saved_arg = mystrdup(arg);
if (split_nameval(saved_arg, ¶m_name, ¶m_value) == 0)
- scan_user_parameter_value(param_value);
+ 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_user_parameter_namespace(masterp->name_space, masterp);
+ }
}
/*
static void show_master(int mode, char **filters)
{
- ARGV **argvp;
+ PC_MASTER_ENT *masterp;
ARGV *argv;
- VSTRING *service_name = 0;
ARGV *service_filter = 0;
/*
* Initialize the service filter.
*/
- if (filters[0]) {
- service_name = vstring_alloc(10);
+ if (filters[0])
service_filter = match_service_init_argv(filters);
- }
/*
* Iterate over the master table.
*/
- for (argvp = master_table; (argv = *argvp) != 0; argvp++) {
- if (service_filter) {
- vstring_sprintf(service_name, "%s.%s",
- argv->argv[0], argv->argv[1]);
- if (match_service_match(service_filter, STR(service_name)) == 0)
- continue;
- }
- print_master_line(mode, argv);
- }
- if (service_filter) {
+ for (masterp = master_table; (argv = masterp->argv) != 0; masterp++)
+ if (service_filter == 0
+ || match_service_match(service_filter, masterp->name_space) != 0)
+ print_master_line(mode, argv);
+ if (service_filter != 0)
argv_free(service_filter);
- vstring_free(service_name);
- }
}
/* show_sasl - show SASL plug-in types */
argv_free(sasl_argv);
}
-/* flag_unused_main_parameters - warn about unused parameters */
+/* flag_unused_parameters - warn about unused parameters */
-static void flag_unused_main_parameters(void)
+static void flag_unused_parameters(DICT *dict, const char *conf_name,
+ PC_MASTER_ENT *local_scope)
{
- const char *myname = "flag_unused_main_parameters";
- DICT *dict;
+ const char *myname = "flag_unused_parameters";
const char *param_name;
const char *param_value;
int how;
/*
- * Iterate over all main.cf entries, and flag parameter names that aren't
- * used anywhere. Show the warning message at the end of the output.
+ * Iterate over all entries, and flag parameter names that aren't used
+ * anywhere. Show the warning message at the end of the output.
*/
- if ((dict = dict_handle(CONFIG_DICT)) == 0)
- msg_panic("%s: parameter dictionary %s not found",
- myname, CONFIG_DICT);
if (dict->sequence == 0)
msg_panic("%s: parameter dictionary %s has no iterator",
- myname, CONFIG_DICT);
+ myname, conf_name);
for (how = DICT_SEQ_FUN_FIRST;
dict->sequence(dict, how, ¶m_name, ¶m_value) == 0;
how = DICT_SEQ_FUN_NEXT) {
- if (htable_locate(param_table, param_name) == 0) {
+ if (htable_locate(param_table, param_name) == 0
+ && (local_scope == 0
+ || htable_locate(local_scope->valid_names, param_name) == 0)) {
vstream_fflush(VSTREAM_OUT);
- msg_warn("%s/" MAIN_CONF_FILE ": unused parameter: %s=%s",
- var_config_dir, param_name, param_value);
+ msg_warn("%s/%s: unused parameter: %s=%s",
+ var_config_dir, conf_name, param_name, param_value);
}
}
}
+/* flag_unused_main_parameters - warn about unused parameters */
+
+static void flag_unused_main_parameters(void)
+{
+ const char *myname = "flag_unused_main_parameters";
+ DICT *dict;
+
+ /*
+ * Iterate over all main.cf entries, and flag parameter names that aren't
+ * used anywhere.
+ */
+ if ((dict = dict_handle(CONFIG_DICT)) == 0)
+ msg_panic("%s: parameter dictionary %s not found",
+ myname, CONFIG_DICT);
+ flag_unused_parameters(dict, MAIN_CONF_FILE, (PC_MASTER_ENT *) 0);
+}
+
/* flag_unused_master_parameters - warn about unused parameters */
static void flag_unused_master_parameters(void)
{
- ARGV **argvp;
- ARGV *argv;
- int field;
- char *arg;
- char *saved_arg;
- char *param_name;
- char *param_value;
+ PC_MASTER_ENT *masterp;
+ DICT *dict;
/*
* Iterate over all master.cf entries, and flag parameter names that
- * aren't used anywhere. Show the warning message at the end of the
- * output.
- *
- * XXX It is possible that a user-defined parameter is defined in master.cf
- * with "-o smtpd_restriction_classes=name", or with "-o name1=value1"
- * and then used in a "-o name2=$name1" macro expansion in that same
- * master.cf entry. To handle this we need to give each master.cf entry
- * its own name space. Until then, we always log "unused parameter"
- * warnings for "-o user-defined-name=value" entries.
+ * aren't used anywhere.
*/
- for (argvp = master_table; (argv = *argvp) != 0; argvp++) {
- for (field = MASTER_FIELD_COUNT; argv->argv[field] != 0; field++) {
- arg = argv->argv[field];
- if (arg[0] != '-')
- break;
- if (strcmp(arg, "-o") == 0 && (arg = argv->argv[field + 1]) != 0) {
- saved_arg = mystrdup(arg);
- if (split_nameval(saved_arg, ¶m_name, ¶m_value) == 0
- && htable_locate(param_table, param_name) == 0) {
- vstream_fflush(VSTREAM_OUT);
- msg_warn("%s/" MASTER_CONF_FILE ": unused parameter: %s=%s",
- var_config_dir, param_name, param_value);
- }
- myfree(saved_arg);
- field += 1;
- }
- }
- }
+ for (masterp = master_table; masterp->argv != 0; masterp++)
+ if ((dict = masterp->all_params) != 0)
+ flag_unused_parameters(dict, MASTER_CONF_FILE, masterp);
}
MAIL_VERSION_STAMP_DECLARE;