xref: /netbsd-src/external/ibm-public/postfix/dist/src/util/name_mask.c (revision 67b9b338a7386232ac596b5fd0cd5a9cc8a03c71)
1 /*	$NetBSD: name_mask.c,v 1.3 2022/10/08 16:12:50 christos 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 equivalent 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 
name_mask_delim_opt(const char * context,const NAME_MASK * table,const char * names,const char * delim,int flags)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 		    myfree(saved_names);
246 		    return (0);
247 		} else if (flags & NAME_MASK_WARN) {
248 		    msg_warn("unknown %s value \"%s\" in \"%s\"",
249 			     context, name, names);
250 		}
251 		break;
252 	    }
253 	    if (lookup(name, np->name) == 0) {
254 		if (msg_verbose)
255 		    msg_info("%s: %s", myname, name);
256 		result |= np->mask;
257 		break;
258 	    }
259 	}
260     }
261     myfree(saved_names);
262     return (result);
263 }
264 
265 /* str_name_mask_opt - mask to string */
266 
str_name_mask_opt(VSTRING * buf,const char * context,const NAME_MASK * table,int mask,int flags)267 const char *str_name_mask_opt(VSTRING *buf, const char *context,
268 			              const NAME_MASK *table,
269 			              int mask, int flags)
270 {
271     const char *myname = "name_mask";
272     const NAME_MASK *np;
273     ssize_t len;
274     static VSTRING *my_buf = 0;
275     int     delim = (flags & NAME_MASK_COMMA ? ',' :
276 		     (flags & NAME_MASK_PIPE ? '|' : ' '));
277 
278     if ((flags & STR_NAME_MASK_REQUIRED) == 0)
279 	msg_panic("%s: missing NAME_MASK_NUMBER/FATAL/RETURN/WARN/IGNORE flag",
280 		  myname);
281 
282     if (buf == 0) {
283 	if (my_buf == 0)
284 	    my_buf = vstring_alloc(1);
285 	buf = my_buf;
286     }
287     VSTRING_RESET(buf);
288 
289     for (np = table; mask != 0; np++) {
290 	if (np->name == 0) {
291 	    if (flags & NAME_MASK_NUMBER) {
292 		vstring_sprintf_append(buf, "0x%x%c", mask, delim);
293 	    } else if (flags & NAME_MASK_FATAL) {
294 		msg_fatal("%s: unknown %s bit in mask: 0x%x",
295 			  myname, context, mask);
296 	    } else if (flags & NAME_MASK_RETURN) {
297 		msg_warn("%s: unknown %s bit in mask: 0x%x",
298 			 myname, context, mask);
299 		return (0);
300 	    } else if (flags & NAME_MASK_WARN) {
301 		msg_warn("%s: unknown %s bit in mask: 0x%x",
302 			 myname, context, mask);
303 	    }
304 	    break;
305 	}
306 	if (mask & np->mask) {
307 	    mask &= ~np->mask;
308 	    vstring_sprintf_append(buf, "%s%c", np->name, delim);
309 	}
310     }
311     if ((len = VSTRING_LEN(buf)) > 0)
312 	vstring_truncate(buf, len - 1);
313     VSTRING_TERMINATE(buf);
314 
315     return (STR(buf));
316 }
317 
318 /* long_name_mask_delim_opt - compute mask corresponding to list of names */
319 
long_name_mask_delim_opt(const char * context,const LONG_NAME_MASK * table,const char * names,const char * delim,int flags)320 long    long_name_mask_delim_opt(const char *context,
321 				         const LONG_NAME_MASK * table,
322 			               const char *names, const char *delim,
323 				         int flags)
324 {
325     const char *myname = "name_mask";
326     char   *saved_names = mystrdup(names);
327     char   *bp = saved_names;
328     long    result = 0;
329     const LONG_NAME_MASK *np;
330     char   *name;
331     int     (*lookup) (const char *, const char *);
332     unsigned long ulval;
333 
334     if ((flags & NAME_MASK_REQUIRED) == 0)
335 	msg_panic("%s: missing NAME_MASK_FATAL/RETURN/WARN/IGNORE flag",
336 		  myname);
337 
338     if (flags & NAME_MASK_ANY_CASE)
339 	lookup = strcasecmp;
340     else
341 	lookup = strcmp;
342 
343     /*
344      * Break up the names string, and look up each component in the table. If
345      * the name is found, merge its mask with the result.
346      */
347     while ((name = mystrtok(&bp, delim)) != 0) {
348 	for (np = table; /* void */ ; np++) {
349 	    if (np->name == 0) {
350 		if ((flags & NAME_MASK_NUMBER)
351 		    && hex_to_ulong(name, ~0UL, &ulval)) {
352 		    result |= ulval;
353 		} else if (flags & NAME_MASK_FATAL) {
354 		    msg_fatal("unknown %s value \"%s\" in \"%s\"",
355 			      context, name, names);
356 		} else if (flags & NAME_MASK_RETURN) {
357 		    msg_warn("unknown %s value \"%s\" in \"%s\"",
358 			     context, name, names);
359 		    myfree(saved_names);
360 		    return (0);
361 		} else if (flags & NAME_MASK_WARN) {
362 		    msg_warn("unknown %s value \"%s\" in \"%s\"",
363 			     context, name, names);
364 		}
365 		break;
366 	    }
367 	    if (lookup(name, np->name) == 0) {
368 		if (msg_verbose)
369 		    msg_info("%s: %s", myname, name);
370 		result |= np->mask;
371 		break;
372 	    }
373 	}
374     }
375 
376     myfree(saved_names);
377     return (result);
378 }
379 
380 /* str_long_name_mask_opt - mask to string */
381 
str_long_name_mask_opt(VSTRING * buf,const char * context,const LONG_NAME_MASK * table,long mask,int flags)382 const char *str_long_name_mask_opt(VSTRING *buf, const char *context,
383 				           const LONG_NAME_MASK * table,
384 				           long mask, int flags)
385 {
386     const char *myname = "name_mask";
387     ssize_t len;
388     static VSTRING *my_buf = 0;
389     int     delim = (flags & NAME_MASK_COMMA ? ',' :
390 		     (flags & NAME_MASK_PIPE ? '|' : ' '));
391     const LONG_NAME_MASK *np;
392 
393     if ((flags & STR_NAME_MASK_REQUIRED) == 0)
394 	msg_panic("%s: missing NAME_MASK_NUMBER/FATAL/RETURN/WARN/IGNORE flag",
395 		  myname);
396 
397     if (buf == 0) {
398 	if (my_buf == 0)
399 	    my_buf = vstring_alloc(1);
400 	buf = my_buf;
401     }
402     VSTRING_RESET(buf);
403 
404     for (np = table; mask != 0; np++) {
405 	if (np->name == 0) {
406 	    if (flags & NAME_MASK_NUMBER) {
407 		vstring_sprintf_append(buf, "0x%lx%c", mask, delim);
408 	    } else if (flags & NAME_MASK_FATAL) {
409 		msg_fatal("%s: unknown %s bit in mask: 0x%lx",
410 			  myname, context, mask);
411 	    } else if (flags & NAME_MASK_RETURN) {
412 		msg_warn("%s: unknown %s bit in mask: 0x%lx",
413 			 myname, context, mask);
414 		return (0);
415 	    } else if (flags & NAME_MASK_WARN) {
416 		msg_warn("%s: unknown %s bit in mask: 0x%lx",
417 			 myname, context, mask);
418 	    }
419 	    break;
420 	}
421 	if (mask & np->mask) {
422 	    mask &= ~np->mask;
423 	    vstring_sprintf_append(buf, "%s%c", np->name, delim);
424 	}
425     }
426     if ((len = VSTRING_LEN(buf)) > 0)
427 	vstring_truncate(buf, len - 1);
428     VSTRING_TERMINATE(buf);
429 
430     return (STR(buf));
431 }
432 
433 /* hex_to_ulong - 0x... to unsigned long or smaller */
434 
hex_to_ulong(char * value,unsigned long mask,unsigned long * ulp)435 static int hex_to_ulong(char *value, unsigned long mask, unsigned long *ulp)
436 {
437     unsigned long result;
438     char   *cp;
439 
440     if (strncasecmp(value, "0x", 2) != 0)
441 	return (0);
442 
443     /*
444      * Check for valid hex number. Since the value starts with 0x, strtoul()
445      * will not allow a negative sign before the first nibble. So we don't
446      * need to worry about explicit +/- signs.
447      */
448     errno = 0;
449     result = strtoul(value, &cp, 16);
450     if (*cp != '\0' || errno == ERANGE)
451 	return (0);
452 
453     *ulp = (result & mask);
454     return (*ulp == result);
455 }
456 
457 #ifdef TEST
458 
459  /*
460   * Stand-alone test program.
461   */
462 #include <stdlib.h>
463 #include <vstream.h>
464 #include <vstring_vstream.h>
465 
main(int argc,char ** argv)466 int     main(int argc, char **argv)
467 {
468     static const NAME_MASK demo_table[] = {
469 	"zero", 1 << 0,
470 	"one", 1 << 1,
471 	"two", 1 << 2,
472 	"three", 1 << 3,
473 	0, 0,
474     };
475     static const NAME_MASK feature_table[] = {
476 	"DEFAULT", NAME_MASK_DEFAULT,
477 	"FATAL", NAME_MASK_FATAL,
478 	"ANY_CASE", NAME_MASK_ANY_CASE,
479 	"RETURN", NAME_MASK_RETURN,
480 	"COMMA", NAME_MASK_COMMA,
481 	"PIPE", NAME_MASK_PIPE,
482 	"NUMBER", NAME_MASK_NUMBER,
483 	"WARN", NAME_MASK_WARN,
484 	"IGNORE", NAME_MASK_IGNORE,
485 	0,
486     };
487     int     in_feature_mask;
488     int     out_feature_mask;
489     int     demo_mask;
490     const char *demo_str;
491     VSTRING *out_buf = vstring_alloc(1);
492     VSTRING *in_buf = vstring_alloc(1);
493 
494     if (argc != 3)
495 	msg_fatal("usage: %s in-feature-mask out-feature-mask", argv[0]);
496     in_feature_mask = name_mask(argv[1], feature_table, argv[1]);
497     out_feature_mask = name_mask(argv[2], feature_table, argv[2]);
498     while (vstring_get_nonl(in_buf, VSTREAM_IN) != VSTREAM_EOF) {
499 	demo_mask = name_mask_opt("name", demo_table,
500 				  STR(in_buf), in_feature_mask);
501 	demo_str = str_name_mask_opt(out_buf, "mask", demo_table,
502 				     demo_mask, out_feature_mask);
503 	vstream_printf("%s -> 0x%x -> %s\n",
504 		       STR(in_buf), demo_mask,
505 		       demo_str ? demo_str : "(null)");
506 	vstream_fflush(VSTREAM_OUT);
507     }
508     vstring_free(in_buf);
509     vstring_free(out_buf);
510     exit(0);
511 }
512 
513 #endif
514