xref: /freebsd/contrib/sendmail/src/util.c (revision 6b3455a7665208c366849f0b2b3bc916fb97516e)
1 /*
2  * Copyright (c) 1998-2004 Sendmail, Inc. and its suppliers.
3  *	All rights reserved.
4  * Copyright (c) 1983, 1995-1997 Eric P. Allman.  All rights reserved.
5  * Copyright (c) 1988, 1993
6  *	The Regents of the University of California.  All rights reserved.
7  *
8  * By using this file, you agree to the terms and conditions set
9  * forth in the LICENSE file which can be found at the top level of
10  * the sendmail distribution.
11  *
12  */
13 
14 #include <sendmail.h>
15 
16 SM_RCSID("@(#)$Id: util.c,v 8.382 2004/03/26 19:01:10 ca Exp $")
17 
18 #include <sysexits.h>
19 #include <sm/xtrap.h>
20 
21 /*
22 **  NEWSTR -- Create a copy of a C string
23 **
24 **	Parameters:
25 **		s -- the string to copy.
26 **
27 **	Returns:
28 **		pointer to newly allocated string.
29 */
30 
31 char *
32 newstr(s)
33 	const char *s;
34 {
35 	size_t l;
36 	char *n;
37 
38 	l = strlen(s);
39 	SM_ASSERT(l + 1 > l);
40 	n = xalloc(l + 1);
41 	sm_strlcpy(n, s, l + 1);
42 	return n;
43 }
44 
45 /*
46 **  ADDQUOTES -- Adds quotes & quote bits to a string.
47 **
48 **	Runs through a string and adds backslashes and quote bits.
49 **
50 **	Parameters:
51 **		s -- the string to modify.
52 **		rpool -- resource pool from which to allocate result
53 **
54 **	Returns:
55 **		pointer to quoted string.
56 */
57 
58 char *
59 addquotes(s, rpool)
60 	char *s;
61 	SM_RPOOL_T *rpool;
62 {
63 	int len = 0;
64 	char c;
65 	char *p = s, *q, *r;
66 
67 	if (s == NULL)
68 		return NULL;
69 
70 	/* Find length of quoted string */
71 	while ((c = *p++) != '\0')
72 	{
73 		len++;
74 		if (c == '\\' || c == '"')
75 			len++;
76 	}
77 
78 	q = r = sm_rpool_malloc_x(rpool, len + 3);
79 	p = s;
80 
81 	/* add leading quote */
82 	*q++ = '"';
83 	while ((c = *p++) != '\0')
84 	{
85 		/* quote \ or " */
86 		if (c == '\\' || c == '"')
87 			*q++ = '\\';
88 		*q++ = c;
89 	}
90 	*q++ = '"';
91 	*q = '\0';
92 	return r;
93 }
94 
95 /*
96 **  STRIPBACKSLASH -- Strip leading backslash from a string.
97 **
98 **	This is done in place.
99 **
100 **	Parameters:
101 **		s -- the string to strip.
102 **
103 **	Returns:
104 **		none.
105 */
106 
107 void
108 stripbackslash(s)
109 	char *s;
110 {
111 	char *p, *q, c;
112 
113 	if (s == NULL || *s == '\0')
114 		return;
115 	p = q = s;
116 	while (*p == '\\' && (p[1] == '\\' || (isascii(p[1]) && isalnum(p[1]))))
117 		p++;
118 	do
119 	{
120 		c = *q++ = *p++;
121 	} while (c != '\0');
122 }
123 
124 /*
125 **  RFC822_STRING -- Checks string for proper RFC822 string quoting.
126 **
127 **	Runs through a string and verifies RFC822 special characters
128 **	are only found inside comments, quoted strings, or backslash
129 **	escaped.  Also verified balanced quotes and parenthesis.
130 **
131 **	Parameters:
132 **		s -- the string to modify.
133 **
134 **	Returns:
135 **		true iff the string is RFC822 compliant, false otherwise.
136 */
137 
138 bool
139 rfc822_string(s)
140 	char *s;
141 {
142 	bool quoted = false;
143 	int commentlev = 0;
144 	char *c = s;
145 
146 	if (s == NULL)
147 		return false;
148 
149 	while (*c != '\0')
150 	{
151 		/* escaped character */
152 		if (*c == '\\')
153 		{
154 			c++;
155 			if (*c == '\0')
156 				return false;
157 		}
158 		else if (commentlev == 0 && *c == '"')
159 			quoted = !quoted;
160 		else if (!quoted)
161 		{
162 			if (*c == ')')
163 			{
164 				/* unbalanced ')' */
165 				if (commentlev == 0)
166 					return false;
167 				else
168 					commentlev--;
169 			}
170 			else if (*c == '(')
171 				commentlev++;
172 			else if (commentlev == 0 &&
173 				 strchr(MustQuoteChars, *c) != NULL)
174 				return false;
175 		}
176 		c++;
177 	}
178 
179 	/* unbalanced '"' or '(' */
180 	return !quoted && commentlev == 0;
181 }
182 /*
183 **  SHORTEN_RFC822_STRING -- Truncate and rebalance an RFC822 string
184 **
185 **	Arbitrarily shorten (in place) an RFC822 string and rebalance
186 **	comments and quotes.
187 **
188 **	Parameters:
189 **		string -- the string to shorten
190 **		length -- the maximum size, 0 if no maximum
191 **
192 **	Returns:
193 **		true if string is changed, false otherwise
194 **
195 **	Side Effects:
196 **		Changes string in place, possibly resulting
197 **		in a shorter string.
198 */
199 
200 bool
201 shorten_rfc822_string(string, length)
202 	char *string;
203 	size_t length;
204 {
205 	bool backslash = false;
206 	bool modified = false;
207 	bool quoted = false;
208 	size_t slen;
209 	int parencount = 0;
210 	char *ptr = string;
211 
212 	/*
213 	**  If have to rebalance an already short enough string,
214 	**  need to do it within allocated space.
215 	*/
216 
217 	slen = strlen(string);
218 	if (length == 0 || slen < length)
219 		length = slen;
220 
221 	while (*ptr != '\0')
222 	{
223 		if (backslash)
224 		{
225 			backslash = false;
226 			goto increment;
227 		}
228 
229 		if (*ptr == '\\')
230 			backslash = true;
231 		else if (*ptr == '(')
232 		{
233 			if (!quoted)
234 				parencount++;
235 		}
236 		else if (*ptr == ')')
237 		{
238 			if (--parencount < 0)
239 				parencount = 0;
240 		}
241 
242 		/* Inside a comment, quotes don't matter */
243 		if (parencount <= 0 && *ptr == '"')
244 			quoted = !quoted;
245 
246 increment:
247 		/* Check for sufficient space for next character */
248 		if (length - (ptr - string) <= (size_t) ((backslash ? 1 : 0) +
249 						parencount +
250 						(quoted ? 1 : 0)))
251 		{
252 			/* Not enough, backtrack */
253 			if (*ptr == '\\')
254 				backslash = false;
255 			else if (*ptr == '(' && !quoted)
256 				parencount--;
257 			else if (*ptr == '"' && parencount == 0)
258 				quoted = false;
259 			break;
260 		}
261 		ptr++;
262 	}
263 
264 	/* Rebalance */
265 	while (parencount-- > 0)
266 	{
267 		if (*ptr != ')')
268 		{
269 			modified = true;
270 			*ptr = ')';
271 		}
272 		ptr++;
273 	}
274 	if (quoted)
275 	{
276 		if (*ptr != '"')
277 		{
278 			modified = true;
279 			*ptr = '"';
280 		}
281 		ptr++;
282 	}
283 	if (*ptr != '\0')
284 	{
285 		modified = true;
286 		*ptr = '\0';
287 	}
288 	return modified;
289 }
290 /*
291 **  FIND_CHARACTER -- find an unquoted character in an RFC822 string
292 **
293 **	Find an unquoted, non-commented character in an RFC822
294 **	string and return a pointer to its location in the
295 **	string.
296 **
297 **	Parameters:
298 **		string -- the string to search
299 **		character -- the character to find
300 **
301 **	Returns:
302 **		pointer to the character, or
303 **		a pointer to the end of the line if character is not found
304 */
305 
306 char *
307 find_character(string, character)
308 	char *string;
309 	int character;
310 {
311 	bool backslash = false;
312 	bool quoted = false;
313 	int parencount = 0;
314 
315 	while (string != NULL && *string != '\0')
316 	{
317 		if (backslash)
318 		{
319 			backslash = false;
320 			if (!quoted && character == '\\' && *string == '\\')
321 				break;
322 			string++;
323 			continue;
324 		}
325 		switch (*string)
326 		{
327 		  case '\\':
328 			backslash = true;
329 			break;
330 
331 		  case '(':
332 			if (!quoted)
333 				parencount++;
334 			break;
335 
336 		  case ')':
337 			if (--parencount < 0)
338 				parencount = 0;
339 			break;
340 		}
341 
342 		/* Inside a comment, nothing matters */
343 		if (parencount > 0)
344 		{
345 			string++;
346 			continue;
347 		}
348 
349 		if (*string == '"')
350 			quoted = !quoted;
351 		else if (*string == character && !quoted)
352 			break;
353 		string++;
354 	}
355 
356 	/* Return pointer to the character */
357 	return string;
358 }
359 
360 /*
361 **  CHECK_BODYTYPE -- check bodytype parameter
362 **
363 **	Parameters:
364 **		bodytype -- bodytype parameter
365 **
366 **	Returns:
367 **		BODYTYPE_* according to parameter
368 **
369 */
370 
371 int
372 check_bodytype(bodytype)
373 	char *bodytype;
374 {
375 	/* check body type for legality */
376 	if (bodytype == NULL)
377 		return BODYTYPE_NONE;
378 	if (sm_strcasecmp(bodytype, "7BIT") == 0)
379 		return BODYTYPE_7BIT;
380 	if (sm_strcasecmp(bodytype, "8BITMIME") == 0)
381 		return BODYTYPE_8BITMIME;
382 	return BODYTYPE_ILLEGAL;
383 }
384 
385 #if _FFR_BESTMX_BETTER_TRUNCATION || _FFR_DNSMAP_MULTI
386 /*
387 **  TRUNCATE_AT_DELIM -- truncate string at a delimiter and append "..."
388 **
389 **	Parameters:
390 **		str -- string to truncate
391 **		len -- maximum length (including '\0') (0 for unlimited)
392 **		delim -- delimiter character
393 **
394 **	Returns:
395 **		None.
396 */
397 
398 void
399 truncate_at_delim(str, len, delim)
400 	char *str;
401 	size_t len;
402 	int delim;
403 {
404 	char *p;
405 
406 	if (str == NULL || len == 0 || strlen(str) < len)
407 		return;
408 
409 	*(str + len - 1) = '\0';
410 	while ((p = strrchr(str, delim)) != NULL)
411 	{
412 		*p = '\0';
413 		if (p - str + 4 < len)
414 		{
415 			*p++ = (char) delim;
416 			*p = '\0';
417 			(void) sm_strlcat(str, "...", len);
418 			return;
419 		}
420 	}
421 
422 	/* Couldn't find a place to append "..." */
423 	if (len > 3)
424 		(void) sm_strlcpy(str, "...", len);
425 	else
426 		str[0] = '\0';
427 }
428 #endif /* _FFR_BESTMX_BETTER_TRUNCATION || _FFR_DNSMAP_MULTI */
429 /*
430 **  XALLOC -- Allocate memory, raise an exception on error
431 **
432 **	Parameters:
433 **		sz -- size of area to allocate.
434 **
435 **	Returns:
436 **		pointer to data region.
437 **
438 **	Exceptions:
439 **		SmHeapOutOfMemory (F:sm.heap) -- cannot allocate memory
440 **
441 **	Side Effects:
442 **		Memory is allocated.
443 */
444 
445 char *
446 #if SM_HEAP_CHECK
447 xalloc_tagged(sz, file, line)
448 	register int sz;
449 	char *file;
450 	int line;
451 #else /* SM_HEAP_CHECK */
452 xalloc(sz)
453 	register int sz;
454 #endif /* SM_HEAP_CHECK */
455 {
456 	register char *p;
457 
458 	/* some systems can't handle size zero mallocs */
459 	if (sz <= 0)
460 		sz = 1;
461 
462 	/* scaffolding for testing error handling code */
463 	sm_xtrap_raise_x(&SmHeapOutOfMemory);
464 
465 	p = sm_malloc_tagged((unsigned) sz, file, line, sm_heap_group());
466 	if (p == NULL)
467 	{
468 		sm_exc_raise_x(&SmHeapOutOfMemory);
469 	}
470 	return p;
471 }
472 /*
473 **  COPYPLIST -- copy list of pointers.
474 **
475 **	This routine is the equivalent of strdup for lists of
476 **	pointers.
477 **
478 **	Parameters:
479 **		list -- list of pointers to copy.
480 **			Must be NULL terminated.
481 **		copycont -- if true, copy the contents of the vector
482 **			(which must be a string) also.
483 **		rpool -- resource pool from which to allocate storage,
484 **			or NULL
485 **
486 **	Returns:
487 **		a copy of 'list'.
488 */
489 
490 char **
491 copyplist(list, copycont, rpool)
492 	char **list;
493 	bool copycont;
494 	SM_RPOOL_T *rpool;
495 {
496 	register char **vp;
497 	register char **newvp;
498 
499 	for (vp = list; *vp != NULL; vp++)
500 		continue;
501 
502 	vp++;
503 
504 	newvp = (char **) sm_rpool_malloc_x(rpool, (vp - list) * sizeof *vp);
505 	memmove((char *) newvp, (char *) list, (int) (vp - list) * sizeof *vp);
506 
507 	if (copycont)
508 	{
509 		for (vp = newvp; *vp != NULL; vp++)
510 			*vp = sm_rpool_strdup_x(rpool, *vp);
511 	}
512 
513 	return newvp;
514 }
515 /*
516 **  COPYQUEUE -- copy address queue.
517 **
518 **	This routine is the equivalent of strdup for address queues;
519 **	addresses marked as QS_IS_DEAD() aren't copied
520 **
521 **	Parameters:
522 **		addr -- list of address structures to copy.
523 **		rpool -- resource pool from which to allocate storage
524 **
525 **	Returns:
526 **		a copy of 'addr'.
527 */
528 
529 ADDRESS *
530 copyqueue(addr, rpool)
531 	ADDRESS *addr;
532 	SM_RPOOL_T *rpool;
533 {
534 	register ADDRESS *newaddr;
535 	ADDRESS *ret;
536 	register ADDRESS **tail = &ret;
537 
538 	while (addr != NULL)
539 	{
540 		if (!QS_IS_DEAD(addr->q_state))
541 		{
542 			newaddr = (ADDRESS *) sm_rpool_malloc_x(rpool,
543 							sizeof *newaddr);
544 			STRUCTCOPY(*addr, *newaddr);
545 			*tail = newaddr;
546 			tail = &newaddr->q_next;
547 		}
548 		addr = addr->q_next;
549 	}
550 	*tail = NULL;
551 
552 	return ret;
553 }
554 /*
555 **  LOG_SENDMAIL_PID -- record sendmail pid and command line.
556 **
557 **	Parameters:
558 **		e -- the current envelope.
559 **
560 **	Returns:
561 **		none.
562 **
563 **	Side Effects:
564 **		writes pidfile, logs command line.
565 **		keeps file open and locked to prevent overwrite of active file
566 */
567 
568 static SM_FILE_T	*Pidf = NULL;
569 
570 void
571 log_sendmail_pid(e)
572 	ENVELOPE *e;
573 {
574 	long sff;
575 	char pidpath[MAXPATHLEN];
576 	extern char *CommandLineArgs;
577 
578 	/* write the pid to the log file for posterity */
579 	sff = SFF_NOLINK|SFF_ROOTOK|SFF_REGONLY|SFF_CREAT|SFF_NBLOCK;
580 	if (TrustedUid != 0 && RealUid == TrustedUid)
581 		sff |= SFF_OPENASROOT;
582 	expand(PidFile, pidpath, sizeof pidpath, e);
583 	Pidf = safefopen(pidpath, O_WRONLY|O_TRUNC, FileMode, sff);
584 	if (Pidf == NULL)
585 	{
586 		if (errno == EWOULDBLOCK)
587 			sm_syslog(LOG_ERR, NOQID,
588 				  "unable to write pid to %s: file in use by another process",
589 				  pidpath);
590 		else
591 			sm_syslog(LOG_ERR, NOQID,
592 				  "unable to write pid to %s: %s",
593 				  pidpath, sm_errstring(errno));
594 	}
595 	else
596 	{
597 		PidFilePid = getpid();
598 
599 		/* write the process id on line 1 */
600 		(void) sm_io_fprintf(Pidf, SM_TIME_DEFAULT, "%ld\n",
601 				     (long) PidFilePid);
602 
603 		/* line 2 contains all command line flags */
604 		(void) sm_io_fprintf(Pidf, SM_TIME_DEFAULT, "%s\n",
605 				     CommandLineArgs);
606 
607 		/* flush */
608 		(void) sm_io_flush(Pidf, SM_TIME_DEFAULT);
609 
610 		/*
611 		**  Leave pid file open until process ends
612 		**  so it's not overwritten by another
613 		**  process.
614 		*/
615 	}
616 	if (LogLevel > 9)
617 		sm_syslog(LOG_INFO, NOQID, "started as: %s", CommandLineArgs);
618 }
619 
620 /*
621 **  CLOSE_SENDMAIL_PID -- close sendmail pid file
622 **
623 **	Parameters:
624 **		none.
625 **
626 **	Returns:
627 **		none.
628 */
629 
630 void
631 close_sendmail_pid()
632 {
633 	if (Pidf == NULL)
634 		return;
635 
636 	(void) sm_io_close(Pidf, SM_TIME_DEFAULT);
637 	Pidf = NULL;
638 }
639 
640 /*
641 **  SET_DELIVERY_MODE -- set and record the delivery mode
642 **
643 **	Parameters:
644 **		mode -- delivery mode
645 **		e -- the current envelope.
646 **
647 **	Returns:
648 **		none.
649 **
650 **	Side Effects:
651 **		sets {deliveryMode} macro
652 */
653 
654 void
655 set_delivery_mode(mode, e)
656 	int mode;
657 	ENVELOPE *e;
658 {
659 	char buf[2];
660 
661 	e->e_sendmode = (char) mode;
662 	buf[0] = (char) mode;
663 	buf[1] = '\0';
664 	macdefine(&e->e_macro, A_TEMP, macid("{deliveryMode}"), buf);
665 }
666 /*
667 **  SET_OP_MODE -- set and record the op mode
668 **
669 **	Parameters:
670 **		mode -- op mode
671 **		e -- the current envelope.
672 **
673 **	Returns:
674 **		none.
675 **
676 **	Side Effects:
677 **		sets {opMode} macro
678 */
679 
680 void
681 set_op_mode(mode)
682 	int mode;
683 {
684 	char buf[2];
685 	extern ENVELOPE BlankEnvelope;
686 
687 	OpMode = (char) mode;
688 	buf[0] = (char) mode;
689 	buf[1] = '\0';
690 	macdefine(&BlankEnvelope.e_macro, A_TEMP, MID_OPMODE, buf);
691 }
692 /*
693 **  PRINTAV -- print argument vector.
694 **
695 **	Parameters:
696 **		fp -- output file pointer.
697 **		av -- argument vector.
698 **
699 **	Returns:
700 **		none.
701 **
702 **	Side Effects:
703 **		prints av.
704 */
705 
706 void
707 printav(fp, av)
708 	SM_FILE_T *fp;
709 	register char **av;
710 {
711 	while (*av != NULL)
712 	{
713 		if (tTd(0, 44))
714 			sm_dprintf("\n\t%08lx=", (unsigned long) *av);
715 		else
716 			(void) sm_io_putc(fp, SM_TIME_DEFAULT, ' ');
717 		xputs(fp, *av++);
718 	}
719 	(void) sm_io_putc(fp, SM_TIME_DEFAULT, '\n');
720 }
721 /*
722 **  XPUTS -- put string doing control escapes.
723 **
724 **	Parameters:
725 **		fp -- output file pointer.
726 **		s -- string to put.
727 **
728 **	Returns:
729 **		none.
730 **
731 **	Side Effects:
732 **		output to stdout
733 */
734 
735 void
736 xputs(fp, s)
737 	SM_FILE_T *fp;
738 	register const char *s;
739 {
740 	register int c;
741 	register struct metamac *mp;
742 	bool shiftout = false;
743 	extern struct metamac MetaMacros[];
744 	static SM_DEBUG_T DebugANSI = SM_DEBUG_INITIALIZER("ANSI",
745 		"@(#)$Debug: ANSI - enable reverse video in debug output $");
746 
747 	/*
748 	**  TermEscape is set here, rather than in main(),
749 	**  because ANSI mode can be turned on or off at any time
750 	**  if we are in -bt rule testing mode.
751 	*/
752 
753 	if (sm_debug_unknown(&DebugANSI))
754 	{
755 		if (sm_debug_active(&DebugANSI, 1))
756 		{
757 			TermEscape.te_rv_on = "\033[7m";
758 			TermEscape.te_rv_off = "\033[0m";
759 		}
760 		else
761 		{
762 			TermEscape.te_rv_on = "";
763 			TermEscape.te_rv_off = "";
764 		}
765 	}
766 
767 	if (s == NULL)
768 	{
769 		(void) sm_io_fprintf(fp, SM_TIME_DEFAULT, "%s<null>%s",
770 				     TermEscape.te_rv_on, TermEscape.te_rv_off);
771 		return;
772 	}
773 	while ((c = (*s++ & 0377)) != '\0')
774 	{
775 		if (shiftout)
776 		{
777 			(void) sm_io_fprintf(fp, SM_TIME_DEFAULT, "%s",
778 					     TermEscape.te_rv_off);
779 			shiftout = false;
780 		}
781 		if (!isascii(c))
782 		{
783 			if (c == MATCHREPL)
784 			{
785 				(void) sm_io_fprintf(fp, SM_TIME_DEFAULT,
786 						     "%s$",
787 						     TermEscape.te_rv_on);
788 				shiftout = true;
789 				if (*s == '\0')
790 					continue;
791 				c = *s++ & 0377;
792 				goto printchar;
793 			}
794 			if (c == MACROEXPAND || c == MACRODEXPAND)
795 			{
796 				(void) sm_io_fprintf(fp, SM_TIME_DEFAULT,
797 						     "%s$",
798 						     TermEscape.te_rv_on);
799 				if (c == MACRODEXPAND)
800 					(void) sm_io_putc(fp,
801 							  SM_TIME_DEFAULT, '&');
802 				shiftout = true;
803 				if (*s == '\0')
804 					continue;
805 				if (strchr("=~&?", *s) != NULL)
806 					(void) sm_io_putc(fp,
807 							  SM_TIME_DEFAULT,
808 							  *s++);
809 				if (bitset(0200, *s))
810 					(void) sm_io_fprintf(fp,
811 							     SM_TIME_DEFAULT,
812 							     "{%s}",
813 							     macname(bitidx(*s++)));
814 				else
815 					(void) sm_io_fprintf(fp,
816 							     SM_TIME_DEFAULT,
817 							     "%c",
818 							     *s++);
819 				continue;
820 			}
821 			for (mp = MetaMacros; mp->metaname != '\0'; mp++)
822 			{
823 				if (bitidx(mp->metaval) == c)
824 				{
825 					(void) sm_io_fprintf(fp,
826 							     SM_TIME_DEFAULT,
827 							     "%s$%c",
828 							     TermEscape.te_rv_on,
829 							     mp->metaname);
830 					shiftout = true;
831 					break;
832 				}
833 			}
834 			if (c == MATCHCLASS || c == MATCHNCLASS)
835 			{
836 				if (bitset(0200, *s))
837 					(void) sm_io_fprintf(fp,
838 							     SM_TIME_DEFAULT,
839 							     "{%s}",
840 							     macname(bitidx(*s++)));
841 				else if (*s != '\0')
842 					(void) sm_io_fprintf(fp,
843 							     SM_TIME_DEFAULT,
844 							     "%c",
845 							     *s++);
846 			}
847 			if (mp->metaname != '\0')
848 				continue;
849 
850 			/* unrecognized meta character */
851 			(void) sm_io_fprintf(fp, SM_TIME_DEFAULT, "%sM-",
852 					     TermEscape.te_rv_on);
853 			shiftout = true;
854 			c &= 0177;
855 		}
856   printchar:
857 		if (isprint(c))
858 		{
859 			(void) sm_io_putc(fp, SM_TIME_DEFAULT, c);
860 			continue;
861 		}
862 
863 		/* wasn't a meta-macro -- find another way to print it */
864 		switch (c)
865 		{
866 		  case '\n':
867 			c = 'n';
868 			break;
869 
870 		  case '\r':
871 			c = 'r';
872 			break;
873 
874 		  case '\t':
875 			c = 't';
876 			break;
877 		}
878 		if (!shiftout)
879 		{
880 			(void) sm_io_fprintf(fp, SM_TIME_DEFAULT, "%s",
881 					     TermEscape.te_rv_on);
882 			shiftout = true;
883 		}
884 		if (isprint(c))
885 		{
886 			(void) sm_io_putc(fp, SM_TIME_DEFAULT, '\\');
887 			(void) sm_io_putc(fp, SM_TIME_DEFAULT, c);
888 		}
889 		else
890 		{
891 			(void) sm_io_putc(fp, SM_TIME_DEFAULT, '^');
892 			(void) sm_io_putc(fp, SM_TIME_DEFAULT, c ^ 0100);
893 		}
894 	}
895 	if (shiftout)
896 		(void) sm_io_fprintf(fp, SM_TIME_DEFAULT, "%s",
897 				     TermEscape.te_rv_off);
898 	(void) sm_io_flush(fp, SM_TIME_DEFAULT);
899 }
900 /*
901 **  MAKELOWER -- Translate a line into lower case
902 **
903 **	Parameters:
904 **		p -- the string to translate.  If NULL, return is
905 **			immediate.
906 **
907 **	Returns:
908 **		none.
909 **
910 **	Side Effects:
911 **		String pointed to by p is translated to lower case.
912 */
913 
914 void
915 makelower(p)
916 	register char *p;
917 {
918 	register char c;
919 
920 	if (p == NULL)
921 		return;
922 	for (; (c = *p) != '\0'; p++)
923 		if (isascii(c) && isupper(c))
924 			*p = tolower(c);
925 }
926 /*
927 **  FIXCRLF -- fix <CR><LF> in line.
928 **
929 **	Looks for the <CR><LF> combination and turns it into the
930 **	UNIX canonical <NL> character.  It only takes one line,
931 **	i.e., it is assumed that the first <NL> found is the end
932 **	of the line.
933 **
934 **	Parameters:
935 **		line -- the line to fix.
936 **		stripnl -- if true, strip the newline also.
937 **
938 **	Returns:
939 **		none.
940 **
941 **	Side Effects:
942 **		line is changed in place.
943 */
944 
945 void
946 fixcrlf(line, stripnl)
947 	char *line;
948 	bool stripnl;
949 {
950 	register char *p;
951 
952 	p = strchr(line, '\n');
953 	if (p == NULL)
954 		return;
955 	if (p > line && p[-1] == '\r')
956 		p--;
957 	if (!stripnl)
958 		*p++ = '\n';
959 	*p = '\0';
960 }
961 /*
962 **  PUTLINE -- put a line like fputs obeying SMTP conventions
963 **
964 **	This routine always guarantees outputing a newline (or CRLF,
965 **	as appropriate) at the end of the string.
966 **
967 **	Parameters:
968 **		l -- line to put.
969 **		mci -- the mailer connection information.
970 **
971 **	Returns:
972 **		none
973 **
974 **	Side Effects:
975 **		output of l to mci->mci_out.
976 */
977 
978 void
979 putline(l, mci)
980 	register char *l;
981 	register MCI *mci;
982 {
983 	putxline(l, strlen(l), mci, PXLF_MAPFROM);
984 }
985 /*
986 **  PUTXLINE -- putline with flags bits.
987 **
988 **	This routine always guarantees outputing a newline (or CRLF,
989 **	as appropriate) at the end of the string.
990 **
991 **	Parameters:
992 **		l -- line to put.
993 **		len -- the length of the line.
994 **		mci -- the mailer connection information.
995 **		pxflags -- flag bits:
996 **		    PXLF_MAPFROM -- map From_ to >From_.
997 **		    PXLF_STRIP8BIT -- strip 8th bit.
998 **		    PXLF_HEADER -- map bare newline in header to newline space.
999 **		    PXLF_NOADDEOL -- don't add an EOL if one wasn't present.
1000 **
1001 **	Returns:
1002 **		none
1003 **
1004 **	Side Effects:
1005 **		output of l to mci->mci_out.
1006 */
1007 
1008 void
1009 putxline(l, len, mci, pxflags)
1010 	register char *l;
1011 	size_t len;
1012 	register MCI *mci;
1013 	int pxflags;
1014 {
1015 	bool dead = false;
1016 	register char *p, *end;
1017 	int slop = 0;
1018 
1019 	/* strip out 0200 bits -- these can look like TELNET protocol */
1020 	if (bitset(MCIF_7BIT, mci->mci_flags) ||
1021 	    bitset(PXLF_STRIP8BIT, pxflags))
1022 	{
1023 		register char svchar;
1024 
1025 		for (p = l; (svchar = *p) != '\0'; ++p)
1026 			if (bitset(0200, svchar))
1027 				*p = svchar &~ 0200;
1028 	}
1029 
1030 	end = l + len;
1031 	do
1032 	{
1033 		bool noeol = false;
1034 
1035 		/* find the end of the line */
1036 		p = memchr(l, '\n', end - l);
1037 		if (p == NULL)
1038 		{
1039 			p = end;
1040 			noeol = true;
1041 		}
1042 
1043 		if (TrafficLogFile != NULL)
1044 			(void) sm_io_fprintf(TrafficLogFile, SM_TIME_DEFAULT,
1045 					     "%05d >>> ", (int) CurrentPid);
1046 
1047 		/* check for line overflow */
1048 		while (mci->mci_mailer->m_linelimit > 0 &&
1049 		       (p - l + slop) > mci->mci_mailer->m_linelimit)
1050 		{
1051 			char *l_base = l;
1052 			register char *q = &l[mci->mci_mailer->m_linelimit - slop - 1];
1053 
1054 			if (l[0] == '.' && slop == 0 &&
1055 			    bitnset(M_XDOT, mci->mci_mailer->m_flags))
1056 			{
1057 				if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT,
1058 					       '.') == SM_IO_EOF)
1059 					dead = true;
1060 				else
1061 				{
1062 					/* record progress for DATA timeout */
1063 					DataProgress = true;
1064 				}
1065 				if (TrafficLogFile != NULL)
1066 					(void) sm_io_putc(TrafficLogFile,
1067 							  SM_TIME_DEFAULT, '.');
1068 			}
1069 			else if (l[0] == 'F' && slop == 0 &&
1070 				 bitset(PXLF_MAPFROM, pxflags) &&
1071 				 strncmp(l, "From ", 5) == 0 &&
1072 				 bitnset(M_ESCFROM, mci->mci_mailer->m_flags))
1073 			{
1074 				if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT,
1075 					       '>') == SM_IO_EOF)
1076 					dead = true;
1077 				else
1078 				{
1079 					/* record progress for DATA timeout */
1080 					DataProgress = true;
1081 				}
1082 				if (TrafficLogFile != NULL)
1083 					(void) sm_io_putc(TrafficLogFile,
1084 							  SM_TIME_DEFAULT,
1085 							  '>');
1086 			}
1087 			if (dead)
1088 				break;
1089 
1090 			while (l < q)
1091 			{
1092 				if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT,
1093 					       (unsigned char) *l++) == SM_IO_EOF)
1094 				{
1095 					dead = true;
1096 					break;
1097 				}
1098 				else
1099 				{
1100 					/* record progress for DATA timeout */
1101 					DataProgress = true;
1102 				}
1103 			}
1104 			if (dead)
1105 				break;
1106 
1107 			if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT, '!') ==
1108 			    SM_IO_EOF ||
1109 			    sm_io_fputs(mci->mci_out, SM_TIME_DEFAULT,
1110 					mci->mci_mailer->m_eol) ==
1111 			    SM_IO_EOF ||
1112 			    sm_io_putc(mci->mci_out, SM_TIME_DEFAULT, ' ') ==
1113 			    SM_IO_EOF)
1114 			{
1115 				dead = true;
1116 				break;
1117 			}
1118 			else
1119 			{
1120 				/* record progress for DATA timeout */
1121 				DataProgress = true;
1122 			}
1123 			if (TrafficLogFile != NULL)
1124 			{
1125 				for (l = l_base; l < q; l++)
1126 					(void) sm_io_putc(TrafficLogFile,
1127 							  SM_TIME_DEFAULT,
1128 							  (unsigned char)*l);
1129 				(void) sm_io_fprintf(TrafficLogFile,
1130 						     SM_TIME_DEFAULT,
1131 						     "!\n%05d >>>  ",
1132 						     (int) CurrentPid);
1133 			}
1134 			slop = 1;
1135 		}
1136 
1137 		if (dead)
1138 			break;
1139 
1140 		/* output last part */
1141 		if (l[0] == '.' && slop == 0 &&
1142 		    bitnset(M_XDOT, mci->mci_mailer->m_flags))
1143 		{
1144 			if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT, '.') ==
1145 			    SM_IO_EOF)
1146 				break;
1147 			else
1148 			{
1149 				/* record progress for DATA timeout */
1150 				DataProgress = true;
1151 			}
1152 			if (TrafficLogFile != NULL)
1153 				(void) sm_io_putc(TrafficLogFile,
1154 						  SM_TIME_DEFAULT, '.');
1155 		}
1156 		else if (l[0] == 'F' && slop == 0 &&
1157 			 bitset(PXLF_MAPFROM, pxflags) &&
1158 			 strncmp(l, "From ", 5) == 0 &&
1159 			 bitnset(M_ESCFROM, mci->mci_mailer->m_flags))
1160 		{
1161 			if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT, '>') ==
1162 			    SM_IO_EOF)
1163 				break;
1164 			else
1165 			{
1166 				/* record progress for DATA timeout */
1167 				DataProgress = true;
1168 			}
1169 			if (TrafficLogFile != NULL)
1170 				(void) sm_io_putc(TrafficLogFile,
1171 						  SM_TIME_DEFAULT, '>');
1172 		}
1173 		for ( ; l < p; ++l)
1174 		{
1175 			if (TrafficLogFile != NULL)
1176 				(void) sm_io_putc(TrafficLogFile,
1177 						  SM_TIME_DEFAULT,
1178 						  (unsigned char)*l);
1179 			if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT,
1180 				       (unsigned char) *l) == SM_IO_EOF)
1181 			{
1182 				dead = true;
1183 				break;
1184 			}
1185 			else
1186 			{
1187 				/* record progress for DATA timeout */
1188 				DataProgress = true;
1189 			}
1190 		}
1191 		if (dead)
1192 			break;
1193 
1194 		if (TrafficLogFile != NULL)
1195 			(void) sm_io_putc(TrafficLogFile, SM_TIME_DEFAULT,
1196 					  '\n');
1197 		if ((!bitset(PXLF_NOADDEOL, pxflags) || !noeol) &&
1198 		    sm_io_fputs(mci->mci_out, SM_TIME_DEFAULT,
1199 				mci->mci_mailer->m_eol) == SM_IO_EOF)
1200 			break;
1201 		else
1202 		{
1203 			/* record progress for DATA timeout */
1204 			DataProgress = true;
1205 		}
1206 		if (l < end && *l == '\n')
1207 		{
1208 			if (*++l != ' ' && *l != '\t' && *l != '\0' &&
1209 			    bitset(PXLF_HEADER, pxflags))
1210 			{
1211 				if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT,
1212 					       ' ') == SM_IO_EOF)
1213 					break;
1214 				else
1215 				{
1216 					/* record progress for DATA timeout */
1217 					DataProgress = true;
1218 				}
1219 
1220 				if (TrafficLogFile != NULL)
1221 					(void) sm_io_putc(TrafficLogFile,
1222 							  SM_TIME_DEFAULT, ' ');
1223 			}
1224 		}
1225 
1226 		/* record progress for DATA timeout */
1227 		DataProgress = true;
1228 	} while (l < end);
1229 }
1230 /*
1231 **  XUNLINK -- unlink a file, doing logging as appropriate.
1232 **
1233 **	Parameters:
1234 **		f -- name of file to unlink.
1235 **
1236 **	Returns:
1237 **		return value of unlink()
1238 **
1239 **	Side Effects:
1240 **		f is unlinked.
1241 */
1242 
1243 int
1244 xunlink(f)
1245 	char *f;
1246 {
1247 	register int i;
1248 	int save_errno;
1249 
1250 	if (LogLevel > 98)
1251 		sm_syslog(LOG_DEBUG, CurEnv->e_id, "unlink %s", f);
1252 
1253 	i = unlink(f);
1254 	save_errno = errno;
1255 	if (i < 0 && LogLevel > 97)
1256 		sm_syslog(LOG_DEBUG, CurEnv->e_id, "%s: unlink-fail %d",
1257 			  f, errno);
1258 	if (i >= 0)
1259 		SYNC_DIR(f, false);
1260 	errno = save_errno;
1261 	return i;
1262 }
1263 /*
1264 **  SFGETS -- "safe" fgets -- times out and ignores random interrupts.
1265 **
1266 **	Parameters:
1267 **		buf -- place to put the input line.
1268 **		siz -- size of buf.
1269 **		fp -- file to read from.
1270 **		timeout -- the timeout before error occurs.
1271 **		during -- what we are trying to read (for error messages).
1272 **
1273 **	Returns:
1274 **		NULL on error (including timeout).  This may also leave
1275 **			buf containing a null string.
1276 **		buf otherwise.
1277 */
1278 
1279 
1280 char *
1281 sfgets(buf, siz, fp, timeout, during)
1282 	char *buf;
1283 	int siz;
1284 	SM_FILE_T *fp;
1285 	time_t timeout;
1286 	char *during;
1287 {
1288 	register char *p;
1289 	int save_errno;
1290 	int io_timeout;
1291 
1292 	SM_REQUIRE(siz > 0);
1293 	SM_REQUIRE(buf != NULL);
1294 
1295 	if (fp == NULL)
1296 	{
1297 		buf[0] = '\0';
1298 		errno = EBADF;
1299 		return NULL;
1300 	}
1301 
1302 	/* try to read */
1303 	p = NULL;
1304 	errno = 0;
1305 
1306 	/* convert the timeout to sm_io notation */
1307 	io_timeout = (timeout <= 0) ? SM_TIME_DEFAULT : timeout * 1000;
1308 	while (!sm_io_eof(fp) && !sm_io_error(fp))
1309 	{
1310 		errno = 0;
1311 		p = sm_io_fgets(fp, io_timeout, buf, siz);
1312 		if (p == NULL && errno == EAGAIN)
1313 		{
1314 			/* The sm_io_fgets() call timedout */
1315 			if (LogLevel > 1)
1316 				sm_syslog(LOG_NOTICE, CurEnv->e_id,
1317 					  "timeout waiting for input from %.100s during %s",
1318 					  CURHOSTNAME,
1319 					  during);
1320 			buf[0] = '\0';
1321 #if XDEBUG
1322 			checkfd012(during);
1323 #endif /* XDEBUG */
1324 			if (TrafficLogFile != NULL)
1325 				(void) sm_io_fprintf(TrafficLogFile,
1326 						     SM_TIME_DEFAULT,
1327 						     "%05d <<< [TIMEOUT]\n",
1328 						     (int) CurrentPid);
1329 			errno = ETIMEDOUT;
1330 			return NULL;
1331 		}
1332 		if (p != NULL || errno != EINTR)
1333 			break;
1334 		(void) sm_io_clearerr(fp);
1335 	}
1336 	save_errno = errno;
1337 
1338 	/* clean up the books and exit */
1339 	LineNumber++;
1340 	if (p == NULL)
1341 	{
1342 		buf[0] = '\0';
1343 		if (TrafficLogFile != NULL)
1344 			(void) sm_io_fprintf(TrafficLogFile, SM_TIME_DEFAULT,
1345 					     "%05d <<< [EOF]\n",
1346 					     (int) CurrentPid);
1347 		errno = save_errno;
1348 		return NULL;
1349 	}
1350 	if (TrafficLogFile != NULL)
1351 		(void) sm_io_fprintf(TrafficLogFile, SM_TIME_DEFAULT,
1352 				     "%05d <<< %s", (int) CurrentPid, buf);
1353 	if (SevenBitInput)
1354 	{
1355 		for (p = buf; *p != '\0'; p++)
1356 			*p &= ~0200;
1357 	}
1358 	else if (!HasEightBits)
1359 	{
1360 		for (p = buf; *p != '\0'; p++)
1361 		{
1362 			if (bitset(0200, *p))
1363 			{
1364 				HasEightBits = true;
1365 				break;
1366 			}
1367 		}
1368 	}
1369 	return buf;
1370 }
1371 /*
1372 **  FGETFOLDED -- like fgets, but knows about folded lines.
1373 **
1374 **	Parameters:
1375 **		buf -- place to put result.
1376 **		n -- bytes available.
1377 **		f -- file to read from.
1378 **
1379 **	Returns:
1380 **		input line(s) on success, NULL on error or SM_IO_EOF.
1381 **		This will normally be buf -- unless the line is too
1382 **			long, when it will be sm_malloc_x()ed.
1383 **
1384 **	Side Effects:
1385 **		buf gets lines from f, with continuation lines (lines
1386 **		with leading white space) appended.  CRLF's are mapped
1387 **		into single newlines.  Any trailing NL is stripped.
1388 */
1389 
1390 char *
1391 fgetfolded(buf, n, f)
1392 	char *buf;
1393 	register int n;
1394 	SM_FILE_T *f;
1395 {
1396 	register char *p = buf;
1397 	char *bp = buf;
1398 	register int i;
1399 
1400 	SM_REQUIRE(n > 0);
1401 	SM_REQUIRE(buf != NULL);
1402 	if (f == NULL)
1403 	{
1404 		buf[0] = '\0';
1405 		errno = EBADF;
1406 		return NULL;
1407 	}
1408 
1409 	n--;
1410 	while ((i = sm_io_getc(f, SM_TIME_DEFAULT)) != SM_IO_EOF)
1411 	{
1412 		if (i == '\r')
1413 		{
1414 			i = sm_io_getc(f, SM_TIME_DEFAULT);
1415 			if (i != '\n')
1416 			{
1417 				if (i != SM_IO_EOF)
1418 					(void) sm_io_ungetc(f, SM_TIME_DEFAULT,
1419 							    i);
1420 				i = '\r';
1421 			}
1422 		}
1423 		if (--n <= 0)
1424 		{
1425 			/* allocate new space */
1426 			char *nbp;
1427 			int nn;
1428 
1429 			nn = (p - bp);
1430 			if (nn < MEMCHUNKSIZE)
1431 				nn *= 2;
1432 			else
1433 				nn += MEMCHUNKSIZE;
1434 			nbp = sm_malloc_x(nn);
1435 			memmove(nbp, bp, p - bp);
1436 			p = &nbp[p - bp];
1437 			if (bp != buf)
1438 				sm_free(bp);
1439 			bp = nbp;
1440 			n = nn - (p - bp);
1441 		}
1442 		*p++ = i;
1443 		if (i == '\n')
1444 		{
1445 			LineNumber++;
1446 			i = sm_io_getc(f, SM_TIME_DEFAULT);
1447 			if (i != SM_IO_EOF)
1448 				(void) sm_io_ungetc(f, SM_TIME_DEFAULT, i);
1449 			if (i != ' ' && i != '\t')
1450 				break;
1451 		}
1452 	}
1453 	if (p == bp)
1454 		return NULL;
1455 	if (p[-1] == '\n')
1456 		p--;
1457 	*p = '\0';
1458 	return bp;
1459 }
1460 /*
1461 **  CURTIME -- return current time.
1462 **
1463 **	Parameters:
1464 **		none.
1465 **
1466 **	Returns:
1467 **		the current time.
1468 */
1469 
1470 time_t
1471 curtime()
1472 {
1473 	auto time_t t;
1474 
1475 	(void) time(&t);
1476 	return t;
1477 }
1478 /*
1479 **  ATOBOOL -- convert a string representation to boolean.
1480 **
1481 **	Defaults to false
1482 **
1483 **	Parameters:
1484 **		s -- string to convert.  Takes "tTyY", empty, and NULL as true,
1485 **			others as false.
1486 **
1487 **	Returns:
1488 **		A boolean representation of the string.
1489 */
1490 
1491 bool
1492 atobool(s)
1493 	register char *s;
1494 {
1495 	if (s == NULL || *s == '\0' || strchr("tTyY", *s) != NULL)
1496 		return true;
1497 	return false;
1498 }
1499 /*
1500 **  ATOOCT -- convert a string representation to octal.
1501 **
1502 **	Parameters:
1503 **		s -- string to convert.
1504 **
1505 **	Returns:
1506 **		An integer representing the string interpreted as an
1507 **		octal number.
1508 */
1509 
1510 int
1511 atooct(s)
1512 	register char *s;
1513 {
1514 	register int i = 0;
1515 
1516 	while (*s >= '0' && *s <= '7')
1517 		i = (i << 3) | (*s++ - '0');
1518 	return i;
1519 }
1520 /*
1521 **  BITINTERSECT -- tell if two bitmaps intersect
1522 **
1523 **	Parameters:
1524 **		a, b -- the bitmaps in question
1525 **
1526 **	Returns:
1527 **		true if they have a non-null intersection
1528 **		false otherwise
1529 */
1530 
1531 bool
1532 bitintersect(a, b)
1533 	BITMAP256 a;
1534 	BITMAP256 b;
1535 {
1536 	int i;
1537 
1538 	for (i = BITMAPBYTES / sizeof (int); --i >= 0; )
1539 	{
1540 		if ((a[i] & b[i]) != 0)
1541 			return true;
1542 	}
1543 	return false;
1544 }
1545 /*
1546 **  BITZEROP -- tell if a bitmap is all zero
1547 **
1548 **	Parameters:
1549 **		map -- the bit map to check
1550 **
1551 **	Returns:
1552 **		true if map is all zero.
1553 **		false if there are any bits set in map.
1554 */
1555 
1556 bool
1557 bitzerop(map)
1558 	BITMAP256 map;
1559 {
1560 	int i;
1561 
1562 	for (i = BITMAPBYTES / sizeof (int); --i >= 0; )
1563 	{
1564 		if (map[i] != 0)
1565 			return false;
1566 	}
1567 	return true;
1568 }
1569 /*
1570 **  STRCONTAINEDIN -- tell if one string is contained in another
1571 **
1572 **	Parameters:
1573 **		icase -- ignore case?
1574 **		a -- possible substring.
1575 **		b -- possible superstring.
1576 **
1577 **	Returns:
1578 **		true if a is contained in b (case insensitive).
1579 **		false otherwise.
1580 */
1581 
1582 bool
1583 strcontainedin(icase, a, b)
1584 	bool icase;
1585 	register char *a;
1586 	register char *b;
1587 {
1588 	int la;
1589 	int lb;
1590 	int c;
1591 
1592 	la = strlen(a);
1593 	lb = strlen(b);
1594 	c = *a;
1595 	if (icase && isascii(c) && isupper(c))
1596 		c = tolower(c);
1597 	for (; lb-- >= la; b++)
1598 	{
1599 		if (icase)
1600 		{
1601 			if (*b != c &&
1602 			    isascii(*b) && isupper(*b) && tolower(*b) != c)
1603 				continue;
1604 			if (sm_strncasecmp(a, b, la) == 0)
1605 				return true;
1606 		}
1607 		else
1608 		{
1609 			if (*b != c)
1610 				continue;
1611 			if (strncmp(a, b, la) == 0)
1612 				return true;
1613 		}
1614 	}
1615 	return false;
1616 }
1617 /*
1618 **  CHECKFD012 -- check low numbered file descriptors
1619 **
1620 **	File descriptors 0, 1, and 2 should be open at all times.
1621 **	This routine verifies that, and fixes it if not true.
1622 **
1623 **	Parameters:
1624 **		where -- a tag printed if the assertion failed
1625 **
1626 **	Returns:
1627 **		none
1628 */
1629 
1630 void
1631 checkfd012(where)
1632 	char *where;
1633 {
1634 #if XDEBUG
1635 	register int i;
1636 
1637 	for (i = 0; i < 3; i++)
1638 		fill_fd(i, where);
1639 #endif /* XDEBUG */
1640 }
1641 /*
1642 **  CHECKFDOPEN -- make sure file descriptor is open -- for extended debugging
1643 **
1644 **	Parameters:
1645 **		fd -- file descriptor to check.
1646 **		where -- tag to print on failure.
1647 **
1648 **	Returns:
1649 **		none.
1650 */
1651 
1652 void
1653 checkfdopen(fd, where)
1654 	int fd;
1655 	char *where;
1656 {
1657 #if XDEBUG
1658 	struct stat st;
1659 
1660 	if (fstat(fd, &st) < 0 && errno == EBADF)
1661 	{
1662 		syserr("checkfdopen(%d): %s not open as expected!", fd, where);
1663 		printopenfds(true);
1664 	}
1665 #endif /* XDEBUG */
1666 }
1667 /*
1668 **  CHECKFDS -- check for new or missing file descriptors
1669 **
1670 **	Parameters:
1671 **		where -- tag for printing.  If null, take a base line.
1672 **
1673 **	Returns:
1674 **		none
1675 **
1676 **	Side Effects:
1677 **		If where is set, shows changes since the last call.
1678 */
1679 
1680 void
1681 checkfds(where)
1682 	char *where;
1683 {
1684 	int maxfd;
1685 	register int fd;
1686 	bool printhdr = true;
1687 	int save_errno = errno;
1688 	static BITMAP256 baseline;
1689 	extern int DtableSize;
1690 
1691 	if (DtableSize > BITMAPBITS)
1692 		maxfd = BITMAPBITS;
1693 	else
1694 		maxfd = DtableSize;
1695 	if (where == NULL)
1696 		clrbitmap(baseline);
1697 
1698 	for (fd = 0; fd < maxfd; fd++)
1699 	{
1700 		struct stat stbuf;
1701 
1702 		if (fstat(fd, &stbuf) < 0 && errno != EOPNOTSUPP)
1703 		{
1704 			if (!bitnset(fd, baseline))
1705 				continue;
1706 			clrbitn(fd, baseline);
1707 		}
1708 		else if (!bitnset(fd, baseline))
1709 			setbitn(fd, baseline);
1710 		else
1711 			continue;
1712 
1713 		/* file state has changed */
1714 		if (where == NULL)
1715 			continue;
1716 		if (printhdr)
1717 		{
1718 			sm_syslog(LOG_DEBUG, CurEnv->e_id,
1719 				  "%s: changed fds:",
1720 				  where);
1721 			printhdr = false;
1722 		}
1723 		dumpfd(fd, true, true);
1724 	}
1725 	errno = save_errno;
1726 }
1727 /*
1728 **  PRINTOPENFDS -- print the open file descriptors (for debugging)
1729 **
1730 **	Parameters:
1731 **		logit -- if set, send output to syslog; otherwise
1732 **			print for debugging.
1733 **
1734 **	Returns:
1735 **		none.
1736 */
1737 
1738 #if NETINET || NETINET6
1739 # include <arpa/inet.h>
1740 #endif /* NETINET || NETINET6 */
1741 
1742 void
1743 printopenfds(logit)
1744 	bool logit;
1745 {
1746 	register int fd;
1747 	extern int DtableSize;
1748 
1749 	for (fd = 0; fd < DtableSize; fd++)
1750 		dumpfd(fd, false, logit);
1751 }
1752 /*
1753 **  DUMPFD -- dump a file descriptor
1754 **
1755 **	Parameters:
1756 **		fd -- the file descriptor to dump.
1757 **		printclosed -- if set, print a notification even if
1758 **			it is closed; otherwise print nothing.
1759 **		logit -- if set, use sm_syslog instead of sm_dprintf()
1760 **
1761 **	Returns:
1762 **		none.
1763 */
1764 
1765 void
1766 dumpfd(fd, printclosed, logit)
1767 	int fd;
1768 	bool printclosed;
1769 	bool logit;
1770 {
1771 	register char *p;
1772 	char *hp;
1773 #ifdef S_IFSOCK
1774 	SOCKADDR sa;
1775 #endif /* S_IFSOCK */
1776 	auto SOCKADDR_LEN_T slen;
1777 	int i;
1778 #if STAT64 > 0
1779 	struct stat64 st;
1780 #else /* STAT64 > 0 */
1781 	struct stat st;
1782 #endif /* STAT64 > 0 */
1783 	char buf[200];
1784 
1785 	p = buf;
1786 	(void) sm_snprintf(p, SPACELEFT(buf, p), "%3d: ", fd);
1787 	p += strlen(p);
1788 
1789 	if (
1790 #if STAT64 > 0
1791 	    fstat64(fd, &st)
1792 #else /* STAT64 > 0 */
1793 	    fstat(fd, &st)
1794 #endif /* STAT64 > 0 */
1795 	    < 0)
1796 	{
1797 		if (errno != EBADF)
1798 		{
1799 			(void) sm_snprintf(p, SPACELEFT(buf, p),
1800 				"CANNOT STAT (%s)",
1801 				sm_errstring(errno));
1802 			goto printit;
1803 		}
1804 		else if (printclosed)
1805 		{
1806 			(void) sm_snprintf(p, SPACELEFT(buf, p), "CLOSED");
1807 			goto printit;
1808 		}
1809 		return;
1810 	}
1811 
1812 	i = fcntl(fd, F_GETFL, 0);
1813 	if (i != -1)
1814 	{
1815 		(void) sm_snprintf(p, SPACELEFT(buf, p), "fl=0x%x, ", i);
1816 		p += strlen(p);
1817 	}
1818 
1819 	(void) sm_snprintf(p, SPACELEFT(buf, p), "mode=%o: ",
1820 			(int) st.st_mode);
1821 	p += strlen(p);
1822 	switch (st.st_mode & S_IFMT)
1823 	{
1824 #ifdef S_IFSOCK
1825 	  case S_IFSOCK:
1826 		(void) sm_snprintf(p, SPACELEFT(buf, p), "SOCK ");
1827 		p += strlen(p);
1828 		memset(&sa, '\0', sizeof sa);
1829 		slen = sizeof sa;
1830 		if (getsockname(fd, &sa.sa, &slen) < 0)
1831 			(void) sm_snprintf(p, SPACELEFT(buf, p), "(%s)",
1832 				 sm_errstring(errno));
1833 		else
1834 		{
1835 			hp = hostnamebyanyaddr(&sa);
1836 			if (hp == NULL)
1837 			{
1838 				/* EMPTY */
1839 				/* do nothing */
1840 			}
1841 # if NETINET
1842 			else if (sa.sa.sa_family == AF_INET)
1843 				(void) sm_snprintf(p, SPACELEFT(buf, p),
1844 					"%s/%d", hp, ntohs(sa.sin.sin_port));
1845 # endif /* NETINET */
1846 # if NETINET6
1847 			else if (sa.sa.sa_family == AF_INET6)
1848 				(void) sm_snprintf(p, SPACELEFT(buf, p),
1849 					"%s/%d", hp, ntohs(sa.sin6.sin6_port));
1850 # endif /* NETINET6 */
1851 			else
1852 				(void) sm_snprintf(p, SPACELEFT(buf, p),
1853 					"%s", hp);
1854 		}
1855 		p += strlen(p);
1856 		(void) sm_snprintf(p, SPACELEFT(buf, p), "->");
1857 		p += strlen(p);
1858 		slen = sizeof sa;
1859 		if (getpeername(fd, &sa.sa, &slen) < 0)
1860 			(void) sm_snprintf(p, SPACELEFT(buf, p), "(%s)",
1861 					sm_errstring(errno));
1862 		else
1863 		{
1864 			hp = hostnamebyanyaddr(&sa);
1865 			if (hp == NULL)
1866 			{
1867 				/* EMPTY */
1868 				/* do nothing */
1869 			}
1870 # if NETINET
1871 			else if (sa.sa.sa_family == AF_INET)
1872 				(void) sm_snprintf(p, SPACELEFT(buf, p),
1873 					"%s/%d", hp, ntohs(sa.sin.sin_port));
1874 # endif /* NETINET */
1875 # if NETINET6
1876 			else if (sa.sa.sa_family == AF_INET6)
1877 				(void) sm_snprintf(p, SPACELEFT(buf, p),
1878 					"%s/%d", hp, ntohs(sa.sin6.sin6_port));
1879 # endif /* NETINET6 */
1880 			else
1881 				(void) sm_snprintf(p, SPACELEFT(buf, p),
1882 					"%s", hp);
1883 		}
1884 		break;
1885 #endif /* S_IFSOCK */
1886 
1887 	  case S_IFCHR:
1888 		(void) sm_snprintf(p, SPACELEFT(buf, p), "CHR: ");
1889 		p += strlen(p);
1890 		goto defprint;
1891 
1892 #ifdef S_IFBLK
1893 	  case S_IFBLK:
1894 		(void) sm_snprintf(p, SPACELEFT(buf, p), "BLK: ");
1895 		p += strlen(p);
1896 		goto defprint;
1897 #endif /* S_IFBLK */
1898 
1899 #if defined(S_IFIFO) && (!defined(S_IFSOCK) || S_IFIFO != S_IFSOCK)
1900 	  case S_IFIFO:
1901 		(void) sm_snprintf(p, SPACELEFT(buf, p), "FIFO: ");
1902 		p += strlen(p);
1903 		goto defprint;
1904 #endif /* defined(S_IFIFO) && (!defined(S_IFSOCK) || S_IFIFO != S_IFSOCK) */
1905 
1906 #ifdef S_IFDIR
1907 	  case S_IFDIR:
1908 		(void) sm_snprintf(p, SPACELEFT(buf, p), "DIR: ");
1909 		p += strlen(p);
1910 		goto defprint;
1911 #endif /* S_IFDIR */
1912 
1913 #ifdef S_IFLNK
1914 	  case S_IFLNK:
1915 		(void) sm_snprintf(p, SPACELEFT(buf, p), "LNK: ");
1916 		p += strlen(p);
1917 		goto defprint;
1918 #endif /* S_IFLNK */
1919 
1920 	  default:
1921 defprint:
1922 		(void) sm_snprintf(p, SPACELEFT(buf, p),
1923 			 "dev=%d/%d, ino=%llu, nlink=%d, u/gid=%d/%d, ",
1924 			 major(st.st_dev), minor(st.st_dev),
1925 			 (ULONGLONG_T) st.st_ino,
1926 			 (int) st.st_nlink, (int) st.st_uid,
1927 			 (int) st.st_gid);
1928 		p += strlen(p);
1929 		(void) sm_snprintf(p, SPACELEFT(buf, p), "size=%llu",
1930 			 (ULONGLONG_T) st.st_size);
1931 		break;
1932 	}
1933 
1934 printit:
1935 	if (logit)
1936 		sm_syslog(LOG_DEBUG, CurEnv ? CurEnv->e_id : NULL,
1937 			  "%.800s", buf);
1938 	else
1939 		sm_dprintf("%s\n", buf);
1940 }
1941 /*
1942 **  SHORTEN_HOSTNAME -- strip local domain information off of hostname.
1943 **
1944 **	Parameters:
1945 **		host -- the host to shorten (stripped in place).
1946 **
1947 **	Returns:
1948 **		place where string was truncated, NULL if not truncated.
1949 */
1950 
1951 char *
1952 shorten_hostname(host)
1953 	char host[];
1954 {
1955 	register char *p;
1956 	char *mydom;
1957 	int i;
1958 	bool canon = false;
1959 
1960 	/* strip off final dot */
1961 	i = strlen(host);
1962 	p = &host[(i == 0) ? 0 : i - 1];
1963 	if (*p == '.')
1964 	{
1965 		*p = '\0';
1966 		canon = true;
1967 	}
1968 
1969 	/* see if there is any domain at all -- if not, we are done */
1970 	p = strchr(host, '.');
1971 	if (p == NULL)
1972 		return NULL;
1973 
1974 	/* yes, we have a domain -- see if it looks like us */
1975 	mydom = macvalue('m', CurEnv);
1976 	if (mydom == NULL)
1977 		mydom = "";
1978 	i = strlen(++p);
1979 	if ((canon ? sm_strcasecmp(p, mydom)
1980 		   : sm_strncasecmp(p, mydom, i)) == 0 &&
1981 			(mydom[i] == '.' || mydom[i] == '\0'))
1982 	{
1983 		*--p = '\0';
1984 		return p;
1985 	}
1986 	return NULL;
1987 }
1988 /*
1989 **  PROG_OPEN -- open a program for reading
1990 **
1991 **	Parameters:
1992 **		argv -- the argument list.
1993 **		pfd -- pointer to a place to store the file descriptor.
1994 **		e -- the current envelope.
1995 **
1996 **	Returns:
1997 **		pid of the process -- -1 if it failed.
1998 */
1999 
2000 pid_t
2001 prog_open(argv, pfd, e)
2002 	char **argv;
2003 	int *pfd;
2004 	ENVELOPE *e;
2005 {
2006 	pid_t pid;
2007 	int save_errno;
2008 	int sff;
2009 	int ret;
2010 	int fdv[2];
2011 	char *p, *q;
2012 	char buf[MAXPATHLEN];
2013 	extern int DtableSize;
2014 
2015 	if (pipe(fdv) < 0)
2016 	{
2017 		syserr("%s: cannot create pipe for stdout", argv[0]);
2018 		return -1;
2019 	}
2020 	pid = fork();
2021 	if (pid < 0)
2022 	{
2023 		syserr("%s: cannot fork", argv[0]);
2024 		(void) close(fdv[0]);
2025 		(void) close(fdv[1]);
2026 		return -1;
2027 	}
2028 	if (pid > 0)
2029 	{
2030 		/* parent */
2031 		(void) close(fdv[1]);
2032 		*pfd = fdv[0];
2033 		return pid;
2034 	}
2035 
2036 	/* Reset global flags */
2037 	RestartRequest = NULL;
2038 	RestartWorkGroup = false;
2039 	ShutdownRequest = NULL;
2040 	PendingSignal = 0;
2041 	CurrentPid = getpid();
2042 
2043 	/*
2044 	**  Initialize exception stack and default exception
2045 	**  handler for child process.
2046 	*/
2047 
2048 	sm_exc_newthread(fatal_error);
2049 
2050 	/* child -- close stdin */
2051 	(void) close(0);
2052 
2053 	/* stdout goes back to parent */
2054 	(void) close(fdv[0]);
2055 	if (dup2(fdv[1], 1) < 0)
2056 	{
2057 		syserr("%s: cannot dup2 for stdout", argv[0]);
2058 		_exit(EX_OSERR);
2059 	}
2060 	(void) close(fdv[1]);
2061 
2062 	/* stderr goes to transcript if available */
2063 	if (e->e_xfp != NULL)
2064 	{
2065 		int xfd;
2066 
2067 		xfd = sm_io_getinfo(e->e_xfp, SM_IO_WHAT_FD, NULL);
2068 		if (xfd >= 0 && dup2(xfd, 2) < 0)
2069 		{
2070 			syserr("%s: cannot dup2 for stderr", argv[0]);
2071 			_exit(EX_OSERR);
2072 		}
2073 	}
2074 
2075 	/* this process has no right to the queue file */
2076 	if (e->e_lockfp != NULL)
2077 		(void) close(sm_io_getinfo(e->e_lockfp, SM_IO_WHAT_FD, NULL));
2078 
2079 	/* chroot to the program mailer directory, if defined */
2080 	if (ProgMailer != NULL && ProgMailer->m_rootdir != NULL)
2081 	{
2082 		expand(ProgMailer->m_rootdir, buf, sizeof buf, e);
2083 		if (chroot(buf) < 0)
2084 		{
2085 			syserr("prog_open: cannot chroot(%s)", buf);
2086 			exit(EX_TEMPFAIL);
2087 		}
2088 		if (chdir("/") < 0)
2089 		{
2090 			syserr("prog_open: cannot chdir(/)");
2091 			exit(EX_TEMPFAIL);
2092 		}
2093 	}
2094 
2095 	/* run as default user */
2096 	endpwent();
2097 	sm_mbdb_terminate();
2098 	if (setgid(DefGid) < 0 && geteuid() == 0)
2099 	{
2100 		syserr("prog_open: setgid(%ld) failed", (long) DefGid);
2101 		exit(EX_TEMPFAIL);
2102 	}
2103 	if (setuid(DefUid) < 0 && geteuid() == 0)
2104 	{
2105 		syserr("prog_open: setuid(%ld) failed", (long) DefUid);
2106 		exit(EX_TEMPFAIL);
2107 	}
2108 
2109 	/* run in some directory */
2110 	if (ProgMailer != NULL)
2111 		p = ProgMailer->m_execdir;
2112 	else
2113 		p = NULL;
2114 	for (; p != NULL; p = q)
2115 	{
2116 		q = strchr(p, ':');
2117 		if (q != NULL)
2118 			*q = '\0';
2119 		expand(p, buf, sizeof buf, e);
2120 		if (q != NULL)
2121 			*q++ = ':';
2122 		if (buf[0] != '\0' && chdir(buf) >= 0)
2123 			break;
2124 	}
2125 	if (p == NULL)
2126 	{
2127 		/* backup directories */
2128 		if (chdir("/tmp") < 0)
2129 			(void) chdir("/");
2130 	}
2131 
2132 	/* Check safety of program to be run */
2133 	sff = SFF_ROOTOK|SFF_EXECOK;
2134 	if (!bitnset(DBS_RUNWRITABLEPROGRAM, DontBlameSendmail))
2135 		sff |= SFF_NOGWFILES|SFF_NOWWFILES;
2136 	if (bitnset(DBS_RUNPROGRAMINUNSAFEDIRPATH, DontBlameSendmail))
2137 		sff |= SFF_NOPATHCHECK;
2138 	else
2139 		sff |= SFF_SAFEDIRPATH;
2140 	ret = safefile(argv[0], DefUid, DefGid, DefUser, sff, 0, NULL);
2141 	if (ret != 0)
2142 		sm_syslog(LOG_INFO, e->e_id,
2143 			  "Warning: prog_open: program %s unsafe: %s",
2144 			  argv[0], sm_errstring(ret));
2145 
2146 	/* arrange for all the files to be closed */
2147 	sm_close_on_exec(STDERR_FILENO + 1, DtableSize);
2148 
2149 	/* now exec the process */
2150 	(void) execve(argv[0], (ARGV_T) argv, (ARGV_T) UserEnviron);
2151 
2152 	/* woops!  failed */
2153 	save_errno = errno;
2154 	syserr("%s: cannot exec", argv[0]);
2155 	if (transienterror(save_errno))
2156 		_exit(EX_OSERR);
2157 	_exit(EX_CONFIG);
2158 	return -1;	/* avoid compiler warning on IRIX */
2159 }
2160 /*
2161 **  GET_COLUMN -- look up a Column in a line buffer
2162 **
2163 **	Parameters:
2164 **		line -- the raw text line to search.
2165 **		col -- the column number to fetch.
2166 **		delim -- the delimiter between columns.  If null,
2167 **			use white space.
2168 **		buf -- the output buffer.
2169 **		buflen -- the length of buf.
2170 **
2171 **	Returns:
2172 **		buf if successful.
2173 **		NULL otherwise.
2174 */
2175 
2176 char *
2177 get_column(line, col, delim, buf, buflen)
2178 	char line[];
2179 	int col;
2180 	int delim;
2181 	char buf[];
2182 	int buflen;
2183 {
2184 	char *p;
2185 	char *begin, *end;
2186 	int i;
2187 	char delimbuf[4];
2188 
2189 	if ((char) delim == '\0')
2190 		(void) sm_strlcpy(delimbuf, "\n\t ", sizeof delimbuf);
2191 	else
2192 	{
2193 		delimbuf[0] = (char) delim;
2194 		delimbuf[1] = '\0';
2195 	}
2196 
2197 	p = line;
2198 	if (*p == '\0')
2199 		return NULL;			/* line empty */
2200 	if (*p == (char) delim && col == 0)
2201 		return NULL;			/* first column empty */
2202 
2203 	begin = line;
2204 
2205 	if (col == 0 && (char) delim == '\0')
2206 	{
2207 		while (*begin != '\0' && isascii(*begin) && isspace(*begin))
2208 			begin++;
2209 	}
2210 
2211 	for (i = 0; i < col; i++)
2212 	{
2213 		if ((begin = strpbrk(begin, delimbuf)) == NULL)
2214 			return NULL;		/* no such column */
2215 		begin++;
2216 		if ((char) delim == '\0')
2217 		{
2218 			while (*begin != '\0' && isascii(*begin) && isspace(*begin))
2219 				begin++;
2220 		}
2221 	}
2222 
2223 	end = strpbrk(begin, delimbuf);
2224 	if (end == NULL)
2225 		i = strlen(begin);
2226 	else
2227 		i = end - begin;
2228 	if (i >= buflen)
2229 		i = buflen - 1;
2230 	(void) sm_strlcpy(buf, begin, i + 1);
2231 	return buf;
2232 }
2233 /*
2234 **  CLEANSTRCPY -- copy string keeping out bogus characters
2235 **
2236 **	Parameters:
2237 **		t -- "to" string.
2238 **		f -- "from" string.
2239 **		l -- length of space available in "to" string.
2240 **
2241 **	Returns:
2242 **		none.
2243 */
2244 
2245 void
2246 cleanstrcpy(t, f, l)
2247 	register char *t;
2248 	register char *f;
2249 	int l;
2250 {
2251 	/* check for newlines and log if necessary */
2252 	(void) denlstring(f, true, true);
2253 
2254 	if (l <= 0)
2255 		syserr("!cleanstrcpy: length == 0");
2256 
2257 	l--;
2258 	while (l > 0 && *f != '\0')
2259 	{
2260 		if (isascii(*f) &&
2261 		    (isalnum(*f) || strchr("!#$%&'*+-./^_`{|}~", *f) != NULL))
2262 		{
2263 			l--;
2264 			*t++ = *f;
2265 		}
2266 		f++;
2267 	}
2268 	*t = '\0';
2269 }
2270 /*
2271 **  DENLSTRING -- convert newlines in a string to spaces
2272 **
2273 **	Parameters:
2274 **		s -- the input string
2275 **		strict -- if set, don't permit continuation lines.
2276 **		logattacks -- if set, log attempted attacks.
2277 **
2278 **	Returns:
2279 **		A pointer to a version of the string with newlines
2280 **		mapped to spaces.  This should be copied.
2281 */
2282 
2283 char *
2284 denlstring(s, strict, logattacks)
2285 	char *s;
2286 	bool strict;
2287 	bool logattacks;
2288 {
2289 	register char *p;
2290 	int l;
2291 	static char *bp = NULL;
2292 	static int bl = 0;
2293 
2294 	p = s;
2295 	while ((p = strchr(p, '\n')) != NULL)
2296 		if (strict || (*++p != ' ' && *p != '\t'))
2297 			break;
2298 	if (p == NULL)
2299 		return s;
2300 
2301 	l = strlen(s) + 1;
2302 	if (bl < l)
2303 	{
2304 		/* allocate more space */
2305 		char *nbp = sm_pmalloc_x(l);
2306 
2307 		if (bp != NULL)
2308 			sm_free(bp);
2309 		bp = nbp;
2310 		bl = l;
2311 	}
2312 	(void) sm_strlcpy(bp, s, l);
2313 	for (p = bp; (p = strchr(p, '\n')) != NULL; )
2314 		*p++ = ' ';
2315 
2316 	if (logattacks)
2317 	{
2318 		sm_syslog(LOG_NOTICE, CurEnv->e_id,
2319 			  "POSSIBLE ATTACK from %.100s: newline in string \"%s\"",
2320 			  RealHostName == NULL ? "[UNKNOWN]" : RealHostName,
2321 			  shortenstring(bp, MAXSHORTSTR));
2322 	}
2323 
2324 	return bp;
2325 }
2326 
2327 /*
2328 **  STRREPLNONPRT -- replace "unprintable" characters in a string with subst
2329 **
2330 **	Parameters:
2331 **		s -- string to manipulate (in place)
2332 **		subst -- character to use as replacement
2333 **
2334 **	Returns:
2335 **		true iff string did not contain "unprintable" characters
2336 */
2337 
2338 bool
2339 strreplnonprt(s, c)
2340 	char *s;
2341 	int c;
2342 {
2343 	bool ok;
2344 
2345 	ok = true;
2346 	if (s == NULL)
2347 		return ok;
2348 	while (*s != '\0')
2349 	{
2350 		if (!(isascii(*s) && isprint(*s)))
2351 		{
2352 			*s = c;
2353 			ok = false;
2354 		}
2355 		++s;
2356 	}
2357 	return ok;
2358 }
2359 
2360 /*
2361 **  STR2PRT -- convert "unprintable" characters in a string to \oct
2362 **
2363 **	Parameters:
2364 **		s -- string to convert
2365 **
2366 **	Returns:
2367 **		converted string.
2368 **		This is a static local buffer, string must be copied
2369 **		before this function is called again!
2370 */
2371 
2372 char *
2373 str2prt(s)
2374 	char *s;
2375 {
2376 	int l;
2377 	char c, *h;
2378 	bool ok;
2379 	static int len = 0;
2380 	static char *buf = NULL;
2381 
2382 	if (s == NULL)
2383 		return NULL;
2384 	ok = true;
2385 	for (h = s, l = 1; *h != '\0'; h++, l++)
2386 	{
2387 		if (*h == '\\')
2388 		{
2389 			++l;
2390 			ok = false;
2391 		}
2392 		else if (!(isascii(*h) && isprint(*h)))
2393 		{
2394 			l += 3;
2395 			ok = false;
2396 		}
2397 	}
2398 	if (ok)
2399 		return s;
2400 	if (l > len)
2401 	{
2402 		char *nbuf = sm_pmalloc_x(l);
2403 
2404 		if (buf != NULL)
2405 			sm_free(buf);
2406 		len = l;
2407 		buf = nbuf;
2408 	}
2409 	for (h = buf; *s != '\0' && l > 0; s++, l--)
2410 	{
2411 		c = *s;
2412 		if (isascii(c) && isprint(c) && c != '\\')
2413 		{
2414 			*h++ = c;
2415 		}
2416 		else
2417 		{
2418 			*h++ = '\\';
2419 			--l;
2420 			switch (c)
2421 			{
2422 			  case '\\':
2423 				*h++ = '\\';
2424 				break;
2425 			  case '\t':
2426 				*h++ = 't';
2427 				break;
2428 			  case '\n':
2429 				*h++ = 'n';
2430 				break;
2431 			  case '\r':
2432 				*h++ = 'r';
2433 				break;
2434 			  default:
2435 				(void) sm_snprintf(h, l, "%03o",
2436 					(unsigned int)((unsigned char) c));
2437 
2438 				/*
2439 				**  XXX since l is unsigned this may
2440 				**  wrap around if the calculation is screwed
2441 				**  up...
2442 				*/
2443 
2444 				l -= 2;
2445 				h += 3;
2446 				break;
2447 			}
2448 		}
2449 	}
2450 	*h = '\0';
2451 	buf[len - 1] = '\0';
2452 	return buf;
2453 }
2454 /*
2455 **  PATH_IS_DIR -- check to see if file exists and is a directory.
2456 **
2457 **	There are some additional checks for security violations in
2458 **	here.  This routine is intended to be used for the host status
2459 **	support.
2460 **
2461 **	Parameters:
2462 **		pathname -- pathname to check for directory-ness.
2463 **		createflag -- if set, create directory if needed.
2464 **
2465 **	Returns:
2466 **		true -- if the indicated pathname is a directory
2467 **		false -- otherwise
2468 */
2469 
2470 bool
2471 path_is_dir(pathname, createflag)
2472 	char *pathname;
2473 	bool createflag;
2474 {
2475 	struct stat statbuf;
2476 
2477 #if HASLSTAT
2478 	if (lstat(pathname, &statbuf) < 0)
2479 #else /* HASLSTAT */
2480 	if (stat(pathname, &statbuf) < 0)
2481 #endif /* HASLSTAT */
2482 	{
2483 		if (errno != ENOENT || !createflag)
2484 			return false;
2485 		if (mkdir(pathname, 0755) < 0)
2486 			return false;
2487 		return true;
2488 	}
2489 	if (!S_ISDIR(statbuf.st_mode))
2490 	{
2491 		errno = ENOTDIR;
2492 		return false;
2493 	}
2494 
2495 	/* security: don't allow writable directories */
2496 	if (bitset(S_IWGRP|S_IWOTH, statbuf.st_mode))
2497 	{
2498 		errno = EACCES;
2499 		return false;
2500 	}
2501 	return true;
2502 }
2503 /*
2504 **  PROC_LIST_ADD -- add process id to list of our children
2505 **
2506 **	Parameters:
2507 **		pid -- pid to add to list.
2508 **		task -- task of pid.
2509 **		type -- type of process.
2510 **		count -- number of processes.
2511 **		other -- other information for this type.
2512 **
2513 **	Returns:
2514 **		none
2515 **
2516 **	Side Effects:
2517 **		May increase CurChildren. May grow ProcList.
2518 */
2519 
2520 typedef struct procs	PROCS_T;
2521 
2522 struct procs
2523 {
2524 	pid_t		proc_pid;
2525 	char		*proc_task;
2526 	int		proc_type;
2527 	int		proc_count;
2528 	int		proc_other;
2529 	SOCKADDR	proc_hostaddr;
2530 };
2531 
2532 static PROCS_T	*volatile ProcListVec = NULL;
2533 static int	ProcListSize = 0;
2534 
2535 void
2536 proc_list_add(pid, task, type, count, other, hostaddr)
2537 	pid_t pid;
2538 	char *task;
2539 	int type;
2540 	int count;
2541 	int other;
2542 	SOCKADDR *hostaddr;
2543 {
2544 	int i;
2545 
2546 	for (i = 0; i < ProcListSize; i++)
2547 	{
2548 		if (ProcListVec[i].proc_pid == NO_PID)
2549 			break;
2550 	}
2551 	if (i >= ProcListSize)
2552 	{
2553 		/* probe the existing vector to avoid growing infinitely */
2554 		proc_list_probe();
2555 
2556 		/* now scan again */
2557 		for (i = 0; i < ProcListSize; i++)
2558 		{
2559 			if (ProcListVec[i].proc_pid == NO_PID)
2560 				break;
2561 		}
2562 	}
2563 	if (i >= ProcListSize)
2564 	{
2565 		/* grow process list */
2566 		PROCS_T *npv;
2567 
2568 		SM_ASSERT(ProcListSize < INT_MAX - PROC_LIST_SEG);
2569 		npv = (PROCS_T *) sm_pmalloc_x((sizeof *npv) *
2570 					       (ProcListSize + PROC_LIST_SEG));
2571 		if (ProcListSize > 0)
2572 		{
2573 			memmove(npv, ProcListVec,
2574 				ProcListSize * sizeof (PROCS_T));
2575 			sm_free(ProcListVec);
2576 		}
2577 
2578 		/* XXX just use memset() to initialize this part? */
2579 		for (i = ProcListSize; i < ProcListSize + PROC_LIST_SEG; i++)
2580 		{
2581 			npv[i].proc_pid = NO_PID;
2582 			npv[i].proc_task = NULL;
2583 			npv[i].proc_type = PROC_NONE;
2584 		}
2585 		i = ProcListSize;
2586 		ProcListSize += PROC_LIST_SEG;
2587 		ProcListVec = npv;
2588 	}
2589 	ProcListVec[i].proc_pid = pid;
2590 	PSTRSET(ProcListVec[i].proc_task, task);
2591 	ProcListVec[i].proc_type = type;
2592 	ProcListVec[i].proc_count = count;
2593 	ProcListVec[i].proc_other = other;
2594 	if (hostaddr != NULL)
2595 		ProcListVec[i].proc_hostaddr = *hostaddr;
2596 	else
2597 		memset(&ProcListVec[i].proc_hostaddr, 0,
2598 			sizeof(ProcListVec[i].proc_hostaddr));
2599 
2600 	/* if process adding itself, it's not a child */
2601 	if (pid != CurrentPid)
2602 	{
2603 		SM_ASSERT(CurChildren < INT_MAX);
2604 		CurChildren++;
2605 	}
2606 }
2607 /*
2608 **  PROC_LIST_SET -- set pid task in process list
2609 **
2610 **	Parameters:
2611 **		pid -- pid to set
2612 **		task -- task of pid
2613 **
2614 **	Returns:
2615 **		none.
2616 */
2617 
2618 void
2619 proc_list_set(pid, task)
2620 	pid_t pid;
2621 	char *task;
2622 {
2623 	int i;
2624 
2625 	for (i = 0; i < ProcListSize; i++)
2626 	{
2627 		if (ProcListVec[i].proc_pid == pid)
2628 		{
2629 			PSTRSET(ProcListVec[i].proc_task, task);
2630 			break;
2631 		}
2632 	}
2633 }
2634 /*
2635 **  PROC_LIST_DROP -- drop pid from process list
2636 **
2637 **	Parameters:
2638 **		pid -- pid to drop
2639 **		st -- process status
2640 **		other -- storage for proc_other (return).
2641 **
2642 **	Returns:
2643 **		none.
2644 **
2645 **	Side Effects:
2646 **		May decrease CurChildren, CurRunners, or
2647 **		set RestartRequest or ShutdownRequest.
2648 **
2649 **	NOTE:	THIS CAN BE CALLED FROM A SIGNAL HANDLER.  DO NOT ADD
2650 **		ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
2651 **		DOING.
2652 */
2653 
2654 void
2655 proc_list_drop(pid, st, other)
2656 	pid_t pid;
2657 	int st;
2658 	int *other;
2659 {
2660 	int i;
2661 	int type = PROC_NONE;
2662 
2663 	for (i = 0; i < ProcListSize; i++)
2664 	{
2665 		if (ProcListVec[i].proc_pid == pid)
2666 		{
2667 			ProcListVec[i].proc_pid = NO_PID;
2668 			type = ProcListVec[i].proc_type;
2669 			if (other != NULL)
2670 				*other = ProcListVec[i].proc_other;
2671 			break;
2672 		}
2673 	}
2674 	if (CurChildren > 0)
2675 		CurChildren--;
2676 
2677 
2678 	if (type == PROC_CONTROL && WIFEXITED(st))
2679 	{
2680 		/* if so, see if we need to restart or shutdown */
2681 		if (WEXITSTATUS(st) == EX_RESTART)
2682 			RestartRequest = "control socket";
2683 		else if (WEXITSTATUS(st) == EX_SHUTDOWN)
2684 			ShutdownRequest = "control socket";
2685 	}
2686 	else if (type == PROC_QUEUE_CHILD && !WIFSTOPPED(st) &&
2687 		 ProcListVec[i].proc_other > -1)
2688 	{
2689 		/* restart this persistent runner */
2690 		mark_work_group_restart(ProcListVec[i].proc_other, st);
2691 	}
2692 	else if (type == PROC_QUEUE)
2693 		CurRunners -= ProcListVec[i].proc_count;
2694 }
2695 /*
2696 **  PROC_LIST_CLEAR -- clear the process list
2697 **
2698 **	Parameters:
2699 **		none.
2700 **
2701 **	Returns:
2702 **		none.
2703 **
2704 **	Side Effects:
2705 **		Sets CurChildren to zero.
2706 */
2707 
2708 void
2709 proc_list_clear()
2710 {
2711 	int i;
2712 
2713 	/* start from 1 since 0 is the daemon itself */
2714 	for (i = 1; i < ProcListSize; i++)
2715 		ProcListVec[i].proc_pid = NO_PID;
2716 	CurChildren = 0;
2717 }
2718 /*
2719 **  PROC_LIST_PROBE -- probe processes in the list to see if they still exist
2720 **
2721 **	Parameters:
2722 **		none
2723 **
2724 **	Returns:
2725 **		none
2726 **
2727 **	Side Effects:
2728 **		May decrease CurChildren.
2729 */
2730 
2731 void
2732 proc_list_probe()
2733 {
2734 	int i;
2735 
2736 	/* start from 1 since 0 is the daemon itself */
2737 	for (i = 1; i < ProcListSize; i++)
2738 	{
2739 		if (ProcListVec[i].proc_pid == NO_PID)
2740 			continue;
2741 		if (kill(ProcListVec[i].proc_pid, 0) < 0)
2742 		{
2743 			if (LogLevel > 3)
2744 				sm_syslog(LOG_DEBUG, CurEnv->e_id,
2745 					  "proc_list_probe: lost pid %d",
2746 					  (int) ProcListVec[i].proc_pid);
2747 			ProcListVec[i].proc_pid = NO_PID;
2748 			SM_FREE_CLR(ProcListVec[i].proc_task);
2749 			CurChildren--;
2750 		}
2751 	}
2752 	if (CurChildren < 0)
2753 		CurChildren = 0;
2754 }
2755 
2756 /*
2757 **  PROC_LIST_DISPLAY -- display the process list
2758 **
2759 **	Parameters:
2760 **		out -- output file pointer
2761 **		prefix -- string to output in front of each line.
2762 **
2763 **	Returns:
2764 **		none.
2765 */
2766 
2767 void
2768 proc_list_display(out, prefix)
2769 	SM_FILE_T *out;
2770 	char *prefix;
2771 {
2772 	int i;
2773 
2774 	for (i = 0; i < ProcListSize; i++)
2775 	{
2776 		if (ProcListVec[i].proc_pid == NO_PID)
2777 			continue;
2778 
2779 		(void) sm_io_fprintf(out, SM_TIME_DEFAULT, "%s%d %s%s\n",
2780 				     prefix,
2781 				     (int) ProcListVec[i].proc_pid,
2782 				     ProcListVec[i].proc_task != NULL ?
2783 				     ProcListVec[i].proc_task : "(unknown)",
2784 				     (OpMode == MD_SMTP ||
2785 				      OpMode == MD_DAEMON ||
2786 				      OpMode == MD_ARPAFTP) ? "\r" : "");
2787 	}
2788 }
2789 
2790 /*
2791 **  PROC_LIST_SIGNAL -- send a signal to a type of process in the list
2792 **
2793 **	Parameters:
2794 **		type -- type of process to signal
2795 **		signal -- the type of signal to send
2796 **
2797 **	Results:
2798 **		none.
2799 **
2800 **	NOTE:	THIS CAN BE CALLED FROM A SIGNAL HANDLER.  DO NOT ADD
2801 **		ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
2802 **		DOING.
2803 */
2804 
2805 void
2806 proc_list_signal(type, signal)
2807 	int type;
2808 	int signal;
2809 {
2810 	int chldwasblocked;
2811 	int alrmwasblocked;
2812 	int i;
2813 	pid_t mypid = getpid();
2814 
2815 	/* block these signals so that we may signal cleanly */
2816 	chldwasblocked = sm_blocksignal(SIGCHLD);
2817 	alrmwasblocked = sm_blocksignal(SIGALRM);
2818 
2819 	/* Find all processes of type and send signal */
2820 	for (i = 0; i < ProcListSize; i++)
2821 	{
2822 		if (ProcListVec[i].proc_pid == NO_PID ||
2823 		    ProcListVec[i].proc_pid == mypid)
2824 			continue;
2825 		if (ProcListVec[i].proc_type != type)
2826 			continue;
2827 		(void) kill(ProcListVec[i].proc_pid, signal);
2828 	}
2829 
2830 	/* restore the signals */
2831 	if (alrmwasblocked == 0)
2832 		(void) sm_releasesignal(SIGALRM);
2833 	if (chldwasblocked == 0)
2834 		(void) sm_releasesignal(SIGCHLD);
2835 }
2836 
2837 /*
2838 **  COUNT_OPEN_CONNECTIONS
2839 **
2840 **	Parameters:
2841 **		hostaddr - ClientAddress
2842 **
2843 **	Returns:
2844 **		the number of open connections for this client
2845 **
2846 */
2847 
2848 int
2849 count_open_connections(hostaddr)
2850 	SOCKADDR *hostaddr;
2851 {
2852 	int i, n;
2853 
2854 	if (hostaddr == NULL)
2855 		return 0;
2856 	n = 0;
2857 	for (i = 0; i < ProcListSize; i++)
2858 	{
2859 		if (ProcListVec[i].proc_pid == NO_PID)
2860 			continue;
2861 
2862 		if (hostaddr->sa.sa_family !=
2863 		    ProcListVec[i].proc_hostaddr.sa.sa_family)
2864 			continue;
2865 #if NETINET
2866 		if (hostaddr->sa.sa_family == AF_INET &&
2867 		    (hostaddr->sin.sin_addr.s_addr ==
2868 		     ProcListVec[i].proc_hostaddr.sin.sin_addr.s_addr))
2869 			n++;
2870 #endif /* NETINET */
2871 #if NETINET6
2872 		if (hostaddr->sa.sa_family == AF_INET6 &&
2873 		    IN6_ARE_ADDR_EQUAL(&(hostaddr->sin6.sin6_addr),
2874 				       &(ProcListVec[i].proc_hostaddr.sin6.sin6_addr)))
2875 			n++;
2876 #endif /* NETINET6 */
2877 	}
2878 	return n;
2879 }
2880