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