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