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