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