1 /* $NetBSD: dict_memcache.c,v 1.2 2017/02/14 01:16:45 christos Exp $ */ 2 3 /*++ 4 /* NAME 5 /* dict_memcache 3 6 /* SUMMARY 7 /* dictionary interface to memcaches 8 /* SYNOPSIS 9 /* #include <dict_memcache.h> 10 /* 11 /* DICT *dict_memcache_open(name, open_flags, dict_flags) 12 /* const char *name; 13 /* int open_flags; 14 /* int dict_flags; 15 /* DESCRIPTION 16 /* dict_memcache_open() opens a memcache, providing 17 /* a dictionary interface for Postfix key->value mappings. 18 /* The result is a pointer to the installed dictionary. 19 /* 20 /* Configuration parameters are described in memcache_table(5). 21 /* 22 /* Arguments: 23 /* .IP name 24 /* The path to the Postfix memcache configuration file. 25 /* .IP open_flags 26 /* O_RDONLY or O_RDWR. This function ignores flags that don't 27 /* specify a read, write or append mode. 28 /* .IP dict_flags 29 /* See dict_open(3). 30 /* SEE ALSO 31 /* dict(3) generic dictionary manager 32 /* HISTORY 33 /* .ad 34 /* .fi 35 /* The first memcache client for Postfix was written by Omar 36 /* Kilani, and was based on libmemcache. The current 37 /* implementation implements the memcache protocol directly, 38 /* and bears no resemblance to earlier work. 39 /* AUTHOR(S) 40 /* Wietse Venema 41 /* IBM T.J. Watson Research 42 /* P.O. Box 704 43 /* Yorktown Heights, NY 10598, USA 44 /*--*/ 45 46 /* System library. */ 47 48 #include <sys_defs.h> 49 #include <errno.h> 50 #include <string.h> 51 #include <ctype.h> 52 #include <stdio.h> /* XXX sscanf() */ 53 54 /* Utility library. */ 55 56 #include <msg.h> 57 #include <mymalloc.h> 58 #include <dict.h> 59 #include <vstring.h> 60 #include <stringops.h> 61 #include <auto_clnt.h> 62 #include <vstream.h> 63 64 /* Global library. */ 65 66 #include <cfg_parser.h> 67 #include <db_common.h> 68 #include <memcache_proto.h> 69 70 /* Application-specific. */ 71 72 #include <dict_memcache.h> 73 74 /* 75 * Structure of one memcache dictionary handle. 76 */ 77 typedef struct { 78 DICT dict; /* parent class */ 79 CFG_PARSER *parser; /* common parameter parser */ 80 void *dbc_ctxt; /* db_common context */ 81 char *key_format; /* query key translation */ 82 int timeout; /* client timeout */ 83 int mc_ttl; /* memcache update expiration */ 84 int mc_flags; /* memcache update flags */ 85 int err_pause; /* delay between errors */ 86 int max_tries; /* number of tries */ 87 int max_line; /* reply line limit */ 88 int max_data; /* reply data limit */ 89 char *memcache; /* memcache server spec */ 90 AUTO_CLNT *clnt; /* memcache client stream */ 91 VSTRING *clnt_buf; /* memcache client buffer */ 92 VSTRING *key_buf; /* lookup key */ 93 VSTRING *res_buf; /* lookup result */ 94 int error; /* memcache dict_errno */ 95 DICT *backup; /* persistent backup */ 96 } DICT_MC; 97 98 /* 99 * Memcache option defaults and names. 100 */ 101 #define DICT_MC_DEF_HOST "localhost" 102 #define DICT_MC_DEF_PORT "11211" 103 #define DICT_MC_DEF_MEMCACHE "inet:" DICT_MC_DEF_HOST ":" DICT_MC_DEF_PORT 104 #define DICT_MC_DEF_KEY_FMT "%s" 105 #define DICT_MC_DEF_MC_TTL 3600 106 #define DICT_MC_DEF_MC_TIMEOUT 2 107 #define DICT_MC_DEF_MC_FLAGS 0 108 #define DICT_MC_DEF_MAX_TRY 2 109 #define DICT_MC_DEF_MAX_LINE 1024 110 #define DICT_MC_DEF_MAX_DATA 10240 111 #define DICT_MC_DEF_ERR_PAUSE 1 112 113 #define DICT_MC_NAME_MEMCACHE "memcache" 114 #define DICT_MC_NAME_BACKUP "backup" 115 #define DICT_MC_NAME_KEY_FMT "key_format" 116 #define DICT_MC_NAME_MC_TTL "ttl" 117 #define DICT_MC_NAME_MC_TIMEOUT "timeout" 118 #define DICT_MC_NAME_MC_FLAGS "flags" 119 #define DICT_MC_NAME_MAX_TRY "max_try" 120 #define DICT_MC_NAME_MAX_LINE "line_size_limit" 121 #define DICT_MC_NAME_MAX_DATA "data_size_limit" 122 #define DICT_MC_NAME_ERR_PAUSE "retry_pause" 123 124 /* 125 * SLMs. 126 */ 127 #define STR(x) vstring_str(x) 128 #define LEN(x) VSTRING_LEN(x) 129 130 /*#define msg_verbose 1*/ 131 132 /* dict_memcache_set - set memcache key/value */ 133 134 static int dict_memcache_set(DICT_MC *dict_mc, const char *value, int ttl) 135 { 136 VSTREAM *fp; 137 int count; 138 size_t data_len = strlen(value); 139 140 /* 141 * Return a permanent error if we can't store this data. This results in 142 * loss of information. 143 */ 144 if (data_len > dict_mc->max_data) { 145 msg_warn("database %s:%s: data for key %s is too long (%s=%d) " 146 "-- not stored", DICT_TYPE_MEMCACHE, dict_mc->dict.name, 147 STR(dict_mc->key_buf), DICT_MC_NAME_MAX_DATA, 148 dict_mc->max_data); 149 /* Not stored! */ 150 DICT_ERR_VAL_RETURN(dict_mc, DICT_ERR_NONE, DICT_STAT_FAIL); 151 } 152 for (count = 0; count < dict_mc->max_tries; count++) { 153 if (count > 0) 154 sleep(dict_mc->err_pause); 155 if ((fp = auto_clnt_access(dict_mc->clnt)) == 0) { 156 break; 157 } else if (memcache_printf(fp, "set %s %d %d %ld", 158 STR(dict_mc->key_buf), dict_mc->mc_flags, 159 ttl, (long) data_len) < 0 160 || memcache_fwrite(fp, value, strlen(value)) < 0 161 || memcache_get(fp, dict_mc->clnt_buf, 162 dict_mc->max_line) < 0) { 163 if (count > 0) 164 msg_warn(errno ? "database %s:%s: I/O error: %m" : 165 "database %s:%s: I/O error", 166 DICT_TYPE_MEMCACHE, dict_mc->dict.name); 167 } else if (strcmp(STR(dict_mc->clnt_buf), "STORED") != 0) { 168 if (count > 0) 169 msg_warn("database %s:%s: update failed: %.30s", 170 DICT_TYPE_MEMCACHE, dict_mc->dict.name, 171 STR(dict_mc->clnt_buf)); 172 } else { 173 /* Victory! */ 174 DICT_ERR_VAL_RETURN(dict_mc, DICT_ERR_NONE, DICT_STAT_SUCCESS); 175 } 176 auto_clnt_recover(dict_mc->clnt); 177 } 178 DICT_ERR_VAL_RETURN(dict_mc, DICT_ERR_RETRY, DICT_STAT_ERROR); 179 } 180 181 /* dict_memcache_get - get memcache key/value */ 182 183 static const char *dict_memcache_get(DICT_MC *dict_mc) 184 { 185 VSTREAM *fp; 186 long todo; 187 int count; 188 189 for (count = 0; count < dict_mc->max_tries; count++) { 190 if (count > 0) 191 sleep(dict_mc->err_pause); 192 if ((fp = auto_clnt_access(dict_mc->clnt)) == 0) { 193 break; 194 } else if (memcache_printf(fp, "get %s", STR(dict_mc->key_buf)) < 0 195 || memcache_get(fp, dict_mc->clnt_buf, dict_mc->max_line) < 0) { 196 if (count > 0) 197 msg_warn(errno ? "database %s:%s: I/O error: %m" : 198 "database %s:%s: I/O error", 199 DICT_TYPE_MEMCACHE, dict_mc->dict.name); 200 } else if (strcmp(STR(dict_mc->clnt_buf), "END") == 0) { 201 /* Not found. */ 202 DICT_ERR_VAL_RETURN(dict_mc, DICT_ERR_NONE, (char *) 0); 203 } else if (sscanf(STR(dict_mc->clnt_buf), 204 "VALUE %*s %*s %ld", &todo) != 1 205 || todo < 0 || todo > dict_mc->max_data) { 206 if (count > 0) 207 msg_warn("%s: unexpected memcache server reply: %.30s", 208 dict_mc->dict.name, STR(dict_mc->clnt_buf)); 209 } else if (memcache_fread(fp, dict_mc->res_buf, todo) < 0) { 210 if (count > 0) 211 msg_warn("%s: EOF receiving memcache server reply", 212 dict_mc->dict.name); 213 } else { 214 /* Victory! */ 215 if (memcache_get(fp, dict_mc->clnt_buf, dict_mc->max_line) < 0 216 || strcmp(STR(dict_mc->clnt_buf), "END") != 0) 217 auto_clnt_recover(dict_mc->clnt); 218 DICT_ERR_VAL_RETURN(dict_mc, DICT_ERR_NONE, STR(dict_mc->res_buf)); 219 } 220 auto_clnt_recover(dict_mc->clnt); 221 } 222 DICT_ERR_VAL_RETURN(dict_mc, DICT_ERR_RETRY, (char *) 0); 223 } 224 225 /* dict_memcache_del - delete memcache key/value */ 226 227 static int dict_memcache_del(DICT_MC *dict_mc) 228 { 229 VSTREAM *fp; 230 int count; 231 232 for (count = 0; count < dict_mc->max_tries; count++) { 233 if (count > 0) 234 sleep(dict_mc->err_pause); 235 if ((fp = auto_clnt_access(dict_mc->clnt)) == 0) { 236 break; 237 } else if (memcache_printf(fp, "delete %s", STR(dict_mc->key_buf)) < 0 238 || memcache_get(fp, dict_mc->clnt_buf, dict_mc->max_line) < 0) { 239 if (count > 0) 240 msg_warn(errno ? "database %s:%s: I/O error: %m" : 241 "database %s:%s: I/O error", 242 DICT_TYPE_MEMCACHE, dict_mc->dict.name); 243 } else if (strcmp(STR(dict_mc->clnt_buf), "DELETED") == 0) { 244 /* Victory! */ 245 DICT_ERR_VAL_RETURN(dict_mc, DICT_ERR_NONE, DICT_STAT_SUCCESS); 246 } else if (strcmp(STR(dict_mc->clnt_buf), "NOT_FOUND") == 0) { 247 /* Not found! */ 248 DICT_ERR_VAL_RETURN(dict_mc, DICT_ERR_NONE, DICT_STAT_FAIL); 249 } else { 250 if (count > 0) 251 msg_warn("database %s:%s: delete failed: %.30s", 252 DICT_TYPE_MEMCACHE, dict_mc->dict.name, 253 STR(dict_mc->clnt_buf)); 254 } 255 auto_clnt_recover(dict_mc->clnt); 256 } 257 DICT_ERR_VAL_RETURN(dict_mc, DICT_ERR_RETRY, DICT_STAT_ERROR); 258 } 259 260 /* dict_memcache_prepare_key - prepare lookup key */ 261 262 static ssize_t dict_memcache_prepare_key(DICT_MC *dict_mc, const char *name) 263 { 264 265 /* 266 * Optionally case-fold the search string. 267 */ 268 if (dict_mc->dict.flags & DICT_FLAG_FOLD_FIX) { 269 if (dict_mc->dict.fold_buf == 0) 270 dict_mc->dict.fold_buf = vstring_alloc(10); 271 vstring_strcpy(dict_mc->dict.fold_buf, name); 272 name = lowercase(STR(dict_mc->dict.fold_buf)); 273 } 274 275 /* 276 * Optionally expand the query key format. 277 */ 278 #define DICT_MC_NO_KEY (0) 279 #define DICT_MC_NO_QUOTING ((void (*)(DICT *, const char *, VSTRING *)) 0) 280 281 if (dict_mc->key_format != 0 282 && strcmp(dict_mc->key_format, DICT_MC_DEF_KEY_FMT) != 0) { 283 VSTRING_RESET(dict_mc->key_buf); 284 if (db_common_expand(dict_mc->dbc_ctxt, dict_mc->key_format, 285 name, DICT_MC_NO_KEY, dict_mc->key_buf, 286 DICT_MC_NO_QUOTING) == 0) 287 return (0); 288 } else { 289 vstring_strcpy(dict_mc->key_buf, name); 290 } 291 292 /* 293 * The length indicates whether the expansion is empty or not. 294 */ 295 return (LEN(dict_mc->key_buf)); 296 } 297 298 /* dict_memcache_valid_key - validate key */ 299 300 static int dict_memcache_valid_key(DICT_MC *dict_mc, 301 const char *name, 302 const char *operation, 303 void (*log_func) (const char *,...)) 304 { 305 unsigned char *cp; 306 int rc; 307 308 #define DICT_MC_SKIP(why) do { \ 309 if (msg_verbose || log_func != msg_info) \ 310 log_func("%s: skipping %s for name \"%s\": %s", \ 311 dict_mc->dict.name, operation, name, (why)); \ 312 DICT_ERR_VAL_RETURN(dict_mc, DICT_ERR_NONE, 0); \ 313 } while (0) 314 315 if (*name == 0) 316 DICT_MC_SKIP("empty lookup key"); 317 if ((rc = db_common_check_domain(dict_mc->dbc_ctxt, name)) == 0) 318 DICT_MC_SKIP("domain mismatch"); 319 if (rc < 0) 320 DICT_ERR_VAL_RETURN(dict_mc, rc, 0); 321 if (dict_memcache_prepare_key(dict_mc, name) == 0) 322 DICT_MC_SKIP("empty lookup key expansion"); 323 for (cp = (unsigned char *) STR(dict_mc->key_buf); *cp; cp++) 324 if (isascii(*cp) && isspace(*cp)) 325 DICT_MC_SKIP("name contains space"); 326 327 DICT_ERR_VAL_RETURN(dict_mc, DICT_ERR_NONE, 1); 328 } 329 330 /* dict_memcache_update - update memcache */ 331 332 static int dict_memcache_update(DICT *dict, const char *name, 333 const char *value) 334 { 335 const char *myname = "dict_memcache_update"; 336 DICT_MC *dict_mc = (DICT_MC *) dict; 337 DICT *backup = dict_mc->backup; 338 int upd_res; 339 340 /* 341 * Skip updates with an inapplicable key, noisily. This results in loss 342 * of information. 343 */ 344 if (dict_memcache_valid_key(dict_mc, name, "update", msg_warn) == 0) 345 DICT_ERR_VAL_RETURN(dict, dict_mc->error, DICT_STAT_FAIL); 346 347 /* 348 * Update the memcache first. 349 */ 350 upd_res = dict_memcache_set(dict_mc, value, dict_mc->mc_ttl); 351 dict->error = dict_mc->error; 352 353 /* 354 * Update the backup database last. 355 */ 356 if (backup) { 357 upd_res = backup->update(backup, name, value); 358 dict->error = backup->error; 359 } 360 if (msg_verbose) 361 msg_info("%s: %s: update key \"%s\"(%s) => \"%s\" %s", 362 myname, dict_mc->dict.name, name, STR(dict_mc->key_buf), 363 value, dict_mc->error ? "(memcache error)" : (backup 364 && backup->error) ? "(backup error)" : "(no error)"); 365 366 return (upd_res); 367 } 368 369 /* dict_memcache_lookup - lookup memcache */ 370 371 static const char *dict_memcache_lookup(DICT *dict, const char *name) 372 { 373 const char *myname = "dict_memcache_lookup"; 374 DICT_MC *dict_mc = (DICT_MC *) dict; 375 DICT *backup = dict_mc->backup; 376 const char *retval; 377 378 /* 379 * Skip lookups with an inapplicable key, silently. This is just asking 380 * for information that cannot exist. 381 */ 382 if (dict_memcache_valid_key(dict_mc, name, "lookup", msg_info) == 0) 383 DICT_ERR_VAL_RETURN(dict, dict_mc->error, (char *) 0); 384 385 /* 386 * Search the memcache first. 387 */ 388 retval = dict_memcache_get(dict_mc); 389 dict->error = dict_mc->error; 390 391 /* 392 * Search the backup database last. Update the memcache if the data is 393 * found. 394 */ 395 if (backup) { 396 backup->error = 0; 397 if (retval == 0) { 398 retval = backup->lookup(backup, name); 399 dict->error = backup->error; 400 /* Update the cache. */ 401 if (retval != 0) 402 dict_memcache_set(dict_mc, retval, dict_mc->mc_ttl); 403 } 404 } 405 if (msg_verbose) 406 msg_info("%s: %s: key \"%s\"(%s) => %s", 407 myname, dict_mc->dict.name, name, STR(dict_mc->key_buf), 408 retval ? retval : dict_mc->error ? "(memcache error)" : 409 (backup && backup->error) ? "(backup error)" : "(not found)"); 410 411 return (retval); 412 } 413 414 /* dict_memcache_delete - delete memcache entry */ 415 416 static int dict_memcache_delete(DICT *dict, const char *name) 417 { 418 const char *myname = "dict_memcache_delete"; 419 DICT_MC *dict_mc = (DICT_MC *) dict; 420 DICT *backup = dict_mc->backup; 421 int del_res; 422 423 /* 424 * Skip lookups with an inapplicable key, noisily. This is just deleting 425 * information that cannot exist. 426 */ 427 if (dict_memcache_valid_key(dict_mc, name, "delete", msg_info) == 0) 428 DICT_ERR_VAL_RETURN(dict, dict_mc->error, dict_mc->error ? 429 DICT_STAT_ERROR : DICT_STAT_FAIL); 430 431 /* 432 * Update the memcache first. 433 */ 434 del_res = dict_memcache_del(dict_mc); 435 dict->error = dict_mc->error; 436 437 /* 438 * Update the persistent database last. 439 */ 440 if (backup) { 441 del_res = backup->delete(backup, name); 442 dict->error = backup->error; 443 } 444 if (msg_verbose) 445 msg_info("%s: %s: delete key \"%s\"(%s) => %s", 446 myname, dict_mc->dict.name, name, STR(dict_mc->key_buf), 447 dict_mc->error ? "(memcache error)" : (backup 448 && backup->error) ? "(backup error)" : "(no error)"); 449 450 return (del_res); 451 } 452 453 /* dict_memcache_sequence - first/next lookup */ 454 455 static int dict_memcache_sequence(DICT *dict, int function, const char **key, 456 const char **value) 457 { 458 const char *myname = "dict_memcache_sequence"; 459 DICT_MC *dict_mc = (DICT_MC *) dict; 460 DICT *backup = dict_mc->backup; 461 int seq_res; 462 463 if (backup == 0) { 464 msg_warn("database %s:%s: first/next support requires backup database", 465 DICT_TYPE_MEMCACHE, dict_mc->dict.name); 466 DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_FAIL); 467 } else { 468 seq_res = backup->sequence(backup, function, key, value); 469 if (msg_verbose) 470 msg_info("%s: %s: key \"%s\" => %s", 471 myname, dict_mc->dict.name, *key ? *key : "(not found)", 472 *value ? *value : backup->error ? "(backup error)" : 473 "(not found)"); 474 DICT_ERR_VAL_RETURN(dict, backup->error, seq_res); 475 } 476 } 477 478 /* dict_memcache_close - close memcache */ 479 480 static void dict_memcache_close(DICT *dict) 481 { 482 DICT_MC *dict_mc = (DICT_MC *) dict; 483 484 cfg_parser_free(dict_mc->parser); 485 db_common_free_ctx(dict_mc->dbc_ctxt); 486 if (dict_mc->key_format) 487 myfree(dict_mc->key_format); 488 myfree(dict_mc->memcache); 489 auto_clnt_free(dict_mc->clnt); 490 vstring_free(dict_mc->clnt_buf); 491 vstring_free(dict_mc->key_buf); 492 vstring_free(dict_mc->res_buf); 493 if (dict->fold_buf) 494 vstring_free(dict->fold_buf); 495 if (dict_mc->backup) 496 dict_close(dict_mc->backup); 497 dict_free(dict); 498 } 499 500 /* dict_memcache_open - open memcache */ 501 502 DICT *dict_memcache_open(const char *name, int open_flags, int dict_flags) 503 { 504 DICT_MC *dict_mc; 505 char *backup; 506 CFG_PARSER *parser; 507 508 /* 509 * Sanity checks. 510 */ 511 if (dict_flags & DICT_FLAG_NO_UNAUTH) 512 return (dict_surrogate(DICT_TYPE_MEMCACHE, name, open_flags, dict_flags, 513 "%s:%s map is not allowed for security-sensitive data", 514 DICT_TYPE_MEMCACHE, name)); 515 open_flags &= (O_RDONLY | O_RDWR | O_WRONLY | O_APPEND); 516 if (open_flags != O_RDONLY && open_flags != O_RDWR) 517 return (dict_surrogate(DICT_TYPE_MEMCACHE, name, open_flags, dict_flags, 518 "%s:%s map requires O_RDONLY or O_RDWR access mode", 519 DICT_TYPE_MEMCACHE, name)); 520 521 /* 522 * Open the configuration file. 523 */ 524 if ((parser = cfg_parser_alloc(name)) == 0) 525 return (dict_surrogate(DICT_TYPE_MEMCACHE, name, open_flags, dict_flags, 526 "open %s: %m", name)); 527 528 /* 529 * Create the dictionary object. 530 */ 531 dict_mc = (DICT_MC *) dict_alloc(DICT_TYPE_MEMCACHE, name, 532 sizeof(*dict_mc)); 533 dict_mc->dict.lookup = dict_memcache_lookup; 534 if (open_flags == O_RDWR) { 535 dict_mc->dict.update = dict_memcache_update; 536 dict_mc->dict.delete = dict_memcache_delete; 537 } 538 dict_mc->dict.sequence = dict_memcache_sequence; 539 dict_mc->dict.close = dict_memcache_close; 540 dict_mc->dict.flags = dict_flags; 541 dict_mc->key_buf = vstring_alloc(10); 542 dict_mc->res_buf = vstring_alloc(10); 543 544 /* 545 * Parse the configuration file. 546 */ 547 dict_mc->parser = parser; 548 dict_mc->key_format = cfg_get_str(dict_mc->parser, DICT_MC_NAME_KEY_FMT, 549 DICT_MC_DEF_KEY_FMT, 0, 0); 550 dict_mc->timeout = cfg_get_int(dict_mc->parser, DICT_MC_NAME_MC_TIMEOUT, 551 DICT_MC_DEF_MC_TIMEOUT, 0, 0); 552 dict_mc->mc_ttl = cfg_get_int(dict_mc->parser, DICT_MC_NAME_MC_TTL, 553 DICT_MC_DEF_MC_TTL, 0, 0); 554 dict_mc->mc_flags = cfg_get_int(dict_mc->parser, DICT_MC_NAME_MC_FLAGS, 555 DICT_MC_DEF_MC_FLAGS, 0, 0); 556 dict_mc->err_pause = cfg_get_int(dict_mc->parser, DICT_MC_NAME_ERR_PAUSE, 557 DICT_MC_DEF_ERR_PAUSE, 1, 0); 558 dict_mc->max_tries = cfg_get_int(dict_mc->parser, DICT_MC_NAME_MAX_TRY, 559 DICT_MC_DEF_MAX_TRY, 1, 0); 560 dict_mc->max_line = cfg_get_int(dict_mc->parser, DICT_MC_NAME_MAX_LINE, 561 DICT_MC_DEF_MAX_LINE, 1, 0); 562 dict_mc->max_data = cfg_get_int(dict_mc->parser, DICT_MC_NAME_MAX_DATA, 563 DICT_MC_DEF_MAX_DATA, 1, 0); 564 dict_mc->memcache = cfg_get_str(dict_mc->parser, DICT_MC_NAME_MEMCACHE, 565 DICT_MC_DEF_MEMCACHE, 0, 0); 566 567 /* 568 * Initialize the memcache client. 569 */ 570 dict_mc->clnt = auto_clnt_create(dict_mc->memcache, dict_mc->timeout, 0, 0); 571 dict_mc->clnt_buf = vstring_alloc(100); 572 573 /* 574 * Open the optional backup database. 575 */ 576 backup = cfg_get_str(dict_mc->parser, DICT_MC_NAME_BACKUP, 577 (char *) 0, 0, 0); 578 if (backup) { 579 dict_mc->backup = dict_open(backup, open_flags, dict_flags); 580 myfree(backup); 581 } else 582 dict_mc->backup = 0; 583 584 /* 585 * Parse templates and common database parameters. Maps that use 586 * substring keys should only be used with the full input key. 587 */ 588 dict_mc->dbc_ctxt = 0; 589 db_common_parse(&dict_mc->dict, &dict_mc->dbc_ctxt, 590 dict_mc->key_format, 1); 591 db_common_parse_domain(dict_mc->parser, dict_mc->dbc_ctxt); 592 if (db_common_dict_partial(dict_mc->dbc_ctxt)) 593 /* Breaks recipient delimiters */ 594 dict_mc->dict.flags |= DICT_FLAG_PATTERN; 595 else 596 dict_mc->dict.flags |= DICT_FLAG_FIXED; 597 598 dict_mc->dict.flags |= DICT_FLAG_MULTI_WRITER; 599 600 return (&dict_mc->dict); 601 } 602