Initial import from FreeBSD RELENG_4:
[dragonfly.git] / contrib / sendmail / src / readcf.c
1 /*
2  * Copyright (c) 1998-2002 Sendmail, Inc. and its suppliers.
3  *      All rights reserved.
4  * Copyright (c) 1983, 1995-1997 Eric P. Allman.  All rights reserved.
5  * Copyright (c) 1988, 1993
6  *      The Regents of the University of California.  All rights reserved.
7  *
8  * By using this file, you agree to the terms and conditions set
9  * forth in the LICENSE file which can be found at the top level of
10  * the sendmail distribution.
11  *
12  */
13
14 #include <sendmail.h>
15
16 SM_RCSID("@(#)$Id: readcf.c,v 8.607.2.8 2003/03/12 22:42:52 gshapiro Exp $")
17
18 #if NETINET || NETINET6
19 # include <arpa/inet.h>
20 #endif /* NETINET || NETINET6 */
21
22 #define SECONDS
23 #define MINUTES * 60
24 #define HOUR    * 3600
25 #define HOURS   HOUR
26
27 static void     fileclass __P((int, char *, char *, bool, bool, bool));
28 static char     **makeargv __P((char *));
29 static void     settimeout __P((char *, char *, bool));
30 static void     toomany __P((int, int));
31 static char     *extrquotstr __P((char *, char **, char *, bool *));
32
33 /*
34 **  READCF -- read configuration file.
35 **
36 **      This routine reads the configuration file and builds the internal
37 **      form.
38 **
39 **      The file is formatted as a sequence of lines, each taken
40 **      atomically.  The first character of each line describes how
41 **      the line is to be interpreted.  The lines are:
42 **              Dxval           Define macro x to have value val.
43 **              Cxword          Put word into class x.
44 **              Fxfile [fmt]    Read file for lines to put into
45 **                              class x.  Use scanf string 'fmt'
46 **                              or "%s" if not present.  Fmt should
47 **                              only produce one string-valued result.
48 **              Hname: value    Define header with field-name 'name'
49 **                              and value as specified; this will be
50 **                              macro expanded immediately before
51 **                              use.
52 **              Sn              Use rewriting set n.
53 **              Rlhs rhs        Rewrite addresses that match lhs to
54 **                              be rhs.
55 **              Mn arg=val...   Define mailer.  n is the internal name.
56 **                              Args specify mailer parameters.
57 **              Oxvalue         Set option x to value.
58 **              O option value  Set option (long name) to value.
59 **              Pname=value     Set precedence name to value.
60 **              Qn arg=val...   Define queue groups.  n is the internal name.
61 **                              Args specify queue parameters.
62 **              Vversioncode[/vendorcode]
63 **                              Version level/vendor name of
64 **                              configuration syntax.
65 **              Kmapname mapclass arguments....
66 **                              Define keyed lookup of a given class.
67 **                              Arguments are class dependent.
68 **              Eenvar=value    Set the environment value to the given value.
69 **
70 **      Parameters:
71 **              cfname -- configuration file name.
72 **              safe -- true if this is the system config file;
73 **                      false otherwise.
74 **              e -- the main envelope.
75 **
76 **      Returns:
77 **              none.
78 **
79 **      Side Effects:
80 **              Builds several internal tables.
81 */
82
83 void
84 readcf(cfname, safe, e)
85         char *cfname;
86         bool safe;
87         register ENVELOPE *e;
88 {
89         SM_FILE_T *cf;
90         int ruleset = -1;
91         char *q;
92         struct rewrite *rwp = NULL;
93         char *bp;
94         auto char *ep;
95         int nfuzzy;
96         char *file;
97         bool optional;
98         bool ok;
99         bool ismap;
100         int mid;
101         register char *p;
102         long sff = SFF_OPENASROOT;
103         struct stat statb;
104         char buf[MAXLINE];
105         char exbuf[MAXLINE];
106         char pvpbuf[MAXLINE + MAXATOM];
107         static char *null_list[1] = { NULL };
108         extern unsigned char TokTypeNoC[];
109
110         FileName = cfname;
111         LineNumber = 0;
112
113         if (DontLockReadFiles)
114                 sff |= SFF_NOLOCK;
115         cf = safefopen(cfname, O_RDONLY, 0444, sff);
116         if (cf == NULL)
117         {
118                 syserr("cannot open");
119                 finis(false, true, EX_OSFILE);
120         }
121
122         if (fstat(sm_io_getinfo(cf, SM_IO_WHAT_FD, NULL), &statb) < 0)
123         {
124                 syserr("cannot fstat");
125                 finis(false, true, EX_OSFILE);
126         }
127
128         if (!S_ISREG(statb.st_mode))
129         {
130                 syserr("not a plain file");
131                 finis(false, true, EX_OSFILE);
132         }
133
134         if (OpMode != MD_TEST && bitset(S_IWGRP|S_IWOTH, statb.st_mode))
135         {
136                 if (OpMode == MD_DAEMON || OpMode == MD_INITALIAS)
137                         (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
138                                              "%s: WARNING: dangerous write permissions\n",
139                                              FileName);
140                 if (LogLevel > 0)
141                         sm_syslog(LOG_CRIT, NOQID,
142                                   "%s: WARNING: dangerous write permissions",
143                                   FileName);
144         }
145
146 #if XLA
147         xla_zero();
148 #endif /* XLA */
149
150         while ((bp = fgetfolded(buf, sizeof buf, cf)) != NULL)
151         {
152                 if (bp[0] == '#')
153                 {
154                         if (bp != buf)
155                                 sm_free(bp); /* XXX */
156                         continue;
157                 }
158
159                 /* do macro expansion mappings */
160                 translate_dollars(bp);
161
162                 /* interpret this line */
163                 errno = 0;
164                 switch (bp[0])
165                 {
166                   case '\0':
167                   case '#':             /* comment */
168                         break;
169
170                   case 'R':             /* rewriting rule */
171                         if (ruleset < 0)
172                         {
173                                 syserr("missing valid ruleset for \"%s\"", bp);
174                                 break;
175                         }
176                         for (p = &bp[1]; *p != '\0' && *p != '\t'; p++)
177                                 continue;
178
179                         if (*p == '\0')
180                         {
181                                 syserr("invalid rewrite line \"%s\" (tab expected)", bp);
182                                 break;
183                         }
184
185                         /* allocate space for the rule header */
186                         if (rwp == NULL)
187                         {
188                                 RewriteRules[ruleset] = rwp =
189                                         (struct rewrite *) xalloc(sizeof *rwp);
190                         }
191                         else
192                         {
193                                 rwp->r_next = (struct rewrite *) xalloc(sizeof *rwp);
194                                 rwp = rwp->r_next;
195                         }
196                         rwp->r_next = NULL;
197
198                         /* expand and save the LHS */
199                         *p = '\0';
200                         expand(&bp[1], exbuf, sizeof exbuf, e);
201                         rwp->r_lhs = prescan(exbuf, '\t', pvpbuf,
202                                              sizeof pvpbuf, NULL,
203                                              ConfigLevel >= 9 ? TokTypeNoC : NULL);
204                         nfuzzy = 0;
205                         if (rwp->r_lhs != NULL)
206                         {
207                                 register char **ap;
208
209                                 rwp->r_lhs = copyplist(rwp->r_lhs, true, NULL);
210
211                                 /* count the number of fuzzy matches in LHS */
212                                 for (ap = rwp->r_lhs; *ap != NULL; ap++)
213                                 {
214                                         char *botch;
215
216                                         botch = NULL;
217                                         switch (**ap & 0377)
218                                         {
219                                           case MATCHZANY:
220                                           case MATCHANY:
221                                           case MATCHONE:
222                                           case MATCHCLASS:
223                                           case MATCHNCLASS:
224                                                 nfuzzy++;
225                                                 break;
226
227                                           case MATCHREPL:
228                                                 botch = "$0-$9";
229                                                 break;
230
231                                           case CANONUSER:
232                                                 botch = "$:";
233                                                 break;
234
235                                           case CALLSUBR:
236                                                 botch = "$>";
237                                                 break;
238
239                                           case CONDIF:
240                                                 botch = "$?";
241                                                 break;
242
243                                           case CONDFI:
244                                                 botch = "$.";
245                                                 break;
246
247                                           case HOSTBEGIN:
248                                                 botch = "$[";
249                                                 break;
250
251                                           case HOSTEND:
252                                                 botch = "$]";
253                                                 break;
254
255                                           case LOOKUPBEGIN:
256                                                 botch = "$(";
257                                                 break;
258
259                                           case LOOKUPEND:
260                                                 botch = "$)";
261                                                 break;
262                                         }
263                                         if (botch != NULL)
264                                                 syserr("Inappropriate use of %s on LHS",
265                                                         botch);
266                                 }
267                                 rwp->r_line = LineNumber;
268                         }
269                         else
270                         {
271                                 syserr("R line: null LHS");
272                                 rwp->r_lhs = null_list;
273                         }
274                         if (nfuzzy > MAXMATCH)
275                         {
276                                 syserr("R line: too many wildcards");
277                                 rwp->r_lhs = null_list;
278                         }
279
280                         /* expand and save the RHS */
281                         while (*++p == '\t')
282                                 continue;
283                         q = p;
284                         while (*p != '\0' && *p != '\t')
285                                 p++;
286                         *p = '\0';
287                         expand(q, exbuf, sizeof exbuf, e);
288                         rwp->r_rhs = prescan(exbuf, '\t', pvpbuf,
289                                              sizeof pvpbuf, NULL,
290                                              ConfigLevel >= 9 ? TokTypeNoC : NULL);
291                         if (rwp->r_rhs != NULL)
292                         {
293                                 register char **ap;
294
295                                 rwp->r_rhs = copyplist(rwp->r_rhs, true, NULL);
296
297                                 /* check no out-of-bounds replacements */
298                                 nfuzzy += '0';
299                                 for (ap = rwp->r_rhs; *ap != NULL; ap++)
300                                 {
301                                         char *botch;
302
303                                         botch = NULL;
304                                         switch (**ap & 0377)
305                                         {
306                                           case MATCHREPL:
307                                                 if ((*ap)[1] <= '0' || (*ap)[1] > nfuzzy)
308                                                 {
309                                                         syserr("replacement $%c out of bounds",
310                                                                 (*ap)[1]);
311                                                 }
312                                                 break;
313
314                                           case MATCHZANY:
315                                                 botch = "$*";
316                                                 break;
317
318                                           case MATCHANY:
319                                                 botch = "$+";
320                                                 break;
321
322                                           case MATCHONE:
323                                                 botch = "$-";
324                                                 break;
325
326                                           case MATCHCLASS:
327                                                 botch = "$=";
328                                                 break;
329
330                                           case MATCHNCLASS:
331                                                 botch = "$~";
332                                                 break;
333
334 #if 0
335 /*
336 **  This doesn't work yet as there are maps defined *after* the cf
337 **  is read such as host, user, and alias.  So for now, it's removed.
338 **  When it comes back, the RELEASE_NOTES entry will be:
339 **      Emit warnings for unknown maps when reading the .cf file.  Based on
340 **              patch from Robert Harker of Harker Systems.
341 */
342
343                                           case LOOKUPBEGIN:
344                                                 /*
345                                                 **  Got a database lookup,
346                                                 **  check if map is defined.
347                                                 */
348
349                                                 ep = *(ap + 1);
350                                                 if ((*ep & 0377) != MACRODEXPAND &&
351                                                     stab(ep, ST_MAP,
352                                                          ST_FIND) == NULL)
353                                                 {
354                                                         (void) sm_io_fprintf(smioout,
355                                                                              SM_TIME_DEFAULT,
356                                                                              "Warning: %s: line %d: map %s not found\n",
357                                                                              FileName,
358                                                                              LineNumber,
359                                                                              ep);
360                                                 }
361                                                 break;
362 #endif /* 0 */
363                                         }
364                                         if (botch != NULL)
365                                                 syserr("Inappropriate use of %s on RHS",
366                                                         botch);
367                                 }
368                         }
369                         else
370                         {
371                                 syserr("R line: null RHS");
372                                 rwp->r_rhs = null_list;
373                         }
374                         break;
375
376                   case 'S':             /* select rewriting set */
377                         expand(&bp[1], exbuf, sizeof exbuf, e);
378                         ruleset = strtorwset(exbuf, NULL, ST_ENTER);
379                         if (ruleset < 0)
380                                 break;
381
382                         rwp = RewriteRules[ruleset];
383                         if (rwp != NULL)
384                         {
385                                 if (OpMode == MD_TEST)
386                                         (void) sm_io_fprintf(smioout,
387                                                              SM_TIME_DEFAULT,
388                                                              "WARNING: Ruleset %s has multiple definitions\n",
389                                                             &bp[1]);
390                                 if (tTd(37, 1))
391                                         sm_dprintf("WARNING: Ruleset %s has multiple definitions\n",
392                                                    &bp[1]);
393                                 while (rwp->r_next != NULL)
394                                         rwp = rwp->r_next;
395                         }
396                         break;
397
398                   case 'D':             /* macro definition */
399                         mid = macid_parse(&bp[1], &ep);
400                         if (mid == 0)
401                                 break;
402                         p = munchstring(ep, NULL, '\0');
403                         macdefine(&e->e_macro, A_TEMP, mid, p);
404                         break;
405
406                   case 'H':             /* required header line */
407                         (void) chompheader(&bp[1], CHHDR_DEF, NULL, e);
408                         break;
409
410                   case 'C':             /* word class */
411                   case 'T':             /* trusted user (set class `t') */
412                         if (bp[0] == 'C')
413                         {
414                                 mid = macid_parse(&bp[1], &ep);
415                                 if (mid == 0)
416                                         break;
417                                 expand(ep, exbuf, sizeof exbuf, e);
418                                 p = exbuf;
419                         }
420                         else
421                         {
422                                 mid = 't';
423                                 p = &bp[1];
424                         }
425                         while (*p != '\0')
426                         {
427                                 register char *wd;
428                                 char delim;
429
430                                 while (*p != '\0' && isascii(*p) && isspace(*p))
431                                         p++;
432                                 wd = p;
433                                 while (*p != '\0' && !(isascii(*p) && isspace(*p)))
434                                         p++;
435                                 delim = *p;
436                                 *p = '\0';
437                                 if (wd[0] != '\0')
438                                         setclass(mid, wd);
439                                 *p = delim;
440                         }
441                         break;
442
443                   case 'F':             /* word class from file */
444                         mid = macid_parse(&bp[1], &ep);
445                         if (mid == 0)
446                                 break;
447                         for (p = ep; isascii(*p) && isspace(*p); )
448                                 p++;
449                         if (p[0] == '-' && p[1] == 'o')
450                         {
451                                 optional = true;
452                                 while (*p != '\0' &&
453                                        !(isascii(*p) && isspace(*p)))
454                                         p++;
455                                 while (isascii(*p) && isspace(*p))
456                                         p++;
457                                 file = p;
458                         }
459                         else
460                                 optional = false;
461
462                         /* check if [key]@map:spec */
463                         ismap = false;
464                         if (!SM_IS_DIR_DELIM(*p) &&
465                             *p != '|' &&
466                             (q = strchr(p, '@')) != NULL)
467                         {
468                                 q++;
469
470                                 /* look for @LDAP or @map: in string */
471                                 if (strcmp(q, "LDAP") == 0 ||
472                                     (*q != ':' &&
473                                      strchr(q, ':') != NULL))
474                                         ismap = true;
475                         }
476
477                         if (ismap)
478                         {
479                                 /* use entire spec */
480                                 file = p;
481                         }
482                         else
483                         {
484                                 file = extrquotstr(p, &q, " ", &ok);
485                                 if (!ok)
486                                 {
487                                         syserr("illegal filename '%s'", p);
488                                         break;
489                                 }
490                         }
491
492                         if (*file == '|' || ismap)
493                                 p = "%s";
494                         else
495                         {
496                                 p = q;
497                                 if (*p == '\0')
498                                         p = "%s";
499                                 else
500                                 {
501                                         *p = '\0';
502                                         while (isascii(*++p) && isspace(*p))
503                                                 continue;
504                                 }
505                         }
506                         fileclass(mid, file, p, ismap, safe, optional);
507                         break;
508
509 #if XLA
510                   case 'L':             /* extended load average description */
511                         xla_init(&bp[1]);
512                         break;
513 #endif /* XLA */
514
515 #if defined(SUN_EXTENSIONS) && defined(SUN_LOOKUP_MACRO)
516                   case 'L':             /* lookup macro */
517                   case 'G':             /* lookup class */
518                         /* reserved for Sun -- NIS+ database lookup */
519                         if (VendorCode != VENDOR_SUN)
520                                 goto badline;
521                         sun_lg_config_line(bp, e);
522                         break;
523 #endif /* defined(SUN_EXTENSIONS) && defined(SUN_LOOKUP_MACRO) */
524
525                   case 'M':             /* define mailer */
526                         makemailer(&bp[1]);
527                         break;
528
529                   case 'O':             /* set option */
530                         setoption(bp[1], &bp[2], safe, false, e);
531                         break;
532
533                   case 'P':             /* set precedence */
534                         if (NumPriorities >= MAXPRIORITIES)
535                         {
536                                 toomany('P', MAXPRIORITIES);
537                                 break;
538                         }
539                         for (p = &bp[1]; *p != '\0' && *p != '='; p++)
540                                 continue;
541                         if (*p == '\0')
542                                 goto badline;
543                         *p = '\0';
544                         Priorities[NumPriorities].pri_name = newstr(&bp[1]);
545                         Priorities[NumPriorities].pri_val = atoi(++p);
546                         NumPriorities++;
547                         break;
548
549                   case 'Q':             /* define queue */
550                         makequeue(&bp[1], true);
551                         break;
552
553                   case 'V':             /* configuration syntax version */
554                         for (p = &bp[1]; isascii(*p) && isspace(*p); p++)
555                                 continue;
556                         if (!isascii(*p) || !isdigit(*p))
557                         {
558                                 syserr("invalid argument to V line: \"%.20s\"",
559                                         &bp[1]);
560                                 break;
561                         }
562                         ConfigLevel = strtol(p, &ep, 10);
563
564                         /*
565                         **  Do heuristic tweaking for back compatibility.
566                         */
567
568                         if (ConfigLevel >= 5)
569                         {
570                                 /* level 5 configs have short name in $w */
571                                 p = macvalue('w', e);
572                                 if (p != NULL && (p = strchr(p, '.')) != NULL)
573                                 {
574                                         *p = '\0';
575                                         macdefine(&e->e_macro, A_TEMP, 'w',
576                                                   macvalue('w', e));
577                                 }
578                         }
579                         if (ConfigLevel >= 6)
580                         {
581                                 ColonOkInAddr = false;
582                         }
583
584                         /*
585                         **  Look for vendor code.
586                         */
587
588                         if (*ep++ == '/')
589                         {
590                                 /* extract vendor code */
591                                 for (p = ep; isascii(*p) && isalpha(*p); )
592                                         p++;
593                                 *p = '\0';
594
595                                 if (!setvendor(ep))
596                                         syserr("invalid V line vendor code: \"%s\"",
597                                                 ep);
598                         }
599                         break;
600
601                   case 'K':
602                         expand(&bp[1], exbuf, sizeof exbuf, e);
603                         (void) makemapentry(exbuf);
604                         break;
605
606                   case 'E':
607                         p = strchr(bp, '=');
608                         if (p != NULL)
609                                 *p++ = '\0';
610                         setuserenv(&bp[1], p);
611                         break;
612
613                   case 'X':             /* mail filter */
614 #if MILTER
615                         milter_setup(&bp[1]);
616 #else /* MILTER */
617                         (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
618                                              "Warning: Filter usage ('X') requires Milter support (-DMILTER)\n");
619 #endif /* MILTER */
620                         break;
621
622                   default:
623                   badline:
624                         syserr("unknown configuration line \"%s\"", bp);
625                 }
626                 if (bp != buf)
627                         sm_free(bp); /* XXX */
628         }
629         if (sm_io_error(cf))
630         {
631                 syserr("I/O read error");
632                 finis(false, true, EX_OSFILE);
633         }
634         (void) sm_io_close(cf, SM_TIME_DEFAULT);
635         FileName = NULL;
636
637         /* initialize host maps from local service tables */
638         inithostmaps();
639
640         /* initialize daemon (if not defined yet) */
641         initdaemon();
642
643         /* determine if we need to do special name-server frotz */
644         {
645                 int nmaps;
646                 char *maptype[MAXMAPSTACK];
647                 short mapreturn[MAXMAPACTIONS];
648
649                 nmaps = switch_map_find("hosts", maptype, mapreturn);
650                 UseNameServer = false;
651                 if (nmaps > 0 && nmaps <= MAXMAPSTACK)
652                 {
653                         register int mapno;
654
655                         for (mapno = 0; mapno < nmaps && !UseNameServer;
656                              mapno++)
657                         {
658                                 if (strcmp(maptype[mapno], "dns") == 0)
659                                         UseNameServer = true;
660                         }
661                 }
662         }
663 }
664 /*
665 **  TRANSLATE_DOLLARS -- convert $x into internal form
666 **
667 **      Actually does all appropriate pre-processing of a config line
668 **      to turn it into internal form.
669 **
670 **      Parameters:
671 **              bp -- the buffer to translate.
672 **
673 **      Returns:
674 **              None.  The buffer is translated in place.  Since the
675 **              translations always make the buffer shorter, this is
676 **              safe without a size parameter.
677 */
678
679 void
680 translate_dollars(bp)
681         char *bp;
682 {
683         register char *p;
684         auto char *ep;
685
686         for (p = bp; *p != '\0'; p++)
687         {
688                 if (*p == '#' && p > bp && ConfigLevel >= 3)
689                 {
690                         register char *e;
691
692                         switch (*--p & 0377)
693                         {
694                           case MACROEXPAND:
695                                 /* it's from $# -- let it go through */
696                                 p++;
697                                 break;
698
699                           case '\\':
700                                 /* it's backslash escaped */
701                                 (void) sm_strlcpy(p, p + 1, strlen(p));
702                                 break;
703
704                           default:
705                                 /* delete leading white space */
706                                 while (isascii(*p) && isspace(*p) &&
707                                        *p != '\n' && p > bp)
708                                         p--;
709                                 if ((e = strchr(++p, '\n')) != NULL)
710                                         (void) sm_strlcpy(p, e, strlen(p));
711                                 else
712                                         *p-- = '\0';
713                                 break;
714                         }
715                         continue;
716                 }
717
718                 if (*p != '$' || p[1] == '\0')
719                         continue;
720
721                 if (p[1] == '$')
722                 {
723                         /* actual dollar sign.... */
724                         (void) sm_strlcpy(p, p + 1, strlen(p));
725                         continue;
726                 }
727
728                 /* convert to macro expansion character */
729                 *p++ = MACROEXPAND;
730
731                 /* special handling for $=, $~, $&, and $? */
732                 if (*p == '=' || *p == '~' || *p == '&' || *p == '?')
733                         p++;
734
735                 /* convert macro name to code */
736                 *p = macid_parse(p, &ep);
737                 if (ep != p + 1)
738                         (void) sm_strlcpy(p + 1, ep, strlen(p + 1));
739         }
740
741         /* strip trailing white space from the line */
742         while (--p > bp && isascii(*p) && isspace(*p))
743                 *p = '\0';
744 }
745 /*
746 **  TOOMANY -- signal too many of some option
747 **
748 **      Parameters:
749 **              id -- the id of the error line
750 **              maxcnt -- the maximum possible values
751 **
752 **      Returns:
753 **              none.
754 **
755 **      Side Effects:
756 **              gives a syserr.
757 */
758
759 static void
760 toomany(id, maxcnt)
761         int id;
762         int maxcnt;
763 {
764         syserr("too many %c lines, %d max", id, maxcnt);
765 }
766 /*
767 **  FILECLASS -- read members of a class from a file
768 **
769 **      Parameters:
770 **              class -- class to define.
771 **              filename -- name of file to read.
772 **              fmt -- scanf string to use for match.
773 **              ismap -- if set, this is a map lookup.
774 **              safe -- if set, this is a safe read.
775 **              optional -- if set, it is not an error for the file to
776 **                      not exist.
777 **
778 **      Returns:
779 **              none
780 **
781 **      Side Effects:
782 **              puts all lines in filename that match a scanf into
783 **                      the named class.
784 */
785
786 /*
787 **  Break up the match into words and add to class.
788 */
789
790 static void
791 parse_class_words(class, line)
792         int class;
793         char *line;
794 {
795         while (line != NULL && *line != '\0')
796         {
797                 register char *q;
798
799                 /* strip leading spaces */
800                 while (isascii(*line) && isspace(*line))
801                         line++;
802                 if (*line == '\0')
803                         break;
804
805                 /* find the end of the word */
806                 q = line;
807                 while (*line != '\0' && !(isascii(*line) && isspace(*line)))
808                         line++;
809                 if (*line != '\0')
810                         *line++ = '\0';
811
812                 /* enter the word in the symbol table */
813                 setclass(class, q);
814         }
815 }
816
817 static void
818 fileclass(class, filename, fmt, ismap, safe, optional)
819         int class;
820         char *filename;
821         char *fmt;
822         bool ismap;
823         bool safe;
824         bool optional;
825 {
826         SM_FILE_T *f;
827         long sff;
828         pid_t pid;
829         register char *p;
830         char buf[MAXLINE];
831
832         if (tTd(37, 2))
833                 sm_dprintf("fileclass(%s, fmt=%s)\n", filename, fmt);
834
835         if (*filename == '\0')
836         {
837                 syserr("fileclass: missing file name");
838                 return;
839         }
840         else if (ismap)
841         {
842                 int status = 0;
843                 char *key;
844                 char *mn;
845                 char *cl, *spec;
846                 STAB *mapclass;
847                 MAP map;
848
849                 mn = newstr(macname(class));
850
851                 key = filename;
852
853                 /* skip past key */
854                 if ((p = strchr(filename, '@')) == NULL)
855                 {
856                         /* should not happen */
857                         syserr("fileclass: bogus map specification");
858                         sm_free(mn);
859                         return;
860                 }
861
862                 /* skip past '@' */
863                 *p++ = '\0';
864                 cl = p;
865
866                 if (strcmp(cl, "LDAP") == 0)
867                 {
868                         int n;
869                         char *lc;
870                         char jbuf[MAXHOSTNAMELEN];
871                         char lcbuf[MAXLINE];
872
873                         /* Get $j */
874                         expand("\201j", jbuf, sizeof jbuf, &BlankEnvelope);
875                         if (jbuf[0] == '\0')
876                         {
877                                 (void) sm_strlcpy(jbuf, "localhost",
878                                                   sizeof jbuf);
879                         }
880
881                         /* impose the default schema */
882                         lc = macvalue(macid("{sendmailMTACluster}"), CurEnv);
883                         if (lc == NULL)
884                                 lc = "";
885                         else
886                         {
887                                 expand(lc, lcbuf, sizeof lcbuf, CurEnv);
888                                 lc = lcbuf;
889                         }
890
891                         cl = "ldap";
892                         n = sm_snprintf(buf, sizeof buf,
893                                         "-k (&(objectClass=sendmailMTAClass)(sendmailMTAClassName=%s)(|(sendmailMTACluster=%s)(sendmailMTAHost=%s))) -v sendmailMTAClassValue",
894                                         mn, lc, jbuf);
895                         if (n >= sizeof buf)
896                         {
897                                 syserr("fileclass: F{%s}: Default LDAP string too long",
898                                        mn);
899                                 sm_free(mn);
900                                 return;
901                         }
902                         spec = buf;
903                 }
904                 else
905                 {
906                         if ((spec = strchr(cl, ':')) == NULL)
907                         {
908                                 syserr("fileclass: F{%s}: missing map class",
909                                        mn);
910                                 sm_free(mn);
911                                 return;
912                         }
913                         *spec++ ='\0';
914                 }
915
916                 /* set up map structure */
917                 mapclass = stab(cl, ST_MAPCLASS, ST_FIND);
918                 if (mapclass == NULL)
919                 {
920                         syserr("fileclass: F{%s}: class %s not available",
921                                mn, cl);
922                         sm_free(mn);
923                         return;
924                 }
925                 memset(&map, '\0', sizeof map);
926                 map.map_class = &mapclass->s_mapclass;
927                 map.map_mname = mn;
928                 map.map_mflags |= MF_FILECLASS;
929
930                 if (tTd(37, 5))
931                         sm_dprintf("fileclass: F{%s}: map class %s, key %s, spec %s\n",
932                                    mn, cl, key, spec);
933
934
935                 /* parse map spec */
936                 if (!map.map_class->map_parse(&map, spec))
937                 {
938                         /* map_parse() showed the error already */
939                         sm_free(mn);
940                         return;
941                 }
942                 map.map_mflags |= MF_VALID;
943
944                 /* open map */
945                 if (map.map_class->map_open(&map, O_RDONLY))
946                 {
947                         map.map_mflags |= MF_OPEN;
948                         map.map_pid = getpid();
949                 }
950                 else
951                 {
952                         if (!optional &&
953                             !bitset(MF_OPTIONAL, map.map_mflags))
954                                 syserr("fileclass: F{%s}: map open failed",
955                                        mn);
956                         sm_free(mn);
957                         return;
958                 }
959
960                 /* lookup */
961                 p = (*map.map_class->map_lookup)(&map, key, NULL, &status);
962                 if (status != EX_OK && status != EX_NOTFOUND)
963                 {
964                         if (!optional)
965                                 syserr("fileclass: F{%s}: map lookup failed",
966                                        mn);
967                         p = NULL;
968                 }
969
970                 /* use the results */
971                 if (p != NULL)
972                         parse_class_words(class, p);
973
974                 /* close map */
975                 map.map_mflags |= MF_CLOSING;
976                 map.map_class->map_close(&map);
977                 map.map_mflags &= ~(MF_OPEN|MF_WRITABLE|MF_CLOSING);
978                 sm_free(mn);
979                 return;
980         }
981         else if (filename[0] == '|')
982         {
983                 auto int fd;
984                 int i;
985                 char *argv[MAXPV + 1];
986
987                 i = 0;
988                 for (p = strtok(&filename[1], " \t");
989                      p != NULL && i < MAXPV;
990                      p = strtok(NULL, " \t"))
991                         argv[i++] = p;
992                 argv[i] = NULL;
993                 pid = prog_open(argv, &fd, CurEnv);
994                 if (pid < 0)
995                         f = NULL;
996                 else
997                         f = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT,
998                                        (void *) &fd, SM_IO_RDONLY, NULL);
999         }
1000         else
1001         {
1002                 pid = -1;
1003                 sff = SFF_REGONLY;
1004                 if (!bitnset(DBS_CLASSFILEINUNSAFEDIRPATH, DontBlameSendmail))
1005                         sff |= SFF_SAFEDIRPATH;
1006                 if (!bitnset(DBS_LINKEDCLASSFILEINWRITABLEDIR,
1007                              DontBlameSendmail))
1008                         sff |= SFF_NOWLINK;
1009                 if (safe)
1010                         sff |= SFF_OPENASROOT;
1011                 else if (RealUid == 0)
1012                         sff |= SFF_ROOTOK;
1013                 if (DontLockReadFiles)
1014                         sff |= SFF_NOLOCK;
1015                 f = safefopen(filename, O_RDONLY, 0, sff);
1016         }
1017         if (f == NULL)
1018         {
1019                 if (!optional)
1020                         syserr("fileclass: cannot open '%s'", filename);
1021                 return;
1022         }
1023
1024         while (sm_io_fgets(f, SM_TIME_DEFAULT, buf, sizeof buf) != NULL)
1025         {
1026 #if SCANF
1027                 char wordbuf[MAXLINE + 1];
1028 #endif /* SCANF */
1029
1030                 if (buf[0] == '#')
1031                         continue;
1032 #if SCANF
1033                 if (sm_io_sscanf(buf, fmt, wordbuf) != 1)
1034                         continue;
1035                 p = wordbuf;
1036 #else /* SCANF */
1037                 p = buf;
1038 #endif /* SCANF */
1039
1040                 parse_class_words(class, p);
1041
1042                 /*
1043                 **  If anything else is added here,
1044                 **  check if the '@' map case above
1045                 **  needs the code as well.
1046                 */
1047         }
1048
1049         (void) sm_io_close(f, SM_TIME_DEFAULT);
1050         if (pid > 0)
1051                 (void) waitfor(pid);
1052 }
1053 /*
1054 **  MAKEMAILER -- define a new mailer.
1055 **
1056 **      Parameters:
1057 **              line -- description of mailer.  This is in labeled
1058 **                      fields.  The fields are:
1059 **                         A -- the argv for this mailer
1060 **                         C -- the character set for MIME conversions
1061 **                         D -- the directory to run in
1062 **                         E -- the eol string
1063 **                         F -- the flags associated with the mailer
1064 **                         L -- the maximum line length
1065 **                         M -- the maximum message size
1066 **                         N -- the niceness at which to run
1067 **                         P -- the path to the mailer
1068 **                         Q -- the queue group for the mailer
1069 **                         R -- the recipient rewriting set
1070 **                         S -- the sender rewriting set
1071 **                         T -- the mailer type (for DSNs)
1072 **                         U -- the uid to run as
1073 **                         W -- the time to wait at the end
1074 **                         m -- maximum messages per connection
1075 **                         r -- maximum number of recipients per message
1076 **                         / -- new root directory
1077 **                      The first word is the canonical name of the mailer.
1078 **
1079 **      Returns:
1080 **              none.
1081 **
1082 **      Side Effects:
1083 **              enters the mailer into the mailer table.
1084 */
1085
1086 void
1087 makemailer(line)
1088         char *line;
1089 {
1090         register char *p;
1091         register struct mailer *m;
1092         register STAB *s;
1093         int i;
1094         char fcode;
1095         auto char *endp;
1096         static int nextmailer = 0;      /* "free" index into Mailer struct */
1097
1098         /* allocate a mailer and set up defaults */
1099         m = (struct mailer *) xalloc(sizeof *m);
1100         memset((char *) m, '\0', sizeof *m);
1101         errno = 0; /* avoid bogus error text */
1102
1103         /* collect the mailer name */
1104         for (p = line;
1105              *p != '\0' && *p != ',' && !(isascii(*p) && isspace(*p));
1106              p++)
1107                 continue;
1108         if (*p != '\0')
1109                 *p++ = '\0';
1110         if (line[0] == '\0')
1111         {
1112                 syserr("name required for mailer");
1113                 return;
1114         }
1115         m->m_name = newstr(line);
1116         m->m_qgrp = NOQGRP;
1117
1118         /* now scan through and assign info from the fields */
1119         while (*p != '\0')
1120         {
1121                 auto char *delimptr;
1122
1123                 while (*p != '\0' &&
1124                        (*p == ',' || (isascii(*p) && isspace(*p))))
1125                         p++;
1126
1127                 /* p now points to field code */
1128                 fcode = *p;
1129                 while (*p != '\0' && *p != '=' && *p != ',')
1130                         p++;
1131                 if (*p++ != '=')
1132                 {
1133                         syserr("mailer %s: `=' expected", m->m_name);
1134                         return;
1135                 }
1136                 while (isascii(*p) && isspace(*p))
1137                         p++;
1138
1139                 /* p now points to the field body */
1140                 p = munchstring(p, &delimptr, ',');
1141
1142                 /* install the field into the mailer struct */
1143                 switch (fcode)
1144                 {
1145                   case 'P':             /* pathname */
1146                         if (*p != '\0') /* error is issued below */
1147                                 m->m_mailer = newstr(p);
1148                         break;
1149
1150                   case 'F':             /* flags */
1151                         for (; *p != '\0'; p++)
1152                         {
1153                                 if (!(isascii(*p) && isspace(*p)))
1154                                 {
1155 #if _FFR_DEPRECATE_MAILER_FLAG_I
1156                                         if (*p == M_INTERNAL)
1157                                                 sm_syslog(LOG_WARNING, NOQID,
1158                                                           "WARNING: mailer=%s, flag=%c deprecated",
1159                                                           m->m_name, *p);
1160 #endif /* _FFR_DEPRECATE_MAILER_FLAG_I */
1161                                         setbitn(bitidx(*p), m->m_flags);
1162                                 }
1163                         }
1164                         break;
1165
1166                   case 'S':             /* sender rewriting ruleset */
1167                   case 'R':             /* recipient rewriting ruleset */
1168                         i = strtorwset(p, &endp, ST_ENTER);
1169                         if (i < 0)
1170                                 return;
1171                         if (fcode == 'S')
1172                                 m->m_sh_rwset = m->m_se_rwset = i;
1173                         else
1174                                 m->m_rh_rwset = m->m_re_rwset = i;
1175
1176                         p = endp;
1177                         if (*p++ == '/')
1178                         {
1179                                 i = strtorwset(p, NULL, ST_ENTER);
1180                                 if (i < 0)
1181                                         return;
1182                                 if (fcode == 'S')
1183                                         m->m_sh_rwset = i;
1184                                 else
1185                                         m->m_rh_rwset = i;
1186                         }
1187                         break;
1188
1189                   case 'E':             /* end of line string */
1190                         if (*p == '\0')
1191                                 syserr("mailer %s: null end-of-line string",
1192                                         m->m_name);
1193                         else
1194                                 m->m_eol = newstr(p);
1195                         break;
1196
1197                   case 'A':             /* argument vector */
1198                         if (*p != '\0') /* error is issued below */
1199                                 m->m_argv = makeargv(p);
1200                         break;
1201
1202                   case 'M':             /* maximum message size */
1203                         m->m_maxsize = atol(p);
1204                         break;
1205
1206                   case 'm':             /* maximum messages per connection */
1207                         m->m_maxdeliveries = atoi(p);
1208                         break;
1209
1210                   case 'r':             /* max recipient per envelope */
1211                         m->m_maxrcpt = atoi(p);
1212                         break;
1213
1214                   case 'L':             /* maximum line length */
1215                         m->m_linelimit = atoi(p);
1216                         if (m->m_linelimit < 0)
1217                                 m->m_linelimit = 0;
1218                         break;
1219
1220                   case 'N':             /* run niceness */
1221                         m->m_nice = atoi(p);
1222                         break;
1223
1224                   case 'D':             /* working directory */
1225                         if (*p == '\0')
1226                                 syserr("mailer %s: null working directory",
1227                                         m->m_name);
1228                         else
1229                                 m->m_execdir = newstr(p);
1230                         break;
1231
1232                   case 'C':             /* default charset */
1233                         if (*p == '\0')
1234                                 syserr("mailer %s: null charset", m->m_name);
1235                         else
1236                                 m->m_defcharset = newstr(p);
1237                         break;
1238
1239                   case 'Q':             /* queue for this mailer */
1240                         if (*p == '\0')
1241                         {
1242                                 syserr("mailer %s: null queue", m->m_name);
1243                                 break;
1244                         }
1245                         s = stab(p, ST_QUEUE, ST_FIND);
1246                         if (s == NULL)
1247                                 syserr("mailer %s: unknown queue %s",
1248                                         m->m_name, p);
1249                         else
1250                                 m->m_qgrp = s->s_quegrp->qg_index;
1251                         break;
1252
1253                   case 'T':             /* MTA-Name/Address/Diagnostic types */
1254                         /* extract MTA name type; default to "dns" */
1255                         m->m_mtatype = newstr(p);
1256                         p = strchr(m->m_mtatype, '/');
1257                         if (p != NULL)
1258                         {
1259                                 *p++ = '\0';
1260                                 if (*p == '\0')
1261                                         p = NULL;
1262                         }
1263                         if (*m->m_mtatype == '\0')
1264                                 m->m_mtatype = "dns";
1265
1266                         /* extract address type; default to "rfc822" */
1267                         m->m_addrtype = p;
1268                         if (p != NULL)
1269                                 p = strchr(p, '/');
1270                         if (p != NULL)
1271                         {
1272                                 *p++ = '\0';
1273                                 if (*p == '\0')
1274                                         p = NULL;
1275                         }
1276                         if (m->m_addrtype == NULL || *m->m_addrtype == '\0')
1277                                 m->m_addrtype = "rfc822";
1278
1279                         /* extract diagnostic type; default to "smtp" */
1280                         m->m_diagtype = p;
1281                         if (m->m_diagtype == NULL || *m->m_diagtype == '\0')
1282                                 m->m_diagtype = "smtp";
1283                         break;
1284
1285                   case 'U':             /* user id */
1286                         if (isascii(*p) && !isdigit(*p))
1287                         {
1288                                 char *q = p;
1289                                 struct passwd *pw;
1290
1291                                 while (*p != '\0' && isascii(*p) &&
1292                                        (isalnum(*p) || strchr("-_", *p) != NULL))
1293                                         p++;
1294                                 while (isascii(*p) && isspace(*p))
1295                                         *p++ = '\0';
1296                                 if (*p != '\0')
1297                                         *p++ = '\0';
1298                                 if (*q == '\0')
1299                                 {
1300                                         syserr("mailer %s: null user name",
1301                                                 m->m_name);
1302                                         break;
1303                                 }
1304                                 pw = sm_getpwnam(q);
1305                                 if (pw == NULL)
1306                                 {
1307                                         syserr("readcf: mailer U= flag: unknown user %s", q);
1308                                         break;
1309                                 }
1310                                 else
1311                                 {
1312                                         m->m_uid = pw->pw_uid;
1313                                         m->m_gid = pw->pw_gid;
1314                                 }
1315                         }
1316                         else
1317                         {
1318                                 auto char *q;
1319
1320                                 m->m_uid = strtol(p, &q, 0);
1321                                 p = q;
1322                                 while (isascii(*p) && isspace(*p))
1323                                         p++;
1324                                 if (*p != '\0')
1325                                         p++;
1326                         }
1327                         while (isascii(*p) && isspace(*p))
1328                                 p++;
1329                         if (*p == '\0')
1330                                 break;
1331                         if (isascii(*p) && !isdigit(*p))
1332                         {
1333                                 char *q = p;
1334                                 struct group *gr;
1335
1336                                 while (isascii(*p) && isalnum(*p))
1337                                         p++;
1338                                 *p++ = '\0';
1339                                 if (*q == '\0')
1340                                 {
1341                                         syserr("mailer %s: null group name",
1342                                                 m->m_name);
1343                                         break;
1344                                 }
1345                                 gr = getgrnam(q);
1346                                 if (gr == NULL)
1347                                 {
1348                                         syserr("readcf: mailer U= flag: unknown group %s", q);
1349                                         break;
1350                                 }
1351                                 else
1352                                         m->m_gid = gr->gr_gid;
1353                         }
1354                         else
1355                         {
1356                                 m->m_gid = strtol(p, NULL, 0);
1357                         }
1358                         break;
1359
1360                   case 'W':             /* wait timeout */
1361                         m->m_wait = convtime(p, 's');
1362                         break;
1363
1364                   case '/':             /* new root directory */
1365                         if (*p == '\0')
1366                                 syserr("mailer %s: null root directory",
1367                                         m->m_name);
1368                         else
1369                                 m->m_rootdir = newstr(p);
1370                         break;
1371
1372                   default:
1373                         syserr("M%s: unknown mailer equate %c=",
1374                                m->m_name, fcode);
1375                         break;
1376                 }
1377
1378                 p = delimptr;
1379         }
1380
1381 #if !HASRRESVPORT
1382         if (bitnset(M_SECURE_PORT, m->m_flags))
1383         {
1384                 (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
1385                                      "M%s: Warning: F=%c set on system that doesn't support rresvport()\n",
1386                                      m->m_name, M_SECURE_PORT);
1387         }
1388 #endif /* !HASRRESVPORT */
1389
1390 #if !HASNICE
1391         if (m->m_nice != 0)
1392         {
1393                 (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
1394                                      "M%s: Warning: N= set on system that doesn't support nice()\n",
1395                                      m->m_name);
1396         }
1397 #endif /* !HASNICE */
1398
1399         /* do some rationality checking */
1400         if (m->m_argv == NULL)
1401         {
1402                 syserr("M%s: A= argument required", m->m_name);
1403                 return;
1404         }
1405         if (m->m_mailer == NULL)
1406         {
1407                 syserr("M%s: P= argument required", m->m_name);
1408                 return;
1409         }
1410
1411         if (nextmailer >= MAXMAILERS)
1412         {
1413                 syserr("too many mailers defined (%d max)", MAXMAILERS);
1414                 return;
1415         }
1416
1417         if (m->m_maxrcpt <= 0)
1418                 m->m_maxrcpt = DEFAULT_MAX_RCPT;
1419
1420         /* do some heuristic cleanup for back compatibility */
1421         if (bitnset(M_LIMITS, m->m_flags))
1422         {
1423                 if (m->m_linelimit == 0)
1424                         m->m_linelimit = SMTPLINELIM;
1425                 if (ConfigLevel < 2)
1426                         setbitn(M_7BITS, m->m_flags);
1427         }
1428
1429         if (strcmp(m->m_mailer, "[TCP]") == 0)
1430         {
1431                 syserr("M%s: P=[TCP] must be replaced by P=[IPC]", m->m_name);
1432                 return;
1433         }
1434
1435         if (strcmp(m->m_mailer, "[IPC]") == 0)
1436         {
1437                 /* Use the second argument for host or path to socket */
1438                 if (m->m_argv[0] == NULL || m->m_argv[1] == NULL ||
1439                     m->m_argv[1][0] == '\0')
1440                 {
1441                         syserr("M%s: too few parameters for %s mailer",
1442                                m->m_name, m->m_mailer);
1443                         return;
1444                 }
1445                 if (strcmp(m->m_argv[0], "TCP") != 0
1446 #if NETUNIX
1447                     && strcmp(m->m_argv[0], "FILE") != 0
1448 #endif /* NETUNIX */
1449                     )
1450                 {
1451                         (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
1452                                              "M%s: Warning: first argument in %s mailer must be %s\n",
1453                                              m->m_name, m->m_mailer,
1454 #if NETUNIX
1455                                              "TCP or FILE"
1456 #else /* NETUNIX */
1457                                              "TCP"
1458 #endif /* NETUNIX */
1459                                      );
1460                 }
1461                 if (m->m_mtatype == NULL)
1462                         m->m_mtatype = "dns";
1463                 if (m->m_addrtype == NULL)
1464                         m->m_addrtype = "rfc822";
1465                 if (m->m_diagtype == NULL)
1466                 {
1467                         if (m->m_argv[0] != NULL &&
1468                             strcmp(m->m_argv[0], "FILE") == 0)
1469                                 m->m_diagtype = "x-unix";
1470                         else
1471                                 m->m_diagtype = "smtp";
1472                 }
1473         }
1474         else if (strcmp(m->m_mailer, "[FILE]") == 0)
1475         {
1476                 /* Use the second argument for filename */
1477                 if (m->m_argv[0] == NULL || m->m_argv[1] == NULL ||
1478                     m->m_argv[2] != NULL)
1479                 {
1480                         syserr("M%s: too %s parameters for [FILE] mailer",
1481                                m->m_name,
1482                                (m->m_argv[0] == NULL ||
1483                                 m->m_argv[1] == NULL) ? "few" : "many");
1484                         return;
1485                 }
1486                 else if (strcmp(m->m_argv[0], "FILE") != 0)
1487                 {
1488                         syserr("M%s: first argument in [FILE] mailer must be FILE",
1489                                m->m_name);
1490                         return;
1491                 }
1492         }
1493
1494         if (m->m_eol == NULL)
1495         {
1496                 char **pp;
1497
1498                 /* default for SMTP is \r\n; use \n for local delivery */
1499                 for (pp = m->m_argv; *pp != NULL; pp++)
1500                 {
1501                         for (p = *pp; *p != '\0'; )
1502                         {
1503                                 if ((*p++ & 0377) == MACROEXPAND && *p == 'u')
1504                                         break;
1505                         }
1506                         if (*p != '\0')
1507                                 break;
1508                 }
1509                 if (*pp == NULL)
1510                         m->m_eol = "\r\n";
1511                 else
1512                         m->m_eol = "\n";
1513         }
1514
1515         /* enter the mailer into the symbol table */
1516         s = stab(m->m_name, ST_MAILER, ST_ENTER);
1517         if (s->s_mailer != NULL)
1518         {
1519                 i = s->s_mailer->m_mno;
1520                 sm_free(s->s_mailer); /* XXX */
1521         }
1522         else
1523         {
1524                 i = nextmailer++;
1525         }
1526         Mailer[i] = s->s_mailer = m;
1527         m->m_mno = i;
1528 }
1529 /*
1530 **  MUNCHSTRING -- translate a string into internal form.
1531 **
1532 **      Parameters:
1533 **              p -- the string to munch.
1534 **              delimptr -- if non-NULL, set to the pointer of the
1535 **                      field delimiter character.
1536 **              delim -- the delimiter for the field.
1537 **
1538 **      Returns:
1539 **              the munched string.
1540 **
1541 **      Side Effects:
1542 **              the munched string is a local static buffer.
1543 **              it must be copied before the function is called again.
1544 */
1545
1546 char *
1547 munchstring(p, delimptr, delim)
1548         register char *p;
1549         char **delimptr;
1550         int delim;
1551 {
1552         register char *q;
1553         bool backslash = false;
1554         bool quotemode = false;
1555         static char buf[MAXLINE];
1556
1557         for (q = buf; *p != '\0' && q < &buf[sizeof buf - 1]; p++)
1558         {
1559                 if (backslash)
1560                 {
1561                         /* everything is roughly literal */
1562                         backslash = false;
1563                         switch (*p)
1564                         {
1565                           case 'r':             /* carriage return */
1566                                 *q++ = '\r';
1567                                 continue;
1568
1569                           case 'n':             /* newline */
1570                                 *q++ = '\n';
1571                                 continue;
1572
1573                           case 'f':             /* form feed */
1574                                 *q++ = '\f';
1575                                 continue;
1576
1577                           case 'b':             /* backspace */
1578                                 *q++ = '\b';
1579                                 continue;
1580                         }
1581                         *q++ = *p;
1582                 }
1583                 else
1584                 {
1585                         if (*p == '\\')
1586                                 backslash = true;
1587                         else if (*p == '"')
1588                                 quotemode = !quotemode;
1589                         else if (quotemode || *p != delim)
1590                                 *q++ = *p;
1591                         else
1592                                 break;
1593                 }
1594         }
1595
1596         if (delimptr != NULL)
1597                 *delimptr = p;
1598         *q++ = '\0';
1599         return buf;
1600 }
1601 /*
1602 **  EXTRQUOTSTR -- extract a (quoted) string.
1603 **
1604 **      This routine deals with quoted (") strings and escaped
1605 **      spaces (\\ ).
1606 **
1607 **      Parameters:
1608 **              p -- source string.
1609 **              delimptr -- if non-NULL, set to the pointer of the
1610 **                      field delimiter character.
1611 **              delimbuf -- delimiters for the field.
1612 **              st -- if non-NULL, store the return value (whether the
1613 **                      string was correctly quoted) here.
1614 **
1615 **      Returns:
1616 **              the extracted string.
1617 **
1618 **      Side Effects:
1619 **              the returned string is a local static buffer.
1620 **              it must be copied before the function is called again.
1621 */
1622
1623 static char *
1624 extrquotstr(p, delimptr, delimbuf, st)
1625         register char *p;
1626         char **delimptr;
1627         char *delimbuf;
1628         bool *st;
1629 {
1630         register char *q;
1631         bool backslash = false;
1632         bool quotemode = false;
1633         static char buf[MAXLINE];
1634
1635         for (q = buf; *p != '\0' && q < &buf[sizeof buf - 1]; p++)
1636         {
1637                 if (backslash)
1638                 {
1639                         backslash = false;
1640                         if (*p != ' ')
1641                                 *q++ = '\\';
1642                 }
1643                 if (*p == '\\')
1644                         backslash = true;
1645                 else if (*p == '"')
1646                         quotemode = !quotemode;
1647                 else if (quotemode ||
1648                          strchr(delimbuf, (int) *p) == NULL)
1649                         *q++ = *p;
1650                 else
1651                         break;
1652         }
1653
1654         if (delimptr != NULL)
1655                 *delimptr = p;
1656         *q++ = '\0';
1657         if (st != NULL)
1658                 *st = !(quotemode || backslash);
1659         return buf;
1660 }
1661 /*
1662 **  MAKEARGV -- break up a string into words
1663 **
1664 **      Parameters:
1665 **              p -- the string to break up.
1666 **
1667 **      Returns:
1668 **              a char **argv (dynamically allocated)
1669 **
1670 **      Side Effects:
1671 **              munges p.
1672 */
1673
1674 static char **
1675 makeargv(p)
1676         register char *p;
1677 {
1678         char *q;
1679         int i;
1680         char **avp;
1681         char *argv[MAXPV + 1];
1682
1683         /* take apart the words */
1684         i = 0;
1685         while (*p != '\0' && i < MAXPV)
1686         {
1687                 q = p;
1688                 while (*p != '\0' && !(isascii(*p) && isspace(*p)))
1689                         p++;
1690                 while (isascii(*p) && isspace(*p))
1691                         *p++ = '\0';
1692                 argv[i++] = newstr(q);
1693         }
1694         argv[i++] = NULL;
1695
1696         /* now make a copy of the argv */
1697         avp = (char **) xalloc(sizeof *avp * i);
1698         memmove((char *) avp, (char *) argv, sizeof *avp * i);
1699
1700         return avp;
1701 }
1702 /*
1703 **  PRINTRULES -- print rewrite rules (for debugging)
1704 **
1705 **      Parameters:
1706 **              none.
1707 **
1708 **      Returns:
1709 **              none.
1710 **
1711 **      Side Effects:
1712 **              prints rewrite rules.
1713 */
1714
1715 void
1716 printrules()
1717 {
1718         register struct rewrite *rwp;
1719         register int ruleset;
1720
1721         for (ruleset = 0; ruleset < 10; ruleset++)
1722         {
1723                 if (RewriteRules[ruleset] == NULL)
1724                         continue;
1725                 (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
1726                                      "\n----Rule Set %d:", ruleset);
1727
1728                 for (rwp = RewriteRules[ruleset]; rwp != NULL; rwp = rwp->r_next)
1729                 {
1730                         (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
1731                                              "\nLHS:");
1732                         printav(rwp->r_lhs);
1733                         (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
1734                                              "RHS:");
1735                         printav(rwp->r_rhs);
1736                 }
1737         }
1738 }
1739 /*
1740 **  PRINTMAILER -- print mailer structure (for debugging)
1741 **
1742 **      Parameters:
1743 **              m -- the mailer to print
1744 **
1745 **      Returns:
1746 **              none.
1747 */
1748
1749 void
1750 printmailer(m)
1751         register MAILER *m;
1752 {
1753         int j;
1754
1755         (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
1756                              "mailer %d (%s): P=%s S=", m->m_mno, m->m_name,
1757                              m->m_mailer);
1758         if (RuleSetNames[m->m_se_rwset] == NULL)
1759                 (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%d/",
1760                                      m->m_se_rwset);
1761         else
1762                 (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%s/",
1763                                      RuleSetNames[m->m_se_rwset]);
1764         if (RuleSetNames[m->m_sh_rwset] == NULL)
1765                 (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%d R=",
1766                                      m->m_sh_rwset);
1767         else
1768                 (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%s R=",
1769                                      RuleSetNames[m->m_sh_rwset]);
1770         if (RuleSetNames[m->m_re_rwset] == NULL)
1771                 (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%d/",
1772                                      m->m_re_rwset);
1773         else
1774                 (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%s/",
1775                                      RuleSetNames[m->m_re_rwset]);
1776         if (RuleSetNames[m->m_rh_rwset] == NULL)
1777                 (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%d ",
1778                                      m->m_rh_rwset);
1779         else
1780                 (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%s ",
1781                                      RuleSetNames[m->m_rh_rwset]);
1782         (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "M=%ld U=%d:%d F=",
1783                              m->m_maxsize, (int) m->m_uid, (int) m->m_gid);
1784         for (j = '\0'; j <= '\177'; j++)
1785                 if (bitnset(j, m->m_flags))
1786                         (void) sm_io_putc(smioout, SM_TIME_DEFAULT, j);
1787         (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, " L=%d E=",
1788                              m->m_linelimit);
1789         xputs(m->m_eol);
1790         if (m->m_defcharset != NULL)
1791                 (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, " C=%s",
1792                                      m->m_defcharset);
1793         (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, " T=%s/%s/%s",
1794                              m->m_mtatype == NULL
1795                                 ? "<undefined>" : m->m_mtatype,
1796                              m->m_addrtype == NULL
1797                                 ? "<undefined>" : m->m_addrtype,
1798                              m->m_diagtype == NULL
1799                                 ? "<undefined>" : m->m_diagtype);
1800         (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, " r=%d", m->m_maxrcpt);
1801         if (m->m_argv != NULL)
1802         {
1803                 char **a = m->m_argv;
1804
1805                 (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, " A=");
1806                 while (*a != NULL)
1807                 {
1808                         if (a != m->m_argv)
1809                                 (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
1810                                                      " ");
1811                         xputs(*a++);
1812                 }
1813         }
1814         (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "\n");
1815 }
1816 /*
1817 **  SETOPTION -- set global processing option
1818 **
1819 **      Parameters:
1820 **              opt -- option name.
1821 **              val -- option value (as a text string).
1822 **              safe -- set if this came from a configuration file.
1823 **                      Some options (if set from the command line) will
1824 **                      reset the user id to avoid security problems.
1825 **              sticky -- if set, don't let other setoptions override
1826 **                      this value.
1827 **              e -- the main envelope.
1828 **
1829 **      Returns:
1830 **              none.
1831 **
1832 **      Side Effects:
1833 **              Sets options as implied by the arguments.
1834 */
1835
1836 static BITMAP256        StickyOpt;              /* set if option is stuck */
1837
1838 #if NAMED_BIND
1839
1840 static struct resolverflags
1841 {
1842         char    *rf_name;       /* name of the flag */
1843         long    rf_bits;        /* bits to set/clear */
1844 } ResolverFlags[] =
1845 {
1846         { "debug",      RES_DEBUG       },
1847         { "aaonly",     RES_AAONLY      },
1848         { "usevc",      RES_USEVC       },
1849         { "primary",    RES_PRIMARY     },
1850         { "igntc",      RES_IGNTC       },
1851         { "recurse",    RES_RECURSE     },
1852         { "defnames",   RES_DEFNAMES    },
1853         { "stayopen",   RES_STAYOPEN    },
1854         { "dnsrch",     RES_DNSRCH      },
1855 # ifdef RES_USE_INET6
1856         { "use_inet6",  RES_USE_INET6   },
1857 # endif /* RES_USE_INET6 */
1858         { "true",       0               },      /* avoid error on old syntax */
1859         { NULL,         0               }
1860 };
1861
1862 #endif /* NAMED_BIND */
1863
1864 #define OI_NONE         0       /* no special treatment */
1865 #define OI_SAFE         0x0001  /* safe for random people to use */
1866 #define OI_SUBOPT       0x0002  /* option has suboptions */
1867
1868 static struct optioninfo
1869 {
1870         char            *o_name;        /* long name of option */
1871         unsigned char   o_code;         /* short name of option */
1872         unsigned short  o_flags;        /* option flags */
1873 } OptionTab[] =
1874 {
1875 #if defined(SUN_EXTENSIONS) && defined(REMOTE_MODE)
1876         { "RemoteMode",                 '>',            OI_NONE },
1877 #endif /* defined(SUN_EXTENSIONS) && defined(REMOTE_MODE) */
1878         { "SevenBitInput",              '7',            OI_SAFE },
1879         { "EightBitMode",               '8',            OI_SAFE },
1880         { "AliasFile",                  'A',            OI_NONE },
1881         { "AliasWait",                  'a',            OI_NONE },
1882         { "BlankSub",                   'B',            OI_NONE },
1883         { "MinFreeBlocks",              'b',            OI_SAFE },
1884         { "CheckpointInterval",         'C',            OI_SAFE },
1885         { "HoldExpensive",              'c',            OI_NONE },
1886         { "DeliveryMode",               'd',            OI_SAFE },
1887         { "ErrorHeader",                'E',            OI_NONE },
1888         { "ErrorMode",                  'e',            OI_SAFE },
1889         { "TempFileMode",               'F',            OI_NONE },
1890         { "SaveFromLine",               'f',            OI_NONE },
1891         { "MatchGECOS",                 'G',            OI_NONE },
1892
1893         /* no long name, just here to avoid problems in setoption */
1894         { "",                           'g',            OI_NONE },
1895         { "HelpFile",                   'H',            OI_NONE },
1896         { "MaxHopCount",                'h',            OI_NONE },
1897         { "ResolverOptions",            'I',            OI_NONE },
1898         { "IgnoreDots",                 'i',            OI_SAFE },
1899         { "ForwardPath",                'J',            OI_NONE },
1900         { "SendMimeErrors",             'j',            OI_SAFE },
1901         { "ConnectionCacheSize",        'k',            OI_NONE },
1902         { "ConnectionCacheTimeout",     'K',            OI_NONE },
1903         { "UseErrorsTo",                'l',            OI_NONE },
1904         { "LogLevel",                   'L',            OI_SAFE },
1905         { "MeToo",                      'm',            OI_SAFE },
1906
1907         /* no long name, just here to avoid problems in setoption */
1908         { "",                           'M',            OI_NONE },
1909         { "CheckAliases",               'n',            OI_NONE },
1910         { "OldStyleHeaders",            'o',            OI_SAFE },
1911         { "DaemonPortOptions",          'O',            OI_NONE },
1912         { "PrivacyOptions",             'p',            OI_SAFE },
1913         { "PostmasterCopy",             'P',            OI_NONE },
1914         { "QueueFactor",                'q',            OI_NONE },
1915         { "QueueDirectory",             'Q',            OI_NONE },
1916         { "DontPruneRoutes",            'R',            OI_NONE },
1917         { "Timeout",                    'r',            OI_SUBOPT },
1918         { "StatusFile",                 'S',            OI_NONE },
1919         { "SuperSafe",                  's',            OI_SAFE },
1920         { "QueueTimeout",               'T',            OI_NONE },
1921         { "TimeZoneSpec",               't',            OI_NONE },
1922         { "UserDatabaseSpec",           'U',            OI_NONE },
1923         { "DefaultUser",                'u',            OI_NONE },
1924         { "FallbackMXhost",             'V',            OI_NONE },
1925         { "Verbose",                    'v',            OI_SAFE },
1926         { "TryNullMXList",              'w',            OI_NONE },
1927         { "QueueLA",                    'x',            OI_NONE },
1928         { "RefuseLA",                   'X',            OI_NONE },
1929         { "RecipientFactor",            'y',            OI_NONE },
1930         { "ForkEachJob",                'Y',            OI_NONE },
1931         { "ClassFactor",                'z',            OI_NONE },
1932         { "RetryFactor",                'Z',            OI_NONE },
1933 #define O_QUEUESORTORD  0x81
1934         { "QueueSortOrder",             O_QUEUESORTORD, OI_SAFE },
1935 #define O_HOSTSFILE     0x82
1936         { "HostsFile",                  O_HOSTSFILE,    OI_NONE },
1937 #define O_MQA           0x83
1938         { "MinQueueAge",                O_MQA,          OI_SAFE },
1939 #define O_DEFCHARSET    0x85
1940         { "DefaultCharSet",             O_DEFCHARSET,   OI_SAFE },
1941 #define O_SSFILE        0x86
1942         { "ServiceSwitchFile",          O_SSFILE,       OI_NONE },
1943 #define O_DIALDELAY     0x87
1944         { "DialDelay",                  O_DIALDELAY,    OI_SAFE },
1945 #define O_NORCPTACTION  0x88
1946         { "NoRecipientAction",          O_NORCPTACTION, OI_SAFE },
1947 #define O_SAFEFILEENV   0x89
1948         { "SafeFileEnvironment",        O_SAFEFILEENV,  OI_NONE },
1949 #define O_MAXMSGSIZE    0x8a
1950         { "MaxMessageSize",             O_MAXMSGSIZE,   OI_NONE },
1951 #define O_COLONOKINADDR 0x8b
1952         { "ColonOkInAddr",              O_COLONOKINADDR, OI_SAFE },
1953 #define O_MAXQUEUERUN   0x8c
1954         { "MaxQueueRunSize",            O_MAXQUEUERUN,  OI_SAFE },
1955 #define O_MAXCHILDREN   0x8d
1956         { "MaxDaemonChildren",          O_MAXCHILDREN,  OI_NONE },
1957 #define O_KEEPCNAMES    0x8e
1958         { "DontExpandCnames",           O_KEEPCNAMES,   OI_NONE },
1959 #define O_MUSTQUOTE     0x8f
1960         { "MustQuoteChars",             O_MUSTQUOTE,    OI_NONE },
1961 #define O_SMTPGREETING  0x90
1962         { "SmtpGreetingMessage",        O_SMTPGREETING, OI_NONE },
1963 #define O_UNIXFROM      0x91
1964         { "UnixFromLine",               O_UNIXFROM,     OI_NONE },
1965 #define O_OPCHARS       0x92
1966         { "OperatorChars",              O_OPCHARS,      OI_NONE },
1967 #define O_DONTINITGRPS  0x93
1968         { "DontInitGroups",             O_DONTINITGRPS, OI_NONE },
1969 #define O_SLFH          0x94
1970         { "SingleLineFromHeader",       O_SLFH,         OI_SAFE },
1971 #define O_ABH           0x95
1972         { "AllowBogusHELO",             O_ABH,          OI_SAFE },
1973 #define O_CONNTHROT     0x97
1974         { "ConnectionRateThrottle",     O_CONNTHROT,    OI_NONE },
1975 #define O_UGW           0x99
1976         { "UnsafeGroupWrites",          O_UGW,          OI_NONE },
1977 #define O_DBLBOUNCE     0x9a
1978         { "DoubleBounceAddress",        O_DBLBOUNCE,    OI_NONE },
1979 #define O_HSDIR         0x9b
1980         { "HostStatusDirectory",        O_HSDIR,        OI_NONE },
1981 #define O_SINGTHREAD    0x9c
1982         { "SingleThreadDelivery",       O_SINGTHREAD,   OI_NONE },
1983 #define O_RUNASUSER     0x9d
1984         { "RunAsUser",                  O_RUNASUSER,    OI_NONE },
1985 #define O_DSN_RRT       0x9e
1986         { "RrtImpliesDsn",              O_DSN_RRT,      OI_NONE },
1987 #define O_PIDFILE       0x9f
1988         { "PidFile",                    O_PIDFILE,      OI_NONE },
1989 #define O_DONTBLAMESENDMAIL     0xa0
1990         { "DontBlameSendmail",          O_DONTBLAMESENDMAIL,    OI_NONE },
1991 #define O_DPI           0xa1
1992         { "DontProbeInterfaces",        O_DPI,          OI_NONE },
1993 #define O_MAXRCPT       0xa2
1994         { "MaxRecipientsPerMessage",    O_MAXRCPT,      OI_SAFE },
1995 #define O_DEADLETTER    0xa3
1996         { "DeadLetterDrop",             O_DEADLETTER,   OI_NONE },
1997 #if _FFR_DONTLOCKFILESFORREAD_OPTION
1998 # define O_DONTLOCK     0xa4
1999         { "DontLockFilesForRead",       O_DONTLOCK,     OI_NONE },
2000 #endif /* _FFR_DONTLOCKFILESFORREAD_OPTION */
2001 #define O_MAXALIASRCSN  0xa5
2002         { "MaxAliasRecursion",          O_MAXALIASRCSN, OI_NONE },
2003 #define O_CNCTONLYTO    0xa6
2004         { "ConnectOnlyTo",              O_CNCTONLYTO,   OI_NONE },
2005 #define O_TRUSTUSER     0xa7
2006         { "TrustedUser",                O_TRUSTUSER,    OI_NONE },
2007 #define O_MAXMIMEHDRLEN 0xa8
2008         { "MaxMimeHeaderLength",        O_MAXMIMEHDRLEN,        OI_NONE },
2009 #define O_CONTROLSOCKET 0xa9
2010         { "ControlSocketName",          O_CONTROLSOCKET,        OI_NONE },
2011 #define O_MAXHDRSLEN    0xaa
2012         { "MaxHeadersLength",           O_MAXHDRSLEN,   OI_NONE },
2013 #if _FFR_MAX_FORWARD_ENTRIES
2014 # define O_MAXFORWARD   0xab
2015         { "MaxForwardEntries",          O_MAXFORWARD,   OI_NONE },
2016 #endif /* _FFR_MAX_FORWARD_ENTRIES */
2017 #define O_PROCTITLEPREFIX       0xac
2018         { "ProcessTitlePrefix",         O_PROCTITLEPREFIX,      OI_NONE },
2019 #define O_SASLINFO      0xad
2020 #if _FFR_ALLOW_SASLINFO
2021         { "DefaultAuthInfo",            O_SASLINFO,     OI_SAFE },
2022 #else /* _FFR_ALLOW_SASLINFO */
2023         { "DefaultAuthInfo",            O_SASLINFO,     OI_NONE },
2024 #endif /* _FFR_ALLOW_SASLINFO */
2025 #define O_SASLMECH      0xae
2026         { "AuthMechanisms",             O_SASLMECH,     OI_NONE },
2027 #define O_CLIENTPORT    0xaf
2028         { "ClientPortOptions",          O_CLIENTPORT,   OI_NONE },
2029 #define O_DF_BUFSIZE    0xb0
2030         { "DataFileBufferSize",         O_DF_BUFSIZE,   OI_NONE },
2031 #define O_XF_BUFSIZE    0xb1
2032         { "XscriptFileBufferSize",      O_XF_BUFSIZE,   OI_NONE },
2033 #define O_LDAPDEFAULTSPEC       0xb2
2034         { "LDAPDefaultSpec",            O_LDAPDEFAULTSPEC,      OI_NONE },
2035 #if _FFR_QUEUEDELAY
2036 # define O_QUEUEDELAY   0xb3
2037         { "QueueDelay",                 O_QUEUEDELAY,   OI_NONE },
2038 #endif /* _FFR_QUEUEDELAY */
2039 #define O_SRVCERTFILE   0xb4
2040         { "ServerCertFile",             O_SRVCERTFILE,  OI_NONE },
2041 #define O_SRVKEYFILE    0xb5
2042         { "ServerKeyFile",              O_SRVKEYFILE,   OI_NONE },
2043 #define O_CLTCERTFILE   0xb6
2044         { "ClientCertFile",             O_CLTCERTFILE,  OI_NONE },
2045 #define O_CLTKEYFILE    0xb7
2046         { "ClientKeyFile",              O_CLTKEYFILE,   OI_NONE },
2047 #define O_CACERTFILE    0xb8
2048         { "CACertFile",                 O_CACERTFILE,   OI_NONE },
2049 #define O_CACERTPATH    0xb9
2050         { "CACertPath",                 O_CACERTPATH,   OI_NONE },
2051 #define O_DHPARAMS      0xba
2052         { "DHParameters",               O_DHPARAMS,     OI_NONE },
2053 #define O_INPUTMILTER   0xbb
2054         { "InputMailFilters",           O_INPUTMILTER,  OI_NONE },
2055 #define O_MILTER        0xbc
2056         { "Milter",                     O_MILTER,       OI_SUBOPT       },
2057 #define O_SASLOPTS      0xbd
2058         { "AuthOptions",                O_SASLOPTS,     OI_NONE },
2059 #define O_QUEUE_FILE_MODE       0xbe
2060         { "QueueFileMode",              O_QUEUE_FILE_MODE, OI_NONE      },
2061 #if _FFR_TLS_1
2062 # define O_DHPARAMS5    0xbf
2063         { "DHParameters512",            O_DHPARAMS5,    OI_NONE },
2064 # define O_CIPHERLIST   0xc0
2065         { "CipherList",                 O_CIPHERLIST,   OI_NONE },
2066 #endif /* _FFR_TLS_1 */
2067 #define O_RANDFILE      0xc1
2068         { "RandFile",                   O_RANDFILE,     OI_NONE },
2069 #define O_TLS_SRV_OPTS  0xc2
2070         { "TLSSrvOptions",              O_TLS_SRV_OPTS, OI_NONE },
2071 #define O_RCPTTHROT     0xc3
2072         { "BadRcptThrottle",            O_RCPTTHROT,    OI_SAFE },
2073 #define O_DLVR_MIN      0xc4
2074         { "DeliverByMin",               O_DLVR_MIN,     OI_NONE },
2075 #define O_MAXQUEUECHILDREN      0xc5
2076         { "MaxQueueChildren",           O_MAXQUEUECHILDREN,     OI_NONE },
2077 #define O_MAXRUNNERSPERQUEUE    0xc6
2078         { "MaxRunnersPerQueue",         O_MAXRUNNERSPERQUEUE,   OI_NONE },
2079 #define O_DIRECTSUBMODIFIERS    0xc7
2080         { "DirectSubmissionModifiers",  O_DIRECTSUBMODIFIERS,   OI_NONE },
2081 #define O_NICEQUEUERUN  0xc8
2082         { "NiceQueueRun",               O_NICEQUEUERUN, OI_NONE },
2083 #define O_SHMKEY        0xc9
2084         { "SharedMemoryKey",            O_SHMKEY,       OI_NONE },
2085 #define O_SASLBITS      0xca
2086         { "AuthMaxBits",                O_SASLBITS,     OI_NONE },
2087 #define O_MBDB          0xcb
2088         { "MailboxDatabase",            O_MBDB,         OI_NONE },
2089 #define O_MSQ           0xcc
2090         { "UseMSP",     O_MSQ,          OI_NONE },
2091 #define O_DELAY_LA      0xcd
2092         { "DelayLA",    O_DELAY_LA,     OI_NONE },
2093 #define O_FASTSPLIT     0xce
2094         { "FastSplit",  O_FASTSPLIT,    OI_NONE },
2095 #if _FFR_SOFT_BOUNCE
2096 # define O_SOFTBOUNCE   0xcf
2097         { "SoftBounce", O_SOFTBOUNCE,   OI_NONE },
2098 #endif /* _FFR_SOFT_BOUNCE */
2099 #if _FFR_SELECT_SHM
2100 # define O_SHMKEYFILE   0xd0
2101         { "SharedMemoryKeyFile",        O_SHMKEYFILE,   OI_NONE },
2102 #endif /* _FFR_SELECT_SHM */
2103 #if _FFR_REJECT_LOG
2104 # define O_REJECTLOGINTERVAL    0xd1
2105         { "RejectLogInterval",  O_REJECTLOGINTERVAL,    OI_NONE },
2106 #endif /* _FFR_REJECT_LOG */
2107 #if _FFR_REQ_DIR_FSYNC_OPT
2108 # define O_REQUIRES_DIR_FSYNC   0xd2
2109         { "RequiresDirfsync",   O_REQUIRES_DIR_FSYNC,   OI_NONE },
2110 #endif /* _FFR_REQ_DIR_FSYNC_OPT */
2111         { NULL,                         '\0',           OI_NONE }
2112 };
2113
2114 # define CANONIFY(val)
2115
2116 # define SET_OPT_DEFAULT(opt, val)      opt = val
2117
2118 /* set a string option by expanding the value and assigning it */
2119 /* WARNING this belongs ONLY into a case statement! */
2120 #define SET_STRING_EXP(str)     \
2121                 expand(val, exbuf, sizeof exbuf, e);    \
2122                 newval = sm_pstrdup_x(exbuf);           \
2123                 if (str != NULL)        \
2124                         sm_free(str);   \
2125                 CANONIFY(newval);       \
2126                 str = newval;           \
2127                 break
2128
2129 #define OPTNAME o->o_name == NULL ? "<unknown>" : o->o_name
2130
2131 void
2132 setoption(opt, val, safe, sticky, e)
2133         int opt;
2134         char *val;
2135         bool safe;
2136         bool sticky;
2137         register ENVELOPE *e;
2138 {
2139         register char *p;
2140         register struct optioninfo *o;
2141         char *subopt;
2142         int mid;
2143         bool can_setuid = RunAsUid == 0;
2144         auto char *ep;
2145         char buf[50];
2146         extern bool Warn_Q_option;
2147 #if _FFR_ALLOW_SASLINFO
2148         extern unsigned int SubmitMode;
2149 #endif /* _FFR_ALLOW_SASLINFO */
2150 #if STARTTLS
2151         char *newval;
2152         char exbuf[MAXLINE];
2153 #endif /* STARTTLS */
2154
2155         errno = 0;
2156         if (opt == ' ')
2157         {
2158                 /* full word options */
2159                 struct optioninfo *sel;
2160
2161                 p = strchr(val, '=');
2162                 if (p == NULL)
2163                         p = &val[strlen(val)];
2164                 while (*--p == ' ')
2165                         continue;
2166                 while (*++p == ' ')
2167                         *p = '\0';
2168                 if (p == val)
2169                 {
2170                         syserr("readcf: null option name");
2171                         return;
2172                 }
2173                 if (*p == '=')
2174                         *p++ = '\0';
2175                 while (*p == ' ')
2176                         p++;
2177                 subopt = strchr(val, '.');
2178                 if (subopt != NULL)
2179                         *subopt++ = '\0';
2180                 sel = NULL;
2181                 for (o = OptionTab; o->o_name != NULL; o++)
2182                 {
2183                         if (sm_strncasecmp(o->o_name, val, strlen(val)) != 0)
2184                                 continue;
2185                         if (strlen(o->o_name) == strlen(val))
2186                         {
2187                                 /* completely specified -- this must be it */
2188                                 sel = NULL;
2189                                 break;
2190                         }
2191                         if (sel != NULL)
2192                                 break;
2193                         sel = o;
2194                 }
2195                 if (sel != NULL && o->o_name == NULL)
2196                         o = sel;
2197                 else if (o->o_name == NULL)
2198                 {
2199                         syserr("readcf: unknown option name %s", val);
2200                         return;
2201                 }
2202                 else if (sel != NULL)
2203                 {
2204                         syserr("readcf: ambiguous option name %s (matches %s and %s)",
2205                                 val, sel->o_name, o->o_name);
2206                         return;
2207                 }
2208                 if (strlen(val) != strlen(o->o_name))
2209                 {
2210                         int oldVerbose = Verbose;
2211
2212                         Verbose = 1;
2213                         message("Option %s used as abbreviation for %s",
2214                                 val, o->o_name);
2215                         Verbose = oldVerbose;
2216                 }
2217                 opt = o->o_code;
2218                 val = p;
2219         }
2220         else
2221         {
2222                 for (o = OptionTab; o->o_name != NULL; o++)
2223                 {
2224                         if (o->o_code == opt)
2225                                 break;
2226                 }
2227                 if (o->o_name == NULL)
2228                 {
2229                         syserr("readcf: unknown option name 0x%x", opt & 0xff);
2230                         return;
2231                 }
2232                 subopt = NULL;
2233         }
2234
2235         if (subopt != NULL && !bitset(OI_SUBOPT, o->o_flags))
2236         {
2237                 if (tTd(37, 1))
2238                         sm_dprintf("setoption: %s does not support suboptions, ignoring .%s\n",
2239                                    OPTNAME, subopt);
2240                 subopt = NULL;
2241         }
2242
2243         if (tTd(37, 1))
2244         {
2245                 sm_dprintf(isascii(opt) && isprint(opt) ?
2246                            "setoption %s (%c)%s%s=" :
2247                            "setoption %s (0x%x)%s%s=",
2248                            OPTNAME, opt, subopt == NULL ? "" : ".",
2249                            subopt == NULL ? "" : subopt);
2250                 xputs(val);
2251         }
2252
2253         /*
2254         **  See if this option is preset for us.
2255         */
2256
2257         if (!sticky && bitnset(opt, StickyOpt))
2258         {
2259                 if (tTd(37, 1))
2260                         sm_dprintf(" (ignored)\n");
2261                 return;
2262         }
2263
2264         /*
2265         **  Check to see if this option can be specified by this user.
2266         */
2267
2268         if (!safe && RealUid == 0)
2269                 safe = true;
2270         if (!safe && !bitset(OI_SAFE, o->o_flags))
2271         {
2272                 if (opt != 'M' || (val[0] != 'r' && val[0] != 's'))
2273                 {
2274                         int dp;
2275
2276                         if (tTd(37, 1))
2277                                 sm_dprintf(" (unsafe)");
2278                         dp = drop_privileges(true);
2279                         setstat(dp);
2280                 }
2281         }
2282         if (tTd(37, 1))
2283                 sm_dprintf("\n");
2284
2285         switch (opt & 0xff)
2286         {
2287           case '7':             /* force seven-bit input */
2288                 SevenBitInput = atobool(val);
2289                 break;
2290
2291           case '8':             /* handling of 8-bit input */
2292 #if MIME8TO7
2293                 switch (*val)
2294                 {
2295                   case 'p':             /* pass 8 bit, convert MIME */
2296                         MimeMode = MM_CVTMIME|MM_PASS8BIT;
2297                         break;
2298
2299                   case 'm':             /* convert 8-bit, convert MIME */
2300                         MimeMode = MM_CVTMIME|MM_MIME8BIT;
2301                         break;
2302
2303                   case 's':             /* strict adherence */
2304                         MimeMode = MM_CVTMIME;
2305                         break;
2306
2307 # if 0
2308                   case 'r':             /* reject 8-bit, don't convert MIME */
2309                         MimeMode = 0;
2310                         break;
2311
2312                   case 'j':             /* "just send 8" */
2313                         MimeMode = MM_PASS8BIT;
2314                         break;
2315
2316                   case 'a':             /* encode 8 bit if available */
2317                         MimeMode = MM_MIME8BIT|MM_PASS8BIT|MM_CVTMIME;
2318                         break;
2319
2320                   case 'c':             /* convert 8 bit to MIME, never 7 bit */
2321                         MimeMode = MM_MIME8BIT;
2322                         break;
2323 # endif /* 0 */
2324
2325                   default:
2326                         syserr("Unknown 8-bit mode %c", *val);
2327                         finis(false, true, EX_USAGE);
2328                 }
2329 #else /* MIME8TO7 */
2330                 (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
2331                                      "Warning: Option: %s requires MIME8TO7 support\n",
2332                                      OPTNAME);
2333 #endif /* MIME8TO7 */
2334                 break;
2335
2336           case 'A':             /* set default alias file */
2337                 if (val[0] == '\0')
2338                 {
2339                         char *al;
2340
2341                         SET_OPT_DEFAULT(al, "aliases");
2342                         setalias(al);
2343                 }
2344                 else
2345                         setalias(val);
2346                 break;
2347
2348           case 'a':             /* look N minutes for "@:@" in alias file */
2349                 if (val[0] == '\0')
2350                         SafeAlias = 5 MINUTES;
2351                 else
2352                         SafeAlias = convtime(val, 'm');
2353                 break;
2354
2355           case 'B':             /* substitution for blank character */
2356                 SpaceSub = val[0];
2357                 if (SpaceSub == '\0')
2358                         SpaceSub = ' ';
2359                 break;
2360
2361           case 'b':             /* min blocks free on queue fs/max msg size */
2362                 p = strchr(val, '/');
2363                 if (p != NULL)
2364                 {
2365                         *p++ = '\0';
2366                         MaxMessageSize = atol(p);
2367                 }
2368                 MinBlocksFree = atol(val);
2369                 break;
2370
2371           case 'c':             /* don't connect to "expensive" mailers */
2372                 NoConnect = atobool(val);
2373                 break;
2374
2375           case 'C':             /* checkpoint every N addresses */
2376                 CheckpointInterval = atoi(val);
2377                 break;
2378
2379           case 'd':             /* delivery mode */
2380                 switch (*val)
2381                 {
2382                   case '\0':
2383                         set_delivery_mode(SM_DELIVER, e);
2384                         break;
2385
2386                   case SM_QUEUE:        /* queue only */
2387                   case SM_DEFER:        /* queue only and defer map lookups */
2388                   case SM_DELIVER:      /* do everything */
2389                   case SM_FORK:         /* fork after verification */
2390                         set_delivery_mode(*val, e);
2391                         break;
2392
2393                   default:
2394                         syserr("Unknown delivery mode %c", *val);
2395                         finis(false, true, EX_USAGE);
2396                 }
2397                 break;
2398
2399           case 'E':             /* error message header/header file */
2400                 if (*val != '\0')
2401                         ErrMsgFile = newstr(val);
2402                 break;
2403
2404           case 'e':             /* set error processing mode */
2405                 switch (*val)
2406                 {
2407                   case EM_QUIET:        /* be silent about it */
2408                   case EM_MAIL:         /* mail back */
2409                   case EM_BERKNET:      /* do berknet error processing */
2410                   case EM_WRITE:        /* write back (or mail) */
2411                   case EM_PRINT:        /* print errors normally (default) */
2412                         e->e_errormode = *val;
2413                         break;
2414                 }
2415                 break;
2416
2417           case 'F':             /* file mode */
2418                 FileMode = atooct(val) & 0777;
2419                 break;
2420
2421           case 'f':             /* save Unix-style From lines on front */
2422                 SaveFrom = atobool(val);
2423                 break;
2424
2425           case 'G':             /* match recipients against GECOS field */
2426                 MatchGecos = atobool(val);
2427                 break;
2428
2429           case 'g':             /* default gid */
2430   g_opt:
2431                 if (isascii(*val) && isdigit(*val))
2432                         DefGid = atoi(val);
2433                 else
2434                 {
2435                         register struct group *gr;
2436
2437                         DefGid = -1;
2438                         gr = getgrnam(val);
2439                         if (gr == NULL)
2440                                 syserr("readcf: option %c: unknown group %s",
2441                                         opt, val);
2442                         else
2443                                 DefGid = gr->gr_gid;
2444                 }
2445                 break;
2446
2447           case 'H':             /* help file */
2448                 if (val[0] == '\0')
2449                 {
2450                         SET_OPT_DEFAULT(HelpFile, "helpfile");
2451                 }
2452                 else
2453                 {
2454                         CANONIFY(val);
2455                         HelpFile = newstr(val);
2456                 }
2457                 break;
2458
2459           case 'h':             /* maximum hop count */
2460                 MaxHopCount = atoi(val);
2461                 break;
2462
2463           case 'I':             /* use internet domain name server */
2464 #if NAMED_BIND
2465                 for (p = val; *p != 0; )
2466                 {
2467                         bool clearmode;
2468                         char *q;
2469                         struct resolverflags *rfp;
2470
2471                         while (*p == ' ')
2472                                 p++;
2473                         if (*p == '\0')
2474                                 break;
2475                         clearmode = false;
2476                         if (*p == '-')
2477                                 clearmode = true;
2478                         else if (*p != '+')
2479                                 p--;
2480                         p++;
2481                         q = p;
2482                         while (*p != '\0' && !(isascii(*p) && isspace(*p)))
2483                                 p++;
2484                         if (*p != '\0')
2485                                 *p++ = '\0';
2486                         if (sm_strcasecmp(q, "HasWildcardMX") == 0)
2487                         {
2488                                 HasWildcardMX = !clearmode;
2489                                 continue;
2490                         }
2491                         if (sm_strcasecmp(q, "WorkAroundBrokenAAAA") == 0)
2492                         {
2493                                 WorkAroundBrokenAAAA = !clearmode;
2494                                 continue;
2495                         }
2496                         for (rfp = ResolverFlags; rfp->rf_name != NULL; rfp++)
2497                         {
2498                                 if (sm_strcasecmp(q, rfp->rf_name) == 0)
2499                                         break;
2500                         }
2501                         if (rfp->rf_name == NULL)
2502                                 syserr("readcf: I option value %s unrecognized", q);
2503                         else if (clearmode)
2504                                 _res.options &= ~rfp->rf_bits;
2505                         else
2506                                 _res.options |= rfp->rf_bits;
2507                 }
2508                 if (tTd(8, 2))
2509                         sm_dprintf("_res.options = %x, HasWildcardMX = %d\n",
2510                                    (unsigned int) _res.options, HasWildcardMX);
2511 #else /* NAMED_BIND */
2512                 usrerr("name server (I option) specified but BIND not compiled in");
2513 #endif /* NAMED_BIND */
2514                 break;
2515
2516           case 'i':             /* ignore dot lines in message */
2517                 IgnrDot = atobool(val);
2518                 break;
2519
2520           case 'j':             /* send errors in MIME (RFC 1341) format */
2521                 SendMIMEErrors = atobool(val);
2522                 break;
2523
2524           case 'J':             /* .forward search path */
2525                 CANONIFY(val);
2526                 ForwardPath = newstr(val);
2527                 break;
2528
2529           case 'k':             /* connection cache size */
2530                 MaxMciCache = atoi(val);
2531                 if (MaxMciCache < 0)
2532                         MaxMciCache = 0;
2533                 break;
2534
2535           case 'K':             /* connection cache timeout */
2536                 MciCacheTimeout = convtime(val, 'm');
2537                 break;
2538
2539           case 'l':             /* use Errors-To: header */
2540                 UseErrorsTo = atobool(val);
2541                 break;
2542
2543           case 'L':             /* log level */
2544                 if (safe || LogLevel < atoi(val))
2545                         LogLevel = atoi(val);
2546                 break;
2547
2548           case 'M':             /* define macro */
2549                 sticky = false;
2550                 mid = macid_parse(val, &ep);
2551                 if (mid == 0)
2552                         break;
2553                 p = newstr(ep);
2554                 if (!safe)
2555                         cleanstrcpy(p, p, MAXNAME);
2556                 macdefine(&CurEnv->e_macro, A_TEMP, mid, p);
2557                 break;
2558
2559           case 'm':             /* send to me too */
2560                 MeToo = atobool(val);
2561                 break;
2562
2563           case 'n':             /* validate RHS in newaliases */
2564                 CheckAliases = atobool(val);
2565                 break;
2566
2567             /* 'N' available -- was "net name" */
2568
2569           case 'O':             /* daemon options */
2570                 if (!setdaemonoptions(val))
2571                         syserr("too many daemons defined (%d max)", MAXDAEMONS);
2572                 break;
2573
2574           case 'o':             /* assume old style headers */
2575                 if (atobool(val))
2576                         CurEnv->e_flags |= EF_OLDSTYLE;
2577                 else
2578                         CurEnv->e_flags &= ~EF_OLDSTYLE;
2579                 break;
2580
2581           case 'p':             /* select privacy level */
2582                 p = val;
2583                 for (;;)
2584                 {
2585                         register struct prival *pv;
2586                         extern struct prival PrivacyValues[];
2587
2588                         while (isascii(*p) && (isspace(*p) || ispunct(*p)))
2589                                 p++;
2590                         if (*p == '\0')
2591                                 break;
2592                         val = p;
2593                         while (isascii(*p) && isalnum(*p))
2594                                 p++;
2595                         if (*p != '\0')
2596                                 *p++ = '\0';
2597
2598                         for (pv = PrivacyValues; pv->pv_name != NULL; pv++)
2599                         {
2600                                 if (sm_strcasecmp(val, pv->pv_name) == 0)
2601                                         break;
2602                         }
2603                         if (pv->pv_name == NULL)
2604                                 syserr("readcf: Op line: %s unrecognized", val);
2605                         else
2606                                 PrivacyFlags |= pv->pv_flag;
2607                 }
2608                 sticky = false;
2609                 break;
2610
2611           case 'P':             /* postmaster copy address for returned mail */
2612                 PostMasterCopy = newstr(val);
2613                 break;
2614
2615           case 'q':             /* slope of queue only function */
2616                 QueueFactor = atoi(val);
2617                 break;
2618
2619           case 'Q':             /* queue directory */
2620                 if (val[0] == '\0')
2621                 {
2622                         QueueDir = "mqueue";
2623                 }
2624                 else
2625                 {
2626                         QueueDir = newstr(val);
2627                 }
2628                 if (RealUid != 0 && !safe)
2629                         Warn_Q_option = true;
2630                 break;
2631
2632           case 'R':             /* don't prune routes */
2633                 DontPruneRoutes = atobool(val);
2634                 break;
2635
2636           case 'r':             /* read timeout */
2637                 if (subopt == NULL)
2638                         inittimeouts(val, sticky);
2639                 else
2640                         settimeout(subopt, val, sticky);
2641                 break;
2642
2643           case 'S':             /* status file */
2644                 if (val[0] == '\0')
2645                 {
2646                         SET_OPT_DEFAULT(StatFile, "statistics");
2647                 }
2648                 else
2649                 {
2650                         CANONIFY(val);
2651                         StatFile = newstr(val);
2652                 }
2653                 break;
2654
2655           case 's':             /* be super safe, even if expensive */
2656                 if (tolower(*val) == 'i')
2657                         SuperSafe = SAFE_INTERACTIVE;
2658                 else
2659                         SuperSafe = atobool(val) ? SAFE_REALLY : SAFE_NO;
2660                 break;
2661
2662           case 'T':             /* queue timeout */
2663                 p = strchr(val, '/');
2664                 if (p != NULL)
2665                 {
2666                         *p++ = '\0';
2667                         settimeout("queuewarn", p, sticky);
2668                 }
2669                 settimeout("queuereturn", val, sticky);
2670                 break;
2671
2672           case 't':             /* time zone name */
2673                 TimeZoneSpec = newstr(val);
2674                 break;
2675
2676           case 'U':             /* location of user database */
2677                 UdbSpec = newstr(val);
2678                 break;
2679
2680           case 'u':             /* set default uid */
2681                 for (p = val; *p != '\0'; p++)
2682                 {
2683 # if _FFR_DOTTED_USERNAMES
2684                         if (*p == '/' || *p == ':')
2685 # else /* _FFR_DOTTED_USERNAMES */
2686                         if (*p == '.' || *p == '/' || *p == ':')
2687 # endif /* _FFR_DOTTED_USERNAMES */
2688                         {
2689                                 *p++ = '\0';
2690                                 break;
2691                         }
2692                 }
2693                 if (isascii(*val) && isdigit(*val))
2694                 {
2695                         DefUid = atoi(val);
2696                         setdefuser();
2697                 }
2698                 else
2699                 {
2700                         register struct passwd *pw;
2701
2702                         DefUid = -1;
2703                         pw = sm_getpwnam(val);
2704                         if (pw == NULL)
2705                         {
2706                                 syserr("readcf: option u: unknown user %s", val);
2707                                 break;
2708                         }
2709                         else
2710                         {
2711                                 DefUid = pw->pw_uid;
2712                                 DefGid = pw->pw_gid;
2713                                 DefUser = newstr(pw->pw_name);
2714                         }
2715                 }
2716
2717 # ifdef UID_MAX
2718                 if (DefUid > UID_MAX)
2719                 {
2720                         syserr("readcf: option u: uid value (%ld) > UID_MAX (%ld); ignored",
2721                                 (long)DefUid, (long)UID_MAX);
2722                         break;
2723                 }
2724 # endif /* UID_MAX */
2725
2726                 /* handle the group if it is there */
2727                 if (*p == '\0')
2728                         break;
2729                 val = p;
2730                 goto g_opt;
2731
2732           case 'V':             /* fallback MX host */
2733                 if (val[0] != '\0')
2734                         FallBackMX = newstr(val);
2735                 break;
2736
2737           case 'v':             /* run in verbose mode */
2738                 Verbose = atobool(val) ? 1 : 0;
2739                 break;
2740
2741           case 'w':             /* if we are best MX, try host directly */
2742                 TryNullMXList = atobool(val);
2743                 break;
2744
2745             /* 'W' available -- was wizard password */
2746
2747           case 'x':             /* load avg at which to auto-queue msgs */
2748                 QueueLA = atoi(val);
2749                 break;
2750
2751           case 'X':     /* load avg at which to auto-reject connections */
2752                 RefuseLA = atoi(val);
2753                 break;
2754
2755           case O_DELAY_LA:      /* load avg at which to delay connections */
2756                 DelayLA = atoi(val);
2757                 break;
2758
2759           case 'y':             /* work recipient factor */
2760                 WkRecipFact = atoi(val);
2761                 break;
2762
2763           case 'Y':             /* fork jobs during queue runs */
2764                 ForkQueueRuns = atobool(val);
2765                 break;
2766
2767           case 'z':             /* work message class factor */
2768                 WkClassFact = atoi(val);
2769                 break;
2770
2771           case 'Z':             /* work time factor */
2772                 WkTimeFact = atoi(val);
2773                 break;
2774
2775
2776 #if _FFR_QUEUE_GROUP_SORTORDER
2777         /* coordinate this with makequeue() */
2778 #endif /* _FFR_QUEUE_GROUP_SORTORDER */
2779           case O_QUEUESORTORD:  /* queue sorting order */
2780                 switch (*val)
2781                 {
2782                   case 'f':     /* File Name */
2783                   case 'F':
2784                         QueueSortOrder = QSO_BYFILENAME;
2785                         break;
2786
2787                   case 'h':     /* Host first */
2788                   case 'H':
2789                         QueueSortOrder = QSO_BYHOST;
2790                         break;
2791
2792                   case 'm':     /* Modification time */
2793                   case 'M':
2794                         QueueSortOrder = QSO_BYMODTIME;
2795                         break;
2796
2797                   case 'p':     /* Priority order */
2798                   case 'P':
2799                         QueueSortOrder = QSO_BYPRIORITY;
2800                         break;
2801
2802                   case 't':     /* Submission time */
2803                   case 'T':
2804                         QueueSortOrder = QSO_BYTIME;
2805                         break;
2806
2807                   case 'r':     /* Random */
2808                   case 'R':
2809                         QueueSortOrder = QSO_RANDOM;
2810                         break;
2811
2812 #if _FFR_RHS
2813                   case 's':     /* Shuffled host name */
2814                   case 'S':
2815                         QueueSortOrder = QSO_BYSHUFFLE;
2816                         break;
2817 #endif /* _FFR_RHS */
2818
2819                   default:
2820                         syserr("Invalid queue sort order \"%s\"", val);
2821                 }
2822                 break;
2823
2824 #if _FFR_QUEUEDELAY
2825           case O_QUEUEDELAY:    /* queue delay algorithm */
2826                 switch (*val)
2827                 {
2828                   case 'e':     /* exponential */
2829                   case 'E':
2830                         QueueAlg = QD_EXP;
2831                         QueueInitDelay = 10 MINUTES;
2832                         QueueMaxDelay = 2 HOURS;
2833                         p = strchr(val, '/');
2834                         if (p != NULL)
2835                         {
2836                                 char *q;
2837
2838                                 *p++ = '\0';
2839                                 q = strchr(p, '/');
2840                                 if (q != NULL)
2841                                         *q++ = '\0';
2842                                 QueueInitDelay = convtime(p, 's');
2843                                 if (q != NULL)
2844                                 {
2845                                         QueueMaxDelay = convtime(q, 's');
2846                                 }
2847                         }
2848                         break;
2849
2850                   case 'l':     /* linear */
2851                   case 'L':
2852                         QueueAlg = QD_LINEAR;
2853                         break;
2854
2855                   default:
2856                         syserr("Invalid queue delay algorithm \"%s\"", val);
2857                 }
2858                 break;
2859 #endif /* _FFR_QUEUEDELAY */
2860
2861           case O_HOSTSFILE:     /* pathname of /etc/hosts file */
2862                 CANONIFY(val);
2863                 HostsFile = newstr(val);
2864                 break;
2865
2866           case O_MQA:           /* minimum queue age between deliveries */
2867                 MinQueueAge = convtime(val, 'm');
2868                 break;
2869
2870           case O_DEFCHARSET:    /* default character set for mimefying */
2871                 DefaultCharSet = newstr(denlstring(val, true, true));
2872                 break;
2873
2874           case O_SSFILE:        /* service switch file */
2875                 CANONIFY(val);
2876                 ServiceSwitchFile = newstr(val);
2877                 break;
2878
2879           case O_DIALDELAY:     /* delay for dial-on-demand operation */
2880                 DialDelay = convtime(val, 's');
2881                 break;
2882
2883           case O_NORCPTACTION:  /* what to do if no recipient */
2884                 if (sm_strcasecmp(val, "none") == 0)
2885                         NoRecipientAction = NRA_NO_ACTION;
2886                 else if (sm_strcasecmp(val, "add-to") == 0)
2887                         NoRecipientAction = NRA_ADD_TO;
2888                 else if (sm_strcasecmp(val, "add-apparently-to") == 0)
2889                         NoRecipientAction = NRA_ADD_APPARENTLY_TO;
2890                 else if (sm_strcasecmp(val, "add-bcc") == 0)
2891                         NoRecipientAction = NRA_ADD_BCC;
2892                 else if (sm_strcasecmp(val, "add-to-undisclosed") == 0)
2893                         NoRecipientAction = NRA_ADD_TO_UNDISCLOSED;
2894                 else
2895                         syserr("Invalid NoRecipientAction: %s", val);
2896                 break;
2897
2898           case O_SAFEFILEENV:   /* chroot() environ for writing to files */
2899                 if (*val == '\0')
2900                         break;
2901
2902                 /* strip trailing slashes */
2903                 p = val + strlen(val) - 1;
2904                 while (p >= val && *p == '/')
2905                         *p-- = '\0';
2906
2907                 if (*val == '\0')
2908                         break;
2909
2910                 SafeFileEnv = newstr(val);
2911                 break;
2912
2913           case O_MAXMSGSIZE:    /* maximum message size */
2914                 MaxMessageSize = atol(val);
2915                 break;
2916
2917           case O_COLONOKINADDR: /* old style handling of colon addresses */
2918                 ColonOkInAddr = atobool(val);
2919                 break;
2920
2921           case O_MAXQUEUERUN:   /* max # of jobs in a single queue run */
2922                 MaxQueueRun = atoi(val);
2923                 break;
2924
2925           case O_MAXCHILDREN:   /* max # of children of daemon */
2926                 MaxChildren = atoi(val);
2927                 break;
2928
2929           case O_MAXQUEUECHILDREN: /* max # of children of daemon */
2930                 MaxQueueChildren = atoi(val);
2931                 break;
2932
2933           case O_MAXRUNNERSPERQUEUE: /* max # runners in a queue group */
2934                 MaxRunnersPerQueue = atoi(val);
2935                 break;
2936
2937           case O_NICEQUEUERUN:          /* nice queue runs */
2938 #if !HASNICE
2939                 (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
2940                                      "Warning: NiceQueueRun set on system that doesn't support nice()\n");
2941 #endif /* !HASNICE */
2942
2943                 /* XXX do we want to check the range? > 0 ? */
2944                 NiceQueueRun = atoi(val);
2945                 break;
2946
2947           case O_SHMKEY:                /* shared memory key */
2948 #if SM_CONF_SHM
2949                 ShmKey = atol(val);
2950 #else /* SM_CONF_SHM */
2951                 (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
2952                                      "Warning: Option: %s requires shared memory support (-DSM_CONF_SHM)\n",
2953                                      OPTNAME);
2954 #endif /* SM_CONF_SHM */
2955                 break;
2956
2957 #if _FFR_SELECT_SHM
2958           case O_SHMKEYFILE:            /* shared memory key file */
2959 # if SM_CONF_SHM
2960                 SET_STRING_EXP(ShmKeyFile);
2961 # else /* SM_CONF_SHM */
2962                 (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
2963                                      "Warning: Option: %s requires shared memory support (-DSM_CONF_SHM)\n",
2964                                      OPTNAME);
2965                 break;
2966 # endif /* SM_CONF_SHM */
2967 #endif /* _FFR_SELECT_SHM */
2968
2969 #if _FFR_MAX_FORWARD_ENTRIES
2970           case O_MAXFORWARD:    /* max # of forward entries */
2971                 MaxForwardEntries = atoi(val);
2972                 break;
2973 #endif /* _FFR_MAX_FORWARD_ENTRIES */
2974
2975           case O_KEEPCNAMES:    /* don't expand CNAME records */
2976                 DontExpandCnames = atobool(val);
2977                 break;
2978
2979           case O_MUSTQUOTE:     /* must quote these characters in phrases */
2980                 (void) sm_strlcpy(buf, "@,;:\\()[]", sizeof buf);
2981                 if (strlen(val) < sizeof buf - 10)
2982                         (void) sm_strlcat(buf, val, sizeof buf);
2983                 else
2984                         (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
2985                                              "Warning: MustQuoteChars too long, ignored.\n");
2986                 MustQuoteChars = newstr(buf);
2987                 break;
2988
2989           case O_SMTPGREETING:  /* SMTP greeting message (old $e macro) */
2990                 SmtpGreeting = newstr(munchstring(val, NULL, '\0'));
2991                 break;
2992
2993           case O_UNIXFROM:      /* UNIX From_ line (old $l macro) */
2994                 UnixFromLine = newstr(munchstring(val, NULL, '\0'));
2995                 break;
2996
2997           case O_OPCHARS:       /* operator characters (old $o macro) */
2998                 if (OperatorChars != NULL)
2999                         (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
3000                                              "Warning: OperatorChars is being redefined.\n         It should only be set before ruleset definitions.\n");
3001                 OperatorChars = newstr(munchstring(val, NULL, '\0'));
3002                 break;
3003
3004           case O_DONTINITGRPS:  /* don't call initgroups(3) */
3005                 DontInitGroups = atobool(val);
3006                 break;
3007
3008           case O_SLFH:          /* make sure from fits on one line */
3009                 SingleLineFromHeader = atobool(val);
3010                 break;
3011
3012           case O_ABH:           /* allow HELO commands with syntax errors */
3013                 AllowBogusHELO = atobool(val);
3014                 break;
3015
3016           case O_CONNTHROT:     /* connection rate throttle */
3017                 ConnRateThrottle = atoi(val);
3018                 break;
3019
3020           case O_UGW:           /* group writable files are unsafe */
3021                 if (!atobool(val))
3022                 {
3023                         setbitn(DBS_GROUPWRITABLEFORWARDFILESAFE,
3024                                 DontBlameSendmail);
3025                         setbitn(DBS_GROUPWRITABLEINCLUDEFILESAFE,
3026                                 DontBlameSendmail);
3027                 }
3028                 break;
3029
3030           case O_DBLBOUNCE:     /* address to which to send double bounces */
3031                 DoubleBounceAddr = newstr(val);
3032                 break;
3033
3034           case O_HSDIR:         /* persistent host status directory */
3035                 if (val[0] != '\0')
3036                 {
3037                         CANONIFY(val);
3038                         HostStatDir = newstr(val);
3039                 }
3040                 break;
3041
3042           case O_SINGTHREAD:    /* single thread deliveries (requires hsdir) */
3043                 SingleThreadDelivery = atobool(val);
3044                 break;
3045
3046           case O_RUNASUSER:     /* run bulk of code as this user */
3047                 for (p = val; *p != '\0'; p++)
3048                 {
3049 # if _FFR_DOTTED_USERNAMES
3050                         if (*p == '/' || *p == ':')
3051 # else /* _FFR_DOTTED_USERNAMES */
3052                         if (*p == '.' || *p == '/' || *p == ':')
3053 # endif /* _FFR_DOTTED_USERNAMES */
3054                         {
3055                                 *p++ = '\0';
3056                                 break;
3057                         }
3058                 }
3059                 if (isascii(*val) && isdigit(*val))
3060                 {
3061                         if (can_setuid)
3062                                 RunAsUid = atoi(val);
3063                 }
3064                 else
3065                 {
3066                         register struct passwd *pw;
3067
3068                         pw = sm_getpwnam(val);
3069                         if (pw == NULL)
3070                         {
3071                                 syserr("readcf: option RunAsUser: unknown user %s", val);
3072                                 break;
3073                         }
3074                         else if (can_setuid)
3075                         {
3076                                 if (*p == '\0')
3077                                         RunAsUserName = newstr(val);
3078                                 RunAsUid = pw->pw_uid;
3079                                 RunAsGid = pw->pw_gid;
3080                         }
3081                         else if (EffGid == pw->pw_gid)
3082                                 RunAsGid = pw->pw_gid;
3083                         else if (UseMSP && *p == '\0')
3084                                 (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
3085                                                      "WARNING: RunAsGid for MSP ignored, check group ids (egid=%d, want=%d)\n",
3086                                                      (int) EffGid,
3087                                                      (int) pw->pw_gid);
3088                 }
3089 # ifdef UID_MAX
3090                 if (RunAsUid > UID_MAX)
3091                 {
3092                         syserr("readcf: option RunAsUser: uid value (%ld) > UID_MAX (%ld); ignored",
3093                                 (long) RunAsUid, (long) UID_MAX);
3094                         break;
3095                 }
3096 # endif /* UID_MAX */
3097                 if (*p != '\0')
3098                 {
3099                         if (isascii(*p) && isdigit(*p))
3100                         {
3101                                 gid_t runasgid;
3102
3103                                 runasgid = (gid_t) atoi(p);
3104                                 if (can_setuid || EffGid == runasgid)
3105                                         RunAsGid = runasgid;
3106                                 else if (UseMSP)
3107                                         (void) sm_io_fprintf(smioout,
3108                                                              SM_TIME_DEFAULT,
3109                                                              "WARNING: RunAsGid for MSP ignored, check group ids (egid=%d, want=%d)\n",
3110                                                              (int) EffGid,
3111                                                              (int) runasgid);
3112                         }
3113                         else
3114                         {
3115                                 register struct group *gr;
3116
3117                                 gr = getgrnam(p);
3118                                 if (gr == NULL)
3119                                         syserr("readcf: option RunAsUser: unknown group %s",
3120                                                 p);
3121                                 else if (can_setuid || EffGid == gr->gr_gid)
3122                                         RunAsGid = gr->gr_gid;
3123                                 else if (UseMSP)
3124                                         (void) sm_io_fprintf(smioout,
3125                                                              SM_TIME_DEFAULT,
3126                                                              "WARNING: RunAsGid for MSP ignored, check group ids (egid=%d, want=%d)\n",
3127                                                              (int) EffGid,
3128                                                              (int) gr->gr_gid);
3129                         }
3130                 }
3131                 if (tTd(47, 5))
3132                         sm_dprintf("readcf: RunAsUser = %d:%d\n",
3133                                    (int) RunAsUid, (int) RunAsGid);
3134                 break;
3135
3136           case O_DSN_RRT:
3137                 RrtImpliesDsn = atobool(val);
3138                 break;
3139
3140           case O_PIDFILE:
3141                 PSTRSET(PidFile, val);
3142                 break;
3143
3144           case O_DONTBLAMESENDMAIL:
3145                 p = val;
3146                 for (;;)
3147                 {
3148                         register struct dbsval *dbs;
3149                         extern struct dbsval DontBlameSendmailValues[];
3150
3151                         while (isascii(*p) && (isspace(*p) || ispunct(*p)))
3152                                 p++;
3153                         if (*p == '\0')
3154                                 break;
3155                         val = p;
3156                         while (isascii(*p) && isalnum(*p))
3157                                 p++;
3158                         if (*p != '\0')
3159                                 *p++ = '\0';
3160
3161                         for (dbs = DontBlameSendmailValues;
3162                              dbs->dbs_name != NULL; dbs++)
3163                         {
3164                                 if (sm_strcasecmp(val, dbs->dbs_name) == 0)
3165                                         break;
3166                         }
3167                         if (dbs->dbs_name == NULL)
3168                                 syserr("readcf: DontBlameSendmail option: %s unrecognized", val);
3169                         else if (dbs->dbs_flag == DBS_SAFE)
3170                                 clrbitmap(DontBlameSendmail);
3171                         else
3172                                 setbitn(dbs->dbs_flag, DontBlameSendmail);
3173                 }
3174                 sticky = false;
3175                 break;
3176
3177           case O_DPI:
3178                 if (sm_strcasecmp(val, "loopback") == 0)
3179                         DontProbeInterfaces = DPI_SKIPLOOPBACK;
3180                 else if (atobool(val))
3181                         DontProbeInterfaces = DPI_PROBENONE;
3182                 else
3183                         DontProbeInterfaces = DPI_PROBEALL;
3184                 break;
3185
3186           case O_MAXRCPT:
3187                 MaxRcptPerMsg = atoi(val);
3188                 break;
3189
3190           case O_RCPTTHROT:
3191                 BadRcptThrottle = atoi(val);
3192                 break;
3193
3194           case O_DEADLETTER:
3195                 CANONIFY(val);
3196                 PSTRSET(DeadLetterDrop, val);
3197                 break;
3198
3199 #if _FFR_DONTLOCKFILESFORREAD_OPTION
3200           case O_DONTLOCK:
3201                 DontLockReadFiles = atobool(val);
3202                 break;
3203 #endif /* _FFR_DONTLOCKFILESFORREAD_OPTION */
3204
3205           case O_MAXALIASRCSN:
3206                 MaxAliasRecursion = atoi(val);
3207                 break;
3208
3209           case O_CNCTONLYTO:
3210                 /* XXX should probably use gethostbyname */
3211 #if NETINET || NETINET6
3212                 ConnectOnlyTo.sa.sa_family = AF_UNSPEC;
3213 # if NETINET6
3214                 if (anynet_pton(AF_INET6, val,
3215                                 &ConnectOnlyTo.sin6.sin6_addr) != 1)
3216                         ConnectOnlyTo.sa.sa_family = AF_INET6;
3217                 else
3218 # endif /* NETINET6 */
3219 # if NETINET
3220                 {
3221                         ConnectOnlyTo.sin.sin_addr.s_addr = inet_addr(val);
3222                         if (ConnectOnlyTo.sin.sin_addr.s_addr != INADDR_NONE)
3223                                 ConnectOnlyTo.sa.sa_family = AF_INET;
3224                 }
3225
3226 # endif /* NETINET */
3227                 if (ConnectOnlyTo.sa.sa_family == AF_UNSPEC)
3228                 {
3229                         syserr("readcf: option ConnectOnlyTo: invalid IP address %s",
3230                                val);
3231                         break;
3232                 }
3233 #endif /* NETINET || NETINET6 */
3234                 break;
3235
3236           case O_TRUSTUSER:
3237 # if !HASFCHOWN && !defined(_FFR_DROP_TRUSTUSER_WARNING)
3238                 if (!UseMSP)
3239                         (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
3240                                              "readcf: option TrustedUser may cause problems on systems\n        which do not support fchown() if UseMSP is not set.\n");
3241 # endif /* !HASFCHOWN && !defined(_FFR_DROP_TRUSTUSER_WARNING) */
3242                 if (isascii(*val) && isdigit(*val))
3243                         TrustedUid = atoi(val);
3244                 else
3245                 {
3246                         register struct passwd *pw;
3247
3248                         TrustedUid = 0;
3249                         pw = sm_getpwnam(val);
3250                         if (pw == NULL)
3251                         {
3252                                 syserr("readcf: option TrustedUser: unknown user %s", val);
3253                                 break;
3254                         }
3255                         else
3256                                 TrustedUid = pw->pw_uid;
3257                 }
3258
3259 # ifdef UID_MAX
3260                 if (TrustedUid > UID_MAX)
3261                 {
3262                         syserr("readcf: option TrustedUser: uid value (%ld) > UID_MAX (%ld)",
3263                                 (long) TrustedUid, (long) UID_MAX);
3264                         TrustedUid = 0;
3265                 }
3266 # endif /* UID_MAX */
3267                 break;
3268
3269           case O_MAXMIMEHDRLEN:
3270                 p = strchr(val, '/');
3271                 if (p != NULL)
3272                         *p++ = '\0';
3273                 MaxMimeHeaderLength = atoi(val);
3274                 if (p != NULL && *p != '\0')
3275                         MaxMimeFieldLength = atoi(p);
3276                 else
3277                         MaxMimeFieldLength = MaxMimeHeaderLength / 2;
3278
3279                 if (MaxMimeHeaderLength < 0)
3280                         MaxMimeHeaderLength = 0;
3281                 else if (MaxMimeHeaderLength < 128)
3282                         (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
3283                                              "Warning: MaxMimeHeaderLength: header length limit set lower than 128\n");
3284
3285                 if (MaxMimeFieldLength < 0)
3286                         MaxMimeFieldLength = 0;
3287                 else if (MaxMimeFieldLength < 40)
3288                         (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
3289                                              "Warning: MaxMimeHeaderLength: field length limit set lower than 40\n");
3290                 break;
3291
3292           case O_CONTROLSOCKET:
3293                 PSTRSET(ControlSocketName, val);
3294                 break;
3295
3296           case O_MAXHDRSLEN:
3297                 MaxHeadersLength = atoi(val);
3298
3299                 if (MaxHeadersLength > 0 &&
3300                     MaxHeadersLength < (MAXHDRSLEN / 2))
3301                         (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
3302                                              "Warning: MaxHeadersLength: headers length limit set lower than %d\n",
3303                                              (MAXHDRSLEN / 2));
3304                 break;
3305
3306           case O_PROCTITLEPREFIX:
3307                 PSTRSET(ProcTitlePrefix, val);
3308                 break;
3309
3310 #if SASL
3311           case O_SASLINFO:
3312 # if _FFR_ALLOW_SASLINFO
3313                 /*
3314                 **  Allow users to select their own authinfo file
3315                 **  under certain circumstances, otherwise just ignore
3316                 **  the option.  If the option isn't ignored, several
3317                 **  commands don't work very well, e.g., mailq.
3318                 **  However, this is not a "perfect" solution.
3319                 **  If mail is queued, the authentication info
3320                 **  will not be used in subsequent delivery attempts.
3321                 **  If we really want to support this, then it has
3322                 **  to be stored in the queue file.
3323                 */
3324                 if (!bitset(SUBMIT_MSA, SubmitMode) && RealUid != 0 &&
3325                     RunAsUid != RealUid)
3326                         break;
3327 # endif /* _FFR_ALLOW_SASLINFO */
3328                 PSTRSET(SASLInfo, val);
3329                 break;
3330
3331           case O_SASLMECH:
3332                 if (AuthMechanisms != NULL)
3333                         sm_free(AuthMechanisms); /* XXX */
3334                 if (*val != '\0')
3335                         AuthMechanisms = newstr(val);
3336                 else
3337                         AuthMechanisms = NULL;
3338                 break;
3339
3340           case O_SASLOPTS:
3341                 while (val != NULL && *val != '\0')
3342                 {
3343                         switch (*val)
3344                         {
3345                           case 'A':
3346                                 SASLOpts |= SASL_AUTH_AUTH;
3347                                 break;
3348
3349                           case 'a':
3350                                 SASLOpts |= SASL_SEC_NOACTIVE;
3351                                 break;
3352
3353                           case 'c':
3354                                 SASLOpts |= SASL_SEC_PASS_CREDENTIALS;
3355                                 break;
3356
3357                           case 'd':
3358                                 SASLOpts |= SASL_SEC_NODICTIONARY;
3359                                 break;
3360
3361                           case 'f':
3362                                 SASLOpts |= SASL_SEC_FORWARD_SECRECY;
3363                                 break;
3364
3365 # if _FFR_SASL_OPT_M
3366 /* to be activated in 8.13 */
3367 #  if SASL >= 20101
3368                           case 'm':
3369                                 SASLOpts |= SASL_SEC_MUTUAL_AUTH;
3370                                 break;
3371 #  endif /* SASL >= 20101 */
3372 # endif /* _FFR_SASL_OPT_M */
3373
3374                           case 'p':
3375                                 SASLOpts |= SASL_SEC_NOPLAINTEXT;
3376                                 break;
3377
3378                           case 'y':
3379                                 SASLOpts |= SASL_SEC_NOANONYMOUS;
3380                                 break;
3381
3382                           case ' ':     /* ignore */
3383                           case '\t':    /* ignore */
3384                           case ',':     /* ignore */
3385                                 break;
3386
3387                           default:
3388                                 (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
3389                                                      "Warning: Option: %s unknown parameter '%c'\n",
3390                                                      OPTNAME,
3391                                                      (isascii(*val) &&
3392                                                         isprint(*val))
3393                                                         ? *val : '?');
3394                                 break;
3395                         }
3396                         ++val;
3397                         val = strpbrk(val, ", \t");
3398                         if (val != NULL)
3399                                 ++val;
3400                 }
3401                 break;
3402
3403           case O_SASLBITS:
3404                 MaxSLBits = atoi(val);
3405                 break;
3406
3407 #else /* SASL */
3408           case O_SASLINFO:
3409           case O_SASLMECH:
3410           case O_SASLOPTS:
3411           case O_SASLBITS:
3412                 (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
3413                                      "Warning: Option: %s requires SASL support (-DSASL)\n",
3414                                      OPTNAME);
3415                 break;
3416 #endif /* SASL */
3417
3418 #if STARTTLS
3419           case O_SRVCERTFILE:
3420                 SET_STRING_EXP(SrvCertFile);
3421           case O_SRVKEYFILE:
3422                 SET_STRING_EXP(SrvKeyFile);
3423           case O_CLTCERTFILE:
3424                 SET_STRING_EXP(CltCertFile);
3425           case O_CLTKEYFILE:
3426                 SET_STRING_EXP(CltKeyFile);
3427           case O_CACERTFILE:
3428                 SET_STRING_EXP(CACertFile);
3429           case O_CACERTPATH:
3430                 SET_STRING_EXP(CACertPath);
3431           case O_DHPARAMS:
3432                 SET_STRING_EXP(DHParams);
3433 # if _FFR_TLS_1
3434           case O_DHPARAMS5:
3435                 SET_STRING_EXP(DHParams5);
3436           case O_CIPHERLIST:
3437                 SET_STRING_EXP(CipherList);
3438 # endif /* _FFR_TLS_1 */
3439
3440         /*
3441         **  XXX How about options per daemon/client instead of globally?
3442         **  This doesn't work well for some options, e.g., no server cert,
3443         **  but fine for others.
3444         **
3445         **  XXX Some people may want different certs per server.
3446         **
3447         **  See also srvfeatures()
3448         */
3449
3450           case O_TLS_SRV_OPTS:
3451                 while (val != NULL && *val != '\0')
3452                 {
3453                         switch (*val)
3454                         {
3455                           case 'V':
3456                                 TLS_Srv_Opts |= TLS_I_NO_VRFY;
3457                                 break;
3458 # if _FFR_TLS_1
3459                         /*
3460                         **  Server without a cert? That works only if
3461                         **  AnonDH is enabled as cipher, which is not in the
3462                         **  default list. Hence the CipherList option must
3463                         **  be available. Moreover: which clients support this
3464                         **  besides sendmail with this setting?
3465                         */
3466
3467                           case 'C':
3468                                 TLS_Srv_Opts &= ~TLS_I_SRV_CERT;
3469                                 break;
3470 # endif /* _FFR_TLS_1 */
3471                           case ' ':     /* ignore */
3472                           case '\t':    /* ignore */
3473                           case ',':     /* ignore */
3474                                 break;
3475                           default:
3476                                 (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
3477                                                      "Warning: Option: %s unknown parameter '%c'\n",
3478                                                      OPTNAME,
3479                                                      (isascii(*val) &&
3480                                                         isprint(*val))
3481                                                         ? *val : '?');
3482                                 break;
3483                         }
3484                         ++val;
3485                         val = strpbrk(val, ", \t");
3486                         if (val != NULL)
3487                                 ++val;
3488                 }
3489                 break;
3490
3491           case O_RANDFILE:
3492                 PSTRSET(RandFile, val);
3493                 break;
3494
3495 #else /* STARTTLS */
3496           case O_SRVCERTFILE:
3497           case O_SRVKEYFILE:
3498           case O_CLTCERTFILE:
3499           case O_CLTKEYFILE:
3500           case O_CACERTFILE:
3501           case O_CACERTPATH:
3502           case O_DHPARAMS:
3503 # if _FFR_TLS_1
3504           case O_DHPARAMS5:
3505           case O_CIPHERLIST:
3506 # endif /* _FFR_TLS_1 */
3507           case O_RANDFILE:
3508                 (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
3509                                      "Warning: Option: %s requires TLS support\n",
3510                                      OPTNAME);
3511                 break;
3512
3513 #endif /* STARTTLS */
3514
3515           case O_CLIENTPORT:
3516                 setclientoptions(val);
3517                 break;
3518
3519           case O_DF_BUFSIZE:
3520                 DataFileBufferSize = atoi(val);
3521                 break;
3522
3523           case O_XF_BUFSIZE:
3524                 XscriptFileBufferSize = atoi(val);
3525                 break;
3526
3527           case O_LDAPDEFAULTSPEC:
3528 #if LDAPMAP
3529                 ldapmap_set_defaults(val);
3530 #else /* LDAPMAP */
3531                 (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
3532                                      "Warning: Option: %s requires LDAP support (-DLDAPMAP)\n",
3533                                      OPTNAME);
3534 #endif /* LDAPMAP */
3535                 break;
3536
3537           case O_INPUTMILTER:
3538 #if MILTER
3539                 InputFilterList = newstr(val);
3540 #else /* MILTER */
3541                 (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
3542                                      "Warning: Option: %s requires Milter support (-DMILTER)\n",
3543                                      OPTNAME);
3544 #endif /* MILTER */
3545                 break;
3546
3547           case O_MILTER:
3548 #if MILTER
3549                 milter_set_option(subopt, val, sticky);
3550 #else /* MILTER */
3551                 (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
3552                                      "Warning: Option: %s requires Milter support (-DMILTER)\n",
3553                                      OPTNAME);
3554 #endif /* MILTER */
3555                 break;
3556
3557           case O_QUEUE_FILE_MODE:       /* queue file mode */
3558                 QueueFileMode = atooct(val) & 0777;
3559                 break;
3560
3561           case O_DLVR_MIN:      /* deliver by minimum time */
3562                 DeliverByMin = convtime(val, 's');
3563                 break;
3564
3565           /* modifiers {daemon_flags} for direct submissions */
3566           case O_DIRECTSUBMODIFIERS:
3567                 {
3568                         BITMAP256 m;    /* ignored */
3569                         extern ENVELOPE BlankEnvelope;
3570
3571                         macdefine(&BlankEnvelope.e_macro, A_PERM,
3572                                   macid("{daemon_flags}"),
3573                                   getmodifiers(val, m));
3574                 }
3575                 break;
3576
3577           case O_FASTSPLIT:
3578                 FastSplit = atoi(val);
3579                 break;
3580
3581           case O_MBDB:
3582                 Mbdb = newstr(val);
3583                 break;
3584
3585           case O_MSQ:
3586                 UseMSP = atobool(val);
3587                 break;
3588
3589 #if _FFR_SOFT_BOUNCE
3590           case O_SOFTBOUNCE:
3591                 SoftBounce = atobool(val);
3592                 break;
3593 #endif /* _FFR_SOFT_BOUNCE */
3594
3595 #if _FFR_REJECT_LOG
3596           case O_REJECTLOGINTERVAL:     /* time btwn log msgs while refusing */
3597                 RejectLogInterval = convtime(val, 'h');
3598                 break;
3599 #endif /* _FFR_REJECT_LOG */
3600
3601 #if _FFR_REQ_DIR_FSYNC_OPT
3602           case O_REQUIRES_DIR_FSYNC:
3603 # if REQUIRES_DIR_FSYNC
3604                 RequiresDirfsync = atobool(val);
3605 # else /* REQUIRES_DIR_FSYNC */
3606                 /* silently ignored... required for cf file option */
3607 # endif /* REQUIRES_DIR_FSYNC */
3608                 break;
3609 #endif /* _FFR_REQ_DIR_FSYNC_OPT */
3610
3611           default:
3612                 if (tTd(37, 1))
3613                 {
3614                         if (isascii(opt) && isprint(opt))
3615                                 sm_dprintf("Warning: option %c unknown\n", opt);
3616                         else
3617                                 sm_dprintf("Warning: option 0x%x unknown\n", opt);
3618                 }
3619                 break;
3620         }
3621
3622         /*
3623         **  Options with suboptions are responsible for taking care
3624         **  of sticky-ness (e.g., that a command line setting is kept
3625         **  when reading in the sendmail.cf file).  This has to be done
3626         **  when the suboptions are parsed since each suboption must be
3627         **  sticky, not the root option.
3628         */
3629
3630         if (sticky && !bitset(OI_SUBOPT, o->o_flags))
3631                 setbitn(opt, StickyOpt);
3632 }
3633 /*
3634 **  SETCLASS -- set a string into a class
3635 **
3636 **      Parameters:
3637 **              class -- the class to put the string in.
3638 **              str -- the string to enter
3639 **
3640 **      Returns:
3641 **              none.
3642 **
3643 **      Side Effects:
3644 **              puts the word into the symbol table.
3645 */
3646
3647 void
3648 setclass(class, str)
3649         int class;
3650         char *str;
3651 {
3652         register STAB *s;
3653
3654         if ((*str & 0377) == MATCHCLASS)
3655         {
3656                 int mid;
3657
3658                 str++;
3659                 mid = macid(str);
3660                 if (mid == 0)
3661                         return;
3662
3663                 if (tTd(37, 8))
3664                         sm_dprintf("setclass(%s, $=%s)\n",
3665                                    macname(class), macname(mid));
3666                 copy_class(mid, class);
3667         }
3668         else
3669         {
3670                 if (tTd(37, 8))
3671                         sm_dprintf("setclass(%s, %s)\n", macname(class), str);
3672
3673                 s = stab(str, ST_CLASS, ST_ENTER);
3674                 setbitn(bitidx(class), s->s_class);
3675         }
3676 }
3677 /*
3678 **  MAKEMAPENTRY -- create a map entry
3679 **
3680 **      Parameters:
3681 **              line -- the config file line
3682 **
3683 **      Returns:
3684 **              A pointer to the map that has been created.
3685 **              NULL if there was a syntax error.
3686 **
3687 **      Side Effects:
3688 **              Enters the map into the dictionary.
3689 */
3690
3691 MAP *
3692 makemapentry(line)
3693         char *line;
3694 {
3695         register char *p;
3696         char *mapname;
3697         char *classname;
3698         register STAB *s;
3699         STAB *class;
3700
3701         for (p = line; isascii(*p) && isspace(*p); p++)
3702                 continue;
3703         if (!(isascii(*p) && isalnum(*p)))
3704         {
3705                 syserr("readcf: config K line: no map name");
3706                 return NULL;
3707         }
3708
3709         mapname = p;
3710         while ((isascii(*++p) && isalnum(*p)) || *p == '_' || *p == '.')
3711                 continue;
3712         if (*p != '\0')
3713                 *p++ = '\0';
3714         while (isascii(*p) && isspace(*p))
3715                 p++;
3716         if (!(isascii(*p) && isalnum(*p)))
3717         {
3718                 syserr("readcf: config K line, map %s: no map class", mapname);
3719                 return NULL;
3720         }
3721         classname = p;
3722         while (isascii(*++p) && isalnum(*p))
3723                 continue;
3724         if (*p != '\0')
3725                 *p++ = '\0';
3726         while (isascii(*p) && isspace(*p))
3727                 p++;
3728
3729         /* look up the class */
3730         class = stab(classname, ST_MAPCLASS, ST_FIND);
3731         if (class == NULL)
3732         {
3733                 syserr("readcf: map %s: class %s not available", mapname,
3734                         classname);
3735                 return NULL;
3736         }
3737
3738         /* enter the map */
3739         s = stab(mapname, ST_MAP, ST_ENTER);
3740         s->s_map.map_class = &class->s_mapclass;
3741         s->s_map.map_mname = newstr(mapname);
3742
3743         if (class->s_mapclass.map_parse(&s->s_map, p))
3744                 s->s_map.map_mflags |= MF_VALID;
3745
3746         if (tTd(37, 5))
3747         {
3748                 sm_dprintf("map %s, class %s, flags %lx, file %s,\n",
3749                            s->s_map.map_mname, s->s_map.map_class->map_cname,
3750                            s->s_map.map_mflags, s->s_map.map_file);
3751                 sm_dprintf("\tapp %s, domain %s, rebuild %s\n",
3752                            s->s_map.map_app, s->s_map.map_domain,
3753                            s->s_map.map_rebuild);
3754         }
3755         return &s->s_map;
3756 }
3757 /*
3758 **  STRTORWSET -- convert string to rewriting set number
3759 **
3760 **      Parameters:
3761 **              p -- the pointer to the string to decode.
3762 **              endp -- if set, store the trailing delimiter here.
3763 **              stabmode -- ST_ENTER to create this entry, ST_FIND if
3764 **                      it must already exist.
3765 **
3766 **      Returns:
3767 **              The appropriate ruleset number.
3768 **              -1 if it is not valid (error already printed)
3769 */
3770
3771 int
3772 strtorwset(p, endp, stabmode)
3773         char *p;
3774         char **endp;
3775         int stabmode;
3776 {
3777         int ruleset;
3778         static int nextruleset = MAXRWSETS;
3779
3780         while (isascii(*p) && isspace(*p))
3781                 p++;
3782         if (!isascii(*p))
3783         {
3784                 syserr("invalid ruleset name: \"%.20s\"", p);
3785                 return -1;
3786         }
3787         if (isdigit(*p))
3788         {
3789                 ruleset = strtol(p, endp, 10);
3790                 if (ruleset >= MAXRWSETS / 2 || ruleset < 0)
3791                 {
3792                         syserr("bad ruleset %d (%d max)",
3793                                 ruleset, MAXRWSETS / 2);
3794                         ruleset = -1;
3795                 }
3796         }
3797         else
3798         {
3799                 STAB *s;
3800                 char delim;
3801                 char *q = NULL;
3802
3803                 q = p;
3804                 while (*p != '\0' && isascii(*p) &&
3805                        (isalnum(*p) || *p == '_'))
3806                         p++;
3807                 if (q == p || !(isascii(*q) && isalpha(*q)))
3808                 {
3809                         /* no valid characters */
3810                         syserr("invalid ruleset name: \"%.20s\"", q);
3811                         return -1;
3812                 }
3813                 while (isascii(*p) && isspace(*p))
3814                         *p++ = '\0';
3815                 delim = *p;
3816                 if (delim != '\0')
3817                         *p = '\0';
3818                 s = stab(q, ST_RULESET, stabmode);
3819                 if (delim != '\0')
3820                         *p = delim;
3821
3822                 if (s == NULL)
3823                         return -1;
3824
3825                 if (stabmode == ST_ENTER && delim == '=')
3826                 {
3827                         while (isascii(*++p) && isspace(*p))
3828                                 continue;
3829                         if (!(isascii(*p) && isdigit(*p)))
3830                         {
3831                                 syserr("bad ruleset definition \"%s\" (number required after `=')", q);
3832                                 ruleset = -1;
3833                         }
3834                         else
3835                         {
3836                                 ruleset = strtol(p, endp, 10);
3837                                 if (ruleset >= MAXRWSETS / 2 || ruleset < 0)
3838                                 {
3839                                         syserr("bad ruleset number %d in \"%s\" (%d max)",
3840                                                 ruleset, q, MAXRWSETS / 2);
3841                                         ruleset = -1;
3842                                 }
3843                         }
3844                 }
3845                 else
3846                 {
3847                         if (endp != NULL)
3848                                 *endp = p;
3849                         if (s->s_ruleset >= 0)
3850                                 ruleset = s->s_ruleset;
3851                         else if ((ruleset = --nextruleset) < MAXRWSETS / 2)
3852                         {
3853                                 syserr("%s: too many named rulesets (%d max)",
3854                                         q, MAXRWSETS / 2);
3855                                 ruleset = -1;
3856                         }
3857                 }
3858                 if (s->s_ruleset >= 0 &&
3859                     ruleset >= 0 &&
3860                     ruleset != s->s_ruleset)
3861                 {
3862                         syserr("%s: ruleset changed value (old %d, new %d)",
3863                                 q, s->s_ruleset, ruleset);
3864                         ruleset = s->s_ruleset;
3865                 }
3866                 else if (ruleset >= 0)
3867                 {
3868                         s->s_ruleset = ruleset;
3869                 }
3870                 if (stabmode == ST_ENTER && ruleset >= 0)
3871                 {
3872                         char *h = NULL;
3873
3874                         if (RuleSetNames[ruleset] != NULL)
3875                                 sm_free(RuleSetNames[ruleset]); /* XXX */
3876                         if (delim != '\0' && (h = strchr(q, delim)) != NULL)
3877                                 *h = '\0';
3878                         RuleSetNames[ruleset] = newstr(q);
3879                         if (delim == '/' && h != NULL)
3880                                 *h = delim;     /* put back delim */
3881                 }
3882         }
3883         return ruleset;
3884 }
3885 /*
3886 **  SETTIMEOUT -- set an individual timeout
3887 **
3888 **      Parameters:
3889 **              name -- the name of the timeout.
3890 **              val -- the value of the timeout.
3891 **              sticky -- if set, don't let other setoptions override
3892 **                      this value.
3893 **
3894 **      Returns:
3895 **              none.
3896 */
3897
3898 /* set if Timeout sub-option is stuck */
3899 static BITMAP256        StickyTimeoutOpt;
3900
3901 static struct timeoutinfo
3902 {
3903         char            *to_name;       /* long name of timeout */
3904         unsigned char   to_code;        /* code for option */
3905 } TimeOutTab[] =
3906 {
3907 #define TO_INITIAL                      0x01
3908         { "initial",                    TO_INITIAL                      },
3909 #define TO_MAIL                         0x02
3910         { "mail",                       TO_MAIL                         },
3911 #define TO_RCPT                         0x03
3912         { "rcpt",                       TO_RCPT                         },
3913 #define TO_DATAINIT                     0x04
3914         { "datainit",                   TO_DATAINIT                     },
3915 #define TO_DATABLOCK                    0x05
3916         { "datablock",                  TO_DATABLOCK                    },
3917 #define TO_DATAFINAL                    0x06
3918         { "datafinal",                  TO_DATAFINAL                    },
3919 #define TO_COMMAND                      0x07
3920         { "command",                    TO_COMMAND                      },
3921 #define TO_RSET                         0x08
3922         { "rset",                       TO_RSET                         },
3923 #define TO_HELO                         0x09
3924         { "helo",                       TO_HELO                         },
3925 #define TO_QUIT                         0x0A
3926         { "quit",                       TO_QUIT                         },
3927 #define TO_MISC                         0x0B
3928         { "misc",                       TO_MISC                         },
3929 #define TO_IDENT                        0x0C
3930         { "ident",                      TO_IDENT                        },
3931 #define TO_FILEOPEN                     0x0D
3932         { "fileopen",                   TO_FILEOPEN                     },
3933 #define TO_CONNECT                      0x0E
3934         { "connect",                    TO_CONNECT                      },
3935 #define TO_ICONNECT                     0x0F
3936         { "iconnect",                   TO_ICONNECT                     },
3937 #define TO_QUEUEWARN                    0x10
3938         { "queuewarn",                  TO_QUEUEWARN                    },
3939         { "queuewarn.*",                TO_QUEUEWARN                    },
3940 #define TO_QUEUEWARN_NORMAL             0x11
3941         { "queuewarn.normal",           TO_QUEUEWARN_NORMAL             },
3942 #define TO_QUEUEWARN_URGENT             0x12
3943         { "queuewarn.urgent",           TO_QUEUEWARN_URGENT             },
3944 #define TO_QUEUEWARN_NON_URGENT         0x13
3945         { "queuewarn.non-urgent",       TO_QUEUEWARN_NON_URGENT         },
3946 #define TO_QUEUERETURN                  0x14
3947         { "queuereturn",                TO_QUEUERETURN                  },
3948         { "queuereturn.*",              TO_QUEUERETURN                  },
3949 #define TO_QUEUERETURN_NORMAL           0x15
3950         { "queuereturn.normal",         TO_QUEUERETURN_NORMAL           },
3951 #define TO_QUEUERETURN_URGENT           0x16
3952         { "queuereturn.urgent",         TO_QUEUERETURN_URGENT           },
3953 #define TO_QUEUERETURN_NON_URGENT       0x17
3954         { "queuereturn.non-urgent",     TO_QUEUERETURN_NON_URGENT       },
3955 #define TO_HOSTSTATUS                   0x18
3956         { "hoststatus",                 TO_HOSTSTATUS                   },
3957 #define TO_RESOLVER_RETRANS             0x19
3958         { "resolver.retrans",           TO_RESOLVER_RETRANS             },
3959 #define TO_RESOLVER_RETRANS_NORMAL      0x1A
3960         { "resolver.retrans.normal",    TO_RESOLVER_RETRANS_NORMAL      },
3961 #define TO_RESOLVER_RETRANS_FIRST       0x1B
3962         { "resolver.retrans.first",     TO_RESOLVER_RETRANS_FIRST       },
3963 #define TO_RESOLVER_RETRY               0x1C
3964         { "resolver.retry",             TO_RESOLVER_RETRY               },
3965 #define TO_RESOLVER_RETRY_NORMAL        0x1D
3966         { "resolver.retry.normal",      TO_RESOLVER_RETRY_NORMAL        },
3967 #define TO_RESOLVER_RETRY_FIRST         0x1E
3968         { "resolver.retry.first",       TO_RESOLVER_RETRY_FIRST         },
3969 #define TO_CONTROL                      0x1F
3970         { "control",                    TO_CONTROL                      },
3971 #define TO_LHLO                         0x20
3972         { "lhlo",                       TO_LHLO                         },
3973 #define TO_AUTH                         0x21
3974         { "auth",                       TO_AUTH                         },
3975 #define TO_STARTTLS                     0x22
3976         { "starttls",                   TO_STARTTLS                     },
3977 #define TO_ACONNECT                     0x23
3978         { "aconnect",                   TO_ACONNECT                     },
3979 #if _FFR_QUEUERETURN_DSN
3980 # define TO_QUEUEWARN_DSN               0x24
3981         { "queuewarn.dsn",              TO_QUEUEWARN_DSN                },
3982 # define TO_QUEUERETURN_DSN             0x25
3983         { "queuereturn.dsn",            TO_QUEUERETURN_DSN              },
3984 #endif /* _FFR_QUEUERETURN_DSN */
3985         { NULL,                         0                               },
3986 };
3987
3988
3989 static void
3990 settimeout(name, val, sticky)
3991         char *name;
3992         char *val;
3993         bool sticky;
3994 {
3995         register struct timeoutinfo *to;
3996         int i, addopts;
3997         time_t toval;
3998
3999         if (tTd(37, 2))
4000                 sm_dprintf("settimeout(%s = %s)", name, val);
4001
4002         for (to = TimeOutTab; to->to_name != NULL; to++)
4003         {
4004                 if (sm_strcasecmp(to->to_name, name) == 0)
4005                         break;
4006         }
4007
4008         if (to->to_name == NULL)
4009         {
4010                 errno = 0; /* avoid bogus error text */
4011                 syserr("settimeout: invalid timeout %s", name);
4012                 return;
4013         }
4014
4015         /*
4016         **  See if this option is preset for us.
4017         */
4018
4019         if (!sticky && bitnset(to->to_code, StickyTimeoutOpt))
4020         {
4021                 if (tTd(37, 2))
4022                         sm_dprintf(" (ignored)\n");
4023                 return;
4024         }
4025
4026         if (tTd(37, 2))
4027                 sm_dprintf("\n");
4028
4029         toval = convtime(val, 'm');
4030         addopts = 0;
4031
4032         switch (to->to_code)
4033         {
4034           case TO_INITIAL:
4035                 TimeOuts.to_initial = toval;
4036                 break;
4037
4038           case TO_MAIL:
4039                 TimeOuts.to_mail = toval;
4040                 break;
4041
4042           case TO_RCPT:
4043                 TimeOuts.to_rcpt = toval;
4044                 break;
4045
4046           case TO_DATAINIT:
4047                 TimeOuts.to_datainit = toval;
4048                 break;
4049
4050           case TO_DATABLOCK:
4051                 TimeOuts.to_datablock = toval;
4052                 break;
4053
4054           case TO_DATAFINAL:
4055                 TimeOuts.to_datafinal = toval;
4056                 break;
4057
4058           case TO_COMMAND:
4059                 TimeOuts.to_nextcommand = toval;
4060                 break;
4061
4062           case TO_RSET:
4063                 TimeOuts.to_rset = toval;
4064                 break;
4065
4066           case TO_HELO:
4067                 TimeOuts.to_helo = toval;
4068                 break;
4069
4070           case TO_QUIT:
4071                 TimeOuts.to_quit = toval;
4072                 break;
4073
4074           case TO_MISC:
4075                 TimeOuts.to_miscshort = toval;
4076                 break;
4077
4078           case TO_IDENT:
4079                 TimeOuts.to_ident = toval;
4080                 break;
4081
4082           case TO_FILEOPEN:
4083                 TimeOuts.to_fileopen = toval;
4084                 break;
4085
4086           case TO_CONNECT:
4087                 TimeOuts.to_connect = toval;
4088                 break;
4089
4090           case TO_ICONNECT:
4091                 TimeOuts.to_iconnect = toval;
4092                 break;
4093
4094           case TO_ACONNECT:
4095                 TimeOuts.to_aconnect = toval;
4096                 break;
4097
4098           case TO_QUEUEWARN:
4099                 toval = convtime(val, 'h');
4100                 TimeOuts.to_q_warning[TOC_NORMAL] = toval;
4101                 TimeOuts.to_q_warning[TOC_URGENT] = toval;
4102                 TimeOuts.to_q_warning[TOC_NONURGENT] = toval;
4103 #if _FFR_QUEUERETURN_DSN
4104                 TimeOuts.to_q_warning[TOC_DSN] = toval;
4105 #endif /* _FFR_QUEUERETURN_DSN */
4106                 addopts = 2;
4107                 break;
4108
4109           case TO_QUEUEWARN_NORMAL:
4110                 toval = convtime(val, 'h');
4111                 TimeOuts.to_q_warning[TOC_NORMAL] = toval;
4112                 break;
4113
4114           case TO_QUEUEWARN_URGENT:
4115                 toval = convtime(val, 'h');
4116                 TimeOuts.to_q_warning[TOC_URGENT] = toval;
4117                 break;
4118
4119           case TO_QUEUEWARN_NON_URGENT:
4120                 toval = convtime(val, 'h');
4121                 TimeOuts.to_q_warning[TOC_NONURGENT] = toval;
4122                 break;
4123
4124 #if _FFR_QUEUERETURN_DSN
4125           case TO_QUEUEWARN_DSN:
4126                 toval = convtime(val, 'h');
4127                 TimeOuts.to_q_warning[TOC_DSN] = toval;
4128                 break;
4129 #endif /* _FFR_QUEUERETURN_DSN */
4130
4131           case TO_QUEUERETURN:
4132                 toval = convtime(val, 'd');
4133                 TimeOuts.to_q_return[TOC_NORMAL] = toval;
4134                 TimeOuts.to_q_return[TOC_URGENT] = toval;
4135                 TimeOuts.to_q_return[TOC_NONURGENT] = toval;
4136 #if _FFR_QUEUERETURN_DSN
4137                 TimeOuts.to_q_return[TOC_DSN] = toval;
4138 #endif /* _FFR_QUEUERETURN_DSN */
4139                 addopts = 2;
4140                 break;
4141
4142           case TO_QUEUERETURN_NORMAL:
4143                 toval = convtime(val, 'd');
4144                 TimeOuts.to_q_return[TOC_NORMAL] = toval;
4145                 break;
4146
4147           case TO_QUEUERETURN_URGENT:
4148                 toval = convtime(val, 'd');
4149                 TimeOuts.to_q_return[TOC_URGENT] = toval;
4150                 break;
4151
4152           case TO_QUEUERETURN_NON_URGENT:
4153                 toval = convtime(val, 'd');
4154                 TimeOuts.to_q_return[TOC_NONURGENT] = toval;
4155                 break;
4156
4157 #if _FFR_QUEUERETURN_DSN
4158           case TO_QUEUERETURN_DSN:
4159                 toval = convtime(val, 'd');
4160                 TimeOuts.to_q_return[TOC_DSN] = toval;
4161                 break;
4162 #endif /* _FFR_QUEUERETURN_DSN */
4163
4164           case TO_HOSTSTATUS:
4165                 MciInfoTimeout = toval;
4166                 break;
4167
4168           case TO_RESOLVER_RETRANS:
4169                 toval = convtime(val, 's');
4170                 TimeOuts.res_retrans[RES_TO_DEFAULT] = toval;
4171                 TimeOuts.res_retrans[RES_TO_FIRST] = toval;
4172                 TimeOuts.res_retrans[RES_TO_NORMAL] = toval;
4173                 addopts = 2;
4174                 break;
4175
4176           case TO_RESOLVER_RETRY:
4177                 i = atoi(val);
4178                 TimeOuts.res_retry[RES_TO_DEFAULT] = i;
4179                 TimeOuts.res_retry[RES_TO_FIRST] = i;
4180                 TimeOuts.res_retry[RES_TO_NORMAL] = i;
4181                 addopts = 2;
4182                 break;
4183
4184           case TO_RESOLVER_RETRANS_NORMAL:
4185                 TimeOuts.res_retrans[RES_TO_NORMAL] = convtime(val, 's');
4186                 break;
4187
4188           case TO_RESOLVER_RETRY_NORMAL:
4189                 TimeOuts.res_retry[RES_TO_NORMAL] = atoi(val);
4190                 break;
4191
4192           case TO_RESOLVER_RETRANS_FIRST:
4193                 TimeOuts.res_retrans[RES_TO_FIRST] = convtime(val, 's');
4194                 break;
4195
4196           case TO_RESOLVER_RETRY_FIRST:
4197                 TimeOuts.res_retry[RES_TO_FIRST] = atoi(val);
4198                 break;
4199
4200           case TO_CONTROL:
4201                 TimeOuts.to_control = toval;
4202                 break;
4203
4204           case TO_LHLO:
4205                 TimeOuts.to_lhlo = toval;
4206                 break;
4207
4208 #if SASL
4209           case TO_AUTH:
4210                 TimeOuts.to_auth = toval;
4211                 break;
4212 #endif /* SASL */
4213
4214 #if STARTTLS
4215           case TO_STARTTLS:
4216                 TimeOuts.to_starttls = toval;
4217                 break;
4218 #endif /* STARTTLS */
4219
4220           default:
4221                 syserr("settimeout: invalid timeout %s", name);
4222                 break;
4223         }
4224
4225         if (sticky)
4226         {
4227                 for (i = 0; i <= addopts; i++)
4228                         setbitn(to->to_code + i, StickyTimeoutOpt);
4229         }
4230 }
4231 /*
4232 **  INITTIMEOUTS -- parse and set timeout values
4233 **
4234 **      Parameters:
4235 **              val -- a pointer to the values.  If NULL, do initial
4236 **                      settings.
4237 **              sticky -- if set, don't let other setoptions override
4238 **                      this suboption value.
4239 **
4240 **      Returns:
4241 **              none.
4242 **
4243 **      Side Effects:
4244 **              Initializes the TimeOuts structure
4245 */
4246
4247 void
4248 inittimeouts(val, sticky)
4249         register char *val;
4250         bool sticky;
4251 {
4252         register char *p;
4253
4254         if (tTd(37, 2))
4255                 sm_dprintf("inittimeouts(%s)\n", val == NULL ? "<NULL>" : val);
4256         if (val == NULL)
4257         {
4258                 TimeOuts.to_connect = (time_t) 0 SECONDS;
4259                 TimeOuts.to_aconnect = (time_t) 0 SECONDS;
4260                 TimeOuts.to_iconnect = (time_t) 0 SECONDS;
4261                 TimeOuts.to_initial = (time_t) 5 MINUTES;
4262                 TimeOuts.to_helo = (time_t) 5 MINUTES;
4263                 TimeOuts.to_mail = (time_t) 10 MINUTES;
4264                 TimeOuts.to_rcpt = (time_t) 1 HOUR;
4265                 TimeOuts.to_datainit = (time_t) 5 MINUTES;
4266                 TimeOuts.to_datablock = (time_t) 1 HOUR;
4267                 TimeOuts.to_datafinal = (time_t) 1 HOUR;
4268                 TimeOuts.to_rset = (time_t) 5 MINUTES;
4269                 TimeOuts.to_quit = (time_t) 2 MINUTES;
4270                 TimeOuts.to_nextcommand = (time_t) 1 HOUR;
4271                 TimeOuts.to_miscshort = (time_t) 2 MINUTES;
4272 #if IDENTPROTO
4273                 TimeOuts.to_ident = (time_t) 5 SECONDS;
4274 #else /* IDENTPROTO */
4275                 TimeOuts.to_ident = (time_t) 0 SECONDS;
4276 #endif /* IDENTPROTO */
4277                 TimeOuts.to_fileopen = (time_t) 60 SECONDS;
4278                 TimeOuts.to_control = (time_t) 2 MINUTES;
4279                 TimeOuts.to_lhlo = (time_t) 2 MINUTES;
4280 #if SASL
4281                 TimeOuts.to_auth = (time_t) 10 MINUTES;
4282 #endif /* SASL */
4283 #if STARTTLS
4284                 TimeOuts.to_starttls = (time_t) 1 HOUR;
4285 #endif /* STARTTLS */
4286                 if (tTd(37, 5))
4287                 {
4288                         sm_dprintf("Timeouts:\n");
4289                         sm_dprintf("  connect = %ld\n",
4290                                    (long) TimeOuts.to_connect);
4291                         sm_dprintf("  aconnect = %ld\n",
4292                                    (long) TimeOuts.to_aconnect);
4293                         sm_dprintf("  initial = %ld\n",
4294                                    (long) TimeOuts.to_initial);
4295                         sm_dprintf("  helo = %ld\n", (long) TimeOuts.to_helo);
4296                         sm_dprintf("  mail = %ld\n", (long) TimeOuts.to_mail);
4297                         sm_dprintf("  rcpt = %ld\n", (long) TimeOuts.to_rcpt);
4298                         sm_dprintf("  datainit = %ld\n",
4299                                    (long) TimeOuts.to_datainit);
4300                         sm_dprintf("  datablock = %ld\n",
4301                                    (long) TimeOuts.to_datablock);
4302                         sm_dprintf("  datafinal = %ld\n",
4303                                    (long) TimeOuts.to_datafinal);
4304                         sm_dprintf("  rset = %ld\n", (long) TimeOuts.to_rset);
4305                         sm_dprintf("  quit = %ld\n", (long) TimeOuts.to_quit);
4306                         sm_dprintf("  nextcommand = %ld\n",
4307                                    (long) TimeOuts.to_nextcommand);
4308                         sm_dprintf("  miscshort = %ld\n",
4309                                    (long) TimeOuts.to_miscshort);
4310                         sm_dprintf("  ident = %ld\n", (long) TimeOuts.to_ident);
4311                         sm_dprintf("  fileopen = %ld\n",
4312                                    (long) TimeOuts.to_fileopen);
4313                         sm_dprintf("  lhlo = %ld\n",
4314                                    (long) TimeOuts.to_lhlo);
4315                         sm_dprintf("  control = %ld\n",
4316                                    (long) TimeOuts.to_control);
4317                 }
4318                 return;
4319         }
4320
4321         for (;; val = p)
4322         {
4323                 while (isascii(*val) && isspace(*val))
4324                         val++;
4325                 if (*val == '\0')
4326                         break;
4327                 for (p = val; *p != '\0' && *p != ','; p++)
4328                         continue;
4329                 if (*p != '\0')
4330                         *p++ = '\0';
4331
4332                 if (isascii(*val) && isdigit(*val))
4333                 {
4334                         /* old syntax -- set everything */
4335                         TimeOuts.to_mail = convtime(val, 'm');
4336                         TimeOuts.to_rcpt = TimeOuts.to_mail;
4337                         TimeOuts.to_datainit = TimeOuts.to_mail;
4338                         TimeOuts.to_datablock = TimeOuts.to_mail;
4339                         TimeOuts.to_datafinal = TimeOuts.to_mail;
4340                         TimeOuts.to_nextcommand = TimeOuts.to_mail;
4341                         if (sticky)
4342                         {
4343                                 setbitn(TO_MAIL, StickyTimeoutOpt);
4344                                 setbitn(TO_RCPT, StickyTimeoutOpt);
4345                                 setbitn(TO_DATAINIT, StickyTimeoutOpt);
4346                                 setbitn(TO_DATABLOCK, StickyTimeoutOpt);
4347                                 setbitn(TO_DATAFINAL, StickyTimeoutOpt);
4348                                 setbitn(TO_COMMAND, StickyTimeoutOpt);
4349                         }
4350                         continue;
4351                 }
4352                 else
4353                 {
4354                         register char *q = strchr(val, ':');
4355
4356                         if (q == NULL && (q = strchr(val, '=')) == NULL)
4357                         {
4358                                 /* syntax error */
4359                                 continue;
4360                         }
4361                         *q++ = '\0';
4362                         settimeout(val, q, sticky);
4363                 }
4364         }
4365 }