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