util.c revision 94334
1/*
2 * Copyright (c) 1998-2002 Sendmail, Inc. and its suppliers.
3 *	All rights reserved.
4 * Copyright (c) 1983, 1995-1997 Eric P. Allman.  All rights reserved.
5 * Copyright (c) 1988, 1993
6 *	The Regents of the University of California.  All rights reserved.
7 *
8 * By using this file, you agree to the terms and conditions set
9 * forth in the LICENSE file which can be found at the top level of
10 * the sendmail distribution.
11 *
12 */
13
14#include <sendmail.h>
15
16SM_RCSID("@(#)$Id: util.c,v 8.360 2002/04/04 21:32:15 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
34char *
35addquotes(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
84bool
85rfc822_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
146bool
147shorten_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
192increment:
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
252char *
253find_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
317int
318check_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
344void
345truncate_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
391char *
392#if SM_HEAP_CHECK
393xalloc_tagged(sz, file, line)
394	register int sz;
395	char *file;
396	int line;
397#else /* SM_HEAP_CHECK */
398xalloc(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
436char **
437copyplist(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
475ADDRESS *
476copyqueue(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
513void
514log_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
567void
568set_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
593void
594set_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
618void
619printav(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
645void
646xputs(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
823void
824makelower(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
854void
855fixcrlf(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
887void
888putline(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**		    PXLF_NOADDEOL -- don't add an EOL if one wasn't present.
909**
910**	Returns:
911**		none
912**
913**	Side Effects:
914**		output of l to mci->mci_out.
915*/
916
917void
918putxline(l, len, mci, pxflags)
919	register char *l;
920	size_t len;
921	register MCI *mci;
922	int pxflags;
923{
924	bool dead = false;
925	register char *p, *end;
926	int slop = 0;
927
928	/* strip out 0200 bits -- these can look like TELNET protocol */
929	if (bitset(MCIF_7BIT, mci->mci_flags) ||
930	    bitset(PXLF_STRIP8BIT, pxflags))
931	{
932		register char svchar;
933
934		for (p = l; (svchar = *p) != '\0'; ++p)
935			if (bitset(0200, svchar))
936				*p = svchar &~ 0200;
937	}
938
939	end = l + len;
940	do
941	{
942		bool noeol = false;
943
944		/* find the end of the line */
945		p = memchr(l, '\n', end - l);
946		if (p == NULL)
947		{
948			p = end;
949			noeol = true;
950		}
951
952		if (TrafficLogFile != NULL)
953			(void) sm_io_fprintf(TrafficLogFile, SM_TIME_DEFAULT,
954					     "%05d >>> ", (int) CurrentPid);
955
956		/* check for line overflow */
957		while (mci->mci_mailer->m_linelimit > 0 &&
958		       (p - l + slop) > mci->mci_mailer->m_linelimit)
959		{
960			char *l_base = l;
961			register char *q = &l[mci->mci_mailer->m_linelimit - slop - 1];
962
963			if (l[0] == '.' && slop == 0 &&
964			    bitnset(M_XDOT, mci->mci_mailer->m_flags))
965			{
966				if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT,
967					       '.') == SM_IO_EOF)
968					dead = true;
969				else
970				{
971					/* record progress for DATA timeout */
972					DataProgress = true;
973				}
974				if (TrafficLogFile != NULL)
975					(void) sm_io_putc(TrafficLogFile,
976							  SM_TIME_DEFAULT, '.');
977			}
978			else if (l[0] == 'F' && slop == 0 &&
979				 bitset(PXLF_MAPFROM, pxflags) &&
980				 strncmp(l, "From ", 5) == 0 &&
981				 bitnset(M_ESCFROM, mci->mci_mailer->m_flags))
982			{
983				if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT,
984					       '>') == SM_IO_EOF)
985					dead = true;
986				else
987				{
988					/* record progress for DATA timeout */
989					DataProgress = true;
990				}
991				if (TrafficLogFile != NULL)
992					(void) sm_io_putc(TrafficLogFile,
993							  SM_TIME_DEFAULT,
994							  '>');
995			}
996			if (dead)
997				break;
998
999			while (l < q)
1000			{
1001				if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT,
1002					       (unsigned char) *l++) == SM_IO_EOF)
1003				{
1004					dead = true;
1005					break;
1006				}
1007				else
1008				{
1009					/* record progress for DATA timeout */
1010					DataProgress = true;
1011				}
1012			}
1013			if (dead)
1014				break;
1015
1016			if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT, '!') ==
1017			    SM_IO_EOF ||
1018			    sm_io_fputs(mci->mci_out, SM_TIME_DEFAULT,
1019					mci->mci_mailer->m_eol) ==
1020			    SM_IO_EOF ||
1021			    sm_io_putc(mci->mci_out, SM_TIME_DEFAULT, ' ') ==
1022			    SM_IO_EOF)
1023			{
1024				dead = true;
1025				break;
1026			}
1027			else
1028			{
1029				/* record progress for DATA timeout */
1030				DataProgress = true;
1031			}
1032			if (TrafficLogFile != NULL)
1033			{
1034				for (l = l_base; l < q; l++)
1035					(void) sm_io_putc(TrafficLogFile,
1036							  SM_TIME_DEFAULT,
1037							  (unsigned char)*l);
1038				(void) sm_io_fprintf(TrafficLogFile,
1039						     SM_TIME_DEFAULT,
1040						     "!\n%05d >>>  ",
1041						     (int) CurrentPid);
1042			}
1043			slop = 1;
1044		}
1045
1046		if (dead)
1047			break;
1048
1049		/* output last part */
1050		if (l[0] == '.' && slop == 0 &&
1051		    bitnset(M_XDOT, mci->mci_mailer->m_flags))
1052		{
1053			if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT, '.') ==
1054			    SM_IO_EOF)
1055				break;
1056			else
1057			{
1058				/* record progress for DATA timeout */
1059				DataProgress = true;
1060			}
1061			if (TrafficLogFile != NULL)
1062				(void) sm_io_putc(TrafficLogFile,
1063						  SM_TIME_DEFAULT, '.');
1064		}
1065		else if (l[0] == 'F' && slop == 0 &&
1066			 bitset(PXLF_MAPFROM, pxflags) &&
1067			 strncmp(l, "From ", 5) == 0 &&
1068			 bitnset(M_ESCFROM, mci->mci_mailer->m_flags))
1069		{
1070			if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT, '>') ==
1071			    SM_IO_EOF)
1072				break;
1073			else
1074			{
1075				/* record progress for DATA timeout */
1076				DataProgress = true;
1077			}
1078			if (TrafficLogFile != NULL)
1079				(void) sm_io_putc(TrafficLogFile,
1080						  SM_TIME_DEFAULT, '>');
1081		}
1082		for ( ; l < p; ++l)
1083		{
1084			if (TrafficLogFile != NULL)
1085				(void) sm_io_putc(TrafficLogFile,
1086						  SM_TIME_DEFAULT,
1087						  (unsigned char)*l);
1088			if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT,
1089				       (unsigned char) *l) == SM_IO_EOF)
1090			{
1091				dead = true;
1092				break;
1093			}
1094			else
1095			{
1096				/* record progress for DATA timeout */
1097				DataProgress = true;
1098			}
1099		}
1100		if (dead)
1101			break;
1102
1103		if (TrafficLogFile != NULL)
1104			(void) sm_io_putc(TrafficLogFile, SM_TIME_DEFAULT,
1105					  '\n');
1106		if ((!bitset(PXLF_NOADDEOL, pxflags) || !noeol) &&
1107		    sm_io_fputs(mci->mci_out, SM_TIME_DEFAULT,
1108				mci->mci_mailer->m_eol) == SM_IO_EOF)
1109			break;
1110		else
1111		{
1112			/* record progress for DATA timeout */
1113			DataProgress = true;
1114		}
1115		if (l < end && *l == '\n')
1116		{
1117			if (*++l != ' ' && *l != '\t' && *l != '\0' &&
1118			    bitset(PXLF_HEADER, pxflags))
1119			{
1120				if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT,
1121					       ' ') == SM_IO_EOF)
1122					break;
1123				else
1124				{
1125					/* record progress for DATA timeout */
1126					DataProgress = true;
1127				}
1128
1129				if (TrafficLogFile != NULL)
1130					(void) sm_io_putc(TrafficLogFile,
1131							  SM_TIME_DEFAULT, ' ');
1132			}
1133		}
1134
1135		/* record progress for DATA timeout */
1136		DataProgress = true;
1137	} while (l < end);
1138}
1139/*
1140**  XUNLINK -- unlink a file, doing logging as appropriate.
1141**
1142**	Parameters:
1143**		f -- name of file to unlink.
1144**
1145**	Returns:
1146**		return value of unlink()
1147**
1148**	Side Effects:
1149**		f is unlinked.
1150*/
1151
1152int
1153xunlink(f)
1154	char *f;
1155{
1156	register int i;
1157	int save_errno;
1158
1159	if (LogLevel > 98)
1160		sm_syslog(LOG_DEBUG, CurEnv->e_id, "unlink %s", f);
1161
1162	i = unlink(f);
1163	save_errno = errno;
1164	if (i < 0 && LogLevel > 97)
1165		sm_syslog(LOG_DEBUG, CurEnv->e_id, "%s: unlink-fail %d",
1166			  f, errno);
1167	if (i >= 0)
1168		SYNC_DIR(f, false);
1169	errno = save_errno;
1170	return i;
1171}
1172/*
1173**  SFGETS -- "safe" fgets -- times out and ignores random interrupts.
1174**
1175**	Parameters:
1176**		buf -- place to put the input line.
1177**		siz -- size of buf.
1178**		fp -- file to read from.
1179**		timeout -- the timeout before error occurs.
1180**		during -- what we are trying to read (for error messages).
1181**
1182**	Returns:
1183**		NULL on error (including timeout).  This may also leave
1184**			buf containing a null string.
1185**		buf otherwise.
1186*/
1187
1188
1189char *
1190sfgets(buf, siz, fp, timeout, during)
1191	char *buf;
1192	int siz;
1193	SM_FILE_T *fp;
1194	time_t timeout;
1195	char *during;
1196{
1197	register char *p;
1198	int save_errno;
1199	int io_timeout;
1200
1201	SM_REQUIRE(siz > 0);
1202	SM_REQUIRE(buf != NULL);
1203
1204	if (fp == NULL)
1205	{
1206		buf[0] = '\0';
1207		errno = EBADF;
1208		return NULL;
1209	}
1210
1211	/* try to read */
1212	p = NULL;
1213	errno = 0;
1214
1215	/* convert the timeout to sm_io notation */
1216	io_timeout = (timeout <= 0) ? SM_TIME_DEFAULT : timeout * 1000;
1217	while (!sm_io_eof(fp) && !sm_io_error(fp))
1218	{
1219		errno = 0;
1220		p = sm_io_fgets(fp, io_timeout, buf, siz);
1221		if (p == NULL && errno == EAGAIN)
1222		{
1223			/* The sm_io_fgets() call timedout */
1224			if (LogLevel > 1)
1225				sm_syslog(LOG_NOTICE, CurEnv->e_id,
1226					  "timeout waiting for input from %.100s during %s",
1227					  CURHOSTNAME,
1228					  during);
1229			buf[0] = '\0';
1230#if XDEBUG
1231			checkfd012(during);
1232#endif /* XDEBUG */
1233			if (TrafficLogFile != NULL)
1234				(void) sm_io_fprintf(TrafficLogFile,
1235						     SM_TIME_DEFAULT,
1236						     "%05d <<< [TIMEOUT]\n",
1237						     (int) CurrentPid);
1238			errno = ETIMEDOUT;
1239			return NULL;
1240		}
1241		if (p != NULL || errno != EINTR)
1242			break;
1243		(void) sm_io_clearerr(fp);
1244	}
1245	save_errno = errno;
1246
1247	/* clean up the books and exit */
1248	LineNumber++;
1249	if (p == NULL)
1250	{
1251		buf[0] = '\0';
1252		if (TrafficLogFile != NULL)
1253			(void) sm_io_fprintf(TrafficLogFile, SM_TIME_DEFAULT,
1254					     "%05d <<< [EOF]\n",
1255					     (int) CurrentPid);
1256		errno = save_errno;
1257		return NULL;
1258	}
1259	if (TrafficLogFile != NULL)
1260		(void) sm_io_fprintf(TrafficLogFile, SM_TIME_DEFAULT,
1261				     "%05d <<< %s", (int) CurrentPid, buf);
1262	if (SevenBitInput)
1263	{
1264		for (p = buf; *p != '\0'; p++)
1265			*p &= ~0200;
1266	}
1267	else if (!HasEightBits)
1268	{
1269		for (p = buf; *p != '\0'; p++)
1270		{
1271			if (bitset(0200, *p))
1272			{
1273				HasEightBits = true;
1274				break;
1275			}
1276		}
1277	}
1278	return buf;
1279}
1280/*
1281**  FGETFOLDED -- like fgets, but knows about folded lines.
1282**
1283**	Parameters:
1284**		buf -- place to put result.
1285**		n -- bytes available.
1286**		f -- file to read from.
1287**
1288**	Returns:
1289**		input line(s) on success, NULL on error or SM_IO_EOF.
1290**		This will normally be buf -- unless the line is too
1291**			long, when it will be sm_malloc_x()ed.
1292**
1293**	Side Effects:
1294**		buf gets lines from f, with continuation lines (lines
1295**		with leading white space) appended.  CRLF's are mapped
1296**		into single newlines.  Any trailing NL is stripped.
1297*/
1298
1299char *
1300fgetfolded(buf, n, f)
1301	char *buf;
1302	register int n;
1303	SM_FILE_T *f;
1304{
1305	register char *p = buf;
1306	char *bp = buf;
1307	register int i;
1308
1309	SM_REQUIRE(n > 0);
1310	SM_REQUIRE(buf != NULL);
1311	if (f == NULL)
1312	{
1313		buf[0] = '\0';
1314		errno = EBADF;
1315		return NULL;
1316	}
1317
1318	n--;
1319	while ((i = sm_io_getc(f, SM_TIME_DEFAULT)) != SM_IO_EOF)
1320	{
1321		if (i == '\r')
1322		{
1323			i = sm_io_getc(f, SM_TIME_DEFAULT);
1324			if (i != '\n')
1325			{
1326				if (i != SM_IO_EOF)
1327					(void) sm_io_ungetc(f, SM_TIME_DEFAULT,
1328							    i);
1329				i = '\r';
1330			}
1331		}
1332		if (--n <= 0)
1333		{
1334			/* allocate new space */
1335			char *nbp;
1336			int nn;
1337
1338			nn = (p - bp);
1339			if (nn < MEMCHUNKSIZE)
1340				nn *= 2;
1341			else
1342				nn += MEMCHUNKSIZE;
1343			nbp = sm_malloc_x(nn);
1344			memmove(nbp, bp, p - bp);
1345			p = &nbp[p - bp];
1346			if (bp != buf)
1347				sm_free(bp);
1348			bp = nbp;
1349			n = nn - (p - bp);
1350		}
1351		*p++ = i;
1352		if (i == '\n')
1353		{
1354			LineNumber++;
1355			i = sm_io_getc(f, SM_TIME_DEFAULT);
1356			if (i != SM_IO_EOF)
1357				(void) sm_io_ungetc(f, SM_TIME_DEFAULT, i);
1358			if (i != ' ' && i != '\t')
1359				break;
1360		}
1361	}
1362	if (p == bp)
1363		return NULL;
1364	if (p[-1] == '\n')
1365		p--;
1366	*p = '\0';
1367	return bp;
1368}
1369/*
1370**  CURTIME -- return current time.
1371**
1372**	Parameters:
1373**		none.
1374**
1375**	Returns:
1376**		the current time.
1377*/
1378
1379time_t
1380curtime()
1381{
1382	auto time_t t;
1383
1384	(void) time(&t);
1385	return t;
1386}
1387/*
1388**  ATOBOOL -- convert a string representation to boolean.
1389**
1390**	Defaults to false
1391**
1392**	Parameters:
1393**		s -- string to convert.  Takes "tTyY", empty, and NULL as true,
1394**			others as false.
1395**
1396**	Returns:
1397**		A boolean representation of the string.
1398*/
1399
1400bool
1401atobool(s)
1402	register char *s;
1403{
1404	if (s == NULL || *s == '\0' || strchr("tTyY", *s) != NULL)
1405		return true;
1406	return false;
1407}
1408/*
1409**  ATOOCT -- convert a string representation to octal.
1410**
1411**	Parameters:
1412**		s -- string to convert.
1413**
1414**	Returns:
1415**		An integer representing the string interpreted as an
1416**		octal number.
1417*/
1418
1419int
1420atooct(s)
1421	register char *s;
1422{
1423	register int i = 0;
1424
1425	while (*s >= '0' && *s <= '7')
1426		i = (i << 3) | (*s++ - '0');
1427	return i;
1428}
1429/*
1430**  BITINTERSECT -- tell if two bitmaps intersect
1431**
1432**	Parameters:
1433**		a, b -- the bitmaps in question
1434**
1435**	Returns:
1436**		true if they have a non-null intersection
1437**		false otherwise
1438*/
1439
1440bool
1441bitintersect(a, b)
1442	BITMAP256 a;
1443	BITMAP256 b;
1444{
1445	int i;
1446
1447	for (i = BITMAPBYTES / sizeof (int); --i >= 0; )
1448	{
1449		if ((a[i] & b[i]) != 0)
1450			return true;
1451	}
1452	return false;
1453}
1454/*
1455**  BITZEROP -- tell if a bitmap is all zero
1456**
1457**	Parameters:
1458**		map -- the bit map to check
1459**
1460**	Returns:
1461**		true if map is all zero.
1462**		false if there are any bits set in map.
1463*/
1464
1465bool
1466bitzerop(map)
1467	BITMAP256 map;
1468{
1469	int i;
1470
1471	for (i = BITMAPBYTES / sizeof (int); --i >= 0; )
1472	{
1473		if (map[i] != 0)
1474			return false;
1475	}
1476	return true;
1477}
1478/*
1479**  STRCONTAINEDIN -- tell if one string is contained in another
1480**
1481**	Parameters:
1482**		icase -- ignore case?
1483**		a -- possible substring.
1484**		b -- possible superstring.
1485**
1486**	Returns:
1487**		true if a is contained in b (case insensitive).
1488**		false otherwise.
1489*/
1490
1491bool
1492strcontainedin(icase, a, b)
1493	bool icase;
1494	register char *a;
1495	register char *b;
1496{
1497	int la;
1498	int lb;
1499	int c;
1500
1501	la = strlen(a);
1502	lb = strlen(b);
1503	c = *a;
1504	if (icase && isascii(c) && isupper(c))
1505		c = tolower(c);
1506	for (; lb-- >= la; b++)
1507	{
1508		if (icase)
1509		{
1510			if (*b != c &&
1511			    isascii(*b) && isupper(*b) && tolower(*b) != c)
1512				continue;
1513			if (sm_strncasecmp(a, b, la) == 0)
1514				return true;
1515		}
1516		else
1517		{
1518			if (*b != c)
1519				continue;
1520			if (strncmp(a, b, la) == 0)
1521				return true;
1522		}
1523	}
1524	return false;
1525}
1526/*
1527**  CHECKFD012 -- check low numbered file descriptors
1528**
1529**	File descriptors 0, 1, and 2 should be open at all times.
1530**	This routine verifies that, and fixes it if not true.
1531**
1532**	Parameters:
1533**		where -- a tag printed if the assertion failed
1534**
1535**	Returns:
1536**		none
1537*/
1538
1539void
1540checkfd012(where)
1541	char *where;
1542{
1543#if XDEBUG
1544	register int i;
1545
1546	for (i = 0; i < 3; i++)
1547		fill_fd(i, where);
1548#endif /* XDEBUG */
1549}
1550/*
1551**  CHECKFDOPEN -- make sure file descriptor is open -- for extended debugging
1552**
1553**	Parameters:
1554**		fd -- file descriptor to check.
1555**		where -- tag to print on failure.
1556**
1557**	Returns:
1558**		none.
1559*/
1560
1561void
1562checkfdopen(fd, where)
1563	int fd;
1564	char *where;
1565{
1566#if XDEBUG
1567	struct stat st;
1568
1569	if (fstat(fd, &st) < 0 && errno == EBADF)
1570	{
1571		syserr("checkfdopen(%d): %s not open as expected!", fd, where);
1572		printopenfds(true);
1573	}
1574#endif /* XDEBUG */
1575}
1576/*
1577**  CHECKFDS -- check for new or missing file descriptors
1578**
1579**	Parameters:
1580**		where -- tag for printing.  If null, take a base line.
1581**
1582**	Returns:
1583**		none
1584**
1585**	Side Effects:
1586**		If where is set, shows changes since the last call.
1587*/
1588
1589void
1590checkfds(where)
1591	char *where;
1592{
1593	int maxfd;
1594	register int fd;
1595	bool printhdr = true;
1596	int save_errno = errno;
1597	static BITMAP256 baseline;
1598	extern int DtableSize;
1599
1600	if (DtableSize > BITMAPBITS)
1601		maxfd = BITMAPBITS;
1602	else
1603		maxfd = DtableSize;
1604	if (where == NULL)
1605		clrbitmap(baseline);
1606
1607	for (fd = 0; fd < maxfd; fd++)
1608	{
1609		struct stat stbuf;
1610
1611		if (fstat(fd, &stbuf) < 0 && errno != EOPNOTSUPP)
1612		{
1613			if (!bitnset(fd, baseline))
1614				continue;
1615			clrbitn(fd, baseline);
1616		}
1617		else if (!bitnset(fd, baseline))
1618			setbitn(fd, baseline);
1619		else
1620			continue;
1621
1622		/* file state has changed */
1623		if (where == NULL)
1624			continue;
1625		if (printhdr)
1626		{
1627			sm_syslog(LOG_DEBUG, CurEnv->e_id,
1628				  "%s: changed fds:",
1629				  where);
1630			printhdr = false;
1631		}
1632		dumpfd(fd, true, true);
1633	}
1634	errno = save_errno;
1635}
1636/*
1637**  PRINTOPENFDS -- print the open file descriptors (for debugging)
1638**
1639**	Parameters:
1640**		logit -- if set, send output to syslog; otherwise
1641**			print for debugging.
1642**
1643**	Returns:
1644**		none.
1645*/
1646
1647#if NETINET || NETINET6
1648# include <arpa/inet.h>
1649#endif /* NETINET || NETINET6 */
1650
1651void
1652printopenfds(logit)
1653	bool logit;
1654{
1655	register int fd;
1656	extern int DtableSize;
1657
1658	for (fd = 0; fd < DtableSize; fd++)
1659		dumpfd(fd, false, logit);
1660}
1661/*
1662**  DUMPFD -- dump a file descriptor
1663**
1664**	Parameters:
1665**		fd -- the file descriptor to dump.
1666**		printclosed -- if set, print a notification even if
1667**			it is closed; otherwise print nothing.
1668**		logit -- if set, send output to syslog instead of stdout.
1669**
1670**	Returns:
1671**		none.
1672*/
1673
1674void
1675dumpfd(fd, printclosed, logit)
1676	int fd;
1677	bool printclosed;
1678	bool logit;
1679{
1680	register char *p;
1681	char *hp;
1682#ifdef S_IFSOCK
1683	SOCKADDR sa;
1684#endif /* S_IFSOCK */
1685	auto SOCKADDR_LEN_T slen;
1686	int i;
1687#if STAT64 > 0
1688	struct stat64 st;
1689#else /* STAT64 > 0 */
1690	struct stat st;
1691#endif /* STAT64 > 0 */
1692	char buf[200];
1693
1694	p = buf;
1695	(void) sm_snprintf(p, SPACELEFT(buf, p), "%3d: ", fd);
1696	p += strlen(p);
1697
1698	if (
1699#if STAT64 > 0
1700	    fstat64(fd, &st)
1701#else /* STAT64 > 0 */
1702	    fstat(fd, &st)
1703#endif /* STAT64 > 0 */
1704	    < 0)
1705	{
1706		if (errno != EBADF)
1707		{
1708			(void) sm_snprintf(p, SPACELEFT(buf, p),
1709				"CANNOT STAT (%s)",
1710				sm_errstring(errno));
1711			goto printit;
1712		}
1713		else if (printclosed)
1714		{
1715			(void) sm_snprintf(p, SPACELEFT(buf, p), "CLOSED");
1716			goto printit;
1717		}
1718		return;
1719	}
1720
1721	i = fcntl(fd, F_GETFL, 0);
1722	if (i != -1)
1723	{
1724		(void) sm_snprintf(p, SPACELEFT(buf, p), "fl=0x%x, ", i);
1725		p += strlen(p);
1726	}
1727
1728	(void) sm_snprintf(p, SPACELEFT(buf, p), "mode=%o: ",
1729			(int) st.st_mode);
1730	p += strlen(p);
1731	switch (st.st_mode & S_IFMT)
1732	{
1733#ifdef S_IFSOCK
1734	  case S_IFSOCK:
1735		(void) sm_snprintf(p, SPACELEFT(buf, p), "SOCK ");
1736		p += strlen(p);
1737		memset(&sa, '\0', sizeof sa);
1738		slen = sizeof sa;
1739		if (getsockname(fd, &sa.sa, &slen) < 0)
1740			(void) sm_snprintf(p, SPACELEFT(buf, p), "(%s)",
1741				 sm_errstring(errno));
1742		else
1743		{
1744			hp = hostnamebyanyaddr(&sa);
1745			if (hp == NULL)
1746			{
1747				/* EMPTY */
1748				/* do nothing */
1749			}
1750# if NETINET
1751			else if (sa.sa.sa_family == AF_INET)
1752				(void) sm_snprintf(p, SPACELEFT(buf, p),
1753					"%s/%d", hp, ntohs(sa.sin.sin_port));
1754# endif /* NETINET */
1755# if NETINET6
1756			else if (sa.sa.sa_family == AF_INET6)
1757				(void) sm_snprintf(p, SPACELEFT(buf, p),
1758					"%s/%d", hp, ntohs(sa.sin6.sin6_port));
1759# endif /* NETINET6 */
1760			else
1761				(void) sm_snprintf(p, SPACELEFT(buf, p),
1762					"%s", hp);
1763		}
1764		p += strlen(p);
1765		(void) sm_snprintf(p, SPACELEFT(buf, p), "->");
1766		p += strlen(p);
1767		slen = sizeof sa;
1768		if (getpeername(fd, &sa.sa, &slen) < 0)
1769			(void) sm_snprintf(p, SPACELEFT(buf, p), "(%s)",
1770					sm_errstring(errno));
1771		else
1772		{
1773			hp = hostnamebyanyaddr(&sa);
1774			if (hp == NULL)
1775			{
1776				/* EMPTY */
1777				/* do nothing */
1778			}
1779# if NETINET
1780			else if (sa.sa.sa_family == AF_INET)
1781				(void) sm_snprintf(p, SPACELEFT(buf, p),
1782					"%s/%d", hp, ntohs(sa.sin.sin_port));
1783# endif /* NETINET */
1784# if NETINET6
1785			else if (sa.sa.sa_family == AF_INET6)
1786				(void) sm_snprintf(p, SPACELEFT(buf, p),
1787					"%s/%d", hp, ntohs(sa.sin6.sin6_port));
1788# endif /* NETINET6 */
1789			else
1790				(void) sm_snprintf(p, SPACELEFT(buf, p),
1791					"%s", hp);
1792		}
1793		break;
1794#endif /* S_IFSOCK */
1795
1796	  case S_IFCHR:
1797		(void) sm_snprintf(p, SPACELEFT(buf, p), "CHR: ");
1798		p += strlen(p);
1799		goto defprint;
1800
1801#ifdef S_IFBLK
1802	  case S_IFBLK:
1803		(void) sm_snprintf(p, SPACELEFT(buf, p), "BLK: ");
1804		p += strlen(p);
1805		goto defprint;
1806#endif /* S_IFBLK */
1807
1808#if defined(S_IFIFO) && (!defined(S_IFSOCK) || S_IFIFO != S_IFSOCK)
1809	  case S_IFIFO:
1810		(void) sm_snprintf(p, SPACELEFT(buf, p), "FIFO: ");
1811		p += strlen(p);
1812		goto defprint;
1813#endif /* defined(S_IFIFO) && (!defined(S_IFSOCK) || S_IFIFO != S_IFSOCK) */
1814
1815#ifdef S_IFDIR
1816	  case S_IFDIR:
1817		(void) sm_snprintf(p, SPACELEFT(buf, p), "DIR: ");
1818		p += strlen(p);
1819		goto defprint;
1820#endif /* S_IFDIR */
1821
1822#ifdef S_IFLNK
1823	  case S_IFLNK:
1824		(void) sm_snprintf(p, SPACELEFT(buf, p), "LNK: ");
1825		p += strlen(p);
1826		goto defprint;
1827#endif /* S_IFLNK */
1828
1829	  default:
1830defprint:
1831		(void) sm_snprintf(p, SPACELEFT(buf, p),
1832			 "dev=%d/%d, ino=%llu, nlink=%d, u/gid=%d/%d, ",
1833			 major(st.st_dev), minor(st.st_dev),
1834			 (ULONGLONG_T) st.st_ino,
1835			 (int) st.st_nlink, (int) st.st_uid,
1836			 (int) st.st_gid);
1837		p += strlen(p);
1838		(void) sm_snprintf(p, SPACELEFT(buf, p), "size=%llu",
1839			 (ULONGLONG_T) st.st_size);
1840		break;
1841	}
1842
1843printit:
1844	if (logit)
1845		sm_syslog(LOG_DEBUG, CurEnv ? CurEnv->e_id : NULL,
1846			  "%.800s", buf);
1847	else
1848		(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%s\n", buf);
1849}
1850/*
1851**  SHORTEN_HOSTNAME -- strip local domain information off of hostname.
1852**
1853**	Parameters:
1854**		host -- the host to shorten (stripped in place).
1855**
1856**	Returns:
1857**		place where string was truncated, NULL if not truncated.
1858*/
1859
1860char *
1861shorten_hostname(host)
1862	char host[];
1863{
1864	register char *p;
1865	char *mydom;
1866	int i;
1867	bool canon = false;
1868
1869	/* strip off final dot */
1870	i = strlen(host);
1871	p = &host[(i == 0) ? 0 : i - 1];
1872	if (*p == '.')
1873	{
1874		*p = '\0';
1875		canon = true;
1876	}
1877
1878	/* see if there is any domain at all -- if not, we are done */
1879	p = strchr(host, '.');
1880	if (p == NULL)
1881		return NULL;
1882
1883	/* yes, we have a domain -- see if it looks like us */
1884	mydom = macvalue('m', CurEnv);
1885	if (mydom == NULL)
1886		mydom = "";
1887	i = strlen(++p);
1888	if ((canon ? sm_strcasecmp(p, mydom)
1889		   : sm_strncasecmp(p, mydom, i)) == 0 &&
1890			(mydom[i] == '.' || mydom[i] == '\0'))
1891	{
1892		*--p = '\0';
1893		return p;
1894	}
1895	return NULL;
1896}
1897/*
1898**  PROG_OPEN -- open a program for reading
1899**
1900**	Parameters:
1901**		argv -- the argument list.
1902**		pfd -- pointer to a place to store the file descriptor.
1903**		e -- the current envelope.
1904**
1905**	Returns:
1906**		pid of the process -- -1 if it failed.
1907*/
1908
1909pid_t
1910prog_open(argv, pfd, e)
1911	char **argv;
1912	int *pfd;
1913	ENVELOPE *e;
1914{
1915	pid_t pid;
1916	int i;
1917	int save_errno;
1918	int sff;
1919	int ret;
1920	int fdv[2];
1921	char *p, *q;
1922	char buf[MAXLINE + 1];
1923	extern int DtableSize;
1924
1925	if (pipe(fdv) < 0)
1926	{
1927		syserr("%s: cannot create pipe for stdout", argv[0]);
1928		return -1;
1929	}
1930	pid = fork();
1931	if (pid < 0)
1932	{
1933		syserr("%s: cannot fork", argv[0]);
1934		(void) close(fdv[0]);
1935		(void) close(fdv[1]);
1936		return -1;
1937	}
1938	if (pid > 0)
1939	{
1940		/* parent */
1941		(void) close(fdv[1]);
1942		*pfd = fdv[0];
1943		return pid;
1944	}
1945
1946	/* Reset global flags */
1947	RestartRequest = NULL;
1948	RestartWorkGroup = false;
1949	ShutdownRequest = NULL;
1950	PendingSignal = 0;
1951	CurrentPid = getpid();
1952
1953	/*
1954	**  Initialize exception stack and default exception
1955	**  handler for child process.
1956	*/
1957
1958	sm_exc_newthread(fatal_error);
1959
1960	/* child -- close stdin */
1961	(void) close(0);
1962
1963	/* stdout goes back to parent */
1964	(void) close(fdv[0]);
1965	if (dup2(fdv[1], 1) < 0)
1966	{
1967		syserr("%s: cannot dup2 for stdout", argv[0]);
1968		_exit(EX_OSERR);
1969	}
1970	(void) close(fdv[1]);
1971
1972	/* stderr goes to transcript if available */
1973	if (e->e_xfp != NULL)
1974	{
1975		int xfd;
1976
1977		xfd = sm_io_getinfo(e->e_xfp, SM_IO_WHAT_FD, NULL);
1978		if (xfd >= 0 && dup2(xfd, 2) < 0)
1979		{
1980			syserr("%s: cannot dup2 for stderr", argv[0]);
1981			_exit(EX_OSERR);
1982		}
1983	}
1984
1985	/* this process has no right to the queue file */
1986	if (e->e_lockfp != NULL)
1987		(void) close(sm_io_getinfo(e->e_lockfp, SM_IO_WHAT_FD, NULL));
1988
1989	/* chroot to the program mailer directory, if defined */
1990	if (ProgMailer != NULL && ProgMailer->m_rootdir != NULL)
1991	{
1992		expand(ProgMailer->m_rootdir, buf, sizeof buf, e);
1993		if (chroot(buf) < 0)
1994		{
1995			syserr("prog_open: cannot chroot(%s)", buf);
1996			exit(EX_TEMPFAIL);
1997		}
1998		if (chdir("/") < 0)
1999		{
2000			syserr("prog_open: cannot chdir(/)");
2001			exit(EX_TEMPFAIL);
2002		}
2003	}
2004
2005	/* run as default user */
2006	endpwent();
2007	sm_mbdb_terminate();
2008	if (setgid(DefGid) < 0 && geteuid() == 0)
2009	{
2010		syserr("prog_open: setgid(%ld) failed", (long) DefGid);
2011		exit(EX_TEMPFAIL);
2012	}
2013	if (setuid(DefUid) < 0 && geteuid() == 0)
2014	{
2015		syserr("prog_open: setuid(%ld) failed", (long) DefUid);
2016		exit(EX_TEMPFAIL);
2017	}
2018
2019	/* run in some directory */
2020	if (ProgMailer != NULL)
2021		p = ProgMailer->m_execdir;
2022	else
2023		p = NULL;
2024	for (; p != NULL; p = q)
2025	{
2026		q = strchr(p, ':');
2027		if (q != NULL)
2028			*q = '\0';
2029		expand(p, buf, sizeof buf, e);
2030		if (q != NULL)
2031			*q++ = ':';
2032		if (buf[0] != '\0' && chdir(buf) >= 0)
2033			break;
2034	}
2035	if (p == NULL)
2036	{
2037		/* backup directories */
2038		if (chdir("/tmp") < 0)
2039			(void) chdir("/");
2040	}
2041
2042	/* Check safety of program to be run */
2043	sff = SFF_ROOTOK|SFF_EXECOK;
2044	if (!bitnset(DBS_RUNWRITABLEPROGRAM, DontBlameSendmail))
2045		sff |= SFF_NOGWFILES|SFF_NOWWFILES;
2046	if (bitnset(DBS_RUNPROGRAMINUNSAFEDIRPATH, DontBlameSendmail))
2047		sff |= SFF_NOPATHCHECK;
2048	else
2049		sff |= SFF_SAFEDIRPATH;
2050	ret = safefile(argv[0], DefUid, DefGid, DefUser, sff, 0, NULL);
2051	if (ret != 0)
2052		sm_syslog(LOG_INFO, e->e_id,
2053			  "Warning: prog_open: program %s unsafe: %s",
2054			  argv[0], sm_errstring(ret));
2055
2056	/* arrange for all the files to be closed */
2057	for (i = 3; i < DtableSize; i++)
2058	{
2059		register int j;
2060
2061		if ((j = fcntl(i, F_GETFD, 0)) != -1)
2062			(void) fcntl(i, F_SETFD, j | FD_CLOEXEC);
2063	}
2064
2065	/* now exec the process */
2066	(void) execve(argv[0], (ARGV_T) argv, (ARGV_T) UserEnviron);
2067
2068	/* woops!  failed */
2069	save_errno = errno;
2070	syserr("%s: cannot exec", argv[0]);
2071	if (transienterror(save_errno))
2072		_exit(EX_OSERR);
2073	_exit(EX_CONFIG);
2074	return -1;	/* avoid compiler warning on IRIX */
2075}
2076/*
2077**  GET_COLUMN -- look up a Column in a line buffer
2078**
2079**	Parameters:
2080**		line -- the raw text line to search.
2081**		col -- the column number to fetch.
2082**		delim -- the delimiter between columns.  If null,
2083**			use white space.
2084**		buf -- the output buffer.
2085**		buflen -- the length of buf.
2086**
2087**	Returns:
2088**		buf if successful.
2089**		NULL otherwise.
2090*/
2091
2092char *
2093get_column(line, col, delim, buf, buflen)
2094	char line[];
2095	int col;
2096	int delim;
2097	char buf[];
2098	int buflen;
2099{
2100	char *p;
2101	char *begin, *end;
2102	int i;
2103	char delimbuf[4];
2104
2105	if ((char) delim == '\0')
2106		(void) sm_strlcpy(delimbuf, "\n\t ", sizeof delimbuf);
2107	else
2108	{
2109		delimbuf[0] = (char) delim;
2110		delimbuf[1] = '\0';
2111	}
2112
2113	p = line;
2114	if (*p == '\0')
2115		return NULL;			/* line empty */
2116	if (*p == (char) delim && col == 0)
2117		return NULL;			/* first column empty */
2118
2119	begin = line;
2120
2121	if (col == 0 && (char) delim == '\0')
2122	{
2123		while (*begin != '\0' && isascii(*begin) && isspace(*begin))
2124			begin++;
2125	}
2126
2127	for (i = 0; i < col; i++)
2128	{
2129		if ((begin = strpbrk(begin, delimbuf)) == NULL)
2130			return NULL;		/* no such column */
2131		begin++;
2132		if ((char) delim == '\0')
2133		{
2134			while (*begin != '\0' && isascii(*begin) && isspace(*begin))
2135				begin++;
2136		}
2137	}
2138
2139	end = strpbrk(begin, delimbuf);
2140	if (end == NULL)
2141		i = strlen(begin);
2142	else
2143		i = end - begin;
2144	if (i >= buflen)
2145		i = buflen - 1;
2146	(void) sm_strlcpy(buf, begin, i + 1);
2147	return buf;
2148}
2149/*
2150**  CLEANSTRCPY -- copy string keeping out bogus characters
2151**
2152**	Parameters:
2153**		t -- "to" string.
2154**		f -- "from" string.
2155**		l -- length of space available in "to" string.
2156**
2157**	Returns:
2158**		none.
2159*/
2160
2161void
2162cleanstrcpy(t, f, l)
2163	register char *t;
2164	register char *f;
2165	int l;
2166{
2167	/* check for newlines and log if necessary */
2168	(void) denlstring(f, true, true);
2169
2170	if (l <= 0)
2171		syserr("!cleanstrcpy: length == 0");
2172
2173	l--;
2174	while (l > 0 && *f != '\0')
2175	{
2176		if (isascii(*f) &&
2177		    (isalnum(*f) || strchr("!#$%&'*+-./^_`{|}~", *f) != NULL))
2178		{
2179			l--;
2180			*t++ = *f;
2181		}
2182		f++;
2183	}
2184	*t = '\0';
2185}
2186/*
2187**  DENLSTRING -- convert newlines in a string to spaces
2188**
2189**	Parameters:
2190**		s -- the input string
2191**		strict -- if set, don't permit continuation lines.
2192**		logattacks -- if set, log attempted attacks.
2193**
2194**	Returns:
2195**		A pointer to a version of the string with newlines
2196**		mapped to spaces.  This should be copied.
2197*/
2198
2199char *
2200denlstring(s, strict, logattacks)
2201	char *s;
2202	bool strict;
2203	bool logattacks;
2204{
2205	register char *p;
2206	int l;
2207	static char *bp = NULL;
2208	static int bl = 0;
2209
2210	p = s;
2211	while ((p = strchr(p, '\n')) != NULL)
2212		if (strict || (*++p != ' ' && *p != '\t'))
2213			break;
2214	if (p == NULL)
2215		return s;
2216
2217	l = strlen(s) + 1;
2218	if (bl < l)
2219	{
2220		/* allocate more space */
2221		char *nbp = sm_pmalloc_x(l);
2222
2223		if (bp != NULL)
2224			sm_free(bp);
2225		bp = nbp;
2226		bl = l;
2227	}
2228	(void) sm_strlcpy(bp, s, l);
2229	for (p = bp; (p = strchr(p, '\n')) != NULL; )
2230		*p++ = ' ';
2231
2232	if (logattacks)
2233	{
2234		sm_syslog(LOG_NOTICE, CurEnv->e_id,
2235			  "POSSIBLE ATTACK from %.100s: newline in string \"%s\"",
2236			  RealHostName == NULL ? "[UNKNOWN]" : RealHostName,
2237			  shortenstring(bp, MAXSHORTSTR));
2238	}
2239
2240	return bp;
2241}
2242/*
2243**  STR2PRT -- convert "unprintable" characters in a string to \oct
2244**
2245**	Parameters:
2246**		s -- string to convert
2247**
2248**	Returns:
2249**		converted string.
2250**		This is a static local buffer, string must be copied
2251**		before this function is called again!
2252*/
2253
2254char *
2255str2prt(s)
2256	char *s;
2257{
2258	int l;
2259	char c, *h;
2260	bool ok;
2261	static int len = 0;
2262	static char *buf = NULL;
2263
2264	if (s == NULL)
2265		return NULL;
2266	ok = true;
2267	for (h = s, l = 1; *h != '\0'; h++, l++)
2268	{
2269		if (*h == '\\')
2270		{
2271			++l;
2272			ok = false;
2273		}
2274		else if (!(isascii(*h) && isprint(*h)))
2275		{
2276			l += 3;
2277			ok = false;
2278		}
2279	}
2280	if (ok)
2281		return s;
2282	if (l > len)
2283	{
2284		char *nbuf = sm_pmalloc_x(l);
2285
2286		if (buf != NULL)
2287			sm_free(buf);
2288		len = l;
2289		buf = nbuf;
2290	}
2291	for (h = buf; *s != '\0' && l > 0; s++, l--)
2292	{
2293		c = *s;
2294		if (isascii(c) && isprint(c) && c != '\\')
2295		{
2296			*h++ = c;
2297		}
2298		else
2299		{
2300			*h++ = '\\';
2301			--l;
2302			switch (c)
2303			{
2304			  case '\\':
2305				*h++ = '\\';
2306				break;
2307			  case '\t':
2308				*h++ = 't';
2309				break;
2310			  case '\n':
2311				*h++ = 'n';
2312				break;
2313			  case '\r':
2314				*h++ = 'r';
2315				break;
2316			  default:
2317				(void) sm_snprintf(h, l, "%03o", (int) c);
2318
2319				/*
2320				**  XXX since l is unsigned this may
2321				**  wrap around if the calculation is screwed
2322				**  up...
2323				*/
2324
2325				l -= 2;
2326				h += 3;
2327				break;
2328			}
2329		}
2330	}
2331	*h = '\0';
2332	buf[len - 1] = '\0';
2333	return buf;
2334}
2335/*
2336**  PATH_IS_DIR -- check to see if file exists and is a directory.
2337**
2338**	There are some additional checks for security violations in
2339**	here.  This routine is intended to be used for the host status
2340**	support.
2341**
2342**	Parameters:
2343**		pathname -- pathname to check for directory-ness.
2344**		createflag -- if set, create directory if needed.
2345**
2346**	Returns:
2347**		true -- if the indicated pathname is a directory
2348**		false -- otherwise
2349*/
2350
2351int
2352path_is_dir(pathname, createflag)
2353	char *pathname;
2354	bool createflag;
2355{
2356	struct stat statbuf;
2357
2358#if HASLSTAT
2359	if (lstat(pathname, &statbuf) < 0)
2360#else /* HASLSTAT */
2361	if (stat(pathname, &statbuf) < 0)
2362#endif /* HASLSTAT */
2363	{
2364		if (errno != ENOENT || !createflag)
2365			return false;
2366		if (mkdir(pathname, 0755) < 0)
2367			return false;
2368		return true;
2369	}
2370	if (!S_ISDIR(statbuf.st_mode))
2371	{
2372		errno = ENOTDIR;
2373		return false;
2374	}
2375
2376	/* security: don't allow writable directories */
2377	if (bitset(S_IWGRP|S_IWOTH, statbuf.st_mode))
2378	{
2379		errno = EACCES;
2380		return false;
2381	}
2382	return true;
2383}
2384/*
2385**  PROC_LIST_ADD -- add process id to list of our children
2386**
2387**	Parameters:
2388**		pid -- pid to add to list.
2389**		task -- task of pid.
2390**		type -- type of process.
2391**		count -- number of processes.
2392**		other -- other information for this type.
2393**
2394**	Returns:
2395**		none
2396**
2397**	Side Effects:
2398**		May increase CurChildren. May grow ProcList.
2399*/
2400
2401typedef struct procs	PROCS_T;
2402
2403struct procs
2404{
2405	pid_t	proc_pid;
2406	char	*proc_task;
2407	int	proc_type;
2408	int	proc_count;
2409	int	proc_other;
2410};
2411
2412static PROCS_T	*volatile ProcListVec = NULL;
2413static int	ProcListSize = 0;
2414
2415void
2416proc_list_add(pid, task, type, count, other)
2417	pid_t pid;
2418	char *task;
2419	int type;
2420	int count;
2421	int other;
2422{
2423	int i;
2424
2425	for (i = 0; i < ProcListSize; i++)
2426	{
2427		if (ProcListVec[i].proc_pid == NO_PID)
2428			break;
2429	}
2430	if (i >= ProcListSize)
2431	{
2432		/* probe the existing vector to avoid growing infinitely */
2433		proc_list_probe();
2434
2435		/* now scan again */
2436		for (i = 0; i < ProcListSize; i++)
2437		{
2438			if (ProcListVec[i].proc_pid == NO_PID)
2439				break;
2440		}
2441	}
2442	if (i >= ProcListSize)
2443	{
2444		/* grow process list */
2445		PROCS_T *npv;
2446
2447		SM_ASSERT(ProcListSize < INT_MAX - PROC_LIST_SEG);
2448		npv = (PROCS_T *) sm_pmalloc_x((sizeof *npv) *
2449					       (ProcListSize + PROC_LIST_SEG));
2450		if (ProcListSize > 0)
2451		{
2452			memmove(npv, ProcListVec,
2453				ProcListSize * sizeof (PROCS_T));
2454			sm_free(ProcListVec);
2455		}
2456
2457		/* XXX just use memset() to initialize this part? */
2458		for (i = ProcListSize; i < ProcListSize + PROC_LIST_SEG; i++)
2459		{
2460			npv[i].proc_pid = NO_PID;
2461			npv[i].proc_task = NULL;
2462			npv[i].proc_type = PROC_NONE;
2463		}
2464		i = ProcListSize;
2465		ProcListSize += PROC_LIST_SEG;
2466		ProcListVec = npv;
2467	}
2468	ProcListVec[i].proc_pid = pid;
2469	PSTRSET(ProcListVec[i].proc_task, task);
2470	ProcListVec[i].proc_type = type;
2471	ProcListVec[i].proc_count = count;
2472	ProcListVec[i].proc_other = other;
2473
2474	/* if process adding itself, it's not a child */
2475	if (pid != CurrentPid)
2476	{
2477		SM_ASSERT(CurChildren < INT_MAX);
2478		CurChildren++;
2479	}
2480}
2481/*
2482**  PROC_LIST_SET -- set pid task in process list
2483**
2484**	Parameters:
2485**		pid -- pid to set
2486**		task -- task of pid
2487**
2488**	Returns:
2489**		none.
2490*/
2491
2492void
2493proc_list_set(pid, task)
2494	pid_t pid;
2495	char *task;
2496{
2497	int i;
2498
2499	for (i = 0; i < ProcListSize; i++)
2500	{
2501		if (ProcListVec[i].proc_pid == pid)
2502		{
2503			PSTRSET(ProcListVec[i].proc_task, task);
2504			break;
2505		}
2506	}
2507}
2508/*
2509**  PROC_LIST_DROP -- drop pid from process list
2510**
2511**	Parameters:
2512**		pid -- pid to drop
2513**		st -- process status
2514**		other -- storage for proc_other (return).
2515**
2516**	Returns:
2517**		none.
2518**
2519**	Side Effects:
2520**		May decrease CurChildren, CurRunners, or
2521**		set RestartRequest or ShutdownRequest.
2522**
2523**	NOTE:	THIS CAN BE CALLED FROM A SIGNAL HANDLER.  DO NOT ADD
2524**		ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
2525**		DOING.
2526*/
2527
2528void
2529proc_list_drop(pid, st, other)
2530	pid_t pid;
2531	int st;
2532	int *other;
2533{
2534	int i;
2535	int type = PROC_NONE;
2536
2537	for (i = 0; i < ProcListSize; i++)
2538	{
2539		if (ProcListVec[i].proc_pid == pid)
2540		{
2541			ProcListVec[i].proc_pid = NO_PID;
2542			type = ProcListVec[i].proc_type;
2543			if (other != NULL)
2544				*other = ProcListVec[i].proc_other;
2545			break;
2546		}
2547	}
2548	if (CurChildren > 0)
2549		CurChildren--;
2550
2551
2552	if (type == PROC_CONTROL && WIFEXITED(st))
2553	{
2554		/* if so, see if we need to restart or shutdown */
2555		if (WEXITSTATUS(st) == EX_RESTART)
2556			RestartRequest = "control socket";
2557		else if (WEXITSTATUS(st) == EX_SHUTDOWN)
2558			ShutdownRequest = "control socket";
2559	}
2560	else if (type == PROC_QUEUE_CHILD && !WIFSTOPPED(st) &&
2561		 ProcListVec[i].proc_other > -1)
2562	{
2563		/* restart this persistent runner */
2564		mark_work_group_restart(ProcListVec[i].proc_other, st);
2565	}
2566	else if (type == PROC_QUEUE)
2567		CurRunners -= ProcListVec[i].proc_count;
2568}
2569/*
2570**  PROC_LIST_CLEAR -- clear the process list
2571**
2572**	Parameters:
2573**		none.
2574**
2575**	Returns:
2576**		none.
2577**
2578**	Side Effects:
2579**		Sets CurChildren to zero.
2580*/
2581
2582void
2583proc_list_clear()
2584{
2585	int i;
2586
2587	/* start from 1 since 0 is the daemon itself */
2588	for (i = 1; i < ProcListSize; i++)
2589		ProcListVec[i].proc_pid = NO_PID;
2590	CurChildren = 0;
2591}
2592/*
2593**  PROC_LIST_PROBE -- probe processes in the list to see if they still exist
2594**
2595**	Parameters:
2596**		none
2597**
2598**	Returns:
2599**		none
2600**
2601**	Side Effects:
2602**		May decrease CurChildren.
2603*/
2604
2605void
2606proc_list_probe()
2607{
2608	int i;
2609
2610	/* start from 1 since 0 is the daemon itself */
2611	for (i = 1; i < ProcListSize; i++)
2612	{
2613		if (ProcListVec[i].proc_pid == NO_PID)
2614			continue;
2615		if (kill(ProcListVec[i].proc_pid, 0) < 0)
2616		{
2617			if (LogLevel > 3)
2618				sm_syslog(LOG_DEBUG, CurEnv->e_id,
2619					  "proc_list_probe: lost pid %d",
2620					  (int) ProcListVec[i].proc_pid);
2621			ProcListVec[i].proc_pid = NO_PID;
2622			SM_FREE_CLR(ProcListVec[i].proc_task);
2623			CurChildren--;
2624		}
2625	}
2626	if (CurChildren < 0)
2627		CurChildren = 0;
2628}
2629
2630/*
2631**  PROC_LIST_DISPLAY -- display the process list
2632**
2633**	Parameters:
2634**		out -- output file pointer
2635**		prefix -- string to output in front of each line.
2636**
2637**	Returns:
2638**		none.
2639*/
2640
2641void
2642proc_list_display(out, prefix)
2643	SM_FILE_T *out;
2644	char *prefix;
2645{
2646	int i;
2647
2648	for (i = 0; i < ProcListSize; i++)
2649	{
2650		if (ProcListVec[i].proc_pid == NO_PID)
2651			continue;
2652
2653		(void) sm_io_fprintf(out, SM_TIME_DEFAULT, "%s%d %s%s\n",
2654				     prefix,
2655				     (int) ProcListVec[i].proc_pid,
2656				     ProcListVec[i].proc_task != NULL ?
2657				     ProcListVec[i].proc_task : "(unknown)",
2658				     (OpMode == MD_SMTP ||
2659				      OpMode == MD_DAEMON ||
2660				      OpMode == MD_ARPAFTP) ? "\r" : "");
2661	}
2662}
2663
2664/*
2665**  PROC_LIST_SIGNAL -- send a signal to a type of process in the list
2666**
2667**	Parameters:
2668**		type -- type of process to signal
2669**		signal -- the type of signal to send
2670**
2671**	Results:
2672**		none.
2673**
2674**	NOTE:	THIS CAN BE CALLED FROM A SIGNAL HANDLER.  DO NOT ADD
2675**		ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
2676**		DOING.
2677*/
2678
2679void
2680proc_list_signal(type, signal)
2681	int type;
2682	int signal;
2683{
2684	int chldwasblocked;
2685	int alrmwasblocked;
2686	int i;
2687	pid_t mypid = getpid();
2688
2689	/* block these signals so that we may signal cleanly */
2690	chldwasblocked = sm_blocksignal(SIGCHLD);
2691	alrmwasblocked = sm_blocksignal(SIGALRM);
2692
2693	/* Find all processes of type and send signal */
2694	for (i = 0; i < ProcListSize; i++)
2695	{
2696		if (ProcListVec[i].proc_pid == NO_PID ||
2697		    ProcListVec[i].proc_pid == mypid)
2698			continue;
2699		if (ProcListVec[i].proc_type != type)
2700			continue;
2701		(void) kill(ProcListVec[i].proc_pid, signal);
2702	}
2703
2704	/* restore the signals */
2705	if (alrmwasblocked == 0)
2706		(void) sm_releasesignal(SIGALRM);
2707	if (chldwasblocked == 0)
2708		(void) sm_releasesignal(SIGCHLD);
2709}
2710