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
103static const char *pcf_ldap_suffixes[] = {
104#include "pcf_ldap_suffixes.h"
105    0,
106};
107
108/* See mysql_table(5). */
109
110static const char *pcf_mysql_suffixes[] = {
111#include "pcf_mysql_suffixes.h"
112    0,
113};
114
115/* See pgsql_table(5). */
116
117static const char *pcf_pgsql_suffixes[] = {
118#include "pcf_pgsql_suffixes.h"
119    0,
120};
121
122/* See sqlite_table(5). */
123
124static const char *pcf_sqlite_suffixes[] = {
125#include "pcf_sqlite_suffixes.h"
126    0,
127};
128
129/* See memcache_table(5). */
130
131static 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  */
139typedef 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
148static 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
168static 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
234static 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
352void    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