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