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