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