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