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