xref: /netbsd-src/external/ibm-public/postfix/dist/src/postconf/postconf_dbms.c (revision c48c605c14fd8622b523d1d6a3f0c0bad133ea89)
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