1/*++
2/* NAME
3/*	name_mask 3
4/* SUMMARY
5/*	map names to bit mask
6/* SYNOPSIS
7/*	#include <name_mask.h>
8/*
9/*	int	name_mask(context, table, names)
10/*	const char *context;
11/*	const NAME_MASK *table;
12/*	const char *names;
13/*
14/*	long	long_name_mask(context, table, names)
15/*	const char *context;
16/*	const LONG_NAME_MASK *table;
17/*	const char *names;
18/*
19/*	const char *str_name_mask(context, table, mask)
20/*	const char *context;
21/*	const NAME_MASK *table;
22/*	int	mask;
23/*
24/*	const char *str_long_name_mask(context, table, mask)
25/*	const char *context;
26/*	const LONG_NAME_MASK *table;
27/*	long	mask;
28/*
29/*	int	name_mask_opt(context, table, names, flags)
30/*	const char *context;
31/*	const NAME_MASK *table;
32/*	const char *names;
33/*	int	flags;
34/*
35/*	long	long_name_mask_opt(context, table, names, flags)
36/*	const char *context;
37/*	const LONG_NAME_MASK *table;
38/*	const char *names;
39/*	int	flags;
40/*
41/*	int	name_mask_delim_opt(context, table, names, delim, flags)
42/*	const char *context;
43/*	const NAME_MASK *table;
44/*	const char *names;
45/*	const char *delim;
46/*	int	flags;
47/*
48/*	long	long_name_mask_delim_opt(context, table, names, delim, flags)
49/*	const char *context;
50/*	const LONG_NAME_MASK *table;
51/*	const char *names;
52/*	const char *delim;
53/*	int	flags;
54/*
55/*	const char *str_name_mask_opt(buf, context, table, mask, flags)
56/*	VSTRING	*buf;
57/*	const char *context;
58/*	const NAME_MASK *table;
59/*	int	mask;
60/*	int	flags;
61/*
62/*	const char *str_long_name_mask_opt(buf, context, table, mask, flags)
63/*	VSTRING	*buf;
64/*	const char *context;
65/*	const LONG_NAME_MASK *table;
66/*	long	mask;
67/*	int	flags;
68/* DESCRIPTION
69/*	name_mask() takes a null-terminated \fItable\fR with (name, mask)
70/*	values and computes the bit-wise OR of the masks that correspond
71/*	to the names listed in the \fInames\fR argument, separated by
72/*	comma and/or whitespace characters. The "long_" version returns
73/*	a "long int" bitmask, rather than an "int" bitmask.
74/*
75/*	str_name_mask() translates a mask into its equlvalent names.
76/*	The result is written to a static buffer that is overwritten
77/*	upon each call. The "long_" version converts a "long int"
78/*	bitmask, rather than an "int" bitmask.
79/*
80/*	name_mask_opt() and str_name_mask_opt() are extended versions
81/*	with additional fine control. name_mask_delim_opt() supports
82/*	non-default delimiter characters.
83/*
84/*	Arguments:
85/* .IP buf
86/*	Null pointer or pointer to buffer storage.
87/* .IP context
88/*	What kind of names and
89/*	masks are being manipulated, in order to make error messages
90/*	more understandable. Typically, this would be the name of a
91/*	user-configurable parameter.
92/* .IP table
93/*	Table with (name, bit mask) pairs.
94/* .IP names
95/*	A list of names that is to be converted into a bit mask.
96/* .IP mask
97/*	A bit mask.
98/* .IP delim
99/*	Delimiter characters to use instead of whitespace and commas.
100/* .IP flags
101/*	Bit-wise OR of one or more of the following.  Where features
102/*	would have conflicting results (e.g., FATAL versus IGNORE),
103/*	the feature that takes precedence is described first.
104/*
105/*	When converting from string to mask, at least one of the
106/*	following must be specified: NAME_MASK_FATAL, NAME_MASK_RETURN,
107/*	NAME_MASK_WARN or NAME_MASK_IGNORE.
108/*
109/*	When converting from mask to string, at least one of the
110/*	following must be specified: NAME_MASK_NUMBER, NAME_MASK_FATAL,
111/*	NAME_MASK_RETURN, NAME_MASK_WARN or NAME_MASK_IGNORE.
112/* .RS
113/* .IP NAME_MASK_NUMBER
114/*	When converting from string to mask, accept hexadecimal
115/*	inputs starting with "0x" followed by hexadecimal digits.
116/*	Each hexadecimal input may specify multiple bits.  This
117/*	feature is ignored for hexadecimal inputs that cannot be
118/*	converted (malformed, out of range, etc.).
119/*
120/*	When converting from mask to string, represent bits not
121/*	defined in \fItable\fR as "0x" followed by hexadecimal
122/*	digits. This conversion always succeeds.
123/* .IP NAME_MASK_FATAL
124/*	Require that all names listed in \fIname\fR exist in
125/*	\fItable\fR or that they can be parsed as a hexadecimal
126/*	string, and require that all bits listed in \fImask\fR exist
127/*	in \fItable\fR or that they can be converted to hexadecimal
128/*	string.  Terminate with a fatal run-time error if this
129/*	condition is not met.  This feature is enabled by default
130/*	when calling name_mask() or str_name_mask().
131/* .IP NAME_MASK_RETURN
132/*	Require that all names listed in \fIname\fR exist in
133/*	\fItable\fR or that they can be parsed as a hexadecimal
134/*	string, and require that all bits listed in \fImask\fR exist
135/*	in \fItable\fR or that they can be converted to hexadecimal
136/*	string.  Log a warning, and return 0 (name_mask()) or a
137/*	null pointer (str_name_mask()) if this condition is not
138/*	met.  This feature is not enabled by default when calling
139/*	name_mask() or str_name_mask().
140/* .IP NAME_MASK_WARN
141/*	Require that all names listed in \fIname\fR exist in
142/*	\fItable\fR or that they can be parsed as a hexadecimal
143/*	string, and require that all bits listed in \fImask\fR exist
144/*	in \fItable\fR or that they can be converted to hexadecimal
145/*	string.  Log a warning if this condition is not met, continue
146/*	processing, and return all valid bits or names.  This feature
147/*	is not enabled by default when calling name_mask() or
148/*	str_name_mask().
149/* .IP NAME_MASK_IGNORE
150/*	Silently ignore names listed in \fIname\fR that don't exist
151/*	in \fItable\fR and that can't be parsed as a hexadecimal
152/*	string, and silently ignore bits listed in \fImask\fR that
153/*	don't exist in \fItable\fR and that can't be converted to
154/*	hexadecimal string.
155/* .IP NAME_MASK_ANY_CASE
156/*	Enable case-insensitive matching.
157/*	This feature is not enabled by default when calling name_mask();
158/*	it has no effect with str_name_mask().
159/* .IP NAME_MASK_COMMA
160/*	Use comma instead of space when converting a mask to string.
161/* .IP NAME_MASK_PIPE
162/*	Use "|" instead of space when converting a mask to string.
163/* .RE
164/*	The value NAME_MASK_NONE explicitly requests no features,
165/*	and NAME_MASK_DEFAULT enables the default options.
166/* DIAGNOSTICS
167/*	Fatal: the \fInames\fR argument specifies a name not found in
168/*	\fItable\fR, or the \fImask\fR specifies a bit not found in
169/*	\fItable\fR.
170/* LICENSE
171/* .ad
172/* .fi
173/*	The Secure Mailer license must be distributed with this software.
174/* AUTHOR(S)
175/*	Wietse Venema
176/*	IBM T.J. Watson Research
177/*	P.O. Box 704
178/*	Yorktown Heights, NY 10598, USA
179/*--*/
180
181/* System library. */
182
183#include <sys_defs.h>
184#include <string.h>
185#include <errno.h>
186#include <stdlib.h>
187
188#ifdef STRCASECMP_IN_STRINGS_H
189#include <strings.h>
190#endif
191
192/* Utility library. */
193
194#include <msg.h>
195#include <mymalloc.h>
196#include <stringops.h>
197#include <name_mask.h>
198#include <vstring.h>
199
200static int hex_to_ulong(char *, unsigned long, unsigned long *);
201
202#define STR(x) vstring_str(x)
203
204/* name_mask_delim_opt - compute mask corresponding to list of names */
205
206int     name_mask_delim_opt(const char *context, const NAME_MASK *table,
207		            const char *names, const char *delim, int flags)
208{
209    const char *myname = "name_mask";
210    char   *saved_names = mystrdup(names);
211    char   *bp = saved_names;
212    int     result = 0;
213    const NAME_MASK *np;
214    char   *name;
215    int     (*lookup) (const char *, const char *);
216    unsigned long ulval;
217
218    if ((flags & NAME_MASK_REQUIRED) == 0)
219	msg_panic("%s: missing NAME_MASK_FATAL/RETURN/WARN/IGNORE flag",
220		  myname);
221
222    if (flags & NAME_MASK_ANY_CASE)
223	lookup = strcasecmp;
224    else
225	lookup = strcmp;
226
227    /*
228     * Break up the names string, and look up each component in the table. If
229     * the name is found, merge its mask with the result.
230     */
231    while ((name = mystrtok(&bp, delim)) != 0) {
232	for (np = table; /* void */ ; np++) {
233	    if (np->name == 0) {
234		if ((flags & NAME_MASK_NUMBER)
235		    && hex_to_ulong(name, ~0U, &ulval)) {
236		    result |= (unsigned int) ulval;
237		} else if (flags & NAME_MASK_FATAL) {
238		    msg_fatal("unknown %s value \"%s\" in \"%s\"",
239			      context, name, names);
240		} else if (flags & NAME_MASK_RETURN) {
241		    msg_warn("unknown %s value \"%s\" in \"%s\"",
242			     context, name, names);
243		    myfree(saved_names);
244		    return (0);
245		} else if (flags & NAME_MASK_WARN) {
246		    msg_warn("unknown %s value \"%s\" in \"%s\"",
247			     context, name, names);
248		}
249		break;
250	    }
251	    if (lookup(name, np->name) == 0) {
252		if (msg_verbose)
253		    msg_info("%s: %s", myname, name);
254		result |= np->mask;
255		break;
256	    }
257	}
258    }
259    myfree(saved_names);
260    return (result);
261}
262
263/* str_name_mask_opt - mask to string */
264
265const char *str_name_mask_opt(VSTRING *buf, const char *context,
266			              const NAME_MASK *table,
267			              int mask, int flags)
268{
269    const char *myname = "name_mask";
270    const NAME_MASK *np;
271    int     len;
272    static VSTRING *my_buf = 0;
273    int     delim = (flags & NAME_MASK_COMMA ? ',' :
274		     (flags & NAME_MASK_PIPE ? '|' : ' '));
275
276    if ((flags & STR_NAME_MASK_REQUIRED) == 0)
277	msg_panic("%s: missing NAME_MASK_NUMBER/FATAL/RETURN/WARN/IGNORE flag",
278		  myname);
279
280    if (buf == 0) {
281	if (my_buf == 0)
282	    my_buf = vstring_alloc(1);
283	buf = my_buf;
284    }
285    VSTRING_RESET(buf);
286
287    for (np = table; mask != 0; np++) {
288	if (np->name == 0) {
289	    if (flags & NAME_MASK_NUMBER) {
290		vstring_sprintf_append(buf, "0x%x%c", mask, delim);
291	    } else if (flags & NAME_MASK_FATAL) {
292		msg_fatal("%s: unknown %s bit in mask: 0x%x",
293			  myname, context, mask);
294	    } else if (flags & NAME_MASK_RETURN) {
295		msg_warn("%s: unknown %s bit in mask: 0x%x",
296			 myname, context, mask);
297		return (0);
298	    } else if (flags & NAME_MASK_WARN) {
299		msg_warn("%s: unknown %s bit in mask: 0x%x",
300			 myname, context, mask);
301	    }
302	    break;
303	}
304	if (mask & np->mask) {
305	    mask &= ~np->mask;
306	    vstring_sprintf_append(buf, "%s%c", np->name, delim);
307	}
308    }
309    if ((len = VSTRING_LEN(buf)) > 0)
310	vstring_truncate(buf, len - 1);
311    VSTRING_TERMINATE(buf);
312
313    return (STR(buf));
314}
315
316/* long_name_mask_delim_opt - compute mask corresponding to list of names */
317
318long    long_name_mask_delim_opt(const char *context,
319				         const LONG_NAME_MASK * table,
320			               const char *names, const char *delim,
321				         int flags)
322{
323    const char *myname = "name_mask";
324    char   *saved_names = mystrdup(names);
325    char   *bp = saved_names;
326    long    result = 0;
327    const LONG_NAME_MASK *np;
328    char   *name;
329    int     (*lookup) (const char *, const char *);
330    unsigned long ulval;
331
332    if ((flags & NAME_MASK_REQUIRED) == 0)
333	msg_panic("%s: missing NAME_MASK_FATAL/RETURN/WARN/IGNORE flag",
334		  myname);
335
336    if (flags & NAME_MASK_ANY_CASE)
337	lookup = strcasecmp;
338    else
339	lookup = strcmp;
340
341    /*
342     * Break up the names string, and look up each component in the table. If
343     * the name is found, merge its mask with the result.
344     */
345    while ((name = mystrtok(&bp, delim)) != 0) {
346	for (np = table; /* void */ ; np++) {
347	    if (np->name == 0) {
348		if ((flags & NAME_MASK_NUMBER)
349		    && hex_to_ulong(name, ~0UL, &ulval)) {
350		    result |= ulval;
351		} else if (flags & NAME_MASK_FATAL) {
352		    msg_fatal("unknown %s value \"%s\" in \"%s\"",
353			      context, name, names);
354		} else if (flags & NAME_MASK_RETURN) {
355		    msg_warn("unknown %s value \"%s\" in \"%s\"",
356			     context, name, names);
357		    myfree(saved_names);
358		    return (0);
359		} else if (flags & NAME_MASK_WARN) {
360		    msg_warn("unknown %s value \"%s\" in \"%s\"",
361			     context, name, names);
362		}
363		break;
364	    }
365	    if (lookup(name, np->name) == 0) {
366		if (msg_verbose)
367		    msg_info("%s: %s", myname, name);
368		result |= np->mask;
369		break;
370	    }
371	}
372    }
373
374    myfree(saved_names);
375    return (result);
376}
377
378/* str_long_name_mask_opt - mask to string */
379
380const char *str_long_name_mask_opt(VSTRING *buf, const char *context,
381				           const LONG_NAME_MASK * table,
382				           long mask, int flags)
383{
384    const char *myname = "name_mask";
385    int     len;
386    static VSTRING *my_buf = 0;
387    int     delim = (flags & NAME_MASK_COMMA ? ',' :
388		     (flags & NAME_MASK_PIPE ? '|' : ' '));
389    const LONG_NAME_MASK *np;
390
391    if ((flags & STR_NAME_MASK_REQUIRED) == 0)
392	msg_panic("%s: missing NAME_MASK_NUMBER/FATAL/RETURN/WARN/IGNORE flag",
393		  myname);
394
395    if (buf == 0) {
396	if (my_buf == 0)
397	    my_buf = vstring_alloc(1);
398	buf = my_buf;
399    }
400    VSTRING_RESET(buf);
401
402    for (np = table; mask != 0; np++) {
403	if (np->name == 0) {
404	    if (flags & NAME_MASK_NUMBER) {
405		vstring_sprintf_append(buf, "0x%lx%c", mask, delim);
406	    } else if (flags & NAME_MASK_FATAL) {
407		msg_fatal("%s: unknown %s bit in mask: 0x%lx",
408			  myname, context, mask);
409	    } else if (flags & NAME_MASK_RETURN) {
410		msg_warn("%s: unknown %s bit in mask: 0x%lx",
411			 myname, context, mask);
412		return (0);
413	    } else if (flags & NAME_MASK_WARN) {
414		msg_warn("%s: unknown %s bit in mask: 0x%lx",
415			 myname, context, mask);
416	    }
417	    break;
418	}
419	if (mask & np->mask) {
420	    mask &= ~np->mask;
421	    vstring_sprintf_append(buf, "%s%c", np->name, delim);
422	}
423    }
424    if ((len = VSTRING_LEN(buf)) > 0)
425	vstring_truncate(buf, len - 1);
426    VSTRING_TERMINATE(buf);
427
428    return (STR(buf));
429}
430
431/* hex_to_ulong - 0x... to unsigned long or smaller */
432
433static int hex_to_ulong(char *value, unsigned long mask, unsigned long *ulp)
434{
435    unsigned long result;
436    char   *cp;
437
438    if (strncasecmp(value, "0x", 2) != 0)
439	return (0);
440
441    /*
442     * Check for valid hex number. Since the value starts with 0x, strtoul()
443     * will not allow a negative sign before the first nibble. So we don't
444     * need to worry about explicit +/- signs.
445     */
446    errno = 0;
447    result = strtoul(value, &cp, 16);
448    if (*cp != '\0' || errno == ERANGE)
449	return (0);
450
451    *ulp = (result & mask);
452    return (*ulp == result);
453}
454
455#ifdef TEST
456
457 /*
458  * Stand-alone test program.
459  */
460#include <stdlib.h>
461#include <vstream.h>
462#include <vstring_vstream.h>
463
464int     main(int argc, char **argv)
465{
466    static const NAME_MASK demo_table[] = {
467	"zero", 1 << 0,
468	"one", 1 << 1,
469	"two", 1 << 2,
470	"three", 1 << 3,
471	0, 0,
472    };
473    static const NAME_MASK feature_table[] = {
474	"DEFAULT", NAME_MASK_DEFAULT,
475	"FATAL", NAME_MASK_FATAL,
476	"ANY_CASE", NAME_MASK_ANY_CASE,
477	"RETURN", NAME_MASK_RETURN,
478	"COMMA", NAME_MASK_COMMA,
479	"PIPE", NAME_MASK_PIPE,
480	"NUMBER", NAME_MASK_NUMBER,
481	"WARN", NAME_MASK_WARN,
482	"IGNORE", NAME_MASK_IGNORE,
483	0,
484    };
485    int     in_feature_mask;
486    int     out_feature_mask;
487    int     demo_mask;
488    const char *demo_str;
489    VSTRING *out_buf = vstring_alloc(1);
490    VSTRING *in_buf = vstring_alloc(1);
491
492    if (argc != 3)
493	msg_fatal("usage: %s in-feature-mask out-feature-mask", argv[0]);
494    in_feature_mask = name_mask(argv[1], feature_table, argv[1]);
495    out_feature_mask = name_mask(argv[2], feature_table, argv[2]);
496    while (vstring_get_nonl(in_buf, VSTREAM_IN) != VSTREAM_EOF) {
497	demo_mask = name_mask_opt("name", demo_table,
498				  STR(in_buf), in_feature_mask);
499	demo_str = str_name_mask_opt(out_buf, "mask", demo_table,
500				     demo_mask, out_feature_mask);
501	vstream_printf("%s -> 0x%x -> %s\n",
502		       STR(in_buf), demo_mask,
503		       demo_str ? demo_str : "(null)");
504	vstream_fflush(VSTREAM_OUT);
505    }
506    vstring_free(in_buf);
507    vstring_free(out_buf);
508    exit(0);
509}
510
511#endif
512