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