1/*	$NetBSD: db_common.c,v 1.3 2022/10/08 16:12:45 christos Exp $	*/
2
3/*++
4/* NAME
5/*	db_common 3
6/* SUMMARY
7/*	utilities common to network based dictionaries
8/* SYNOPSIS
9/*	#include "db_common.h"
10/*
11/*	int	db_common_parse(dict, ctx, format, query)
12/*	DICT	*dict;
13/*	void	**ctx;
14/*	const char *format;
15/*	int	query;
16/*
17/*	void	db_common_free_context(ctx)
18/*	void	*ctx;
19/*
20/*	int	db_common_expand(ctx, format, value, key, buf, quote_func);
21/*	void	*ctx;
22/*	const char *format;
23/*	const char *value;
24/*	const char *key;
25/*	VSTRING	*buf;
26/*	void	(*quote_func)(DICT *, const char *, VSTRING *);
27/*
28/*	int	db_common_check_domain(domain_list, addr);
29/*	STRING_LIST *domain_list;
30/*	const char *addr;
31/*
32/*	void	db_common_sql_build_query(query,parser);
33/*	VSTRING	*query;
34/*	CFG_PARSER *parser;
35/*
36/* DESCRIPTION
37/*	This module implements utilities common to network based dictionaries.
38/*
39/*	\fIdb_common_parse\fR parses query and result substitution templates.
40/*	It must be called for each template before any calls to
41/*	\fIdb_common_expand\fR. The \fIctx\fR argument must be initialized to
42/*	a reference to a (void *)0 before the first template is parsed, this
43/*	causes memory for the context to be allocated and the new pointer is
44/*	stored in *ctx. When the dictionary is closed, this memory must be
45/*	freed with a final call to \fBdb_common_free_context\fR.
46/*
47/*	Calls for additional templates associated with the same map must use the
48/*	same ctx argument. The context accumulates run-time lookup key and result
49/*	validation information (inapplicable keys or results are skipped) and is
50/*	needed later in each call of \fIdb_common_expand\fR. A non-zero return
51/*	value indicates that data-dependent '%' expansions were found in the input
52/*	template.
53/*
54/*	db_common_alloc() provides a way to use db_common_parse_domain()
55/*	etc. without prior db_common_parse() call.
56/*
57/*	\fIdb_common_expand\fR expands the specifiers in \fIformat\fR.
58/*	When the input data lacks all fields needed for the expansion, zero
59/*	is returned and the query or result should be skipped. Otherwise
60/*	the expansion is appended to the result buffer (after a comma if the
61/*	result buffer is not empty).
62/*
63/*	If not NULL, the \fBquote_func\fR callback performs database-specific
64/*	quoting of each variable before expansion.
65/*	\fBvalue\fR is the lookup key for query expansion and result for result
66/*	expansion. \fBkey\fR is NULL for query expansion and the lookup key for
67/*	result expansion.
68/* .PP
69/*	The following '%' expansions are performed on \fBvalue\fR:
70/* .IP %%
71/*	A literal percent character.
72/* .IP %s
73/*	The entire lookup key \fIaddr\fR.
74/* .IP %u
75/*	If \fBaddr\fR is a fully qualified address, the local part of the
76/*	address.  Otherwise \fIaddr\fR.
77/* .IP %d
78/*	If \fIaddr\fR is a fully qualified address, the domain part of the
79/*	address.  Otherwise the query against the database is suppressed and
80/*	the lookup returns no results.
81/*
82/*	The following '%' expansions are performed on the lookup \fBkey\fR:
83/* .IP %S
84/*	The entire lookup key \fIkey\fR.
85/* .IP %U
86/*	If \fBkey\fR is a fully qualified address, the local part of the
87/*	address.  Otherwise \fIkey\fR.
88/* .IP %D
89/*	If \fIkey\fR is a fully qualified address, the domain part of the
90/*	address.  Otherwise the query against the database is suppressed and
91/*	the lookup returns no results.
92/* .PP
93/*	\fIdb_common_check_domain\fR() checks the domain list so
94/*	that query optimization can be performed. The result is >0
95/*	(match found), 0 (no match), or <0 (dictionary error code).
96/*
97/* .PP
98/*	\fIdb_common_sql_build_query\fR builds the "default"(backwards compatible)
99/*	query from the 'table', 'select_field', 'where_field' and
100/*	'additional_conditions' parameters, checking for errors.
101/*
102/* DIAGNOSTICS
103/*	Fatal errors: invalid substitution format, invalid string_list pattern,
104/*	insufficient parameters.
105/* SEE ALSO
106/*	dict(3) dictionary manager
107/*	string_list(3) string list pattern matching
108/*	match_ops(3) simple string or host pattern matching
109/* LICENSE
110/* .ad
111/* .fi
112/*	The Secure Mailer license must be distributed with this software.
113/* AUTHOR(S)
114/*	Wietse Venema
115/*	IBM T.J. Watson Research
116/*	P.O. Box 704
117/*	Yorktown Heights, NY 10598, USA
118/*
119/*	Wietse Venema
120/*	Google, Inc.
121/*	111 8th Avenue
122/*	New York, NY 10011, USA
123/*
124/*	Liviu Daia
125/*	Institute of Mathematics of the Romanian Academy
126/*	P.O. BOX 1-764
127/*	RO-014700 Bucharest, ROMANIA
128/*
129/*	Jose Luis Tallon
130/*	G4 J.E. - F.I. - U.P.M.
131/*	Campus de Montegancedo, S/N
132/*	E-28660 Madrid, SPAIN
133/*
134/*	Victor Duchovni
135/*	Morgan Stanley
136/*--*/
137
138 /*
139  * System library.
140  */
141#include "sys_defs.h"
142#include <stddef.h>
143#include <string.h>
144
145 /*
146  * Global library.
147  */
148#include "cfg_parser.h"
149
150 /*
151  * Utility library.
152  */
153#include <mymalloc.h>
154#include <vstring.h>
155#include <msg.h>
156#include <dict.h>
157
158 /*
159  * Application specific
160  */
161#include "db_common.h"
162
163#define	DB_COMMON_KEY_DOMAIN	(1 << 0)/* Need lookup key domain */
164#define	DB_COMMON_KEY_USER	(1 << 1)/* Need lookup key localpart */
165#define	DB_COMMON_VALUE_DOMAIN	(1 << 2)/* Need result domain */
166#define	DB_COMMON_VALUE_USER	(1 << 3)/* Need result localpart */
167#define	DB_COMMON_KEY_PARTIAL	(1 << 4)/* Key uses input substrings */
168
169typedef struct {
170    DICT   *dict;
171    STRING_LIST *domain;
172    int     flags;
173    int     nparts;
174} DB_COMMON_CTX;
175
176/* db_common_alloc - allocate db_common context */
177
178void   *db_common_alloc(DICT *dict)
179{
180    DB_COMMON_CTX *ctx;
181
182    ctx = (DB_COMMON_CTX *) mymalloc(sizeof *ctx);
183    ctx->dict = dict;
184    ctx->domain = 0;
185    ctx->flags = 0;
186    ctx->nparts = 0;
187    return ((void *) ctx);
188}
189
190/* db_common_parse - validate query or result template */
191
192int     db_common_parse(DICT *dict, void **ctxPtr, const char *format, int query)
193{
194    DB_COMMON_CTX *ctx = (DB_COMMON_CTX *) *ctxPtr;
195    const char *cp;
196    int     dynamic = 0;
197
198    if (ctx == 0)
199	ctx = (DB_COMMON_CTX *) (*ctxPtr = db_common_alloc(dict));
200
201    for (cp = format; *cp; ++cp)
202	if (*cp == '%')
203	    switch (*++cp) {
204	    case '%':
205		break;
206	    case 'u':
207		ctx->flags |=
208		    query ? DB_COMMON_KEY_USER | DB_COMMON_KEY_PARTIAL
209		    : DB_COMMON_VALUE_USER;
210		dynamic = 1;
211		break;
212	    case 'd':
213		ctx->flags |=
214		    query ? DB_COMMON_KEY_DOMAIN | DB_COMMON_KEY_PARTIAL
215		    : DB_COMMON_VALUE_DOMAIN;
216		dynamic = 1;
217		break;
218	    case 's':
219	    case 'S':
220		dynamic = 1;
221		break;
222	    case 'U':
223		ctx->flags |= DB_COMMON_KEY_PARTIAL | DB_COMMON_KEY_USER;
224		dynamic = 1;
225		break;
226	    case '1':
227	    case '2':
228	    case '3':
229	    case '4':
230	    case '5':
231	    case '6':
232	    case '7':
233	    case '8':
234	    case '9':
235
236		/*
237		 * Find highest %[1-9] index in query template. Input keys
238		 * will be constrained to those with at least this many
239		 * domain components. This makes the db_common_expand() code
240		 * safe from invalid inputs.
241		 */
242		if (ctx->nparts < *cp - '0')
243		    ctx->nparts = *cp - '0';
244		/* FALLTHROUGH */
245	    case 'D':
246		ctx->flags |= DB_COMMON_KEY_PARTIAL | DB_COMMON_KEY_DOMAIN;
247		dynamic = 1;
248		break;
249	    default:
250		msg_fatal("db_common_parse: %s: Invalid %s template: %s",
251		       ctx->dict->name, query ? "query" : "result", format);
252	    }
253    return dynamic;
254}
255
256/* db_common_parse_domain - parse domain matchlist*/
257
258void    db_common_parse_domain(CFG_PARSER *parser, void *ctxPtr)
259{
260    DB_COMMON_CTX *ctx = (DB_COMMON_CTX *) ctxPtr;
261    char   *domainlist;
262    const char *myname = "db_common_parse_domain";
263
264    domainlist = cfg_get_str(parser, "domain", "", 0, 0);
265    if (*domainlist) {
266	ctx->domain = string_list_init(parser->name, MATCH_FLAG_RETURN,
267				       domainlist);
268	if (ctx->domain == 0)
269
270	    /*
271	     * The "domain" optimization skips input keys that may in fact
272	     * have unwanted matches in the database, so failure to create
273	     * the match list is fatal.
274	     */
275	    msg_fatal("%s: %s: domain match list creation using '%s' failed",
276		      myname, parser->name, domainlist);
277    }
278    myfree(domainlist);
279}
280
281/* db_common_dict_partial - Does query use partial lookup keys? */
282
283int     db_common_dict_partial(void *ctxPtr)
284{
285#if 0					/* Breaks recipient_delimiter */
286    DB_COMMON_CTX *ctx = (DB_COMMON_CTX *) ctxPtr;
287
288    return (ctx->domain || ctx->flags & DB_COMMON_KEY_PARTIAL);
289#endif
290    return (0);
291}
292
293/* db_common_free_ctx - free parse context */
294
295void    db_common_free_ctx(void *ctxPtr)
296{
297    DB_COMMON_CTX *ctx = (DB_COMMON_CTX *) ctxPtr;
298
299    if (ctx->domain)
300	string_list_free(ctx->domain);
301    myfree((void *) ctxPtr);
302}
303
304/* db_common_expand - expand query and result templates */
305
306int     db_common_expand(void *ctxArg, const char *format, const char *value,
307			         const char *key, VSTRING *result,
308			         db_quote_callback_t quote_func)
309{
310    const char *myname = "db_common_expand";
311    DB_COMMON_CTX *ctx = (DB_COMMON_CTX *) ctxArg;
312    const char *vdomain = 0;
313    const char *kdomain = 0;
314    const char *domain = 0;
315    int     dflag = key ? DB_COMMON_VALUE_DOMAIN : DB_COMMON_KEY_DOMAIN;
316    char   *vuser = 0;
317    char   *kuser = 0;
318    ARGV   *parts = 0;
319    int     i;
320    const char *cp;
321
322    /* Skip NULL values, silently. */
323    if (value == 0)
324	return (0);
325
326    /* Don't silenty skip empty query string or empty lookup results. */
327    if (*value == 0) {
328	if (key)
329	    msg_warn("table \"%s:%s\": empty lookup result for: \"%s\""
330		     " -- ignored", ctx->dict->type, ctx->dict->name, key);
331	else
332	    msg_warn("table \"%s:%s\": empty query string"
333		     " -- ignored", ctx->dict->type, ctx->dict->name);
334	return (0);
335    }
336    if (key) {
337	/* This is a result template and the input value is the result */
338	if (ctx->flags & (DB_COMMON_VALUE_DOMAIN | DB_COMMON_VALUE_USER))
339	    if ((vdomain = strrchr(value, '@')) != 0)
340		++vdomain;
341
342	if (((!vdomain || !*vdomain) && (ctx->flags & DB_COMMON_VALUE_DOMAIN) != 0)
343	    || (vdomain == value + 1 && (ctx->flags & DB_COMMON_VALUE_USER) != 0))
344	    return (0);
345
346	/* The result format may use the local or domain part of the key */
347	if (ctx->flags & (DB_COMMON_KEY_DOMAIN | DB_COMMON_KEY_USER))
348	    if ((kdomain = strrchr(key, '@')) != 0)
349		++kdomain;
350
351	/*
352	 * The key should already be checked before the query. No harm if the
353	 * query did not get optimized out, so we just issue a warning.
354	 */
355	if (((!kdomain || !*kdomain) && (ctx->flags & DB_COMMON_KEY_DOMAIN) != 0)
356	|| (kdomain == key + 1 && (ctx->flags & DB_COMMON_KEY_USER) != 0)) {
357	    msg_warn("%s: %s: lookup key '%s' skipped after query", myname,
358		     ctx->dict->name, value);
359	    return (0);
360	}
361    } else {
362	/* This is a query template and the input value is the lookup key */
363	if (ctx->flags & (DB_COMMON_KEY_DOMAIN | DB_COMMON_KEY_USER))
364	    if ((vdomain = strrchr(value, '@')) != 0)
365		++vdomain;
366
367	if (((!vdomain || !*vdomain) && (ctx->flags & DB_COMMON_KEY_DOMAIN) != 0)
368	|| (vdomain == value + 1 && (ctx->flags & DB_COMMON_KEY_USER) != 0))
369	    return (0);
370    }
371
372    if (ctx->nparts > 0) {
373	parts = argv_split(key ? kdomain : vdomain, ".");
374
375	/*
376	 * Filter out input keys whose domains lack enough labels to fill-in
377	 * the query template. See below and also db_common_parse() which
378	 * initializes ctx->nparts.
379	 */
380	if (parts->argc < ctx->nparts) {
381	    argv_free(parts);
382	    return (0);
383	}
384
385	/*
386	 * Skip domains with leading, consecutive or trailing '.' separators
387	 * among the required labels.
388	 */
389	for (i = 0; i < ctx->nparts; i++)
390	    if (*parts->argv[parts->argc - i - 1] == 0) {
391		argv_free(parts);
392		return (0);
393	    }
394    }
395    if (VSTRING_LEN(result) > 0)
396	VSTRING_ADDCH(result, ',');
397
398#define QUOTE_VAL(d, q, v, buf) do { \
399	if (q) \
400	    q(d, v, buf); \
401	else \
402	    vstring_strcat(buf, v); \
403    } while (0)
404
405    /*
406     * Replace all instances of %s with the address to look up. Replace %u
407     * with the user portion, and %d with the domain portion. "%%" expands to
408     * "%".  lowercase -> addr, uppercase -> key
409     */
410    for (cp = format; *cp; cp++) {
411	if (*cp == '%') {
412	    switch (*++cp) {
413
414	    case '%':
415		VSTRING_ADDCH(result, '%');
416		break;
417
418	    case 's':
419		QUOTE_VAL(ctx->dict, quote_func, value, result);
420		break;
421
422	    case 'u':
423		if (vdomain) {
424		    if (vuser == 0)
425			vuser = mystrndup(value, vdomain - value - 1);
426		    QUOTE_VAL(ctx->dict, quote_func, vuser, result);
427		} else
428		    QUOTE_VAL(ctx->dict, quote_func, value, result);
429		break;
430
431	    case 'd':
432		if (!(ctx->flags & dflag))
433		    msg_panic("%s: %s: %s: bad query/result template context",
434			      myname, ctx->dict->name, format);
435		if (!vdomain)
436		    msg_panic("%s: %s: %s: expanding domain-less key or value",
437			      myname, ctx->dict->name, format);
438		QUOTE_VAL(ctx->dict, quote_func, vdomain, result);
439		break;
440
441	    case 'S':
442		if (key)
443		    QUOTE_VAL(ctx->dict, quote_func, key, result);
444		else
445		    QUOTE_VAL(ctx->dict, quote_func, value, result);
446		break;
447
448	    case 'U':
449		if (key) {
450		    if (kdomain) {
451			if (kuser == 0)
452			    kuser = mystrndup(key, kdomain - key - 1);
453			QUOTE_VAL(ctx->dict, quote_func, kuser, result);
454		    } else
455			QUOTE_VAL(ctx->dict, quote_func, key, result);
456		} else {
457		    if (vdomain) {
458			if (vuser == 0)
459			    vuser = mystrndup(value, vdomain - value - 1);
460			QUOTE_VAL(ctx->dict, quote_func, vuser, result);
461		    } else
462			QUOTE_VAL(ctx->dict, quote_func, value, result);
463		}
464		break;
465
466	    case 'D':
467		if (!(ctx->flags & DB_COMMON_KEY_DOMAIN))
468		    msg_panic("%s: %s: %s: bad query/result template context",
469			      myname, ctx->dict->name, format);
470		if ((domain = key ? kdomain : vdomain) == 0)
471		    msg_panic("%s: %s: %s: expanding domain-less key or value",
472			      myname, ctx->dict->name, format);
473		QUOTE_VAL(ctx->dict, quote_func, domain, result);
474		break;
475
476	    case '1':
477	    case '2':
478	    case '3':
479	    case '4':
480	    case '5':
481	    case '6':
482	    case '7':
483	    case '8':
484	    case '9':
485
486		/*
487		 * Interpolate %[1-9] components into the query string. By
488		 * this point db_common_parse() has identified the highest
489		 * component index, and (see above) keys with fewer
490		 * components have been filtered out. The "parts" ARGV is
491		 * guaranteed to be initialized and hold enough elements to
492		 * satisfy the query template.
493		 */
494		if (!(ctx->flags & DB_COMMON_KEY_DOMAIN)
495		    || ctx->nparts < *cp - '0')
496		    msg_panic("%s: %s: %s: bad query/result template context",
497			      myname, ctx->dict->name, format);
498		if (!parts || parts->argc < ctx->nparts)
499		    msg_panic("%s: %s: %s: key has too few domain labels",
500			      myname, ctx->dict->name, format);
501		QUOTE_VAL(ctx->dict, quote_func,
502			  parts->argv[parts->argc - (*cp - '0')], result);
503		break;
504
505	    default:
506		msg_fatal("%s: %s: invalid %s template '%s'", myname,
507			  ctx->dict->name, key ? "result" : "query",
508			  format);
509	    }
510	} else
511	    VSTRING_ADDCH(result, *cp);
512    }
513    VSTRING_TERMINATE(result);
514
515    if (vuser)
516	myfree(vuser);
517    if (kuser)
518	myfree(kuser);
519    if (parts)
520	argv_free(parts);
521
522    return (1);
523}
524
525
526/* db_common_check_domain - check domain list */
527
528int     db_common_check_domain(void *ctxPtr, const char *addr)
529{
530    DB_COMMON_CTX *ctx = (DB_COMMON_CTX *) ctxPtr;
531    char   *domain;
532
533    if (ctx->domain) {
534	if ((domain = strrchr(addr, '@')) != NULL)
535	    ++domain;
536	if (domain == NULL || domain == addr + 1)
537	    return (0);
538	if (match_list_match(ctx->domain, domain) == 0)
539	    return (ctx->domain->error);
540    }
541    return (1);
542}
543
544/* db_common_sql_build_query -- build query for SQL maptypes */
545
546void    db_common_sql_build_query(VSTRING *query, CFG_PARSER *parser)
547{
548    const char *myname = "db_common_sql_build_query";
549    char   *table;
550    char   *select_field;
551    char   *where_field;
552    char   *additional_conditions;
553
554    /*
555     * Build "old style" query: "select %s from %s where %s"
556     */
557    if ((table = cfg_get_str(parser, "table", NULL, 1, 0)) == 0)
558	msg_fatal("%s: 'table' parameter not defined", myname);
559
560    if ((select_field = cfg_get_str(parser, "select_field", NULL, 1, 0)) == 0)
561	msg_fatal("%s: 'select_field' parameter not defined", myname);
562
563    if ((where_field = cfg_get_str(parser, "where_field", NULL, 1, 0)) == 0)
564	msg_fatal("%s: 'where_field' parameter not defined", myname);
565
566    additional_conditions = cfg_get_str(parser, "additional_conditions",
567					"", 0, 0);
568
569    vstring_sprintf(query, "SELECT %s FROM %s WHERE %s='%%s' %s",
570		    select_field, table, where_field,
571		    additional_conditions);
572
573    myfree(table);
574    myfree(select_field);
575    myfree(where_field);
576    myfree(additional_conditions);
577}
578