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