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 
169 typedef 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 
db_common_alloc(DICT * dict)178 void   *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 
db_common_parse(DICT * dict,void ** ctxPtr,const char * format,int query)192 int     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 
db_common_parse_domain(CFG_PARSER * parser,void * ctxPtr)258 void    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 
db_common_dict_partial(void * ctxPtr)283 int     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 
db_common_free_ctx(void * ctxPtr)295 void    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 
db_common_expand(void * ctxArg,const char * format,const char * value,const char * key,VSTRING * result,db_quote_callback_t quote_func)306 int     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 
db_common_check_domain(void * ctxPtr,const char * addr)528 int     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 
db_common_sql_build_query(VSTRING * query,CFG_PARSER * parser)546 void    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