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