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