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 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 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 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 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 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 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 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