1 /* $NetBSD: dict.c,v 1.3 2020/03/18 19:05:21 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 247 /* System libraries. */ 248 249 #include "sys_defs.h" 250 #include <sys/stat.h> 251 #include <fcntl.h> 252 #include <ctype.h> 253 #include <string.h> 254 #include <time.h> 255 256 /* Utility library. */ 257 258 #include "msg.h" 259 #include "htable.h" 260 #include "mymalloc.h" 261 #include "vstream.h" 262 #include "vstring.h" 263 #include "readlline.h" 264 #include "mac_expand.h" 265 #include "stringops.h" 266 #include "iostuff.h" 267 #include "name_mask.h" 268 #include "dict.h" 269 #include "dict_ht.h" 270 #include "warn_stat.h" 271 #include "line_number.h" 272 273 static HTABLE *dict_table; 274 275 /* 276 * Each (name, dictionary) instance has a reference count. The count is part 277 * of the name, not the dictionary. The same dictionary may be registered 278 * under multiple names. The structure below keeps track of instances and 279 * reference counts. 280 */ 281 typedef struct { 282 DICT *dict; 283 int refcount; 284 } DICT_NODE; 285 286 #define dict_node(dict) \ 287 (dict_table ? (DICT_NODE *) htable_find(dict_table, dict) : 0) 288 289 /* Find a dictionary handle by name for lookup purposes. */ 290 291 #define DICT_FIND_FOR_LOOKUP(dict, dict_name) do { \ 292 DICT_NODE *node; \ 293 if ((node = dict_node(dict_name)) != 0) \ 294 dict = node->dict; \ 295 else \ 296 dict = 0; \ 297 } while (0) 298 299 /* Find a dictionary handle by name for update purposes. */ 300 301 #define DICT_FIND_FOR_UPDATE(dict, dict_name) do { \ 302 DICT_NODE *node; \ 303 if ((node = dict_node(dict_name)) == 0) { \ 304 dict = dict_ht_open(dict_name, O_CREAT | O_RDWR, 0); \ 305 dict_register(dict_name, dict); \ 306 } else \ 307 dict = node->dict; \ 308 } while (0) 309 310 #define STR(x) vstring_str(x) 311 312 /* dict_register - make association with dictionary */ 313 314 void dict_register(const char *dict_name, DICT *dict_info) 315 { 316 const char *myname = "dict_register"; 317 DICT_NODE *node; 318 319 if (dict_table == 0) 320 dict_table = htable_create(0); 321 if ((node = dict_node(dict_name)) == 0) { 322 node = (DICT_NODE *) mymalloc(sizeof(*node)); 323 node->dict = dict_info; 324 node->refcount = 0; 325 htable_enter(dict_table, dict_name, (void *) node); 326 } else if (dict_info != node->dict) 327 msg_fatal("%s: dictionary name exists: %s", myname, dict_name); 328 node->refcount++; 329 if (msg_verbose > 1) 330 msg_info("%s: %s %d", myname, dict_name, node->refcount); 331 } 332 333 /* dict_handle - locate generic dictionary handle */ 334 335 DICT *dict_handle(const char *dict_name) 336 { 337 DICT_NODE *node; 338 339 return ((node = dict_node(dict_name)) != 0 ? node->dict : 0); 340 } 341 342 /* dict_node_free - dict_unregister() callback */ 343 344 static void dict_node_free(void *ptr) 345 { 346 DICT_NODE *node = (DICT_NODE *) ptr; 347 DICT *dict = node->dict; 348 349 if (dict->close) 350 dict->close(dict); 351 myfree((void *) node); 352 } 353 354 /* dict_unregister - break association with named dictionary */ 355 356 void dict_unregister(const char *dict_name) 357 { 358 const char *myname = "dict_unregister"; 359 DICT_NODE *node; 360 361 if ((node = dict_node(dict_name)) == 0) 362 msg_panic("non-existing dictionary: %s", dict_name); 363 if (msg_verbose > 1) 364 msg_info("%s: %s %d", myname, dict_name, node->refcount); 365 if (--(node->refcount) == 0) 366 htable_delete(dict_table, dict_name, dict_node_free); 367 } 368 369 /* dict_update - replace or add dictionary entry */ 370 371 int dict_update(const char *dict_name, const char *member, const char *value) 372 { 373 const char *myname = "dict_update"; 374 DICT *dict; 375 376 DICT_FIND_FOR_UPDATE(dict, dict_name); 377 if (msg_verbose > 1) 378 msg_info("%s: %s = %s", myname, member, value); 379 return (dict->update(dict, member, value)); 380 } 381 382 /* dict_lookup - look up dictionary entry */ 383 384 const char *dict_lookup(const char *dict_name, const char *member) 385 { 386 const char *myname = "dict_lookup"; 387 DICT *dict; 388 const char *ret; 389 390 DICT_FIND_FOR_LOOKUP(dict, dict_name); 391 if (dict != 0) { 392 ret = dict->lookup(dict, member); 393 if (msg_verbose > 1) 394 msg_info("%s: %s = %s", myname, member, ret ? ret : 395 dict->error ? "(error)" : "(notfound)"); 396 return (ret); 397 } else { 398 if (msg_verbose > 1) 399 msg_info("%s: %s = %s", myname, member, "(notfound)"); 400 return (0); 401 } 402 } 403 404 /* dict_delete - delete dictionary entry */ 405 406 int dict_delete(const char *dict_name, const char *member) 407 { 408 const char *myname = "dict_delete"; 409 DICT *dict; 410 411 DICT_FIND_FOR_LOOKUP(dict, dict_name); 412 if (msg_verbose > 1) 413 msg_info("%s: delete %s", myname, member); 414 return (dict ? dict->delete(dict, member) : DICT_STAT_FAIL); 415 } 416 417 /* dict_sequence - traverse dictionary */ 418 419 int dict_sequence(const char *dict_name, const int func, 420 const char **member, const char **value) 421 { 422 const char *myname = "dict_sequence"; 423 DICT *dict; 424 425 DICT_FIND_FOR_LOOKUP(dict, dict_name); 426 if (msg_verbose > 1) 427 msg_info("%s: sequence func %d", myname, func); 428 return (dict ? dict->sequence(dict, func, member, value) : DICT_STAT_FAIL); 429 } 430 431 /* dict_error - return last error */ 432 433 int dict_error(const char *dict_name) 434 { 435 DICT *dict; 436 437 DICT_FIND_FOR_LOOKUP(dict, dict_name); 438 return (dict ? dict->error : DICT_ERR_NONE); 439 } 440 441 /* dict_load_file_xt - read entries from text file */ 442 443 int dict_load_file_xt(const char *dict_name, const char *path) 444 { 445 VSTREAM *fp; 446 struct stat st; 447 time_t before; 448 time_t after; 449 450 /* 451 * Read the file again if it is hot. This may result in reading a partial 452 * parameter name when a file changes in the middle of a read. 453 */ 454 for (before = time((time_t *) 0); /* see below */ ; before = after) { 455 if ((fp = vstream_fopen(path, O_RDONLY, 0)) == 0) 456 return (0); 457 dict_load_fp(dict_name, fp); 458 if (fstat(vstream_fileno(fp), &st) < 0) 459 msg_fatal("fstat %s: %m", path); 460 if (vstream_ferror(fp) || vstream_fclose(fp)) 461 msg_fatal("read %s: %m", path); 462 after = time((time_t *) 0); 463 if (st.st_mtime < before - 1 || st.st_mtime > after) 464 break; 465 if (msg_verbose > 1) 466 msg_info("pausing to let %s cool down", path); 467 doze(300000); 468 } 469 return (1); 470 } 471 472 /* dict_load_fp - read entries from open stream */ 473 474 void dict_load_fp(const char *dict_name, VSTREAM *fp) 475 { 476 const char *myname = "dict_load_fp"; 477 VSTRING *buf; 478 char *member; 479 char *val; 480 const char *old; 481 int last_line; 482 int lineno; 483 const char *err; 484 struct stat st; 485 DICT *dict; 486 487 /* 488 * Instantiate the dictionary even if the file is empty. 489 */ 490 DICT_FIND_FOR_UPDATE(dict, dict_name); 491 buf = vstring_alloc(100); 492 last_line = 0; 493 494 if (fstat(vstream_fileno(fp), &st) < 0) 495 msg_fatal("fstat %s: %m", VSTREAM_PATH(fp)); 496 while (readllines(buf, fp, &last_line, &lineno)) { 497 if ((err = split_nameval(STR(buf), &member, &val)) != 0) 498 msg_fatal("%s, line %d: %s: \"%s\"", 499 VSTREAM_PATH(fp), 500 lineno, 501 err, STR(buf)); 502 if (msg_verbose > 1) 503 msg_info("%s: %s = %s", myname, member, val); 504 if ((old = dict->lookup(dict, member)) != 0 505 && strcmp(old, val) != 0) 506 msg_warn("%s, line %d: overriding earlier entry: %s=%s", 507 VSTREAM_PATH(fp), lineno, member, old); 508 if (dict->update(dict, member, val) != 0) 509 msg_fatal("%s, line %d: unable to update %s:%s", 510 VSTREAM_PATH(fp), lineno, dict->type, dict->name); 511 } 512 vstring_free(buf); 513 dict->owner.uid = st.st_uid; 514 dict->owner.status = (st.st_uid != 0); 515 } 516 517 /* dict_eval_lookup - macro parser call-back routine */ 518 519 static const char *dict_eval_lookup(const char *key, int unused_type, 520 void *context) 521 { 522 char *dict_name = (char *) context; 523 const char *pp = 0; 524 DICT *dict; 525 526 /* 527 * XXX how would one recover? 528 */ 529 DICT_FIND_FOR_LOOKUP(dict, dict_name); 530 if (dict != 0 531 && (pp = dict->lookup(dict, key)) == 0 && dict->error != 0) 532 msg_fatal("dictionary %s: lookup %s: operation failed", dict_name, key); 533 return (pp); 534 } 535 536 /* dict_eval - expand embedded dictionary references */ 537 538 const char *dict_eval(const char *dict_name, const char *value, int recursive) 539 { 540 const char *myname = "dict_eval"; 541 static VSTRING *buf; 542 int status; 543 544 /* 545 * Initialize. 546 */ 547 if (buf == 0) 548 buf = vstring_alloc(10); 549 550 /* 551 * Expand macros, possibly recursively. 552 */ 553 #define DONT_FILTER (char *) 0 554 555 status = mac_expand(buf, value, 556 recursive ? MAC_EXP_FLAG_RECURSE : MAC_EXP_FLAG_NONE, 557 DONT_FILTER, dict_eval_lookup, (void *) dict_name); 558 if (status & MAC_PARSE_ERROR) 559 msg_fatal("dictionary %s: macro processing error", dict_name); 560 if (msg_verbose > 1) { 561 if (strcmp(value, STR(buf)) != 0) 562 msg_info("%s: expand %s -> %s", myname, value, STR(buf)); 563 else 564 msg_info("%s: const %s", myname, value); 565 } 566 return (STR(buf)); 567 } 568 569 /* dict_walk - iterate over all dictionaries in arbitrary order */ 570 571 void dict_walk(DICT_WALK_ACTION action, void *ptr) 572 { 573 HTABLE_INFO **ht_info_list; 574 HTABLE_INFO **ht; 575 HTABLE_INFO *h; 576 577 ht_info_list = htable_list(dict_table); 578 for (ht = ht_info_list; (h = *ht) != 0; ht++) 579 action(h->key, (DICT *) h->value, ptr); 580 myfree((void *) ht_info_list); 581 } 582 583 /* dict_changed_name - see if any dictionary has changed */ 584 585 const char *dict_changed_name(void) 586 { 587 const char *myname = "dict_changed_name"; 588 struct stat st; 589 HTABLE_INFO **ht_info_list; 590 HTABLE_INFO **ht; 591 HTABLE_INFO *h; 592 const char *status; 593 DICT *dict; 594 595 ht_info_list = htable_list(dict_table); 596 for (status = 0, ht = ht_info_list; status == 0 && (h = *ht) != 0; ht++) { 597 dict = ((DICT_NODE *) h->value)->dict; 598 if (dict->stat_fd < 0) /* not file-based */ 599 continue; 600 if (dict->mtime == 0) /* not bloody likely */ 601 msg_warn("%s: table %s: null time stamp", myname, h->key); 602 if (fstat(dict->stat_fd, &st) < 0) 603 msg_fatal("%s: fstat: %m", myname); 604 if (((dict->flags & DICT_FLAG_MULTI_WRITER) == 0 605 && st.st_mtime != dict->mtime) 606 || st.st_nlink == 0) 607 status = h->key; 608 } 609 myfree((void *) ht_info_list); 610 return (status); 611 } 612 613 /* dict_changed - backwards compatibility */ 614 615 int dict_changed(void) 616 { 617 return (dict_changed_name() != 0); 618 } 619 620 /* 621 * Mapping between flag names and flag values. 622 */ 623 static const NAME_MASK dict_mask[] = { 624 "warn_dup", DICT_FLAG_DUP_WARN, /* if file, warn about dups */ 625 "ignore_dup", DICT_FLAG_DUP_IGNORE, /* if file, ignore dups */ 626 "try0null", DICT_FLAG_TRY0NULL, /* do not append 0 to key/value */ 627 "try1null", DICT_FLAG_TRY1NULL, /* append 0 to key/value */ 628 "fixed", DICT_FLAG_FIXED, /* fixed key map */ 629 "pattern", DICT_FLAG_PATTERN, /* keys are patterns */ 630 "lock", DICT_FLAG_LOCK, /* lock before access */ 631 "replace", DICT_FLAG_DUP_REPLACE, /* if file, replace dups */ 632 "sync_update", DICT_FLAG_SYNC_UPDATE, /* if file, sync updates */ 633 "debug", DICT_FLAG_DEBUG, /* log access */ 634 "no_regsub", DICT_FLAG_NO_REGSUB, /* disallow regexp substitution */ 635 "no_proxy", DICT_FLAG_NO_PROXY, /* disallow proxy mapping */ 636 "no_unauth", DICT_FLAG_NO_UNAUTH, /* disallow unauthenticated data */ 637 "fold_fix", DICT_FLAG_FOLD_FIX, /* case-fold with fixed-case key map */ 638 "fold_mul", DICT_FLAG_FOLD_MUL, /* case-fold with multi-case key map */ 639 "open_lock", DICT_FLAG_OPEN_LOCK, /* permanent lock upon open */ 640 "bulk_update", DICT_FLAG_BULK_UPDATE, /* bulk update if supported */ 641 "multi_writer", DICT_FLAG_MULTI_WRITER, /* multi-writer safe */ 642 "utf8_request", DICT_FLAG_UTF8_REQUEST, /* request UTF-8 activation */ 643 "utf8_active", DICT_FLAG_UTF8_ACTIVE, /* UTF-8 is activated */ 644 "src_rhs_is_file", DICT_FLAG_SRC_RHS_IS_FILE, /* value from file */ 645 0, 646 }; 647 648 /* dict_flags_str - convert bitmask to symbolic flag names */ 649 650 const char *dict_flags_str(int dict_flags) 651 { 652 static VSTRING *buf = 0; 653 654 if (buf == 0) 655 buf = vstring_alloc(1); 656 657 return (str_name_mask_opt(buf, "dictionary flags", dict_mask, dict_flags, 658 NAME_MASK_NUMBER | NAME_MASK_PIPE)); 659 } 660 661 /* dict_flags_mask - convert symbolic flag names to bitmask */ 662 663 int dict_flags_mask(const char *names) 664 { 665 return (name_mask("dictionary flags", dict_mask, names)); 666 } 667