xref: /csrg-svn/usr.sbin/sendmail/src/readcf.c (revision 67736)
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.37 (Berkeley) 08/23/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 	errno = 0;
1161 	if (opt == ' ')
1162 	{
1163 		/* full word options */
1164 		struct optioninfo *sel;
1165 
1166 		p = strchr(val, '=');
1167 		if (p == NULL)
1168 			p = &val[strlen(val)];
1169 		while (*--p == ' ')
1170 			continue;
1171 		while (*++p == ' ')
1172 			*p = '\0';
1173 		if (p == val)
1174 		{
1175 			syserr("readcf: null option name");
1176 			return;
1177 		}
1178 		if (*p == '=')
1179 			*p++ = '\0';
1180 		while (*p == ' ')
1181 			p++;
1182 		sel = NULL;
1183 		for (o = OptionTab; o->o_name != NULL; o++)
1184 		{
1185 			if (strncasecmp(o->o_name, val, strlen(val)) != 0)
1186 				continue;
1187 			if (strlen(o->o_name) == strlen(val))
1188 			{
1189 				/* completely specified -- this must be it */
1190 				sel = NULL;
1191 				break;
1192 			}
1193 			if (sel != NULL)
1194 				break;
1195 			sel = o;
1196 		}
1197 		if (sel != NULL && o->o_name == NULL)
1198 			o = sel;
1199 		else if (o->o_name == NULL)
1200 			syserr("readcf: unknown option name %s", val);
1201 		else if (sel != NULL)
1202 		{
1203 			syserr("readcf: ambiguous option name %s (matches %s and %s)",
1204 				val, sel->o_name, o->o_name);
1205 			return;
1206 		}
1207 		if (strlen(val) != strlen(o->o_name))
1208 		{
1209 			bool oldVerbose = Verbose;
1210 
1211 			Verbose = TRUE;
1212 			message("Option %s used as abbreviation for %s",
1213 				val, o->o_name);
1214 			Verbose = oldVerbose;
1215 		}
1216 		opt = o->o_code;
1217 		val = p;
1218 	}
1219 	else
1220 	{
1221 		for (o = OptionTab; o->o_name != NULL; o++)
1222 		{
1223 			if (o->o_code == opt)
1224 				break;
1225 		}
1226 	}
1227 
1228 	if (tTd(37, 1))
1229 	{
1230 		printf(isascii(opt) && isprint(opt) ?
1231 			    "setoption %s (%c)=%s" : "setoption %s (0x%x)=%s",
1232 			o->o_name == NULL ? "<unknown>" : o->o_name,
1233 			opt, val);
1234 	}
1235 
1236 	/*
1237 	**  See if this option is preset for us.
1238 	*/
1239 
1240 	if (!sticky && bitnset(opt, StickyOpt))
1241 	{
1242 		if (tTd(37, 1))
1243 			printf(" (ignored)\n");
1244 		return;
1245 	}
1246 
1247 	/*
1248 	**  Check to see if this option can be specified by this user.
1249 	*/
1250 
1251 	if (!safe && RealUid == 0)
1252 		safe = TRUE;
1253 	if (!safe && !o->o_safe)
1254 	{
1255 		if (opt != 'M' || (val[0] != 'r' && val[0] != 's'))
1256 		{
1257 			if (tTd(37, 1))
1258 				printf(" (unsafe)");
1259 			if (RealUid != geteuid())
1260 			{
1261 				if (tTd(37, 1))
1262 					printf("(Resetting uid)");
1263 				(void) setgid(RealGid);
1264 				(void) setuid(RealUid);
1265 			}
1266 		}
1267 	}
1268 	if (tTd(37, 1))
1269 		printf("\n");
1270 
1271 	switch (opt & 0xff)
1272 	{
1273 	  case '7':		/* force seven-bit input */
1274 		SevenBitInput = atobool(val);
1275 		break;
1276 
1277 	  case '8':		/* handling of 8-bit input */
1278 		switch (*val)
1279 		{
1280 		  case 'r':		/* reject 8-bit, don't convert MIME */
1281 			MimeMode = 0;
1282 			break;
1283 
1284 		  case 'm':		/* convert 8-bit, convert MIME */
1285 			MimeMode = MM_CVTMIME|MM_MIME8BIT;
1286 			break;
1287 
1288 		  case 'j':		/* "just send 8" */
1289 			MimeMode = MM_PASS8BIT;
1290 			break;
1291 
1292 		  case 'p':		/* pass 8 bit, convert MIME */
1293 			MimeMode = MM_PASS8BIT|MM_CVTMIME;
1294 			break;
1295 
1296 		  case 's':		/* strict adherence */
1297 			MimeMode = MM_CVTMIME;
1298 			break;
1299 
1300 		  case 'a':		/* encode 8 bit if available */
1301 			MimeMode = MM_MIME8BIT|MM_PASS8BIT|MM_CVTMIME;
1302 			break;
1303 
1304 		  case 'c':		/* convert 8 bit to MIME, never 7 bit */
1305 			MimeMode = MM_MIME8BIT;
1306 			break;
1307 
1308 		  default:
1309 			syserr("Unknown 8-bit mode %c", *val);
1310 			exit(EX_USAGE);
1311 		}
1312 		break;
1313 
1314 	  case 'A':		/* set default alias file */
1315 		if (val[0] == '\0')
1316 			setalias("aliases");
1317 		else
1318 			setalias(val);
1319 		break;
1320 
1321 	  case 'a':		/* look N minutes for "@:@" in alias file */
1322 		if (val[0] == '\0')
1323 			SafeAlias = 5 * 60;		/* five minutes */
1324 		else
1325 			SafeAlias = convtime(val, 'm');
1326 		break;
1327 
1328 	  case 'B':		/* substitution for blank character */
1329 		SpaceSub = val[0];
1330 		if (SpaceSub == '\0')
1331 			SpaceSub = ' ';
1332 		break;
1333 
1334 	  case 'b':		/* min blocks free on queue fs/max msg size */
1335 		p = strchr(val, '/');
1336 		if (p != NULL)
1337 		{
1338 			*p++ = '\0';
1339 			MaxMessageSize = atol(p);
1340 		}
1341 		MinBlocksFree = atol(val);
1342 		break;
1343 
1344 	  case 'c':		/* don't connect to "expensive" mailers */
1345 		NoConnect = atobool(val);
1346 		break;
1347 
1348 	  case 'C':		/* checkpoint every N addresses */
1349 		CheckpointInterval = atoi(val);
1350 		break;
1351 
1352 	  case 'd':		/* delivery mode */
1353 		switch (*val)
1354 		{
1355 		  case '\0':
1356 			e->e_sendmode = SM_DELIVER;
1357 			break;
1358 
1359 		  case SM_QUEUE:	/* queue only */
1360 #ifndef QUEUE
1361 			syserr("need QUEUE to set -odqueue");
1362 #endif /* QUEUE */
1363 			/* fall through..... */
1364 
1365 		  case SM_DELIVER:	/* do everything */
1366 		  case SM_FORK:		/* fork after verification */
1367 			e->e_sendmode = *val;
1368 			break;
1369 
1370 		  default:
1371 			syserr("Unknown delivery mode %c", *val);
1372 			exit(EX_USAGE);
1373 		}
1374 		break;
1375 
1376 	  case 'D':		/* rebuild alias database as needed */
1377 		AutoRebuild = atobool(val);
1378 		break;
1379 
1380 	  case 'E':		/* error message header/header file */
1381 		if (*val != '\0')
1382 			ErrMsgFile = newstr(val);
1383 		break;
1384 
1385 	  case 'e':		/* set error processing mode */
1386 		switch (*val)
1387 		{
1388 		  case EM_QUIET:	/* be silent about it */
1389 		  case EM_MAIL:		/* mail back */
1390 		  case EM_BERKNET:	/* do berknet error processing */
1391 		  case EM_WRITE:	/* write back (or mail) */
1392 		  case EM_PRINT:	/* print errors normally (default) */
1393 			e->e_errormode = *val;
1394 			break;
1395 		}
1396 		break;
1397 
1398 	  case 'F':		/* file mode */
1399 		FileMode = atooct(val) & 0777;
1400 		break;
1401 
1402 	  case 'f':		/* save Unix-style From lines on front */
1403 		SaveFrom = atobool(val);
1404 		break;
1405 
1406 	  case 'G':		/* match recipients against GECOS field */
1407 		MatchGecos = atobool(val);
1408 		break;
1409 
1410 	  case 'g':		/* default gid */
1411 		if (isascii(*val) && isdigit(*val))
1412 			DefGid = atoi(val);
1413 		else
1414 		{
1415 			register struct group *gr;
1416 
1417 			DefGid = -1;
1418 			gr = getgrnam(val);
1419 			if (gr == NULL)
1420 				syserr("readcf: option g: unknown group %s", val);
1421 			else
1422 				DefGid = gr->gr_gid;
1423 		}
1424 		break;
1425 
1426 	  case 'H':		/* help file */
1427 		if (val[0] == '\0')
1428 			HelpFile = "sendmail.hf";
1429 		else
1430 			HelpFile = newstr(val);
1431 		break;
1432 
1433 	  case 'h':		/* maximum hop count */
1434 		MaxHopCount = atoi(val);
1435 		break;
1436 
1437 	  case 'I':		/* use internet domain name server */
1438 #if NAMED_BIND
1439 		UseNameServer = TRUE;
1440 		for (p = val; *p != 0; )
1441 		{
1442 			bool clearmode;
1443 			char *q;
1444 			struct resolverflags *rfp;
1445 
1446 			while (*p == ' ')
1447 				p++;
1448 			if (*p == '\0')
1449 				break;
1450 			clearmode = FALSE;
1451 			if (*p == '-')
1452 				clearmode = TRUE;
1453 			else if (*p != '+')
1454 				p--;
1455 			p++;
1456 			q = p;
1457 			while (*p != '\0' && !(isascii(*p) && isspace(*p)))
1458 				p++;
1459 			if (*p != '\0')
1460 				*p++ = '\0';
1461 			for (rfp = ResolverFlags; rfp->rf_name != NULL; rfp++)
1462 			{
1463 				if (strcasecmp(q, rfp->rf_name) == 0)
1464 					break;
1465 			}
1466 			if (rfp->rf_name == NULL)
1467 				syserr("readcf: I option value %s unrecognized", q);
1468 			else if (clearmode)
1469 				_res.options &= ~rfp->rf_bits;
1470 			else
1471 				_res.options |= rfp->rf_bits;
1472 		}
1473 		if (tTd(8, 2))
1474 			printf("_res.options = %x\n", _res.options);
1475 #else
1476 		usrerr("name server (I option) specified but BIND not compiled in");
1477 #endif
1478 		break;
1479 
1480 	  case 'i':		/* ignore dot lines in message */
1481 		IgnrDot = atobool(val);
1482 		break;
1483 
1484 	  case 'j':		/* send errors in MIME (RFC 1341) format */
1485 		SendMIMEErrors = atobool(val);
1486 		break;
1487 
1488 	  case 'J':		/* .forward search path */
1489 		ForwardPath = newstr(val);
1490 		break;
1491 
1492 	  case 'k':		/* connection cache size */
1493 		MaxMciCache = atoi(val);
1494 		if (MaxMciCache < 0)
1495 			MaxMciCache = 0;
1496 		break;
1497 
1498 	  case 'K':		/* connection cache timeout */
1499 		MciCacheTimeout = convtime(val, 'm');
1500 		break;
1501 
1502 	  case 'l':		/* use Errors-To: header */
1503 		UseErrorsTo = atobool(val);
1504 		break;
1505 
1506 	  case 'L':		/* log level */
1507 		if (safe || LogLevel < atoi(val))
1508 			LogLevel = atoi(val);
1509 		break;
1510 
1511 	  case 'M':		/* define macro */
1512 		define(val[0], newstr(&val[1]), CurEnv);
1513 		sticky = FALSE;
1514 		break;
1515 
1516 	  case 'm':		/* send to me too */
1517 		MeToo = atobool(val);
1518 		break;
1519 
1520 	  case 'n':		/* validate RHS in newaliases */
1521 		CheckAliases = atobool(val);
1522 		break;
1523 
1524 	    /* 'N' available -- was "net name" */
1525 
1526 	  case 'O':		/* daemon options */
1527 		setdaemonoptions(val);
1528 		break;
1529 
1530 	  case 'o':		/* assume old style headers */
1531 		if (atobool(val))
1532 			CurEnv->e_flags |= EF_OLDSTYLE;
1533 		else
1534 			CurEnv->e_flags &= ~EF_OLDSTYLE;
1535 		break;
1536 
1537 	  case 'p':		/* select privacy level */
1538 		p = val;
1539 		for (;;)
1540 		{
1541 			register struct prival *pv;
1542 			extern struct prival PrivacyValues[];
1543 
1544 			while (isascii(*p) && (isspace(*p) || ispunct(*p)))
1545 				p++;
1546 			if (*p == '\0')
1547 				break;
1548 			val = p;
1549 			while (isascii(*p) && isalnum(*p))
1550 				p++;
1551 			if (*p != '\0')
1552 				*p++ = '\0';
1553 
1554 			for (pv = PrivacyValues; pv->pv_name != NULL; pv++)
1555 			{
1556 				if (strcasecmp(val, pv->pv_name) == 0)
1557 					break;
1558 			}
1559 			if (pv->pv_name == NULL)
1560 				syserr("readcf: Op line: %s unrecognized", val);
1561 			PrivacyFlags |= pv->pv_flag;
1562 		}
1563 		break;
1564 
1565 	  case 'P':		/* postmaster copy address for returned mail */
1566 		PostMasterCopy = newstr(val);
1567 		break;
1568 
1569 	  case 'q':		/* slope of queue only function */
1570 		QueueFactor = atoi(val);
1571 		break;
1572 
1573 	  case 'Q':		/* queue directory */
1574 		if (val[0] == '\0')
1575 			QueueDir = "mqueue";
1576 		else
1577 			QueueDir = newstr(val);
1578 		if (RealUid != 0 && !safe)
1579 			Warn_Q_option = TRUE;
1580 		break;
1581 
1582 	  case 'R':		/* don't prune routes */
1583 		DontPruneRoutes = atobool(val);
1584 		break;
1585 
1586 	  case 'r':		/* read timeout */
1587 		settimeouts(val);
1588 		break;
1589 
1590 	  case 'S':		/* status file */
1591 		if (val[0] == '\0')
1592 			StatFile = "sendmail.st";
1593 		else
1594 			StatFile = newstr(val);
1595 		break;
1596 
1597 	  case 's':		/* be super safe, even if expensive */
1598 		SuperSafe = atobool(val);
1599 		break;
1600 
1601 	  case 'T':		/* queue timeout */
1602 		p = strchr(val, '/');
1603 		if (p != NULL)
1604 		{
1605 			*p++ = '\0';
1606 			TimeOuts.to_q_warning[TOC_NORMAL] = convtime(p, 'd');
1607 		}
1608 		TimeOuts.to_q_return[TOC_NORMAL] = convtime(val, 'h');
1609 		break;
1610 
1611 	  case 't':		/* time zone name */
1612 		TimeZoneSpec = newstr(val);
1613 		break;
1614 
1615 	  case 'U':		/* location of user database */
1616 		UdbSpec = newstr(val);
1617 		break;
1618 
1619 	  case 'u':		/* set default uid */
1620 		if (isascii(*val) && isdigit(*val))
1621 			DefUid = atoi(val);
1622 		else
1623 		{
1624 			register struct passwd *pw;
1625 
1626 			DefUid = -1;
1627 			pw = getpwnam(val);
1628 			if (pw == NULL)
1629 				syserr("readcf: option u: unknown user %s", val);
1630 			else
1631 				DefUid = pw->pw_uid;
1632 		}
1633 		setdefuser();
1634 		break;
1635 
1636 	  case 'V':		/* fallback MX host */
1637 		FallBackMX = newstr(val);
1638 		break;
1639 
1640 	  case 'v':		/* run in verbose mode */
1641 		Verbose = atobool(val);
1642 		break;
1643 
1644 	  case 'w':		/* if we are best MX, try host directly */
1645 		TryNullMXList = atobool(val);
1646 		break;
1647 
1648 	    /* 'W' available -- was wizard password */
1649 
1650 	  case 'x':		/* load avg at which to auto-queue msgs */
1651 		QueueLA = atoi(val);
1652 		break;
1653 
1654 	  case 'X':		/* load avg at which to auto-reject connections */
1655 		RefuseLA = atoi(val);
1656 		break;
1657 
1658 	  case 'y':		/* work recipient factor */
1659 		WkRecipFact = atoi(val);
1660 		break;
1661 
1662 	  case 'Y':		/* fork jobs during queue runs */
1663 		ForkQueueRuns = atobool(val);
1664 		break;
1665 
1666 	  case 'z':		/* work message class factor */
1667 		WkClassFact = atoi(val);
1668 		break;
1669 
1670 	  case 'Z':		/* work time factor */
1671 		WkTimeFact = atoi(val);
1672 		break;
1673 
1674 	  case O_BSP:		/* SMTP Peers can't handle 2-line greeting */
1675 		BrokenSmtpPeers = atobool(val);
1676 		break;
1677 
1678 	  case O_SQBH:		/* sort work queue by host first */
1679 		SortQueueByHost = atobool(val);
1680 		break;
1681 
1682 	  case O_DNICE:		/* delivery nice value */
1683 		DeliveryNiceness = atoi(val);
1684 		break;
1685 
1686 	  case O_MQA:		/* minimum queue age between deliveries */
1687 		MinQueueAge = convtime(val, 'm');
1688 		break;
1689 
1690 	  case O_MHSA:		/* maximum age of cached host status */
1691 		MaxHostStatAge = convtime(val, 'm');
1692 		break;
1693 
1694 	  default:
1695 		break;
1696 	}
1697 	if (sticky)
1698 		setbitn(opt, StickyOpt);
1699 	return;
1700 }
1701 /*
1702 **  SETCLASS -- set a word into a class
1703 **
1704 **	Parameters:
1705 **		class -- the class to put the word in.
1706 **		word -- the word to enter
1707 **
1708 **	Returns:
1709 **		none.
1710 **
1711 **	Side Effects:
1712 **		puts the word into the symbol table.
1713 */
1714 
1715 setclass(class, word)
1716 	int class;
1717 	char *word;
1718 {
1719 	register STAB *s;
1720 
1721 	if (tTd(37, 8))
1722 		printf("setclass(%c, %s)\n", class, word);
1723 	s = stab(word, ST_CLASS, ST_ENTER);
1724 	setbitn(class, s->s_class);
1725 }
1726 /*
1727 **  MAKEMAPENTRY -- create a map entry
1728 **
1729 **	Parameters:
1730 **		line -- the config file line
1731 **
1732 **	Returns:
1733 **		TRUE if it successfully entered the map entry.
1734 **		FALSE otherwise (usually syntax error).
1735 **
1736 **	Side Effects:
1737 **		Enters the map into the dictionary.
1738 */
1739 
1740 void
1741 makemapentry(line)
1742 	char *line;
1743 {
1744 	register char *p;
1745 	char *mapname;
1746 	char *classname;
1747 	register STAB *s;
1748 	STAB *class;
1749 
1750 	for (p = line; isascii(*p) && isspace(*p); p++)
1751 		continue;
1752 	if (!(isascii(*p) && isalnum(*p)))
1753 	{
1754 		syserr("readcf: config K line: no map name");
1755 		return;
1756 	}
1757 
1758 	mapname = p;
1759 	while (isascii(*++p) && isalnum(*p))
1760 		continue;
1761 	if (*p != '\0')
1762 		*p++ = '\0';
1763 	while (isascii(*p) && isspace(*p))
1764 		p++;
1765 	if (!(isascii(*p) && isalnum(*p)))
1766 	{
1767 		syserr("readcf: config K line, map %s: no map class", mapname);
1768 		return;
1769 	}
1770 	classname = p;
1771 	while (isascii(*++p) && isalnum(*p))
1772 		continue;
1773 	if (*p != '\0')
1774 		*p++ = '\0';
1775 	while (isascii(*p) && isspace(*p))
1776 		p++;
1777 
1778 	/* look up the class */
1779 	class = stab(classname, ST_MAPCLASS, ST_FIND);
1780 	if (class == NULL)
1781 	{
1782 		syserr("readcf: map %s: class %s not available", mapname, classname);
1783 		return;
1784 	}
1785 
1786 	/* enter the map */
1787 	s = stab(mapname, ST_MAP, ST_ENTER);
1788 	s->s_map.map_class = &class->s_mapclass;
1789 	s->s_map.map_mname = newstr(mapname);
1790 
1791 	if (class->s_mapclass.map_parse(&s->s_map, p))
1792 		s->s_map.map_mflags |= MF_VALID;
1793 
1794 	if (tTd(37, 5))
1795 	{
1796 		printf("map %s, class %s, flags %x, file %s,\n",
1797 			s->s_map.map_mname, s->s_map.map_class->map_cname,
1798 			s->s_map.map_mflags,
1799 			s->s_map.map_file == NULL ? "(null)" : s->s_map.map_file);
1800 		printf("\tapp %s, domain %s, rebuild %s\n",
1801 			s->s_map.map_app == NULL ? "(null)" : s->s_map.map_app,
1802 			s->s_map.map_domain == NULL ? "(null)" : s->s_map.map_domain,
1803 			s->s_map.map_rebuild == NULL ? "(null)" : s->s_map.map_rebuild);
1804 	}
1805 }
1806 /*
1807 **  SETTIMEOUTS -- parse and set timeout values
1808 **
1809 **	Parameters:
1810 **		val -- a pointer to the values.  If NULL, do initial
1811 **			settings.
1812 **
1813 **	Returns:
1814 **		none.
1815 **
1816 **	Side Effects:
1817 **		Initializes the TimeOuts structure
1818 */
1819 
1820 #define SECONDS
1821 #define MINUTES	* 60
1822 #define HOUR	* 3600
1823 
1824 settimeouts(val)
1825 	register char *val;
1826 {
1827 	register char *p;
1828 	extern time_t convtime();
1829 
1830 	if (val == NULL)
1831 	{
1832 		TimeOuts.to_initial = (time_t) 5 MINUTES;
1833 		TimeOuts.to_helo = (time_t) 5 MINUTES;
1834 		TimeOuts.to_mail = (time_t) 10 MINUTES;
1835 		TimeOuts.to_rcpt = (time_t) 1 HOUR;
1836 		TimeOuts.to_datainit = (time_t) 5 MINUTES;
1837 		TimeOuts.to_datablock = (time_t) 1 HOUR;
1838 		TimeOuts.to_datafinal = (time_t) 1 HOUR;
1839 		TimeOuts.to_rset = (time_t) 5 MINUTES;
1840 		TimeOuts.to_quit = (time_t) 2 MINUTES;
1841 		TimeOuts.to_nextcommand = (time_t) 1 HOUR;
1842 		TimeOuts.to_miscshort = (time_t) 2 MINUTES;
1843 		TimeOuts.to_ident = (time_t) 30 SECONDS;
1844 		TimeOuts.to_fileopen = (time_t) 60 SECONDS;
1845 		return;
1846 	}
1847 
1848 	for (;; val = p)
1849 	{
1850 		while (isascii(*val) && isspace(*val))
1851 			val++;
1852 		if (*val == '\0')
1853 			break;
1854 		for (p = val; *p != '\0' && *p != ','; p++)
1855 			continue;
1856 		if (*p != '\0')
1857 			*p++ = '\0';
1858 
1859 		if (isascii(*val) && isdigit(*val))
1860 		{
1861 			/* old syntax -- set everything */
1862 			TimeOuts.to_mail = convtime(val, 'm');
1863 			TimeOuts.to_rcpt = TimeOuts.to_mail;
1864 			TimeOuts.to_datainit = TimeOuts.to_mail;
1865 			TimeOuts.to_datablock = TimeOuts.to_mail;
1866 			TimeOuts.to_datafinal = TimeOuts.to_mail;
1867 			TimeOuts.to_nextcommand = TimeOuts.to_mail;
1868 			continue;
1869 		}
1870 		else
1871 		{
1872 			register char *q = strchr(val, ':');
1873 			time_t to;
1874 
1875 			if (q == NULL && (q = strchr(val, '=')) == NULL)
1876 			{
1877 				/* syntax error */
1878 				continue;
1879 			}
1880 			*q++ = '\0';
1881 			to = convtime(q, 'm');
1882 
1883 			if (strcasecmp(val, "initial") == 0)
1884 				TimeOuts.to_initial = to;
1885 			else if (strcasecmp(val, "mail") == 0)
1886 				TimeOuts.to_mail = to;
1887 			else if (strcasecmp(val, "rcpt") == 0)
1888 				TimeOuts.to_rcpt = to;
1889 			else if (strcasecmp(val, "datainit") == 0)
1890 				TimeOuts.to_datainit = to;
1891 			else if (strcasecmp(val, "datablock") == 0)
1892 				TimeOuts.to_datablock = to;
1893 			else if (strcasecmp(val, "datafinal") == 0)
1894 				TimeOuts.to_datafinal = to;
1895 			else if (strcasecmp(val, "command") == 0)
1896 				TimeOuts.to_nextcommand = to;
1897 			else if (strcasecmp(val, "rset") == 0)
1898 				TimeOuts.to_rset = to;
1899 			else if (strcasecmp(val, "helo") == 0)
1900 				TimeOuts.to_helo = to;
1901 			else if (strcasecmp(val, "quit") == 0)
1902 				TimeOuts.to_quit = to;
1903 			else if (strcasecmp(val, "misc") == 0)
1904 				TimeOuts.to_miscshort = to;
1905 			else if (strcasecmp(val, "ident") == 0)
1906 				TimeOuts.to_ident = to;
1907 			else if (strcasecmp(val, "fileopen") == 0)
1908 				TimeOuts.to_fileopen = to;
1909 			else if (strcasecmp(val, "queuewarn") == 0)
1910 				TimeOuts.to_q_warning[TOC_NORMAL] = to;
1911 			else if (strcasecmp(val, "queuereturn") == 0)
1912 				TimeOuts.to_q_return[TOC_NORMAL] = to;
1913 			else if (strcasecmp(val, "queuewarn.normal") == 0)
1914 				TimeOuts.to_q_warning[TOC_NORMAL] = to;
1915 			else if (strcasecmp(val, "queuereturn.normal") == 0)
1916 				TimeOuts.to_q_return[TOC_NORMAL] = to;
1917 			else if (strcasecmp(val, "queuewarn.urgent") == 0)
1918 				TimeOuts.to_q_warning[TOC_URGENT] = to;
1919 			else if (strcasecmp(val, "queuereturn.urgent") == 0)
1920 				TimeOuts.to_q_return[TOC_URGENT] = to;
1921 			else if (strcasecmp(val, "queuewarn.non-urgent") == 0)
1922 				TimeOuts.to_q_warning[TOC_NONURGENT] = to;
1923 			else if (strcasecmp(val, "queuereturn.non-urgent") == 0)
1924 				TimeOuts.to_q_return[TOC_NONURGENT] = to;
1925 			else
1926 				syserr("settimeouts: invalid timeout %s", val);
1927 		}
1928 	}
1929 }
1930