1 /* $NetBSD: db_common.c,v 1.1.1.2 2010/04/17 10:24:30 tron 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\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. 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 /* \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). 59 /* 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 64 /* result expansion. 65 /* .PP 66 /* The following '%' expansions are performed on \fBvalue\fR: 67 /* .IP %% 68 /* A literal percent character. 69 /* .IP %s 70 /* The entire lookup key \fIaddr\fR. 71 /* .IP %u 72 /* If \fBaddr\fR is a fully qualified address, the local part of the 73 /* address. Otherwise \fIaddr\fR. 74 /* .IP %d 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. 78 /* 79 /* The following '%' expansions are performed on the lookup \fBkey\fR: 80 /* .IP %S 81 /* The entire lookup key \fIkey\fR. 82 /* .IP %U 83 /* If \fBkey\fR is a fully qualified address, the local part of the 84 /* address. Otherwise \fIkey\fR. 85 /* .IP %D 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. 89 /* 90 /* .PP 91 /* \fIdb_common_check_domain\fR checks domain list so that query optimization 92 /* can be performed 93 /* 94 /* .PP 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. 98 /* 99 /* DIAGNOSTICS 100 /* Fatal errors: invalid substitution format, invalid string_list pattern, 101 /* insufficient parameters. 102 /* SEE ALSO 103 /* dict(3) dictionary manager 104 /* string_list(3) string list pattern matching 105 /* match_ops(3) simple string or host pattern matching 106 /* LICENSE 107 /* .ad 108 /* .fi 109 /* The Secure Mailer license must be distributed with this software. 110 /* AUTHOR(S) 111 /* Wietse Venema 112 /* IBM T.J. Watson Research 113 /* P.O. Box 704 114 /* Yorktown Heights, NY 10598, USA 115 /* 116 /* Liviu Daia 117 /* Institute of Mathematics of the Romanian Academy 118 /* P.O. BOX 1-764 119 /* RO-014700 Bucharest, ROMANIA 120 /* 121 /* Jose Luis Tallon 122 /* G4 J.E. - F.I. - U.P.M. 123 /* Campus de Montegancedo, S/N 124 /* E-28660 Madrid, SPAIN 125 /* 126 /* Victor Duchovni 127 /* Morgan Stanley 128 /*--*/ 129 130 /* 131 * System library. 132 */ 133 #include "sys_defs.h" 134 #include <stddef.h> 135 #include <string.h> 136 137 /* 138 * Global library. 139 */ 140 #include "cfg_parser.h" 141 142 /* 143 * Utility library. 144 */ 145 #include <mymalloc.h> 146 #include <vstring.h> 147 #include <msg.h> 148 #include <dict.h> 149 150 /* 151 * Application specific 152 */ 153 #include "db_common.h" 154 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 */ 160 161 typedef struct { 162 DICT *dict; 163 STRING_LIST *domain; 164 int flags; 165 int nparts; 166 } DB_COMMON_CTX; 167 168 /* db_common_parse - validate query or result template */ 169 170 int db_common_parse(DICT *dict, void **ctxPtr, const char *format, int query) 171 { 172 DB_COMMON_CTX *ctx = (DB_COMMON_CTX *) * ctxPtr; 173 const char *cp; 174 int dynamic = 0; 175 176 if (ctx == 0) { 177 ctx = (DB_COMMON_CTX *) (*ctxPtr = mymalloc(sizeof *ctx)); 178 ctx->dict = dict; 179 ctx->domain = 0; 180 ctx->flags = 0; 181 ctx->nparts = 0; 182 } 183 for (cp = format; *cp; ++cp) 184 if (*cp == '%') 185 switch (*++cp) { 186 case '%': 187 break; 188 case 'u': 189 ctx->flags |= 190 query ? DB_COMMON_KEY_USER | DB_COMMON_KEY_PARTIAL 191 : DB_COMMON_VALUE_USER; 192 dynamic = 1; 193 break; 194 case 'd': 195 ctx->flags |= 196 query ? DB_COMMON_KEY_DOMAIN | DB_COMMON_KEY_PARTIAL 197 : DB_COMMON_VALUE_DOMAIN; 198 dynamic = 1; 199 break; 200 case 's': 201 case 'S': 202 dynamic = 1; 203 break; 204 case 'U': 205 ctx->flags |= DB_COMMON_KEY_PARTIAL | DB_COMMON_KEY_USER; 206 dynamic = 1; 207 break; 208 case '1': 209 case '2': 210 case '3': 211 case '4': 212 case '5': 213 case '6': 214 case '7': 215 case '8': 216 case '9': 217 218 /* 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. 223 */ 224 if (ctx->nparts < *cp - '0') 225 ctx->nparts = *cp - '0'; 226 /* FALLTHROUGH */ 227 case 'D': 228 ctx->flags |= DB_COMMON_KEY_PARTIAL | DB_COMMON_KEY_DOMAIN; 229 dynamic = 1; 230 break; 231 default: 232 msg_fatal("db_common_parse: %s: Invalid %s template: %s", 233 ctx->dict->name, query ? "query" : "result", format); 234 } 235 return dynamic; 236 } 237 238 /* db_common_parse_domain - parse domain matchlist*/ 239 240 void db_common_parse_domain(CFG_PARSER *parser, void *ctxPtr) 241 { 242 DB_COMMON_CTX *ctx = (DB_COMMON_CTX *) ctxPtr; 243 char *domainlist; 244 const char *myname = "db_common_parse_domain"; 245 246 domainlist = cfg_get_str(parser, "domain", "", 0, 0); 247 if (*domainlist) { 248 ctx->domain = string_list_init(MATCH_FLAG_NONE, domainlist); 249 if (ctx->domain == 0) 250 251 /* 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. 255 */ 256 msg_fatal("%s: %s: domain match list creation using '%s' failed", 257 myname, parser->name, domainlist); 258 } 259 myfree(domainlist); 260 } 261 262 /* db_common_dict_partial - Does query use partial lookup keys? */ 263 264 int db_common_dict_partial(void *ctxPtr) 265 { 266 #if 0 /* Breaks recipient_delimiter */ 267 DB_COMMON_CTX *ctx = (DB_COMMON_CTX *) ctxPtr; 268 269 return (ctx->domain || ctx->flags & DB_COMMON_KEY_PARTIAL); 270 #endif 271 return (0); 272 } 273 274 /* db_common_free_ctx - free parse context */ 275 276 void db_common_free_ctx(void *ctxPtr) 277 { 278 DB_COMMON_CTX *ctx = (DB_COMMON_CTX *) ctxPtr; 279 280 if (ctx->domain) 281 string_list_free(ctx->domain); 282 myfree((char *) ctxPtr); 283 } 284 285 /* db_common_expand - expand query and result templates */ 286 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) 290 { 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; 297 char *vuser = 0; 298 char *kuser = 0; 299 ARGV *parts = 0; 300 int i; 301 const char *cp; 302 303 /* Skip NULL values, silently. */ 304 if (value == 0) 305 return (0); 306 307 /* Don't silenty skip empty query string or empty lookup results. */ 308 if (*value == 0) { 309 if (key) 310 msg_warn("table \"%s:%s\": empty lookup result for: \"%s\"" 311 " -- ignored", ctx->dict->type, ctx->dict->name, key); 312 else 313 msg_warn("table \"%s:%s\": empty query string" 314 " -- ignored", ctx->dict->type, ctx->dict->name); 315 return (0); 316 } 317 if (key) { 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) 321 ++vdomain; 322 323 if (((!vdomain || !*vdomain) && (ctx->flags & DB_COMMON_VALUE_DOMAIN) != 0) 324 || (vdomain == value + 1 && (ctx->flags & DB_COMMON_VALUE_USER) != 0)) 325 return (0); 326 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) 330 ++kdomain; 331 332 /* 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. 335 */ 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); 340 return (0); 341 } 342 } else { 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) 346 ++vdomain; 347 348 if (((!vdomain || !*vdomain) && (ctx->flags & DB_COMMON_KEY_DOMAIN) != 0) 349 || (vdomain == value + 1 && (ctx->flags & DB_COMMON_KEY_USER) != 0)) 350 return (0); 351 } 352 353 if (ctx->nparts > 0) { 354 parts = argv_split(key ? kdomain : vdomain, "."); 355 356 /* 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. 360 */ 361 if (parts->argc < ctx->nparts) { 362 argv_free(parts); 363 return (0); 364 } 365 366 /* 367 * Skip domains with leading, consecutive or trailing '.' separators 368 * among the required labels. 369 */ 370 for (i = 0; i < ctx->nparts; i++) 371 if (*parts->argv[parts->argc - i - 1] == 0) { 372 argv_free(parts); 373 return (0); 374 } 375 } 376 if (VSTRING_LEN(result) > 0) 377 VSTRING_ADDCH(result, ','); 378 379 #define QUOTE_VAL(d, q, v, buf) do { \ 380 if (q) \ 381 q(d, v, buf); \ 382 else \ 383 vstring_strcat(buf, v); \ 384 } while (0) 385 386 /* 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 390 */ 391 for (cp = format; *cp; cp++) { 392 if (*cp == '%') { 393 switch (*++cp) { 394 395 case '%': 396 VSTRING_ADDCH(result, '%'); 397 break; 398 399 case 's': 400 QUOTE_VAL(ctx->dict, quote_func, value, result); 401 break; 402 403 case 'u': 404 if (vdomain) { 405 if (vuser == 0) 406 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 509 int 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 527 void 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