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