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