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