xref: /netbsd-src/external/ibm-public/postfix/dist/src/util/dict_open.c (revision bdc22b2e01993381dcefeff2bc9b56ca75a4235c)
1 /*	$NetBSD: dict_open.c,v 1.2 2017/02/14 01:16:49 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 /* .PP
168 /*	Specify DICT_FLAG_NONE for no special processing.
169 /*
170 /*	The dictionary types are as follows:
171 /* .IP environ
172 /*	The process environment array. The \fIdict_name\fR argument is ignored.
173 /* .IP dbm
174 /*	DBM file.
175 /* .IP hash
176 /*	Berkeley DB file in hash format.
177 /* .IP btree
178 /*	Berkeley DB file in btree format.
179 /* .IP nis
180 /*	NIS map. Only read access is supported.
181 /* .IP nisplus
182 /*	NIS+ map. Only read access is supported.
183 /* .IP netinfo
184 /*	NetInfo table. Only read access is supported.
185 /* .IP ldap
186 /*	LDAP ("light-weight" directory access protocol) database access.
187 /* .IP pcre
188 /*	PERL-compatible regular expressions.
189 /* .IP regexp
190 /*	POSIX-compatible regular expressions.
191 /* .IP texthash
192 /*	Flat text in postmap(1) input format.
193 /* .PP
194 /*	dict_open3() takes separate arguments for dictionary type and
195 /*	name, but otherwise performs the same functions as dict_open().
196 /*
197 /*	The dict_get(), dict_put(), dict_del(), and dict_seq()
198 /*	macros evaluate their first argument multiple times.
199 /*	These names should have been in uppercase.
200 /*
201 /*	dict_get() retrieves the value stored in the named dictionary
202 /*	under the given key. A null pointer means the value was not found.
203 /*	As with dict_lookup(), the result is owned by the lookup table
204 /*	implementation. Make a copy if the result is to be modified,
205 /*	or if the result is to survive multiple table lookups.
206 /*
207 /*	dict_put() stores the specified key and value into the named
208 /*	dictionary. A zero (DICT_STAT_SUCCESS) result means the
209 /*	update was made.
210 /*
211 /*	dict_del() removes a dictionary entry, and returns
212 /*	DICT_STAT_SUCCESS in case of success.
213 /*
214 /*	dict_seq() iterates over all members in the named dictionary.
215 /*	func is define DICT_SEQ_FUN_FIRST (select first member) or
216 /*	DICT_SEQ_FUN_NEXT (select next member). A zero (DICT_STAT_SUCCESS)
217 /*	result means that an entry was found.
218 /*
219 /*	dict_close() closes the specified dictionary and cleans up the
220 /*	associated data structures.
221 /*
222 /*	dict_open_register() adds support for a new dictionary type.
223 /*
224 /*	dict_open_extend() registers a call-back function that looks
225 /*	up the dictionary open() function for a type that is not
226 /*	registered, or null in case of error. The result value is
227 /*	the last previously-registered call-back or null.
228 /*
229 /*	dict_mapnames() returns a sorted list with the names of all available
230 /*	dictionary types.
231 /*
232 /*	dict_mapnames_extend() registers a call-back function that
233 /*	enumerates additional dictionary type names. The result
234 /*	will be sorted by dict_mapnames().  The result value
235 /*	is the last previously-registered call-back or null.
236 /*
237 /*	dict_setjmp() saves processing context and makes that context
238 /*	available for use with dict_longjmp().  Normally, dict_setjmp()
239 /*	returns zero.  A non-zero result means that dict_setjmp()
240 /*	returned through a dict_longjmp() call; the result is the
241 /*	\fIval\fR argument given to dict_longjmp(). dict_isjmp()
242 /*	returns non-zero when dict_setjmp() and dict_longjmp()
243 /*	are enabled for a given dictionary.
244 /*
245 /*	NB: non-local jumps such as dict_longjmp() are not safe for
246 /*	jumping out of any routine that manipulates DICT data.
247 /*	longjmp() like calls are best avoided in signal handlers.
248 /*
249 /*	dict_type_override() changes the symbolic dictionary type.
250 /*	This is used by dictionaries whose internals are based on
251 /*	some other dictionary type.
252 /* DIAGNOSTICS
253 /*	Fatal error: open error, unsupported dictionary type, attempt to
254 /*	update non-writable dictionary.
255 /*
256 /*	The lookup routine returns non-null when the request is
257 /*	satisfied. The update, delete and sequence routines return
258 /*	zero (DICT_STAT_SUCCESS) when the request is satisfied.
259 /*	The dict->errno value is non-zero only when the last operation
260 /*	was not satisfied due to a dictionary access error. This
261 /*	can have the following values:
262 /* .IP DICT_ERR_NONE(zero)
263 /*	There was no dictionary access error. For example, the
264 /*	request was satisfied, the requested information did not
265 /*	exist in the dictionary, or the information already existed
266 /*	when it should not exist (collision).
267 /* .IP DICT_ERR_RETRY(<0)
268 /*	The dictionary was temporarily unavailable. This can happen
269 /*	with network-based services.
270 /* .IP DICT_ERR_CONFIG(<0)
271 /*	The dictionary was unavailable due to a configuration error.
272 /* .PP
273 /*	Generally, a program is expected to test the function result
274 /*	value for "success" first. If the operation was not successful,
275 /*	a program is expected to test for a non-zero dict->error
276 /*	status to distinguish between a data notfound/collision
277 /*	condition or a dictionary access error.
278 /* LICENSE
279 /* .ad
280 /* .fi
281 /*	The Secure Mailer license must be distributed with this software.
282 /* AUTHOR(S)
283 /*	Wietse Venema
284 /*	IBM T.J. Watson Research
285 /*	P.O. Box 704
286 /*	Yorktown Heights, NY 10598, USA
287 /*--*/
288 
289 /* System library. */
290 
291 #include <sys_defs.h>
292 #include <string.h>
293 #include <stdlib.h>
294 
295 /* Utility library. */
296 
297 #include <argv.h>
298 #include <mymalloc.h>
299 #include <msg.h>
300 #include <dict.h>
301 #include <dict_cdb.h>
302 #include <dict_env.h>
303 #include <dict_unix.h>
304 #include <dict_tcp.h>
305 #include <dict_sdbm.h>
306 #include <dict_dbm.h>
307 #include <dict_db.h>
308 #include <dict_lmdb.h>
309 #include <dict_nis.h>
310 #include <dict_nisplus.h>
311 #include <dict_ni.h>
312 #include <dict_pcre.h>
313 #include <dict_regexp.h>
314 #include <dict_static.h>
315 #include <dict_cidr.h>
316 #include <dict_ht.h>
317 #include <dict_thash.h>
318 #include <dict_sockmap.h>
319 #include <dict_fail.h>
320 #include <dict_pipe.h>
321 #include <dict_random.h>
322 #include <dict_union.h>
323 #include <dict_inline.h>
324 #include <stringops.h>
325 #include <split_at.h>
326 #include <htable.h>
327 #include <myflock.h>
328 
329  /*
330   * lookup table for available map types.
331   */
332 typedef struct {
333     char   *type;
334     DICT_OPEN_FN open;
335 } DICT_OPEN_INFO;
336 
337 static const DICT_OPEN_INFO dict_open_info[] = {
338     DICT_TYPE_ENVIRON, dict_env_open,
339     DICT_TYPE_HT, dict_ht_open,
340     DICT_TYPE_UNIX, dict_unix_open,
341     DICT_TYPE_TCP, dict_tcp_open,
342 #ifdef HAS_DBM
343     DICT_TYPE_DBM, dict_dbm_open,
344 #endif
345 #ifdef HAS_DB
346     DICT_TYPE_HASH, dict_hash_open,
347     DICT_TYPE_BTREE, dict_btree_open,
348 #endif
349 #ifdef HAS_NIS
350     DICT_TYPE_NIS, dict_nis_open,
351 #endif
352 #ifdef HAS_NISPLUS
353     DICT_TYPE_NISPLUS, dict_nisplus_open,
354 #endif
355 #ifdef HAS_NETINFO
356     DICT_TYPE_NETINFO, dict_ni_open,
357 #endif
358 #ifdef HAS_POSIX_REGEXP
359     DICT_TYPE_REGEXP, dict_regexp_open,
360 #endif
361     DICT_TYPE_STATIC, dict_static_open,
362     DICT_TYPE_CIDR, dict_cidr_open,
363     DICT_TYPE_THASH, dict_thash_open,
364     DICT_TYPE_SOCKMAP, dict_sockmap_open,
365     DICT_TYPE_FAIL, dict_fail_open,
366     DICT_TYPE_PIPE, dict_pipe_open,
367     DICT_TYPE_RANDOM, dict_random_open,
368     DICT_TYPE_UNION, dict_union_open,
369     DICT_TYPE_INLINE, dict_inline_open,
370 #ifndef USE_DYNAMIC_MAPS
371 #ifdef HAS_PCRE
372     DICT_TYPE_PCRE, dict_pcre_open,
373 #endif
374 #ifdef HAS_CDB
375     DICT_TYPE_CDB, dict_cdb_open,
376 #endif
377 #ifdef HAS_SDBM
378     DICT_TYPE_SDBM, dict_sdbm_open,
379 #endif
380 #ifdef HAS_LMDB
381     DICT_TYPE_LMDB, dict_lmdb_open,
382 #endif
383 #endif					/* !USE_DYNAMIC_MAPS */
384     0,
385 };
386 
387 static HTABLE *dict_open_hash;
388 
389  /*
390   * Extension hooks.
391   */
392 static DICT_OPEN_EXTEND_FN dict_open_extend_hook;
393 static DICT_MAPNAMES_EXTEND_FN dict_mapnames_extend_hook;
394 
395  /*
396   * Workaround.
397   */
398 DEFINE_DICT_LMDB_MAP_SIZE;
399 DEFINE_DICT_DB_CACHE_SIZE;
400 
401 /* dict_open_init - one-off initialization */
402 
403 static void dict_open_init(void)
404 {
405     const char *myname = "dict_open_init";
406     const DICT_OPEN_INFO *dp;
407 
408     if (dict_open_hash != 0)
409 	msg_panic("%s: multiple initialization", myname);
410     dict_open_hash = htable_create(10);
411 
412     for (dp = dict_open_info; dp->type; dp++)
413 	htable_enter(dict_open_hash, dp->type, (void *) dp);
414 }
415 
416 /* dict_open - open dictionary */
417 
418 DICT   *dict_open(const char *dict_spec, int open_flags, int dict_flags)
419 {
420     char   *saved_dict_spec = mystrdup(dict_spec);
421     char   *dict_name;
422     DICT   *dict;
423 
424     if ((dict_name = split_at(saved_dict_spec, ':')) == 0)
425 	msg_fatal("open dictionary: expecting \"type:name\" form instead of \"%s\"",
426 		  dict_spec);
427 
428     dict = dict_open3(saved_dict_spec, dict_name, open_flags, dict_flags);
429     myfree(saved_dict_spec);
430     return (dict);
431 }
432 
433 
434 /* dict_open3 - open dictionary */
435 
436 DICT   *dict_open3(const char *dict_type, const char *dict_name,
437 		           int open_flags, int dict_flags)
438 {
439     const char *myname = "dict_open";
440     DICT_OPEN_INFO *dp;
441     DICT_OPEN_FN open_fn;
442     DICT   *dict;
443 
444     if (*dict_type == 0 || *dict_name == 0)
445 	msg_fatal("open dictionary: expecting \"type:name\" form instead of \"%s:%s\"",
446 		  dict_type, dict_name);
447     if (dict_open_hash == 0)
448 	dict_open_init();
449     if ((dp = (DICT_OPEN_INFO *) htable_find(dict_open_hash, dict_type)) == 0) {
450 	if (dict_open_extend_hook != 0
451 	    && (open_fn = dict_open_extend_hook(dict_type)) != 0) {
452 	    dict_open_register(dict_type, open_fn);
453 	    dp = (DICT_OPEN_INFO *) htable_find(dict_open_hash, dict_type);
454 	}
455 	if (dp == 0)
456 	    return (dict_surrogate(dict_type, dict_name, open_flags, dict_flags,
457 			     "unsupported dictionary type: %s", dict_type));
458     }
459     if ((dict = dp->open(dict_name, open_flags, dict_flags)) == 0)
460 	return (dict_surrogate(dict_type, dict_name, open_flags, dict_flags,
461 			    "cannot open %s:%s: %m", dict_type, dict_name));
462     if (msg_verbose)
463 	msg_info("%s: %s:%s", myname, dict_type, dict_name);
464     /* XXX The choice between wait-for-lock or no-wait is hard-coded. */
465     if (dict->flags & DICT_FLAG_OPEN_LOCK) {
466 	if (dict->flags & DICT_FLAG_LOCK)
467 	    msg_panic("%s: attempt to open %s:%s with both \"open\" lock and \"access\" lock",
468 		      myname, dict_type, dict_name);
469 	/* Multi-writer safe map: downgrade persistent lock to temporary. */
470 	if (dict->flags & DICT_FLAG_MULTI_WRITER) {
471 	    dict->flags &= ~DICT_FLAG_OPEN_LOCK;
472 	    dict->flags |= DICT_FLAG_LOCK;
473 	}
474 	/* Multi-writer unsafe map: acquire exclusive lock or bust. */
475 	else if (dict->lock(dict, MYFLOCK_OP_EXCLUSIVE | MYFLOCK_OP_NOWAIT) < 0)
476 	    msg_fatal("%s:%s: unable to get exclusive lock: %m",
477 		      dict_type, dict_name);
478     }
479     /* Last step: insert proxy for UTF-8 syntax checks and casefolding. */
480     if ((dict->flags & DICT_FLAG_UTF8_ACTIVE) == 0
481 	&& DICT_NEED_UTF8_ACTIVATION(util_utf8_enable, dict_flags))
482 	dict = dict_utf8_activate(dict);
483     return (dict);
484 }
485 
486 /* dict_open_register - register dictionary type */
487 
488 void    dict_open_register(const char *type, DICT_OPEN_FN open)
489 {
490     const char *myname = "dict_open_register";
491     DICT_OPEN_INFO *dp;
492     HTABLE_INFO *ht;
493 
494     if (dict_open_hash == 0)
495 	dict_open_init();
496     if (htable_find(dict_open_hash, type))
497 	msg_panic("%s: dictionary type exists: %s", myname, type);
498     dp = (DICT_OPEN_INFO *) mymalloc(sizeof(*dp));
499     dp->open = open;
500     ht = htable_enter(dict_open_hash, type, (void *) dp);
501     dp->type = ht->key;
502 }
503 
504 /* dict_open_extend - register alternate dictionary search routine */
505 
506 DICT_OPEN_EXTEND_FN dict_open_extend(DICT_OPEN_EXTEND_FN new_cb)
507 {
508     DICT_OPEN_EXTEND_FN old_cb;
509 
510     old_cb = dict_open_extend_hook;
511     dict_open_extend_hook = new_cb;
512     return (old_cb);
513 }
514 
515 /* dict_sort_alpha_cpp - qsort() callback */
516 
517 static int dict_sort_alpha_cpp(const void *a, const void *b)
518 {
519     return (strcmp(((char **) a)[0], ((char **) b)[0]));
520 }
521 
522 /* dict_mapnames - return an ARGV of available map_names */
523 
524 ARGV   *dict_mapnames()
525 {
526     HTABLE_INFO **ht_info;
527     HTABLE_INFO **ht;
528     DICT_OPEN_INFO *dp;
529     ARGV   *mapnames;
530 
531     if (dict_open_hash == 0)
532 	dict_open_init();
533     mapnames = argv_alloc(dict_open_hash->used + 1);
534     for (ht_info = ht = htable_list(dict_open_hash); *ht; ht++) {
535 	dp = (DICT_OPEN_INFO *) ht[0]->value;
536 	argv_add(mapnames, dp->type, ARGV_END);
537     }
538     if (dict_mapnames_extend_hook != 0)
539 	(void) dict_mapnames_extend_hook(mapnames);
540     qsort((void *) mapnames->argv, mapnames->argc, sizeof(mapnames->argv[0]),
541 	  dict_sort_alpha_cpp);
542     myfree((void *) ht_info);
543     argv_terminate(mapnames);
544     return mapnames;
545 }
546 
547 /* dict_mapnames_extend - register alternate dictionary type list routine */
548 
549 DICT_MAPNAMES_EXTEND_FN dict_mapnames_extend(DICT_MAPNAMES_EXTEND_FN new_cb)
550 {
551     DICT_MAPNAMES_EXTEND_FN old_cb;
552 
553     old_cb = dict_mapnames_extend_hook;
554     dict_mapnames_extend_hook = new_cb;
555     return (old_cb);
556 }
557 
558 /* dict_type_override - disguise a dictionary type */
559 
560 void    dict_type_override(DICT *dict, const char *type)
561 {
562     myfree(dict->type);
563     dict->type = mystrdup(type);
564 }
565 
566 #ifdef TEST
567 
568  /*
569   * Proof-of-concept test program.
570   */
571 int     main(int argc, char **argv)
572 {
573     dict_test(argc, argv);
574     return (0);
575 }
576 
577 #endif
578