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