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