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, ¶m_name, ¶m_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