xref: /csrg-svn/usr.sbin/sendmail/src/util.c (revision 69748)
1 /*
2  * Copyright (c) 1983, 1995 Eric P. Allman
3  * Copyright (c) 1988, 1993
4  *	The Regents of the University of California.  All rights reserved.
5  *
6  * %sccs.include.redist.c%
7  */
8 
9 #ifndef lint
10 static char sccsid[] = "@(#)util.c	8.72 (Berkeley) 05/28/95";
11 #endif /* not lint */
12 
13 # include "sendmail.h"
14 # include <sysexits.h>
15 /*
16 **  STRIPQUOTES -- Strip quotes & quote bits from a string.
17 **
18 **	Runs through a string and strips off unquoted quote
19 **	characters and quote bits.  This is done in place.
20 **
21 **	Parameters:
22 **		s -- the string to strip.
23 **
24 **	Returns:
25 **		none.
26 **
27 **	Side Effects:
28 **		none.
29 **
30 **	Called By:
31 **		deliver
32 */
33 
34 void
35 stripquotes(s)
36 	char *s;
37 {
38 	register char *p;
39 	register char *q;
40 	register char c;
41 
42 	if (s == NULL)
43 		return;
44 
45 	p = q = s;
46 	do
47 	{
48 		c = *p++;
49 		if (c == '\\')
50 			c = *p++;
51 		else if (c == '"')
52 			continue;
53 		*q++ = c;
54 	} while (c != '\0');
55 }
56 /*
57 **  XALLOC -- Allocate memory and bitch wildly on failure.
58 **
59 **	THIS IS A CLUDGE.  This should be made to give a proper
60 **	error -- but after all, what can we do?
61 **
62 **	Parameters:
63 **		sz -- size of area to allocate.
64 **
65 **	Returns:
66 **		pointer to data region.
67 **
68 **	Side Effects:
69 **		Memory is allocated.
70 */
71 
72 char *
73 xalloc(sz)
74 	register int sz;
75 {
76 	register char *p;
77 
78 	/* some systems can't handle size zero mallocs */
79 	if (sz <= 0)
80 		sz = 1;
81 
82 	p = malloc((unsigned) sz);
83 	if (p == NULL)
84 	{
85 		syserr("Out of memory!!");
86 		abort();
87 		/* exit(EX_UNAVAILABLE); */
88 	}
89 	return (p);
90 }
91 /*
92 **  COPYPLIST -- copy list of pointers.
93 **
94 **	This routine is the equivalent of newstr for lists of
95 **	pointers.
96 **
97 **	Parameters:
98 **		list -- list of pointers to copy.
99 **			Must be NULL terminated.
100 **		copycont -- if TRUE, copy the contents of the vector
101 **			(which must be a string) also.
102 **
103 **	Returns:
104 **		a copy of 'list'.
105 **
106 **	Side Effects:
107 **		none.
108 */
109 
110 char **
111 copyplist(list, copycont)
112 	char **list;
113 	bool copycont;
114 {
115 	register char **vp;
116 	register char **newvp;
117 
118 	for (vp = list; *vp != NULL; vp++)
119 		continue;
120 
121 	vp++;
122 
123 	newvp = (char **) xalloc((int) (vp - list) * sizeof *vp);
124 	bcopy((char *) list, (char *) newvp, (int) (vp - list) * sizeof *vp);
125 
126 	if (copycont)
127 	{
128 		for (vp = newvp; *vp != NULL; vp++)
129 			*vp = newstr(*vp);
130 	}
131 
132 	return (newvp);
133 }
134 /*
135 **  COPYQUEUE -- copy address queue.
136 **
137 **	This routine is the equivalent of newstr for address queues
138 **	addresses marked with QDONTSEND aren't copied
139 **
140 **	Parameters:
141 **		addr -- list of address structures to copy.
142 **
143 **	Returns:
144 **		a copy of 'addr'.
145 **
146 **	Side Effects:
147 **		none.
148 */
149 
150 ADDRESS *
151 copyqueue(addr)
152 	ADDRESS *addr;
153 {
154 	register ADDRESS *newaddr;
155 	ADDRESS *ret;
156 	register ADDRESS **tail = &ret;
157 
158 	while (addr != NULL)
159 	{
160 		if (!bitset(QDONTSEND, addr->q_flags))
161 		{
162 			newaddr = (ADDRESS *) xalloc(sizeof(ADDRESS));
163 			STRUCTCOPY(*addr, *newaddr);
164 			*tail = newaddr;
165 			tail = &newaddr->q_next;
166 		}
167 		addr = addr->q_next;
168 	}
169 	*tail = NULL;
170 
171 	return ret;
172 }
173 /*
174 **  PRINTAV -- print argument vector.
175 **
176 **	Parameters:
177 **		av -- argument vector.
178 **
179 **	Returns:
180 **		none.
181 **
182 **	Side Effects:
183 **		prints av.
184 */
185 
186 void
187 printav(av)
188 	register char **av;
189 {
190 	while (*av != NULL)
191 	{
192 		if (tTd(0, 44))
193 			printf("\n\t%08x=", *av);
194 		else
195 			(void) putchar(' ');
196 		xputs(*av++);
197 	}
198 	(void) putchar('\n');
199 }
200 /*
201 **  LOWER -- turn letter into lower case.
202 **
203 **	Parameters:
204 **		c -- character to turn into lower case.
205 **
206 **	Returns:
207 **		c, in lower case.
208 **
209 **	Side Effects:
210 **		none.
211 */
212 
213 char
214 lower(c)
215 	register char c;
216 {
217 	return((isascii(c) && isupper(c)) ? tolower(c) : c);
218 }
219 /*
220 **  XPUTS -- put string doing control escapes.
221 **
222 **	Parameters:
223 **		s -- string to put.
224 **
225 **	Returns:
226 **		none.
227 **
228 **	Side Effects:
229 **		output to stdout
230 */
231 
232 void
233 xputs(s)
234 	register const char *s;
235 {
236 	register int c;
237 	register struct metamac *mp;
238 	extern struct metamac MetaMacros[];
239 
240 	if (s == NULL)
241 	{
242 		printf("<null>");
243 		return;
244 	}
245 	while ((c = (*s++ & 0377)) != '\0')
246 	{
247 		if (!isascii(c))
248 		{
249 			if (c == MATCHREPL)
250 			{
251 				putchar('$');
252 				continue;
253 			}
254 			if (c == MACROEXPAND)
255 			{
256 				putchar('$');
257 				if (bitset(0200, *s))
258 					printf("{%s}", macname(*s++ & 0377));
259 				continue;
260 			}
261 			for (mp = MetaMacros; mp->metaname != '\0'; mp++)
262 			{
263 				if ((mp->metaval & 0377) == c)
264 				{
265 					printf("$%c", mp->metaname);
266 					break;
267 				}
268 			}
269 			if (mp->metaname != '\0')
270 				continue;
271 			(void) putchar('\\');
272 			c &= 0177;
273 		}
274 		if (isprint(c))
275 		{
276 			putchar(c);
277 			continue;
278 		}
279 
280 		/* wasn't a meta-macro -- find another way to print it */
281 		switch (c)
282 		{
283 		  case '\n':
284 			c = 'n';
285 			break;
286 
287 		  case '\r':
288 			c = 'r';
289 			break;
290 
291 		  case '\t':
292 			c = 't';
293 			break;
294 
295 		  default:
296 			(void) putchar('^');
297 			(void) putchar(c ^ 0100);
298 			continue;
299 		}
300 		(void) putchar('\\');
301 		(void) putchar(c);
302 	}
303 	(void) fflush(stdout);
304 }
305 /*
306 **  MAKELOWER -- Translate a line into lower case
307 **
308 **	Parameters:
309 **		p -- the string to translate.  If NULL, return is
310 **			immediate.
311 **
312 **	Returns:
313 **		none.
314 **
315 **	Side Effects:
316 **		String pointed to by p is translated to lower case.
317 **
318 **	Called By:
319 **		parse
320 */
321 
322 void
323 makelower(p)
324 	register char *p;
325 {
326 	register char c;
327 
328 	if (p == NULL)
329 		return;
330 	for (; (c = *p) != '\0'; p++)
331 		if (isascii(c) && isupper(c))
332 			*p = tolower(c);
333 }
334 /*
335 **  BUILDFNAME -- build full name from gecos style entry.
336 **
337 **	This routine interprets the strange entry that would appear
338 **	in the GECOS field of the password file.
339 **
340 **	Parameters:
341 **		p -- name to build.
342 **		login -- the login name of this user (for &).
343 **		buf -- place to put the result.
344 **
345 **	Returns:
346 **		none.
347 **
348 **	Side Effects:
349 **		none.
350 */
351 
352 void
353 buildfname(gecos, login, buf)
354 	register char *gecos;
355 	char *login;
356 	char *buf;
357 {
358 	register char *p;
359 	register char *bp = buf;
360 	int l;
361 
362 	if (*gecos == '*')
363 		gecos++;
364 
365 	/* find length of final string */
366 	l = 0;
367 	for (p = gecos; *p != '\0' && *p != ',' && *p != ';' && *p != '%'; p++)
368 	{
369 		if (*p == '&')
370 			l += strlen(login);
371 		else
372 			l++;
373 	}
374 
375 	/* now fill in buf */
376 	for (p = gecos; *p != '\0' && *p != ',' && *p != ';' && *p != '%'; p++)
377 	{
378 		if (*p == '&')
379 		{
380 			(void) strcpy(bp, login);
381 			*bp = toupper(*bp);
382 			while (*bp != '\0')
383 				bp++;
384 		}
385 		else
386 			*bp++ = *p;
387 	}
388 	*bp = '\0';
389 }
390 /*
391 **  SAFEFILE -- return true if a file exists and is safe for a user.
392 **
393 **	Parameters:
394 **		fn -- filename to check.
395 **		uid -- user id to compare against.
396 **		gid -- group id to compare against.
397 **		uname -- user name to compare against (used for group
398 **			sets).
399 **		flags -- modifiers:
400 **			SFF_MUSTOWN -- "uid" must own this file.
401 **			SFF_NOSLINK -- file cannot be a symbolic link.
402 **		mode -- mode bits that must match.
403 **		st -- if set, points to a stat structure that will
404 **			get the stat info for the file.
405 **
406 **	Returns:
407 **		0 if fn exists, is owned by uid, and matches mode.
408 **		An errno otherwise.  The actual errno is cleared.
409 **
410 **	Side Effects:
411 **		none.
412 */
413 
414 #include <grp.h>
415 
416 #ifndef S_IXOTH
417 # define S_IXOTH	(S_IEXEC >> 6)
418 #endif
419 
420 #ifndef S_IXGRP
421 # define S_IXGRP	(S_IEXEC >> 3)
422 #endif
423 
424 #ifndef S_IXUSR
425 # define S_IXUSR	(S_IEXEC)
426 #endif
427 
428 #define ST_MODE_NOFILE	0171147		/* unlikely to occur */
429 
430 int
431 safefile(fn, uid, gid, uname, flags, mode, st)
432 	char *fn;
433 	uid_t uid;
434 	gid_t gid;
435 	char *uname;
436 	int flags;
437 	int mode;
438 	struct stat *st;
439 {
440 	register char *p;
441 	register struct group *gr = NULL;
442 	struct stat stbuf;
443 
444 	if (tTd(54, 4))
445 		printf("safefile(%s, uid=%d, gid=%d, flags=%x, mode=%o):\n",
446 			fn, uid, gid, flags, mode);
447 	errno = 0;
448 	if (st == NULL)
449 		st = &stbuf;
450 
451 	if (!bitset(SFF_NOPATHCHECK, flags) ||
452 	    (uid == 0 && !bitset(SFF_ROOTOK, flags)))
453 	{
454 		/* check the path to the file for acceptability */
455 		for (p = fn; (p = strchr(++p, '/')) != NULL; *p = '/')
456 		{
457 			*p = '\0';
458 			if (stat(fn, &stbuf) < 0)
459 				break;
460 			if (uid == 0 && bitset(S_IWGRP|S_IWOTH, stbuf.st_mode))
461 				message("051 WARNING: writable directory %s",
462 					fn);
463 			if (uid == 0 && !bitset(SFF_ROOTOK, flags))
464 			{
465 				if (bitset(S_IXOTH, stbuf.st_mode))
466 					continue;
467 				break;
468 			}
469 			if (stbuf.st_uid == uid &&
470 			    bitset(S_IXUSR, stbuf.st_mode))
471 				continue;
472 			if (stbuf.st_gid == gid &&
473 			    bitset(S_IXGRP, stbuf.st_mode))
474 				continue;
475 #ifndef NO_GROUP_SET
476 			if (uname != NULL &&
477 			    ((gr != NULL && gr->gr_gid == stbuf.st_gid) ||
478 			     (gr = getgrgid(stbuf.st_gid)) != NULL))
479 			{
480 				register char **gp;
481 
482 				for (gp = gr->gr_mem; gp != NULL && *gp != NULL; gp++)
483 					if (strcmp(*gp, uname) == 0)
484 						break;
485 				if (gp != NULL && *gp != NULL &&
486 				    bitset(S_IXGRP, stbuf.st_mode))
487 					continue;
488 			}
489 #endif
490 			if (!bitset(S_IXOTH, stbuf.st_mode))
491 				break;
492 		}
493 		if (p != NULL)
494 		{
495 			int ret = errno;
496 
497 			if (ret == 0)
498 				ret = EACCES;
499 			if (tTd(54, 4))
500 				printf("\t[dir %s] %s\n", fn, errstring(ret));
501 			*p = '/';
502 			return ret;
503 		}
504 	}
505 
506 #ifdef HASLSTAT
507 	if ((bitset(SFF_NOSLINK, flags) ? lstat(fn, st)
508 					: stat(fn, st)) < 0)
509 #else
510 	if (stat(fn, st) < 0)
511 #endif
512 	{
513 		int ret = errno;
514 
515 		if (tTd(54, 4))
516 			printf("\t%s\n", errstring(ret));
517 
518 		errno = 0;
519 		if (!bitset(SFF_CREAT, flags))
520 			return ret;
521 
522 		/* check to see if legal to create the file */
523 		p = strrchr(fn, '/');
524 		if (p == NULL)
525 			return ENOTDIR;
526 		*p = '\0';
527 		if (stat(fn, &stbuf) >= 0)
528 		{
529 			int md = S_IWRITE|S_IEXEC;
530 			if (stbuf.st_uid != uid)
531 				md >>= 6;
532 			if ((stbuf.st_mode & md) != md)
533 				errno = EACCES;
534 		}
535 		ret = errno;
536 		if (tTd(54, 4))
537 			printf("\t[final dir %s uid %d mode %o] %s\n",
538 				fn, stbuf.st_uid, stbuf.st_mode,
539 				errstring(ret));
540 		*p = '/';
541 		st->st_mode = ST_MODE_NOFILE;
542 		return ret;
543 	}
544 
545 #ifdef S_ISLNK
546 	if (bitset(SFF_NOSLINK, flags) && S_ISLNK(st->st_mode))
547 	{
548 		if (tTd(54, 4))
549 			printf("\t[slink mode %o]\tEPERM\n", st->st_mode);
550 		return EPERM;
551 	}
552 #endif
553 	if (bitset(SFF_REGONLY, flags) && !S_ISREG(st->st_mode))
554 	{
555 		if (tTd(54, 4))
556 			printf("\t[non-reg mode %o]\tEPERM\n", st->st_mode);
557 		return EPERM;
558 	}
559 	if (bitset(S_IWUSR|S_IWGRP|S_IWOTH, mode) && bitset(0111, st->st_mode))
560 	{
561 		if (tTd(29, 5))
562 			printf("failed (mode %o: x bits)\n", st->st_mode);
563 		return EPERM;
564 	}
565 
566 	if (bitset(SFF_SETUIDOK, flags))
567 	{
568 		if (bitset(S_ISUID, st->st_mode) &&
569 		    (st->st_uid != 0 || bitset(SFF_ROOTOK, flags)))
570 		{
571 			uid = st->st_uid;
572 			uname = NULL;
573 		}
574 		if (bitset(S_ISGID, st->st_mode) &&
575 		    (st->st_gid != 0 || bitset(SFF_ROOTOK, flags)))
576 			gid = st->st_gid;
577 	}
578 
579 	if (uid == 0 && !bitset(SFF_ROOTOK, flags))
580 		mode >>= 6;
581 	else if (st->st_uid != uid)
582 	{
583 		mode >>= 3;
584 		if (st->st_gid == gid)
585 			;
586 #ifndef NO_GROUP_SET
587 		else if (uname != NULL &&
588 			 ((gr != NULL && gr->gr_gid == st->st_gid) ||
589 			  (gr = getgrgid(st->st_gid)) != NULL))
590 		{
591 			register char **gp;
592 
593 			for (gp = gr->gr_mem; *gp != NULL; gp++)
594 				if (strcmp(*gp, uname) == 0)
595 					break;
596 			if (*gp == NULL)
597 				mode >>= 3;
598 		}
599 #endif
600 		else
601 			mode >>= 3;
602 	}
603 	if (tTd(54, 4))
604 		printf("\t[uid %d, stat %o, mode %o] ",
605 			st->st_uid, st->st_mode, mode);
606 	if ((st->st_uid == uid || st->st_uid == 0 ||
607 	     !bitset(SFF_MUSTOWN, flags)) &&
608 	    (st->st_mode & mode) == mode)
609 	{
610 		if (tTd(54, 4))
611 			printf("\tOK\n");
612 		return 0;
613 	}
614 	if (tTd(54, 4))
615 		printf("\tEACCES\n");
616 	return EACCES;
617 }
618 /*
619 **  SAFEFOPEN -- do a file open with extra checking
620 **
621 **	Parameters:
622 **		fn -- the file name to open.
623 **		omode -- the open-style mode flags.
624 **		cmode -- the create-style mode flags.
625 **		sff -- safefile flags.
626 **
627 **	Returns:
628 **		Same as fopen.
629 */
630 
631 #ifndef O_ACCMODE
632 # define O_ACCMODE	(O_RDONLY|O_WRONLY|O_RDWR)
633 #endif
634 
635 FILE *
636 safefopen(fn, omode, cmode, sff)
637 	char *fn;
638 	int omode;
639 	int cmode;
640 	int sff;
641 {
642 	int rval;
643 	FILE *fp;
644 	int smode;
645 	struct stat stb, sta;
646 	extern char RealUserName[];
647 
648 	if (bitset(O_CREAT, omode))
649 		sff |= SFF_CREAT;
650 	smode = 0;
651 	switch (omode & O_ACCMODE)
652 	{
653 	  case O_RDONLY:
654 		smode = S_IREAD;
655 		break;
656 
657 	  case O_WRONLY:
658 		smode = S_IWRITE;
659 		break;
660 
661 	  case O_RDWR:
662 		smode = S_IREAD|S_IWRITE;
663 		break;
664 
665 	  default:
666 		smode = 0;
667 		break;
668 	}
669 	if (bitset(SFF_OPENASROOT, sff))
670 		rval = safefile(fn, 0, 0, NULL, sff, smode, &stb);
671 	else
672 		rval = safefile(fn, RealUid, RealGid, RealUserName,
673 				sff, smode, &stb);
674 	if (rval != 0)
675 	{
676 		errno = rval;
677 		return NULL;
678 	}
679 	if (stb.st_mode == ST_MODE_NOFILE)
680 		omode |= O_EXCL;
681 
682 	fp = dfopen(fn, omode, cmode);
683 	if (fp == NULL)
684 		return NULL;
685 	if (bitset(O_EXCL, omode))
686 		return fp;
687 	if (fstat(fileno(fp), &sta) < 0 ||
688 	    sta.st_nlink != stb.st_nlink ||
689 	    sta.st_dev != stb.st_dev ||
690 	    sta.st_ino != stb.st_ino ||
691 	    sta.st_uid != stb.st_uid ||
692 	    sta.st_gid != stb.st_gid)
693 	{
694 		syserr("554 cannot open: file %s changed after open", fn);
695 		fclose(fp);
696 		errno = EPERM;
697 		return NULL;
698 	}
699 	return fp;
700 }
701 /*
702 **  FIXCRLF -- fix <CR><LF> in line.
703 **
704 **	Looks for the <CR><LF> combination and turns it into the
705 **	UNIX canonical <NL> character.  It only takes one line,
706 **	i.e., it is assumed that the first <NL> found is the end
707 **	of the line.
708 **
709 **	Parameters:
710 **		line -- the line to fix.
711 **		stripnl -- if true, strip the newline also.
712 **
713 **	Returns:
714 **		none.
715 **
716 **	Side Effects:
717 **		line is changed in place.
718 */
719 
720 void
721 fixcrlf(line, stripnl)
722 	char *line;
723 	bool stripnl;
724 {
725 	register char *p;
726 
727 	p = strchr(line, '\n');
728 	if (p == NULL)
729 		return;
730 	if (p > line && p[-1] == '\r')
731 		p--;
732 	if (!stripnl)
733 		*p++ = '\n';
734 	*p = '\0';
735 }
736 /*
737 **  DFOPEN -- determined file open
738 **
739 **	This routine has the semantics of fopen, except that it will
740 **	keep trying a few times to make this happen.  The idea is that
741 **	on very loaded systems, we may run out of resources (inodes,
742 **	whatever), so this tries to get around it.
743 */
744 
745 struct omodes
746 {
747 	int	mask;
748 	int	mode;
749 	char	*farg;
750 } OpenModes[] =
751 {
752 	O_ACCMODE,		O_RDONLY,		"r",
753 	O_ACCMODE|O_APPEND,	O_WRONLY,		"w",
754 	O_ACCMODE|O_APPEND,	O_WRONLY|O_APPEND,	"a",
755 	O_TRUNC,		0,			"w+",
756 	O_APPEND,		O_APPEND,		"a+",
757 	0,			0,			"r+",
758 };
759 
760 FILE *
761 dfopen(filename, omode, cmode)
762 	char *filename;
763 	int omode;
764 	int cmode;
765 {
766 	register int tries;
767 	int fd;
768 	register struct omodes *om;
769 	struct stat st;
770 
771 	for (om = OpenModes; om->mask != 0; om++)
772 		if ((omode & om->mask) == om->mode)
773 			break;
774 
775 	for (tries = 0; tries < 10; tries++)
776 	{
777 		sleep((unsigned) (10 * tries));
778 		errno = 0;
779 		fd = open(filename, omode, cmode);
780 		if (fd >= 0)
781 			break;
782 		switch (errno)
783 		{
784 		  case ENFILE:		/* system file table full */
785 		  case EINTR:		/* interrupted syscall */
786 #ifdef ETXTBSY
787 		  case ETXTBSY:		/* Apollo: net file locked */
788 #endif
789 			continue;
790 		}
791 		break;
792 	}
793 	if (fd >= 0 && fstat(fd, &st) >= 0 && S_ISREG(st.st_mode))
794 	{
795 		int locktype;
796 
797 		/* lock the file to avoid accidental conflicts */
798 		if ((omode & O_ACCMODE) != O_RDONLY)
799 			locktype = LOCK_EX;
800 		else
801 			locktype = LOCK_SH;
802 		(void) lockfile(fd, filename, NULL, locktype);
803 		errno = 0;
804 	}
805 	if (fd < 0)
806 		return NULL;
807 	else
808 		return fdopen(fd, om->farg);
809 }
810 /*
811 **  PUTLINE -- put a line like fputs obeying SMTP conventions
812 **
813 **	This routine always guarantees outputing a newline (or CRLF,
814 **	as appropriate) at the end of the string.
815 **
816 **	Parameters:
817 **		l -- line to put.
818 **		mci -- the mailer connection information.
819 **
820 **	Returns:
821 **		none
822 **
823 **	Side Effects:
824 **		output of l to fp.
825 */
826 
827 void
828 putline(l, mci)
829 	register char *l;
830 	register MCI *mci;
831 {
832 	putxline(l, mci, PXLF_MAPFROM);
833 }
834 /*
835 **  PUTXLINE -- putline with flags bits.
836 **
837 **	This routine always guarantees outputing a newline (or CRLF,
838 **	as appropriate) at the end of the string.
839 **
840 **	Parameters:
841 **		l -- line to put.
842 **		mci -- the mailer connection information.
843 **		pxflags -- flag bits:
844 **		    PXLF_MAPFROM -- map From_ to >From_.
845 **		    PXLF_STRIP8BIT -- strip 8th bit.
846 **
847 **	Returns:
848 **		none
849 **
850 **	Side Effects:
851 **		output of l to fp.
852 */
853 
854 void
855 putxline(l, mci, pxflags)
856 	register char *l;
857 	register MCI *mci;
858 	int pxflags;
859 {
860 	register char *p;
861 	register char svchar;
862 	int slop = 0;
863 
864 	/* strip out 0200 bits -- these can look like TELNET protocol */
865 	if (bitset(MCIF_7BIT, mci->mci_flags) ||
866 	    bitset(PXLF_STRIP8BIT, pxflags))
867 	{
868 		for (p = l; (svchar = *p) != '\0'; ++p)
869 			if (bitset(0200, svchar))
870 				*p = svchar &~ 0200;
871 	}
872 
873 	do
874 	{
875 		/* find the end of the line */
876 		p = strchr(l, '\n');
877 		if (p == NULL)
878 			p = &l[strlen(l)];
879 
880 		if (TrafficLogFile != NULL)
881 			fprintf(TrafficLogFile, "%05d >>> ", getpid());
882 
883 		/* check for line overflow */
884 		while (mci->mci_mailer->m_linelimit > 0 &&
885 		       (p - l + slop) > mci->mci_mailer->m_linelimit)
886 		{
887 			register char *q = &l[mci->mci_mailer->m_linelimit - slop - 1];
888 
889 			svchar = *q;
890 			*q = '\0';
891 			if (l[0] == '.' && slop == 0 &&
892 			    bitnset(M_XDOT, mci->mci_mailer->m_flags))
893 			{
894 				(void) putc('.', mci->mci_out);
895 				if (TrafficLogFile != NULL)
896 					(void) putc('.', TrafficLogFile);
897 			}
898 			else if (l[0] == 'F' && slop == 0 &&
899 				 bitset(PXLF_MAPFROM, pxflags) &&
900 				 strncmp(l, "From ", 5) == 0 &&
901 				 bitnset(M_ESCFROM, mci->mci_mailer->m_flags))
902 			{
903 				(void) putc('>', mci->mci_out);
904 				if (TrafficLogFile != NULL)
905 					(void) putc('>', TrafficLogFile);
906 			}
907 			fputs(l, mci->mci_out);
908 			(void) putc('!', mci->mci_out);
909 			fputs(mci->mci_mailer->m_eol, mci->mci_out);
910 			(void) putc(' ', mci->mci_out);
911 			if (TrafficLogFile != NULL)
912 				fprintf(TrafficLogFile, "%s!\n%05d >>>  ",
913 					l, getpid());
914 			*q = svchar;
915 			l = q;
916 			slop = 1;
917 		}
918 
919 		/* output last part */
920 		if (l[0] == '.' && slop == 0 &&
921 		    bitnset(M_XDOT, mci->mci_mailer->m_flags))
922 		{
923 			(void) putc('.', mci->mci_out);
924 			if (TrafficLogFile != NULL)
925 				(void) putc('.', TrafficLogFile);
926 		}
927 		if (TrafficLogFile != NULL)
928 			fprintf(TrafficLogFile, "%.*s\n", p - l, l);
929 		for ( ; l < p; ++l)
930 			(void) putc(*l, mci->mci_out);
931 		fputs(mci->mci_mailer->m_eol, mci->mci_out);
932 		if (*l == '\n')
933 			++l;
934 	} while (l[0] != '\0');
935 }
936 /*
937 **  XUNLINK -- unlink a file, doing logging as appropriate.
938 **
939 **	Parameters:
940 **		f -- name of file to unlink.
941 **
942 **	Returns:
943 **		none.
944 **
945 **	Side Effects:
946 **		f is unlinked.
947 */
948 
949 void
950 xunlink(f)
951 	char *f;
952 {
953 	register int i;
954 
955 # ifdef LOG
956 	if (LogLevel > 98)
957 		syslog(LOG_DEBUG, "%s: unlink %s", CurEnv->e_id, f);
958 # endif /* LOG */
959 
960 	i = unlink(f);
961 # ifdef LOG
962 	if (i < 0 && LogLevel > 97)
963 		syslog(LOG_DEBUG, "%s: unlink-fail %d", f, errno);
964 # endif /* LOG */
965 }
966 /*
967 **  XFCLOSE -- close a file, doing logging as appropriate.
968 **
969 **	Parameters:
970 **		fp -- file pointer for the file to close
971 **		a, b -- miscellaneous crud to print for debugging
972 **
973 **	Returns:
974 **		none.
975 **
976 **	Side Effects:
977 **		fp is closed.
978 */
979 
980 void
981 xfclose(fp, a, b)
982 	FILE *fp;
983 	char *a, *b;
984 {
985 	if (tTd(53, 99))
986 		printf("xfclose(%x) %s %s\n", fp, a, b);
987 #ifdef XDEBUG
988 	if (fileno(fp) == 1)
989 		syserr("xfclose(%s %s): fd = 1", a, b);
990 #endif
991 	if (fclose(fp) < 0 && tTd(53, 99))
992 		printf("xfclose FAILURE: %s\n", errstring(errno));
993 }
994 /*
995 **  SFGETS -- "safe" fgets -- times out and ignores random interrupts.
996 **
997 **	Parameters:
998 **		buf -- place to put the input line.
999 **		siz -- size of buf.
1000 **		fp -- file to read from.
1001 **		timeout -- the timeout before error occurs.
1002 **		during -- what we are trying to read (for error messages).
1003 **
1004 **	Returns:
1005 **		NULL on error (including timeout).  This will also leave
1006 **			buf containing a null string.
1007 **		buf otherwise.
1008 **
1009 **	Side Effects:
1010 **		none.
1011 */
1012 
1013 static jmp_buf	CtxReadTimeout;
1014 static void	readtimeout();
1015 
1016 char *
1017 sfgets(buf, siz, fp, timeout, during)
1018 	char *buf;
1019 	int siz;
1020 	FILE *fp;
1021 	time_t timeout;
1022 	char *during;
1023 {
1024 	register EVENT *ev = NULL;
1025 	register char *p;
1026 
1027 	if (fp == NULL)
1028 	{
1029 		buf[0] = '\0';
1030 		return NULL;
1031 	}
1032 
1033 	/* set the timeout */
1034 	if (timeout != 0)
1035 	{
1036 		if (setjmp(CtxReadTimeout) != 0)
1037 		{
1038 # ifdef LOG
1039 			syslog(LOG_NOTICE,
1040 			    "timeout waiting for input from %s during %s\n",
1041 			    CurHostName? CurHostName: "local", during);
1042 # endif
1043 			errno = 0;
1044 			usrerr("451 timeout waiting for input during %s",
1045 				during);
1046 			buf[0] = '\0';
1047 #ifdef XDEBUG
1048 			checkfd012(during);
1049 #endif
1050 			return (NULL);
1051 		}
1052 		ev = setevent(timeout, readtimeout, 0);
1053 	}
1054 
1055 	/* try to read */
1056 	p = NULL;
1057 	while (!feof(fp) && !ferror(fp))
1058 	{
1059 		errno = 0;
1060 		p = fgets(buf, siz, fp);
1061 		if (p != NULL || errno != EINTR)
1062 			break;
1063 		clearerr(fp);
1064 	}
1065 
1066 	/* clear the event if it has not sprung */
1067 	clrevent(ev);
1068 
1069 	/* clean up the books and exit */
1070 	LineNumber++;
1071 	if (p == NULL)
1072 	{
1073 		buf[0] = '\0';
1074 		if (TrafficLogFile != NULL)
1075 			fprintf(TrafficLogFile, "%05d <<< [EOF]\n", getpid());
1076 		return (NULL);
1077 	}
1078 	if (TrafficLogFile != NULL)
1079 		fprintf(TrafficLogFile, "%05d <<< %s", getpid(), buf);
1080 	if (SevenBitInput)
1081 	{
1082 		for (p = buf; *p != '\0'; p++)
1083 			*p &= ~0200;
1084 	}
1085 	else if (!HasEightBits)
1086 	{
1087 		for (p = buf; *p != '\0'; p++)
1088 		{
1089 			if (bitset(0200, *p))
1090 			{
1091 				HasEightBits = TRUE;
1092 				break;
1093 			}
1094 		}
1095 	}
1096 	return (buf);
1097 }
1098 
1099 static void
1100 readtimeout(timeout)
1101 	time_t timeout;
1102 {
1103 	longjmp(CtxReadTimeout, 1);
1104 }
1105 /*
1106 **  FGETFOLDED -- like fgets, but know about folded lines.
1107 **
1108 **	Parameters:
1109 **		buf -- place to put result.
1110 **		n -- bytes available.
1111 **		f -- file to read from.
1112 **
1113 **	Returns:
1114 **		input line(s) on success, NULL on error or EOF.
1115 **		This will normally be buf -- unless the line is too
1116 **			long, when it will be xalloc()ed.
1117 **
1118 **	Side Effects:
1119 **		buf gets lines from f, with continuation lines (lines
1120 **		with leading white space) appended.  CRLF's are mapped
1121 **		into single newlines.  Any trailing NL is stripped.
1122 */
1123 
1124 char *
1125 fgetfolded(buf, n, f)
1126 	char *buf;
1127 	register int n;
1128 	FILE *f;
1129 {
1130 	register char *p = buf;
1131 	char *bp = buf;
1132 	register int i;
1133 
1134 	n--;
1135 	while ((i = getc(f)) != EOF)
1136 	{
1137 		if (i == '\r')
1138 		{
1139 			i = getc(f);
1140 			if (i != '\n')
1141 			{
1142 				if (i != EOF)
1143 					(void) ungetc(i, f);
1144 				i = '\r';
1145 			}
1146 		}
1147 		if (--n <= 0)
1148 		{
1149 			/* allocate new space */
1150 			char *nbp;
1151 			int nn;
1152 
1153 			nn = (p - bp);
1154 			if (nn < MEMCHUNKSIZE)
1155 				nn *= 2;
1156 			else
1157 				nn += MEMCHUNKSIZE;
1158 			nbp = xalloc(nn);
1159 			bcopy(bp, nbp, p - bp);
1160 			p = &nbp[p - bp];
1161 			if (bp != buf)
1162 				free(bp);
1163 			bp = nbp;
1164 			n = nn - (p - bp);
1165 		}
1166 		*p++ = i;
1167 		if (i == '\n')
1168 		{
1169 			LineNumber++;
1170 			i = getc(f);
1171 			if (i != EOF)
1172 				(void) ungetc(i, f);
1173 			if (i != ' ' && i != '\t')
1174 				break;
1175 		}
1176 	}
1177 	if (p == bp)
1178 		return (NULL);
1179 	if (p[-1] == '\n')
1180 		p--;
1181 	*p = '\0';
1182 	return (bp);
1183 }
1184 /*
1185 **  CURTIME -- return current time.
1186 **
1187 **	Parameters:
1188 **		none.
1189 **
1190 **	Returns:
1191 **		the current time.
1192 **
1193 **	Side Effects:
1194 **		none.
1195 */
1196 
1197 time_t
1198 curtime()
1199 {
1200 	auto time_t t;
1201 
1202 	(void) time(&t);
1203 	return (t);
1204 }
1205 /*
1206 **  ATOBOOL -- convert a string representation to boolean.
1207 **
1208 **	Defaults to "TRUE"
1209 **
1210 **	Parameters:
1211 **		s -- string to convert.  Takes "tTyY" as true,
1212 **			others as false.
1213 **
1214 **	Returns:
1215 **		A boolean representation of the string.
1216 **
1217 **	Side Effects:
1218 **		none.
1219 */
1220 
1221 bool
1222 atobool(s)
1223 	register char *s;
1224 {
1225 	if (s == NULL || *s == '\0' || strchr("tTyY", *s) != NULL)
1226 		return (TRUE);
1227 	return (FALSE);
1228 }
1229 /*
1230 **  ATOOCT -- convert a string representation to octal.
1231 **
1232 **	Parameters:
1233 **		s -- string to convert.
1234 **
1235 **	Returns:
1236 **		An integer representing the string interpreted as an
1237 **		octal number.
1238 **
1239 **	Side Effects:
1240 **		none.
1241 */
1242 
1243 int
1244 atooct(s)
1245 	register char *s;
1246 {
1247 	register int i = 0;
1248 
1249 	while (*s >= '0' && *s <= '7')
1250 		i = (i << 3) | (*s++ - '0');
1251 	return (i);
1252 }
1253 /*
1254 **  WAITFOR -- wait for a particular process id.
1255 **
1256 **	Parameters:
1257 **		pid -- process id to wait for.
1258 **
1259 **	Returns:
1260 **		status of pid.
1261 **		-1 if pid never shows up.
1262 **
1263 **	Side Effects:
1264 **		none.
1265 */
1266 
1267 int
1268 waitfor(pid)
1269 	int pid;
1270 {
1271 #ifdef WAITUNION
1272 	union wait st;
1273 #else
1274 	auto int st;
1275 #endif
1276 	int i;
1277 
1278 	do
1279 	{
1280 		errno = 0;
1281 		i = wait(&st);
1282 	} while ((i >= 0 || errno == EINTR) && i != pid);
1283 	if (i < 0)
1284 		return -1;
1285 #ifdef WAITUNION
1286 	return st.w_status;
1287 #else
1288 	return st;
1289 #endif
1290 }
1291 /*
1292 **  BITINTERSECT -- tell if two bitmaps intersect
1293 **
1294 **	Parameters:
1295 **		a, b -- the bitmaps in question
1296 **
1297 **	Returns:
1298 **		TRUE if they have a non-null intersection
1299 **		FALSE otherwise
1300 **
1301 **	Side Effects:
1302 **		none.
1303 */
1304 
1305 bool
1306 bitintersect(a, b)
1307 	BITMAP a;
1308 	BITMAP b;
1309 {
1310 	int i;
1311 
1312 	for (i = BITMAPBYTES / sizeof (int); --i >= 0; )
1313 		if ((a[i] & b[i]) != 0)
1314 			return (TRUE);
1315 	return (FALSE);
1316 }
1317 /*
1318 **  BITZEROP -- tell if a bitmap is all zero
1319 **
1320 **	Parameters:
1321 **		map -- the bit map to check
1322 **
1323 **	Returns:
1324 **		TRUE if map is all zero.
1325 **		FALSE if there are any bits set in map.
1326 **
1327 **	Side Effects:
1328 **		none.
1329 */
1330 
1331 bool
1332 bitzerop(map)
1333 	BITMAP map;
1334 {
1335 	int i;
1336 
1337 	for (i = BITMAPBYTES / sizeof (int); --i >= 0; )
1338 		if (map[i] != 0)
1339 			return (FALSE);
1340 	return (TRUE);
1341 }
1342 /*
1343 **  STRCONTAINEDIN -- tell if one string is contained in another
1344 **
1345 **	Parameters:
1346 **		a -- possible substring.
1347 **		b -- possible superstring.
1348 **
1349 **	Returns:
1350 **		TRUE if a is contained in b.
1351 **		FALSE otherwise.
1352 */
1353 
1354 bool
1355 strcontainedin(a, b)
1356 	register char *a;
1357 	register char *b;
1358 {
1359 	int la;
1360 	int lb;
1361 	int c;
1362 
1363 	la = strlen(a);
1364 	lb = strlen(b);
1365 	c = *a;
1366 	if (isascii(c) && isupper(c))
1367 		c = tolower(c);
1368 	for (; lb-- >= la; b++)
1369 	{
1370 		if (*b != c && isascii(*b) && isupper(*b) && tolower(*b) != c)
1371 			continue;
1372 		if (strncasecmp(a, b, la) == 0)
1373 			return TRUE;
1374 	}
1375 	return FALSE;
1376 }
1377 /*
1378 **  CHECKFD012 -- check low numbered file descriptors
1379 **
1380 **	File descriptors 0, 1, and 2 should be open at all times.
1381 **	This routine verifies that, and fixes it if not true.
1382 **
1383 **	Parameters:
1384 **		where -- a tag printed if the assertion failed
1385 **
1386 **	Returns:
1387 **		none
1388 */
1389 
1390 void
1391 checkfd012(where)
1392 	char *where;
1393 {
1394 #ifdef XDEBUG
1395 	register int i;
1396 	struct stat stbuf;
1397 
1398 	for (i = 0; i < 3; i++)
1399 	{
1400 		if (fstat(i, &stbuf) < 0 && errno != EOPNOTSUPP)
1401 		{
1402 			/* oops.... */
1403 			int fd;
1404 
1405 			syserr("%s: fd %d not open", where, i);
1406 			fd = open("/dev/null", i == 0 ? O_RDONLY : O_WRONLY, 0666);
1407 			if (fd != i)
1408 			{
1409 				(void) dup2(fd, i);
1410 				(void) close(fd);
1411 			}
1412 		}
1413 	}
1414 #endif /* XDEBUG */
1415 }
1416 /*
1417 **  PRINTOPENFDS -- print the open file descriptors (for debugging)
1418 **
1419 **	Parameters:
1420 **		logit -- if set, send output to syslog; otherwise
1421 **			print for debugging.
1422 **
1423 **	Returns:
1424 **		none.
1425 */
1426 
1427 #include <arpa/inet.h>
1428 
1429 void
1430 printopenfds(logit)
1431 	bool logit;
1432 {
1433 	register int fd;
1434 	extern int DtableSize;
1435 
1436 	for (fd = 0; fd < DtableSize; fd++)
1437 		dumpfd(fd, FALSE, logit);
1438 }
1439 /*
1440 **  DUMPFD -- dump a file descriptor
1441 **
1442 **	Parameters:
1443 **		fd -- the file descriptor to dump.
1444 **		printclosed -- if set, print a notification even if
1445 **			it is closed; otherwise print nothing.
1446 **		logit -- if set, send output to syslog instead of stdout.
1447 */
1448 
1449 void
1450 dumpfd(fd, printclosed, logit)
1451 	int fd;
1452 	bool printclosed;
1453 	bool logit;
1454 {
1455 	register char *p;
1456 	char *hp;
1457 	char *fmtstr;
1458 	SOCKADDR sa;
1459 	auto int slen;
1460 	struct stat st;
1461 	char buf[200];
1462 	extern char *hostnamebyanyaddr();
1463 
1464 	p = buf;
1465 	sprintf(p, "%3d: ", fd);
1466 	p += strlen(p);
1467 
1468 	if (fstat(fd, &st) < 0)
1469 	{
1470 		if (printclosed || errno != EBADF)
1471 		{
1472 			sprintf(p, "CANNOT STAT (%s)", errstring(errno));
1473 			goto printit;
1474 		}
1475 		return;
1476 	}
1477 
1478 	slen = fcntl(fd, F_GETFL, NULL);
1479 	if (slen != -1)
1480 	{
1481 		sprintf(p, "fl=0x%x, ", slen);
1482 		p += strlen(p);
1483 	}
1484 
1485 	sprintf(p, "mode=%o: ", st.st_mode);
1486 	p += strlen(p);
1487 	switch (st.st_mode & S_IFMT)
1488 	{
1489 #ifdef S_IFSOCK
1490 	  case S_IFSOCK:
1491 		sprintf(p, "SOCK ");
1492 		p += strlen(p);
1493 		slen = sizeof sa;
1494 		if (getsockname(fd, &sa.sa, &slen) < 0)
1495 			sprintf(p, "(%s)", errstring(errno));
1496 		else
1497 		{
1498 			hp = hostnamebyanyaddr(&sa);
1499 			if (sa.sa.sa_family == AF_INET)
1500 				sprintf(p, "%s/%d", hp, ntohs(sa.sin.sin_port));
1501 			else
1502 				sprintf(p, "%s", hp);
1503 		}
1504 		p += strlen(p);
1505 		sprintf(p, "->");
1506 		p += strlen(p);
1507 		slen = sizeof sa;
1508 		if (getpeername(fd, &sa.sa, &slen) < 0)
1509 			sprintf(p, "(%s)", errstring(errno));
1510 		else
1511 		{
1512 			hp = hostnamebyanyaddr(&sa);
1513 			if (sa.sa.sa_family == AF_INET)
1514 				sprintf(p, "%s/%d", hp, ntohs(sa.sin.sin_port));
1515 			else
1516 				sprintf(p, "%s", hp);
1517 		}
1518 		break;
1519 #endif
1520 
1521 	  case S_IFCHR:
1522 		sprintf(p, "CHR: ");
1523 		p += strlen(p);
1524 		goto defprint;
1525 
1526 	  case S_IFBLK:
1527 		sprintf(p, "BLK: ");
1528 		p += strlen(p);
1529 		goto defprint;
1530 
1531 #if defined(S_IFIFO) && (!defined(S_IFSOCK) || S_IFIFO != S_IFSOCK)
1532 	  case S_IFIFO:
1533 		sprintf(p, "FIFO: ");
1534 		p += strlen(p);
1535 		goto defprint;
1536 #endif
1537 
1538 #ifdef S_IFDIR
1539 	  case S_IFDIR:
1540 		sprintf(p, "DIR: ");
1541 		p += strlen(p);
1542 		goto defprint;
1543 #endif
1544 
1545 #ifdef S_IFLNK
1546 	  case S_IFLNK:
1547 		sprintf(p, "LNK: ");
1548 		p += strlen(p);
1549 		goto defprint;
1550 #endif
1551 
1552 	  default:
1553 defprint:
1554 		if (sizeof st.st_size > sizeof (long))
1555 			fmtstr = "dev=%d/%d, ino=%d, nlink=%d, u/gid=%d/%d, size=%qd";
1556 		else
1557 			fmtstr = "dev=%d/%d, ino=%d, nlink=%d, u/gid=%d/%d, size=%ld";
1558 		sprintf(p, fmtstr,
1559 			major(st.st_dev), minor(st.st_dev), st.st_ino,
1560 			st.st_nlink, st.st_uid, st.st_gid, st.st_size);
1561 		break;
1562 	}
1563 
1564 printit:
1565 #ifdef LOG
1566 	if (logit)
1567 		syslog(LOG_DEBUG, "%s", buf);
1568 	else
1569 #endif
1570 		printf("%s\n", buf);
1571 }
1572 /*
1573 **  SHORTENSTRING -- return short version of a string
1574 **
1575 **	If the string is already short, just return it.  If it is too
1576 **	long, return the head and tail of the string.
1577 **
1578 **	Parameters:
1579 **		s -- the string to shorten.
1580 **		m -- the max length of the string.
1581 **
1582 **	Returns:
1583 **		Either s or a short version of s.
1584 */
1585 
1586 #ifndef MAXSHORTSTR
1587 # define MAXSHORTSTR	203
1588 #endif
1589 
1590 char *
1591 shortenstring(s, m)
1592 	register const char *s;
1593 	int m;
1594 {
1595 	int l;
1596 	static char buf[MAXSHORTSTR + 1];
1597 
1598 	l = strlen(s);
1599 	if (l < m)
1600 		return (char *) s;
1601 	if (m > MAXSHORTSTR)
1602 		m = MAXSHORTSTR;
1603 	else if (m < 10)
1604 	{
1605 		if (m < 5)
1606 		{
1607 			strncpy(buf, s, m);
1608 			buf[m] = '\0';
1609 			return buf;
1610 		}
1611 		strncpy(buf, s, m - 3);
1612 		strcpy(buf + m - 3, "...");
1613 		return buf;
1614 	}
1615 	m = (m - 3) / 2;
1616 	strncpy(buf, s, m);
1617 	strcpy(buf + m, "...");
1618 	strcpy(buf + m + 3, s + l - m);
1619 	return buf;
1620 }
1621 /*
1622 **  SHORTEN_HOSTNAME -- strip local domain information off of hostname.
1623 **
1624 **	Parameters:
1625 **		host -- the host to shorten (stripped in place).
1626 **
1627 **	Returns:
1628 **		none.
1629 */
1630 
1631 void
1632 shorten_hostname(host)
1633 	char host[];
1634 {
1635 	register char *p;
1636 	char *mydom;
1637 	int i;
1638 
1639 	/* strip off final dot */
1640 	p = &host[strlen(host) - 1];
1641 	if (*p == '.')
1642 		*p = '\0';
1643 
1644 	/* see if there is any domain at all -- if not, we are done */
1645 	p = strchr(host, '.');
1646 	if (p == NULL)
1647 		return;
1648 
1649 	/* yes, we have a domain -- see if it looks like us */
1650 	mydom = macvalue('m', CurEnv);
1651 	if (mydom == NULL)
1652 		mydom = "";
1653 	i = strlen(++p);
1654 	if (strncasecmp(p, mydom, i) == 0 &&
1655 	    (mydom[i] == '.' || mydom[i] == '\0'))
1656 		*--p = '\0';
1657 }
1658 /*
1659 **  PROG_OPEN -- open a program for reading
1660 **
1661 **	Parameters:
1662 **		argv -- the argument list.
1663 **		pfd -- pointer to a place to store the file descriptor.
1664 **		e -- the current envelope.
1665 **
1666 **	Returns:
1667 **		pid of the process -- -1 if it failed.
1668 */
1669 
1670 int
1671 prog_open(argv, pfd, e)
1672 	char **argv;
1673 	int *pfd;
1674 	ENVELOPE *e;
1675 {
1676 	int pid;
1677 	int i;
1678 	int saveerrno;
1679 	int fdv[2];
1680 	char *p, *q;
1681 	char buf[MAXLINE + 1];
1682 	extern int DtableSize;
1683 
1684 	if (pipe(fdv) < 0)
1685 	{
1686 		syserr("%s: cannot create pipe for stdout", argv[0]);
1687 		return -1;
1688 	}
1689 	pid = fork();
1690 	if (pid < 0)
1691 	{
1692 		syserr("%s: cannot fork", argv[0]);
1693 		close(fdv[0]);
1694 		close(fdv[1]);
1695 		return -1;
1696 	}
1697 	if (pid > 0)
1698 	{
1699 		/* parent */
1700 		close(fdv[1]);
1701 		*pfd = fdv[0];
1702 		return pid;
1703 	}
1704 
1705 	/* child -- close stdin */
1706 	close(0);
1707 
1708 	/* stdout goes back to parent */
1709 	close(fdv[0]);
1710 	if (dup2(fdv[1], 1) < 0)
1711 	{
1712 		syserr("%s: cannot dup2 for stdout", argv[0]);
1713 		_exit(EX_OSERR);
1714 	}
1715 	close(fdv[1]);
1716 
1717 	/* stderr goes to transcript if available */
1718 	if (e->e_xfp != NULL)
1719 	{
1720 		if (dup2(fileno(e->e_xfp), 2) < 0)
1721 		{
1722 			syserr("%s: cannot dup2 for stderr", argv[0]);
1723 			_exit(EX_OSERR);
1724 		}
1725 	}
1726 
1727 	/* this process has no right to the queue file */
1728 	if (e->e_lockfp != NULL)
1729 		close(fileno(e->e_lockfp));
1730 
1731 	/* run as default user */
1732 	setgid(DefGid);
1733 	setuid(DefUid);
1734 
1735 	/* run in some directory */
1736 	if (ProgMailer != NULL)
1737 		p = ProgMailer->m_execdir;
1738 	else
1739 		p = NULL;
1740 	for (; p != NULL; p = q)
1741 	{
1742 		q = strchr(p, ':');
1743 		if (q != NULL)
1744 			*q = '\0';
1745 		expand(p, buf, sizeof buf, e);
1746 		if (q != NULL)
1747 			*q++ = ':';
1748 		if (buf[0] != '\0' && chdir(buf) >= 0)
1749 			break;
1750 	}
1751 	if (p == NULL)
1752 	{
1753 		/* backup directories */
1754 		if (chdir("/tmp") < 0)
1755 			(void) chdir("/");
1756 	}
1757 
1758 	/* arrange for all the files to be closed */
1759 	for (i = 3; i < DtableSize; i++)
1760 	{
1761 		register int j;
1762 
1763 		if ((j = fcntl(i, F_GETFD, 0)) != -1)
1764 			(void) fcntl(i, F_SETFD, j | 1);
1765 	}
1766 
1767 	/* now exec the process */
1768 	execve(argv[0], (ARGV_T) argv, (ARGV_T) UserEnviron);
1769 
1770 	/* woops!  failed */
1771 	saveerrno = errno;
1772 	syserr("%s: cannot exec", argv[0]);
1773 	if (transienterror(saveerrno))
1774 		_exit(EX_OSERR);
1775 	_exit(EX_CONFIG);
1776 }
1777 /*
1778 **  GET_COLUMN  -- look up a Column in a line buffer
1779 **
1780 **	Parameters:
1781 **		line -- the raw text line to search.
1782 **		col -- the column number to fetch.
1783 **		delim -- the delimiter between columns.  If null,
1784 **			use white space.
1785 **		buf -- the output buffer.
1786 **
1787 **	Returns:
1788 **		buf if successful.
1789 **		NULL otherwise.
1790 */
1791 
1792 char *
1793 get_column(line, col, delim, buf)
1794 	char line[];
1795 	int col;
1796 	char delim;
1797 	char buf[];
1798 {
1799 	char *p;
1800 	char *begin, *end;
1801 	int i;
1802 	char delimbuf[3];
1803 
1804 	if (delim == '\0')
1805 		strcpy(delimbuf, "\n\t ");
1806 	else
1807 	{
1808 		delimbuf[0] = delim;
1809 		delimbuf[1] = '\0';
1810 	}
1811 
1812 	p = line;
1813 	if (*p == '\0')
1814 		return NULL;			/* line empty */
1815 	if (*p == delim && col == 0)
1816 		return NULL;			/* first column empty */
1817 
1818 	begin = line;
1819 
1820 	if (col == 0 && delim == '\0')
1821 	{
1822 		while (*begin && isspace(*begin))
1823 			begin++;
1824 	}
1825 
1826 	for (i = 0; i < col; i++)
1827 	{
1828 		if ((begin = strpbrk(begin, delimbuf)) == NULL)
1829 			return NULL;		/* no such column */
1830 		begin++;
1831 		if (delim == '\0')
1832 		{
1833 			while (*begin && isspace(*begin))
1834 				begin++;
1835 		}
1836 	}
1837 
1838 	end = strpbrk(begin, delimbuf);
1839 	if (end == NULL)
1840 	{
1841 		strcpy(buf, begin);
1842 	}
1843 	else
1844 	{
1845 		strncpy(buf, begin, end - begin);
1846 		buf[end - begin] = '\0';
1847 	}
1848 	return buf;
1849 }
1850 /*
1851 **  CLEANSTRCPY -- copy string keeping out bogus characters
1852 **
1853 **	Parameters:
1854 **		t -- "to" string.
1855 **		f -- "from" string.
1856 **		l -- length of space available in "to" string.
1857 **
1858 **	Returns:
1859 **		none.
1860 */
1861 
1862 void
1863 cleanstrcpy(t, f, l)
1864 	register char *t;
1865 	register char *f;
1866 	int l;
1867 {
1868 #ifdef LOG
1869 	/* check for newlines and log if necessary */
1870 	(void) denlstring(f, TRUE, TRUE);
1871 #endif
1872 
1873 	l--;
1874 	while (l > 0 && *f != '\0')
1875 	{
1876 		if (isascii(*f) &&
1877 		    (isalnum(*f) || strchr("!#$%&'*+-./^_`{|}~", *f) != NULL))
1878 		{
1879 			l--;
1880 			*t++ = *f;
1881 		}
1882 		f++;
1883 	}
1884 	*t = '\0';
1885 }
1886 /*
1887 **  DENLSTRING -- convert newlines in a string to spaces
1888 **
1889 **	Parameters:
1890 **		s -- the input string
1891 **		strict -- if set, don't permit continuation lines.
1892 **		logattacks -- if set, log attempted attacks.
1893 **
1894 **	Returns:
1895 **		A pointer to a version of the string with newlines
1896 **		mapped to spaces.  This should be copied.
1897 */
1898 
1899 char *
1900 denlstring(s, strict, logattacks)
1901 	char *s;
1902 	bool strict;
1903 	bool logattacks;
1904 {
1905 	register char *p;
1906 	int l;
1907 	static char *bp = NULL;
1908 	static int bl = 0;
1909 
1910 	p = s;
1911 	while ((p = strchr(p, '\n')) != NULL)
1912 		if (strict || (*++p != ' ' && *p != '\t'))
1913 			break;
1914 	if (p == NULL)
1915 		return s;
1916 
1917 	l = strlen(s) + 1;
1918 	if (bl < l)
1919 	{
1920 		/* allocate more space */
1921 		if (bp != NULL)
1922 			free(bp);
1923 		bp = xalloc(l);
1924 		bl = l;
1925 	}
1926 	strcpy(bp, s);
1927 	for (p = bp; (p = strchr(p, '\n')) != NULL; )
1928 		*p++ = ' ';
1929 
1930 /*
1931 #ifdef LOG
1932 	if (logattacks)
1933 	{
1934 		syslog(LOG_NOTICE, "POSSIBLE ATTACK from %s: newline in string \"%s\"",
1935 			RealHostName == NULL ? "[UNKNOWN]" : RealHostName,
1936 			shortenstring(bp, 80));
1937 	}
1938 #endif
1939 */
1940 
1941 	return bp;
1942 }
1943