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