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