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