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