xref: /netbsd-src/external/ibm-public/postfix/dist/src/global/db_common.c (revision bdc22b2e01993381dcefeff2bc9b56ca75a4235c)
1 /*	$NetBSD: db_common.c,v 1.2 2017/02/14 01:16: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-depedent '%' 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 /*	the 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 /*	Liviu Daia
120 /*	Institute of Mathematics of the Romanian Academy
121 /*	P.O. BOX 1-764
122 /*	RO-014700 Bucharest, ROMANIA
123 /*
124 /*	Jose Luis Tallon
125 /*	G4 J.E. - F.I. - U.P.M.
126 /*	Campus de Montegancedo, S/N
127 /*	E-28660 Madrid, SPAIN
128 /*
129 /*	Victor Duchovni
130 /*	Morgan Stanley
131 /*--*/
132 
133  /*
134   * System library.
135   */
136 #include "sys_defs.h"
137 #include <stddef.h>
138 #include <string.h>
139 
140  /*
141   * Global library.
142   */
143 #include "cfg_parser.h"
144 
145  /*
146   * Utility library.
147   */
148 #include <mymalloc.h>
149 #include <vstring.h>
150 #include <msg.h>
151 #include <dict.h>
152 
153  /*
154   * Application specific
155   */
156 #include "db_common.h"
157 
158 #define	DB_COMMON_KEY_DOMAIN	(1 << 0)/* Need lookup key domain */
159 #define	DB_COMMON_KEY_USER	(1 << 1)/* Need lookup key localpart */
160 #define	DB_COMMON_VALUE_DOMAIN	(1 << 2)/* Need result domain */
161 #define	DB_COMMON_VALUE_USER	(1 << 3)/* Need result localpart */
162 #define	DB_COMMON_KEY_PARTIAL	(1 << 4)/* Key uses input substrings */
163 
164 typedef struct {
165     DICT   *dict;
166     STRING_LIST *domain;
167     int     flags;
168     int     nparts;
169 } DB_COMMON_CTX;
170 
171 /* db_common_alloc - allocate db_common context */
172 
173 void   *db_common_alloc(DICT *dict)
174 {
175     DB_COMMON_CTX *ctx;
176 
177     ctx = (DB_COMMON_CTX *) mymalloc(sizeof *ctx);
178     ctx->dict = dict;
179     ctx->domain = 0;
180     ctx->flags = 0;
181     ctx->nparts = 0;
182     return ((void *) ctx);
183 }
184 
185 /* db_common_parse - validate query or result template */
186 
187 int     db_common_parse(DICT *dict, void **ctxPtr, const char *format, int query)
188 {
189     DB_COMMON_CTX *ctx = (DB_COMMON_CTX *) *ctxPtr;
190     const char *cp;
191     int     dynamic = 0;
192 
193     if (ctx == 0)
194 	ctx = (DB_COMMON_CTX *) (*ctxPtr = db_common_alloc(dict));
195 
196     for (cp = format; *cp; ++cp)
197 	if (*cp == '%')
198 	    switch (*++cp) {
199 	    case '%':
200 		break;
201 	    case 'u':
202 		ctx->flags |=
203 		    query ? DB_COMMON_KEY_USER | DB_COMMON_KEY_PARTIAL
204 		    : DB_COMMON_VALUE_USER;
205 		dynamic = 1;
206 		break;
207 	    case 'd':
208 		ctx->flags |=
209 		    query ? DB_COMMON_KEY_DOMAIN | DB_COMMON_KEY_PARTIAL
210 		    : DB_COMMON_VALUE_DOMAIN;
211 		dynamic = 1;
212 		break;
213 	    case 's':
214 	    case 'S':
215 		dynamic = 1;
216 		break;
217 	    case 'U':
218 		ctx->flags |= DB_COMMON_KEY_PARTIAL | DB_COMMON_KEY_USER;
219 		dynamic = 1;
220 		break;
221 	    case '1':
222 	    case '2':
223 	    case '3':
224 	    case '4':
225 	    case '5':
226 	    case '6':
227 	    case '7':
228 	    case '8':
229 	    case '9':
230 
231 		/*
232 		 * Find highest %[1-9] index in query template. Input keys
233 		 * will be constrained to those with at least this many
234 		 * domain components. This makes the db_common_expand() code
235 		 * safe from invalid inputs.
236 		 */
237 		if (ctx->nparts < *cp - '0')
238 		    ctx->nparts = *cp - '0';
239 		/* FALLTHROUGH */
240 	    case 'D':
241 		ctx->flags |= DB_COMMON_KEY_PARTIAL | DB_COMMON_KEY_DOMAIN;
242 		dynamic = 1;
243 		break;
244 	    default:
245 		msg_fatal("db_common_parse: %s: Invalid %s template: %s",
246 		       ctx->dict->name, query ? "query" : "result", format);
247 	    }
248     return dynamic;
249 }
250 
251 /* db_common_parse_domain - parse domain matchlist*/
252 
253 void    db_common_parse_domain(CFG_PARSER *parser, void *ctxPtr)
254 {
255     DB_COMMON_CTX *ctx = (DB_COMMON_CTX *) ctxPtr;
256     char   *domainlist;
257     const char *myname = "db_common_parse_domain";
258 
259     domainlist = cfg_get_str(parser, "domain", "", 0, 0);
260     if (*domainlist) {
261 	ctx->domain = string_list_init(parser->name, MATCH_FLAG_RETURN,
262 				       domainlist);
263 	if (ctx->domain == 0)
264 
265 	    /*
266 	     * The "domain" optimization skips input keys that may in fact
267 	     * have unwanted matches in the database, so failure to create
268 	     * the match list is fatal.
269 	     */
270 	    msg_fatal("%s: %s: domain match list creation using '%s' failed",
271 		      myname, parser->name, domainlist);
272     }
273     myfree(domainlist);
274 }
275 
276 /* db_common_dict_partial - Does query use partial lookup keys? */
277 
278 int     db_common_dict_partial(void *ctxPtr)
279 {
280 #if 0					/* Breaks recipient_delimiter */
281     DB_COMMON_CTX *ctx = (DB_COMMON_CTX *) ctxPtr;
282 
283     return (ctx->domain || ctx->flags & DB_COMMON_KEY_PARTIAL);
284 #endif
285     return (0);
286 }
287 
288 /* db_common_free_ctx - free parse context */
289 
290 void    db_common_free_ctx(void *ctxPtr)
291 {
292     DB_COMMON_CTX *ctx = (DB_COMMON_CTX *) ctxPtr;
293 
294     if (ctx->domain)
295 	string_list_free(ctx->domain);
296     myfree((void *) ctxPtr);
297 }
298 
299 /* db_common_expand - expand query and result templates */
300 
301 int     db_common_expand(void *ctxArg, const char *format, const char *value,
302 			         const char *key, VSTRING *result,
303 			         db_quote_callback_t quote_func)
304 {
305     const char *myname = "db_common_expand";
306     DB_COMMON_CTX *ctx = (DB_COMMON_CTX *) ctxArg;
307     const char *vdomain = 0;
308     const char *kdomain = 0;
309     const char *domain = 0;
310     int     dflag = key ? DB_COMMON_VALUE_DOMAIN : DB_COMMON_KEY_DOMAIN;
311     char   *vuser = 0;
312     char   *kuser = 0;
313     ARGV   *parts = 0;
314     int     i;
315     const char *cp;
316 
317     /* Skip NULL values, silently. */
318     if (value == 0)
319 	return (0);
320 
321     /* Don't silenty skip empty query string or empty lookup results. */
322     if (*value == 0) {
323 	if (key)
324 	    msg_warn("table \"%s:%s\": empty lookup result for: \"%s\""
325 		     " -- ignored", ctx->dict->type, ctx->dict->name, key);
326 	else
327 	    msg_warn("table \"%s:%s\": empty query string"
328 		     " -- ignored", ctx->dict->type, ctx->dict->name);
329 	return (0);
330     }
331     if (key) {
332 	/* This is a result template and the input value is the result */
333 	if (ctx->flags & (DB_COMMON_VALUE_DOMAIN | DB_COMMON_VALUE_USER))
334 	    if ((vdomain = strrchr(value, '@')) != 0)
335 		++vdomain;
336 
337 	if (((!vdomain || !*vdomain) && (ctx->flags & DB_COMMON_VALUE_DOMAIN) != 0)
338 	    || (vdomain == value + 1 && (ctx->flags & DB_COMMON_VALUE_USER) != 0))
339 	    return (0);
340 
341 	/* The result format may use the local or domain part of the key */
342 	if (ctx->flags & (DB_COMMON_KEY_DOMAIN | DB_COMMON_KEY_USER))
343 	    if ((kdomain = strrchr(key, '@')) != 0)
344 		++kdomain;
345 
346 	/*
347 	 * The key should already be checked before the query. No harm if the
348 	 * query did not get optimized out, so we just issue a warning.
349 	 */
350 	if (((!kdomain || !*kdomain) && (ctx->flags & DB_COMMON_KEY_DOMAIN) != 0)
351 	|| (kdomain == key + 1 && (ctx->flags & DB_COMMON_KEY_USER) != 0)) {
352 	    msg_warn("%s: %s: lookup key '%s' skipped after query", myname,
353 		     ctx->dict->name, value);
354 	    return (0);
355 	}
356     } else {
357 	/* This is a query template and the input value is the lookup key */
358 	if (ctx->flags & (DB_COMMON_KEY_DOMAIN | DB_COMMON_KEY_USER))
359 	    if ((vdomain = strrchr(value, '@')) != 0)
360 		++vdomain;
361 
362 	if (((!vdomain || !*vdomain) && (ctx->flags & DB_COMMON_KEY_DOMAIN) != 0)
363 	|| (vdomain == value + 1 && (ctx->flags & DB_COMMON_KEY_USER) != 0))
364 	    return (0);
365     }
366 
367     if (ctx->nparts > 0) {
368 	parts = argv_split(key ? kdomain : vdomain, ".");
369 
370 	/*
371 	 * Filter out input keys whose domains lack enough labels to fill-in
372 	 * the query template. See below and also db_common_parse() which
373 	 * initializes ctx->nparts.
374 	 */
375 	if (parts->argc < ctx->nparts) {
376 	    argv_free(parts);
377 	    return (0);
378 	}
379 
380 	/*
381 	 * Skip domains with leading, consecutive or trailing '.' separators
382 	 * among the required labels.
383 	 */
384 	for (i = 0; i < ctx->nparts; i++)
385 	    if (*parts->argv[parts->argc - i - 1] == 0) {
386 		argv_free(parts);
387 		return (0);
388 	    }
389     }
390     if (VSTRING_LEN(result) > 0)
391 	VSTRING_ADDCH(result, ',');
392 
393 #define QUOTE_VAL(d, q, v, buf) do { \
394 	if (q) \
395 	    q(d, v, buf); \
396 	else \
397 	    vstring_strcat(buf, v); \
398     } while (0)
399 
400     /*
401      * Replace all instances of %s with the address to look up. Replace %u
402      * with the user portion, and %d with the domain portion. "%%" expands to
403      * "%".  lowercase -> addr, uppercase -> key
404      */
405     for (cp = format; *cp; cp++) {
406 	if (*cp == '%') {
407 	    switch (*++cp) {
408 
409 	    case '%':
410 		VSTRING_ADDCH(result, '%');
411 		break;
412 
413 	    case 's':
414 		QUOTE_VAL(ctx->dict, quote_func, value, result);
415 		break;
416 
417 	    case 'u':
418 		if (vdomain) {
419 		    if (vuser == 0)
420 			vuser = mystrndup(value, vdomain - value - 1);
421 		    QUOTE_VAL(ctx->dict, quote_func, vuser, result);
422 		} else
423 		    QUOTE_VAL(ctx->dict, quote_func, value, result);
424 		break;
425 
426 	    case 'd':
427 		if (!(ctx->flags & dflag))
428 		    msg_panic("%s: %s: %s: bad query/result template context",
429 			      myname, ctx->dict->name, format);
430 		if (!vdomain)
431 		    msg_panic("%s: %s: %s: expanding domain-less key or value",
432 			      myname, ctx->dict->name, format);
433 		QUOTE_VAL(ctx->dict, quote_func, vdomain, result);
434 		break;
435 
436 	    case 'S':
437 		if (key)
438 		    QUOTE_VAL(ctx->dict, quote_func, key, result);
439 		else
440 		    QUOTE_VAL(ctx->dict, quote_func, value, result);
441 		break;
442 
443 	    case 'U':
444 		if (key) {
445 		    if (kdomain) {
446 			if (kuser == 0)
447 			    kuser = mystrndup(key, kdomain - key - 1);
448 			QUOTE_VAL(ctx->dict, quote_func, kuser, result);
449 		    } else
450 			QUOTE_VAL(ctx->dict, quote_func, key, result);
451 		} else {
452 		    if (vdomain) {
453 			if (vuser == 0)
454 			    vuser = mystrndup(value, vdomain - value - 1);
455 			QUOTE_VAL(ctx->dict, quote_func, vuser, result);
456 		    } else
457 			QUOTE_VAL(ctx->dict, quote_func, value, result);
458 		}
459 		break;
460 
461 	    case 'D':
462 		if (!(ctx->flags & DB_COMMON_KEY_DOMAIN))
463 		    msg_panic("%s: %s: %s: bad query/result template context",
464 			      myname, ctx->dict->name, format);
465 		if ((domain = key ? kdomain : vdomain) == 0)
466 		    msg_panic("%s: %s: %s: expanding domain-less key or value",
467 			      myname, ctx->dict->name, format);
468 		QUOTE_VAL(ctx->dict, quote_func, domain, result);
469 		break;
470 
471 	    case '1':
472 	    case '2':
473 	    case '3':
474 	    case '4':
475 	    case '5':
476 	    case '6':
477 	    case '7':
478 	    case '8':
479 	    case '9':
480 
481 		/*
482 		 * Interpolate %[1-9] components into the query string. By
483 		 * this point db_common_parse() has identified the highest
484 		 * component index, and (see above) keys with fewer
485 		 * components have been filtered out. The "parts" ARGV is
486 		 * guaranteed to be initialized and hold enough elements to
487 		 * satisfy the query template.
488 		 */
489 		if (!(ctx->flags & DB_COMMON_KEY_DOMAIN)
490 		    || ctx->nparts < *cp - '0')
491 		    msg_panic("%s: %s: %s: bad query/result template context",
492 			      myname, ctx->dict->name, format);
493 		if (!parts || parts->argc < ctx->nparts)
494 		    msg_panic("%s: %s: %s: key has too few domain labels",
495 			      myname, ctx->dict->name, format);
496 		QUOTE_VAL(ctx->dict, quote_func,
497 			  parts->argv[parts->argc - (*cp - '0')], result);
498 		break;
499 
500 	    default:
501 		msg_fatal("%s: %s: invalid %s template '%s'", myname,
502 			  ctx->dict->name, key ? "result" : "query",
503 			  format);
504 	    }
505 	} else
506 	    VSTRING_ADDCH(result, *cp);
507     }
508     VSTRING_TERMINATE(result);
509 
510     if (vuser)
511 	myfree(vuser);
512     if (kuser)
513 	myfree(kuser);
514     if (parts)
515 	argv_free(parts);
516 
517     return (1);
518 }
519 
520 
521 /* db_common_check_domain - check domain list */
522 
523 int     db_common_check_domain(void *ctxPtr, const char *addr)
524 {
525     DB_COMMON_CTX *ctx = (DB_COMMON_CTX *) ctxPtr;
526     char   *domain;
527 
528     if (ctx->domain) {
529 	if ((domain = strrchr(addr, '@')) != NULL)
530 	    ++domain;
531 	if (domain == NULL || domain == addr + 1)
532 	    return (0);
533 	if (match_list_match(ctx->domain, domain) == 0)
534 	    return (ctx->domain->error);
535     }
536     return (1);
537 }
538 
539 /* db_common_sql_build_query -- build query for SQL maptypes */
540 
541 void    db_common_sql_build_query(VSTRING *query, CFG_PARSER *parser)
542 {
543     const char *myname = "db_common_sql_build_query";
544     char   *table;
545     char   *select_field;
546     char   *where_field;
547     char   *additional_conditions;
548 
549     /*
550      * Build "old style" query: "select %s from %s where %s"
551      */
552     if ((table = cfg_get_str(parser, "table", NULL, 1, 0)) == 0)
553 	msg_fatal("%s: 'table' parameter not defined", myname);
554 
555     if ((select_field = cfg_get_str(parser, "select_field", NULL, 1, 0)) == 0)
556 	msg_fatal("%s: 'select_field' parameter not defined", myname);
557 
558     if ((where_field = cfg_get_str(parser, "where_field", NULL, 1, 0)) == 0)
559 	msg_fatal("%s: 'where_field' parameter not defined", myname);
560 
561     additional_conditions = cfg_get_str(parser, "additional_conditions",
562 					"", 0, 0);
563 
564     vstring_sprintf(query, "SELECT %s FROM %s WHERE %s='%%s' %s",
565 		    select_field, table, where_field,
566 		    additional_conditions);
567 
568     myfree(table);
569     myfree(select_field);
570     myfree(where_field);
571     myfree(additional_conditions);
572 }
573