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
pcf_check_dbms_client(const PCF_DBMS_INFO * dp,const char * cf_file)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
pcf_register_dbms_helper(char * str_value,const char * (flag_parameter)(const char *,int,PCF_MASTER_ENT *),PCF_MASTER_ENT * local_scope,int recurse)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
pcf_register_dbms_parameters(const char * param_value,const char * (flag_parameter)(const char *,int,PCF_MASTER_ENT *),PCF_MASTER_ENT * local_scope)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