xref: /netbsd-src/external/ibm-public/postfix/dist/src/postconf/postconf_master.c (revision 3587d6f89c746bbb4f886219ddacd41ace480ecf)
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 
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 static 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 
207 static 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 
221 static 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 
240 static 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 
297 static 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 
315 static 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 
354 void    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 
370 const 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 
413 void    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 
473 void    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 
608 void    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 
663 static 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 
786 void    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 
856 void    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 
884 static 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 
914 static 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 
921 static 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 
965 void    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 
1033 void    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