1 /*++
2 /* NAME
3 /*	dict 3
4 /* SUMMARY
5 /*	dictionary manager
6 /* SYNOPSIS
7 /*	#include <dict.h>
8 /*
9 /*	void	dict_register(dict_name, dict_info)
10 /*	const char *dict_name;
11 /*	DICT	*dict_info;
12 /*
13 /*	DICT	*dict_handle(dict_name)
14 /*	const char *dict_name;
15 /*
16 /*	void	dict_unregister(dict_name)
17 /*	const char *dict_name;
18 /*
19 /*	int	dict_update(dict_name, member, value)
20 /*	const char *dict_name;
21 /*	const char *member;
22 /*	const char *value;
23 /*
24 /*	const char *dict_lookup(dict_name, member)
25 /*	const char *dict_name;
26 /*	const char *member;
27 /*
28 /*	int	dict_delete(dict_name, member)
29 /*	const char *dict_name;
30 /*	const char *member;
31 /*
32 /*	int	dict_sequence(dict_name, func, member, value)
33 /*	const char *dict_name;
34 /*	int	func;
35 /*	const char **member;
36 /*	const char **value;
37 /*
38 /*	const char *dict_eval(dict_name, string, int recursive)
39 /*	const char *dict_name;
40 /*	const char *string;
41 /*	int	recursive;
42 /*
43 /*	int	dict_walk(action, context)
44 /*	void	(*action)(dict_name, dict_handle, context)
45 /*	void	*context;
46 /*
47 /*	int	dict_error(dict_name)
48 /*	const char *dict_name;
49 /*
50 /*	const char *dict_changed_name()
51 /*
52 /*	void	DICT_OWNER_AGGREGATE_INIT(aggregate)
53 /*	DICT_OWNER aggregate;
54 /*
55 /*	void	DICT_OWNER_AGGREGATE_UPDATE(aggregate, source)
56 /*	DICT_OWNER aggregate;
57 /*	DICT_OWNER source;
58 /* AUXILIARY FUNCTIONS
59 /*	int	dict_load_file_xt(dict_name, path)
60 /*	const char *dict_name;
61 /*	const char *path;
62 /*
63 /*	void	dict_load_fp(dict_name, fp)
64 /*	const char *dict_name;
65 /*	VSTREAM	*fp;
66 /*
67 /*	const char *dict_flags_str(dict_flags)
68 /*	int	dict_flags;
69 /*
70 /*	int	dict_flags_mask(names)
71 /*	const char *names;
72 /* DESCRIPTION
73 /*	This module maintains a collection of name-value dictionaries.
74 /*	Each dictionary has its own name and has its own methods to read
75 /*	or update members. Examples of dictionaries that can be accessed
76 /*	in this manner are the global UNIX-style process environment,
77 /*	hash tables, NIS maps, DBM files, and so on. Dictionary values
78 /*	are not limited to strings but can be arbitrary objects as long
79 /*	as they can be represented by character pointers.
80 /* FEATURES
81 /* .fi
82 /* .ad
83 /*	Notable features of this module are:
84 /* .IP "macro expansion (string-valued dictionaries only)"
85 /*	Macros of the form $\fIname\fR can be expanded to the current
86 /*	value of \fIname\fR. The forms $(\fIname\fR) and ${\fIname\fR} are
87 /*	also supported.
88 /* .IP "unknown names"
89 /*	An update request for an unknown dictionary name will trigger
90 /*	the instantiation of an in-memory dictionary with that name.
91 /*	A lookup request (including delete and sequence) for an
92 /*	unknown dictionary will result in a "not found" and "no
93 /*	error" result.
94 /* .PP
95 /*	dict_register() adds a new dictionary, including access methods,
96 /*	to the list of known dictionaries, or increments the reference
97 /*	count for an existing (name, dictionary) pair.  Otherwise, it is
98 /*	an error to pass an existing name (this would cause a memory leak).
99 /*
100 /*	dict_handle() returns the generic dictionary handle of the
101 /*	named dictionary, or a null pointer when the named dictionary
102 /*	is not found.
103 /*
104 /*	dict_unregister() decrements the reference count of the named
105 /*	dictionary. When the reference count reaches zero, dict_unregister()
106 /*	breaks the (name, dictionary) association and executes the
107 /*	dictionary's optional \fIremove\fR method.
108 /*
109 /*	dict_update() updates the value of the named dictionary member.
110 /*	The dictionary member and the named dictionary are instantiated
111 /*	on the fly.  The result value is zero (DICT_STAT_SUCCESS)
112 /*	when the update was made.
113 /*
114 /*	dict_lookup() returns the value of the named member (i.e. without
115 /*	expanding macros in the member value).  The \fIdict_name\fR argument
116 /*	specifies the dictionary to search. The result is a null pointer
117 /*	when no value is found, otherwise the result is owned by the
118 /*	underlying dictionary method. Make a copy if the result is to be
119 /*	modified, or if the result is to survive multiple dict_lookup() calls.
120 /*
121 /*	dict_delete() removes the named member from the named dictionary.
122 /*	The result value is zero (DICT_STAT_SUCCESS) when the member
123 /*	was found.
124 /*
125 /*	dict_sequence() steps through the named dictionary and returns
126 /*	keys and values in some implementation-defined order. The func
127 /*	argument is DICT_SEQ_FUN_FIRST to set the cursor to the first
128 /*	entry or DICT_SEQ_FUN_NEXT to select the next entry. The result
129 /*	is owned by the underlying dictionary method. Make a copy if the
130 /*	result is to be modified, or if the result is to survive multiple
131 /*	dict_sequence() calls. The result value is zero (DICT_STAT_SUCCESS)
132 /*	when a member was found.
133 /*
134 /*	dict_eval() expands macro references in the specified string.
135 /*	The result is owned by the dictionary manager. Make a copy if the
136 /*	result is to survive multiple dict_eval() calls. When the
137 /*	\fIrecursive\fR argument is non-zero, macro references in macro
138 /*	lookup results are expanded recursively.
139 /*
140 /*	dict_walk() iterates over all registered dictionaries in some
141 /*	arbitrary order, and invokes the specified action routine with
142 /*	as arguments:
143 /* .IP "const char *dict_name"
144 /*	Dictionary name.
145 /* .IP "DICT *dict_handle"
146 /*	Generic dictionary handle.
147 /* .IP "char *context"
148 /*	Application context from the caller.
149 /* .PP
150 /*	dict_changed_name() returns non-zero when any dictionary needs to
151 /*	be re-opened because it has changed or because it was unlinked.
152 /*	A non-zero result is the name of a changed dictionary.
153 /*
154 /*	dict_load_file_xt() reads name-value entries from the named file.
155 /*	Lines that begin with whitespace are concatenated to the preceding
156 /*	line (the newline is deleted).
157 /*	Each entry is stored in the dictionary named by \fIdict_name\fR.
158 /*	The result is zero if the file could not be opened.
159 /*
160 /*	dict_load_fp() reads name-value entries from an open stream.
161 /*	It has the same semantics as the dict_load_file_xt() function.
162 /*
163 /*	dict_flags_str() returns a printable representation of the
164 /*	specified dictionary flags. The result is overwritten upon
165 /*	each call.
166 /*
167 /*	dict_flags_mask() returns the bitmask for the specified
168 /*	comma/space-separated dictionary flag names.
169 /* TRUST AND PROVENANCE
170 /* .ad
171 /* .fi
172 /*	Each dictionary has an owner attribute that contains (status,
173 /*	uid) information about the owner of a dictionary.  The
174 /*	status is one of the following:
175 /* .IP DICT_OWNER_TRUSTED
176 /*	The dictionary is owned by a trusted user. The uid is zero,
177 /*	and specifies a UNIX user ID.
178 /* .IP DICT_OWNER_UNTRUSTED
179 /*	The dictionary is owned by an untrusted user.  The uid is
180 /*	non-zero, and specifies a UNIX user ID.
181 /* .IP DICT_OWNER_UNKNOWN
182 /*	The dictionary is owned by an unspecified user.  For example,
183 /*	the origin is unauthenticated, or different parts of a
184 /*	dictionary aggregate (see below) are owned by different
185 /*	untrusted users.  The uid is non-zero and does not specify
186 /*	a UNIX user ID.
187 /* .PP
188 /*	Note that dictionary ownership does not necessarily imply
189 /*	ownership of lookup results. For example, a PCRE table may
190 /*	be owned by the trusted root user, but the result of $number
191 /*	expansion can contain data from an arbitrary remote SMTP
192 /*	client.  See dict_open(3) for how to disallow $number
193 /*	expansions with security-sensitive operations.
194 /*
195 /*	Two macros are available to help determine the provenance
196 /*	and trustworthiness of a dictionary aggregate. The macros
197 /*	are unsafe because they may evaluate arguments more than
198 /*	once.
199 /*
200 /*	DICT_OWNER_AGGREGATE_INIT() initialize aggregate owner
201 /*	attributes to the highest trust level.
202 /*
203 /*	DICT_OWNER_AGGREGATE_UPDATE() updates the aggregate owner
204 /*	attributes with the attributes of the specified source, and
205 /*	reduces the aggregate trust level as appropriate.
206 /* SEE ALSO
207 /*	htable(3)
208 /* BUGS
209 /* DIAGNOSTICS
210 /*	Fatal errors: out of memory, malformed macro name.
211 /*
212 /*	The lookup routine returns non-null when the request is
213 /*	satisfied. The update, delete and sequence routines return
214 /*	zero (DICT_STAT_SUCCESS) when the request is satisfied.
215 /*	The dict_error() function returns non-zero only when the
216 /*	last operation was not satisfied due to a dictionary access
217 /*	error. The result can have the following values:
218 /* .IP DICT_ERR_NONE(zero)
219 /*	There was no dictionary access error. For example, the
220 /*	request was satisfied, the requested information did not
221 /*	exist in the dictionary, or the information already existed
222 /*	when it should not exist (collision).
223 /* .IP DICT_ERR_RETRY(<0)
224 /*	The dictionary was temporarily unavailable. This can happen
225 /*	with network-based services.
226 /* .IP DICT_ERR_CONFIG(<0)
227 /*	The dictionary was unavailable due to a configuration error.
228 /* .PP
229 /*	Generally, a program is expected to test the function result
230 /*	value for "success" first. If the operation was not successful,
231 /*	a program is expected to test for a non-zero dict->error
232 /*	status to distinguish between a data notfound/collision
233 /*	condition or a dictionary access error.
234 /* LICENSE
235 /* .ad
236 /* .fi
237 /*	The Secure Mailer license must be distributed with this software.
238 /* AUTHOR(S)
239 /*	Wietse Venema
240 /*	IBM T.J. Watson Research
241 /*	P.O. Box 704
242 /*	Yorktown Heights, NY 10598, USA
243 /*--*/
244 
245 /* System libraries. */
246 
247 #include "sys_defs.h"
248 #include <sys/stat.h>
249 #include <fcntl.h>
250 #include <ctype.h>
251 #include <string.h>
252 #include <time.h>
253 
254 /* Utility library. */
255 
256 #include "msg.h"
257 #include "htable.h"
258 #include "mymalloc.h"
259 #include "vstream.h"
260 #include "vstring.h"
261 #include "readlline.h"
262 #include "mac_expand.h"
263 #include "stringops.h"
264 #include "iostuff.h"
265 #include "name_mask.h"
266 #include "dict.h"
267 #include "dict_ht.h"
268 #include "warn_stat.h"
269 #include "line_number.h"
270 
271 static HTABLE *dict_table;
272 
273  /*
274   * Each (name, dictionary) instance has a reference count. The count is part
275   * of the name, not the dictionary. The same dictionary may be registered
276   * under multiple names. The structure below keeps track of instances and
277   * reference counts.
278   */
279 typedef struct {
280     DICT   *dict;
281     int     refcount;
282 } DICT_NODE;
283 
284 #define dict_node(dict) \
285 	(dict_table ? (DICT_NODE *) htable_find(dict_table, dict) : 0)
286 
287 /* Find a dictionary handle by name for lookup purposes. */
288 
289 #define DICT_FIND_FOR_LOOKUP(dict, dict_name) do { \
290     DICT_NODE *node; \
291     if ((node = dict_node(dict_name)) != 0) \
292 	dict = node->dict; \
293     else \
294 	dict = 0; \
295 } while (0)
296 
297 /* Find a dictionary handle by name for update purposes. */
298 
299 #define DICT_FIND_FOR_UPDATE(dict, dict_name) do { \
300     DICT_NODE *node; \
301     if ((node = dict_node(dict_name)) == 0) { \
302 	dict = dict_ht_open(dict_name, O_CREAT | O_RDWR, 0); \
303 	dict_register(dict_name, dict); \
304     } else \
305 	dict = node->dict; \
306 } while (0)
307 
308 #define STR(x)	vstring_str(x)
309 
310 /* dict_register - make association with dictionary */
311 
dict_register(const char * dict_name,DICT * dict_info)312 void    dict_register(const char *dict_name, DICT *dict_info)
313 {
314     const char *myname = "dict_register";
315     DICT_NODE *node;
316 
317     if (dict_table == 0)
318 	dict_table = htable_create(0);
319     if ((node = dict_node(dict_name)) == 0) {
320 	node = (DICT_NODE *) mymalloc(sizeof(*node));
321 	node->dict = dict_info;
322 	node->refcount = 0;
323 	htable_enter(dict_table, dict_name, (void *) node);
324     } else if (dict_info != node->dict)
325 	msg_fatal("%s: dictionary name exists: %s", myname, dict_name);
326     node->refcount++;
327     if (msg_verbose > 1)
328 	msg_info("%s: %s %d", myname, dict_name, node->refcount);
329 }
330 
331 /* dict_handle - locate generic dictionary handle */
332 
dict_handle(const char * dict_name)333 DICT   *dict_handle(const char *dict_name)
334 {
335     DICT_NODE *node;
336 
337     return ((node = dict_node(dict_name)) != 0 ? node->dict : 0);
338 }
339 
340 /* dict_node_free - dict_unregister() callback */
341 
dict_node_free(void * ptr)342 static void dict_node_free(void *ptr)
343 {
344     DICT_NODE *node = (DICT_NODE *) ptr;
345     DICT   *dict = node->dict;
346 
347     if (dict->close)
348 	dict->close(dict);
349     myfree((void *) node);
350 }
351 
352 /* dict_unregister - break association with named dictionary */
353 
dict_unregister(const char * dict_name)354 void    dict_unregister(const char *dict_name)
355 {
356     const char *myname = "dict_unregister";
357     DICT_NODE *node;
358 
359     if ((node = dict_node(dict_name)) == 0)
360 	msg_panic("non-existing dictionary: %s", dict_name);
361     if (msg_verbose > 1)
362 	msg_info("%s: %s %d", myname, dict_name, node->refcount);
363     if (--(node->refcount) == 0)
364 	htable_delete(dict_table, dict_name, dict_node_free);
365 }
366 
367 /* dict_update - replace or add dictionary entry */
368 
dict_update(const char * dict_name,const char * member,const char * value)369 int     dict_update(const char *dict_name, const char *member, const char *value)
370 {
371     const char *myname = "dict_update";
372     DICT   *dict;
373 
374     DICT_FIND_FOR_UPDATE(dict, dict_name);
375     if (msg_verbose > 1)
376 	msg_info("%s: %s = %s", myname, member, value);
377     return (dict->update(dict, member, value));
378 }
379 
380 /* dict_lookup - look up dictionary entry */
381 
dict_lookup(const char * dict_name,const char * member)382 const char *dict_lookup(const char *dict_name, const char *member)
383 {
384     const char *myname = "dict_lookup";
385     DICT   *dict;
386     const char *ret;
387 
388     DICT_FIND_FOR_LOOKUP(dict, dict_name);
389     if (dict != 0) {
390 	ret = dict->lookup(dict, member);
391 	if (msg_verbose > 1)
392 	    msg_info("%s: %s = %s", myname, member, ret ? ret :
393 		     dict->error ? "(error)" : "(notfound)");
394 	return (ret);
395     } else {
396 	if (msg_verbose > 1)
397 	    msg_info("%s: %s = %s", myname, member, "(notfound)");
398 	return (0);
399     }
400 }
401 
402 /* dict_delete - delete dictionary entry */
403 
dict_delete(const char * dict_name,const char * member)404 int     dict_delete(const char *dict_name, const char *member)
405 {
406     const char *myname = "dict_delete";
407     DICT   *dict;
408 
409     DICT_FIND_FOR_LOOKUP(dict, dict_name);
410     if (msg_verbose > 1)
411 	msg_info("%s: delete %s", myname, member);
412     return (dict ? dict->delete(dict, member) : DICT_STAT_FAIL);
413 }
414 
415 /* dict_sequence - traverse dictionary */
416 
dict_sequence(const char * dict_name,const int func,const char ** member,const char ** value)417 int     dict_sequence(const char *dict_name, const int func,
418 		              const char **member, const char **value)
419 {
420     const char *myname = "dict_sequence";
421     DICT   *dict;
422 
423     DICT_FIND_FOR_LOOKUP(dict, dict_name);
424     if (msg_verbose > 1)
425 	msg_info("%s: sequence func %d", myname, func);
426     return (dict ? dict->sequence(dict, func, member, value) : DICT_STAT_FAIL);
427 }
428 
429 /* dict_error - return last error */
430 
dict_error(const char * dict_name)431 int     dict_error(const char *dict_name)
432 {
433     DICT   *dict;
434 
435     DICT_FIND_FOR_LOOKUP(dict, dict_name);
436     return (dict ? dict->error : DICT_ERR_NONE);
437 }
438 
439 /* dict_load_file_xt - read entries from text file */
440 
dict_load_file_xt(const char * dict_name,const char * path)441 int     dict_load_file_xt(const char *dict_name, const char *path)
442 {
443     VSTREAM *fp;
444     struct stat st;
445     time_t  before;
446     time_t  after;
447 
448     /*
449      * Read the file again if it is hot. This may result in reading a partial
450      * parameter name when a file changes in the middle of a read.
451      */
452     for (before = time((time_t *) 0); /* see below */ ; before = after) {
453 	if ((fp = vstream_fopen(path, O_RDONLY, 0)) == 0)
454 	    return (0);
455 	dict_load_fp(dict_name, fp);
456 	if (fstat(vstream_fileno(fp), &st) < 0)
457 	    msg_fatal("fstat %s: %m", path);
458 	if (vstream_ferror(fp) || vstream_fclose(fp))
459 	    msg_fatal("read %s: %m", path);
460 	after = time((time_t *) 0);
461 	if (st.st_mtime < before - 1 || st.st_mtime > after)
462 	    break;
463 	if (msg_verbose > 1)
464 	    msg_info("pausing to let %s cool down", path);
465 	doze(300000);
466     }
467     return (1);
468 }
469 
470 /* dict_load_fp - read entries from open stream */
471 
dict_load_fp(const char * dict_name,VSTREAM * fp)472 void    dict_load_fp(const char *dict_name, VSTREAM *fp)
473 {
474     const char *myname = "dict_load_fp";
475     VSTRING *buf;
476     char   *member;
477     char   *val;
478     const char *old;
479     int     last_line;
480     int     lineno;
481     const char *err;
482     struct stat st;
483     DICT   *dict;
484 
485     /*
486      * Instantiate the dictionary even if the file is empty.
487      */
488     DICT_FIND_FOR_UPDATE(dict, dict_name);
489     buf = vstring_alloc(100);
490     last_line = 0;
491 
492     if (fstat(vstream_fileno(fp), &st) < 0)
493 	msg_fatal("fstat %s: %m", VSTREAM_PATH(fp));
494     while (readllines(buf, fp, &last_line, &lineno)) {
495 	if ((err = split_nameval(STR(buf), &member, &val)) != 0)
496 	    msg_fatal("%s, line %d: %s: \"%s\"",
497 		      VSTREAM_PATH(fp),
498 		      lineno,
499 		      err, STR(buf));
500 	if (msg_verbose > 1)
501 	    msg_info("%s: %s = %s", myname, member, val);
502 	if ((old = dict->lookup(dict, member)) != 0
503 	    && strcmp(old, val) != 0)
504 	    msg_warn("%s, line %d: overriding earlier entry: %s=%s",
505 		     VSTREAM_PATH(fp), lineno, member, old);
506 	if (dict->update(dict, member, val) != 0)
507 	    msg_fatal("%s, line %d: unable to update %s:%s",
508 		      VSTREAM_PATH(fp), lineno, dict->type, dict->name);
509     }
510     vstring_free(buf);
511     dict->owner.uid = st.st_uid;
512     dict->owner.status = (st.st_uid != 0);
513 }
514 
515 /* dict_eval_lookup - macro parser call-back routine */
516 
dict_eval_lookup(const char * key,int unused_type,void * context)517 static const char *dict_eval_lookup(const char *key, int unused_type,
518 				            void *context)
519 {
520     char   *dict_name = (char *) context;
521     const char *pp = 0;
522     DICT   *dict;
523 
524     /*
525      * XXX how would one recover?
526      */
527     DICT_FIND_FOR_LOOKUP(dict, dict_name);
528     if (dict != 0
529 	&& (pp = dict->lookup(dict, key)) == 0 && dict->error != 0)
530 	msg_fatal("dictionary %s: lookup %s: operation failed", dict_name, key);
531     return (pp);
532 }
533 
534 /* dict_eval - expand embedded dictionary references */
535 
dict_eval(const char * dict_name,const char * value,int recursive)536 const char *dict_eval(const char *dict_name, const char *value, int recursive)
537 {
538     const char *myname = "dict_eval";
539     static VSTRING *buf;
540     int     status;
541 
542     /*
543      * Initialize.
544      */
545     if (buf == 0)
546 	buf = vstring_alloc(10);
547 
548     /*
549      * Expand macros, possibly recursively.
550      */
551 #define DONT_FILTER (char *) 0
552 
553     status = mac_expand(buf, value,
554 			recursive ? MAC_EXP_FLAG_RECURSE : MAC_EXP_FLAG_NONE,
555 			DONT_FILTER, dict_eval_lookup, (void *) dict_name);
556     if (status & MAC_PARSE_ERROR)
557 	msg_fatal("dictionary %s: macro processing error", dict_name);
558     if (msg_verbose > 1) {
559 	if (strcmp(value, STR(buf)) != 0)
560 	    msg_info("%s: expand %s -> %s", myname, value, STR(buf));
561 	else
562 	    msg_info("%s: const  %s", myname, value);
563     }
564     return (STR(buf));
565 }
566 
567 /* dict_walk - iterate over all dictionaries in arbitrary order */
568 
dict_walk(DICT_WALK_ACTION action,void * ptr)569 void    dict_walk(DICT_WALK_ACTION action, void *ptr)
570 {
571     HTABLE_INFO **ht_info_list;
572     HTABLE_INFO **ht;
573     HTABLE_INFO *h;
574 
575     ht_info_list = htable_list(dict_table);
576     for (ht = ht_info_list; (h = *ht) != 0; ht++)
577 	action(h->key, (DICT *) h->value, ptr);
578     myfree((void *) ht_info_list);
579 }
580 
581 /* dict_changed_name - see if any dictionary has changed */
582 
dict_changed_name(void)583 const char *dict_changed_name(void)
584 {
585     const char *myname = "dict_changed_name";
586     struct stat st;
587     HTABLE_INFO **ht_info_list;
588     HTABLE_INFO **ht;
589     HTABLE_INFO *h;
590     const char *status;
591     DICT   *dict;
592 
593     ht_info_list = htable_list(dict_table);
594     for (status = 0, ht = ht_info_list; status == 0 && (h = *ht) != 0; ht++) {
595 	dict = ((DICT_NODE *) h->value)->dict;
596 	if (dict->stat_fd < 0)			/* not file-based */
597 	    continue;
598 	if (dict->mtime == 0)			/* not bloody likely */
599 	    msg_warn("%s: table %s: null time stamp", myname, h->key);
600 	if (fstat(dict->stat_fd, &st) < 0)
601 	    msg_fatal("%s: fstat: %m", myname);
602 	if (((dict->flags & DICT_FLAG_MULTI_WRITER) == 0
603 	     && st.st_mtime != dict->mtime)
604 	    || st.st_nlink == 0)
605 	    status = h->key;
606     }
607     myfree((void *) ht_info_list);
608     return (status);
609 }
610 
611 /* dict_changed - backwards compatibility */
612 
dict_changed(void)613 int     dict_changed(void)
614 {
615     return (dict_changed_name() != 0);
616 }
617 
618  /*
619   * Mapping between flag names and flag values.
620   */
621 static const NAME_MASK dict_mask[] = {
622     "warn_dup", DICT_FLAG_DUP_WARN,	/* if file, warn about dups */
623     "ignore_dup", DICT_FLAG_DUP_IGNORE,	/* if file, ignore dups */
624     "try0null", DICT_FLAG_TRY0NULL,	/* do not append 0 to key/value */
625     "try1null", DICT_FLAG_TRY1NULL,	/* append 0 to key/value */
626     "fixed", DICT_FLAG_FIXED,		/* fixed key map */
627     "pattern", DICT_FLAG_PATTERN,	/* keys are patterns */
628     "lock", DICT_FLAG_LOCK,		/* lock before access */
629     "replace", DICT_FLAG_DUP_REPLACE,	/* if file, replace dups */
630     "sync_update", DICT_FLAG_SYNC_UPDATE,	/* if file, sync updates */
631     "debug", DICT_FLAG_DEBUG,		/* log access */
632     "no_regsub", DICT_FLAG_NO_REGSUB,	/* disallow regexp substitution */
633     "no_proxy", DICT_FLAG_NO_PROXY,	/* disallow proxy mapping */
634     "no_unauth", DICT_FLAG_NO_UNAUTH,	/* disallow unauthenticated data */
635     "fold_fix", DICT_FLAG_FOLD_FIX,	/* case-fold with fixed-case key map */
636     "fold_mul", DICT_FLAG_FOLD_MUL,	/* case-fold with multi-case key map */
637     "open_lock", DICT_FLAG_OPEN_LOCK,	/* permanent lock upon open */
638     "bulk_update", DICT_FLAG_BULK_UPDATE,	/* bulk update if supported */
639     "multi_writer", DICT_FLAG_MULTI_WRITER,	/* multi-writer safe */
640     "utf8_request", DICT_FLAG_UTF8_REQUEST,	/* request UTF-8 activation */
641     "utf8_active", DICT_FLAG_UTF8_ACTIVE,	/* UTF-8 is activated */
642     "src_rhs_is_file", DICT_FLAG_SRC_RHS_IS_FILE,	/* value from file */
643     0,
644 };
645 
646 /* dict_flags_str - convert bitmask to symbolic flag names */
647 
dict_flags_str(int dict_flags)648 const char *dict_flags_str(int dict_flags)
649 {
650     static VSTRING *buf = 0;
651 
652     if (buf == 0)
653 	buf = vstring_alloc(1);
654 
655     return (str_name_mask_opt(buf, "dictionary flags", dict_mask, dict_flags,
656 			      NAME_MASK_NUMBER | NAME_MASK_PIPE));
657 }
658 
659 /* dict_flags_mask - convert symbolic flag names to bitmask */
660 
dict_flags_mask(const char * names)661 int     dict_flags_mask(const char *names)
662 {
663     return (name_mask("dictionary flags", dict_mask, names));
664 }
665