1 /* $NetBSD: postconf_user.c,v 1.4 2022/10/08 16:12:47 christos Exp $ */
2
3 /*++
4 /* NAME
5 /* postconf_user 3
6 /* SUMMARY
7 /* support for user-defined main.cf parameter names
8 /* SYNOPSIS
9 /* #include <postconf.h>
10 /*
11 /* void pcf_register_user_parameters()
12 /* DESCRIPTION
13 /* Postfix has multiple parameter name spaces: the global
14 /* main.cf parameter name space, and the local parameter name
15 /* space of each master.cf entry. Parameters in local name
16 /* spaces take precedence over global parameters.
17 /*
18 /* There are three categories of known parameter names: built-in,
19 /* service-defined (see postconf_service.c), and valid
20 /* user-defined.
21 /*
22 /* There are two categories of valid user-defined parameter
23 /* names:
24 /*
25 /* - Parameters whose user-defined-name appears in the value
26 /* of smtpd_restriction_classes in main.cf or master.cf.
27 /*
28 /* - Parameters whose $user-defined-name appear in the value
29 /* of "name=value" entries in main.cf or master.cf.
30 /*
31 /* - In both cases the parameters must have a
32 /* "user-defined-name=value" entry in main.cf or master.cf.
33 /*
34 /* Other user-defined parameter names are flagged as "unused".
35 /*
36 /* pcf_register_user_parameters() scans the global and per-service
37 /* name spaces for user-defined parameters and flags parameters
38 /* as "valid" in the global name space (pcf_param_table) or
39 /* in the per-service name space (valid_params).
40 /*
41 /* This function also invokes pcf_register_dbms_parameters() to
42 /* to instantiate legacy per-dbms parameters, and to examine
43 /* per-dbms configuration files. This is limited to the content
44 /* of global and local, built-in and per-service, parameters.
45 /* DIAGNOSTICS
46 /* Problems are reported to the standard error stream.
47 /* LICENSE
48 /* .ad
49 /* .fi
50 /* The Secure Mailer license must be distributed with this software.
51 /* AUTHOR(S)
52 /* Wietse Venema
53 /* IBM T.J. Watson Research
54 /* P.O. Box 704
55 /* Yorktown Heights, NY 10598, USA
56 /*
57 /* Wietse Venema
58 /* Google, Inc.
59 /* 111 8th Avenue
60 /* New York, NY 10011, USA
61 /*--*/
62
63 /* System library. */
64
65 #include <sys_defs.h>
66 #include <string.h>
67
68 /* Utility library. */
69
70 #include <msg.h>
71 #include <mymalloc.h>
72 #include <htable.h>
73 #include <mac_expand.h>
74 #include <stringops.h>
75
76 /* Global library. */
77
78 #include <mail_conf.h>
79 #include <mail_params.h>
80
81 /* Application-specific. */
82
83 #include <postconf.h>
84
85 /*
86 * Hash with all user-defined names in the global smtpd_restriction_classes
87 * value. This is used when validating "-o user-defined-name=value" entries
88 * in master.cf.
89 */
90 static HTABLE *pcf_rest_class_table;
91
92 /*
93 * SLMs.
94 */
95 #define STR(x) vstring_str(x)
96
97 /*
98 * Macros to make code with obscure constants more readable.
99 */
100 #define NO_SCAN_RESULT ((VSTRING *) 0)
101 #define NO_SCAN_FILTER ((char *) 0)
102
103 /* SCAN_USER_PARAMETER_VALUE - examine macro names in parameter value */
104
105 #define SCAN_USER_PARAMETER_VALUE(value, class, scope) do { \
106 PCF_PARAM_CTX _ctx; \
107 _ctx.local_scope = (scope); \
108 _ctx.param_class = (class); \
109 (void) mac_expand(NO_SCAN_RESULT, (value), MAC_EXP_FLAG_SCAN, \
110 NO_SCAN_FILTER, pcf_flag_user_parameter_wrapper, (void *) &_ctx); \
111 } while (0)
112
113 /* pcf_convert_user_parameter - get user-defined parameter string value */
114
pcf_convert_user_parameter(void * unused_ptr)115 static const char *pcf_convert_user_parameter(void *unused_ptr)
116 {
117 return (""); /* can't happen */
118 }
119
120 /* pcf_flag_user_parameter - flag user-defined name "valid" if it has name=value */
121
pcf_flag_user_parameter(const char * mac_name,int param_class,PCF_MASTER_ENT * local_scope)122 static const char *pcf_flag_user_parameter(const char *mac_name,
123 int param_class,
124 PCF_MASTER_ENT *local_scope)
125 {
126 const char *source = local_scope ? MASTER_CONF_FILE : MAIN_CONF_FILE;
127 int user_supplied = 0;
128
129 /*
130 * If the name=value exists in the local (or global) name space, update
131 * the local (or global) "valid" parameter name table.
132 *
133 * Do not "validate" user-defined parameters whose name appears only as
134 * macro expansion; this is how Postfix historically implements backwards
135 * compatibility after a feature name change.
136 */
137 if (local_scope && dict_get(local_scope->all_params, mac_name)) {
138 user_supplied = 1;
139 /* $name in master.cf references name=value in master.cf. */
140 if (PCF_PARAM_TABLE_LOCATE(local_scope->valid_names, mac_name) == 0) {
141 PCF_PARAM_TABLE_ENTER(local_scope->valid_names, mac_name,
142 param_class, PCF_PARAM_NO_DATA,
143 pcf_convert_user_parameter);
144 if (msg_verbose)
145 msg_info("$%s in %s:%s validates %s=value in %s:%s",
146 mac_name, MASTER_CONF_FILE,
147 local_scope->name_space,
148 mac_name, MASTER_CONF_FILE,
149 local_scope->name_space);
150 }
151 } else if (mail_conf_lookup(mac_name) != 0) {
152 user_supplied = 1;
153 /* $name in main/master.cf references name=value in main.cf. */
154 if (PCF_PARAM_TABLE_LOCATE(pcf_param_table, mac_name) == 0) {
155 PCF_PARAM_TABLE_ENTER(pcf_param_table, mac_name, param_class,
156 PCF_PARAM_NO_DATA, pcf_convert_user_parameter);
157 if (msg_verbose) {
158 if (local_scope)
159 msg_info("$%s in %s:%s validates %s=value in %s",
160 mac_name, MASTER_CONF_FILE,
161 local_scope->name_space,
162 mac_name, MAIN_CONF_FILE);
163 else
164 msg_info("$%s in %s validates %s=value in %s",
165 mac_name, MAIN_CONF_FILE,
166 mac_name, MAIN_CONF_FILE);
167 }
168 }
169 }
170 if (local_scope == 0) {
171 for (local_scope = pcf_master_table; local_scope->argv; local_scope++) {
172 if (local_scope->all_params != 0
173 && dict_get(local_scope->all_params, mac_name) != 0) {
174 user_supplied = 1;
175 /* $name in main.cf references name=value in master.cf. */
176 if (PCF_PARAM_TABLE_LOCATE(local_scope->valid_names, mac_name) == 0) {
177 PCF_PARAM_TABLE_ENTER(local_scope->valid_names, mac_name,
178 param_class, PCF_PARAM_NO_DATA,
179 pcf_convert_user_parameter);
180 if (msg_verbose)
181 msg_info("$%s in %s validates %s=value in %s:%s",
182 mac_name, MAIN_CONF_FILE,
183 mac_name, MASTER_CONF_FILE,
184 local_scope->name_space);
185 }
186 }
187 }
188 }
189
190 /*
191 * Warn about a $name that has no user-supplied explicit value or
192 * Postfix-supplied default value. We don't enforce this for legacy DBMS
193 * parameters because they exist only for backwards compatibility, so we
194 * don't bother to figure out which parameters come without defaults.
195 */
196 if (user_supplied == 0 && (param_class & PCF_PARAM_FLAG_DBMS) == 0
197 && PCF_PARAM_TABLE_LOCATE(pcf_param_table, mac_name) == 0)
198 msg_warn("%s/%s: undefined parameter: %s",
199 var_config_dir, source, mac_name);
200 return (0);
201 }
202
203 /* pcf_flag_user_parameter_wrapper - mac_expand call-back helper */
204
pcf_flag_user_parameter_wrapper(const char * mac_name,int unused_mode,void * context)205 static const char *pcf_flag_user_parameter_wrapper(const char *mac_name,
206 int unused_mode,
207 void *context)
208 {
209 PCF_PARAM_CTX *ctx = (PCF_PARAM_CTX *) context;
210
211 return (pcf_flag_user_parameter(mac_name, ctx->param_class, ctx->local_scope));
212 }
213
214 /* pcf_lookup_eval - generalized mail_conf_lookup_eval */
215
pcf_lookup_eval(const char * dict_name,const char * name)216 static const char *pcf_lookup_eval(const char *dict_name, const char *name)
217 {
218 const char *value;
219
220 #define RECURSIVE 1
221
222 if ((value = dict_lookup(dict_name, name)) != 0)
223 value = dict_eval(dict_name, value, RECURSIVE);
224 return (value);
225 }
226
227 /* pcf_scan_user_parameter_namespace - scan parameters in name space */
228
pcf_scan_user_parameter_namespace(const char * dict_name,PCF_MASTER_ENT * local_scope)229 static void pcf_scan_user_parameter_namespace(const char *dict_name,
230 PCF_MASTER_ENT *local_scope)
231 {
232 const char *myname = "pcf_scan_user_parameter_namespace";
233 const char *class_list;
234 char *saved_class_list;
235 char *cp;
236 DICT *dict;
237 char *param_name;
238 int how;
239 const char *cparam_name;
240 const char *cparam_value;
241 PCF_PARAM_NODE *node;
242 const char *source = local_scope ? MASTER_CONF_FILE : MAIN_CONF_FILE;
243
244 /*
245 * Flag parameter names in smtpd_restriction_classes as "valid", but only
246 * if they have a "name=value" entry. If we are in not in a local name
247 * space, update the global restriction class name table, so that we can
248 * query the global table from within a local master.cf name space.
249 */
250 if ((class_list = pcf_lookup_eval(dict_name, VAR_REST_CLASSES)) != 0) {
251 cp = saved_class_list = mystrdup(class_list);
252 while ((param_name = mystrtok(&cp, CHARS_COMMA_SP)) != 0) {
253 if (local_scope == 0
254 && htable_locate(pcf_rest_class_table, param_name) == 0)
255 htable_enter(pcf_rest_class_table, param_name, "");
256 pcf_flag_user_parameter(param_name, PCF_PARAM_FLAG_USER, local_scope);
257 }
258 myfree(saved_class_list);
259 }
260
261 /*
262 * For all "name=value" instances: a) if the name space is local and the
263 * name appears in the global restriction class table, flag the name as
264 * "valid" in the local name space; b) scan the value for macro
265 * expansions of unknown parameter names, and flag those parameter names
266 * as "valid" if they have a "name=value" entry.
267 *
268 * We delete name=value entries for read-only parameters, to maintain
269 * compatibility with Postfix programs that ignore such settings.
270 */
271 if ((dict = dict_handle(dict_name)) == 0)
272 msg_panic("%s: parameter dictionary %s not found",
273 myname, dict_name);
274 if (dict->sequence == 0)
275 msg_panic("%s: parameter dictionary %s has no iterator",
276 myname, dict_name);
277 for (how = DICT_SEQ_FUN_FIRST;
278 dict->sequence(dict, how, &cparam_name, &cparam_value) == 0;
279 how = DICT_SEQ_FUN_NEXT) {
280 if (local_scope != 0
281 && PCF_PARAM_TABLE_LOCATE(local_scope->valid_names, cparam_name) == 0
282 && htable_locate(pcf_rest_class_table, cparam_name) != 0)
283 PCF_PARAM_TABLE_ENTER(local_scope->valid_names, cparam_name,
284 PCF_PARAM_FLAG_USER, PCF_PARAM_NO_DATA,
285 pcf_convert_user_parameter);
286 if ((node = PCF_PARAM_TABLE_FIND(pcf_param_table, cparam_name)) != 0) {
287 if (PCF_READONLY_PARAMETER(node)) {
288 msg_warn("%s/%s: read-only parameter assignment: %s=%s",
289 var_config_dir, source, cparam_name, cparam_value);
290 /* Can't use dict_del() with Postfix<2.10 htable_sequence(). */
291 if (dict_del(dict, cparam_name) != 0)
292 msg_panic("%s: can't delete %s/%s parameter entry for %s",
293 myname, var_config_dir, source, cparam_name);
294 continue;
295 }
296 /* Re-label legacy parameter as user-defined, so it's printed. */
297 if (PCF_LEGACY_PARAMETER(node))
298 PCF_PARAM_CLASS_OVERRIDE(node, PCF_PARAM_FLAG_USER);
299 /* Skip "do not expand" parameters. */
300 if (PCF_RAW_PARAMETER(node))
301 continue;
302 }
303 SCAN_USER_PARAMETER_VALUE(cparam_value, PCF_PARAM_FLAG_USER, local_scope);
304 #ifdef LEGACY_DBMS_SUPPORT
305
306 /*
307 * Scan global or local parameters that are built-in or per-service
308 * (when node == 0, the parameter doesn't exist in the global
309 * namespace and therefore it can't be built-in or per-service).
310 */
311 if (node != 0
312 && (PCF_BUILTIN_PARAMETER(node) || PCF_SERVICE_PARAMETER(node)))
313 pcf_register_dbms_parameters(cparam_value, pcf_flag_user_parameter,
314 local_scope);
315 #endif
316 }
317 }
318
319 /* pcf_scan_default_parameter_values - scan parameters at implicit defaults */
320
pcf_scan_default_parameter_values(HTABLE * valid_params,const char * dict_name,PCF_MASTER_ENT * local_scope)321 static void pcf_scan_default_parameter_values(HTABLE *valid_params,
322 const char *dict_name,
323 PCF_MASTER_ENT *local_scope)
324 {
325 const char *myname = "pcf_scan_default_parameter_values";
326 PCF_PARAM_INFO **list;
327 PCF_PARAM_INFO **ht;
328 const char *param_value;
329
330 list = PCF_PARAM_TABLE_LIST(valid_params);
331 for (ht = list; *ht; ht++) {
332 /* Skip "do not expand" parameters. */
333 if (PCF_RAW_PARAMETER(PCF_PARAM_INFO_NODE(*ht)))
334 continue;
335 /* Skip parameters with a non-default value. */
336 if (dict_lookup(dict_name, PCF_PARAM_INFO_NAME(*ht)))
337 continue;
338 if ((param_value = pcf_convert_param_node(PCF_SHOW_DEFS, PCF_PARAM_INFO_NAME(*ht),
339 PCF_PARAM_INFO_NODE(*ht))) == 0)
340 msg_panic("%s: parameter %s has no default value",
341 myname, PCF_PARAM_INFO_NAME(*ht));
342 SCAN_USER_PARAMETER_VALUE(param_value, PCF_PARAM_FLAG_USER, local_scope);
343 /* No need to scan default values for legacy DBMS configuration. */
344 }
345 myfree((void *) list);
346 }
347
348 /* pcf_register_user_parameters - add parameters with user-defined names */
349
pcf_register_user_parameters(void)350 void pcf_register_user_parameters(void)
351 {
352 const char *myname = "pcf_register_user_parameters";
353 PCF_MASTER_ENT *masterp;
354 ARGV *argv;
355 char *arg;
356 char *aval;
357 int field;
358 char *saved_arg;
359 char *param_name;
360 char *param_value;
361 DICT *dict;
362
363 /*
364 * Sanity checks.
365 */
366 if (pcf_param_table == 0)
367 msg_panic("%s: global parameter table is not initialized", myname);
368 if (pcf_master_table == 0)
369 msg_panic("%s: master table is not initialized", myname);
370 if (pcf_rest_class_table != 0)
371 msg_panic("%s: restriction class table is already initialized", myname);
372
373 /*
374 * Initialize the table with global restriction class names.
375 */
376 pcf_rest_class_table = htable_create(1);
377
378 /*
379 * Initialize the per-service parameter name spaces.
380 */
381 for (masterp = pcf_master_table; (argv = masterp->argv) != 0; masterp++) {
382 for (field = PCF_MASTER_MIN_FIELDS; argv->argv[field] != 0; field++) {
383 arg = argv->argv[field];
384 if (arg[0] != '-' || strcmp(arg, "--") == 0)
385 break;
386 if (strchr(pcf_daemon_options_expecting_value, arg[1]) == 0
387 || (aval = argv->argv[field + 1]) == 0)
388 continue;
389 if (strcmp(arg, "-o") == 0) {
390 saved_arg = mystrdup(aval);
391 if (split_nameval(saved_arg, ¶m_name, ¶m_value) == 0)
392 dict_update(masterp->name_space, param_name, param_value);
393 myfree(saved_arg);
394 }
395 field += 1;
396 }
397 if ((dict = dict_handle(masterp->name_space)) != 0) {
398 masterp->all_params = dict;
399 masterp->valid_names = htable_create(1);
400 }
401 }
402
403 /*
404 * Scan the "-o parameter=value" instances in each master.cf name space.
405 */
406 for (masterp = pcf_master_table; masterp->argv != 0; masterp++)
407 if (masterp->all_params != 0)
408 pcf_scan_user_parameter_namespace(masterp->name_space, masterp);
409
410 /*
411 * Scan parameter values that are left at their defaults in the global
412 * name space. Some defaults contain the $name of an obsolete parameter
413 * for backwards compatibility purposes. We might warn that an explicit
414 * name=value is obsolete, but we must not warn that the parameter is
415 * unused.
416 */
417 pcf_scan_default_parameter_values(pcf_param_table, CONFIG_DICT,
418 (PCF_MASTER_ENT *) 0);
419
420 /*
421 * Scan the explicit name=value entries in the global name space.
422 */
423 pcf_scan_user_parameter_namespace(CONFIG_DICT, (PCF_MASTER_ENT *) 0);
424 }
425