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