xref: /original-bsd/usr.sbin/sendmail/src/readcf.c (revision c4f3b704)
1 /*
2  * Copyright (c) 1983, 1995 Eric P. Allman
3  * Copyright (c) 1988, 1993
4  *	The Regents of the University of California.  All rights reserved.
5  *
6  * %sccs.include.redist.c%
7  */
8 
9 #ifndef lint
10 static char sccsid[] = "@(#)readcf.c	8.98 (Berkeley) 05/30/95";
11 #endif /* not lint */
12 
13 # include "sendmail.h"
14 # include <grp.h>
15 #if NAMED_BIND
16 # include <resolv.h>
17 #endif
18 
19 /*
20 **  READCF -- read control file.
21 **
22 **	This routine reads the control file and builds the internal
23 **	form.
24 **
25 **	The file is formatted as a sequence of lines, each taken
26 **	atomically.  The first character of each line describes how
27 **	the line is to be interpreted.  The lines are:
28 **		Dxval		Define macro x to have value val.
29 **		Cxword		Put word into class x.
30 **		Fxfile [fmt]	Read file for lines to put into
31 **				class x.  Use scanf string 'fmt'
32 **				or "%s" if not present.  Fmt should
33 **				only produce one string-valued result.
34 **		Hname: value	Define header with field-name 'name'
35 **				and value as specified; this will be
36 **				macro expanded immediately before
37 **				use.
38 **		Sn		Use rewriting set n.
39 **		Rlhs rhs	Rewrite addresses that match lhs to
40 **				be rhs.
41 **		Mn arg=val...	Define mailer.  n is the internal name.
42 **				Args specify mailer parameters.
43 **		Oxvalue		Set option x to value.
44 **		Pname=value	Set precedence name to value.
45 **		Vversioncode[/vendorcode]
46 **				Version level/vendor name of
47 **				configuration syntax.
48 **		Kmapname mapclass arguments....
49 **				Define keyed lookup of a given class.
50 **				Arguments are class dependent.
51 **		Eenvar=value	Set the environment value to the given value.
52 **
53 **	Parameters:
54 **		cfname -- control file name.
55 **		safe -- TRUE if this is the system config file;
56 **			FALSE otherwise.
57 **		e -- the main envelope.
58 **
59 **	Returns:
60 **		none.
61 **
62 **	Side Effects:
63 **		Builds several internal tables.
64 */
65 
66 void
67 readcf(cfname, safe, e)
68 	char *cfname;
69 	bool safe;
70 	register ENVELOPE *e;
71 {
72 	FILE *cf;
73 	int ruleset = 0;
74 	char *q;
75 	struct rewrite *rwp = NULL;
76 	char *bp;
77 	auto char *ep;
78 	int nfuzzy;
79 	char *file;
80 	bool optional;
81 	int mid;
82 	char buf[MAXLINE];
83 	register char *p;
84 	extern char **copyplist();
85 	struct stat statb;
86 	char exbuf[MAXLINE];
87 	char pvpbuf[MAXLINE + MAXATOM];
88 	static char *null_list[1] = { NULL };
89 	extern char *munchstring __P((char *, char **));
90 	extern void fileclass __P((int, char *, char *, bool, bool));
91 	extern void toomany __P((int, int));
92 
93 	FileName = cfname;
94 	LineNumber = 0;
95 
96 	cf = fopen(cfname, "r");
97 	if (cf == NULL)
98 	{
99 		syserr("cannot open");
100 		exit(EX_OSFILE);
101 	}
102 
103 	if (fstat(fileno(cf), &statb) < 0)
104 	{
105 		syserr("cannot fstat");
106 		exit(EX_OSFILE);
107 	}
108 
109 	if (!S_ISREG(statb.st_mode))
110 	{
111 		syserr("not a plain file");
112 		exit(EX_OSFILE);
113 	}
114 
115 	if (OpMode != MD_TEST && bitset(S_IWGRP|S_IWOTH, statb.st_mode))
116 	{
117 		if (OpMode == MD_DAEMON || OpMode == MD_INITALIAS)
118 			fprintf(stderr, "%s: WARNING: dangerous write permissions\n",
119 				FileName);
120 #ifdef LOG
121 		if (LogLevel > 0)
122 			syslog(LOG_CRIT, "%s: WARNING: dangerous write permissions",
123 				FileName);
124 #endif
125 	}
126 
127 #ifdef XLA
128 	xla_zero();
129 #endif
130 
131 	while ((bp = fgetfolded(buf, sizeof buf, cf)) != NULL)
132 	{
133 		if (bp[0] == '#')
134 		{
135 			if (bp != buf)
136 				free(bp);
137 			continue;
138 		}
139 
140 		/* do macro expansion mappings */
141 		for (p = bp; *p != '\0'; p++)
142 		{
143 			if (*p == '#' && p > bp && ConfigLevel >= 3)
144 			{
145 				/* this is an on-line comment */
146 				register char *e;
147 
148 				switch (*--p & 0377)
149 				{
150 				  case MACROEXPAND:
151 					/* it's from $# -- let it go through */
152 					p++;
153 					break;
154 
155 				  case '\\':
156 					/* it's backslash escaped */
157 					(void) strcpy(p, p + 1);
158 					break;
159 
160 				  default:
161 					/* delete preceeding white space */
162 					while (isascii(*p) && isspace(*p) && p > bp)
163 						p--;
164 					if ((e = strchr(++p, '\n')) != NULL)
165 						(void) strcpy(p, e);
166 					else
167 						p[0] = p[1] = '\0';
168 					break;
169 				}
170 				continue;
171 			}
172 
173 			if (*p != '$' || p[1] == '\0')
174 				continue;
175 
176 			if (p[1] == '$')
177 			{
178 				/* actual dollar sign.... */
179 				(void) strcpy(p, p + 1);
180 				continue;
181 			}
182 
183 			/* convert to macro expansion character */
184 			*p++ = MACROEXPAND;
185 
186 			/* convert macro name to code */
187 			*p = macid(p, &ep);
188 			if (ep != p)
189 				strcpy(p + 1, ep);
190 		}
191 
192 		/* interpret this line */
193 		errno = 0;
194 		switch (bp[0])
195 		{
196 		  case '\0':
197 		  case '#':		/* comment */
198 			break;
199 
200 		  case 'R':		/* rewriting rule */
201 			for (p = &bp[1]; *p != '\0' && *p != '\t'; p++)
202 				continue;
203 
204 			if (*p == '\0')
205 			{
206 				syserr("invalid rewrite line \"%s\" (tab expected)", bp);
207 				break;
208 			}
209 
210 			/* allocate space for the rule header */
211 			if (rwp == NULL)
212 			{
213 				RewriteRules[ruleset] = rwp =
214 					(struct rewrite *) xalloc(sizeof *rwp);
215 			}
216 			else
217 			{
218 				rwp->r_next = (struct rewrite *) xalloc(sizeof *rwp);
219 				rwp = rwp->r_next;
220 			}
221 			rwp->r_next = NULL;
222 
223 			/* expand and save the LHS */
224 			*p = '\0';
225 			expand(&bp[1], exbuf, sizeof exbuf, e);
226 			rwp->r_lhs = prescan(exbuf, '\t', pvpbuf,
227 					     sizeof pvpbuf, NULL, NULL);
228 			nfuzzy = 0;
229 			if (rwp->r_lhs != NULL)
230 			{
231 				register char **ap;
232 
233 				rwp->r_lhs = copyplist(rwp->r_lhs, TRUE);
234 
235 				/* count the number of fuzzy matches in LHS */
236 				for (ap = rwp->r_lhs; *ap != NULL; ap++)
237 				{
238 					char *botch;
239 
240 					botch = NULL;
241 					switch (**ap & 0377)
242 					{
243 					  case MATCHZANY:
244 					  case MATCHANY:
245 					  case MATCHONE:
246 					  case MATCHCLASS:
247 					  case MATCHNCLASS:
248 						nfuzzy++;
249 						break;
250 
251 					  case MATCHREPL:
252 						botch = "$0-$9";
253 						break;
254 
255 					  case CANONNET:
256 						botch = "$#";
257 						break;
258 
259 					  case CANONUSER:
260 						botch = "$:";
261 						break;
262 
263 					  case CALLSUBR:
264 						botch = "$>";
265 						break;
266 
267 					  case CONDIF:
268 						botch = "$?";
269 						break;
270 
271 					  case CONDELSE:
272 						botch = "$|";
273 						break;
274 
275 					  case CONDFI:
276 						botch = "$.";
277 						break;
278 
279 					  case HOSTBEGIN:
280 						botch = "$[";
281 						break;
282 
283 					  case HOSTEND:
284 						botch = "$]";
285 						break;
286 
287 					  case LOOKUPBEGIN:
288 						botch = "$(";
289 						break;
290 
291 					  case LOOKUPEND:
292 						botch = "$)";
293 						break;
294 					}
295 					if (botch != NULL)
296 						syserr("Inappropriate use of %s on LHS",
297 							botch);
298 				}
299 			}
300 			else
301 			{
302 				syserr("R line: null LHS");
303 				rwp->r_lhs = null_list;
304 			}
305 
306 			/* expand and save the RHS */
307 			while (*++p == '\t')
308 				continue;
309 			q = p;
310 			while (*p != '\0' && *p != '\t')
311 				p++;
312 			*p = '\0';
313 			expand(q, exbuf, sizeof exbuf, e);
314 			rwp->r_rhs = prescan(exbuf, '\t', pvpbuf,
315 					     sizeof pvpbuf, NULL, NULL);
316 			if (rwp->r_rhs != NULL)
317 			{
318 				register char **ap;
319 
320 				rwp->r_rhs = copyplist(rwp->r_rhs, TRUE);
321 
322 				/* check no out-of-bounds replacements */
323 				nfuzzy += '0';
324 				for (ap = rwp->r_rhs; *ap != NULL; ap++)
325 				{
326 					char *botch;
327 
328 					botch = NULL;
329 					switch (**ap & 0377)
330 					{
331 					  case MATCHREPL:
332 						if ((*ap)[1] <= '0' || (*ap)[1] > nfuzzy)
333 						{
334 							syserr("replacement $%c out of bounds",
335 								(*ap)[1]);
336 						}
337 						break;
338 
339 					  case MATCHZANY:
340 						botch = "$*";
341 						break;
342 
343 					  case MATCHANY:
344 						botch = "$+";
345 						break;
346 
347 					  case MATCHONE:
348 						botch = "$-";
349 						break;
350 
351 					  case MATCHCLASS:
352 						botch = "$=";
353 						break;
354 
355 					  case MATCHNCLASS:
356 						botch = "$~";
357 						break;
358 					}
359 					if (botch != NULL)
360 						syserr("Inappropriate use of %s on RHS",
361 							botch);
362 				}
363 			}
364 			else
365 			{
366 				syserr("R line: null RHS");
367 				rwp->r_rhs = null_list;
368 			}
369 			break;
370 
371 		  case 'S':		/* select rewriting set */
372 			ruleset = strtorwset(&bp[1], NULL, ST_ENTER);
373 			rwp = NULL;
374 			break;
375 
376 		  case 'D':		/* macro definition */
377 			mid = macid(&bp[1], &ep);
378 			p = munchstring(ep, NULL);
379 			define(mid, newstr(p), e);
380 			break;
381 
382 		  case 'H':		/* required header line */
383 			(void) chompheader(&bp[1], TRUE, NULL, e);
384 			break;
385 
386 		  case 'C':		/* word class */
387 		  case 'T':		/* trusted user (set class `t') */
388 			if (bp[0] == 'C')
389 			{
390 				mid = macid(&bp[1], &ep);
391 				expand(ep, exbuf, sizeof exbuf, e);
392 				p = exbuf;
393 			}
394 			else
395 			{
396 				mid = 't';
397 				p = &bp[1];
398 			}
399 			while (*p != '\0')
400 			{
401 				register char *wd;
402 				char delim;
403 
404 				while (*p != '\0' && isascii(*p) && isspace(*p))
405 					p++;
406 				wd = p;
407 				while (*p != '\0' && !(isascii(*p) && isspace(*p)))
408 					p++;
409 				delim = *p;
410 				*p = '\0';
411 				if (wd[0] != '\0')
412 					setclass(mid, wd);
413 				*p = delim;
414 			}
415 			break;
416 
417 		  case 'F':		/* word class from file */
418 			mid = macid(&bp[1], &ep);
419 			for (p = ep; isascii(*p) && isspace(*p); )
420 				p++;
421 			if (p[0] == '-' && p[1] == 'o')
422 			{
423 				optional = TRUE;
424 				while (*p != '\0' && !(isascii(*p) && isspace(*p)))
425 					p++;
426 				while (isascii(*p) && isspace(*p))
427 					p++;
428 			}
429 			else
430 				optional = FALSE;
431 			file = p;
432 			while (*p != '\0' && !(isascii(*p) && isspace(*p)))
433 				p++;
434 			if (*p == '\0')
435 				p = "%s";
436 			else
437 			{
438 				*p = '\0';
439 				while (isascii(*++p) && isspace(*p))
440 					continue;
441 			}
442 			fileclass(bp[1], file, p, safe, optional);
443 			break;
444 
445 #ifdef XLA
446 		  case 'L':		/* extended load average description */
447 			xla_init(&bp[1]);
448 			break;
449 #endif
450 
451 		  case 'M':		/* define mailer */
452 			makemailer(&bp[1]);
453 			break;
454 
455 		  case 'O':		/* set option */
456 			setoption(bp[1], &bp[2], safe, FALSE, e);
457 			break;
458 
459 		  case 'P':		/* set precedence */
460 			if (NumPriorities >= MAXPRIORITIES)
461 			{
462 				toomany('P', MAXPRIORITIES);
463 				break;
464 			}
465 			for (p = &bp[1]; *p != '\0' && *p != '=' && *p != '\t'; p++)
466 				continue;
467 			if (*p == '\0')
468 				goto badline;
469 			*p = '\0';
470 			Priorities[NumPriorities].pri_name = newstr(&bp[1]);
471 			Priorities[NumPriorities].pri_val = atoi(++p);
472 			NumPriorities++;
473 			break;
474 
475 		  case 'V':		/* configuration syntax version */
476 			for (p = &bp[1]; isascii(*p) && isspace(*p); p++)
477 				continue;
478 			if (!isascii(*p) || !isdigit(*p))
479 			{
480 				syserr("invalid argument to V line: \"%.20s\"",
481 					&bp[1]);
482 				break;
483 			}
484 			ConfigLevel = strtol(p, &ep, 10);
485 
486 			/*
487 			**  Do heuristic tweaking for back compatibility.
488 			*/
489 
490 			if (ConfigLevel >= 5)
491 			{
492 				/* level 5 configs have short name in $w */
493 				p = macvalue('w', e);
494 				if (p != NULL && (p = strchr(p, '.')) != NULL)
495 					*p = '\0';
496 			}
497 			if (ConfigLevel >= 6)
498 			{
499 				ColonOkInAddr = FALSE;
500 			}
501 
502 			/*
503 			**  Look for vendor code.
504 			*/
505 
506 			if (*ep++ == '/')
507 			{
508 				/* extract vendor code */
509 				for (p = ep; isascii(*p) && isalpha(*p); )
510 					p++;
511 				*p = '\0';
512 
513 				if (!setvendor(ep))
514 					syserr("invalid V line vendor code: \"%s\"",
515 						ep);
516 			}
517 			break;
518 
519 		  case 'K':
520 			(void) makemapentry(&bp[1]);
521 			break;
522 
523 		  case 'E':
524 			p = strchr(bp, '=');
525 			if (p != NULL)
526 				*p++ = '\0';
527 			setuserenv(&bp[1], p);
528 			break;
529 
530 		  default:
531 		  badline:
532 			syserr("unknown control line \"%s\"", bp);
533 		}
534 		if (bp != buf)
535 			free(bp);
536 	}
537 	if (ferror(cf))
538 	{
539 		syserr("I/O read error", cfname);
540 		exit(EX_OSFILE);
541 	}
542 	fclose(cf);
543 	FileName = NULL;
544 
545 	/* initialize host maps from local service tables */
546 	inithostmaps();
547 
548 	/* determine if we need to do special name-server frotz */
549 	{
550 		int nmaps;
551 		char *maptype[MAXMAPSTACK];
552 		short mapreturn[MAXMAPACTIONS];
553 
554 		nmaps = switch_map_find("hosts", maptype, mapreturn);
555 		UseNameServer = FALSE;
556 		if (nmaps > 0 && nmaps <= MAXMAPSTACK)
557 		{
558 			register int mapno;
559 
560 			for (mapno = 0; mapno < nmaps && !UseNameServer; mapno++)
561 			{
562 				if (strcmp(maptype[mapno], "dns") == 0)
563 					UseNameServer = TRUE;
564 			}
565 		}
566 
567 #ifdef HESIOD
568 		nmaps = switch_map_find("passwd", maptype, mapreturn);
569 		UseHesiod = FALSE;
570 		if (nmaps > 0 && nmaps <= MAXMAPSTACK)
571 		{
572 			register int mapno;
573 
574 			for (mapno = 0; mapno < nmaps && !UseHesiod; mapno++)
575 			{
576 				if (strcmp(maptype[mapno], "hesiod") == 0)
577 					UseHesiod = TRUE;
578 			}
579 		}
580 #endif
581 	}
582 }
583 /*
584 **  TOOMANY -- signal too many of some option
585 **
586 **	Parameters:
587 **		id -- the id of the error line
588 **		maxcnt -- the maximum possible values
589 **
590 **	Returns:
591 **		none.
592 **
593 **	Side Effects:
594 **		gives a syserr.
595 */
596 
597 void
598 toomany(id, maxcnt)
599 	int id;
600 	int maxcnt;
601 {
602 	syserr("too many %c lines, %d max", id, maxcnt);
603 }
604 /*
605 **  FILECLASS -- read members of a class from a file
606 **
607 **	Parameters:
608 **		class -- class to define.
609 **		filename -- name of file to read.
610 **		fmt -- scanf string to use for match.
611 **		safe -- if set, this is a safe read.
612 **		optional -- if set, it is not an error for the file to
613 **			not exist.
614 **
615 **	Returns:
616 **		none
617 **
618 **	Side Effects:
619 **
620 **		puts all lines in filename that match a scanf into
621 **			the named class.
622 */
623 
624 void
625 fileclass(class, filename, fmt, safe, optional)
626 	int class;
627 	char *filename;
628 	char *fmt;
629 	bool safe;
630 	bool optional;
631 {
632 	FILE *f;
633 	int sff;
634 	int pid;
635 	register char *p;
636 	char buf[MAXLINE];
637 
638 	if (tTd(37, 2))
639 		printf("fileclass(%s, fmt=%s)\n", filename, fmt);
640 
641 	if (filename[0] == '|')
642 	{
643 		auto int fd;
644 		int i;
645 		char *argv[MAXPV + 1];
646 
647 		i = 0;
648 		for (p = strtok(&filename[1], " \t"); p != NULL; p = strtok(NULL, " \t"))
649 		{
650 			if (i >= MAXPV)
651 				break;
652 			argv[i++] = p;
653 		}
654 		argv[i] = NULL;
655 		pid = prog_open(argv, &fd, CurEnv);
656 		if (pid < 0)
657 			f = NULL;
658 		else
659 			f = fdopen(fd, "r");
660 	}
661 	else
662 	{
663 		pid = -1;
664 		sff = SFF_REGONLY;
665 		if (safe)
666 			sff |= SFF_OPENASROOT;
667 		f = safefopen(filename, O_RDONLY, 0, sff);
668 	}
669 	if (f == NULL)
670 	{
671 		if (!optional)
672 			syserr("fileclass: cannot open %s", filename);
673 		return;
674 	}
675 
676 	while (fgets(buf, sizeof buf, f) != NULL)
677 	{
678 		register char *p;
679 # ifdef SCANF
680 		char wordbuf[MAXNAME+1];
681 
682 		if (sscanf(buf, fmt, wordbuf) != 1)
683 			continue;
684 		p = wordbuf;
685 # else /* SCANF */
686 		p = buf;
687 # endif /* SCANF */
688 
689 		/*
690 		**  Break up the match into words.
691 		*/
692 
693 		while (*p != '\0')
694 		{
695 			register char *q;
696 
697 			/* strip leading spaces */
698 			while (isascii(*p) && isspace(*p))
699 				p++;
700 			if (*p == '\0')
701 				break;
702 
703 			/* find the end of the word */
704 			q = p;
705 			while (*p != '\0' && !(isascii(*p) && isspace(*p)))
706 				p++;
707 			if (*p != '\0')
708 				*p++ = '\0';
709 
710 			/* enter the word in the symbol table */
711 			setclass(class, q);
712 		}
713 	}
714 
715 	(void) fclose(f);
716 	if (pid > 0)
717 		(void) waitfor(pid);
718 }
719 /*
720 **  MAKEMAILER -- define a new mailer.
721 **
722 **	Parameters:
723 **		line -- description of mailer.  This is in labeled
724 **			fields.  The fields are:
725 **			   A -- the argv for this mailer
726 **			   C -- the character set for MIME conversions
727 **			   D -- the directory to run in
728 **			   E -- the eol string
729 **			   F -- the flags associated with the mailer
730 **			   L -- the maximum line length
731 **			   M -- the maximum message size
732 **			   N -- the niceness at which to run
733 **			   P -- the path to the mailer
734 **			   R -- the recipient rewriting set
735 **			   S -- the sender rewriting set
736 **			   T -- the mailer type (for DSNs)
737 **			   U -- the uid to run as
738 **			The first word is the canonical name of the mailer.
739 **
740 **	Returns:
741 **		none.
742 **
743 **	Side Effects:
744 **		enters the mailer into the mailer table.
745 */
746 
747 void
748 makemailer(line)
749 	char *line;
750 {
751 	register char *p;
752 	register struct mailer *m;
753 	register STAB *s;
754 	int i;
755 	char fcode;
756 	auto char *endp;
757 	extern int NextMailer;
758 	extern char **makeargv();
759 	extern char *munchstring();
760 
761 	/* allocate a mailer and set up defaults */
762 	m = (struct mailer *) xalloc(sizeof *m);
763 	bzero((char *) m, sizeof *m);
764 	m->m_eol = "\n";
765 	m->m_uid = m->m_gid = 0;
766 
767 	/* collect the mailer name */
768 	for (p = line; *p != '\0' && *p != ',' && !(isascii(*p) && isspace(*p)); p++)
769 		continue;
770 	if (*p != '\0')
771 		*p++ = '\0';
772 	m->m_name = newstr(line);
773 
774 	/* now scan through and assign info from the fields */
775 	while (*p != '\0')
776 	{
777 		auto char *delimptr;
778 
779 		while (*p != '\0' && (*p == ',' || (isascii(*p) && isspace(*p))))
780 			p++;
781 
782 		/* p now points to field code */
783 		fcode = *p;
784 		while (*p != '\0' && *p != '=' && *p != ',')
785 			p++;
786 		if (*p++ != '=')
787 		{
788 			syserr("mailer %s: `=' expected", m->m_name);
789 			return;
790 		}
791 		while (isascii(*p) && isspace(*p))
792 			p++;
793 
794 		/* p now points to the field body */
795 		p = munchstring(p, &delimptr);
796 
797 		/* install the field into the mailer struct */
798 		switch (fcode)
799 		{
800 		  case 'P':		/* pathname */
801 			m->m_mailer = newstr(p);
802 			break;
803 
804 		  case 'F':		/* flags */
805 			for (; *p != '\0'; p++)
806 				if (!(isascii(*p) && isspace(*p)))
807 					setbitn(*p, m->m_flags);
808 			break;
809 
810 		  case 'S':		/* sender rewriting ruleset */
811 		  case 'R':		/* recipient rewriting ruleset */
812 			i = strtorwset(p, &endp, ST_ENTER);
813 			if (i < 0)
814 				return;
815 			if (fcode == 'S')
816 				m->m_sh_rwset = m->m_se_rwset = i;
817 			else
818 				m->m_rh_rwset = m->m_re_rwset = i;
819 
820 			p = endp;
821 			if (*p++ == '/')
822 			{
823 				i = strtorwset(p, NULL);
824 				if (i < 0)
825 					return;
826 				if (fcode == 'S')
827 					m->m_sh_rwset = i;
828 				else
829 					m->m_rh_rwset = i;
830 			}
831 			break;
832 
833 		  case 'E':		/* end of line string */
834 			m->m_eol = newstr(p);
835 			break;
836 
837 		  case 'A':		/* argument vector */
838 			m->m_argv = makeargv(p);
839 			break;
840 
841 		  case 'M':		/* maximum message size */
842 			m->m_maxsize = atol(p);
843 			break;
844 
845 		  case 'L':		/* maximum line length */
846 			m->m_linelimit = atoi(p);
847 			break;
848 
849 		  case 'N':		/* run niceness */
850 			m->m_nice = atoi(p);
851 			break;
852 
853 		  case 'D':		/* working directory */
854 			m->m_execdir = newstr(p);
855 			break;
856 
857 		  case 'C':		/* default charset */
858 			m->m_defcharset = newstr(p);
859 			break;
860 
861 		  case 'T':		/* MTA-Name/Address/Diagnostic types */
862 			m->m_mtatype = newstr(p);
863 			p = strchr(m->m_mtatype, '/');
864 			if (p != NULL)
865 			{
866 				*p++ = '\0';
867 				if (*p == '\0')
868 					p = NULL;
869 			}
870 			if (p == NULL)
871 				m->m_addrtype = m->m_mtatype;
872 			else
873 			{
874 				m->m_addrtype = p;
875 				p = strchr(p, '/');
876 			}
877 			if (p != NULL)
878 			{
879 				*p++ = '\0';
880 				if (*p == '\0')
881 					p = NULL;
882 			}
883 			if (p == NULL)
884 				m->m_diagtype = m->m_mtatype;
885 			else
886 				m->m_diagtype = p;
887 			break;
888 
889 		  case 'U':		/* user id */
890 			if (isascii(*p) && !isdigit(*p))
891 			{
892 				char *q = p;
893 				struct passwd *pw;
894 
895 				while (isascii(*p) && isalnum(*p))
896 					p++;
897 				while (isascii(*p) && isspace(*p))
898 					*p++ = '\0';
899 				if (*p != '\0')
900 					*p++ = '\0';
901 				pw = sm_getpwnam(q);
902 				if (pw == NULL)
903 					syserr("readcf: mailer U= flag: unknown user %s", q);
904 				else
905 				{
906 					m->m_uid = pw->pw_uid;
907 					m->m_gid = pw->pw_gid;
908 				}
909 			}
910 			else
911 			{
912 				auto char *q;
913 
914 				m->m_uid = strtol(p, &q, 0);
915 				p = q;
916 			}
917 			while (isascii(*p) && isspace(*p))
918 				p++;
919 			if (*p == '\0')
920 				break;
921 			if (isascii(*p) && !isdigit(*p))
922 			{
923 				char *q = p;
924 				struct group *gr;
925 
926 				while (isascii(*p) && isalnum(*p))
927 					p++;
928 				*p++ = '\0';
929 				gr = getgrnam(q);
930 				if (gr == NULL)
931 					syserr("readcf: mailer U= flag: unknown group %s", q);
932 				else
933 					m->m_gid = gr->gr_gid;
934 			}
935 			else
936 			{
937 				m->m_gid = strtol(p, NULL, 0);
938 			}
939 			break;
940 		}
941 
942 		p = delimptr;
943 	}
944 
945 	/* do some rationality checking */
946 	if (m->m_argv == NULL)
947 	{
948 		syserr("M%s: A= argument required", m->m_name);
949 		return;
950 	}
951 	if (m->m_mailer == NULL)
952 	{
953 		syserr("M%s: P= argument required", m->m_name);
954 		return;
955 	}
956 
957 	if (NextMailer >= MAXMAILERS)
958 	{
959 		syserr("too many mailers defined (%d max)", MAXMAILERS);
960 		return;
961 	}
962 
963 	/* do some heuristic cleanup for back compatibility */
964 	if (bitnset(M_LIMITS, m->m_flags))
965 	{
966 		if (m->m_linelimit == 0)
967 			m->m_linelimit = SMTPLINELIM;
968 		if (ConfigLevel < 2)
969 			setbitn(M_7BITS, m->m_flags);
970 	}
971 
972 	if (ConfigLevel < 6 &&
973 	    (strcmp(m->m_mailer, "[IPC]") == 0 ||
974 	     strcmp(m->m_mailer, "[TCP]") == 0))
975 	{
976 		if (m->m_mtatype == NULL)
977 			m->m_mtatype = "dns";
978 		if (m->m_addrtype == NULL)
979 			m->m_addrtype = "rfc822";
980 		if (m->m_diagtype == NULL)
981 			m->m_diagtype = "smtp";
982 	}
983 
984 	/* enter the mailer into the symbol table */
985 	s = stab(m->m_name, ST_MAILER, ST_ENTER);
986 	if (s->s_mailer != NULL)
987 	{
988 		i = s->s_mailer->m_mno;
989 		free(s->s_mailer);
990 	}
991 	else
992 	{
993 		i = NextMailer++;
994 	}
995 	Mailer[i] = s->s_mailer = m;
996 	m->m_mno = i;
997 }
998 /*
999 **  MUNCHSTRING -- translate a string into internal form.
1000 **
1001 **	Parameters:
1002 **		p -- the string to munch.
1003 **		delimptr -- if non-NULL, set to the pointer of the
1004 **			field delimiter character.
1005 **
1006 **	Returns:
1007 **		the munched string.
1008 */
1009 
1010 char *
1011 munchstring(p, delimptr)
1012 	register char *p;
1013 	char **delimptr;
1014 {
1015 	register char *q;
1016 	bool backslash = FALSE;
1017 	bool quotemode = FALSE;
1018 	static char buf[MAXLINE];
1019 
1020 	for (q = buf; *p != '\0'; p++)
1021 	{
1022 		if (backslash)
1023 		{
1024 			/* everything is roughly literal */
1025 			backslash = FALSE;
1026 			switch (*p)
1027 			{
1028 			  case 'r':		/* carriage return */
1029 				*q++ = '\r';
1030 				continue;
1031 
1032 			  case 'n':		/* newline */
1033 				*q++ = '\n';
1034 				continue;
1035 
1036 			  case 'f':		/* form feed */
1037 				*q++ = '\f';
1038 				continue;
1039 
1040 			  case 'b':		/* backspace */
1041 				*q++ = '\b';
1042 				continue;
1043 			}
1044 			*q++ = *p;
1045 		}
1046 		else
1047 		{
1048 			if (*p == '\\')
1049 				backslash = TRUE;
1050 			else if (*p == '"')
1051 				quotemode = !quotemode;
1052 			else if (quotemode || *p != ',')
1053 				*q++ = *p;
1054 			else
1055 				break;
1056 		}
1057 	}
1058 
1059 	if (delimptr != NULL)
1060 		*delimptr = p;
1061 	*q++ = '\0';
1062 	return (buf);
1063 }
1064 /*
1065 **  MAKEARGV -- break up a string into words
1066 **
1067 **	Parameters:
1068 **		p -- the string to break up.
1069 **
1070 **	Returns:
1071 **		a char **argv (dynamically allocated)
1072 **
1073 **	Side Effects:
1074 **		munges p.
1075 */
1076 
1077 char **
1078 makeargv(p)
1079 	register char *p;
1080 {
1081 	char *q;
1082 	int i;
1083 	char **avp;
1084 	char *argv[MAXPV + 1];
1085 
1086 	/* take apart the words */
1087 	i = 0;
1088 	while (*p != '\0' && i < MAXPV)
1089 	{
1090 		q = p;
1091 		while (*p != '\0' && !(isascii(*p) && isspace(*p)))
1092 			p++;
1093 		while (isascii(*p) && isspace(*p))
1094 			*p++ = '\0';
1095 		argv[i++] = newstr(q);
1096 	}
1097 	argv[i++] = NULL;
1098 
1099 	/* now make a copy of the argv */
1100 	avp = (char **) xalloc(sizeof *avp * i);
1101 	bcopy((char *) argv, (char *) avp, sizeof *avp * i);
1102 
1103 	return (avp);
1104 }
1105 /*
1106 **  PRINTRULES -- print rewrite rules (for debugging)
1107 **
1108 **	Parameters:
1109 **		none.
1110 **
1111 **	Returns:
1112 **		none.
1113 **
1114 **	Side Effects:
1115 **		prints rewrite rules.
1116 */
1117 
1118 void
1119 printrules()
1120 {
1121 	register struct rewrite *rwp;
1122 	register int ruleset;
1123 
1124 	for (ruleset = 0; ruleset < 10; ruleset++)
1125 	{
1126 		if (RewriteRules[ruleset] == NULL)
1127 			continue;
1128 		printf("\n----Rule Set %d:", ruleset);
1129 
1130 		for (rwp = RewriteRules[ruleset]; rwp != NULL; rwp = rwp->r_next)
1131 		{
1132 			printf("\nLHS:");
1133 			printav(rwp->r_lhs);
1134 			printf("RHS:");
1135 			printav(rwp->r_rhs);
1136 		}
1137 	}
1138 }
1139 /*
1140 **  PRINTMAILER -- print mailer structure (for debugging)
1141 **
1142 **	Parameters:
1143 **		m -- the mailer to print
1144 **
1145 **	Returns:
1146 **		none.
1147 */
1148 
1149 void
1150 printmailer(m)
1151 	register MAILER *m;
1152 {
1153 	int j;
1154 
1155 	printf("mailer %d (%s): P=%s S=%d/%d R=%d/%d M=%ld U=%d:%d F=",
1156 		m->m_mno, m->m_name,
1157 		m->m_mailer, m->m_se_rwset, m->m_sh_rwset,
1158 		m->m_re_rwset, m->m_rh_rwset, m->m_maxsize,
1159 		m->m_uid, m->m_gid);
1160 	for (j = '\0'; j <= '\177'; j++)
1161 		if (bitnset(j, m->m_flags))
1162 			(void) putchar(j);
1163 	printf(" L=%d E=", m->m_linelimit);
1164 	xputs(m->m_eol);
1165 	if (m->m_defcharset != NULL)
1166 		printf(" C=%s", m->m_defcharset);
1167 	printf(" T=%s/%s/%s",
1168 		m->m_mtatype == NULL ? "<undefined>" : m->m_mtatype,
1169 		m->m_addrtype == NULL ? "<undefined>" : m->m_addrtype,
1170 		m->m_diagtype == NULL ? "<undefined>" : m->m_diagtype);
1171 	if (m->m_argv != NULL)
1172 	{
1173 		char **a = m->m_argv;
1174 
1175 		printf(" A=");
1176 		while (*a != NULL)
1177 		{
1178 			if (a != m->m_argv)
1179 				printf(" ");
1180 			xputs(*a++);
1181 		}
1182 	}
1183 	printf("\n");
1184 }
1185 /*
1186 **  SETOPTION -- set global processing option
1187 **
1188 **	Parameters:
1189 **		opt -- option name.
1190 **		val -- option value (as a text string).
1191 **		safe -- set if this came from a configuration file.
1192 **			Some options (if set from the command line) will
1193 **			reset the user id to avoid security problems.
1194 **		sticky -- if set, don't let other setoptions override
1195 **			this value.
1196 **		e -- the main envelope.
1197 **
1198 **	Returns:
1199 **		none.
1200 **
1201 **	Side Effects:
1202 **		Sets options as implied by the arguments.
1203 */
1204 
1205 static BITMAP	StickyOpt;		/* set if option is stuck */
1206 extern void	settimeout __P((char *, char *));
1207 
1208 
1209 #if NAMED_BIND
1210 
1211 struct resolverflags
1212 {
1213 	char	*rf_name;	/* name of the flag */
1214 	long	rf_bits;	/* bits to set/clear */
1215 } ResolverFlags[] =
1216 {
1217 	"debug",	RES_DEBUG,
1218 	"aaonly",	RES_AAONLY,
1219 	"usevc",	RES_USEVC,
1220 	"primary",	RES_PRIMARY,
1221 	"igntc",	RES_IGNTC,
1222 	"recurse",	RES_RECURSE,
1223 	"defnames",	RES_DEFNAMES,
1224 	"stayopen",	RES_STAYOPEN,
1225 	"dnsrch",	RES_DNSRCH,
1226 	"true",		0,		/* to avoid error on old syntax */
1227 	NULL,		0
1228 };
1229 
1230 #endif
1231 
1232 struct optioninfo
1233 {
1234 	char	*o_name;	/* long name of option */
1235 	u_char	o_code;		/* short name of option */
1236 	bool	o_safe;		/* safe for random people to use */
1237 } OptionTab[] =
1238 {
1239 	"SevenBitInput",	'7',		TRUE,
1240 #if MIME8TO7
1241 	"EightBitMode",		'8',		TRUE,
1242 #endif
1243 	"AliasFile",		'A',		FALSE,
1244 	"AliasWait",		'a',		FALSE,
1245 	"BlankSub",		'B',		FALSE,
1246 	"MinFreeBlocks",	'b',		TRUE,
1247 	"CheckpointInterval",	'C',		TRUE,
1248 	"HoldExpensive",	'c',		FALSE,
1249 	"AutoRebuildAliases",	'D',		FALSE,
1250 	"DeliveryMode",		'd',		TRUE,
1251 	"ErrorHeader",		'E',		FALSE,
1252 	"ErrorMode",		'e',		TRUE,
1253 	"TempFileMode",		'F',		FALSE,
1254 	"SaveFromLine",		'f',		FALSE,
1255 	"MatchGECOS",		'G',		FALSE,
1256 	"HelpFile",		'H',		FALSE,
1257 	"MaxHopCount",		'h',		FALSE,
1258 	"ResolverOptions",	'I',		FALSE,
1259 	"IgnoreDots",		'i',		TRUE,
1260 	"ForwardPath",		'J',		FALSE,
1261 	"SendMimeErrors",	'j',		TRUE,
1262 	"ConnectionCacheSize",	'k',		FALSE,
1263 	"ConnectionCacheTimeout", 'K',		FALSE,
1264 	"UseErrorsTo",		'l',		FALSE,
1265 	"LogLevel",		'L',		FALSE,
1266 	"MeToo",		'm',		TRUE,
1267 	"CheckAliases",		'n',		FALSE,
1268 	"OldStyleHeaders",	'o',		TRUE,
1269 	"DaemonPortOptions",	'O',		FALSE,
1270 	"PrivacyOptions",	'p',		TRUE,
1271 	"PostmasterCopy",	'P',		FALSE,
1272 	"QueueFactor",		'q',		FALSE,
1273 	"QueueDirectory",	'Q',		FALSE,
1274 	"DontPruneRoutes",	'R',		FALSE,
1275 	"Timeout",		'r',		TRUE,
1276 	"StatusFile",		'S',		FALSE,
1277 	"SuperSafe",		's',		TRUE,
1278 	"QueueTimeout",		'T',		FALSE,
1279 	"TimeZoneSpec",		't',		FALSE,
1280 	"UserDatabaseSpec",	'U',		FALSE,
1281 	"DefaultUser",		'u',		FALSE,
1282 	"FallbackMXhost",	'V',		FALSE,
1283 	"Verbose",		'v',		TRUE,
1284 	"TryNullMXList",	'w',		TRUE,
1285 	"QueueLA",		'x',		FALSE,
1286 	"RefuseLA",		'X',		FALSE,
1287 	"RecipientFactor",	'y',		FALSE,
1288 	"ForkEachJob",		'Y',		FALSE,
1289 	"ClassFactor",		'z',		FALSE,
1290 	"RetryFactor",		'Z',		FALSE,
1291 #define O_QUEUESORTORD	0x81
1292 	"QueueSortOrder",	O_QUEUESORTORD,	TRUE,
1293 #define O_HOSTSFILE	0x82
1294 	"HostsFile",		O_HOSTSFILE,	FALSE,
1295 #define O_MQA		0x83
1296 	"MinQueueAge",		O_MQA,		TRUE,
1297 #define O_MHSA		0x84
1298 /*
1299 	"MaxHostStatAge",	O_MHSA,		TRUE,
1300 */
1301 #define O_DEFCHARSET	0x85
1302 	"DefaultCharSet",	O_DEFCHARSET,	TRUE,
1303 #define O_SSFILE	0x86
1304 	"ServiceSwitchFile",	O_SSFILE,	FALSE,
1305 #define O_DIALDELAY	0x87
1306 	"DialDelay",		O_DIALDELAY,	TRUE,
1307 #define O_NORCPTACTION	0x88
1308 	"NoRecipientAction",	O_NORCPTACTION,	TRUE,
1309 #define O_SAFEFILEENV	0x89
1310 	"SafeFileEnvironment",	O_SAFEFILEENV,	FALSE,
1311 #define O_MAXMSGSIZE	0x8a
1312 	"MaxMessageSize",	O_MAXMSGSIZE,	FALSE,
1313 #define O_COLONOKINADDR	0x8b
1314 	"ColonOkInAddr",	O_COLONOKINADDR, TRUE,
1315 #define O_MAXQUEUERUN	0x8c
1316 	"MaxQueueRunSize",	O_MAXQUEUERUN,	TRUE,
1317 
1318 	NULL,			'\0',		FALSE,
1319 };
1320 
1321 
1322 
1323 void
1324 setoption(opt, val, safe, sticky, e)
1325 	int opt;
1326 	char *val;
1327 	bool safe;
1328 	bool sticky;
1329 	register ENVELOPE *e;
1330 {
1331 	register char *p;
1332 	register struct optioninfo *o;
1333 	char *subopt;
1334 	extern bool atobool();
1335 	extern time_t convtime();
1336 	extern int QueueLA;
1337 	extern int RefuseLA;
1338 	extern bool Warn_Q_option;
1339 
1340 	errno = 0;
1341 	if (opt == ' ')
1342 	{
1343 		/* full word options */
1344 		struct optioninfo *sel;
1345 
1346 		p = strchr(val, '=');
1347 		if (p == NULL)
1348 			p = &val[strlen(val)];
1349 		while (*--p == ' ')
1350 			continue;
1351 		while (*++p == ' ')
1352 			*p = '\0';
1353 		if (p == val)
1354 		{
1355 			syserr("readcf: null option name");
1356 			return;
1357 		}
1358 		if (*p == '=')
1359 			*p++ = '\0';
1360 		while (*p == ' ')
1361 			p++;
1362 		subopt = strchr(val, '.');
1363 		if (subopt != NULL)
1364 			*subopt++ = '\0';
1365 		sel = NULL;
1366 		for (o = OptionTab; o->o_name != NULL; o++)
1367 		{
1368 			if (strncasecmp(o->o_name, val, strlen(val)) != 0)
1369 				continue;
1370 			if (strlen(o->o_name) == strlen(val))
1371 			{
1372 				/* completely specified -- this must be it */
1373 				sel = NULL;
1374 				break;
1375 			}
1376 			if (sel != NULL)
1377 				break;
1378 			sel = o;
1379 		}
1380 		if (sel != NULL && o->o_name == NULL)
1381 			o = sel;
1382 		else if (o->o_name == NULL)
1383 		{
1384 			syserr("readcf: unknown option name %s", val);
1385 			return;
1386 		}
1387 		else if (sel != NULL)
1388 		{
1389 			syserr("readcf: ambiguous option name %s (matches %s and %s)",
1390 				val, sel->o_name, o->o_name);
1391 			return;
1392 		}
1393 		if (strlen(val) != strlen(o->o_name))
1394 		{
1395 			bool oldVerbose = Verbose;
1396 
1397 			Verbose = TRUE;
1398 			message("Option %s used as abbreviation for %s",
1399 				val, o->o_name);
1400 			Verbose = oldVerbose;
1401 		}
1402 		opt = o->o_code;
1403 		val = p;
1404 	}
1405 	else
1406 	{
1407 		for (o = OptionTab; o->o_name != NULL; o++)
1408 		{
1409 			if (o->o_code == opt)
1410 				break;
1411 		}
1412 		subopt = NULL;
1413 	}
1414 
1415 	if (tTd(37, 1))
1416 	{
1417 		printf(isascii(opt) && isprint(opt) ?
1418 			    "setoption %s (%c).%s=%s" :
1419 			    "setoption %s (0x%x).%s=%s",
1420 			o->o_name == NULL ? "<unknown>" : o->o_name,
1421 			opt,
1422 			subopt == NULL ? "" : subopt,
1423 			val);
1424 	}
1425 
1426 	/*
1427 	**  See if this option is preset for us.
1428 	*/
1429 
1430 	if (!sticky && bitnset(opt, StickyOpt))
1431 	{
1432 		if (tTd(37, 1))
1433 			printf(" (ignored)\n");
1434 		return;
1435 	}
1436 
1437 	/*
1438 	**  Check to see if this option can be specified by this user.
1439 	*/
1440 
1441 	if (!safe && RealUid == 0)
1442 		safe = TRUE;
1443 	if (!safe && !o->o_safe)
1444 	{
1445 		if (opt != 'M' || (val[0] != 'r' && val[0] != 's'))
1446 		{
1447 			if (tTd(37, 1))
1448 				printf(" (unsafe)");
1449 			if (RealUid != geteuid())
1450 			{
1451 				if (tTd(37, 1))
1452 					printf("(Resetting uid)");
1453 				(void) setgid(RealGid);
1454 				(void) setuid(RealUid);
1455 			}
1456 		}
1457 	}
1458 	if (tTd(37, 1))
1459 		printf("\n");
1460 
1461 	switch (opt & 0xff)
1462 	{
1463 	  case '7':		/* force seven-bit input */
1464 		SevenBitInput = atobool(val);
1465 		break;
1466 
1467 #if MIME8TO7
1468 	  case '8':		/* handling of 8-bit input */
1469 		switch (*val)
1470 		{
1471 		  case 'm':		/* convert 8-bit, convert MIME */
1472 			MimeMode = MM_CVTMIME|MM_MIME8BIT;
1473 			break;
1474 
1475 		  case 'p':		/* pass 8 bit, convert MIME */
1476 			MimeMode = MM_CVTMIME|MM_PASS8BIT;
1477 			break;
1478 
1479 		  case 's':		/* strict adherence */
1480 			MimeMode = MM_CVTMIME;
1481 			break;
1482 
1483 #if 0
1484 		  case 'r':		/* reject 8-bit, don't convert MIME */
1485 			MimeMode = 0;
1486 			break;
1487 
1488 		  case 'j':		/* "just send 8" */
1489 			MimeMode = MM_PASS8BIT;
1490 			break;
1491 
1492 		  case 'a':		/* encode 8 bit if available */
1493 			MimeMode = MM_MIME8BIT|MM_PASS8BIT|MM_CVTMIME;
1494 			break;
1495 
1496 		  case 'c':		/* convert 8 bit to MIME, never 7 bit */
1497 			MimeMode = MM_MIME8BIT;
1498 			break;
1499 #endif
1500 
1501 		  default:
1502 			syserr("Unknown 8-bit mode %c", *val);
1503 			exit(EX_USAGE);
1504 		}
1505 		break;
1506 #endif
1507 
1508 	  case 'A':		/* set default alias file */
1509 		if (val[0] == '\0')
1510 			setalias("aliases");
1511 		else
1512 			setalias(val);
1513 		break;
1514 
1515 	  case 'a':		/* look N minutes for "@:@" in alias file */
1516 		if (val[0] == '\0')
1517 			SafeAlias = 5 * 60;		/* five minutes */
1518 		else
1519 			SafeAlias = convtime(val, 'm');
1520 		break;
1521 
1522 	  case 'B':		/* substitution for blank character */
1523 		SpaceSub = val[0];
1524 		if (SpaceSub == '\0')
1525 			SpaceSub = ' ';
1526 		break;
1527 
1528 	  case 'b':		/* min blocks free on queue fs/max msg size */
1529 		p = strchr(val, '/');
1530 		if (p != NULL)
1531 		{
1532 			*p++ = '\0';
1533 			MaxMessageSize = atol(p);
1534 		}
1535 		MinBlocksFree = atol(val);
1536 		break;
1537 
1538 	  case 'c':		/* don't connect to "expensive" mailers */
1539 		NoConnect = atobool(val);
1540 		break;
1541 
1542 	  case 'C':		/* checkpoint every N addresses */
1543 		CheckpointInterval = atoi(val);
1544 		break;
1545 
1546 	  case 'd':		/* delivery mode */
1547 		switch (*val)
1548 		{
1549 		  case '\0':
1550 			e->e_sendmode = SM_DELIVER;
1551 			break;
1552 
1553 		  case SM_QUEUE:	/* queue only */
1554 #ifndef QUEUE
1555 			syserr("need QUEUE to set -odqueue");
1556 #endif /* QUEUE */
1557 			/* fall through..... */
1558 
1559 		  case SM_DELIVER:	/* do everything */
1560 		  case SM_FORK:		/* fork after verification */
1561 			e->e_sendmode = *val;
1562 			break;
1563 
1564 		  default:
1565 			syserr("Unknown delivery mode %c", *val);
1566 			exit(EX_USAGE);
1567 		}
1568 		break;
1569 
1570 	  case 'D':		/* rebuild alias database as needed */
1571 		AutoRebuild = atobool(val);
1572 		break;
1573 
1574 	  case 'E':		/* error message header/header file */
1575 		if (*val != '\0')
1576 			ErrMsgFile = newstr(val);
1577 		break;
1578 
1579 	  case 'e':		/* set error processing mode */
1580 		switch (*val)
1581 		{
1582 		  case EM_QUIET:	/* be silent about it */
1583 		  case EM_MAIL:		/* mail back */
1584 		  case EM_BERKNET:	/* do berknet error processing */
1585 		  case EM_WRITE:	/* write back (or mail) */
1586 		  case EM_PRINT:	/* print errors normally (default) */
1587 			e->e_errormode = *val;
1588 			break;
1589 		}
1590 		break;
1591 
1592 	  case 'F':		/* file mode */
1593 		FileMode = atooct(val) & 0777;
1594 		break;
1595 
1596 	  case 'f':		/* save Unix-style From lines on front */
1597 		SaveFrom = atobool(val);
1598 		break;
1599 
1600 	  case 'G':		/* match recipients against GECOS field */
1601 		MatchGecos = atobool(val);
1602 		break;
1603 
1604 	  case 'g':		/* default gid */
1605   g_opt:
1606 		if (isascii(*val) && isdigit(*val))
1607 			DefGid = atoi(val);
1608 		else
1609 		{
1610 			register struct group *gr;
1611 
1612 			DefGid = -1;
1613 			gr = getgrnam(val);
1614 			if (gr == NULL)
1615 				syserr("readcf: option %c: unknown group %s",
1616 					opt, val);
1617 			else
1618 				DefGid = gr->gr_gid;
1619 		}
1620 		break;
1621 
1622 	  case 'H':		/* help file */
1623 		if (val[0] == '\0')
1624 			HelpFile = "sendmail.hf";
1625 		else
1626 			HelpFile = newstr(val);
1627 		break;
1628 
1629 	  case 'h':		/* maximum hop count */
1630 		MaxHopCount = atoi(val);
1631 		break;
1632 
1633 	  case 'I':		/* use internet domain name server */
1634 #if NAMED_BIND
1635 		for (p = val; *p != 0; )
1636 		{
1637 			bool clearmode;
1638 			char *q;
1639 			struct resolverflags *rfp;
1640 
1641 			while (*p == ' ')
1642 				p++;
1643 			if (*p == '\0')
1644 				break;
1645 			clearmode = FALSE;
1646 			if (*p == '-')
1647 				clearmode = TRUE;
1648 			else if (*p != '+')
1649 				p--;
1650 			p++;
1651 			q = p;
1652 			while (*p != '\0' && !(isascii(*p) && isspace(*p)))
1653 				p++;
1654 			if (*p != '\0')
1655 				*p++ = '\0';
1656 			if (strcasecmp(q, "HasWildcardMX") == 0)
1657 			{
1658 				NoMXforCanon = !clearmode;
1659 				continue;
1660 			}
1661 			for (rfp = ResolverFlags; rfp->rf_name != NULL; rfp++)
1662 			{
1663 				if (strcasecmp(q, rfp->rf_name) == 0)
1664 					break;
1665 			}
1666 			if (rfp->rf_name == NULL)
1667 				syserr("readcf: I option value %s unrecognized", q);
1668 			else if (clearmode)
1669 				_res.options &= ~rfp->rf_bits;
1670 			else
1671 				_res.options |= rfp->rf_bits;
1672 		}
1673 		if (tTd(8, 2))
1674 			printf("_res.options = %x, HasWildcardMX = %d\n",
1675 				_res.options, !NoMXforCanon);
1676 #else
1677 		usrerr("name server (I option) specified but BIND not compiled in");
1678 #endif
1679 		break;
1680 
1681 	  case 'i':		/* ignore dot lines in message */
1682 		IgnrDot = atobool(val);
1683 		break;
1684 
1685 	  case 'j':		/* send errors in MIME (RFC 1341) format */
1686 		SendMIMEErrors = atobool(val);
1687 		break;
1688 
1689 	  case 'J':		/* .forward search path */
1690 		ForwardPath = newstr(val);
1691 		break;
1692 
1693 	  case 'k':		/* connection cache size */
1694 		MaxMciCache = atoi(val);
1695 		if (MaxMciCache < 0)
1696 			MaxMciCache = 0;
1697 		break;
1698 
1699 	  case 'K':		/* connection cache timeout */
1700 		MciCacheTimeout = convtime(val, 'm');
1701 		break;
1702 
1703 	  case 'l':		/* use Errors-To: header */
1704 		UseErrorsTo = atobool(val);
1705 		break;
1706 
1707 	  case 'L':		/* log level */
1708 		if (safe || LogLevel < atoi(val))
1709 			LogLevel = atoi(val);
1710 		break;
1711 
1712 	  case 'M':		/* define macro */
1713 		p = newstr(&val[1]);
1714 		if (!safe)
1715 			cleanstrcpy(p, p, MAXNAME);
1716 		define(val[0], p, CurEnv);
1717 		sticky = FALSE;
1718 		break;
1719 
1720 	  case 'm':		/* send to me too */
1721 		MeToo = atobool(val);
1722 		break;
1723 
1724 	  case 'n':		/* validate RHS in newaliases */
1725 		CheckAliases = atobool(val);
1726 		break;
1727 
1728 	    /* 'N' available -- was "net name" */
1729 
1730 	  case 'O':		/* daemon options */
1731 		setdaemonoptions(val);
1732 		break;
1733 
1734 	  case 'o':		/* assume old style headers */
1735 		if (atobool(val))
1736 			CurEnv->e_flags |= EF_OLDSTYLE;
1737 		else
1738 			CurEnv->e_flags &= ~EF_OLDSTYLE;
1739 		break;
1740 
1741 	  case 'p':		/* select privacy level */
1742 		p = val;
1743 		for (;;)
1744 		{
1745 			register struct prival *pv;
1746 			extern struct prival PrivacyValues[];
1747 
1748 			while (isascii(*p) && (isspace(*p) || ispunct(*p)))
1749 				p++;
1750 			if (*p == '\0')
1751 				break;
1752 			val = p;
1753 			while (isascii(*p) && isalnum(*p))
1754 				p++;
1755 			if (*p != '\0')
1756 				*p++ = '\0';
1757 
1758 			for (pv = PrivacyValues; pv->pv_name != NULL; pv++)
1759 			{
1760 				if (strcasecmp(val, pv->pv_name) == 0)
1761 					break;
1762 			}
1763 			if (pv->pv_name == NULL)
1764 				syserr("readcf: Op line: %s unrecognized", val);
1765 			PrivacyFlags |= pv->pv_flag;
1766 		}
1767 		sticky = FALSE;
1768 		break;
1769 
1770 	  case 'P':		/* postmaster copy address for returned mail */
1771 		PostMasterCopy = newstr(val);
1772 		break;
1773 
1774 	  case 'q':		/* slope of queue only function */
1775 		QueueFactor = atoi(val);
1776 		break;
1777 
1778 	  case 'Q':		/* queue directory */
1779 		if (val[0] == '\0')
1780 			QueueDir = "mqueue";
1781 		else
1782 			QueueDir = newstr(val);
1783 		if (RealUid != 0 && !safe)
1784 			Warn_Q_option = TRUE;
1785 		break;
1786 
1787 	  case 'R':		/* don't prune routes */
1788 		DontPruneRoutes = atobool(val);
1789 		break;
1790 
1791 	  case 'r':		/* read timeout */
1792 		if (subopt == NULL)
1793 			inittimeouts(val);
1794 		else
1795 			settimeout(subopt, val);
1796 		break;
1797 
1798 	  case 'S':		/* status file */
1799 		if (val[0] == '\0')
1800 			StatFile = "sendmail.st";
1801 		else
1802 			StatFile = newstr(val);
1803 		break;
1804 
1805 	  case 's':		/* be super safe, even if expensive */
1806 		SuperSafe = atobool(val);
1807 		break;
1808 
1809 	  case 'T':		/* queue timeout */
1810 		p = strchr(val, '/');
1811 		if (p != NULL)
1812 		{
1813 			*p++ = '\0';
1814 			settimeout("queuewarn", p);
1815 		}
1816 		settimeout("queuereturn", val);
1817 		break;
1818 
1819 	  case 't':		/* time zone name */
1820 		TimeZoneSpec = newstr(val);
1821 		break;
1822 
1823 	  case 'U':		/* location of user database */
1824 		UdbSpec = newstr(val);
1825 		break;
1826 
1827 	  case 'u':		/* set default uid */
1828 		for (p = val; *p != '\0'; p++)
1829 		{
1830 			if (*p == '.' || *p == '/' || *p == ':')
1831 			{
1832 				*p++ = '\0';
1833 				break;
1834 			}
1835 		}
1836 		if (isascii(*val) && isdigit(*val))
1837 			DefUid = atoi(val);
1838 		else
1839 		{
1840 			register struct passwd *pw;
1841 
1842 			DefUid = -1;
1843 			pw = sm_getpwnam(val);
1844 			if (pw == NULL)
1845 				syserr("readcf: option u: unknown user %s", val);
1846 			else
1847 			{
1848 				DefUid = pw->pw_uid;
1849 				DefGid = pw->pw_gid;
1850 			}
1851 		}
1852 		setdefuser();
1853 
1854 		/* handle the group if it is there */
1855 		if (*p == '\0')
1856 			break;
1857 		val = p;
1858 		goto g_opt;
1859 
1860 	  case 'V':		/* fallback MX host */
1861 		FallBackMX = newstr(val);
1862 		break;
1863 
1864 	  case 'v':		/* run in verbose mode */
1865 		Verbose = atobool(val);
1866 		break;
1867 
1868 	  case 'w':		/* if we are best MX, try host directly */
1869 		TryNullMXList = atobool(val);
1870 		break;
1871 
1872 	    /* 'W' available -- was wizard password */
1873 
1874 	  case 'x':		/* load avg at which to auto-queue msgs */
1875 		QueueLA = atoi(val);
1876 		break;
1877 
1878 	  case 'X':		/* load avg at which to auto-reject connections */
1879 		RefuseLA = atoi(val);
1880 		break;
1881 
1882 	  case 'y':		/* work recipient factor */
1883 		WkRecipFact = atoi(val);
1884 		break;
1885 
1886 	  case 'Y':		/* fork jobs during queue runs */
1887 		ForkQueueRuns = atobool(val);
1888 		break;
1889 
1890 	  case 'z':		/* work message class factor */
1891 		WkClassFact = atoi(val);
1892 		break;
1893 
1894 	  case 'Z':		/* work time factor */
1895 		WkTimeFact = atoi(val);
1896 		break;
1897 
1898 	  case O_QUEUESORTORD:	/* queue sorting order */
1899 		switch (*val)
1900 		{
1901 		  case 'h':	/* Host first */
1902 		  case 'H':
1903 			QueueSortOrder = QS_BYHOST;
1904 			break;
1905 
1906 		  case 'p':	/* Priority order */
1907 		  case 'P':
1908 			QueueSortOrder = QS_BYPRIORITY;
1909 			break;
1910 
1911 		  default:
1912 			syserr("Invalid queue sort order \"%s\"", val);
1913 		}
1914 		break;
1915 
1916 	  case O_HOSTSFILE:	/* pathname of /etc/hosts file */
1917 		HostsFile = newstr(val);
1918 		break;
1919 
1920 	  case O_MQA:		/* minimum queue age between deliveries */
1921 		MinQueueAge = convtime(val, 'm');
1922 		break;
1923 
1924 	  case O_MHSA:		/* maximum age of cached host status */
1925 		MaxHostStatAge = convtime(val, 'm');
1926 		break;
1927 
1928 	  case O_DEFCHARSET:	/* default character set for mimefying */
1929 		DefaultCharSet = newstr(denlstring(val, TRUE, TRUE));
1930 		break;
1931 
1932 	  case O_SSFILE:	/* service switch file */
1933 		ServiceSwitchFile = newstr(val);
1934 		break;
1935 
1936 	  case O_DIALDELAY:	/* delay for dial-on-demand operation */
1937 		DialDelay = convtime(val, 's');
1938 		break;
1939 
1940 	  case O_NORCPTACTION:	/* what to do if no recipient */
1941 		if (strcasecmp(val, "none") == 0)
1942 			NoRecipientAction = NRA_NO_ACTION;
1943 		else if (strcasecmp(val, "add-to") == 0)
1944 			NoRecipientAction = NRA_ADD_TO;
1945 		else if (strcasecmp(val, "add-apparently-to") == 0)
1946 			NoRecipientAction = NRA_ADD_APPARENTLY_TO;
1947 		else if (strcasecmp(val, "add-bcc") == 0)
1948 			NoRecipientAction = NRA_ADD_BCC;
1949 		else if (strcasecmp(val, "add-to-undisclosed") == 0)
1950 			NoRecipientAction = NRA_ADD_TO_UNDISCLOSED;
1951 		else
1952 			syserr("Invalid NoRecipientAction: %s", val);
1953 
1954 	  case O_SAFEFILEENV:	/* chroot() environ for writing to files */
1955 		SafeFileEnv = newstr(val);
1956 		break;
1957 
1958 	  case O_MAXMSGSIZE:	/* maximum message size */
1959 		MaxMessageSize = atol(val);
1960 		break;
1961 
1962 	  case O_COLONOKINADDR:	/* old style handling of colon addresses */
1963 		ColonOkInAddr = atobool(val);
1964 		break;
1965 
1966 	  case O_MAXQUEUERUN:	/* max # of jobs in a single queue run */
1967 		MaxQueueRun = atol(val);
1968 		break;
1969 
1970 	  default:
1971 		if (tTd(37, 1))
1972 		{
1973 			if (isascii(opt) && isprint(opt))
1974 				printf("Warning: option %c unknown\n", opt);
1975 			else
1976 				printf("Warning: option 0x%x unknown\n", opt);
1977 		}
1978 		break;
1979 	}
1980 	if (sticky)
1981 		setbitn(opt, StickyOpt);
1982 }
1983 /*
1984 **  SETCLASS -- set a string into a class
1985 **
1986 **	Parameters:
1987 **		class -- the class to put the string in.
1988 **		str -- the string to enter
1989 **
1990 **	Returns:
1991 **		none.
1992 **
1993 **	Side Effects:
1994 **		puts the word into the symbol table.
1995 */
1996 
1997 void
1998 setclass(class, str)
1999 	int class;
2000 	char *str;
2001 {
2002 	register STAB *s;
2003 
2004 	if (tTd(37, 8))
2005 		printf("setclass(%c, %s)\n", class, str);
2006 	s = stab(str, ST_CLASS, ST_ENTER);
2007 	setbitn(class, s->s_class);
2008 }
2009 /*
2010 **  MAKEMAPENTRY -- create a map entry
2011 **
2012 **	Parameters:
2013 **		line -- the config file line
2014 **
2015 **	Returns:
2016 **		A pointer to the map that has been created.
2017 **		NULL if there was a syntax error.
2018 **
2019 **	Side Effects:
2020 **		Enters the map into the dictionary.
2021 */
2022 
2023 MAP *
2024 makemapentry(line)
2025 	char *line;
2026 {
2027 	register char *p;
2028 	char *mapname;
2029 	char *classname;
2030 	register STAB *s;
2031 	STAB *class;
2032 
2033 	for (p = line; isascii(*p) && isspace(*p); p++)
2034 		continue;
2035 	if (!(isascii(*p) && isalnum(*p)))
2036 	{
2037 		syserr("readcf: config K line: no map name");
2038 		return NULL;
2039 	}
2040 
2041 	mapname = p;
2042 	while ((isascii(*++p) && isalnum(*p)) || *p == '.')
2043 		continue;
2044 	if (*p != '\0')
2045 		*p++ = '\0';
2046 	while (isascii(*p) && isspace(*p))
2047 		p++;
2048 	if (!(isascii(*p) && isalnum(*p)))
2049 	{
2050 		syserr("readcf: config K line, map %s: no map class", mapname);
2051 		return NULL;
2052 	}
2053 	classname = p;
2054 	while (isascii(*++p) && isalnum(*p))
2055 		continue;
2056 	if (*p != '\0')
2057 		*p++ = '\0';
2058 	while (isascii(*p) && isspace(*p))
2059 		p++;
2060 
2061 	/* look up the class */
2062 	class = stab(classname, ST_MAPCLASS, ST_FIND);
2063 	if (class == NULL)
2064 	{
2065 		syserr("readcf: map %s: class %s not available", mapname, classname);
2066 		return NULL;
2067 	}
2068 
2069 	/* enter the map */
2070 	s = stab(mapname, ST_MAP, ST_ENTER);
2071 	s->s_map.map_class = &class->s_mapclass;
2072 	s->s_map.map_mname = newstr(mapname);
2073 
2074 	if (class->s_mapclass.map_parse(&s->s_map, p))
2075 		s->s_map.map_mflags |= MF_VALID;
2076 
2077 	if (tTd(37, 5))
2078 	{
2079 		printf("map %s, class %s, flags %x, file %s,\n",
2080 			s->s_map.map_mname, s->s_map.map_class->map_cname,
2081 			s->s_map.map_mflags,
2082 			s->s_map.map_file == NULL ? "(null)" : s->s_map.map_file);
2083 		printf("\tapp %s, domain %s, rebuild %s\n",
2084 			s->s_map.map_app == NULL ? "(null)" : s->s_map.map_app,
2085 			s->s_map.map_domain == NULL ? "(null)" : s->s_map.map_domain,
2086 			s->s_map.map_rebuild == NULL ? "(null)" : s->s_map.map_rebuild);
2087 	}
2088 
2089 	return &s->s_map;
2090 }
2091 /*
2092 **  STRTORWSET -- convert string to rewriting set number
2093 **
2094 **	Parameters:
2095 **		p -- the pointer to the string to decode.
2096 **		endp -- if set, store the trailing delimiter here.
2097 **		stabmode -- ST_ENTER to create this entry, ST_FIND if
2098 **			it must already exist.
2099 **
2100 **	Returns:
2101 **		The appropriate ruleset number.
2102 **		-1 if it is not valid (error already printed)
2103 */
2104 
2105 int
2106 strtorwset(p, endp, stabmode)
2107 	char *p;
2108 	char **endp;
2109 	int stabmode;
2110 {
2111 	int ruleset;
2112 	static int nextruleset = MAXRWSETS;
2113 
2114 	while (isascii(*p) && isspace(*p))
2115 		p++;
2116 	if (!isascii(*p))
2117 	{
2118 		syserr("invalid ruleset name: \"%.20s\"", p);
2119 		return -1;
2120 	}
2121 	if (isdigit(*p))
2122 	{
2123 		ruleset = strtol(p, endp, 10);
2124 		if (ruleset >= MAXRWSETS / 2 || ruleset < 0)
2125 		{
2126 			syserr("bad ruleset %d (%d max)",
2127 				ruleset, MAXRWSETS / 2);
2128 			ruleset = -1;
2129 		}
2130 	}
2131 	else
2132 	{
2133 		STAB *s;
2134 		char delim;
2135 		char *q;
2136 
2137 		q = p;
2138 		while (*p != '\0' && isascii(*p) &&
2139 		       (isalnum(*p) || strchr("-_$", *p) != NULL))
2140 			p++;
2141 		while (isascii(*p) && isspace(*p))
2142 			*p++ = '\0';
2143 		delim = *p;
2144 		if (delim != '\0')
2145 			*p = '\0';
2146 		s = stab(q, ST_RULESET, stabmode);
2147 		if (delim != '\0')
2148 			*p = delim;
2149 
2150 		if (s == NULL)
2151 		{
2152 			syserr("unknown ruleset %s", q);
2153 			return -1;
2154 		}
2155 
2156 		if (stabmode == ST_ENTER && delim == '=')
2157 		{
2158 			ruleset = strtol(p, endp, 10);
2159 			if (ruleset >= MAXRWSETS / 2 || ruleset < 0)
2160 			{
2161 				syserr("bad ruleset %s = %d (%d max)",
2162 					q, ruleset, MAXRWSETS / 2);
2163 				ruleset = -1;
2164 			}
2165 		}
2166 		else
2167 		{
2168 			if (endp != NULL)
2169 				*endp = p;
2170 			if (s->s_ruleset > 0)
2171 				ruleset = s->s_ruleset;
2172 			else if ((ruleset = --nextruleset) < MAXRWSETS / 2)
2173 			{
2174 				syserr("%s: too many named rulesets (%d max)",
2175 					q, MAXRWSETS / 2);
2176 				ruleset = -1;
2177 			}
2178 		}
2179 		if (s->s_ruleset > 0 && ruleset >= 0 && ruleset != s->s_ruleset)
2180 		{
2181 			syserr("%s: ruleset changed value (old %d, new %d)",
2182 				q, ruleset, s->s_ruleset);
2183 			ruleset = s->s_ruleset;
2184 		}
2185 		else if (ruleset > 0)
2186 		{
2187 			s->s_ruleset = ruleset;
2188 		}
2189 	}
2190 	return ruleset;
2191 }
2192 /*
2193 **  INITTIMEOUTS -- parse and set timeout values
2194 **
2195 **	Parameters:
2196 **		val -- a pointer to the values.  If NULL, do initial
2197 **			settings.
2198 **
2199 **	Returns:
2200 **		none.
2201 **
2202 **	Side Effects:
2203 **		Initializes the TimeOuts structure
2204 */
2205 
2206 #define SECONDS
2207 #define MINUTES	* 60
2208 #define HOUR	* 3600
2209 
2210 void
2211 inittimeouts(val)
2212 	register char *val;
2213 {
2214 	register char *p;
2215 	extern time_t convtime();
2216 
2217 	if (val == NULL)
2218 	{
2219 		TimeOuts.to_initial = (time_t) 5 MINUTES;
2220 		TimeOuts.to_helo = (time_t) 5 MINUTES;
2221 		TimeOuts.to_mail = (time_t) 10 MINUTES;
2222 		TimeOuts.to_rcpt = (time_t) 1 HOUR;
2223 		TimeOuts.to_datainit = (time_t) 5 MINUTES;
2224 		TimeOuts.to_datablock = (time_t) 1 HOUR;
2225 		TimeOuts.to_datafinal = (time_t) 1 HOUR;
2226 		TimeOuts.to_rset = (time_t) 5 MINUTES;
2227 		TimeOuts.to_quit = (time_t) 2 MINUTES;
2228 		TimeOuts.to_nextcommand = (time_t) 1 HOUR;
2229 		TimeOuts.to_miscshort = (time_t) 2 MINUTES;
2230 #if IDENTPROTO
2231 		TimeOuts.to_ident = (time_t) 30 SECONDS;
2232 #else
2233 		TimeOuts.to_ident = (time_t) 0 SECONDS;
2234 #endif
2235 		TimeOuts.to_fileopen = (time_t) 60 SECONDS;
2236 		return;
2237 	}
2238 
2239 	for (;; val = p)
2240 	{
2241 		while (isascii(*val) && isspace(*val))
2242 			val++;
2243 		if (*val == '\0')
2244 			break;
2245 		for (p = val; *p != '\0' && *p != ','; p++)
2246 			continue;
2247 		if (*p != '\0')
2248 			*p++ = '\0';
2249 
2250 		if (isascii(*val) && isdigit(*val))
2251 		{
2252 			/* old syntax -- set everything */
2253 			TimeOuts.to_mail = convtime(val, 'm');
2254 			TimeOuts.to_rcpt = TimeOuts.to_mail;
2255 			TimeOuts.to_datainit = TimeOuts.to_mail;
2256 			TimeOuts.to_datablock = TimeOuts.to_mail;
2257 			TimeOuts.to_datafinal = TimeOuts.to_mail;
2258 			TimeOuts.to_nextcommand = TimeOuts.to_mail;
2259 			continue;
2260 		}
2261 		else
2262 		{
2263 			register char *q = strchr(val, ':');
2264 
2265 			if (q == NULL && (q = strchr(val, '=')) == NULL)
2266 			{
2267 				/* syntax error */
2268 				continue;
2269 			}
2270 			*q++ = '\0';
2271 			settimeout(val, q);
2272 		}
2273 	}
2274 }
2275 /*
2276 **  SETTIMEOUT -- set an individual timeout
2277 **
2278 **	Parameters:
2279 **		name -- the name of the timeout.
2280 **		val -- the value of the timeout.
2281 **
2282 **	Returns:
2283 **		none.
2284 */
2285 
2286 void
2287 settimeout(name, val)
2288 	char *name;
2289 	char *val;
2290 {
2291 	register char *p;
2292 	time_t to;
2293 	extern time_t convtime();
2294 
2295 	to = convtime(val, 'm');
2296 	p = strchr(name, '.');
2297 	if (p != NULL)
2298 		*p++ = '\0';
2299 
2300 	if (strcasecmp(name, "initial") == 0)
2301 		TimeOuts.to_initial = to;
2302 	else if (strcasecmp(name, "mail") == 0)
2303 		TimeOuts.to_mail = to;
2304 	else if (strcasecmp(name, "rcpt") == 0)
2305 		TimeOuts.to_rcpt = to;
2306 	else if (strcasecmp(name, "datainit") == 0)
2307 		TimeOuts.to_datainit = to;
2308 	else if (strcasecmp(name, "datablock") == 0)
2309 		TimeOuts.to_datablock = to;
2310 	else if (strcasecmp(name, "datafinal") == 0)
2311 		TimeOuts.to_datafinal = to;
2312 	else if (strcasecmp(name, "command") == 0)
2313 		TimeOuts.to_nextcommand = to;
2314 	else if (strcasecmp(name, "rset") == 0)
2315 		TimeOuts.to_rset = to;
2316 	else if (strcasecmp(name, "helo") == 0)
2317 		TimeOuts.to_helo = to;
2318 	else if (strcasecmp(name, "quit") == 0)
2319 		TimeOuts.to_quit = to;
2320 	else if (strcasecmp(name, "misc") == 0)
2321 		TimeOuts.to_miscshort = to;
2322 	else if (strcasecmp(name, "ident") == 0)
2323 		TimeOuts.to_ident = to;
2324 	else if (strcasecmp(name, "fileopen") == 0)
2325 		TimeOuts.to_fileopen = to;
2326 	else if (strcasecmp(name, "queuewarn") == 0)
2327 	{
2328 		to = convtime(val, 'h');
2329 		if (p == NULL || strcmp(p, "*") == 0)
2330 		{
2331 			TimeOuts.to_q_warning[TOC_NORMAL] = to;
2332 			TimeOuts.to_q_warning[TOC_URGENT] = to;
2333 			TimeOuts.to_q_warning[TOC_NONURGENT] = to;
2334 		}
2335 		else if (strcasecmp(p, "normal") == 0)
2336 			TimeOuts.to_q_warning[TOC_NORMAL] = to;
2337 		else if (strcasecmp(p, "urgent") == 0)
2338 			TimeOuts.to_q_warning[TOC_URGENT] = to;
2339 		else if (strcasecmp(p, "non-urgent") == 0)
2340 			TimeOuts.to_q_warning[TOC_NONURGENT] = to;
2341 		else
2342 			syserr("settimeout: invalid queuewarn subtimeout %s", p);
2343 	}
2344 	else if (strcasecmp(name, "queuereturn") == 0)
2345 	{
2346 		to = convtime(val, 'd');
2347 		if (p == NULL || strcmp(p, "*") == 0)
2348 		{
2349 			TimeOuts.to_q_return[TOC_NORMAL] = to;
2350 			TimeOuts.to_q_return[TOC_URGENT] = to;
2351 			TimeOuts.to_q_return[TOC_NONURGENT] = to;
2352 		}
2353 		else if (strcasecmp(p, "normal") == 0)
2354 			TimeOuts.to_q_return[TOC_NORMAL] = to;
2355 		else if (strcasecmp(p, "urgent") == 0)
2356 			TimeOuts.to_q_return[TOC_URGENT] = to;
2357 		else if (strcasecmp(p, "non-urgent") == 0)
2358 			TimeOuts.to_q_return[TOC_NONURGENT] = to;
2359 		else
2360 			syserr("settimeout: invalid queuereturn subtimeout %s", p);
2361 	}
2362 	else
2363 		syserr("settimeout: invalid timeout %s", name);
2364 }
2365