diff options
Diffstat (limited to 'src/postconf/postconf_master.c')
-rw-r--r-- | src/postconf/postconf_master.c | 1120 |
1 files changed, 1120 insertions, 0 deletions
diff --git a/src/postconf/postconf_master.c b/src/postconf/postconf_master.c new file mode 100644 index 0000000..5b876b2 --- /dev/null +++ b/src/postconf/postconf_master.c @@ -0,0 +1,1120 @@ +/*++ +/* NAME +/* postconf_master 3 +/* SUMMARY +/* support for master.cf +/* SYNOPSIS +/* #include <postconf.h> +/* +/* const char pcf_daemon_options_expecting_value[]; +/* +/* void pcf_read_master(fail_on_open) +/* int fail_on_open; +/* +/* void pcf_show_master_entries(fp, mode, service_filters) +/* VSTREAM *fp; +/* int mode; +/* char **service_filters; +/* +/* void pcf_show_master_fields(fp, mode, n_filters, field_filters) +/* VSTREAM *fp; +/* int mode; +/* int n_filters; +/* char **field_filters; +/* +/* void pcf_edit_master_field(masterp, field, new_value) +/* PCF_MASTER_ENT *masterp; +/* int field; +/* const char *new_value; +/* +/* void pcf_show_master_params(fp, mode, argc, **param_filters) +/* VSTREAM *fp; +/* int mode; +/* int argc; +/* char **param_filters; +/* +/* void pcf_edit_master_param(masterp, mode, param_name, param_value) +/* PCF_MASTER_ENT *masterp; +/* int mode; +/* const char *param_name; +/* const char *param_value; +/* AUXILIARY FUNCTIONS +/* const char *pcf_parse_master_entry(masterp, buf) +/* PCF_MASTER_ENT *masterp; +/* const char *buf; +/* +/* void pcf_print_master_entry(fp, mode, masterp) +/* VSTREAM *fp; +/* int mode; +/* PCF_MASTER_ENT *masterp; +/* +/* void pcf_free_master_entry(masterp) +/* PCF_MASTER_ENT *masterp; +/* DESCRIPTION +/* pcf_read_master() reads entries from master.cf into memory. +/* +/* pcf_show_master_entries() writes the entries in the master.cf +/* file to the specified stream. +/* +/* pcf_show_master_fields() writes name/type/field=value records +/* to the specified stream. +/* +/* pcf_edit_master_field() updates the value of a single-column +/* or multi-column attribute. +/* +/* pcf_show_master_params() writes name/type/parameter=value +/* records to the specified stream. +/* +/* pcf_edit_master_param() updates, removes or adds the named +/* parameter in a master.cf entry (the remove request ignores +/* the parameter value). +/* +/* pcf_daemon_options_expecting_value[] is an array of master.cf +/* daemon command-line options that expect an option value. +/* +/* pcf_parse_master_entry() parses a (perhaps multi-line) +/* string that contains a complete master.cf entry, and +/* normalizes daemon command-line options to simplify further +/* handling. +/* +/* pcf_print_master_entry() prints a parsed master.cf entry. +/* +/* pcf_free_master_entry() returns storage to the heap that +/* was allocated by pcf_parse_master_entry(). +/* +/* Arguments +/* .IP fail_on_open +/* Specify FAIL_ON_OPEN if open failure is a fatal error, +/* WARN_ON_OPEN if a warning should be logged instead. +/* .IP fp +/* Output stream. +/* .IP mode +/* Bit-wise OR of flags. Flags other than the following are +/* ignored. +/* .RS +/* .IP PCF_FOLD_LINE +/* Wrap long output lines. +/* .IP PCF_SHOW_EVAL +/* Expand $name in parameter values. +/* .IP PCF_EDIT_EXCL +/* Request that pcf_edit_master_param() removes the parameter. +/* .RE +/* .IP n_filters +/* The number of command-line filters. +/* .IP field_filters +/* A list of zero or more service field patterns (name/type/field). +/* The output is formatted as "name/type/field = value". If +/* no filters are specified, pcf_show_master_fields() outputs +/* the fields of all master.cf entries in the specified order. +/* .IP param_filters +/* A list of zero or more service parameter patterns +/* (name/type/parameter). The output is formatted as +/* "name/type/parameter = value". If no filters are specified, +/* pcf_show_master_params() outputs the parameters of all +/* master.cf entries in sorted order. +/* .IP service_filters +/* A list of zero or more service patterns (name or name/type). +/* If no filters are specified, pcf_show_master_entries() +/* outputs all master.cf entries in the specified order. +/* .IP field +/* Index into parsed master.cf entry. +/* .IP new_value +/* Replacement value for the specified field. It is split in +/* whitespace in case of a multi-field attribute. +/* DIAGNOSTICS +/* Problems are reported to the standard error stream. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <string.h> +#include <stdlib.h> +#include <stdarg.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <vstring.h> +#include <argv.h> +#include <vstream.h> +#include <readlline.h> +#include <stringops.h> +#include <split_at.h> +#include <dict_ht.h> + +/* Global library. */ + +#include <mail_params.h> + +/* Master library. */ + +#include <master_proto.h> + +/* Application-specific. */ + +#include <postconf.h> + +const char pcf_daemon_options_expecting_value[] = "o"; + + /* + * Data structure to capture a command-line service field filter. + */ +typedef struct { + int match_count; /* hit count */ + const char *raw_text; /* full pattern text */ + ARGV *service_pattern; /* parsed service name, type, ... */ + int field_pattern; /* parsed field pattern */ + const char *param_pattern; /* parameter pattern */ +} PCF_MASTER_FLD_REQ; + + /* + * Valid inputs. + */ +static const char *pcf_valid_master_types[] = { + MASTER_XPORT_NAME_UNIX, + MASTER_XPORT_NAME_FIFO, + MASTER_XPORT_NAME_INET, + MASTER_XPORT_NAME_PASS, + MASTER_XPORT_NAME_UXDG, + 0, +}; + +static const char pcf_valid_bool_types[] = "yn-"; + +static VSTRING *pcf_exp_buf; + +#define STR(x) vstring_str(x) + +/* pcf_extract_field - extract text from {}, trim leading/trailing blanks */ + +static void pcf_extract_field(ARGV *argv, int field, const char *parens) +{ + char *arg = argv->argv[field]; + char *err; + + if ((err = extpar(&arg, parens, EXTPAR_FLAG_STRIP)) != 0) { + msg_warn("%s: %s", MASTER_CONF_FILE, err); + myfree(err); + } + argv_replace_one(argv, field, arg); +} + +/* pcf_normalize_nameval - normalize name = value from inside {} */ + +static void pcf_normalize_nameval(ARGV *argv, int field) +{ + char *arg = argv->argv[field]; + char *name; + char *value; + const char *err; + char *normalized; + + if ((err = split_nameval(arg, &name, &value)) != 0) { + msg_warn("%s: %s: \"%s\"", MASTER_CONF_FILE, err, arg); + } else { + normalized = concatenate(name, "=", value, (char *) 0); + argv_replace_one(argv, field, normalized); + myfree(normalized); + } +} + +/* pcf_normalize_daemon_args - bring daemon arguments into canonical form */ + +static void pcf_normalize_daemon_args(ARGV *argv) +{ + int field; + char *arg; + char *cp; + char *junk; + int extract_field; + + /* + * Normalize options to simplify later processing. + */ + for (field = PCF_MASTER_MIN_FIELDS; argv->argv[field] != 0; field++) { + arg = argv->argv[field]; + if (arg[0] != '-' || strcmp(arg, "--") == 0) + break; + for (cp = arg + 1; *cp; cp++) { + if (strchr(pcf_daemon_options_expecting_value, *cp) != 0 + && cp > arg + 1) { + /* Split "-stuffozz" into "-stuff" and "-ozz". */ + junk = concatenate("-", cp, (char *) 0); + argv_insert_one(argv, field + 1, junk); + myfree(junk); + *cp = 0; /* XXX argv_replace_one() */ + break; + } + } + if (strchr(pcf_daemon_options_expecting_value, arg[1]) == 0) + /* Option requires no value. */ + continue; + if (arg[2] != 0) { + /* Split "-oname=value" into "-o" "name=value". */ + argv_insert_one(argv, field + 1, arg + 2); + arg[2] = 0; /* XXX argv_replace_one() */ + field += 1; + extract_field = (argv->argv[field][0] == CHARS_BRACE[0]); + } else if (argv->argv[field + 1] != 0) { + /* Already in "-o" "name=value" form. */ + field += 1; + extract_field = (argv->argv[field][0] == CHARS_BRACE[0]); + } else + extract_field = 0; + /* Extract text inside {}, optionally convert to name=value. */ + if (extract_field) { + pcf_extract_field(argv, field, CHARS_BRACE); + if (argv->argv[field - 1][1] == 'o') + pcf_normalize_nameval(argv, field); + } + } + /* Normalize non-option arguments. */ + for ( /* void */ ; argv->argv[field] != 0; field++) + /* Extract text inside {}. */ + if (argv->argv[field][0] == CHARS_BRACE[0]) + pcf_extract_field(argv, field, CHARS_BRACE); +} + +/* pcf_fix_fatal - fix multiline text before release */ + +static NORETURN PRINTFLIKE(1, 2) pcf_fix_fatal(const char *fmt,...) +{ + VSTRING *buf = vstring_alloc(100); + va_list ap; + + /* + * Replace newline with whitespace. + */ + va_start(ap, fmt); + vstring_vsprintf(buf, fmt, ap); + va_end(ap); + translit(STR(buf), "\n", " "); + msg_fatal("%s", STR(buf)); + /* NOTREACHED */ +} + +/* pcf_check_master_entry - sanity check master.cf entry */ + +static void pcf_check_master_entry(ARGV *argv, const char *raw_text) +{ + const char **cpp; + char *cp; + int len; + int field; + + cp = argv->argv[PCF_MASTER_FLD_TYPE]; + for (cpp = pcf_valid_master_types; /* see below */ ; cpp++) { + if (*cpp == 0) + pcf_fix_fatal("invalid " PCF_MASTER_NAME_TYPE " field \"%s\" in \"%s\"", + cp, raw_text); + if (strcmp(*cpp, cp) == 0) + break; + } + + for (field = PCF_MASTER_FLD_PRIVATE; field <= PCF_MASTER_FLD_CHROOT; field++) { + cp = argv->argv[field]; + if (cp[1] != 0 || strchr(pcf_valid_bool_types, *cp) == 0) + pcf_fix_fatal("invalid %s field \"%s\" in \"%s\"", + pcf_str_field_pattern(field), cp, raw_text); + } + + cp = argv->argv[PCF_MASTER_FLD_WAKEUP]; + len = strlen(cp); + if (len > 0 && cp[len - 1] == '?') + len--; + if (!(cp[0] == '-' && len == 1) && strspn(cp, "0123456789") != len) + pcf_fix_fatal("invalid " PCF_MASTER_NAME_WAKEUP " field \"%s\" in \"%s\"", + cp, raw_text); + + cp = argv->argv[PCF_MASTER_FLD_MAXPROC]; + if (strcmp("-", cp) != 0 && cp[strspn(cp, "0123456789")] != 0) + pcf_fix_fatal("invalid " PCF_MASTER_NAME_MAXPROC " field \"%s\" in \"%s\"", + cp, raw_text); +} + +/* pcf_free_master_entry - destroy parsed entry */ + +void pcf_free_master_entry(PCF_MASTER_ENT *masterp) +{ + /* XX Fixme: allocation/deallocation asymmetry. */ + myfree(masterp->name_space); + argv_free(masterp->argv); + if (masterp->valid_names) + htable_free(masterp->valid_names, myfree); + if (masterp->ro_params) + dict_close(masterp->ro_params); + if (masterp->all_params) + dict_close(masterp->all_params); + myfree((void *) masterp); +} + +/* pcf_parse_master_entry - parse one master line */ + +const char *pcf_parse_master_entry(PCF_MASTER_ENT *masterp, const char *buf) +{ + ARGV *argv; + char *ro_name_space; + char *process_name; + + /* + * We can't use the master daemon's master_ent routines in their current + * form. They convert everything to internal form, and they skip disabled + * services. + * + * The postconf command needs to show default fields as "-", and needs to + * know about all service names so that it can generate service-dependent + * parameter names (transport-dependent etc.). + * + * XXX Do per-field sanity checks. + */ + argv = argv_splitq(buf, PCF_MASTER_BLANKS, CHARS_BRACE); + if (argv->argc < PCF_MASTER_MIN_FIELDS) { + argv_free(argv); /* Coverity 201311 */ + return ("bad field count"); + } + pcf_check_master_entry(argv, buf); + pcf_normalize_daemon_args(argv); + masterp->name_space = + concatenate(argv->argv[0], PCF_NAMESP_SEP_STR, argv->argv[1], (char *) 0); + ro_name_space = + concatenate("ro", PCF_NAMESP_SEP_STR, masterp->name_space, (char *) 0); + masterp->argv = argv; + masterp->valid_names = 0; + masterp->ro_params = dict_ht_open(ro_name_space, O_CREAT | O_RDWR, 0); + process_name = basename(argv->argv[PCF_MASTER_FLD_CMD]); + dict_put(masterp->ro_params, VAR_PROCNAME, process_name); + dict_put(masterp->ro_params, VAR_SERVNAME, + strcmp(process_name, argv->argv[0]) != 0 ? + argv->argv[0] : process_name); + myfree(ro_name_space); + masterp->all_params = 0; + return (0); +} + +/* pcf_read_master - read and digest the master.cf file */ + +void pcf_read_master(int fail_on_open_error) +{ + const char *myname = "pcf_read_master"; + char *path; + VSTRING *buf; + VSTREAM *fp; + const char *err; + int entry_count = 0; + int line_count; + int last_line = 0; + + /* + * Sanity check. + */ + if (pcf_master_table != 0) + msg_panic("%s: master table is already initialized", myname); + + /* + * Get the location of master.cf. + */ + if (var_config_dir == 0) + pcf_set_config_dir(); + path = concatenate(var_config_dir, "/", MASTER_CONF_FILE, (char *) 0); + + /* + * Initialize the in-memory master table. + */ + pcf_master_table = (PCF_MASTER_ENT *) mymalloc(sizeof(*pcf_master_table)); + + /* + * Skip blank lines and comment lines. Degrade gracefully if master.cf is + * not available, and master.cf is not the primary target. + */ + if ((fp = vstream_fopen(path, O_RDONLY, 0)) == 0) { + if (fail_on_open_error) + msg_fatal("open %s: %m", path); + msg_warn("open %s: %m", path); + } else { + buf = vstring_alloc(100); + while (readllines(buf, fp, &last_line, &line_count) != 0) { + pcf_master_table = (PCF_MASTER_ENT *) myrealloc((void *) pcf_master_table, + (entry_count + 2) * sizeof(*pcf_master_table)); + if ((err = pcf_parse_master_entry(pcf_master_table + entry_count, + STR(buf))) != 0) + msg_fatal("file %s: line %d: %s", path, line_count, err); + entry_count += 1; + } + vstream_fclose(fp); + vstring_free(buf); + } + + /* + * Null-terminate the master table and clean up. + */ + pcf_master_table[entry_count].argv = 0; + myfree(path); +} + +/* pcf_print_master_entry - print one master line */ + +void pcf_print_master_entry(VSTREAM *fp, int mode, PCF_MASTER_ENT *masterp) +{ + char **argv = masterp->argv->argv; + const char *arg; + const char *aval; + int arg_len; + int line_len; + int field; + int in_daemon_options; + int need_parens; + static int column_goal[] = { + 0, /* service */ + 11, /* type */ + 17, /* private */ + 25, /* unpriv */ + 33, /* chroot */ + 41, /* wakeup */ + 49, /* maxproc */ + 57, /* command */ + }; + +#define ADD_TEXT(text, len) do { \ + vstream_fputs(text, fp); line_len += len; } \ + while (0) +#define ADD_SPACE ADD_TEXT(" ", 1) + + if (pcf_exp_buf == 0) + pcf_exp_buf = vstring_alloc(100); + + /* + * Show the standard fields at their preferred column position. Use at + * least one-space column separation. + */ + for (line_len = 0, field = 0; field < PCF_MASTER_MIN_FIELDS; field++) { + arg = argv[field]; + if (line_len > 0) { + do { + ADD_SPACE; + } while (line_len < column_goal[field]); + } + ADD_TEXT(arg, strlen(arg)); + } + + /* + * Format the daemon command-line options and non-option arguments. Here, + * we have no data-dependent preference for column positions, but we do + * have argument grouping preferences. + */ + in_daemon_options = 1; + for ( /* void */ ; (arg = argv[field]) != 0; field++) { + arg_len = strlen(arg); + aval = 0; + need_parens = 0; + if (in_daemon_options) { + + /* + * Try to show the generic options (-v -D) on the first line, and + * non-options on a later line. + */ + if (arg[0] != '-' || strcmp(arg, "--") == 0) { + in_daemon_options = 0; +#if 0 + if (mode & PCF_FOLD_LINE) + /* Force line wrap. */ + line_len = PCF_LINE_LIMIT; +#endif + } + + /* + * Special processing for options that require a value. + */ + else if (strchr(pcf_daemon_options_expecting_value, arg[1]) != 0 + && (aval = argv[field + 1]) != 0) { + + /* Force line wrap before option with value. */ + line_len = PCF_LINE_LIMIT; + + /* + * Optionally, expand $name in parameter value. + */ + if (strcmp(arg, "-o") == 0 + && (mode & PCF_SHOW_EVAL) != 0) + aval = pcf_expand_parameter_value(pcf_exp_buf, mode, + aval, masterp); + + /* + * Keep option and value on the same line. + */ + arg_len += strlen(aval) + 3; + if ((need_parens = aval[strcspn(aval, PCF_MASTER_BLANKS)]) != 0) + arg_len += 2; + } + } else { + need_parens = arg[strcspn(arg, PCF_MASTER_BLANKS)]; + } + + /* + * Insert a line break when the next item won't fit. + */ + if (line_len > PCF_INDENT_LEN) { + if ((mode & PCF_FOLD_LINE) == 0 + || line_len + 1 + arg_len < PCF_LINE_LIMIT) { + ADD_SPACE; + } else { + vstream_fputs("\n" PCF_INDENT_TEXT, fp); + line_len = PCF_INDENT_LEN; + } + } + if (in_daemon_options == 0 && need_parens) + ADD_TEXT("{", 1); + ADD_TEXT(arg, strlen(arg)); + if (in_daemon_options == 0 && need_parens) + ADD_TEXT("}", 1); + if (aval) { + ADD_TEXT(" ", 1); + if (need_parens) + ADD_TEXT("{", 1); + ADD_TEXT(aval, strlen(aval)); + if (need_parens) + ADD_TEXT("}", 1); + field += 1; + + /* Force line wrap after option with value. */ + line_len = PCF_LINE_LIMIT; + + } + } + vstream_fputs("\n", fp); + + if (msg_verbose) + vstream_fflush(fp); +} + +/* pcf_show_master_entries - show master.cf entries */ + +void pcf_show_master_entries(VSTREAM *fp, int mode, int argc, char **argv) +{ + PCF_MASTER_ENT *masterp; + PCF_MASTER_FLD_REQ *field_reqs; + PCF_MASTER_FLD_REQ *req; + + /* + * Parse the filter expressions. + */ + if (argc > 0) { + field_reqs = (PCF_MASTER_FLD_REQ *) + mymalloc(sizeof(*field_reqs) * argc); + for (req = field_reqs; req < field_reqs + argc; req++) { + req->match_count = 0; + req->raw_text = *argv++; + req->service_pattern = + pcf_parse_service_pattern(req->raw_text, 1, 2); + if (req->service_pattern == 0) + msg_fatal("-M option requires service_name[/type]"); + } + } + + /* + * Iterate over the master table. + */ + for (masterp = pcf_master_table; masterp->argv != 0; masterp++) { + if (argc > 0) { + for (req = field_reqs; req < field_reqs + argc; req++) { + if (PCF_MATCH_SERVICE_PATTERN(req->service_pattern, + masterp->argv->argv[0], + masterp->argv->argv[1])) { + req->match_count++; + pcf_print_master_entry(fp, mode, masterp); + } + } + } else { + pcf_print_master_entry(fp, mode, masterp); + } + } + + /* + * Cleanup. + */ + if (argc > 0) { + for (req = field_reqs; req < field_reqs + argc; req++) { + if (req->match_count == 0) + msg_warn("unmatched request: \"%s\"", req->raw_text); + argv_free(req->service_pattern); + } + myfree((void *) field_reqs); + } +} + +/* pcf_print_master_field - scaffolding */ + +static void pcf_print_master_field(VSTREAM *fp, int mode, + PCF_MASTER_ENT *masterp, + int field) +{ + char **argv = masterp->argv->argv; + const char *arg; + const char *aval; + int arg_len; + int line_len; + int in_daemon_options; + int need_parens; + + if (pcf_exp_buf == 0) + pcf_exp_buf = vstring_alloc(100); + + /* + * Show the field value, or the first value in the case of a multi-column + * field. + */ +#define ADD_CHAR(ch) ADD_TEXT((ch), 1) + + line_len = 0; + if ((mode & PCF_HIDE_NAME) == 0) { + ADD_TEXT(argv[0], strlen(argv[0])); + ADD_CHAR(PCF_NAMESP_SEP_STR); + ADD_TEXT(argv[1], strlen(argv[1])); + ADD_CHAR(PCF_NAMESP_SEP_STR); + ADD_TEXT(pcf_str_field_pattern(field), strlen(pcf_str_field_pattern(field))); + } + if ((mode & (PCF_HIDE_NAME | PCF_HIDE_VALUE)) == 0) { + ADD_TEXT(" = ", 3); + } + if ((mode & PCF_HIDE_VALUE) == 0) { + if (line_len > 0 && line_len + strlen(argv[field]) > PCF_LINE_LIMIT) { + vstream_fputs("\n" PCF_INDENT_TEXT, fp); + line_len = PCF_INDENT_LEN; + } + ADD_TEXT(argv[field], strlen(argv[field])); + } + + /* + * Format the daemon command-line options and non-option arguments. Here, + * we have no data-dependent preference for column positions, but we do + * have argument grouping preferences. + */ + if (field == PCF_MASTER_FLD_CMD && (mode & PCF_HIDE_VALUE) == 0) { + in_daemon_options = 1; + for (field += 1; (arg = argv[field]) != 0; field++) { + arg_len = strlen(arg); + aval = 0; + need_parens = 0; + if (in_daemon_options) { + + /* + * We make no special case for generic options (-v -D) + * options. + */ + if (arg[0] != '-' || strcmp(arg, "--") == 0) { + in_daemon_options = 0; + } else if (strchr(pcf_daemon_options_expecting_value, arg[1]) != 0 + && (aval = argv[field + 1]) != 0) { + + /* Force line break before option with value. */ + line_len = PCF_LINE_LIMIT; + + /* + * Optionally, expand $name in parameter value. + */ + if (strcmp(arg, "-o") == 0 + && (mode & PCF_SHOW_EVAL) != 0) + aval = pcf_expand_parameter_value(pcf_exp_buf, mode, + aval, masterp); + + /* + * Keep option and value on the same line. + */ + arg_len += strlen(aval) + 1; + if ((need_parens = aval[strcspn(aval, PCF_MASTER_BLANKS)]) != 0) + arg_len += 2; + } + } else { + need_parens = arg[strcspn(arg, PCF_MASTER_BLANKS)]; + } + + /* + * Insert a line break when the next item won't fit. + */ + if (line_len > PCF_INDENT_LEN) { + if ((mode & PCF_FOLD_LINE) == 0 + || line_len + 1 + arg_len < PCF_LINE_LIMIT) { + ADD_SPACE; + } else { + vstream_fputs("\n" PCF_INDENT_TEXT, fp); + line_len = PCF_INDENT_LEN; + } + } + if (in_daemon_options == 0 && need_parens) + ADD_TEXT("{", 1); + ADD_TEXT(arg, strlen(arg)); + if (in_daemon_options == 0 && need_parens) + ADD_TEXT("}", 1); + if (aval) { + ADD_SPACE; + if (need_parens) + ADD_TEXT("{", 1); + ADD_TEXT(aval, strlen(aval)); + if (need_parens) + ADD_TEXT("}", 1); + field += 1; + + /* Force line break after option with value. */ + line_len = PCF_LINE_LIMIT; + } + } + } + vstream_fputs("\n", fp); + + if (msg_verbose) + vstream_fflush(fp); +} + +/* pcf_show_master_fields - show master.cf fields */ + +void pcf_show_master_fields(VSTREAM *fp, int mode, int argc, char **argv) +{ + const char *myname = "pcf_show_master_fields"; + PCF_MASTER_ENT *masterp; + PCF_MASTER_FLD_REQ *field_reqs; + PCF_MASTER_FLD_REQ *req; + int field; + + /* + * Parse the filter expressions. + */ + if (argc > 0) { + field_reqs = (PCF_MASTER_FLD_REQ *) + mymalloc(sizeof(*field_reqs) * argc); + for (req = field_reqs; req < field_reqs + argc; req++) { + req->match_count = 0; + req->raw_text = *argv++; + req->service_pattern = + pcf_parse_service_pattern(req->raw_text, 1, 3); + if (req->service_pattern == 0) + msg_fatal("-F option requires service_name[/type[/field]]"); + field = req->field_pattern = + pcf_parse_field_pattern(req->service_pattern->argv[2]); + if (pcf_is_magic_field_pattern(field) == 0 + && (field < 0 || field > PCF_MASTER_FLD_CMD)) + msg_panic("%s: bad attribute field index: %d", + myname, field); + } + } + + /* + * Iterate over the master table. + */ + for (masterp = pcf_master_table; masterp->argv != 0; masterp++) { + if (argc > 0) { + for (req = field_reqs; req < field_reqs + argc; req++) { + if (PCF_MATCH_SERVICE_PATTERN(req->service_pattern, + masterp->argv->argv[0], + masterp->argv->argv[1])) { + req->match_count++; + field = req->field_pattern; + if (pcf_is_magic_field_pattern(field)) { + for (field = 0; field <= PCF_MASTER_FLD_CMD; field++) + pcf_print_master_field(fp, mode, masterp, field); + } else { + pcf_print_master_field(fp, mode, masterp, field); + } + } + } + } else { + for (field = 0; field <= PCF_MASTER_FLD_CMD; field++) + pcf_print_master_field(fp, mode, masterp, field); + } + } + + /* + * Cleanup. + */ + if (argc > 0) { + for (req = field_reqs; req < field_reqs + argc; req++) { + if (req->match_count == 0) + msg_warn("unmatched request: \"%s\"", req->raw_text); + argv_free(req->service_pattern); + } + myfree((void *) field_reqs); + } +} + +/* pcf_edit_master_field - replace master.cf field value. */ + +void pcf_edit_master_field(PCF_MASTER_ENT *masterp, int field, + const char *new_value) +{ + + /* + * Replace multi-column attribute. + */ + if (field == PCF_MASTER_FLD_CMD) { + argv_truncate(masterp->argv, PCF_MASTER_FLD_CMD); + argv_splitq_append(masterp->argv, new_value, PCF_MASTER_BLANKS, CHARS_BRACE); + pcf_normalize_daemon_args(masterp->argv); + } + + /* + * Replace single-column attribute. + */ + else { + argv_replace_one(masterp->argv, field, new_value); + } + + /* + * Do per-field sanity checks. + */ + pcf_check_master_entry(masterp->argv, new_value); +} + +/* pcf_print_master_param - scaffolding */ + +static void pcf_print_master_param(VSTREAM *fp, int mode, + PCF_MASTER_ENT *masterp, + const char *param_name, + const char *param_value) +{ + if (pcf_exp_buf == 0) + pcf_exp_buf = vstring_alloc(100); + + if (mode & PCF_HIDE_VALUE) { + pcf_print_line(fp, mode, "%s%c%s\n", + masterp->name_space, PCF_NAMESP_SEP_CH, + param_name); + } else { + if ((mode & PCF_SHOW_EVAL) != 0) + param_value = pcf_expand_parameter_value(pcf_exp_buf, mode, + param_value, masterp); + if ((mode & PCF_HIDE_NAME) == 0) { + pcf_print_line(fp, mode, "%s%c%s = %s\n", + masterp->name_space, PCF_NAMESP_SEP_CH, + param_name, param_value); + } else { + pcf_print_line(fp, mode, "%s\n", param_value); + } + } + if (msg_verbose) + vstream_fflush(fp); +} + +/* pcf_sort_argv_cb - sort argv call-back */ + +static int pcf_sort_argv_cb(const void *a, const void *b) +{ + return (strcmp(*(char **) a, *(char **) b)); +} + +/* pcf_show_master_any_param - show any parameter in master.cf service entry */ + +static void pcf_show_master_any_param(VSTREAM *fp, int mode, + PCF_MASTER_ENT *masterp) +{ + const char *myname = "pcf_show_master_any_param"; + ARGV *argv = argv_alloc(10); + DICT *dict = masterp->all_params; + const char *param_name; + const char *param_value; + int param_count = 0; + int how; + char **cpp; + + /* + * Print parameters in sorted order. The number of parameters per + * master.cf entry is small, so we optimize for code simplicity and don't + * worry about the cost of double lookup. + */ + + /* Look up the parameter names and ignore the values. */ + + for (how = DICT_SEQ_FUN_FIRST; + dict->sequence(dict, how, ¶m_name, ¶m_value) == 0; + how = DICT_SEQ_FUN_NEXT) { + argv_add(argv, param_name, ARGV_END); + param_count++; + } + + /* Print the parameters in sorted order. */ + + qsort(argv->argv, param_count, sizeof(argv->argv[0]), pcf_sort_argv_cb); + for (cpp = argv->argv; (param_name = *cpp) != 0; cpp++) { + if ((param_value = dict_get(dict, param_name)) == 0) + msg_panic("%s: parameter name not found: %s", myname, param_name); + pcf_print_master_param(fp, mode, masterp, param_name, param_value); + } + + /* + * Clean up. + */ + argv_free(argv); +} + +/* pcf_show_master_params - show master.cf params */ + +void pcf_show_master_params(VSTREAM *fp, int mode, int argc, char **argv) +{ + PCF_MASTER_ENT *masterp; + PCF_MASTER_FLD_REQ *field_reqs; + PCF_MASTER_FLD_REQ *req; + DICT *dict; + const char *param_value; + + /* + * Parse the filter expressions. + */ + if (argc > 0) { + field_reqs = (PCF_MASTER_FLD_REQ *) + mymalloc(sizeof(*field_reqs) * argc); + for (req = field_reqs; req < field_reqs + argc; req++) { + req->match_count = 0; + req->raw_text = *argv++; + req->service_pattern = + pcf_parse_service_pattern(req->raw_text, 1, 3); + if (req->service_pattern == 0) + msg_fatal("-P option requires service_name[/type[/parameter]]"); + req->param_pattern = req->service_pattern->argv[2]; + } + } + + /* + * Iterate over the master table. + */ + for (masterp = pcf_master_table; masterp->argv != 0; masterp++) { + if ((dict = masterp->all_params) != 0) { + if (argc > 0) { + for (req = field_reqs; req < field_reqs + argc; req++) { + if (PCF_MATCH_SERVICE_PATTERN(req->service_pattern, + masterp->argv->argv[0], + masterp->argv->argv[1])) { + if (PCF_IS_MAGIC_PARAM_PATTERN(req->param_pattern)) { + pcf_show_master_any_param(fp, mode, masterp); + req->match_count += 1; + } else if ((param_value = dict_get(dict, + req->param_pattern)) != 0) { + pcf_print_master_param(fp, mode, masterp, + req->param_pattern, + param_value); + req->match_count += 1; + } + } + } + } else { + pcf_show_master_any_param(fp, mode, masterp); + } + } + } + + /* + * Cleanup. + */ + if (argc > 0) { + for (req = field_reqs; req < field_reqs + argc; req++) { + if (req->match_count == 0) + msg_warn("unmatched request: \"%s\"", req->raw_text); + argv_free(req->service_pattern); + } + myfree((void *) field_reqs); + } +} + +/* pcf_edit_master_param - update, add or remove -o parameter=value */ + +void pcf_edit_master_param(PCF_MASTER_ENT *masterp, int mode, + const char *param_name, + const char *param_value) +{ + const char *myname = "pcf_edit_master_param"; + ARGV *argv = masterp->argv; + const char *arg; + const char *aval; + int param_match = 0; + int name_len = strlen(param_name); + int field; + + for (field = PCF_MASTER_MIN_FIELDS; argv->argv[field] != 0; field++) { + arg = argv->argv[field]; + + /* + * Stop at the first non-option argument or end-of-list. + */ + if (arg[0] != '-' || strcmp(arg, "--") == 0) { + break; + } + + /* + * Zoom in on command-line options with a value. + */ + else if (strchr(pcf_daemon_options_expecting_value, arg[1]) != 0 + && (aval = argv->argv[field + 1]) != 0) { + + /* + * Zoom in on "-o parameter=value". + */ + if (strcmp(arg, "-o") == 0) { + if (strncmp(aval, param_name, name_len) == 0 + && aval[name_len] == '=') { + param_match = 1; + switch (mode & (PCF_EDIT_CONF | PCF_EDIT_EXCL)) { + + /* + * Update parameter=value. + */ + case PCF_EDIT_CONF: + aval = concatenate(param_name, "=", + param_value, (char *) 0); + argv_replace_one(argv, field + 1, aval); + myfree((void *) aval); + if (masterp->all_params) + dict_put(masterp->all_params, param_name, param_value); + /* XXX Update parameter "used/defined" status. */ + break; + + /* + * Delete parameter=value. + */ + case PCF_EDIT_EXCL: + argv_delete(argv, field, 2); + if (masterp->all_params) + dict_del(masterp->all_params, param_name); + /* XXX Update parameter "used/defined" status. */ + field -= 2; + break; + default: + msg_panic("%s: unexpected mode: %d", myname, mode); + } + } + } + + /* + * Skip over the command-line option value. + */ + field += 1; + } + } + + /* + * Add unmatched parameter. + */ + if ((mode & PCF_EDIT_CONF) && param_match == 0) { + /* XXX Generalize: argv_insert(argv, where, list...) */ + argv_insert_one(argv, field, "-o"); + aval = concatenate(param_name, "=", + param_value, (char *) 0); + argv_insert_one(argv, field + 1, aval); + if (masterp->all_params) + dict_put(masterp->all_params, param_name, param_value); + /* XXX May affect parameter "used/defined" status. */ + myfree((void *) aval); + param_match = 1; + } +} |