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