1 /* $NetBSD: postconf_dbms.c,v 1.5 2023/12/23 20:30:44 christos Exp $ */ 2 3 /*++ 4 /* NAME 5 /* postconf_dbms 3 6 /* SUMMARY 7 /* legacy support for database-defined main.cf parameter names 8 /* SYNOPSIS 9 /* #include <postconf.h> 10 /* 11 /* void pcf_register_dbms_parameters(param_value, flag_parameter, 12 /* local_scope) 13 /* const char *param_value; 14 /* const char *(flag_parameter) (const char *, int, PCF_MASTER_ENT *); 15 /* PCF_MASTER_ENT *local_scope; 16 /* DESCRIPTION 17 /* This module implements legacy support for database configuration 18 /* where main.cf parameter names are generated by prepending 19 /* the database name to a database-defined suffix. 20 /* 21 /* Arguments: 22 /* .IP param_value 23 /* A parameter value to be searched for "type:table" strings. 24 /* When a database type is found that supports legacy-style 25 /* configuration, the table name is combined with each of the 26 /* database-defined suffixes to generate candidate parameter 27 /* names for that database type; if the table name specifies 28 /* a client configuration file, that file is scanned for unused 29 /* parameter settings. 30 /* .IP flag_parameter 31 /* A function that takes as arguments a candidate parameter 32 /* name, parameter flags, and a PCF_MASTER_ENT pointer. The 33 /* function will flag the parameter as "used" if it has a 34 /* "name=value" entry in the local or global namespace. 35 /* .IP local_scope 36 /* The local namespace. 37 /* DIAGNOSTICS 38 /* No explicit diagnostics. 39 /* LICENSE 40 /* .ad 41 /* .fi 42 /* The Secure Mailer license must be distributed with this software. 43 /* AUTHOR(S) 44 /* Wietse Venema 45 /* IBM T.J. Watson Research 46 /* P.O. Box 704 47 /* Yorktown Heights, NY 10598, USA 48 /* 49 /* Wietse Venema 50 /* Google, Inc. 51 /* 111 8th Avenue 52 /* New York, NY 10011, USA 53 /*--*/ 54 55 /* System library. */ 56 57 #include <sys_defs.h> 58 #include <sys/stat.h> 59 #include <errno.h> 60 #include <string.h> 61 62 /* Utility library. */ 63 64 #include <stringops.h> 65 #include <split_at.h> 66 #include <mac_expand.h> 67 #include <dict.h> 68 #include <msg.h> 69 #include <mymalloc.h> 70 71 /* Global library. */ 72 73 #include <mail_conf.h> 74 #include <mail_params.h> 75 #include <dict_ht.h> 76 #include <dict_proxy.h> 77 #include <dict_ldap.h> 78 #include <dict_mysql.h> 79 #include <dict_pgsql.h> 80 #include <dict_sqlite.h> 81 #include <dict_memcache.h> 82 #include <dict_regexp.h> 83 #include <dict_pcre.h> 84 85 /* Application-specific. */ 86 87 #include <postconf.h> 88 89 /* 90 * SLMs. 91 */ 92 #define STR(x) vstring_str(x) 93 94 #ifdef LEGACY_DBMS_SUPPORT 95 96 /* 97 * The legacy database interface automagically instantiates a list of 98 * parameters by prepending the table name to database-specific suffixes. 99 */ 100 101 /* See ldap_table(5). */ 102 103 static const char *pcf_ldap_suffixes[] = { 104 #include "pcf_ldap_suffixes.h" 105 0, 106 }; 107 108 /* See mysql_table(5). */ 109 110 static const char *pcf_mysql_suffixes[] = { 111 #include "pcf_mysql_suffixes.h" 112 0, 113 }; 114 115 /* See pgsql_table(5). */ 116 117 static const char *pcf_pgsql_suffixes[] = { 118 #include "pcf_pgsql_suffixes.h" 119 0, 120 }; 121 122 /* See sqlite_table(5). */ 123 124 static const char *pcf_sqlite_suffixes[] = { 125 #include "pcf_sqlite_suffixes.h" 126 0, 127 }; 128 129 /* See memcache_table(5). */ 130 131 static const char *pcf_memcache_suffixes[] = { 132 #include "pcf_memcache_suffixes.h" 133 0, 134 }; 135 136 /* 137 * Bundle up the database types and their suffix lists. 138 */ 139 typedef struct { 140 const char *db_type; 141 int db_class; 142 const char **db_suffixes; 143 } PCF_DBMS_INFO; 144 145 #define PCF_DBMS_CLASS_CLIENT (1) /* DB name is client config path */ 146 #define PCF_DBMS_CLASS_REGEX (2) /* DB name contains regex patterns */ 147 148 static const PCF_DBMS_INFO pcf_dbms_info[] = { 149 {DICT_TYPE_LDAP, PCF_DBMS_CLASS_CLIENT, pcf_ldap_suffixes}, 150 {DICT_TYPE_MYSQL, PCF_DBMS_CLASS_CLIENT, pcf_mysql_suffixes}, 151 {DICT_TYPE_PGSQL, PCF_DBMS_CLASS_CLIENT, pcf_pgsql_suffixes}, 152 {DICT_TYPE_SQLITE, PCF_DBMS_CLASS_CLIENT, pcf_sqlite_suffixes}, 153 {DICT_TYPE_MEMCACHE, PCF_DBMS_CLASS_CLIENT, pcf_memcache_suffixes}, 154 {DICT_TYPE_REGEXP, PCF_DBMS_CLASS_REGEX}, 155 {DICT_TYPE_PCRE, PCF_DBMS_CLASS_REGEX}, 156 {0}, 157 }; 158 159 /* 160 * Workaround to prevent a false warning about "#comment after other text", 161 * when an inline pcre or regexp pattern contains "#text". 162 */ 163 #define PCF_DBMS_RECURSE 1 /* Parse inline {map-entry} */ 164 #define PCF_DBMS_NO_RECURSE 0 /* Don't parse inline {map-entry} */ 165 166 /* pcf_check_dbms_client - look for unused names in client configuration */ 167 168 static void pcf_check_dbms_client(const PCF_DBMS_INFO *dp, const char *cf_file) 169 { 170 DICT *dict; 171 VSTREAM *fp; 172 const char **cpp; 173 const char *name; 174 const char *value; 175 char *dict_spec; 176 int dir; 177 178 /* 179 * We read each database client configuration file into its own 180 * dictionary, and nag only the first time that a file is visited. 181 */ 182 dict_spec = concatenate(dp->db_type, ":", cf_file, (char *) 0); 183 if ((dict = dict_handle(dict_spec)) == 0) { 184 struct stat st; 185 186 /* 187 * Populate the dictionary with settings in this database client 188 * configuration file. Don't die if a file can't be opened - some 189 * files may contain passwords and should not be world-readable. 190 * Note: dict_load_fp() nags about duplicate parameter settings. 191 */ 192 dict = dict_ht_open(dict_spec, O_CREAT | O_RDWR, 0); 193 dict_register(dict_spec, dict); 194 if ((fp = vstream_fopen(cf_file, O_RDONLY, 0)) == 0) { 195 if (errno != EACCES) 196 msg_warn("open \"%s\" configuration \"%s\": %m", 197 dp->db_type, cf_file); 198 myfree(dict_spec); 199 return; 200 } 201 if (fstat(vstream_fileno(fp), &st) == 0 && !S_ISREG(st.st_mode)) { 202 msg_warn("open \"%s\" configuration \"%s\": not a regular file", 203 dp->db_type, cf_file); 204 myfree(dict_spec); 205 (void) vstream_fclose(fp); 206 return; 207 } 208 dict_load_fp(dict_spec, fp); 209 if (vstream_fclose(fp)) { 210 msg_warn("read \"%s\" configuration \"%s\": %m", 211 dp->db_type, cf_file); 212 myfree(dict_spec); 213 return; 214 } 215 216 /* 217 * Remove all known database client parameters from this dictionary, 218 * then report the remaining ones as "unused". We use ad-hoc logging 219 * code, because a database client parameter namespace is unlike the 220 * parameter namespaces in main.cf or master.cf. 221 */ 222 for (cpp = dp->db_suffixes; *cpp; cpp++) 223 (void) dict_del(dict, *cpp); 224 for (dir = DICT_SEQ_FUN_FIRST; 225 dict->sequence(dict, dir, &name, &value) == DICT_STAT_SUCCESS; 226 dir = DICT_SEQ_FUN_NEXT) 227 msg_warn("%s: unused parameter: %s=%s", dict_spec, name, value); 228 } 229 myfree(dict_spec); 230 } 231 232 /* pcf_register_dbms_helper - parse one possible database type:name */ 233 234 static void pcf_register_dbms_helper(char *str_value, 235 const char *(flag_parameter) (const char *, int, PCF_MASTER_ENT *), 236 PCF_MASTER_ENT *local_scope, 237 int recurse) 238 { 239 const PCF_DBMS_INFO *dp; 240 char *db_type; 241 char *prefix; 242 static VSTRING *candidate = 0; 243 const char **cpp; 244 char *err; 245 246 /* 247 * Naive parsing. We don't really know if this substring specifies a 248 * database or some other text. 249 */ 250 while ((db_type = mystrtokq_cw(&str_value, CHARS_COMMA_SP, CHARS_BRACE, 251 local_scope ? pcf_get_master_path() : pcf_get_main_path())) != 0) { 252 if (*db_type == CHARS_BRACE[0]) { 253 if ((err = extpar(&db_type, CHARS_BRACE, EXTPAR_FLAG_NONE)) != 0) { 254 /* XXX Encapsulate this in pcf_warn() function. */ 255 if (local_scope) 256 msg_warn("%s:%s: %s", pcf_get_master_path(), 257 local_scope->name_space, err); 258 else 259 msg_warn("%s: %s", pcf_get_main_path(), err); 260 myfree(err); 261 } 262 if (recurse) 263 pcf_register_dbms_helper(db_type, flag_parameter, local_scope, 264 recurse); 265 continue; 266 } 267 268 /* 269 * Skip over "proxy:" maptypes, to emulate the proxymap(8) server's 270 * behavior when opening a local database configuration file. 271 */ 272 while ((prefix = split_at(db_type, ':')) != 0 273 && strcmp(db_type, DICT_TYPE_PROXY) == 0) 274 db_type = prefix; 275 276 if (prefix == 0) 277 continue; 278 279 /* 280 * Look for database:prefix where the prefix is an absolute pathname. 281 * Then, report unknown database client configuration parameters. 282 * 283 * XXX What about a pathname beginning with '.'? This supposedly is 284 * relative to the queue directory, which is the default directory 285 * for all Postfix daemon processes. This would also have to handle 286 * the case that the queue is not yet created. 287 */ 288 if (*prefix == '/') { 289 for (dp = pcf_dbms_info; dp->db_type != 0; dp++) { 290 if (strcmp(db_type, dp->db_type) == 0) { 291 if (dp->db_class == PCF_DBMS_CLASS_CLIENT) 292 pcf_check_dbms_client(dp, prefix); 293 break; 294 } 295 } 296 continue; 297 } 298 299 /* 300 * Look for database:prefix where the prefix is not a pathname and 301 * the database is a known type. Synthesize candidate parameter names 302 * from the user-defined prefix and from the database-defined suffix 303 * list, and see if those parameters have a "name=value" entry in the 304 * local or global namespace. 305 */ 306 if (*prefix != '.') { 307 int next_recurse = recurse; 308 309 if (*prefix == CHARS_BRACE[0]) { 310 if ((err = extpar(&prefix, CHARS_BRACE, EXTPAR_FLAG_NONE)) != 0) { 311 /* XXX Encapsulate this in pcf_warn() function. */ 312 if (local_scope) 313 msg_warn("%s:%s: %s", pcf_get_master_path(), 314 local_scope->name_space, err); 315 else 316 msg_warn("%s: %s", pcf_get_main_path(), err); 317 myfree(err); 318 } 319 for (dp = pcf_dbms_info; dp->db_type != 0; dp++) { 320 if (strcmp(db_type, dp->db_type) == 0) { 321 if (dp->db_class == PCF_DBMS_CLASS_REGEX) 322 next_recurse = PCF_DBMS_NO_RECURSE; 323 break; 324 } 325 } 326 pcf_register_dbms_helper(prefix, flag_parameter, local_scope, 327 next_recurse); 328 continue; 329 } else { 330 for (dp = pcf_dbms_info; dp->db_type != 0; dp++) { 331 if (strcmp(db_type, dp->db_type) == 0) { 332 if (dp->db_class == PCF_DBMS_CLASS_CLIENT) { 333 for (cpp = dp->db_suffixes; *cpp; cpp++) { 334 vstring_sprintf(candidate ? candidate : 335 (candidate = vstring_alloc(30)), 336 "%s_%s", prefix, *cpp); 337 flag_parameter(STR(candidate), 338 PCF_PARAM_FLAG_DBMS | PCF_PARAM_FLAG_USER, 339 local_scope); 340 } 341 } 342 break; 343 } 344 } 345 } 346 } 347 } 348 } 349 350 /* pcf_register_dbms_parameters - look for database_type:prefix_name */ 351 352 void pcf_register_dbms_parameters(const char *param_value, 353 const char *(flag_parameter) (const char *, int, PCF_MASTER_ENT *), 354 PCF_MASTER_ENT *local_scope) 355 { 356 char *bufp; 357 static VSTRING *buffer = 0; 358 359 /* 360 * XXX This does not examine both sides of conditional macro expansion, 361 * and may expand the "wrong" conditional macros. This is the best we can 362 * do for legacy database configuration support. 363 */ 364 if (buffer == 0) 365 buffer = vstring_alloc(100); 366 bufp = pcf_expand_parameter_value(buffer, PCF_SHOW_EVAL, param_value, 367 local_scope); 368 pcf_register_dbms_helper(bufp, flag_parameter, local_scope, PCF_DBMS_RECURSE); 369 } 370 371 #endif 372