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