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