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