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