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