xref: /netbsd-src/external/ibm-public/postfix/dist/src/postconf/postconf_user.c (revision 67b9b338a7386232ac596b5fd0cd5a9cc8a03c71)
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, &param_name, &param_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