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