summaryrefslogtreecommitdiffstats
path: root/src/postconf/postconf_master.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/postconf/postconf_master.c')
-rw-r--r--src/postconf/postconf_master.c1120
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, &param_name, &param_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;
+ }
+}