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