1193323Sed/*	$NetBSD$	*/
2193323Sed
3193323Sed/*++
4193323Sed/* NAME
5193323Sed/*	db_common 3
6193323Sed/* SUMMARY
7193323Sed/*	utilities common to network based dictionaries
8193323Sed/* SYNOPSIS
9193323Sed/*	#include "db_common.h"
10193323Sed/*
11193323Sed/*	int	db_common_parse(dict, ctx, format, query)
12193323Sed/*	DICT	*dict;
13193323Sed/*	void	**ctx;
14193323Sed/*	const char *format;
15193323Sed/*	int	query;
16193323Sed/*
17193323Sed/*	void	db_common_free_context(ctx)
18193323Sed/*	void	*ctx;
19193323Sed/*
20193323Sed/*	int	db_common_expand(ctx, format, value, key, buf, quote_func);
21193323Sed/*	void	*ctx;
22199481Srdivacky/*	const char *format;
23193323Sed/*	const char *value;
24193323Sed/*	const char *key;
25194612Sed/*	VSTRING	*buf;
26193323Sed/*	void	(*quote_func)(DICT *, const char *, VSTRING *);
27193323Sed/*
28193323Sed/*	int	db_common_check_domain(domain_list, addr);
29193323Sed/*	STRING_LIST *domain_list;
30198090Srdivacky/*	const char *addr;
31193323Sed/*
32193323Sed/*	void	db_common_sql_build_query(query,parser);
33193323Sed/*	VSTRING	*query;
34193323Sed/*	CFG_PARSER *parser;
35198090Srdivacky/*
36193323Sed/* DESCRIPTION
37193323Sed/*	This module implements utilities common to network based dictionaries.
38193323Sed/*
39193323Sed/*	\fIdb_common_parse\fR parses query and result substitution templates.
40193323Sed/*	It must be called for each template before any calls to
41198090Srdivacky/*	\fIdb_common_expand\fR. The \fIctx\fB argument must be initialized to
42198090Srdivacky/*	a reference to a (void *)0 before the first template is parsed, this
43198090Srdivacky/*	causes memory for the context to be allocated and the new pointer is
44198090Srdivacky/*	stored in *ctx. When the dictionary is closed, this memory must be
45193323Sed/*	freed with a final call to \fBdb_common_free_context\fR.
46193323Sed/*
47193323Sed/*	Calls for additional templates associated with the same map must use the
48193323Sed/*	same ctx argument. The context accumulates run-time lookup key and result
49199481Srdivacky/*	validation information (inapplicable keys or results are skipped) and is
50199481Srdivacky/*	needed later in each call of \fIdb_common_expand\fR. A non-zero return
51199481Srdivacky/*	value indicates that data-depedent '%' expansions were found in the input
52199481Srdivacky/*	template.
53199481Srdivacky/*
54199481Srdivacky/*	\fIdb_common_expand\fR expands the specifiers in \fIformat\fR.
55199481Srdivacky/*	When the input data lacks all fields needed for the expansion, zero
56193323Sed/*	is returned and the query or result should be skipped. Otherwise
57198090Srdivacky/*	the expansion is appended to the result buffer (after a comma if the
58198090Srdivacky/*	the result buffer is not empty).
59193323Sed/*
60193323Sed/*	If not NULL, the \fBquote_func\fR callback performs database-specific
61193323Sed/*	quoting of each variable before expansion.
62193323Sed/*	\fBvalue\fR is the lookup key for query expansion and result for result
63193323Sed/*	expansion. \fBkey\fR is NULL for query expansion and the lookup key for
64193323Sed/*	result expansion.
65193323Sed/* .PP
66193323Sed/*	The following '%' expansions are performed on \fBvalue\fR:
67193323Sed/* .IP %%
68193323Sed/*	A literal percent character.
69193323Sed/* .IP %s
70193323Sed/*	The entire lookup key \fIaddr\fR.
71193323Sed/* .IP %u
72193323Sed/*	If \fBaddr\fR is a fully qualified address, the local part of the
73193323Sed/*	address.  Otherwise \fIaddr\fR.
74193323Sed/* .IP %d
75193323Sed/*	If \fIaddr\fR is a fully qualified address, the domain part of the
76193323Sed/*	address.  Otherwise the query against the database is suppressed and
77193323Sed/*	the lookup returns no results.
78193323Sed/*
79193323Sed/*	The following '%' expansions are performed on the lookup \fBkey\fR:
80193323Sed/* .IP %S
81193323Sed/*	The entire lookup key \fIkey\fR.
82193323Sed/* .IP %U
83193323Sed/*	If \fBkey\fR is a fully qualified address, the local part of the
84193323Sed/*	address.  Otherwise \fIkey\fR.
85193323Sed/* .IP %D
86193323Sed/*	If \fIkey\fR is a fully qualified address, the domain part of the
87193323Sed/*	address.  Otherwise the query against the database is suppressed and
88193323Sed/*	the lookup returns no results.
89193323Sed/*
90193323Sed/* .PP
91193323Sed/*	\fIdb_common_check_domain\fR checks domain list so that query optimization
92193323Sed/*	can be performed
93193323Sed/*
94193323Sed/* .PP
95193323Sed/*	\fIdb_common_sql_build_query\fR builds the "default"(backwards compatible)
96193323Sed/*	query from the 'table', 'select_field', 'where_field' and
97193323Sed/*	'additional_conditions' parameters, checking for errors.
98193323Sed/*
99193323Sed/* DIAGNOSTICS
100193323Sed/*	Fatal errors: invalid substitution format, invalid string_list pattern,
101193323Sed/*	insufficient parameters.
102193323Sed/* SEE ALSO
103193323Sed/*	dict(3) dictionary manager
104193323Sed/*	string_list(3) string list pattern matching
105193323Sed/*	match_ops(3) simple string or host pattern matching
106193323Sed/* LICENSE
107193323Sed/* .ad
108193323Sed/* .fi
109193323Sed/*	The Secure Mailer license must be distributed with this software.
110193323Sed/* AUTHOR(S)
111193323Sed/*	Wietse Venema
112193323Sed/*	IBM T.J. Watson Research
113193323Sed/*	P.O. Box 704
114193323Sed/*	Yorktown Heights, NY 10598, USA
115193323Sed/*
116193323Sed/*	Liviu Daia
117193323Sed/*	Institute of Mathematics of the Romanian Academy
118199481Srdivacky/*	P.O. BOX 1-764
119199481Srdivacky/*	RO-014700 Bucharest, ROMANIA
120199481Srdivacky/*
121199481Srdivacky/*	Jose Luis Tallon
122199481Srdivacky/*	G4 J.E. - F.I. - U.P.M.
123199481Srdivacky/*	Campus de Montegancedo, S/N
124199481Srdivacky/*	E-28660 Madrid, SPAIN
125199481Srdivacky/*
126199481Srdivacky/*	Victor Duchovni
127199481Srdivacky/*	Morgan Stanley
128199481Srdivacky/*--*/
129199481Srdivacky
130199481Srdivacky /*
131199481Srdivacky  * System library.
132199481Srdivacky  */
133199481Srdivacky#include "sys_defs.h"
134193323Sed#include <stddef.h>
135193323Sed#include <string.h>
136194612Sed
137193323Sed /*
138193323Sed  * Global library.
139193323Sed  */
140193323Sed#include "cfg_parser.h"
141193323Sed
142193323Sed /*
143193323Sed  * Utility library.
144193323Sed  */
145193323Sed#include <mymalloc.h>
146193323Sed#include <vstring.h>
147193323Sed#include <msg.h>
148193323Sed#include <dict.h>
149193323Sed
150193323Sed /*
151193323Sed  * Application specific
152193323Sed  */
153193323Sed#include "db_common.h"
154193323Sed
155193323Sed#define	DB_COMMON_KEY_DOMAIN	(1 << 0)/* Need lookup key domain */
156193323Sed#define	DB_COMMON_KEY_USER	(1 << 1)/* Need lookup key localpart */
157193323Sed#define	DB_COMMON_VALUE_DOMAIN	(1 << 2)/* Need result domain */
158193323Sed#define	DB_COMMON_VALUE_USER	(1 << 3)/* Need result localpart */
159193323Sed#define	DB_COMMON_KEY_PARTIAL	(1 << 4)/* Key uses input substrings */
160193323Sed
161193323Sedtypedef struct {
162193323Sed    DICT   *dict;
163193323Sed    STRING_LIST *domain;
164198090Srdivacky    int     flags;
165198090Srdivacky    int     nparts;
166198090Srdivacky} DB_COMMON_CTX;
167193323Sed
168193323Sed/* db_common_parse - validate query or result template */
169193323Sed
170193323Sedint     db_common_parse(DICT *dict, void **ctxPtr, const char *format, int query)
171198090Srdivacky{
172193323Sed    DB_COMMON_CTX *ctx = (DB_COMMON_CTX *) *ctxPtr;
173193323Sed    const char *cp;
174193323Sed    int     dynamic = 0;
175193323Sed
176193323Sed    if (ctx == 0) {
177193323Sed	ctx = (DB_COMMON_CTX *) (*ctxPtr = mymalloc(sizeof *ctx));
178193323Sed	ctx->dict = dict;
179193323Sed	ctx->domain = 0;
180193323Sed	ctx->flags = 0;
181193323Sed	ctx->nparts = 0;
182193323Sed    }
183193323Sed    for (cp = format; *cp; ++cp)
184193323Sed	if (*cp == '%')
185193323Sed	    switch (*++cp) {
186193323Sed	    case '%':
187193323Sed		break;
188193323Sed	    case 'u':
189193323Sed		ctx->flags |=
190193323Sed		    query ? DB_COMMON_KEY_USER | DB_COMMON_KEY_PARTIAL
191193323Sed		    : DB_COMMON_VALUE_USER;
192193323Sed		dynamic = 1;
193193323Sed		break;
194193323Sed	    case 'd':
195193323Sed		ctx->flags |=
196193323Sed		    query ? DB_COMMON_KEY_DOMAIN | DB_COMMON_KEY_PARTIAL
197193323Sed		    : DB_COMMON_VALUE_DOMAIN;
198193323Sed		dynamic = 1;
199193323Sed		break;
200193323Sed	    case 's':
201193323Sed	    case 'S':
202193323Sed		dynamic = 1;
203193323Sed		break;
204193323Sed	    case 'U':
205193323Sed		ctx->flags |= DB_COMMON_KEY_PARTIAL | DB_COMMON_KEY_USER;
206193323Sed		dynamic = 1;
207193323Sed		break;
208193323Sed	    case '1':
209193323Sed	    case '2':
210193323Sed	    case '3':
211193323Sed	    case '4':
212193323Sed	    case '5':
213193323Sed	    case '6':
214193323Sed	    case '7':
215193323Sed	    case '8':
216193323Sed	    case '9':
217193323Sed
218193323Sed		/*
219193323Sed		 * Find highest %[1-9] index in query template. Input keys
220193323Sed		 * will be constrained to those with at least this many
221193323Sed		 * domain components. This makes the db_common_expand() code
222193323Sed		 * safe from invalid inputs.
223193323Sed		 */
224193323Sed		if (ctx->nparts < *cp - '0')
225193323Sed		    ctx->nparts = *cp - '0';
226193323Sed		/* FALLTHROUGH */
227193323Sed	    case 'D':
228193323Sed		ctx->flags |= DB_COMMON_KEY_PARTIAL | DB_COMMON_KEY_DOMAIN;
229193323Sed		dynamic = 1;
230193323Sed		break;
231193323Sed	    default:
232193323Sed		msg_fatal("db_common_parse: %s: Invalid %s template: %s",
233198090Srdivacky		       ctx->dict->name, query ? "query" : "result", format);
234193323Sed	    }
235193323Sed    return dynamic;
236193323Sed}
237193323Sed
238193323Sed/* db_common_parse_domain - parse domain matchlist*/
239193323Sed
240193323Sedvoid    db_common_parse_domain(CFG_PARSER *parser, void *ctxPtr)
241193323Sed{
242193323Sed    DB_COMMON_CTX *ctx = (DB_COMMON_CTX *) ctxPtr;
243193323Sed    char   *domainlist;
244193323Sed    const char *myname = "db_common_parse_domain";
245193323Sed
246193323Sed    domainlist = cfg_get_str(parser, "domain", "", 0, 0);
247193323Sed    if (*domainlist) {
248193323Sed	ctx->domain = string_list_init(MATCH_FLAG_NONE, domainlist);
249193323Sed	if (ctx->domain == 0)
250193323Sed
251193323Sed	    /*
252193323Sed	     * The "domain" optimization skips input keys that may in fact
253193323Sed	     * have unwanted matches in the database, so failure to create
254193323Sed	     * the match list is fatal.
255193323Sed	     */
256193323Sed	    msg_fatal("%s: %s: domain match list creation using '%s' failed",
257193323Sed		      myname, parser->name, domainlist);
258193323Sed    }
259193323Sed    myfree(domainlist);
260193323Sed}
261193323Sed
262193323Sed/* db_common_dict_partial - Does query use partial lookup keys? */
263193323Sed
264193323Sedint     db_common_dict_partial(void *ctxPtr)
265193323Sed{
266193323Sed#if 0					/* Breaks recipient_delimiter */
267193323Sed    DB_COMMON_CTX *ctx = (DB_COMMON_CTX *) ctxPtr;
268193323Sed
269193323Sed    return (ctx->domain || ctx->flags & DB_COMMON_KEY_PARTIAL);
270193323Sed#endif
271193323Sed    return (0);
272193323Sed}
273193323Sed
274193323Sed/* db_common_free_ctx - free parse context */
275193323Sed
276193323Sedvoid    db_common_free_ctx(void *ctxPtr)
277193323Sed{
278193323Sed    DB_COMMON_CTX *ctx = (DB_COMMON_CTX *) ctxPtr;
279193323Sed
280193323Sed    if (ctx->domain)
281193323Sed	string_list_free(ctx->domain);
282193323Sed    myfree((char *) ctxPtr);
283193323Sed}
284193323Sed
285193323Sed/* db_common_expand - expand query and result templates */
286193323Sed
287193323Sedint     db_common_expand(void *ctxArg, const char *format, const char *value,
288193323Sed			         const char *key, VSTRING *result,
289193323Sed			         db_quote_callback_t quote_func)
290193323Sed{
291202375Srdivacky    const char *myname = "db_common_expand";
292193323Sed    DB_COMMON_CTX *ctx = (DB_COMMON_CTX *) ctxArg;
293193323Sed    const char *vdomain = 0;
294193323Sed    const char *kdomain = 0;
295193323Sed    const char *domain = 0;
296193323Sed    int     dflag = key ? DB_COMMON_VALUE_DOMAIN : DB_COMMON_KEY_DOMAIN;
297193323Sed    char   *vuser = 0;
298193323Sed    char   *kuser = 0;
299202375Srdivacky    ARGV   *parts = 0;
300202375Srdivacky    int     i;
301202375Srdivacky    const char *cp;
302202375Srdivacky
303202375Srdivacky    /* Skip NULL values, silently. */
304193323Sed    if (value == 0)
305193323Sed	return (0);
306193323Sed
307198090Srdivacky    /* Don't silenty skip empty query string or empty lookup results. */
308198090Srdivacky    if (*value == 0) {
309193323Sed	if (key)
310193323Sed	    msg_warn("table \"%s:%s\": empty lookup result for: \"%s\""
311193323Sed		     " -- ignored", ctx->dict->type, ctx->dict->name, key);
312198090Srdivacky	else
313198090Srdivacky	    msg_warn("table \"%s:%s\": empty query string"
314193323Sed		     " -- ignored", ctx->dict->type, ctx->dict->name);
315193323Sed	return (0);
316193323Sed    }
317193323Sed    if (key) {
318193323Sed	/* This is a result template and the input value is the result */
319193323Sed	if (ctx->flags & (DB_COMMON_VALUE_DOMAIN | DB_COMMON_VALUE_USER))
320193323Sed	    if ((vdomain = strrchr(value, '@')) != 0)
321198090Srdivacky		++vdomain;
322198090Srdivacky
323198090Srdivacky	if (((!vdomain || !*vdomain) && (ctx->flags & DB_COMMON_VALUE_DOMAIN) != 0)
324198090Srdivacky	    || (vdomain == value + 1 && (ctx->flags & DB_COMMON_VALUE_USER) != 0))
325198090Srdivacky	    return (0);
326198090Srdivacky
327198090Srdivacky	/* The result format may use the local or domain part of the key */
328200581Srdivacky	if (ctx->flags & (DB_COMMON_KEY_DOMAIN | DB_COMMON_KEY_USER))
329200581Srdivacky	    if ((kdomain = strrchr(key, '@')) != 0)
330200581Srdivacky		++kdomain;
331200581Srdivacky
332200581Srdivacky	/*
333193323Sed	 * The key should already be checked before the query. No harm if the
334193323Sed	 * query did not get optimized out, so we just issue a warning.
335193323Sed	 */
336193323Sed	if (((!kdomain || !*kdomain) && (ctx->flags & DB_COMMON_KEY_DOMAIN) != 0)
337193323Sed	|| (kdomain == key + 1 && (ctx->flags & DB_COMMON_KEY_USER) != 0)) {
338193323Sed	    msg_warn("%s: %s: lookup key '%s' skipped after query", myname,
339193323Sed		     ctx->dict->name, value);
340193323Sed	    return (0);
341193323Sed	}
342193323Sed    } else {
343193323Sed	/* This is a query template and the input value is the lookup key */
344193323Sed	if (ctx->flags & (DB_COMMON_KEY_DOMAIN | DB_COMMON_KEY_USER))
345193323Sed	    if ((vdomain = strrchr(value, '@')) != 0)
346193323Sed		++vdomain;
347193323Sed
348193323Sed	if (((!vdomain || !*vdomain) && (ctx->flags & DB_COMMON_KEY_DOMAIN) != 0)
349193323Sed	|| (vdomain == value + 1 && (ctx->flags & DB_COMMON_KEY_USER) != 0))
350193323Sed	    return (0);
351193323Sed    }
352193323Sed
353193323Sed    if (ctx->nparts > 0) {
354193323Sed	parts = argv_split(key ? kdomain : vdomain, ".");
355193323Sed
356193323Sed	/*
357193323Sed	 * Filter out input keys whose domains lack enough labels to fill-in
358193323Sed	 * the query template. See below and also db_common_parse() which
359193323Sed	 * initializes ctx->nparts.
360193323Sed	 */
361193323Sed	if (parts->argc < ctx->nparts) {
362193323Sed	    argv_free(parts);
363198090Srdivacky	    return (0);
364198090Srdivacky	}
365198090Srdivacky
366198090Srdivacky	/*
367193323Sed	 * Skip domains with leading, consecutive or trailing '.' separators
368198090Srdivacky	 * among the required labels.
369198090Srdivacky	 */
370198090Srdivacky	for (i = 0; i < ctx->nparts; i++)
371198090Srdivacky	    if (*parts->argv[parts->argc - i - 1] == 0) {
372198090Srdivacky		argv_free(parts);
373198090Srdivacky		return (0);
374193323Sed	    }
375193323Sed    }
376193323Sed    if (VSTRING_LEN(result) > 0)
377193323Sed	VSTRING_ADDCH(result, ',');
378193323Sed
379193323Sed#define QUOTE_VAL(d, q, v, buf) do { \
380193323Sed	if (q) \
381193323Sed	    q(d, v, buf); \
382193323Sed	else \
383193323Sed	    vstring_strcat(buf, v); \
384193323Sed    } while (0)
385193323Sed
386193323Sed    /*
387193323Sed     * Replace all instances of %s with the address to look up. Replace %u
388193323Sed     * with the user portion, and %d with the domain portion. "%%" expands to
389193323Sed     * "%".  lowercase -> addr, uppercase -> key
390193323Sed     */
391193323Sed    for (cp = format; *cp; cp++) {
392193323Sed	if (*cp == '%') {
393193323Sed	    switch (*++cp) {
394193323Sed
395193323Sed	    case '%':
396193323Sed		VSTRING_ADDCH(result, '%');
397193323Sed		break;
398193323Sed
399193323Sed	    case 's':
400193323Sed		QUOTE_VAL(ctx->dict, quote_func, value, result);
401193323Sed		break;
402193323Sed
403193323Sed	    case 'u':
404193323Sed		if (vdomain) {
405193323Sed		    if (vuser == 0)
406193323Sed			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