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