7 /* utilities common to network based dictionaries
9 /* #include "db_common.h"
11 /* int db_common_parse(dict, ctx, format, query)
14 /* const char *format;
17 /* void db_common_free_context(ctx)
20 /* int db_common_expand(ctx, format, value, key, buf, quote_func);
22 /* const char *format;
26 /* void (*quote_func)(DICT *, const char *, VSTRING *);
28 /* int db_common_check_domain(domain_list, addr);
29 /* STRING_LIST *domain_list;
32 /* void db_common_sql_build_query(query,parser);
34 /* CFG_PARSER *parser;
37 /* This module implements utilities common to network based dictionaries.
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.
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
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).
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
66 /* The following '%' expansions are performed on \fBvalue\fR:
68 /* A literal percent character.
70 /* The entire lookup key \fIaddr\fR.
72 /* If \fBaddr\fR is a fully qualified address, the local part of the
73 /* address. Otherwise \fIaddr\fR.
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.
79 /* The following '%' expansions are performed on the lookup \fBkey\fR:
81 /* The entire lookup key \fIkey\fR.
83 /* If \fBkey\fR is a fully qualified address, the local part of the
84 /* address. Otherwise \fIkey\fR.
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.
91 /* \fIdb_common_check_domain\fR checks domain list so that query optimization
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.
100 /* Fatal errors: invalid substitution format, invalid string_list pattern,
101 /* insufficient parameters.
103 /* dict(3) dictionary manager
104 /* string_list(3) string list pattern matching
105 /* match_ops(3) simple string or host pattern matching
109 /* The Secure Mailer license must be distributed with this software.
112 /* IBM T.J. Watson Research
114 /* Yorktown Heights, NY 10598, USA
117 /* Institute of Mathematics of the Romanian Academy
119 /* RO-014700 Bucharest, ROMANIA
122 /* G4 J.E. - F.I. - U.P.M.
123 /* Campus de Montegancedo, S/N
124 /* E-28660 Madrid, SPAIN
133 #include "sys_defs.h"
140 #include "cfg_parser.h"
145 #include <mymalloc.h>
151 * Application specific
153 #include "db_common.h"
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 */
168 /* db_common_parse - validate query or result template */
170 int db_common_parse(DICT
*dict
, void **ctxPtr
, const char *format
, int query
)
172 DB_COMMON_CTX
*ctx
= (DB_COMMON_CTX
*) * ctxPtr
;
177 ctx
= (DB_COMMON_CTX
*) (*ctxPtr
= mymalloc(sizeof *ctx
));
183 for (cp
= format
; *cp
; ++cp
)
190 query
? DB_COMMON_KEY_USER
| DB_COMMON_KEY_PARTIAL
191 : DB_COMMON_VALUE_USER
;
196 query
? DB_COMMON_KEY_DOMAIN
| DB_COMMON_KEY_PARTIAL
197 : DB_COMMON_VALUE_DOMAIN
;
205 ctx
->flags
|= DB_COMMON_KEY_PARTIAL
| DB_COMMON_KEY_USER
;
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.
224 if (ctx
->nparts
< *cp
- '0')
225 ctx
->nparts
= *cp
- '0';
228 ctx
->flags
|= DB_COMMON_KEY_PARTIAL
| DB_COMMON_KEY_DOMAIN
;
232 msg_fatal("db_common_parse: %s: Invalid %s template: %s",
233 dict
->name
, query
? "query" : "result", format
);
238 /* db_common_parse_domain - parse domain matchlist*/
240 void db_common_parse_domain(CFG_PARSER
*parser
, void *ctxPtr
)
242 DB_COMMON_CTX
*ctx
= (DB_COMMON_CTX
*) ctxPtr
;
244 const char *myname
= "db_common_parse_domain";
246 domainlist
= cfg_get_str(parser
, "domain", "", 0, 0);
248 ctx
->domain
= string_list_init(MATCH_FLAG_NONE
, domainlist
);
249 if (ctx
->domain
== 0)
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.
256 msg_fatal("%s: %s: domain match list creation using '%s' failed",
257 myname
, parser
->name
, domainlist
);
262 /* db_common_dict_partial - Does query use partial lookup keys? */
264 int db_common_dict_partial(void *ctxPtr
)
266 #if 0 /* Breaks recipient_delimiter */
267 DB_COMMON_CTX
*ctx
= (DB_COMMON_CTX
*) ctxPtr
;
269 return (ctx
->domain
|| ctx
->flags
& DB_COMMON_KEY_PARTIAL
);
274 /* db_common_free_ctx - free parse context */
276 void db_common_free_ctx(void *ctxPtr
)
278 DB_COMMON_CTX
*ctx
= (DB_COMMON_CTX
*) ctxPtr
;
281 string_list_free(ctx
->domain
);
282 myfree((char *) ctxPtr
);
285 /* db_common_expand - expand query and result templates */
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
)
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
;
303 /* Skip NULL values, silently. */
307 /* Don't silenty skip empty query string or empty lookup results. */
310 msg_warn("table \"%s:%s\": empty lookup result for: \"%s\""
311 " -- ignored", ctx
->dict
->type
, ctx
->dict
->name
, key
);
313 msg_warn("table \"%s:%s\": empty query string"
314 " -- ignored", ctx
->dict
->type
, ctx
->dict
->name
);
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)
323 if (((!vdomain
|| !*vdomain
) && (ctx
->flags
& DB_COMMON_VALUE_DOMAIN
) != 0)
324 || (vdomain
== value
+ 1 && (ctx
->flags
& DB_COMMON_VALUE_USER
) != 0))
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)
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.
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
);
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)
348 if (((!vdomain
|| !*vdomain
) && (ctx
->flags
& DB_COMMON_KEY_DOMAIN
) != 0)
349 || (vdomain
== value
+ 1 && (ctx
->flags
& DB_COMMON_KEY_USER
) != 0))
353 if (ctx
->nparts
> 0) {
354 parts
= argv_split(key
? kdomain
: vdomain
, ".");
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.
361 if (parts
->argc
< ctx
->nparts
) {
367 * Skip domains with leading, consecutive or trailing '.' separators
368 * among the required labels.
370 for (i
= 0; i
< ctx
->nparts
; i
++)
371 if (*parts
->argv
[parts
->argc
- i
- 1] == 0) {
376 if (VSTRING_LEN(result
) > 0)
377 VSTRING_ADDCH(result
, ',');
379 #define QUOTE_VAL(d, q, v, buf) do { \
383 vstring_strcat(buf, v); \
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
391 for (cp
= format
; *cp
; cp
++) {
396 VSTRING_ADDCH(result
, '%');
400 QUOTE_VAL(ctx
->dict
, quote_func
, value
, result
);
406 vuser
= mystrndup(value
, vdomain
- value
- 1);
407 QUOTE_VAL(ctx
->dict
, quote_func
, vuser
, result
);
409 QUOTE_VAL(ctx
->dict
, quote_func
, value
, result
);
413 if (!(ctx
->flags
& dflag
))
414 msg_panic("%s: %s: %s: bad query/result template context",
415 myname
, ctx
->dict
->name
, format
);
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
);
424 QUOTE_VAL(ctx
->dict
, quote_func
, key
, result
);
426 QUOTE_VAL(ctx
->dict
, quote_func
, value
, result
);
433 kuser
= mystrndup(key
, kdomain
- key
- 1);
434 QUOTE_VAL(ctx
->dict
, quote_func
, kuser
, result
);
436 QUOTE_VAL(ctx
->dict
, quote_func
, key
, result
);
440 vuser
= mystrndup(value
, vdomain
- value
- 1);
441 QUOTE_VAL(ctx
->dict
, quote_func
, vuser
, result
);
443 QUOTE_VAL(ctx
->dict
, quote_func
, value
, result
);
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
);
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.
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
);
487 msg_fatal("%s: %s: invalid %s template '%s'", myname
,
488 ctx
->dict
->name
, key
? "result" : "query",
492 VSTRING_ADDCH(result
, *cp
);
494 VSTRING_TERMINATE(result
);
507 /* db_common_check_domain - check domain list */
509 int db_common_check_domain(void *ctxPtr
, const char *addr
)
511 DB_COMMON_CTX
*ctx
= (DB_COMMON_CTX
*) ctxPtr
;
515 if ((domain
= strrchr(addr
, '@')) != NULL
)
517 if (domain
== NULL
|| domain
== addr
+ 1)
519 if (match_list_match(ctx
->domain
, domain
) == 0)
525 /* db_common_sql_build_query -- build query for SQL maptypes */
527 void db_common_sql_build_query(VSTRING
*query
, CFG_PARSER
*parser
)
529 const char *myname
= "db_common_sql_build_query";
533 char *additional_conditions
;
536 * Build "old style" query: "select %s from %s where %s"
538 if ((table
= cfg_get_str(parser
, "table", NULL
, 1, 0)) == 0)
539 msg_fatal("%s: 'table' parameter not defined", myname
);
541 if ((select_field
= cfg_get_str(parser
, "select_field", NULL
, 1, 0)) == 0)
542 msg_fatal("%s: 'select_field' parameter not defined", myname
);
544 if ((where_field
= cfg_get_str(parser
, "where_field", NULL
, 1, 0)) == 0)
545 msg_fatal("%s: 'where_field' parameter not defined", myname
);
547 additional_conditions
= cfg_get_str(parser
, "additional_conditions",
550 vstring_sprintf(query
, "SELECT %s FROM %s WHERE %s='%%s' %s",
551 select_field
, table
, where_field
,
552 additional_conditions
);
555 myfree(select_field
);
557 myfree(additional_conditions
);