1 /*        $NetBSD: name_mask.c,v 1.4 2025/02/25 19:15:52 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