1/*	$NetBSD$	*/
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
161typedef 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
170int     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
240void    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
264int     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
276void    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
287int     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
509int     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
527void    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