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