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