1 /* $NetBSD: postconf_master.c,v 1.6 2020/03/18 19:05:17 christos Exp $ */ 2 3 /*++ 4 /* NAME 5 /* postconf_master 3 6 /* SUMMARY 7 /* support for master.cf 8 /* SYNOPSIS 9 /* #include <postconf.h> 10 /* 11 /* const char pcf_daemon_options_expecting_value[]; 12 /* 13 /* void pcf_read_master(fail_on_open) 14 /* int fail_on_open; 15 /* 16 /* void pcf_show_master_entries(fp, mode, service_filters) 17 /* VSTREAM *fp; 18 /* int mode; 19 /* char **service_filters; 20 /* 21 /* void pcf_show_master_fields(fp, mode, n_filters, field_filters) 22 /* VSTREAM *fp; 23 /* int mode; 24 /* int n_filters; 25 /* char **field_filters; 26 /* 27 /* void pcf_edit_master_field(masterp, field, new_value) 28 /* PCF_MASTER_ENT *masterp; 29 /* int field; 30 /* const char *new_value; 31 /* 32 /* void pcf_show_master_params(fp, mode, argc, **param_filters) 33 /* VSTREAM *fp; 34 /* int mode; 35 /* int argc; 36 /* char **param_filters; 37 /* 38 /* void pcf_edit_master_param(masterp, mode, param_name, param_value) 39 /* PCF_MASTER_ENT *masterp; 40 /* int mode; 41 /* const char *param_name; 42 /* const char *param_value; 43 /* AUXILIARY FUNCTIONS 44 /* const char *pcf_parse_master_entry(masterp, buf) 45 /* PCF_MASTER_ENT *masterp; 46 /* const char *buf; 47 /* 48 /* void pcf_print_master_entry(fp, mode, masterp) 49 /* VSTREAM *fp; 50 /* int mode; 51 /* PCF_MASTER_ENT *masterp; 52 /* 53 /* void pcf_free_master_entry(masterp) 54 /* PCF_MASTER_ENT *masterp; 55 /* DESCRIPTION 56 /* pcf_read_master() reads entries from master.cf into memory. 57 /* 58 /* pcf_show_master_entries() writes the entries in the master.cf 59 /* file to the specified stream. 60 /* 61 /* pcf_show_master_fields() writes name/type/field=value records 62 /* to the specified stream. 63 /* 64 /* pcf_edit_master_field() updates the value of a single-column 65 /* or multi-column attribute. 66 /* 67 /* pcf_show_master_params() writes name/type/parameter=value 68 /* records to the specified stream. 69 /* 70 /* pcf_edit_master_param() updates, removes or adds the named 71 /* parameter in a master.cf entry (the remove request ignores 72 /* the parameter value). 73 /* 74 /* pcf_daemon_options_expecting_value[] is an array of master.cf 75 /* daemon command-line options that expect an option value. 76 /* 77 /* pcf_parse_master_entry() parses a (perhaps multi-line) 78 /* string that contains a complete master.cf entry, and 79 /* normalizes daemon command-line options to simplify further 80 /* handling. 81 /* 82 /* pcf_print_master_entry() prints a parsed master.cf entry. 83 /* 84 /* pcf_free_master_entry() returns storage to the heap that 85 /* was allocated by pcf_parse_master_entry(). 86 /* 87 /* Arguments 88 /* .IP fail_on_open 89 /* Specify FAIL_ON_OPEN if open failure is a fatal error, 90 /* WARN_ON_OPEN if a warning should be logged instead. 91 /* .IP fp 92 /* Output stream. 93 /* .IP mode 94 /* Bit-wise OR of flags. Flags other than the following are 95 /* ignored. 96 /* .RS 97 /* .IP PCF_FOLD_LINE 98 /* Wrap long output lines. 99 /* .IP PCF_SHOW_EVAL 100 /* Expand $name in parameter values. 101 /* .IP PCF_EDIT_EXCL 102 /* Request that pcf_edit_master_param() removes the parameter. 103 /* .RE 104 /* .IP n_filters 105 /* The number of command-line filters. 106 /* .IP field_filters 107 /* A list of zero or more service field patterns (name/type/field). 108 /* The output is formatted as "name/type/field = value". If 109 /* no filters are specified, pcf_show_master_fields() outputs 110 /* the fields of all master.cf entries in the specified order. 111 /* .IP param_filters 112 /* A list of zero or more service parameter patterns 113 /* (name/type/parameter). The output is formatted as 114 /* "name/type/parameter = value". If no filters are specified, 115 /* pcf_show_master_params() outputs the parameters of all 116 /* master.cf entries in sorted order. 117 /* .IP service_filters 118 /* A list of zero or more service patterns (name or name/type). 119 /* If no filters are specified, pcf_show_master_entries() 120 /* outputs all master.cf entries in the specified order. 121 /* .IP field 122 /* Index into parsed master.cf entry. 123 /* .IP new_value 124 /* Replacement value for the specified field. It is split in 125 /* whitespace in case of a multi-field attribute. 126 /* DIAGNOSTICS 127 /* Problems are reported to the standard error stream. 128 /* LICENSE 129 /* .ad 130 /* .fi 131 /* The Secure Mailer license must be distributed with this software. 132 /* AUTHOR(S) 133 /* Wietse Venema 134 /* IBM T.J. Watson Research 135 /* P.O. Box 704 136 /* Yorktown Heights, NY 10598, USA 137 /* 138 /* Wietse Venema 139 /* Google, Inc. 140 /* 111 8th Avenue 141 /* New York, NY 10011, USA 142 /*--*/ 143 144 /* System library. */ 145 146 #include <sys_defs.h> 147 #include <string.h> 148 #include <stdlib.h> 149 #include <stdarg.h> 150 151 /* Utility library. */ 152 153 #include <msg.h> 154 #include <mymalloc.h> 155 #include <vstring.h> 156 #include <argv.h> 157 #include <vstream.h> 158 #include <readlline.h> 159 #include <stringops.h> 160 #include <split_at.h> 161 162 /* Global library. */ 163 164 #include <mail_params.h> 165 166 /* Master library. */ 167 168 #include <master_proto.h> 169 170 /* Application-specific. */ 171 172 #include <postconf.h> 173 174 const char pcf_daemon_options_expecting_value[] = "o"; 175 176 /* 177 * Data structure to capture a command-line service field filter. 178 */ 179 typedef struct { 180 int match_count; /* hit count */ 181 const char *raw_text; /* full pattern text */ 182 ARGV *service_pattern; /* parsed service name, type, ... */ 183 int field_pattern; /* parsed field pattern */ 184 const char *param_pattern; /* parameter pattern */ 185 } PCF_MASTER_FLD_REQ; 186 187 /* 188 * Valid inputs. 189 */ 190 static const char *pcf_valid_master_types[] = { 191 MASTER_XPORT_NAME_UNIX, 192 MASTER_XPORT_NAME_FIFO, 193 MASTER_XPORT_NAME_INET, 194 MASTER_XPORT_NAME_PASS, 195 MASTER_XPORT_NAME_UXDG, 196 0, 197 }; 198 199 static const char pcf_valid_bool_types[] = "yn-"; 200 201 #define STR(x) vstring_str(x) 202 203 /* pcf_extract_field - extract text from {}, trim leading/trailing blanks */ 204 205 static void pcf_extract_field(ARGV *argv, int field, const char *parens) 206 { 207 char *arg = argv->argv[field]; 208 char *err; 209 210 if ((err = extpar(&arg, parens, EXTPAR_FLAG_STRIP)) != 0) { 211 msg_warn("%s: %s", MASTER_CONF_FILE, err); 212 myfree(err); 213 } 214 argv_replace_one(argv, field, arg); 215 } 216 217 /* pcf_normalize_nameval - normalize name = value from inside {} */ 218 219 static void pcf_normalize_nameval(ARGV *argv, int field) 220 { 221 char *arg = argv->argv[field]; 222 char *name; 223 char *value; 224 const char *err; 225 char *normalized; 226 227 if ((err = split_nameval(arg, &name, &value)) != 0) { 228 msg_warn("%s: %s: \"%s\"", MASTER_CONF_FILE, err, arg); 229 } else { 230 normalized = concatenate(name, "=", value, (char *) 0); 231 argv_replace_one(argv, field, normalized); 232 myfree(normalized); 233 } 234 } 235 236 /* pcf_normalize_daemon_args - bring daemon arguments into canonical form */ 237 238 static void pcf_normalize_daemon_args(ARGV *argv) 239 { 240 int field; 241 char *arg; 242 char *cp; 243 char *junk; 244 int extract_field; 245 246 /* 247 * Normalize options to simplify later processing. 248 */ 249 for (field = PCF_MASTER_MIN_FIELDS; argv->argv[field] != 0; field++) { 250 arg = argv->argv[field]; 251 if (arg[0] != '-' || strcmp(arg, "--") == 0) 252 break; 253 for (cp = arg + 1; *cp; cp++) { 254 if (strchr(pcf_daemon_options_expecting_value, *cp) != 0 255 && cp > arg + 1) { 256 /* Split "-stuffozz" into "-stuff" and "-ozz". */ 257 junk = concatenate("-", cp, (char *) 0); 258 argv_insert_one(argv, field + 1, junk); 259 myfree(junk); 260 *cp = 0; /* XXX argv_replace_one() */ 261 break; 262 } 263 } 264 if (strchr(pcf_daemon_options_expecting_value, arg[1]) == 0) 265 /* Option requires no value. */ 266 continue; 267 if (arg[2] != 0) { 268 /* Split "-oname=value" into "-o" "name=value". */ 269 argv_insert_one(argv, field + 1, arg + 2); 270 arg[2] = 0; /* XXX argv_replace_one() */ 271 field += 1; 272 extract_field = (argv->argv[field][0] == CHARS_BRACE[0]); 273 } else if (argv->argv[field + 1] != 0) { 274 /* Already in "-o" "name=value" form. */ 275 field += 1; 276 extract_field = (argv->argv[field][0] == CHARS_BRACE[0]); 277 } else 278 extract_field = 0; 279 /* Extract text inside {}, optionally convert to name=value. */ 280 if (extract_field) { 281 pcf_extract_field(argv, field, CHARS_BRACE); 282 if (argv->argv[field - 1][1] == 'o') 283 pcf_normalize_nameval(argv, field); 284 } 285 } 286 /* Normalize non-option arguments. */ 287 for ( /* void */ ; argv->argv[field] != 0; field++) 288 /* Extract text inside {}. */ 289 if (argv->argv[field][0] == CHARS_BRACE[0]) 290 pcf_extract_field(argv, field, CHARS_BRACE); 291 } 292 293 /* pcf_fix_fatal - fix multiline text before release */ 294 295 static NORETURN PRINTFLIKE(1, 2) pcf_fix_fatal(const char *fmt,...) 296 { 297 VSTRING *buf = vstring_alloc(100); 298 va_list ap; 299 300 /* 301 * Replace newline with whitespace. 302 */ 303 va_start(ap, fmt); 304 vstring_vsprintf(buf, fmt, ap); 305 va_end(ap); 306 translit(STR(buf), "\n", " "); 307 msg_fatal("%s", STR(buf)); 308 /* NOTREACHED */ 309 } 310 311 /* pcf_check_master_entry - sanity check master.cf entry */ 312 313 static void pcf_check_master_entry(ARGV *argv, const char *raw_text) 314 { 315 const char **cpp; 316 char *cp; 317 int len; 318 int field; 319 320 cp = argv->argv[PCF_MASTER_FLD_TYPE]; 321 for (cpp = pcf_valid_master_types; /* see below */ ; cpp++) { 322 if (*cpp == 0) 323 pcf_fix_fatal("invalid " PCF_MASTER_NAME_TYPE " field \"%s\" in \"%s\"", 324 cp, raw_text); 325 if (strcmp(*cpp, cp) == 0) 326 break; 327 } 328 329 for (field = PCF_MASTER_FLD_PRIVATE; field <= PCF_MASTER_FLD_CHROOT; field++) { 330 cp = argv->argv[field]; 331 if (cp[1] != 0 || strchr(pcf_valid_bool_types, *cp) == 0) 332 pcf_fix_fatal("invalid %s field \"%s\" in \"%s\"", 333 pcf_str_field_pattern(field), cp, raw_text); 334 } 335 336 cp = argv->argv[PCF_MASTER_FLD_WAKEUP]; 337 len = strlen(cp); 338 if (len > 0 && cp[len - 1] == '?') 339 len--; 340 if (!(cp[0] == '-' && len == 1) && strspn(cp, "0123456789") != len) 341 pcf_fix_fatal("invalid " PCF_MASTER_NAME_WAKEUP " field \"%s\" in \"%s\"", 342 cp, raw_text); 343 344 cp = argv->argv[PCF_MASTER_FLD_MAXPROC]; 345 if (strcmp("-", cp) != 0 && cp[strspn(cp, "0123456789")] != 0) 346 pcf_fix_fatal("invalid " PCF_MASTER_NAME_MAXPROC " field \"%s\" in \"%s\"", 347 cp, raw_text); 348 } 349 350 /* pcf_free_master_entry - destroy parsed entry */ 351 352 void pcf_free_master_entry(PCF_MASTER_ENT *masterp) 353 { 354 /* XX Fixme: allocation/deallocation asymmetry. */ 355 myfree(masterp->name_space); 356 argv_free(masterp->argv); 357 if (masterp->valid_names) 358 htable_free(masterp->valid_names, myfree); 359 if (masterp->ro_params) 360 dict_close(masterp->ro_params); 361 if (masterp->all_params) 362 dict_close(masterp->all_params); 363 myfree((void *) masterp); 364 } 365 366 /* pcf_parse_master_entry - parse one master line */ 367 368 const char *pcf_parse_master_entry(PCF_MASTER_ENT *masterp, const char *buf) 369 { 370 ARGV *argv; 371 char *ro_name_space; 372 char *process_name; 373 374 /* 375 * We can't use the master daemon's master_ent routines in their current 376 * form. They convert everything to internal form, and they skip disabled 377 * services. 378 * 379 * The postconf command needs to show default fields as "-", and needs to 380 * know about all service names so that it can generate service-dependent 381 * parameter names (transport-dependent etc.). 382 * 383 * XXX Do per-field sanity checks. 384 */ 385 argv = argv_splitq(buf, PCF_MASTER_BLANKS, CHARS_BRACE); 386 if (argv->argc < PCF_MASTER_MIN_FIELDS) { 387 argv_free(argv); /* Coverity 201311 */ 388 return ("bad field count"); 389 } 390 pcf_check_master_entry(argv, buf); 391 pcf_normalize_daemon_args(argv); 392 masterp->name_space = 393 concatenate(argv->argv[0], PCF_NAMESP_SEP_STR, argv->argv[1], (char *) 0); 394 ro_name_space = 395 concatenate("ro", PCF_NAMESP_SEP_STR, masterp->name_space, (char *) 0); 396 masterp->argv = argv; 397 masterp->valid_names = 0; 398 process_name = basename(argv->argv[PCF_MASTER_FLD_CMD]); 399 dict_update(ro_name_space, VAR_PROCNAME, process_name); 400 dict_update(ro_name_space, VAR_SERVNAME, 401 strcmp(process_name, argv->argv[0]) != 0 ? 402 argv->argv[0] : process_name); 403 masterp->ro_params = dict_handle(ro_name_space); 404 myfree(ro_name_space); 405 masterp->all_params = 0; 406 return (0); 407 } 408 409 /* pcf_read_master - read and digest the master.cf file */ 410 411 void pcf_read_master(int fail_on_open_error) 412 { 413 const char *myname = "pcf_read_master"; 414 char *path; 415 VSTRING *buf; 416 VSTREAM *fp; 417 const char *err; 418 int entry_count = 0; 419 int line_count; 420 int last_line = 0; 421 422 /* 423 * Sanity check. 424 */ 425 if (pcf_master_table != 0) 426 msg_panic("%s: master table is already initialized", myname); 427 428 /* 429 * Get the location of master.cf. 430 */ 431 if (var_config_dir == 0) 432 pcf_set_config_dir(); 433 path = concatenate(var_config_dir, "/", MASTER_CONF_FILE, (char *) 0); 434 435 /* 436 * Initialize the in-memory master table. 437 */ 438 pcf_master_table = (PCF_MASTER_ENT *) mymalloc(sizeof(*pcf_master_table)); 439 440 /* 441 * Skip blank lines and comment lines. Degrade gracefully if master.cf is 442 * not available, and master.cf is not the primary target. 443 */ 444 if ((fp = vstream_fopen(path, O_RDONLY, 0)) == 0) { 445 if (fail_on_open_error) 446 msg_fatal("open %s: %m", path); 447 msg_warn("open %s: %m", path); 448 } else { 449 buf = vstring_alloc(100); 450 while (readllines(buf, fp, &last_line, &line_count) != 0) { 451 pcf_master_table = (PCF_MASTER_ENT *) myrealloc((void *) pcf_master_table, 452 (entry_count + 2) * sizeof(*pcf_master_table)); 453 if ((err = pcf_parse_master_entry(pcf_master_table + entry_count, 454 STR(buf))) != 0) 455 msg_fatal("file %s: line %d: %s", path, line_count, err); 456 entry_count += 1; 457 } 458 vstream_fclose(fp); 459 vstring_free(buf); 460 } 461 462 /* 463 * Null-terminate the master table and clean up. 464 */ 465 pcf_master_table[entry_count].argv = 0; 466 myfree(path); 467 } 468 469 /* pcf_print_master_entry - print one master line */ 470 471 void pcf_print_master_entry(VSTREAM *fp, int mode, PCF_MASTER_ENT *masterp) 472 { 473 char **argv = masterp->argv->argv; 474 const char *arg; 475 const char *aval; 476 int arg_len; 477 int line_len; 478 int field; 479 int in_daemon_options; 480 int need_parens; 481 static int column_goal[] = { 482 0, /* service */ 483 11, /* type */ 484 17, /* private */ 485 25, /* unpriv */ 486 33, /* chroot */ 487 41, /* wakeup */ 488 49, /* maxproc */ 489 57, /* command */ 490 }; 491 492 #define ADD_TEXT(text, len) do { \ 493 vstream_fputs(text, fp); line_len += len; } \ 494 while (0) 495 #define ADD_SPACE ADD_TEXT(" ", 1) 496 497 /* 498 * Show the standard fields at their preferred column position. Use at 499 * least one-space column separation. 500 */ 501 for (line_len = 0, field = 0; field < PCF_MASTER_MIN_FIELDS; field++) { 502 arg = argv[field]; 503 if (line_len > 0) { 504 do { 505 ADD_SPACE; 506 } while (line_len < column_goal[field]); 507 } 508 ADD_TEXT(arg, strlen(arg)); 509 } 510 511 /* 512 * Format the daemon command-line options and non-option arguments. Here, 513 * we have no data-dependent preference for column positions, but we do 514 * have argument grouping preferences. 515 */ 516 in_daemon_options = 1; 517 for ( /* void */ ; (arg = argv[field]) != 0; field++) { 518 arg_len = strlen(arg); 519 aval = 0; 520 need_parens = 0; 521 if (in_daemon_options) { 522 523 /* 524 * Try to show the generic options (-v -D) on the first line, and 525 * non-options on a later line. 526 */ 527 if (arg[0] != '-' || strcmp(arg, "--") == 0) { 528 in_daemon_options = 0; 529 #if 0 530 if (mode & PCF_FOLD_LINE) 531 /* Force line wrap. */ 532 line_len = PCF_LINE_LIMIT; 533 #endif 534 } 535 536 /* 537 * Special processing for options that require a value. 538 */ 539 else if (strchr(pcf_daemon_options_expecting_value, arg[1]) != 0 540 && (aval = argv[field + 1]) != 0) { 541 542 /* Force line wrap before option with value. */ 543 line_len = PCF_LINE_LIMIT; 544 545 /* 546 * Optionally, expand $name in parameter value. 547 */ 548 if (strcmp(arg, "-o") == 0 549 && (mode & PCF_SHOW_EVAL) != 0) 550 aval = pcf_expand_parameter_value((VSTRING *) 0, mode, 551 aval, masterp); 552 553 /* 554 * Keep option and value on the same line. 555 */ 556 arg_len += strlen(aval) + 3; 557 if ((need_parens = aval[strcspn(aval, PCF_MASTER_BLANKS)]) != 0) 558 arg_len += 2; 559 } 560 } else { 561 need_parens = arg[strcspn(arg, PCF_MASTER_BLANKS)]; 562 } 563 564 /* 565 * Insert a line break when the next item won't fit. 566 */ 567 if (line_len > PCF_INDENT_LEN) { 568 if ((mode & PCF_FOLD_LINE) == 0 569 || line_len + 1 + arg_len < PCF_LINE_LIMIT) { 570 ADD_SPACE; 571 } else { 572 vstream_fputs("\n" PCF_INDENT_TEXT, fp); 573 line_len = PCF_INDENT_LEN; 574 } 575 } 576 if (in_daemon_options == 0 && need_parens) 577 ADD_TEXT("{", 1); 578 ADD_TEXT(arg, strlen(arg)); 579 if (in_daemon_options == 0 && need_parens) 580 ADD_TEXT("}", 1); 581 if (aval) { 582 ADD_TEXT(" ", 1); 583 if (need_parens) 584 ADD_TEXT("{", 1); 585 ADD_TEXT(aval, strlen(aval)); 586 if (need_parens) 587 ADD_TEXT("}", 1); 588 field += 1; 589 590 /* Force line wrap after option with value. */ 591 line_len = PCF_LINE_LIMIT; 592 593 } 594 } 595 vstream_fputs("\n", fp); 596 597 if (msg_verbose) 598 vstream_fflush(fp); 599 } 600 601 /* pcf_show_master_entries - show master.cf entries */ 602 603 void pcf_show_master_entries(VSTREAM *fp, int mode, int argc, char **argv) 604 { 605 PCF_MASTER_ENT *masterp; 606 PCF_MASTER_FLD_REQ *field_reqs; 607 PCF_MASTER_FLD_REQ *req; 608 609 /* 610 * Parse the filter expressions. 611 */ 612 if (argc > 0) { 613 field_reqs = (PCF_MASTER_FLD_REQ *) 614 mymalloc(sizeof(*field_reqs) * argc); 615 for (req = field_reqs; req < field_reqs + argc; req++) { 616 req->match_count = 0; 617 req->raw_text = *argv++; 618 req->service_pattern = 619 pcf_parse_service_pattern(req->raw_text, 1, 2); 620 if (req->service_pattern == 0) 621 msg_fatal("-M option requires service_name[/type]"); 622 } 623 } 624 625 /* 626 * Iterate over the master table. 627 */ 628 for (masterp = pcf_master_table; masterp->argv != 0; masterp++) { 629 if (argc > 0) { 630 for (req = field_reqs; req < field_reqs + argc; req++) { 631 if (PCF_MATCH_SERVICE_PATTERN(req->service_pattern, 632 masterp->argv->argv[0], 633 masterp->argv->argv[1])) { 634 req->match_count++; 635 pcf_print_master_entry(fp, mode, masterp); 636 } 637 } 638 } else { 639 pcf_print_master_entry(fp, mode, masterp); 640 } 641 } 642 643 /* 644 * Cleanup. 645 */ 646 if (argc > 0) { 647 for (req = field_reqs; req < field_reqs + argc; req++) { 648 if (req->match_count == 0) 649 msg_warn("unmatched request: \"%s\"", req->raw_text); 650 argv_free(req->service_pattern); 651 } 652 myfree((void *) field_reqs); 653 } 654 } 655 656 /* pcf_print_master_field - scaffolding */ 657 658 static void pcf_print_master_field(VSTREAM *fp, int mode, 659 PCF_MASTER_ENT *masterp, 660 int field) 661 { 662 char **argv = masterp->argv->argv; 663 const char *arg; 664 const char *aval; 665 int arg_len; 666 int line_len; 667 int in_daemon_options; 668 int need_parens; 669 670 /* 671 * Show the field value, or the first value in the case of a multi-column 672 * field. 673 */ 674 #define ADD_CHAR(ch) ADD_TEXT((ch), 1) 675 676 line_len = 0; 677 if ((mode & PCF_HIDE_NAME) == 0) { 678 ADD_TEXT(argv[0], strlen(argv[0])); 679 ADD_CHAR(PCF_NAMESP_SEP_STR); 680 ADD_TEXT(argv[1], strlen(argv[1])); 681 ADD_CHAR(PCF_NAMESP_SEP_STR); 682 ADD_TEXT(pcf_str_field_pattern(field), strlen(pcf_str_field_pattern(field))); 683 } 684 if ((mode & (PCF_HIDE_NAME | PCF_HIDE_VALUE)) == 0) { 685 ADD_TEXT(" = ", 3); 686 } 687 if ((mode & PCF_HIDE_VALUE) == 0) { 688 if (line_len > 0 && line_len + strlen(argv[field]) > PCF_LINE_LIMIT) { 689 vstream_fputs("\n" PCF_INDENT_TEXT, fp); 690 line_len = PCF_INDENT_LEN; 691 } 692 ADD_TEXT(argv[field], strlen(argv[field])); 693 } 694 695 /* 696 * Format the daemon command-line options and non-option arguments. Here, 697 * we have no data-dependent preference for column positions, but we do 698 * have argument grouping preferences. 699 */ 700 if (field == PCF_MASTER_FLD_CMD && (mode & PCF_HIDE_VALUE) == 0) { 701 in_daemon_options = 1; 702 for (field += 1; (arg = argv[field]) != 0; field++) { 703 arg_len = strlen(arg); 704 aval = 0; 705 need_parens = 0; 706 if (in_daemon_options) { 707 708 /* 709 * We make no special case for generic options (-v -D) 710 * options. 711 */ 712 if (arg[0] != '-' || strcmp(arg, "--") == 0) { 713 in_daemon_options = 0; 714 } else if (strchr(pcf_daemon_options_expecting_value, arg[1]) != 0 715 && (aval = argv[field + 1]) != 0) { 716 717 /* Force line break before option with value. */ 718 line_len = PCF_LINE_LIMIT; 719 720 /* 721 * Optionally, expand $name in parameter value. 722 */ 723 if (strcmp(arg, "-o") == 0 724 && (mode & PCF_SHOW_EVAL) != 0) 725 aval = pcf_expand_parameter_value((VSTRING *) 0, mode, 726 aval, masterp); 727 728 /* 729 * Keep option and value on the same line. 730 */ 731 arg_len += strlen(aval) + 1; 732 if ((need_parens = aval[strcspn(aval, PCF_MASTER_BLANKS)]) != 0) 733 arg_len += 2; 734 } 735 } else { 736 need_parens = arg[strcspn(arg, PCF_MASTER_BLANKS)]; 737 } 738 739 /* 740 * Insert a line break when the next item won't fit. 741 */ 742 if (line_len > PCF_INDENT_LEN) { 743 if ((mode & PCF_FOLD_LINE) == 0 744 || line_len + 1 + arg_len < PCF_LINE_LIMIT) { 745 ADD_SPACE; 746 } else { 747 vstream_fputs("\n" PCF_INDENT_TEXT, fp); 748 line_len = PCF_INDENT_LEN; 749 } 750 } 751 if (in_daemon_options == 0 && need_parens) 752 ADD_TEXT("{", 1); 753 ADD_TEXT(arg, strlen(arg)); 754 if (in_daemon_options == 0 && need_parens) 755 ADD_TEXT("}", 1); 756 if (aval) { 757 ADD_SPACE; 758 if (need_parens) 759 ADD_TEXT("{", 1); 760 ADD_TEXT(aval, strlen(aval)); 761 if (need_parens) 762 ADD_TEXT("}", 1); 763 field += 1; 764 765 /* Force line break after option with value. */ 766 line_len = PCF_LINE_LIMIT; 767 } 768 } 769 } 770 vstream_fputs("\n", fp); 771 772 if (msg_verbose) 773 vstream_fflush(fp); 774 } 775 776 /* pcf_show_master_fields - show master.cf fields */ 777 778 void pcf_show_master_fields(VSTREAM *fp, int mode, int argc, char **argv) 779 { 780 const char *myname = "pcf_show_master_fields"; 781 PCF_MASTER_ENT *masterp; 782 PCF_MASTER_FLD_REQ *field_reqs; 783 PCF_MASTER_FLD_REQ *req; 784 int field; 785 786 /* 787 * Parse the filter expressions. 788 */ 789 if (argc > 0) { 790 field_reqs = (PCF_MASTER_FLD_REQ *) 791 mymalloc(sizeof(*field_reqs) * argc); 792 for (req = field_reqs; req < field_reqs + argc; req++) { 793 req->match_count = 0; 794 req->raw_text = *argv++; 795 req->service_pattern = 796 pcf_parse_service_pattern(req->raw_text, 1, 3); 797 if (req->service_pattern == 0) 798 msg_fatal("-F option requires service_name[/type[/field]]"); 799 field = req->field_pattern = 800 pcf_parse_field_pattern(req->service_pattern->argv[2]); 801 if (pcf_is_magic_field_pattern(field) == 0 802 && (field < 0 || field > PCF_MASTER_FLD_CMD)) 803 msg_panic("%s: bad attribute field index: %d", 804 myname, field); 805 } 806 } 807 808 /* 809 * Iterate over the master table. 810 */ 811 for (masterp = pcf_master_table; masterp->argv != 0; masterp++) { 812 if (argc > 0) { 813 for (req = field_reqs; req < field_reqs + argc; req++) { 814 if (PCF_MATCH_SERVICE_PATTERN(req->service_pattern, 815 masterp->argv->argv[0], 816 masterp->argv->argv[1])) { 817 req->match_count++; 818 field = req->field_pattern; 819 if (pcf_is_magic_field_pattern(field)) { 820 for (field = 0; field <= PCF_MASTER_FLD_CMD; field++) 821 pcf_print_master_field(fp, mode, masterp, field); 822 } else { 823 pcf_print_master_field(fp, mode, masterp, field); 824 } 825 } 826 } 827 } else { 828 for (field = 0; field <= PCF_MASTER_FLD_CMD; field++) 829 pcf_print_master_field(fp, mode, masterp, field); 830 } 831 } 832 833 /* 834 * Cleanup. 835 */ 836 if (argc > 0) { 837 for (req = field_reqs; req < field_reqs + argc; req++) { 838 if (req->match_count == 0) 839 msg_warn("unmatched request: \"%s\"", req->raw_text); 840 argv_free(req->service_pattern); 841 } 842 myfree((void *) field_reqs); 843 } 844 } 845 846 /* pcf_edit_master_field - replace master.cf field value. */ 847 848 void pcf_edit_master_field(PCF_MASTER_ENT *masterp, int field, 849 const char *new_value) 850 { 851 852 /* 853 * Replace multi-column attribute. 854 */ 855 if (field == PCF_MASTER_FLD_CMD) { 856 argv_truncate(masterp->argv, PCF_MASTER_FLD_CMD); 857 argv_splitq_append(masterp->argv, new_value, PCF_MASTER_BLANKS, CHARS_BRACE); 858 pcf_normalize_daemon_args(masterp->argv); 859 } 860 861 /* 862 * Replace single-column attribute. 863 */ 864 else { 865 argv_replace_one(masterp->argv, field, new_value); 866 } 867 868 /* 869 * Do per-field sanity checks. 870 */ 871 pcf_check_master_entry(masterp->argv, new_value); 872 } 873 874 /* pcf_print_master_param - scaffolding */ 875 876 static void pcf_print_master_param(VSTREAM *fp, int mode, 877 PCF_MASTER_ENT *masterp, 878 const char *param_name, 879 const char *param_value) 880 { 881 if (mode & PCF_HIDE_VALUE) { 882 pcf_print_line(fp, mode, "%s%c%s\n", 883 masterp->name_space, PCF_NAMESP_SEP_CH, 884 param_name); 885 } else { 886 if ((mode & PCF_SHOW_EVAL) != 0) 887 param_value = pcf_expand_parameter_value((VSTRING *) 0, mode, 888 param_value, masterp); 889 if ((mode & PCF_HIDE_NAME) == 0) { 890 pcf_print_line(fp, mode, "%s%c%s = %s\n", 891 masterp->name_space, PCF_NAMESP_SEP_CH, 892 param_name, param_value); 893 } else { 894 pcf_print_line(fp, mode, "%s\n", param_value); 895 } 896 } 897 if (msg_verbose) 898 vstream_fflush(fp); 899 } 900 901 /* pcf_sort_argv_cb - sort argv call-back */ 902 903 static int pcf_sort_argv_cb(const void *a, const void *b) 904 { 905 return (strcmp(*(char **) a, *(char **) b)); 906 } 907 908 /* pcf_show_master_any_param - show any parameter in master.cf service entry */ 909 910 static void pcf_show_master_any_param(VSTREAM *fp, int mode, 911 PCF_MASTER_ENT *masterp) 912 { 913 const char *myname = "pcf_show_master_any_param"; 914 ARGV *argv = argv_alloc(10); 915 DICT *dict = masterp->all_params; 916 const char *param_name; 917 const char *param_value; 918 int param_count = 0; 919 int how; 920 char **cpp; 921 922 /* 923 * Print parameters in sorted order. The number of parameters per 924 * master.cf entry is small, so we optmiize for code simplicity and don't 925 * worry about the cost of double lookup. 926 */ 927 928 /* Look up the parameter names and ignore the values. */ 929 930 for (how = DICT_SEQ_FUN_FIRST; 931 dict->sequence(dict, how, ¶m_name, ¶m_value) == 0; 932 how = DICT_SEQ_FUN_NEXT) { 933 argv_add(argv, param_name, ARGV_END); 934 param_count++; 935 } 936 937 /* Print the parameters in sorted order. */ 938 939 qsort(argv->argv, param_count, sizeof(argv->argv[0]), pcf_sort_argv_cb); 940 for (cpp = argv->argv; (param_name = *cpp) != 0; cpp++) { 941 if ((param_value = dict_get(dict, param_name)) == 0) 942 msg_panic("%s: parameter name not found: %s", myname, param_name); 943 pcf_print_master_param(fp, mode, masterp, param_name, param_value); 944 } 945 946 /* 947 * Clean up. 948 */ 949 argv_free(argv); 950 } 951 952 /* pcf_show_master_params - show master.cf params */ 953 954 void pcf_show_master_params(VSTREAM *fp, int mode, int argc, char **argv) 955 { 956 PCF_MASTER_ENT *masterp; 957 PCF_MASTER_FLD_REQ *field_reqs; 958 PCF_MASTER_FLD_REQ *req; 959 DICT *dict; 960 const char *param_value; 961 962 /* 963 * Parse the filter expressions. 964 */ 965 if (argc > 0) { 966 field_reqs = (PCF_MASTER_FLD_REQ *) 967 mymalloc(sizeof(*field_reqs) * argc); 968 for (req = field_reqs; req < field_reqs + argc; req++) { 969 req->match_count = 0; 970 req->raw_text = *argv++; 971 req->service_pattern = 972 pcf_parse_service_pattern(req->raw_text, 1, 3); 973 if (req->service_pattern == 0) 974 msg_fatal("-P option requires service_name[/type[/parameter]]"); 975 req->param_pattern = req->service_pattern->argv[2]; 976 } 977 } 978 979 /* 980 * Iterate over the master table. 981 */ 982 for (masterp = pcf_master_table; masterp->argv != 0; masterp++) { 983 if ((dict = masterp->all_params) != 0) { 984 if (argc > 0) { 985 for (req = field_reqs; req < field_reqs + argc; req++) { 986 if (PCF_MATCH_SERVICE_PATTERN(req->service_pattern, 987 masterp->argv->argv[0], 988 masterp->argv->argv[1])) { 989 if (PCF_IS_MAGIC_PARAM_PATTERN(req->param_pattern)) { 990 pcf_show_master_any_param(fp, mode, masterp); 991 req->match_count += 1; 992 } else if ((param_value = dict_get(dict, 993 req->param_pattern)) != 0) { 994 pcf_print_master_param(fp, mode, masterp, 995 req->param_pattern, 996 param_value); 997 req->match_count += 1; 998 } 999 } 1000 } 1001 } else { 1002 pcf_show_master_any_param(fp, mode, masterp); 1003 } 1004 } 1005 } 1006 1007 /* 1008 * Cleanup. 1009 */ 1010 if (argc > 0) { 1011 for (req = field_reqs; req < field_reqs + argc; req++) { 1012 if (req->match_count == 0) 1013 msg_warn("unmatched request: \"%s\"", req->raw_text); 1014 argv_free(req->service_pattern); 1015 } 1016 myfree((void *) field_reqs); 1017 } 1018 } 1019 1020 /* pcf_edit_master_param - update, add or remove -o parameter=value */ 1021 1022 void pcf_edit_master_param(PCF_MASTER_ENT *masterp, int mode, 1023 const char *param_name, 1024 const char *param_value) 1025 { 1026 const char *myname = "pcf_edit_master_param"; 1027 ARGV *argv = masterp->argv; 1028 const char *arg; 1029 const char *aval; 1030 int param_match = 0; 1031 int name_len = strlen(param_name); 1032 int field; 1033 1034 for (field = PCF_MASTER_MIN_FIELDS; argv->argv[field] != 0; field++) { 1035 arg = argv->argv[field]; 1036 1037 /* 1038 * Stop at the first non-option argument or end-of-list. 1039 */ 1040 if (arg[0] != '-' || strcmp(arg, "--") == 0) { 1041 break; 1042 } 1043 1044 /* 1045 * Zoom in on command-line options with a value. 1046 */ 1047 else if (strchr(pcf_daemon_options_expecting_value, arg[1]) != 0 1048 && (aval = argv->argv[field + 1]) != 0) { 1049 1050 /* 1051 * Zoom in on "-o parameter=value". 1052 */ 1053 if (strcmp(arg, "-o") == 0) { 1054 if (strncmp(aval, param_name, name_len) == 0 1055 && aval[name_len] == '=') { 1056 param_match = 1; 1057 switch (mode & (PCF_EDIT_CONF | PCF_EDIT_EXCL)) { 1058 1059 /* 1060 * Update parameter=value. 1061 */ 1062 case PCF_EDIT_CONF: 1063 aval = concatenate(param_name, "=", 1064 param_value, (char *) 0); 1065 argv_replace_one(argv, field + 1, aval); 1066 myfree((void *) aval); 1067 if (masterp->all_params) 1068 dict_put(masterp->all_params, param_name, param_value); 1069 /* XXX Update parameter "used/defined" status. */ 1070 break; 1071 1072 /* 1073 * Delete parameter=value. 1074 */ 1075 case PCF_EDIT_EXCL: 1076 argv_delete(argv, field, 2); 1077 if (masterp->all_params) 1078 dict_del(masterp->all_params, param_name); 1079 /* XXX Update parameter "used/defined" status. */ 1080 field -= 2; 1081 break; 1082 default: 1083 msg_panic("%s: unexpected mode: %d", myname, mode); 1084 } 1085 } 1086 } 1087 1088 /* 1089 * Skip over the command-line option value. 1090 */ 1091 field += 1; 1092 } 1093 } 1094 1095 /* 1096 * Add unmatched parameter. 1097 */ 1098 if ((mode & PCF_EDIT_CONF) && param_match == 0) { 1099 /* XXX Generalize: argv_insert(argv, where, list...) */ 1100 argv_insert_one(argv, field, "-o"); 1101 aval = concatenate(param_name, "=", 1102 param_value, (char *) 0); 1103 argv_insert_one(argv, field + 1, aval); 1104 if (masterp->all_params) 1105 dict_put(masterp->all_params, param_name, param_value); 1106 /* XXX May affect parameter "used/defined" status. */ 1107 myfree((void *) aval); 1108 param_match = 1; 1109 } 1110 } 1111