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