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