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