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