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