1/* $NetBSD: dict_open.c,v 1.4 2023/12/23 20:30:46 christos Exp $ */ 2 3/*++ 4/* NAME 5/* dict_open 3 6/* SUMMARY 7/* low-level dictionary interface 8/* SYNOPSIS 9/* #include <dict.h> 10/* 11/* DICT *dict_open(dict_spec, open_flags, dict_flags) 12/* const char *dict_spec; 13/* int open_flags; 14/* int dict_flags; 15/* 16/* DICT *dict_open3(dict_type, dict_name, open_flags, dict_flags) 17/* const char *dict_type; 18/* const char *dict_name; 19/* int open_flags; 20/* int dict_flags; 21/* 22/* int dict_put(dict, key, value) 23/* DICT *dict; 24/* const char *key; 25/* const char *value; 26/* 27/* const char *dict_get(dict, key) 28/* DICT *dict; 29/* const char *key; 30/* 31/* int dict_del(dict, key) 32/* DICT *dict; 33/* const char *key; 34/* 35/* int dict_seq(dict, func, key, value) 36/* DICT *dict; 37/* int func; 38/* const char **key; 39/* const char **value; 40/* 41/* void dict_close(dict) 42/* DICT *dict; 43/* 44/* typedef struct { 45/* .in +4 46/* char *type; 47/* DICT_OPEN_FN dict_fn; 48/* MKMAP_OPEN_FN mkmap_fn; /* See <mkmap.h> */ 49/* .in -4 50/* } DICT_OPEN_INFO; 51/* 52/* typedef DICT *(*DICT_OPEN_FN) (const char *, int, int); 53/* 54/* void dict_open_register(open_info) 55/* DICT_OPEN_INFO *open_info; 56/* 57/* const DICT_OPEN_INFO *dict_open_lookup(dict_type) 58/* const char *dict_type; 59/* 60/* typedef DICT_OPEN_INFO (*DICT_OPEN_EXTEND_FN)(char *); 61/* 62/* DICT_OPEN_EXTEND_FN dict_open_extend(call_back) 63/* DICT_OPEN_EXTEND_FN call_back; 64/* 65/* ARGV *dict_mapnames() 66/* 67/* typedef ARGV *(*DICT_MAPNAMES_EXTEND_FN)(ARGV *names); 68/* 69/* DICT_MAPNAMES_EXTEND_FN dict_mapnames_extend(call_back) 70/* DICT_MAPNAMES_EXTEND_FN call_back; 71/* 72/* int dict_isjmp(dict) 73/* DICT *dict; 74/* 75/* int dict_setjmp(dict) 76/* DICT *dict; 77/* 78/* int dict_longjmp(dict, val) 79/* DICT *dict; 80/* int val; 81/* 82/* void dict_type_override(dict, type) 83/* DICT *dict; 84/* const char *type; 85/* DESCRIPTION 86/* This module implements a low-level interface to multiple 87/* physical dictionary types. 88/* 89/* dict_open() takes a type:name pair that specifies a dictionary type 90/* and dictionary name, opens the dictionary, and returns a dictionary 91/* handle. The \fIopen_flags\fR arguments are as in open(2). The 92/* \fIdict_flags\fR are the bit-wise OR of zero or more of the following: 93/* .IP DICT_FLAG_DUP_WARN 94/* Warn about duplicate keys, if the underlying database does not 95/* support duplicate keys. The default is to terminate with a fatal 96/* error. 97/* .IP DICT_FLAG_DUP_IGNORE 98/* Ignore duplicate keys if the underlying database does not 99/* support duplicate keys. The default is to terminate with a fatal 100/* error. 101/* .IP DICT_FLAG_DUP_REPLACE 102/* Replace duplicate keys if the underlying database supports such 103/* an operation. The default is to terminate with a fatal error. 104/* .IP DICT_FLAG_TRY0NULL 105/* With maps where this is appropriate, append no null byte to 106/* keys and values. 107/* When neither DICT_FLAG_TRY0NULL nor DICT_FLAG_TRY1NULL are 108/* specified, the software guesses what format to use for reading; 109/* and in the absence of definite information, a system-dependent 110/* default is chosen for writing. 111/* .IP DICT_FLAG_TRY1NULL 112/* With maps where this is appropriate, append one null byte to 113/* keys and values. 114/* When neither DICT_FLAG_TRY0NULL nor DICT_FLAG_TRY1NULL are 115/* specified, the software guesses what format to use for reading; 116/* and in the absence of definite information, a system-dependent 117/* default is chosen for writing. 118/* .IP DICT_FLAG_LOCK 119/* With maps where this is appropriate, acquire an exclusive lock 120/* before writing, and acquire a shared lock before reading. 121/* Release the lock when the operation completes. 122/* .IP DICT_FLAG_OPEN_LOCK 123/* The behavior of this flag depends on whether a database 124/* sets the DICT_FLAG_MULTI_WRITER flag to indicate that it 125/* is multi-writer safe. 126/* 127/* With databases that are not multi-writer safe, dict_open() 128/* acquires a persistent exclusive lock, or it terminates with 129/* a fatal run-time error. 130/* 131/* With databases that are multi-writer safe, dict_open() 132/* downgrades the DICT_FLAG_OPEN_LOCK flag (persistent lock) 133/* to DICT_FLAG_LOCK (temporary lock). 134/* .IP DICT_FLAG_FOLD_FIX 135/* With databases whose lookup fields are fixed-case strings, 136/* fold the search string to lower case before accessing the 137/* database. This includes hash:, cdb:, dbm:. nis:, ldap:, 138/* *sql. WARNING: case folding is supported only for ASCII or 139/* valid UTF-8. 140/* .IP DICT_FLAG_FOLD_MUL 141/* With databases where one lookup field can match both upper 142/* and lower case, fold the search key to lower case before 143/* accessing the database. This includes regexp: and pcre:. 144/* WARNING: case folding is supported only for ASCII or valid 145/* UTF-8. 146/* .IP DICT_FLAG_FOLD_ANY 147/* Short-hand for (DICT_FLAG_FOLD_FIX | DICT_FLAG_FOLD_MUL). 148/* .IP DICT_FLAG_SYNC_UPDATE 149/* With file-based maps, flush I/O buffers to file after each update. 150/* Thus feature is not supported with some file-based dictionaries. 151/* .IP DICT_FLAG_NO_REGSUB 152/* Disallow regular expression substitution from the lookup string 153/* into the lookup result, to block data injection attacks. 154/* .IP DICT_FLAG_NO_PROXY 155/* Disallow access through the unprivileged \fBproxymap\fR 156/* service, to block privilege escalation attacks. 157/* .IP DICT_FLAG_NO_UNAUTH 158/* Disallow lookup mechanisms that lack any form of authentication, 159/* to block privilege escalation attacks (example: tcp_table; 160/* even NIS can be secured to some extent by requiring that 161/* the server binds to a privileged port). 162/* .IP DICT_FLAG_PARANOID 163/* A combination of all the paranoia flags: DICT_FLAG_NO_REGSUB, 164/* DICT_FLAG_NO_PROXY and DICT_FLAG_NO_UNAUTH. 165/* .IP DICT_FLAG_BULK_UPDATE 166/* Enable preliminary code for bulk-mode database updates. 167/* The caller must create an exception handler with dict_jmp_alloc() 168/* and must trap exceptions from the database client with dict_setjmp(). 169/* .IP DICT_FLAG_DEBUG 170/* Enable additional logging. 171/* .IP DICT_FLAG_UTF8_REQUEST 172/* With util_utf8_enable != 0, require that lookup/update/delete 173/* keys and values are valid UTF-8. Skip a lookup/update/delete 174/* request with a non-UTF-8 key, skip an update request with 175/* a non-UTF-8 value, and fail a lookup request with a non-UTF-8 176/* value. 177/* .IP DICT_FLAG_SRC_RHS_IS_FILE 178/* With dictionaries that are created from source text, each 179/* value in the source of a dictionary specifies a list of 180/* file names separated by comma and/or whitespace. The file 181/* contents are concatenated with a newline inserted between 182/* files, and the base64-encoded result is stored under the 183/* key. 184/* .sp 185/* NOTE 1: it is up to the application to decode lookup results 186/* with dict_file_lookup() or equivalent (this requires that 187/* the dictionary is opened with DICT_FLAG_SRC_RHS_IS_FILE). 188/* Decoding is not built into the normal dictionary lookup 189/* method, because that would complicate dictionary nesting, 190/* pipelining, and proxying. 191/* .sp 192/* NOTE 2: it is up to the application to convert file names 193/* into base64-encoded file content before calling the dictionary 194/* update method (see dict_file(3) for support). Automatic 195/* file content encoding is available only when a dictionary 196/* is created from source text. 197/* .PP 198/* Specify DICT_FLAG_NONE for no special processing. 199/* 200/* The dictionary types are as follows: 201/* .IP environ 202/* The process environment array. The \fIdict_name\fR argument is ignored. 203/* .IP dbm 204/* DBM file. 205/* .IP hash 206/* Berkeley DB file in hash format. 207/* .IP btree 208/* Berkeley DB file in btree format. 209/* .IP nis 210/* NIS map. Only read access is supported. 211/* .IP nisplus 212/* NIS+ map. Only read access is supported. 213/* .IP netinfo 214/* NetInfo table. Only read access is supported. 215/* .IP ldap 216/* LDAP ("light-weight" directory access protocol) database access. 217/* .IP pcre 218/* PERL-compatible regular expressions. 219/* .IP regexp 220/* POSIX-compatible regular expressions. 221/* .IP texthash 222/* Flat text in postmap(1) input format. 223/* .PP 224/* dict_open3() takes separate arguments for dictionary type and 225/* name, but otherwise performs the same functions as dict_open(). 226/* 227/* The dict_get(), dict_put(), dict_del(), and dict_seq() 228/* macros evaluate their first argument multiple times. 229/* These names should have been in uppercase. 230/* 231/* dict_get() retrieves the value stored in the named dictionary 232/* under the given key. A null pointer means the value was not found. 233/* As with dict_lookup(), the result is owned by the lookup table 234/* implementation. Make a copy if the result is to be modified, 235/* or if the result is to survive multiple table lookups. 236/* 237/* dict_put() stores the specified key and value into the named 238/* dictionary. A zero (DICT_STAT_SUCCESS) result means the 239/* update was made. 240/* 241/* dict_del() removes a dictionary entry, and returns 242/* DICT_STAT_SUCCESS in case of success. 243/* 244/* dict_seq() iterates over all members in the named dictionary. 245/* func is define DICT_SEQ_FUN_FIRST (select first member) or 246/* DICT_SEQ_FUN_NEXT (select next member). A zero (DICT_STAT_SUCCESS) 247/* result means that an entry was found. 248/* 249/* dict_close() closes the specified dictionary and cleans up the 250/* associated data structures. 251/* 252/* dict_open_register() adds support for a new dictionary type. 253/* NOTE: this function does not copy its argument. 254/* 255/* dict_open_lookup() returns a pointer to the DICT_OPEN_INFO 256/* for the specified dictionary type, or a null pointer if the 257/* requested information is not found. 258/* 259/* dict_open_extend() registers a call-back function that looks 260/* up the dictionary open() function for a type that is not 261/* registered, or null in case of error. The result value is 262/* the last previously-registered call-back or null. 263/* 264/* dict_mapnames() returns a sorted list with the names of all available 265/* dictionary types. 266/* 267/* dict_mapnames_extend() registers a call-back function that 268/* enumerates additional dictionary type names. The result 269/* will be sorted by dict_mapnames(). The result value 270/* is the last previously-registered call-back or null. 271/* 272/* dict_setjmp() saves processing context and makes that context 273/* available for use with dict_longjmp(). Normally, dict_setjmp() 274/* returns zero. A non-zero result means that dict_setjmp() 275/* returned through a dict_longjmp() call; the result is the 276/* \fIval\fR argument given to dict_longjmp(). dict_isjmp() 277/* returns non-zero when dict_setjmp() and dict_longjmp() 278/* are enabled for a given dictionary. 279/* 280/* NB: non-local jumps such as dict_longjmp() are not safe for 281/* jumping out of any routine that manipulates DICT data. 282/* longjmp() like calls are best avoided in signal handlers. 283/* 284/* dict_type_override() changes the symbolic dictionary type. 285/* This is used by dictionaries whose internals are based on 286/* some other dictionary type. 287/* DIAGNOSTICS 288/* Fatal error: open error, unsupported dictionary type, attempt to 289/* update non-writable dictionary. 290/* 291/* The lookup routine returns non-null when the request is 292/* satisfied. The update, delete and sequence routines return 293/* zero (DICT_STAT_SUCCESS) when the request is satisfied. 294/* The dict->errno value is non-zero only when the last operation 295/* was not satisfied due to a dictionary access error. This 296/* can have the following values: 297/* .IP DICT_ERR_NONE(zero) 298/* There was no dictionary access error. For example, the 299/* request was satisfied, the requested information did not 300/* exist in the dictionary, or the information already existed 301/* when it should not exist (collision). 302/* .IP DICT_ERR_RETRY(<0) 303/* The dictionary was temporarily unavailable. This can happen 304/* with network-based services. 305/* .IP DICT_ERR_CONFIG(<0) 306/* The dictionary was unavailable due to a configuration error. 307/* .PP 308/* Generally, a program is expected to test the function result 309/* value for "success" first. If the operation was not successful, 310/* a program is expected to test for a non-zero dict->error 311/* status to distinguish between a data notfound/collision 312/* condition or a dictionary access error. 313/* LICENSE 314/* .ad 315/* .fi 316/* The Secure Mailer license must be distributed with this software. 317/* AUTHOR(S) 318/* Wietse Venema 319/* IBM T.J. Watson Research 320/* P.O. Box 704 321/* Yorktown Heights, NY 10598, USA 322/* 323/* Wietse Venema 324/* Google, Inc. 325/* 111 8th Avenue 326/* New York, NY 10011, USA 327/*--*/ 328 329/* System library. */ 330 331#include <sys_defs.h> 332#include <string.h> 333#include <stdlib.h> 334 335/* Utility library. */ 336 337#include <argv.h> 338#include <mymalloc.h> 339#include <msg.h> 340#include <dict.h> 341#include <dict_cdb.h> 342#include <dict_env.h> 343#include <dict_unix.h> 344#include <dict_tcp.h> 345#include <dict_sdbm.h> 346#include <dict_dbm.h> 347#include <dict_db.h> 348#include <dict_lmdb.h> 349#include <dict_nis.h> 350#include <dict_nisplus.h> 351#include <dict_ni.h> 352#include <dict_pcre.h> 353#include <dict_regexp.h> 354#include <dict_static.h> 355#include <dict_cidr.h> 356#include <dict_ht.h> 357#include <dict_thash.h> 358#include <dict_sockmap.h> 359#include <dict_fail.h> 360#include <dict_pipe.h> 361#include <dict_random.h> 362#include <dict_union.h> 363#include <dict_inline.h> 364#include <stringops.h> 365#include <split_at.h> 366#include <htable.h> 367#include <myflock.h> 368#include <mkmap.h> 369 370 /* 371 * lookup table for available map types. 372 */ 373static const DICT_OPEN_INFO dict_open_info[] = { 374 DICT_TYPE_ENVIRON, dict_env_open, 0, 375 DICT_TYPE_HT, dict_ht_open, 0, 376 DICT_TYPE_UNIX, dict_unix_open, 0, 377 DICT_TYPE_TCP, dict_tcp_open, 0, 378#ifdef HAS_DBM 379 DICT_TYPE_DBM, dict_dbm_open, mkmap_dbm_open, 380#endif 381#ifdef HAS_DB 382 DICT_TYPE_HASH, dict_hash_open, mkmap_hash_open, 383 DICT_TYPE_BTREE, dict_btree_open, mkmap_btree_open, 384#endif 385#ifdef HAS_NIS 386 DICT_TYPE_NIS, dict_nis_open, 0, 387#endif 388#ifdef HAS_NISPLUS 389 DICT_TYPE_NISPLUS, dict_nisplus_open, 0, 390#endif 391#ifdef HAS_NETINFO 392 DICT_TYPE_NETINFO, dict_ni_open, 0, 393#endif 394#ifdef HAS_POSIX_REGEXP 395 DICT_TYPE_REGEXP, dict_regexp_open, 0, 396#endif 397 DICT_TYPE_STATIC, dict_static_open, 0, 398 DICT_TYPE_CIDR, dict_cidr_open, 0, 399 DICT_TYPE_THASH, dict_thash_open, 0, 400 DICT_TYPE_SOCKMAP, dict_sockmap_open, 0, 401 DICT_TYPE_FAIL, dict_fail_open, mkmap_fail_open, 402 DICT_TYPE_PIPE, dict_pipe_open, 0, 403 DICT_TYPE_RANDOM, dict_random_open, 0, 404 DICT_TYPE_UNION, dict_union_open, 0, 405 DICT_TYPE_INLINE, dict_inline_open, 0, 406#ifndef USE_DYNAMIC_MAPS 407#ifdef HAS_PCRE 408 DICT_TYPE_PCRE, dict_pcre_open, 0, 409#endif 410#ifdef HAS_CDB 411 DICT_TYPE_CDB, dict_cdb_open, mkmap_cdb_open, 412#endif 413#ifdef HAS_SDBM 414 DICT_TYPE_SDBM, dict_sdbm_open, mkmap_sdbm_open, 415#endif 416#ifdef HAS_LMDB 417 DICT_TYPE_LMDB, dict_lmdb_open, mkmap_lmdb_open, 418#endif 419#endif /* !USE_DYNAMIC_MAPS */ 420 0, 421}; 422 423static HTABLE *dict_open_hash; 424 425 /* 426 * Extension hooks. 427 */ 428static DICT_OPEN_EXTEND_FN dict_open_extend_hook; 429static DICT_MAPNAMES_EXTEND_FN dict_mapnames_extend_hook; 430 431 /* 432 * Workaround: define global variables here to control database cache sizes. 433 * When a database driver is dynamically loaded, global control variables 434 * cannot simply be owned by the loadable objects because that would result 435 * in build-time linker errors. 436 */ 437DEFINE_DICT_LMDB_MAP_SIZE; 438DEFINE_DICT_DB_CACHE_SIZE; 439 440 /* 441 * Replace obscure code with a more readable expression. 442 */ 443#define NEED_DICT_OPEN_INIT() (dict_open_hash == 0) 444 445/* dict_open_init - one-off initialization */ 446 447static void dict_open_init(void) 448{ 449 const char *myname = "dict_open_init"; 450 const DICT_OPEN_INFO *dp; 451 452 if (!NEED_DICT_OPEN_INIT()) 453 msg_panic("%s: multiple initialization", myname); 454 dict_open_hash = htable_create(10); 455 456 for (dp = dict_open_info; dp->type; dp++) 457 htable_enter(dict_open_hash, dp->type, (void *) dp); 458} 459 460/* dict_open - open dictionary */ 461 462DICT *dict_open(const char *dict_spec, int open_flags, int dict_flags) 463{ 464 char *saved_dict_spec = mystrdup(dict_spec); 465 char *dict_name; 466 DICT *dict; 467 468 if ((dict_name = split_at(saved_dict_spec, ':')) == 0) 469 msg_fatal("open dictionary: expecting \"type:name\" form instead of \"%s\"", 470 dict_spec); 471 472 dict = dict_open3(saved_dict_spec, dict_name, open_flags, dict_flags); 473 myfree(saved_dict_spec); 474 return (dict); 475} 476 477/* dict_open3 - open dictionary */ 478 479DICT *dict_open3(const char *dict_type, const char *dict_name, 480 int open_flags, int dict_flags) 481{ 482 const char *myname = "dict_open"; 483 const DICT_OPEN_INFO *dp; 484 DICT *dict; 485 486 if (*dict_type == 0 || *dict_name == 0) 487 msg_fatal("open dictionary: expecting \"type:name\" form instead of \"%s:%s\"", 488 dict_type, dict_name); 489 if (NEED_DICT_OPEN_INIT()) 490 dict_open_init(); 491 if ((dp = dict_open_lookup(dict_type)) == 0) 492 return (dict_surrogate(dict_type, dict_name, open_flags, dict_flags, 493 "unsupported dictionary type: %s", dict_type)); 494 if ((dict = dp->dict_fn(dict_name, open_flags, dict_flags)) == 0) 495 return (dict_surrogate(dict_type, dict_name, open_flags, dict_flags, 496 "cannot open %s:%s: %m", dict_type, dict_name)); 497 if (msg_verbose) 498 msg_info("%s: %s:%s", myname, dict_type, dict_name); 499 /* XXX The choice between wait-for-lock or no-wait is hard-coded. */ 500 if (dict->flags & DICT_FLAG_OPEN_LOCK) { 501 if (dict->flags & DICT_FLAG_LOCK) 502 msg_panic("%s: attempt to open %s:%s with both \"open\" lock and \"access\" lock", 503 myname, dict_type, dict_name); 504 /* Multi-writer safe map: downgrade persistent lock to temporary. */ 505 if (dict->flags & DICT_FLAG_MULTI_WRITER) { 506 dict->flags &= ~DICT_FLAG_OPEN_LOCK; 507 dict->flags |= DICT_FLAG_LOCK; 508 } 509 /* Multi-writer unsafe map: acquire exclusive lock or bust. */ 510 else if (dict->lock(dict, MYFLOCK_OP_EXCLUSIVE | MYFLOCK_OP_NOWAIT) < 0) 511 msg_fatal("%s:%s: unable to get exclusive lock: %m", 512 dict_type, dict_name); 513 } 514 /* Last step: insert proxy for UTF-8 syntax checks and casefolding. */ 515 if ((dict->flags & DICT_FLAG_UTF8_ACTIVE) == 0 516 && DICT_NEED_UTF8_ACTIVATION(util_utf8_enable, dict_flags)) 517 dict = dict_utf8_activate(dict); 518 return (dict); 519} 520 521/* dict_open_register - register dictionary type */ 522 523void dict_open_register(const DICT_OPEN_INFO *dp) 524{ 525 const char *myname = "dict_open_register"; 526 527 if (msg_verbose > 1) 528 msg_info("%s: %s", myname, dp->type); 529 if (NEED_DICT_OPEN_INIT()) 530 dict_open_init(); 531 if (htable_find(dict_open_hash, dp->type)) 532 msg_panic("%s: dictionary type exists: %s", myname, dp->type); 533 (void) htable_enter(dict_open_hash, dp->type, (void *) dp); 534} 535 536/* dict_open_lookup - look up DICT_OPEN_INFO for dictionary type */ 537 538const DICT_OPEN_INFO *dict_open_lookup(const char *dict_type) 539{ 540 const char myname[] = "dict_open_lookup"; 541 const DICT_OPEN_INFO *dp; 542 543 if (msg_verbose > 1) 544 msg_info("%s: %s", myname, dict_type); 545 if ((dp = (DICT_OPEN_INFO *) htable_find(dict_open_hash, dict_type)) == 0 546 && dict_open_extend_hook != 0 547 && (dp = dict_open_extend_hook(dict_type)) != 0) 548 dict_open_register(dp); 549 return (dp); 550} 551 552/* dict_open_extend - register alternate dictionary search routine */ 553 554DICT_OPEN_EXTEND_FN dict_open_extend(DICT_OPEN_EXTEND_FN new_cb) 555{ 556 DICT_OPEN_EXTEND_FN old_cb; 557 558 old_cb = dict_open_extend_hook; 559 dict_open_extend_hook = new_cb; 560 return (old_cb); 561} 562 563/* dict_mapnames - return an ARGV of available map_names */ 564 565ARGV *dict_mapnames() 566{ 567 HTABLE_INFO **ht_info; 568 HTABLE_INFO **ht; 569 DICT_OPEN_INFO *dp; 570 ARGV *mapnames; 571 572 if (NEED_DICT_OPEN_INIT()) 573 dict_open_init(); 574 mapnames = argv_alloc(dict_open_hash->used + 1); 575 for (ht_info = ht = htable_list(dict_open_hash); *ht; ht++) { 576 dp = (DICT_OPEN_INFO *) ht[0]->value; 577 argv_add(mapnames, dp->type, ARGV_END); 578 } 579 if (dict_mapnames_extend_hook != 0) 580 (void) dict_mapnames_extend_hook(mapnames); 581 argv_qsort(mapnames, (ARGV_COMPAR_FN) 0); 582 /* In case some drivers have been loaded dynamically. */ 583 argv_uniq(mapnames, (ARGV_COMPAR_FN) 0); 584 myfree((void *) ht_info); 585 argv_terminate(mapnames); 586 return mapnames; 587} 588 589/* dict_mapnames_extend - register alternate dictionary type list routine */ 590 591DICT_MAPNAMES_EXTEND_FN dict_mapnames_extend(DICT_MAPNAMES_EXTEND_FN new_cb) 592{ 593 DICT_MAPNAMES_EXTEND_FN old_cb; 594 595 old_cb = dict_mapnames_extend_hook; 596 dict_mapnames_extend_hook = new_cb; 597 return (old_cb); 598} 599 600/* dict_type_override - disguise a dictionary type */ 601 602void dict_type_override(DICT *dict, const char *type) 603{ 604 myfree(dict->type); 605 dict->type = mystrdup(type); 606} 607 608#ifdef TEST 609 610 /* 611 * Proof-of-concept test program. 612 */ 613int main(int argc, char **argv) 614{ 615 dict_test(argc, argv); 616 return (0); 617} 618 619#endif 620