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