xref: /freebsd/contrib/sendmail/src/util.c (revision d39bd2c1388b520fcba9abed1932acacead60fba)
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 *
addquotes(s,rpool)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
stripbackslash(s)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
rfc822_string(s)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
shorten_rfc822_string(string,length)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 *
find_character(string,character)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
check_bodytype(bodytype)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
truncate_at_delim(str,len,delim)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
xalloc_tagged(sz,file,line)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 **
copyplist(list,copycont,rpool)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 *
copyqueue(addr,rpool)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
log_sendmail_pid(e)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
close_sendmail_pid()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
set_delivery_mode(mode,e)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
set_op_mode(mode)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
printav(fp,av)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 				(void) sm_io_putc(fp, SM_TIME_DEFAULT, v);
761 # if 0
762 				if (isascii(v) && isprint(v))
763 					(void) sm_io_putc(fp, SM_TIME_DEFAULT,
764 							v);
765 				else
766 					(void) sm_io_fprintf(fp,
767 						SM_TIME_DEFAULT, "\\x%hhx", v);
768 # endif
769 			}
770 			if (*av != NULL)
771 				(void) sm_io_putc(fp, SM_TIME_DEFAULT, ' ');
772 			continue;
773 		}
774 #endif /* _FFR_8BITENVADDR */
775 		if (tTd(0, 44))
776 			(void) sm_io_fprintf(fp, SM_TIME_DEFAULT,
777 				"\n\t%08lx=", (unsigned long) *av);
778 		else
779 			(void) sm_io_putc(fp, SM_TIME_DEFAULT, ' ');
780 		if (tTd(0, 99))
781 			(void) sm_io_fprintf(fp, SM_TIME_DEFAULT,
782 				"%s", str2prt(*av++));
783 		else
784 			xputs(fp, *av++);
785 	}
786 
787 	/* don't print this if invoked directly (not via xputs())? */
788 	(void) sm_io_putc(fp, SM_TIME_DEFAULT, '\n');
789 }
790 
791 /*
792 **  XPUTS -- put string doing control escapes.
793 **
794 **	Parameters:
795 **		fp -- output file pointer.
796 **		s -- string to put. [A]
797 **
798 **	Returns:
799 **		none.
800 **
801 **	Side Effects:
802 **		output to stdout
803 */
804 
805 void
xputs(fp,s)806 xputs(fp, s)
807 	SM_FILE_T *fp;
808 	const char *s;
809 {
810 	int c;
811 	struct metamac *mp;
812 	bool shiftout = false;
813 	extern struct metamac MetaMacros[];
814 	static SM_DEBUG_T DebugANSI = SM_DEBUG_INITIALIZER("ANSI",
815 		"@(#)$Debug: ANSI - enable reverse video in debug output $");
816 #if _FFR_8BITENVADDR
817 	if (tTd(0, 9))
818 	{
819 		char *av[2];
820 
821 		av[0] = (char *) s;
822 		av[1] = NULL;
823 		printav(fp, av);
824 		return;
825 	}
826 #endif
827 
828 	/*
829 	**  TermEscape is set here, rather than in main(),
830 	**  because ANSI mode can be turned on or off at any time
831 	**  if we are in -bt rule testing mode.
832 	*/
833 
834 	if (sm_debug_unknown(&DebugANSI))
835 	{
836 		if (sm_debug_active(&DebugANSI, 1))
837 		{
838 			TermEscape.te_rv_on = "\033[7m";
839 			TermEscape.te_normal = "\033[0m";
840 		}
841 		else
842 		{
843 			TermEscape.te_rv_on = "";
844 			TermEscape.te_normal = "";
845 		}
846 	}
847 
848 	if (s == NULL)
849 	{
850 		(void) sm_io_fprintf(fp, SM_TIME_DEFAULT, "%s<null>%s",
851 				     TermEscape.te_rv_on, TermEscape.te_normal);
852 		return;
853 	}
854 	while ((c = (*s++ & 0377)) != '\0')
855 	{
856 		if (shiftout)
857 		{
858 			(void) sm_io_fprintf(fp, SM_TIME_DEFAULT, "%s",
859 					     TermEscape.te_normal);
860 			shiftout = false;
861 		}
862 		if (!isascii(c) && !tTd(84, 1))
863 		{
864 			if (c == MATCHREPL)
865 			{
866 				(void) sm_io_fprintf(fp, SM_TIME_DEFAULT,
867 						     "%s$",
868 						     TermEscape.te_rv_on);
869 				shiftout = true;
870 				if (*s == '\0')
871 					continue;
872 				c = *s++ & 0377;
873 				goto printchar;
874 			}
875 			if (c == MACROEXPAND || c == MACRODEXPAND)
876 			{
877 				(void) sm_io_fprintf(fp, SM_TIME_DEFAULT,
878 						     "%s$",
879 						     TermEscape.te_rv_on);
880 				if (c == MACRODEXPAND)
881 					(void) sm_io_putc(fp,
882 							  SM_TIME_DEFAULT, '&');
883 				shiftout = true;
884 				if (*s == '\0')
885 					continue;
886 				if (strchr("=~&?", *s) != NULL)
887 					(void) sm_io_putc(fp,
888 							  SM_TIME_DEFAULT,
889 							  *s++);
890 				if (bitset(0200, *s))
891 					(void) sm_io_fprintf(fp,
892 							     SM_TIME_DEFAULT,
893 							     "{%s}",
894 							     macname(bitidx(*s++)));
895 				else
896 					(void) sm_io_fprintf(fp,
897 							     SM_TIME_DEFAULT,
898 							     "%c",
899 							     *s++);
900 				continue;
901 			}
902 			for (mp = MetaMacros; mp->metaname != '\0'; mp++)
903 			{
904 				if (bitidx(mp->metaval) == c)
905 				{
906 					(void) sm_io_fprintf(fp,
907 							     SM_TIME_DEFAULT,
908 							     "%s$%c",
909 							     TermEscape.te_rv_on,
910 							     mp->metaname);
911 					shiftout = true;
912 					break;
913 				}
914 			}
915 			if (c == MATCHCLASS || c == MATCHNCLASS)
916 			{
917 				if (bitset(0200, *s))
918 					(void) sm_io_fprintf(fp,
919 							     SM_TIME_DEFAULT,
920 							     "{%s}",
921 							     macname(bitidx(*s++)));
922 				else if (*s != '\0')
923 					(void) sm_io_fprintf(fp,
924 							     SM_TIME_DEFAULT,
925 							     "%c",
926 							     *s++);
927 			}
928 			if (mp->metaname != '\0')
929 				continue;
930 
931 			/* unrecognized meta character */
932 			(void) sm_io_fprintf(fp, SM_TIME_DEFAULT, "%sM-",
933 					     TermEscape.te_rv_on);
934 			shiftout = true;
935 			c &= 0177;
936 		}
937   printchar:
938 		if (isascii(c) && isprint(c))
939 		{
940 			(void) sm_io_putc(fp, SM_TIME_DEFAULT, c);
941 			continue;
942 		}
943 
944 		/* wasn't a meta-macro -- find another way to print it */
945 		switch (c)
946 		{
947 		  case '\n':
948 			c = 'n';
949 			break;
950 
951 		  case '\r':
952 			c = 'r';
953 			break;
954 
955 		  case '\t':
956 			c = 't';
957 			break;
958 		}
959 		if (!shiftout)
960 		{
961 			(void) sm_io_fprintf(fp, SM_TIME_DEFAULT, "%s",
962 					     TermEscape.te_rv_on);
963 			shiftout = true;
964 		}
965 		if (isascii(c) && isprint(c))
966 		{
967 			(void) sm_io_putc(fp, SM_TIME_DEFAULT, '\\');
968 			(void) sm_io_putc(fp, SM_TIME_DEFAULT, c);
969 		}
970 		else if (tTd(84, 2))
971 			(void) sm_io_fprintf(fp, SM_TIME_DEFAULT, " %o ", c);
972 		else if (tTd(84, 1))
973 			(void) sm_io_fprintf(fp, SM_TIME_DEFAULT, " %#x ", c);
974 		else if (!isascii(c) && !tTd(84, 1))
975 		{
976 			(void) sm_io_putc(fp, SM_TIME_DEFAULT, '^');
977 			(void) sm_io_putc(fp, SM_TIME_DEFAULT, c ^ 0100);
978 		}
979 	}
980 	if (shiftout)
981 		(void) sm_io_fprintf(fp, SM_TIME_DEFAULT, "%s",
982 				     TermEscape.te_normal);
983 	(void) sm_io_flush(fp, SM_TIME_DEFAULT);
984 }
985 
986 /*
987 **  MAKELOWER_A -- Translate a line into lower case
988 **
989 **	Parameters:
990 **		pp -- pointer to the string to translate (modified in place if possible). [A]
991 **		rpool -- rpool to use to reallocate string if needed
992 **			(if NULL: uses sm_malloc() internally)
993 **
994 **	Returns:
995 **		lower cased string
996 **
997 **	Side Effects:
998 **		String pointed to by pp is translated to lower case if possible.
999 */
1000 
1001 char *
makelower_a(pp,rpool)1002 makelower_a(pp, rpool)
1003 	char **pp;
1004 	SM_RPOOL_T *rpool;
1005 {
1006 	char c;
1007 	char *orig, *p;
1008 
1009 	SM_REQUIRE(pp != NULL);
1010 	p = *pp;
1011 	if (p == NULL)
1012 		return p;
1013 	orig = p;
1014 
1015 #if USE_EAI
1016 	if (!addr_is_ascii(p))
1017 	{
1018 		char *new;
1019 
1020 		new = sm_lowercase(p);
1021 		if (new == p)
1022 			return p;
1023 		*pp = sm_rpool_strdup_tagged_x(rpool, new, "makelower", 0, 1);
1024 		return *pp;
1025 	}
1026 #endif /* USE_EAI */
1027 	for (; (c = *p) != '\0'; p++)
1028 		if (isascii(c) && isupper(c))
1029 			*p = tolower(c);
1030 	return orig;
1031 }
1032 
1033 #if 0
1034 makelower: Optimization for EAI case?
1035 
1036 	unsigned char ch;
1037 
1038 	while ((ch = (unsigned char)*str) != '\0' && ch < 127)
1039 	{
1040 		if (isascii(c) && isupper(c))
1041 			*str = tolower(ch);
1042 		++str;
1043 	}
1044 	if ('\0' == ch)
1045 		return orig;
1046 	handle UTF-8 case: invoke sm_lowercase() etc
1047 #endif /* 0 */
1048 
1049 /*
1050 **  MAKELOWER_BUF -- Translate a line into lower case
1051 **
1052 **	Parameters:
1053 **		str -- string to translate. [A]
1054 **		buf -- where to place lower case version.
1055 **		buflen -- size of buf
1056 **
1057 **	Returns:
1058 **		nothing
1059 **
1060 **	Side Effects:
1061 **		String pointed to by str is translated to lower case if possible.
1062 **
1063 **	Note:
1064 **		if str is lower cased in place and str == buf (same pointer),
1065 **		then it is not explicitly copied.
1066 */
1067 
1068 void
makelower_buf(str,buf,buflen)1069 makelower_buf(str, buf, buflen)
1070 	char *str;
1071 	char *buf;
1072 	int buflen;
1073 {
1074 	char *lower;
1075 
1076 	SM_REQUIRE(buf != NULL);
1077 	if (str == NULL)
1078 		return;
1079 	lower = makelower_a(&str, NULL);
1080 	if (lower != str || str != buf)
1081 	{
1082 		sm_strlcpy(buf, lower, buflen);
1083 		if (lower != str)
1084 			SM_FREE(lower);
1085 	}
1086 	return;
1087 }
1088 
1089 /*
1090 **  FIXCRLF -- fix CRLF in line.
1091 **
1092 **	XXX: Could this be a problem for EAI? That is, can there
1093 **		be a string with \n and the previous octet is \n
1094 **		but is part of a UTF8 "char"?
1095 **
1096 **	Looks for the CRLF combination and turns it into the
1097 **	UNIX canonical LF character.  It only takes one line,
1098 **	i.e., it is assumed that the first LF found is the end
1099 **	of the line.
1100 **
1101 **	Parameters:
1102 **		line -- the line to fix. [A]
1103 **		stripnl -- if true, strip the newline also.
1104 **
1105 **	Returns:
1106 **		none.
1107 **
1108 **	Side Effects:
1109 **		line is changed in place.
1110 */
1111 
1112 void
fixcrlf(line,stripnl)1113 fixcrlf(line, stripnl)
1114 	char *line;
1115 	bool stripnl;
1116 {
1117 	register char *p;
1118 
1119 	p = strchr(line, '\n');
1120 	if (p == NULL)
1121 		return;
1122 	if (p > line && p[-1] == '\r')
1123 		p--;
1124 	if (!stripnl)
1125 		*p++ = '\n';
1126 	*p = '\0';
1127 }
1128 
1129 /*
1130 **  PUTLINE -- put a line like fputs obeying SMTP conventions
1131 **
1132 **	This routine always guarantees outputting a newline (or CRLF,
1133 **	as appropriate) at the end of the string.
1134 **
1135 **	Parameters:
1136 **		l -- line to put. (should be [x])
1137 **		mci -- the mailer connection information.
1138 **
1139 **	Returns:
1140 **		true iff line was written successfully
1141 **
1142 **	Side Effects:
1143 **		output of l to mci->mci_out.
1144 */
1145 
1146 bool
putline(l,mci)1147 putline(l, mci)
1148 	register char *l;
1149 	register MCI *mci;
1150 {
1151 	return putxline(l, strlen(l), mci, PXLF_MAPFROM);
1152 }
1153 
1154 /*
1155 **  PUTXLINE -- putline with flags bits.
1156 **
1157 **	This routine always guarantees outputting a newline (or CRLF,
1158 **	as appropriate) at the end of the string.
1159 **
1160 **	Parameters:
1161 **		l -- line to put. (should be [x])
1162 **		len -- the length of the line.
1163 **		mci -- the mailer connection information.
1164 **		pxflags -- flag bits:
1165 **		    PXLF_MAPFROM -- map From_ to >From_.
1166 **		    PXLF_STRIP8BIT -- strip 8th bit.
1167 **		    PXLF_HEADER -- map bare newline in header to newline space.
1168 **		    PXLF_NOADDEOL -- don't add an EOL if one wasn't present.
1169 **		    PXLF_STRIPMQUOTE -- strip METAQUOTE bytes.
1170 **
1171 **	Returns:
1172 **		true iff line was written successfully
1173 **
1174 **	Side Effects:
1175 **		output of l to mci->mci_out.
1176 */
1177 
1178 #define PUTX(limit)							\
1179 	do								\
1180 	{								\
1181 		quotenext = false;					\
1182 		while (l < limit)					\
1183 		{							\
1184 			unsigned char c = (unsigned char) *l++;		\
1185 									\
1186 			if (bitset(PXLF_STRIPMQUOTE, pxflags) &&	\
1187 			    !quotenext && c == METAQUOTE)		\
1188 			{						\
1189 				quotenext = true;			\
1190 				continue;				\
1191 			}						\
1192 			quotenext = false;				\
1193 			if (strip8bit)					\
1194 				c &= 0177;				\
1195 			if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT,	\
1196 				       c) == SM_IO_EOF)			\
1197 			{						\
1198 				dead = true;				\
1199 				break;					\
1200 			}						\
1201 			if (TrafficLogFile != NULL)			\
1202 				(void) sm_io_putc(TrafficLogFile,	\
1203 						  SM_TIME_DEFAULT,	\
1204 						  c);			\
1205 		}							\
1206 	} while (0)
1207 
1208 bool
putxline(l,len,mci,pxflags)1209 putxline(l, len, mci, pxflags)
1210 	register char *l;
1211 	size_t len;
1212 	register MCI *mci;
1213 	int pxflags;
1214 {
1215 	register char *p, *end;
1216 	int slop;
1217 	bool dead, quotenext, strip8bit;
1218 
1219 	/* strip out 0200 bits -- these can look like TELNET protocol */
1220 	strip8bit = bitset(MCIF_7BIT, mci->mci_flags) ||
1221 		    bitset(PXLF_STRIP8BIT, pxflags);
1222 	dead = false;
1223 	slop = 0;
1224 
1225 	end = l + len;
1226 	do
1227 	{
1228 		bool noeol = false;
1229 
1230 		/* find the end of the line */
1231 		p = memchr(l, '\n', end - l);
1232 		if (p == NULL)
1233 		{
1234 			p = end;
1235 			noeol = true;
1236 		}
1237 
1238 		if (TrafficLogFile != NULL)
1239 			(void) sm_io_fprintf(TrafficLogFile, SM_TIME_DEFAULT,
1240 					     "%05d >>> ", (int) CurrentPid);
1241 
1242 		/* check for line overflow */
1243 		while (mci->mci_mailer->m_linelimit > 0 &&
1244 		       (p - l + slop) > mci->mci_mailer->m_linelimit)
1245 		{
1246 			register char *q = &l[mci->mci_mailer->m_linelimit - slop - 1];
1247 
1248 			if (l[0] == '.' && slop == 0 &&
1249 			    bitnset(M_XDOT, mci->mci_mailer->m_flags))
1250 			{
1251 				if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT,
1252 					       '.') == SM_IO_EOF)
1253 					dead = true;
1254 				if (TrafficLogFile != NULL)
1255 					(void) sm_io_putc(TrafficLogFile,
1256 							  SM_TIME_DEFAULT, '.');
1257 			}
1258 			else if (l[0] == 'F' && slop == 0 &&
1259 				 bitset(PXLF_MAPFROM, pxflags) &&
1260 				 strncmp(l, "From ", 5) == 0 &&
1261 				 bitnset(M_ESCFROM, mci->mci_mailer->m_flags))
1262 			{
1263 				if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT,
1264 					       '>') == SM_IO_EOF)
1265 					dead = true;
1266 				if (TrafficLogFile != NULL)
1267 					(void) sm_io_putc(TrafficLogFile,
1268 							  SM_TIME_DEFAULT,
1269 							  '>');
1270 			}
1271 			if (dead)
1272 				break;
1273 
1274 			PUTX(q);
1275 			if (dead)
1276 				break;
1277 
1278 			if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT,
1279 					'!') == SM_IO_EOF ||
1280 			    sm_io_fputs(mci->mci_out, SM_TIME_DEFAULT,
1281 					mci->mci_mailer->m_eol) == SM_IO_EOF ||
1282 			    sm_io_putc(mci->mci_out, SM_TIME_DEFAULT,
1283 					' ') == SM_IO_EOF)
1284 			{
1285 				dead = true;
1286 				break;
1287 			}
1288 			if (TrafficLogFile != NULL)
1289 			{
1290 				(void) sm_io_fprintf(TrafficLogFile,
1291 						     SM_TIME_DEFAULT,
1292 						     "!\n%05d >>>  ",
1293 						     (int) CurrentPid);
1294 			}
1295 			slop = 1;
1296 		}
1297 
1298 		if (dead)
1299 			break;
1300 
1301 		/* output last part */
1302 		if (l[0] == '.' && slop == 0 &&
1303 		    bitnset(M_XDOT, mci->mci_mailer->m_flags) &&
1304 		    !bitset(MCIF_INLONGLINE, mci->mci_flags))
1305 		{
1306 			if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT, '.') ==
1307 			    SM_IO_EOF)
1308 			{
1309 				dead = true;
1310 				break;
1311 			}
1312 			if (TrafficLogFile != NULL)
1313 				(void) sm_io_putc(TrafficLogFile,
1314 						  SM_TIME_DEFAULT, '.');
1315 		}
1316 		else if (l[0] == 'F' && slop == 0 &&
1317 			 bitset(PXLF_MAPFROM, pxflags) &&
1318 			 strncmp(l, "From ", 5) == 0 &&
1319 			 bitnset(M_ESCFROM, mci->mci_mailer->m_flags) &&
1320 			 !bitset(MCIF_INLONGLINE, mci->mci_flags))
1321 		{
1322 			if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT, '>') ==
1323 			    SM_IO_EOF)
1324 			{
1325 				dead = true;
1326 				break;
1327 			}
1328 			if (TrafficLogFile != NULL)
1329 				(void) sm_io_putc(TrafficLogFile,
1330 						  SM_TIME_DEFAULT, '>');
1331 		}
1332 		PUTX(p);
1333 		if (dead)
1334 			break;
1335 
1336 		if (TrafficLogFile != NULL)
1337 			(void) sm_io_putc(TrafficLogFile, SM_TIME_DEFAULT,
1338 					  '\n');
1339 		if ((!bitset(PXLF_NOADDEOL, pxflags) || !noeol))
1340 		{
1341 			mci->mci_flags &= ~MCIF_INLONGLINE;
1342 			if (sm_io_fputs(mci->mci_out, SM_TIME_DEFAULT,
1343 					mci->mci_mailer->m_eol) == SM_IO_EOF)
1344 			{
1345 				dead = true;
1346 				break;
1347 			}
1348 		}
1349 		else
1350 			mci->mci_flags |= MCIF_INLONGLINE;
1351 
1352 		if (l < end && *l == '\n')
1353 		{
1354 			if (*++l != ' ' && *l != '\t' && *l != '\0' &&
1355 			    bitset(PXLF_HEADER, pxflags))
1356 			{
1357 				if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT,
1358 					       ' ') == SM_IO_EOF)
1359 				{
1360 					dead = true;
1361 					break;
1362 				}
1363 
1364 				if (TrafficLogFile != NULL)
1365 					(void) sm_io_putc(TrafficLogFile,
1366 							  SM_TIME_DEFAULT, ' ');
1367 			}
1368 		}
1369 
1370 	} while (l < end);
1371 	return !dead;
1372 }
1373 
1374 /*
1375 **  XUNLINK -- unlink a file, doing logging as appropriate.
1376 **
1377 **	Parameters:
1378 **		f -- name of file to unlink.
1379 **
1380 **	Returns:
1381 **		return value of unlink()
1382 **
1383 **	Side Effects:
1384 **		f is unlinked.
1385 */
1386 
1387 int
xunlink(f)1388 xunlink(f)
1389 	char *f;
1390 {
1391 	register int i;
1392 	int save_errno;
1393 
1394 	if (LogLevel > 98)
1395 		sm_syslog(LOG_DEBUG, CurEnv->e_id, "unlink %s", f);
1396 
1397 	i = unlink(f);
1398 	save_errno = errno;
1399 	if (i < 0 && LogLevel > 97)
1400 		sm_syslog(LOG_DEBUG, CurEnv->e_id, "%s: unlink-fail %d",
1401 			  f, errno);
1402 	if (i >= 0)
1403 		SYNC_DIR(f, false);
1404 	errno = save_errno;
1405 	return i;
1406 }
1407 
1408 /*
1409 **  SFGETS -- "safe" fgets -- times out and ignores random interrupts.
1410 **
1411 **	Parameters:
1412 **		buf -- place to put the input line.
1413 **			(can be [A], but should be [x])
1414 **		siz -- size of buf.
1415 **		fp -- file to read from.
1416 **		timeout -- the timeout before error occurs.
1417 **		during -- what we are trying to read (for error messages).
1418 **
1419 **	Returns:
1420 **		NULL on error (including timeout).  This may also leave
1421 **			buf containing a null string.
1422 **		buf otherwise.
1423 */
1424 
1425 char *
sfgets(buf,siz,fp,timeout,during)1426 sfgets(buf, siz, fp, timeout, during)
1427 	char *buf;
1428 	int siz;
1429 	SM_FILE_T *fp;
1430 	time_t timeout;
1431 	char *during;
1432 {
1433 	register char *p;
1434 	int save_errno, io_timeout, l;
1435 
1436 	SM_REQUIRE(siz > 0);
1437 	SM_REQUIRE(buf != NULL);
1438 
1439 	if (fp == NULL)
1440 	{
1441 		buf[0] = '\0';
1442 		errno = EBADF;
1443 		return NULL;
1444 	}
1445 
1446 	/* try to read */
1447 	l = -1;
1448 	errno = 0;
1449 
1450 	/* convert the timeout to sm_io notation */
1451 	io_timeout = (timeout <= 0) ? SM_TIME_DEFAULT : timeout * 1000;
1452 	while (!sm_io_eof(fp) && !sm_io_error(fp))
1453 	{
1454 		errno = 0;
1455 		l = sm_io_fgets(fp, io_timeout, buf, siz);
1456 		if (l < 0 && errno == EAGAIN)
1457 		{
1458 			/* The sm_io_fgets() call timedout */
1459 			if (LogLevel > 1)
1460 				sm_syslog(LOG_NOTICE, CurEnv->e_id,
1461 					  "timeout waiting for input from %.100s during %s",
1462 					  CURHOSTNAME,
1463 					  during);
1464 			buf[0] = '\0';
1465 			checkfd012(during);
1466 			if (TrafficLogFile != NULL)
1467 				(void) sm_io_fprintf(TrafficLogFile,
1468 						     SM_TIME_DEFAULT,
1469 						     "%05d <<< [TIMEOUT]\n",
1470 						     (int) CurrentPid);
1471 			errno = ETIMEDOUT;
1472 			return NULL;
1473 		}
1474 		if (l >= 0 || errno != EINTR)
1475 			break;
1476 		(void) sm_io_clearerr(fp);
1477 	}
1478 	save_errno = errno;
1479 
1480 	/* clean up the books and exit */
1481 	LineNumber++;
1482 	if (l < 0)
1483 	{
1484 		buf[0] = '\0';
1485 		if (TrafficLogFile != NULL)
1486 			(void) sm_io_fprintf(TrafficLogFile, SM_TIME_DEFAULT,
1487 					     "%05d <<< [EOF]\n",
1488 					     (int) CurrentPid);
1489 		errno = save_errno;
1490 		return NULL;
1491 	}
1492 	if (TrafficLogFile != NULL)
1493 		(void) sm_io_fprintf(TrafficLogFile, SM_TIME_DEFAULT,
1494 				     "%05d <<< %s", (int) CurrentPid, buf);
1495 	if (SevenBitInput)
1496 	{
1497 		for (p = buf; *p != '\0'; p++)
1498 			*p &= ~0200;
1499 	}
1500 	else if (!HasEightBits)
1501 	{
1502 		for (p = buf; *p != '\0'; p++)
1503 		{
1504 			if (bitset(0200, *p))
1505 			{
1506 				HasEightBits = true;
1507 				break;
1508 			}
1509 		}
1510 	}
1511 	return buf;
1512 }
1513 
1514 /*
1515 **  FGETFOLDED -- like fgets, but knows about folded lines.
1516 **
1517 **	Parameters:
1518 **		buf -- place to put result.
1519 **			(can be [A], but should be [x])
1520 **		np -- pointer to bytes available; will be updated with
1521 **			the actual buffer size (not number of bytes filled)
1522 **			on return.
1523 **		f -- file to read from.
1524 **
1525 **	Returns:
1526 **		input line(s) on success, NULL on error or SM_IO_EOF.
1527 **		This will normally be buf -- unless the line is too
1528 **			long, when it will be sm_malloc_x()ed.
1529 **
1530 **	Side Effects:
1531 **		buf gets lines from f, with continuation lines (lines
1532 **		with leading white space) appended.  CRLF's are mapped
1533 **		into single newlines.  Any trailing LF is stripped.
1534 **		Increases LineNumber for each line.
1535 */
1536 
1537 char *
fgetfolded(buf,np,f)1538 fgetfolded(buf, np, f)
1539 	char *buf;
1540 	int *np;
1541 	SM_FILE_T *f;
1542 {
1543 	register char *p = buf;
1544 	char *bp = buf;
1545 	register int i;
1546 	int n;
1547 
1548 	SM_REQUIRE(np != NULL);
1549 	n = *np;
1550 	SM_REQUIRE(n > 0);
1551 	SM_REQUIRE(buf != NULL);
1552 	if (f == NULL)
1553 	{
1554 		buf[0] = '\0';
1555 		errno = EBADF;
1556 		return NULL;
1557 	}
1558 
1559 	n--;
1560 	while ((i = sm_io_getc(f, SM_TIME_DEFAULT)) != SM_IO_EOF)
1561 	{
1562 		if (i == '\r')
1563 		{
1564 			i = sm_io_getc(f, SM_TIME_DEFAULT);
1565 			if (i != '\n')
1566 			{
1567 				if (i != SM_IO_EOF)
1568 					(void) sm_io_ungetc(f, SM_TIME_DEFAULT,
1569 							    i);
1570 				i = '\r';
1571 			}
1572 		}
1573 		if (--n <= 0)
1574 		{
1575 			/* allocate new space */
1576 			char *nbp;
1577 			int nn;
1578 
1579 			nn = (p - bp);
1580 			if (nn < MEMCHUNKSIZE)
1581 				nn *= 2;
1582 			else
1583 				nn += MEMCHUNKSIZE;
1584 			nbp = sm_malloc_x(nn);
1585 			memmove(nbp, bp, p - bp);
1586 			p = &nbp[p - bp];
1587 			if (bp != buf)
1588 				sm_free(bp);
1589 			bp = nbp;
1590 			n = nn - (p - bp);
1591 			*np = nn;
1592 		}
1593 		*p++ = i;
1594 		if (i == '\n')
1595 		{
1596 			LineNumber++;
1597 			i = sm_io_getc(f, SM_TIME_DEFAULT);
1598 			if (i != SM_IO_EOF)
1599 				(void) sm_io_ungetc(f, SM_TIME_DEFAULT, i);
1600 			if (i != ' ' && i != '\t')
1601 				break;
1602 		}
1603 	}
1604 	if (p == bp)
1605 		return NULL;
1606 	if (p[-1] == '\n')
1607 		p--;
1608 	*p = '\0';
1609 	return bp;
1610 }
1611 
1612 /*
1613 **  CURTIME -- return current time.
1614 **
1615 **	Parameters:
1616 **		none.
1617 **
1618 **	Returns:
1619 **		the current time.
1620 */
1621 
1622 time_t
curtime()1623 curtime()
1624 {
1625 	auto time_t t;
1626 
1627 	(void) time(&t);
1628 	return t;
1629 }
1630 
1631 /*
1632 **  ATOBOOL -- convert a string representation to boolean.
1633 **
1634 **	Defaults to false
1635 **
1636 **	Parameters:
1637 **		s -- string to convert.  Takes "tTyY", empty, and NULL as true,
1638 **			others as false.
1639 **
1640 **	Returns:
1641 **		A boolean representation of the string.
1642 */
1643 
1644 bool
atobool(s)1645 atobool(s)
1646 	register char *s;
1647 {
1648 	if (SM_IS_EMPTY(s) || strchr("tTyY", *s) != NULL)
1649 		return true;
1650 	return false;
1651 }
1652 
1653 /*
1654 **  ATOOCT -- convert a string representation to octal.
1655 **
1656 **	Parameters:
1657 **		s -- string to convert.
1658 **
1659 **	Returns:
1660 **		An integer representing the string interpreted as an
1661 **		octal number.
1662 */
1663 
1664 int
atooct(s)1665 atooct(s)
1666 	register char *s;
1667 {
1668 	register int i = 0;
1669 
1670 	while (*s >= '0' && *s <= '7')
1671 		i = (i << 3) | (*s++ - '0');
1672 	return i;
1673 }
1674 
1675 /*
1676 **  BITINTERSECT -- tell if two bitmaps intersect
1677 **
1678 **	Parameters:
1679 **		a, b -- the bitmaps in question
1680 **
1681 **	Returns:
1682 **		true if they have a non-null intersection
1683 **		false otherwise
1684 */
1685 
1686 bool
bitintersect(a,b)1687 bitintersect(a, b)
1688 	BITMAP256 a;
1689 	BITMAP256 b;
1690 {
1691 	int i;
1692 
1693 	for (i = BITMAPBYTES / sizeof(int); --i >= 0; )
1694 	{
1695 		if ((a[i] & b[i]) != 0)
1696 			return true;
1697 	}
1698 	return false;
1699 }
1700 
1701 /*
1702 **  BITZEROP -- tell if a bitmap is all zero
1703 **
1704 **	Parameters:
1705 **		map -- the bit map to check
1706 **
1707 **	Returns:
1708 **		true if map is all zero.
1709 **		false if there are any bits set in map.
1710 */
1711 
1712 bool
bitzerop(map)1713 bitzerop(map)
1714 	BITMAP256 map;
1715 {
1716 	int i;
1717 
1718 	for (i = BITMAPBYTES / sizeof(int); --i >= 0; )
1719 	{
1720 		if (map[i] != 0)
1721 			return false;
1722 	}
1723 	return true;
1724 }
1725 
1726 /*
1727 **  STRCONTAINEDIN -- tell if one string is contained in another
1728 **
1729 **	Parameters:
1730 **		icase -- ignore case?
1731 **		a -- possible substring. [A]
1732 **		b -- possible superstring. [A]
1733 **		(both must be the same format: X or Q)
1734 **
1735 **	Returns:
1736 **		true if a is contained in b (case insensitive).
1737 **		false otherwise.
1738 */
1739 
1740 bool
strcontainedin(icase,a,b)1741 strcontainedin(icase, a, b)
1742 	bool icase;
1743 	register char *a;
1744 	register char *b;
1745 {
1746 	int la;
1747 	int lb;
1748 	int c;
1749 
1750 	la = strlen(a);
1751 	lb = strlen(b);
1752 	c = *a;
1753 	if (icase && isascii(c) && isupper(c))
1754 		c = tolower(c);
1755 	for (; lb-- >= la; b++)
1756 	{
1757 		if (icase)
1758 		{
1759 			if (*b != c &&
1760 			    isascii(*b) && isupper(*b) && tolower(*b) != c)
1761 				continue;
1762 			if (sm_strncasecmp(a, b, la) == 0)
1763 				return true;
1764 		}
1765 		else
1766 		{
1767 			if (*b != c)
1768 				continue;
1769 			if (strncmp(a, b, la) == 0)
1770 				return true;
1771 		}
1772 	}
1773 	return false;
1774 }
1775 
1776 #if XDEBUG
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
checkfd012(where)1791 checkfd012(where)
1792 	char *where;
1793 {
1794 	register int i;
1795 
1796 	for (i = 0; i < 3; i++)
1797 		fill_fd(i, where);
1798 }
1799 
1800 /*
1801 **  CHECKFDOPEN -- make sure file descriptor is open -- for extended debugging
1802 **
1803 **	Parameters:
1804 **		fd -- file descriptor to check.
1805 **		where -- tag to print on failure.
1806 **
1807 **	Returns:
1808 **		none.
1809 */
1810 
1811 void
checkfdopen(fd,where)1812 checkfdopen(fd, where)
1813 	int fd;
1814 	char *where;
1815 {
1816 	struct stat st;
1817 
1818 	if (fstat(fd, &st) < 0 && errno == EBADF)
1819 	{
1820 		syserr("checkfdopen(%d): %s not open as expected!", fd, where);
1821 		printopenfds(true);
1822 	}
1823 }
1824 #endif /* XDEBUG */
1825 
1826 /*
1827 **  CHECKFDS -- check for new or missing file descriptors
1828 **
1829 **	Parameters:
1830 **		where -- tag for printing.  If null, take a base line.
1831 **
1832 **	Returns:
1833 **		none
1834 **
1835 **	Side Effects:
1836 **		If where is set, shows changes since the last call.
1837 */
1838 
1839 void
checkfds(where)1840 checkfds(where)
1841 	char *where;
1842 {
1843 	int maxfd;
1844 	register int fd;
1845 	bool printhdr = true;
1846 	int save_errno = errno;
1847 	static BITMAP256 baseline;
1848 	extern int DtableSize;
1849 
1850 	if (DtableSize > BITMAPBITS)
1851 		maxfd = BITMAPBITS;
1852 	else
1853 		maxfd = DtableSize;
1854 	if (where == NULL)
1855 		clrbitmap(baseline);
1856 
1857 	for (fd = 0; fd < maxfd; fd++)
1858 	{
1859 		struct stat stbuf;
1860 
1861 		if (fstat(fd, &stbuf) < 0 && errno != EOPNOTSUPP)
1862 		{
1863 			if (!bitnset(fd, baseline))
1864 				continue;
1865 			clrbitn(fd, baseline);
1866 		}
1867 		else if (!bitnset(fd, baseline))
1868 			setbitn(fd, baseline);
1869 		else
1870 			continue;
1871 
1872 		/* file state has changed */
1873 		if (where == NULL)
1874 			continue;
1875 		if (printhdr)
1876 		{
1877 			sm_syslog(LOG_DEBUG, CurEnv->e_id,
1878 				  "%s: changed fds:",
1879 				  where);
1880 			printhdr = false;
1881 		}
1882 		dumpfd(fd, true, true);
1883 	}
1884 	errno = save_errno;
1885 }
1886 
1887 /*
1888 **  PRINTOPENFDS -- print the open file descriptors (for debugging)
1889 **
1890 **	Parameters:
1891 **		logit -- if set, send output to syslog; otherwise
1892 **			print for debugging.
1893 **
1894 **	Returns:
1895 **		none.
1896 */
1897 
1898 #if NETINET || NETINET6
1899 # include <arpa/inet.h>
1900 #endif
1901 
1902 void
printopenfds(logit)1903 printopenfds(logit)
1904 	bool logit;
1905 {
1906 	register int fd;
1907 	extern int DtableSize;
1908 
1909 	for (fd = 0; fd < DtableSize; fd++)
1910 		dumpfd(fd, false, logit);
1911 }
1912 
1913 /*
1914 **  DUMPFD -- dump a file descriptor
1915 **
1916 **	Parameters:
1917 **		fd -- the file descriptor to dump.
1918 **		printclosed -- if set, print a notification even if
1919 **			it is closed; otherwise print nothing.
1920 **		logit -- if set, use sm_syslog instead of sm_dprintf()
1921 **
1922 **	Returns:
1923 **		none.
1924 */
1925 
1926 void
dumpfd(fd,printclosed,logit)1927 dumpfd(fd, printclosed, logit)
1928 	int fd;
1929 	bool printclosed;
1930 	bool logit;
1931 {
1932 	register char *p;
1933 	char *hp;
1934 #ifdef S_IFSOCK
1935 	SOCKADDR sa;
1936 #endif
1937 	auto SOCKADDR_LEN_T slen;
1938 	int i;
1939 #if STAT64 > 0
1940 	struct stat64 st;
1941 #else
1942 	struct stat st;
1943 #endif
1944 	char buf[200];
1945 
1946 	p = buf;
1947 	(void) sm_snprintf(p, SPACELEFT(buf, p), "%3d: ", fd);
1948 	p += strlen(p);
1949 
1950 	if (
1951 #if STAT64 > 0
1952 	    fstat64(fd, &st)
1953 #else
1954 	    fstat(fd, &st)
1955 #endif
1956 	    < 0)
1957 	{
1958 		if (errno != EBADF)
1959 		{
1960 			(void) sm_snprintf(p, SPACELEFT(buf, p),
1961 				"CANNOT STAT (%s)",
1962 				sm_errstring(errno));
1963 			goto printit;
1964 		}
1965 		else if (printclosed)
1966 		{
1967 			(void) sm_snprintf(p, SPACELEFT(buf, p), "CLOSED");
1968 			goto printit;
1969 		}
1970 		return;
1971 	}
1972 
1973 	i = fcntl(fd, F_GETFL, 0);
1974 	if (i != -1)
1975 	{
1976 		(void) sm_snprintf(p, SPACELEFT(buf, p), "fl=0x%x, ", i);
1977 		p += strlen(p);
1978 	}
1979 
1980 	(void) sm_snprintf(p, SPACELEFT(buf, p), "mode=%o: ",
1981 			(unsigned int) st.st_mode);
1982 	p += strlen(p);
1983 	switch (st.st_mode & S_IFMT)
1984 	{
1985 #ifdef S_IFSOCK
1986 	  case S_IFSOCK:
1987 		(void) sm_snprintf(p, SPACELEFT(buf, p), "SOCK ");
1988 		p += strlen(p);
1989 		memset(&sa, '\0', sizeof(sa));
1990 		slen = sizeof(sa);
1991 		if (getsockname(fd, &sa.sa, &slen) < 0)
1992 			(void) sm_snprintf(p, SPACELEFT(buf, p), "(%s)",
1993 				 sm_errstring(errno));
1994 		else
1995 		{
1996 			hp = hostnamebyanyaddr(&sa);
1997 			if (hp == NULL)
1998 			{
1999 				/* EMPTY */
2000 				/* do nothing */
2001 			}
2002 # if NETINET
2003 			else if (sa.sa.sa_family == AF_INET)
2004 				(void) sm_snprintf(p, SPACELEFT(buf, p),
2005 					"%s/%d", hp, ntohs(sa.sin.sin_port));
2006 # endif
2007 # if NETINET6
2008 			else if (sa.sa.sa_family == AF_INET6)
2009 				(void) sm_snprintf(p, SPACELEFT(buf, p),
2010 					"%s/%d", hp, ntohs(sa.sin6.sin6_port));
2011 # endif
2012 			else
2013 				(void) sm_snprintf(p, SPACELEFT(buf, p),
2014 					"%s", hp);
2015 		}
2016 		p += strlen(p);
2017 		(void) sm_snprintf(p, SPACELEFT(buf, p), "->");
2018 		p += strlen(p);
2019 		slen = sizeof(sa);
2020 		if (getpeername(fd, &sa.sa, &slen) < 0)
2021 			(void) sm_snprintf(p, SPACELEFT(buf, p), "(%s)",
2022 					sm_errstring(errno));
2023 		else
2024 		{
2025 			hp = hostnamebyanyaddr(&sa);
2026 			if (hp == NULL)
2027 			{
2028 				/* EMPTY */
2029 				/* do nothing */
2030 			}
2031 # if NETINET
2032 			else if (sa.sa.sa_family == AF_INET)
2033 				(void) sm_snprintf(p, SPACELEFT(buf, p),
2034 					"%s/%d", hp, ntohs(sa.sin.sin_port));
2035 # endif
2036 # if NETINET6
2037 			else if (sa.sa.sa_family == AF_INET6)
2038 				(void) sm_snprintf(p, SPACELEFT(buf, p),
2039 					"%s/%d", hp, ntohs(sa.sin6.sin6_port));
2040 # endif
2041 			else
2042 				(void) sm_snprintf(p, SPACELEFT(buf, p),
2043 					"%s", hp);
2044 		}
2045 		break;
2046 #endif /* S_IFSOCK */
2047 
2048 	  case S_IFCHR:
2049 		(void) sm_snprintf(p, SPACELEFT(buf, p), "CHR: ");
2050 		p += strlen(p);
2051 		goto defprint;
2052 
2053 #ifdef S_IFBLK
2054 	  case S_IFBLK:
2055 		(void) sm_snprintf(p, SPACELEFT(buf, p), "BLK: ");
2056 		p += strlen(p);
2057 		goto defprint;
2058 #endif
2059 
2060 #if defined(S_IFIFO) && (!defined(S_IFSOCK) || S_IFIFO != S_IFSOCK)
2061 	  case S_IFIFO:
2062 		(void) sm_snprintf(p, SPACELEFT(buf, p), "FIFO: ");
2063 		p += strlen(p);
2064 		goto defprint;
2065 #endif
2066 
2067 #ifdef S_IFDIR
2068 	  case S_IFDIR:
2069 		(void) sm_snprintf(p, SPACELEFT(buf, p), "DIR: ");
2070 		p += strlen(p);
2071 		goto defprint;
2072 #endif
2073 
2074 #ifdef S_IFLNK
2075 	  case S_IFLNK:
2076 		(void) sm_snprintf(p, SPACELEFT(buf, p), "LNK: ");
2077 		p += strlen(p);
2078 		goto defprint;
2079 #endif
2080 
2081 	  default:
2082 defprint:
2083 		(void) sm_snprintf(p, SPACELEFT(buf, p),
2084 			 "dev=%ld/%ld, ino=%llu, nlink=%d, u/gid=%ld/%ld, ",
2085 			 (long) major(st.st_dev), (long) minor(st.st_dev),
2086 			 (ULONGLONG_T) st.st_ino,
2087 			 (int) st.st_nlink, (long) st.st_uid,
2088 			 (long) st.st_gid);
2089 		p += strlen(p);
2090 		(void) sm_snprintf(p, SPACELEFT(buf, p), "size=%llu",
2091 			 (ULONGLONG_T) st.st_size);
2092 		break;
2093 	}
2094 
2095 printit:
2096 	if (logit)
2097 		sm_syslog(LOG_DEBUG, CurEnv ? CurEnv->e_id : NULL,
2098 			  "%.800s", buf);
2099 	else
2100 		sm_dprintf("%s\n", buf);
2101 }
2102 
2103 /*
2104 **  SHORTEN_HOSTNAME -- strip local domain information off of hostname.
2105 **
2106 **	Parameters:
2107 **		host -- the host to shorten (stripped in place).
2108 **		[EAI: matched against $m: must be same format;
2109 **		conversion needed?]
2110 **
2111 **	Returns:
2112 **		place where string was truncated, NULL if not truncated.
2113 */
2114 
2115 char *
shorten_hostname(host)2116 shorten_hostname(host)
2117 	char host[];
2118 {
2119 	register char *p;
2120 	char *mydom;
2121 	int i;
2122 	bool canon = false;
2123 
2124 	/* strip off final dot */
2125 	i = strlen(host);
2126 	p = &host[(i == 0) ? 0 : i - 1];
2127 	if (*p == '.')
2128 	{
2129 		*p = '\0';
2130 		canon = true;
2131 	}
2132 
2133 	/* see if there is any domain at all -- if not, we are done */
2134 	p = strchr(host, '.');
2135 	if (p == NULL)
2136 		return NULL;
2137 
2138 	/* yes, we have a domain -- see if it looks like us */
2139 	mydom = macvalue('m', CurEnv);
2140 	if (mydom == NULL)
2141 		mydom = "";
2142 	i = strlen(++p);
2143 	if ((canon ? sm_strcasecmp(p, mydom)
2144 		   : sm_strncasecmp(p, mydom, i)) == 0 &&
2145 			(mydom[i] == '.' || mydom[i] == '\0'))
2146 	{
2147 		*--p = '\0';
2148 		return p;
2149 	}
2150 	return NULL;
2151 }
2152 
2153 /*
2154 **  PROG_OPEN -- open a program for reading
2155 **
2156 **	Parameters:
2157 **		argv -- the argument list.
2158 **		pfd -- pointer to a place to store the file descriptor.
2159 **		e -- the current envelope.
2160 **
2161 **	Returns:
2162 **		pid of the process -- -1 if it failed.
2163 */
2164 
2165 pid_t
prog_open(argv,pfd,e)2166 prog_open(argv, pfd, e)
2167 	char **argv;
2168 	int *pfd;
2169 	ENVELOPE *e;
2170 {
2171 	pid_t pid;
2172 	int save_errno;
2173 	int sff;
2174 	int ret;
2175 	int fdv[2];
2176 	char *p, *q;
2177 	char buf[MAXPATHLEN];
2178 	extern int DtableSize;
2179 
2180 	if (pipe(fdv) < 0)
2181 	{
2182 		syserr("%s: cannot create pipe for stdout", argv[0]);
2183 		return -1;
2184 	}
2185 	pid = fork();
2186 	if (pid < 0)
2187 	{
2188 		syserr("%s: cannot fork", argv[0]);
2189 		(void) close(fdv[0]);
2190 		(void) close(fdv[1]);
2191 		return -1;
2192 	}
2193 	if (pid > 0)
2194 	{
2195 		/* parent */
2196 		(void) close(fdv[1]);
2197 		*pfd = fdv[0];
2198 		return pid;
2199 	}
2200 
2201 	/* Reset global flags */
2202 	RestartRequest = NULL;
2203 	RestartWorkGroup = false;
2204 	ShutdownRequest = NULL;
2205 	PendingSignal = 0;
2206 	CurrentPid = getpid();
2207 
2208 	/*
2209 	**  Initialize exception stack and default exception
2210 	**  handler for child process.
2211 	*/
2212 
2213 	sm_exc_newthread(fatal_error);
2214 
2215 	/* child -- close stdin */
2216 	(void) close(0);
2217 
2218 	/* stdout goes back to parent */
2219 	(void) close(fdv[0]);
2220 	if (dup2(fdv[1], 1) < 0)
2221 	{
2222 		syserr("%s: cannot dup2 for stdout", argv[0]);
2223 		_exit(EX_OSERR);
2224 	}
2225 	(void) close(fdv[1]);
2226 
2227 	/* stderr goes to transcript if available */
2228 	if (e->e_xfp != NULL)
2229 	{
2230 		int xfd;
2231 
2232 		xfd = sm_io_getinfo(e->e_xfp, SM_IO_WHAT_FD, NULL);
2233 		if (xfd >= 0 && dup2(xfd, 2) < 0)
2234 		{
2235 			syserr("%s: cannot dup2 for stderr", argv[0]);
2236 			_exit(EX_OSERR);
2237 		}
2238 	}
2239 
2240 	/* this process has no right to the queue file */
2241 	if (e->e_lockfp != NULL)
2242 	{
2243 		int fd;
2244 
2245 		fd = sm_io_getinfo(e->e_lockfp, SM_IO_WHAT_FD, NULL);
2246 		if (fd >= 0)
2247 			(void) close(fd);
2248 		else
2249 			syserr("%s: lockfp does not have a fd", argv[0]);
2250 	}
2251 
2252 	/* chroot to the program mailer directory, if defined */
2253 	if (ProgMailer != NULL && ProgMailer->m_rootdir != NULL)
2254 	{
2255 		expand(ProgMailer->m_rootdir, buf, sizeof(buf), e);
2256 		if (chroot(buf) < 0)
2257 		{
2258 			syserr("prog_open: cannot chroot(%s)", buf);
2259 			exit(EX_TEMPFAIL);
2260 		}
2261 		if (chdir("/") < 0)
2262 		{
2263 			syserr("prog_open: cannot chdir(/)");
2264 			exit(EX_TEMPFAIL);
2265 		}
2266 	}
2267 
2268 	/* run as default user */
2269 	endpwent();
2270 	sm_mbdb_terminate();
2271 #if _FFR_MEMSTAT
2272 	(void) sm_memstat_close();
2273 #endif
2274 	if (setgid(DefGid) < 0 && geteuid() == 0)
2275 	{
2276 		syserr("prog_open: setgid(%ld) failed", (long) DefGid);
2277 		exit(EX_TEMPFAIL);
2278 	}
2279 	if (setuid(DefUid) < 0 && geteuid() == 0)
2280 	{
2281 		syserr("prog_open: setuid(%ld) failed", (long) DefUid);
2282 		exit(EX_TEMPFAIL);
2283 	}
2284 
2285 	/* run in some directory */
2286 	if (ProgMailer != NULL)
2287 		p = ProgMailer->m_execdir;
2288 	else
2289 		p = NULL;
2290 	for (; p != NULL; p = q)
2291 	{
2292 		q = strchr(p, ':');
2293 		if (q != NULL)
2294 			*q = '\0';
2295 		expand(p, buf, sizeof(buf), e);
2296 		if (q != NULL)
2297 			*q++ = ':';
2298 		if (buf[0] != '\0' && chdir(buf) >= 0)
2299 			break;
2300 	}
2301 	if (p == NULL)
2302 	{
2303 		/* backup directories */
2304 		if (chdir("/tmp") < 0)
2305 			(void) chdir("/");
2306 	}
2307 
2308 	/* Check safety of program to be run */
2309 	sff = SFF_ROOTOK|SFF_EXECOK;
2310 	if (!bitnset(DBS_RUNWRITABLEPROGRAM, DontBlameSendmail))
2311 		sff |= SFF_NOGWFILES|SFF_NOWWFILES;
2312 	if (bitnset(DBS_RUNPROGRAMINUNSAFEDIRPATH, DontBlameSendmail))
2313 		sff |= SFF_NOPATHCHECK;
2314 	else
2315 		sff |= SFF_SAFEDIRPATH;
2316 	ret = safefile(argv[0], DefUid, DefGid, DefUser, sff, 0, NULL);
2317 	if (ret != 0)
2318 		sm_syslog(LOG_INFO, e->e_id,
2319 			  "Warning: prog_open: program %s unsafe: %s",
2320 			  argv[0], sm_errstring(ret));
2321 
2322 	/* arrange for all the files to be closed */
2323 	sm_close_on_exec(STDERR_FILENO + 1, DtableSize);
2324 
2325 	/* now exec the process */
2326 	(void) execve(argv[0], (ARGV_T) argv, (ARGV_T) UserEnviron);
2327 
2328 	/* woops!  failed */
2329 	save_errno = errno;
2330 	syserr("%s: cannot exec", argv[0]);
2331 	if (transienterror(save_errno))
2332 		_exit(EX_OSERR);
2333 	_exit(EX_CONFIG);
2334 	return -1;	/* avoid compiler warning on IRIX */
2335 }
2336 
2337 /*
2338 **  GET_COLUMN -- look up a Column in a line buffer
2339 **
2340 **	Parameters:
2341 **		line -- the raw text line to search. [A]
2342 **		col -- the column number to fetch.
2343 **		delim -- the delimiter between columns.  If null,
2344 **			use white space.
2345 **		buf -- the output buffer.
2346 **		buflen -- the length of buf.
2347 **
2348 **	Returns:
2349 **		buf if successful.
2350 **		NULL otherwise.
2351 */
2352 
2353 char *
get_column(line,col,delim,buf,buflen)2354 get_column(line, col, delim, buf, buflen)
2355 	char line[];
2356 	int col;
2357 	int delim;
2358 	char buf[];
2359 	int buflen;
2360 {
2361 	char *p;
2362 	char *begin, *end;
2363 	int i;
2364 	char delimbuf[4];
2365 
2366 	if ((char) delim == '\0')
2367 		(void) sm_strlcpy(delimbuf, "\n\t ", sizeof(delimbuf));
2368 	else
2369 	{
2370 		delimbuf[0] = (char) delim;
2371 		delimbuf[1] = '\0';
2372 	}
2373 
2374 	p = line;
2375 	if (*p == '\0')
2376 		return NULL;			/* line empty */
2377 	if (*p == (char) delim && col == 0)
2378 		return NULL;			/* first column empty */
2379 
2380 	begin = line;
2381 
2382 	if (col == 0 && (char) delim == '\0')
2383 	{
2384 		while (*begin != '\0' && SM_ISSPACE(*begin))
2385 			begin++;
2386 	}
2387 
2388 	for (i = 0; i < col; i++)
2389 	{
2390 		if ((begin = strpbrk(begin, delimbuf)) == NULL)
2391 			return NULL;		/* no such column */
2392 		begin++;
2393 		if ((char) delim == '\0')
2394 		{
2395 			while (*begin != '\0' && SM_ISSPACE(*begin))
2396 				begin++;
2397 		}
2398 	}
2399 
2400 	end = strpbrk(begin, delimbuf);
2401 	if (end == NULL)
2402 		i = strlen(begin);
2403 	else
2404 		i = end - begin;
2405 	if (i >= buflen)
2406 		i = buflen - 1;
2407 	(void) sm_strlcpy(buf, begin, i + 1);
2408 	return buf;
2409 }
2410 
2411 /*
2412 **  CLEANSTRCPY -- copy string keeping out bogus characters
2413 **	XXX: This may be a problem for EAI?
2414 **
2415 **	Parameters:
2416 **		t -- "to" string.
2417 **		f -- "from" string. [A]
2418 **		l -- length of space available in "to" string.
2419 **
2420 **	Returns:
2421 **		none.
2422 */
2423 
2424 void
cleanstrcpy(t,f,l)2425 cleanstrcpy(t, f, l)
2426 	register char *t;
2427 	register char *f;
2428 	int l;
2429 {
2430 	/* check for newlines and log if necessary */
2431 	(void) denlstring(f, true, true);
2432 
2433 	if (l <= 0)
2434 		syserr("!cleanstrcpy: length == 0");
2435 
2436 	l--;
2437 	while (l > 0 && *f != '\0')
2438 	{
2439 		if (isascii(*f) &&
2440 		    (isalnum(*f) || strchr("!#$%&'*+-./^_`{|}~", *f) != NULL))
2441 		{
2442 			l--;
2443 			*t++ = *f;
2444 		}
2445 		f++;
2446 	}
2447 	*t = '\0';
2448 }
2449 
2450 /*
2451 **  DENLSTRING -- convert newlines in a string to spaces
2452 **
2453 **	Parameters:
2454 **		s -- the input string [A]
2455 **		strict -- if set, don't permit continuation lines.
2456 **		logattacks -- if set, log attempted attacks.
2457 **
2458 **	Returns:
2459 **		A pointer to a version of the string with newlines
2460 **		mapped to spaces.  This should be copied.
2461 */
2462 
2463 char *
denlstring(s,strict,logattacks)2464 denlstring(s, strict, logattacks)
2465 	char *s;
2466 	bool strict;
2467 	bool logattacks;
2468 {
2469 	register char *p;
2470 	int l;
2471 	static char *bp = NULL;
2472 	static int bl = 0;
2473 
2474 	p = s;
2475 	while ((p = strchr(p, '\n')) != NULL)
2476 		if (strict || (*++p != ' ' && *p != '\t'))
2477 			break;
2478 	if (p == NULL)
2479 		return s;
2480 
2481 	l = strlen(s) + 1;
2482 	if (bl < l)
2483 	{
2484 		/* allocate more space */
2485 		char *nbp = sm_pmalloc_x(l);
2486 
2487 		if (bp != NULL)
2488 			sm_free(bp);
2489 		bp = nbp;
2490 		bl = l;
2491 	}
2492 	(void) sm_strlcpy(bp, s, l);
2493 	for (p = bp; (p = strchr(p, '\n')) != NULL; )
2494 		*p++ = ' ';
2495 
2496 	if (logattacks)
2497 	{
2498 		sm_syslog(LOG_NOTICE, CurEnv ? CurEnv->e_id : NULL,
2499 			  "POSSIBLE ATTACK from %.100s: newline in string \"%s\"",
2500 			  RealHostName == NULL ? "[UNKNOWN]" : RealHostName,
2501 			  shortenstring(bp, MAXSHORTSTR));
2502 	}
2503 
2504 	return bp;
2505 }
2506 
2507 /*
2508 **  STRREPLNONPRT -- replace "unprintable" characters in a string with subst
2509 **
2510 **	Parameters:
2511 **		s -- string to manipulate (in place) [A]
2512 **		c -- character to use as replacement
2513 **
2514 **	Returns:
2515 **		true iff string did not contain "unprintable" characters
2516 */
2517 
2518 bool
strreplnonprt(s,c)2519 strreplnonprt(s, c)
2520 	char *s;
2521 	int c;
2522 {
2523 	bool ok;
2524 
2525 	ok = true;
2526 	if (s == NULL)
2527 		return ok;
2528 	while (*s != '\0')
2529 	{
2530 		if (!(isascii(*s) && isprint(*s)))
2531 		{
2532 			*s = c;
2533 			ok = false;
2534 		}
2535 		++s;
2536 	}
2537 	return ok;
2538 }
2539 
2540 /*
2541 **  PATH_IS_DIR -- check to see if file exists and is a directory.
2542 **
2543 **	There are some additional checks for security violations in
2544 **	here.  This routine is intended to be used for the host status
2545 **	support.
2546 **
2547 **	Parameters:
2548 **		pathname -- pathname to check for directory-ness. [x]
2549 **		createflag -- if set, create directory if needed.
2550 **
2551 **	Returns:
2552 **		true -- if the indicated pathname is a directory
2553 **		false -- otherwise
2554 */
2555 
2556 bool
path_is_dir(pathname,createflag)2557 path_is_dir(pathname, createflag)
2558 	char *pathname;
2559 	bool createflag;
2560 {
2561 	struct stat statbuf;
2562 
2563 #if HASLSTAT
2564 	if (lstat(pathname, &statbuf) < 0)
2565 #else
2566 	if (stat(pathname, &statbuf) < 0)
2567 #endif
2568 	{
2569 		if (errno != ENOENT || !createflag)
2570 			return false;
2571 		if (mkdir(pathname, 0755) < 0)
2572 			return false;
2573 		return true;
2574 	}
2575 	if (!S_ISDIR(statbuf.st_mode))
2576 	{
2577 		errno = ENOTDIR;
2578 		return false;
2579 	}
2580 
2581 	/* security: don't allow writable directories */
2582 	if (bitset(S_IWGRP|S_IWOTH, statbuf.st_mode))
2583 	{
2584 		errno = EACCES;
2585 		return false;
2586 	}
2587 	return true;
2588 }
2589 
2590 /*
2591 **  PROC_LIST_ADD -- add process id to list of our children
2592 **
2593 **	Parameters:
2594 **		pid -- pid to add to list.
2595 **		task -- task of pid.
2596 **		type -- type of process.
2597 **		count -- number of processes.
2598 **		other -- other information for this type.
2599 **
2600 **	Returns:
2601 **		none
2602 **
2603 **	Side Effects:
2604 **		May increase CurChildren. May grow ProcList.
2605 */
2606 
2607 typedef struct procs	PROCS_T;
2608 
2609 struct procs
2610 {
2611 	pid_t		proc_pid;
2612 	char		*proc_task;
2613 	int		proc_type;
2614 	int		proc_count;
2615 	int		proc_other;
2616 	SOCKADDR	proc_hostaddr;
2617 };
2618 
2619 static PROCS_T	*volatile ProcListVec = NULL;
2620 static int	ProcListSize = 0;
2621 
2622 void
proc_list_add(pid,task,type,count,other,hostaddr)2623 proc_list_add(pid, task, type, count, other, hostaddr)
2624 	pid_t pid;
2625 	char *task;
2626 	int type;
2627 	int count;
2628 	int other;
2629 	SOCKADDR *hostaddr;
2630 {
2631 	int i;
2632 
2633 	for (i = 0; i < ProcListSize; i++)
2634 	{
2635 		if (ProcListVec[i].proc_pid == NO_PID)
2636 			break;
2637 	}
2638 	if (i >= ProcListSize)
2639 	{
2640 		/* probe the existing vector to avoid growing infinitely */
2641 		proc_list_probe();
2642 
2643 		/* now scan again */
2644 		for (i = 0; i < ProcListSize; i++)
2645 		{
2646 			if (ProcListVec[i].proc_pid == NO_PID)
2647 				break;
2648 		}
2649 	}
2650 	if (i >= ProcListSize)
2651 	{
2652 		/* grow process list */
2653 		int chldwasblocked;
2654 		PROCS_T *npv;
2655 
2656 		SM_ASSERT(ProcListSize < INT_MAX - PROC_LIST_SEG);
2657 		npv = (PROCS_T *) sm_pmalloc_x((sizeof(*npv)) *
2658 					       (ProcListSize + PROC_LIST_SEG));
2659 
2660 		/* Block SIGCHLD so reapchild() doesn't mess with us */
2661 		chldwasblocked = sm_blocksignal(SIGCHLD);
2662 		if (ProcListSize > 0)
2663 		{
2664 			memmove(npv, ProcListVec,
2665 				ProcListSize * sizeof(PROCS_T));
2666 			sm_free(ProcListVec);
2667 		}
2668 
2669 		/* XXX just use memset() to initialize this part? */
2670 		for (i = ProcListSize; i < ProcListSize + PROC_LIST_SEG; i++)
2671 		{
2672 			npv[i].proc_pid = NO_PID;
2673 			npv[i].proc_task = NULL;
2674 			npv[i].proc_type = PROC_NONE;
2675 		}
2676 		i = ProcListSize;
2677 		ProcListSize += PROC_LIST_SEG;
2678 		ProcListVec = npv;
2679 		if (chldwasblocked == 0)
2680 			(void) sm_releasesignal(SIGCHLD);
2681 	}
2682 	ProcListVec[i].proc_pid = pid;
2683 	PSTRSET(ProcListVec[i].proc_task, task);
2684 	ProcListVec[i].proc_type = type;
2685 	ProcListVec[i].proc_count = count;
2686 	ProcListVec[i].proc_other = other;
2687 	if (hostaddr != NULL)
2688 		ProcListVec[i].proc_hostaddr = *hostaddr;
2689 	else
2690 		memset(&ProcListVec[i].proc_hostaddr, 0,
2691 			sizeof(ProcListVec[i].proc_hostaddr));
2692 
2693 	/* if process adding itself, it's not a child */
2694 	if (pid != CurrentPid)
2695 	{
2696 		SM_ASSERT(CurChildren < INT_MAX);
2697 		CurChildren++;
2698 	}
2699 }
2700 
2701 /*
2702 **  PROC_LIST_SET -- set pid task in process list
2703 **
2704 **	Parameters:
2705 **		pid -- pid to set
2706 **		task -- task of pid
2707 **
2708 **	Returns:
2709 **		none.
2710 */
2711 
2712 void
proc_list_set(pid,task)2713 proc_list_set(pid, task)
2714 	pid_t pid;
2715 	char *task;
2716 {
2717 	int i;
2718 
2719 	for (i = 0; i < ProcListSize; i++)
2720 	{
2721 		if (ProcListVec[i].proc_pid == pid)
2722 		{
2723 			PSTRSET(ProcListVec[i].proc_task, task);
2724 			break;
2725 		}
2726 	}
2727 }
2728 
2729 /*
2730 **  PROC_LIST_DROP -- drop pid from process list
2731 **
2732 **	Parameters:
2733 **		pid -- pid to drop
2734 **		st -- process status
2735 **		other -- storage for proc_other (return).
2736 **
2737 **	Returns:
2738 **		none.
2739 **
2740 **	Side Effects:
2741 **		May decrease CurChildren, CurRunners, or
2742 **		set RestartRequest or ShutdownRequest.
2743 **
2744 **	NOTE:	THIS CAN BE CALLED FROM A SIGNAL HANDLER.  DO NOT ADD
2745 **		ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
2746 **		DOING.
2747 */
2748 
2749 void
proc_list_drop(pid,st,other)2750 proc_list_drop(pid, st, other)
2751 	pid_t pid;
2752 	int st;
2753 	int *other;
2754 {
2755 	int i;
2756 	int type = PROC_NONE;
2757 
2758 	for (i = 0; i < ProcListSize; i++)
2759 	{
2760 		if (ProcListVec[i].proc_pid == pid)
2761 		{
2762 			ProcListVec[i].proc_pid = NO_PID;
2763 			type = ProcListVec[i].proc_type;
2764 			if (other != NULL)
2765 				*other = ProcListVec[i].proc_other;
2766 			if (CurChildren > 0)
2767 				CurChildren--;
2768 			break;
2769 		}
2770 	}
2771 
2772 	if (type == PROC_CONTROL && WIFEXITED(st))
2773 	{
2774 		/* if so, see if we need to restart or shutdown */
2775 		if (WEXITSTATUS(st) == EX_RESTART)
2776 			RestartRequest = "control socket";
2777 		else if (WEXITSTATUS(st) == EX_SHUTDOWN)
2778 			ShutdownRequest = "control socket";
2779 	}
2780 	else if (type == PROC_QUEUE_CHILD && !WIFSTOPPED(st) &&
2781 		 ProcListVec[i].proc_other > -1)
2782 	{
2783 		/* restart this persistent runner */
2784 		mark_work_group_restart(ProcListVec[i].proc_other, st);
2785 	}
2786 	else if (type == PROC_QUEUE)
2787 	{
2788 		CurRunners -= ProcListVec[i].proc_count;
2789 
2790 		/* CHK_CUR_RUNNERS() can't be used here: uses syslog() */
2791 		if (CurRunners < 0)
2792 			CurRunners = 0;
2793 	}
2794 }
2795 
2796 /*
2797 **  PROC_LIST_CLEAR -- clear the process list
2798 **
2799 **	Parameters:
2800 **		none.
2801 **
2802 **	Returns:
2803 **		none.
2804 **
2805 **	Side Effects:
2806 **		Sets CurChildren to zero.
2807 */
2808 
2809 void
proc_list_clear()2810 proc_list_clear()
2811 {
2812 	int i;
2813 
2814 	/* start from 1 since 0 is the daemon itself */
2815 	for (i = 1; i < ProcListSize; i++)
2816 		ProcListVec[i].proc_pid = NO_PID;
2817 	CurChildren = 0;
2818 }
2819 
2820 /*
2821 **  PROC_LIST_PROBE -- probe processes in the list to see if they still exist
2822 **
2823 **	Parameters:
2824 **		none
2825 **
2826 **	Returns:
2827 **		none
2828 **
2829 **	Side Effects:
2830 **		May decrease CurChildren.
2831 */
2832 
2833 void
proc_list_probe()2834 proc_list_probe()
2835 {
2836 	int i, children;
2837 	int chldwasblocked;
2838 	pid_t pid;
2839 
2840 	children = 0;
2841 	chldwasblocked = sm_blocksignal(SIGCHLD);
2842 
2843 	/* start from 1 since 0 is the daemon itself */
2844 	for (i = 1; i < ProcListSize; i++)
2845 	{
2846 		pid = ProcListVec[i].proc_pid;
2847 		if (pid == NO_PID || pid == CurrentPid)
2848 			continue;
2849 		if (kill(pid, 0) < 0)
2850 		{
2851 			if (LogLevel > 3)
2852 				sm_syslog(LOG_DEBUG, CurEnv->e_id,
2853 					  "proc_list_probe: lost pid %d",
2854 					  (int) ProcListVec[i].proc_pid);
2855 			ProcListVec[i].proc_pid = NO_PID;
2856 			SM_FREE(ProcListVec[i].proc_task);
2857 
2858 			if (ProcListVec[i].proc_type == PROC_QUEUE)
2859 			{
2860 				CurRunners -= ProcListVec[i].proc_count;
2861 				CHK_CUR_RUNNERS("proc_list_probe", i,
2862 						ProcListVec[i].proc_count);
2863 			}
2864 
2865 			CurChildren--;
2866 		}
2867 		else
2868 		{
2869 			++children;
2870 		}
2871 	}
2872 	if (CurChildren < 0)
2873 		CurChildren = 0;
2874 	if (chldwasblocked == 0)
2875 		(void) sm_releasesignal(SIGCHLD);
2876 	if (LogLevel > 10 && children != CurChildren && CurrentPid == DaemonPid)
2877 	{
2878 		sm_syslog(LOG_ERR, NOQID,
2879 			  "proc_list_probe: found %d children, expected %d",
2880 			  children, CurChildren);
2881 	}
2882 }
2883 
2884 /*
2885 **  PROC_LIST_DISPLAY -- display the process list
2886 **
2887 **	Parameters:
2888 **		out -- output file pointer
2889 **		prefix -- string to output in front of each line.
2890 **
2891 **	Returns:
2892 **		none.
2893 */
2894 
2895 void
proc_list_display(out,prefix)2896 proc_list_display(out, prefix)
2897 	SM_FILE_T *out;
2898 	char *prefix;
2899 {
2900 	int i;
2901 
2902 	for (i = 0; i < ProcListSize; i++)
2903 	{
2904 		if (ProcListVec[i].proc_pid == NO_PID)
2905 			continue;
2906 
2907 		(void) sm_io_fprintf(out, SM_TIME_DEFAULT, "%s%d %s%s\n",
2908 				     prefix,
2909 				     (int) ProcListVec[i].proc_pid,
2910 				     ProcListVec[i].proc_task != NULL ?
2911 				     ProcListVec[i].proc_task : "(unknown)",
2912 				     (OpMode == MD_SMTP ||
2913 				      OpMode == MD_DAEMON ||
2914 				      OpMode == MD_ARPAFTP) ? "\r" : "");
2915 	}
2916 }
2917 
2918 /*
2919 **  PROC_LIST_SIGNAL -- send a signal to a type of process in the list
2920 **
2921 **	Parameters:
2922 **		type -- type of process to signal
2923 **		signal -- the type of signal to send
2924 **
2925 **	Returns:
2926 **		none.
2927 **
2928 **	NOTE:	THIS CAN BE CALLED FROM A SIGNAL HANDLER.  DO NOT ADD
2929 **		ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
2930 **		DOING.
2931 */
2932 
2933 void
proc_list_signal(type,signal)2934 proc_list_signal(type, signal)
2935 	int type;
2936 	int signal;
2937 {
2938 	int chldwasblocked;
2939 	int alrmwasblocked;
2940 	int i;
2941 	pid_t mypid = getpid();
2942 
2943 	/* block these signals so that we may signal cleanly */
2944 	chldwasblocked = sm_blocksignal(SIGCHLD);
2945 	alrmwasblocked = sm_blocksignal(SIGALRM);
2946 
2947 	/* Find all processes of type and send signal */
2948 	for (i = 0; i < ProcListSize; i++)
2949 	{
2950 		if (ProcListVec[i].proc_pid == NO_PID ||
2951 		    ProcListVec[i].proc_pid == mypid)
2952 			continue;
2953 		if (ProcListVec[i].proc_type != type)
2954 			continue;
2955 		(void) kill(ProcListVec[i].proc_pid, signal);
2956 	}
2957 
2958 	/* restore the signals */
2959 	if (alrmwasblocked == 0)
2960 		(void) sm_releasesignal(SIGALRM);
2961 	if (chldwasblocked == 0)
2962 		(void) sm_releasesignal(SIGCHLD);
2963 }
2964 
2965 /*
2966 **  COUNT_OPEN_CONNECTIONS
2967 **
2968 **	Parameters:
2969 **		hostaddr - ClientAddress
2970 **
2971 **	Returns:
2972 **		the number of open connections for this client
2973 **
2974 */
2975 
2976 int
count_open_connections(hostaddr)2977 count_open_connections(hostaddr)
2978 	SOCKADDR *hostaddr;
2979 {
2980 	int i, n;
2981 
2982 	if (hostaddr == NULL)
2983 		return 0;
2984 
2985 	/*
2986 	**  This code gets called before proc_list_add() gets called,
2987 	**  so we (the daemon child for this connection) have not yet
2988 	**  counted ourselves.  Hence initialize the counter to 1
2989 	**  instead of 0 to compensate.
2990 	*/
2991 
2992 	n = 1;
2993 	for (i = 0; i < ProcListSize; i++)
2994 	{
2995 		if (ProcListVec[i].proc_pid == NO_PID)
2996 			continue;
2997 		if (hostaddr->sa.sa_family !=
2998 		    ProcListVec[i].proc_hostaddr.sa.sa_family)
2999 			continue;
3000 #if NETINET
3001 		if (hostaddr->sa.sa_family == AF_INET &&
3002 		    (hostaddr->sin.sin_addr.s_addr ==
3003 		     ProcListVec[i].proc_hostaddr.sin.sin_addr.s_addr))
3004 			n++;
3005 #endif
3006 #if NETINET6
3007 		if (hostaddr->sa.sa_family == AF_INET6 &&
3008 		    IN6_ARE_ADDR_EQUAL(&(hostaddr->sin6.sin6_addr),
3009 				       &(ProcListVec[i].proc_hostaddr.sin6.sin6_addr)))
3010 			n++;
3011 #endif
3012 	}
3013 	return n;
3014 }
3015 
3016 #if _FFR_XCNCT
3017 /*
3018 **  XCONNECT -- get X-CONNECT info
3019 **
3020 **	Parameters:
3021 **		inchannel -- FILE to check
3022 **
3023 **	Returns:
3024 **		>0 if X-CONNECT/PROXY was used successfully (D_XCNCT*)
3025 **		0 if X-CONNECT/PROXY was not given
3026 **		-1 on error
3027 **		-2 PROXY UNKNOWN
3028 */
3029 
3030 /*
3031 **  HA proxy version 1:
3032 **
3033 **  PROXY TCP[4|6] IPv[4|6]-src-addr IPv[4|6]-dst-addr src-port dst-port\r\n
3034 **  PROXY UNKNOWN ...
3035 **  examples:
3036 **	"PROXY TCP4 255.255.255.255 255.255.255.255 65535 65535\r\n"
3037 **	"PROXY TCP6 ffff:f...f:ffff ffff:f...f:ffff 65535 65535\r\n"
3038 **	"PROXY UNKNOWN\r\n"
3039 */
3040 
3041 int
xconnect(inchannel)3042 xconnect(inchannel)
3043 	SM_FILE_T *inchannel;
3044 {
3045 	int r, i;
3046 	char *p, *b, delim, inp[MAXINPLINE];
3047 	SOCKADDR addr;
3048 	char **pvp;
3049 	char pvpbuf[PSBUFSIZE];
3050 	char *peerhostname;	/* name of SMTP peer or "localhost" */
3051 	extern ENVELOPE BlankEnvelope;
3052 #if _FFR_HAPROXY
3053 	int haproxy = AF_UNSPEC;
3054 # define HAPROXY "PROXY "
3055 # define HAPROXYLEN (sizeof(HAPROXY) - 1)
3056 #endif
3057 
3058 #define XCONNECT "X-CONNECT "
3059 #define XCNNCTLEN (sizeof(XCONNECT) - 1)
3060 
3061 	/* Ask the ruleset whether to use x-connect */
3062 	pvp = NULL;
3063 	peerhostname = RealHostName;
3064 	if (peerhostname == NULL)
3065 		peerhostname = "localhost";
3066 	r = rscap("x_connect", peerhostname,
3067 		  anynet_ntoa(&RealHostAddr), &BlankEnvelope,
3068 		  &pvp, pvpbuf, sizeof(pvpbuf));
3069 	if (tTd(75, 8))
3070 		sm_syslog(LOG_INFO, NOQID, "x-connect: rscap=%d", r);
3071 	if (r == EX_UNAVAILABLE)
3072 		return 0;
3073 	if (r != EX_OK)
3074 	{
3075 		/* ruleset error */
3076 		sm_syslog(LOG_INFO, NOQID, "x-connect: rscap=%d", r);
3077 		return 0;
3078 	}
3079 	if (pvp != NULL && pvp[0] != NULL && (pvp[0][0] & 0377) == CANONNET)
3080 	{
3081 		/* $#: no x-connect */
3082 		if (tTd(75, 7))
3083 			sm_syslog(LOG_INFO, NOQID, "x-connect: nope");
3084 		return 0;
3085 	}
3086 
3087 #if _FFR_HAPROXY
3088 	if (pvp != NULL && pvp[0] != NULL && strcasecmp(pvp[0], "haproxy1") == 0)
3089 	{
3090 		haproxy = AF_LOCAL;
3091 	}
3092 #endif
3093 
3094 # if _FFR_XCNCT > 1
3095 	if (pvp != NULL && pvp[0] != NULL &&
3096 	    pvp[0][0] == '2' && pvp[0][1] == '2' && pvp[0][2] == '0')
3097 	{
3098 		char *hostname;			/* my hostname ($j) */
3099 
3100 		hostname = macvalue('j', &BlankEnvelope);
3101 		if (tTd(75, 7))
3102 			sm_syslog(LOG_INFO, NOQID, "x-connect=%s", pvp[0]);
3103 		message("220-%s %s", hostname != NULL ? hostname : "xconnect",
3104 			pvp[1] != NULL ? pvp[1] : "waiting for xconnect");
3105 		sm_io_flush(OutChannel, SM_TIME_DEFAULT);
3106 	}
3107 # endif
3108 
3109 	p = sfgets(inp, sizeof(inp), InChannel, TimeOuts.to_nextcommand, "pre");
3110 	if (tTd(75, 6))
3111 		sm_syslog(LOG_INFO, NOQID, "x-connect: input=%s", p);
3112 #if _FFR_HAPROXY
3113 	if (AF_UNSPEC != haproxy)
3114 	{
3115 		if (p == NULL || strncasecmp(p, HAPROXY, HAPROXYLEN) != 0)
3116 			return -1;
3117 		p += HAPROXYLEN;
3118 # define HAPUNKNOWN "UNKNOWN"
3119 # define HAPUNKNOWNLEN (sizeof(HAPUNKNOWN) - 1)
3120 		if (strncasecmp(p, HAPUNKNOWN, HAPUNKNOWNLEN) == 0)
3121 		{
3122 			/* how to handle this? */
3123 			sm_syslog(LOG_INFO, NOQID, "haproxy: input=%s, status=ignored", p);
3124 			return -2;
3125 		}
3126 # define HAPTCP4 "TCP4 "
3127 # define HAPTCP6 "TCP6 "
3128 # define HAPTCPNLEN (sizeof(HAPTCP4) - 1)
3129 		if (strncasecmp(p, HAPTCP4, HAPTCPNLEN) == 0)
3130 			haproxy = AF_INET;
3131 # if NETINET6
3132 		if (strncasecmp(p, HAPTCP6, HAPTCPNLEN) == 0)
3133 			haproxy = AF_INET6;
3134 # endif
3135 		if (AF_LOCAL != haproxy)
3136 		{
3137 			p += HAPTCPNLEN;
3138 			goto getip;
3139 		}
3140 		return -1;
3141 	}
3142 #endif
3143 	if (p == NULL || strncasecmp(p, XCONNECT, XCNNCTLEN) != 0)
3144 		return -1;
3145 	p += XCNNCTLEN;
3146 	while (SM_ISSPACE(*p))
3147 		p++;
3148 
3149 #if _FFR_HAPROXY
3150   getip:
3151 #endif
3152 	/* parameters: IPAddress [Hostname[ M]] */
3153 	b = p;
3154 	while (*p != '\0' && isascii(*p) &&
3155 	       (isalnum(*p) || *p == '.' || *p== ':'))
3156 		p++;
3157 	delim = *p;
3158 	*p = '\0';
3159 
3160 	memset(&addr, '\0', sizeof(addr));
3161 	addr.sin.sin_addr.s_addr = inet_addr(b);
3162 	if (addr.sin.sin_addr.s_addr != INADDR_NONE)
3163 	{
3164 		addr.sa.sa_family = AF_INET;
3165 		memcpy(&RealHostAddr, &addr, sizeof(addr));
3166 		if (tTd(75, 2))
3167 			sm_syslog(LOG_INFO, NOQID, "x-connect: addr=%s",
3168 				anynet_ntoa(&RealHostAddr));
3169 	}
3170 # if NETINET6
3171 	else if ((r = inet_pton(AF_INET6, b, &addr.sin6.sin6_addr)) == 1)
3172 	{
3173 		addr.sa.sa_family = AF_INET6;
3174 		memcpy(&RealHostAddr, &addr, sizeof(addr));
3175 	}
3176 # endif
3177 	else
3178 		return -1;
3179 #if _FFR_HAPROXY
3180 	if (AF_UNSPEC != haproxy)
3181 	{
3182 		/*
3183 		**  dst-addr and dst-port are ignored because
3184 		**  they are not really worth to check:
3185 		**  IPv[4|6]-dst-addr: must be one of "our" addresses,
3186 		**  dst-port: must be the DaemonPort.
3187 		**  We also do not check whether
3188 		**  haproxy == addr.sa.sa_family
3189 		*/
3190 
3191 		if (' ' == delim)
3192 		{
3193 			b = ++p;
3194 			while (*p != '\0' && !SM_ISSPACE(*p))
3195 				++p;
3196 			if (*p != '\0' && SM_ISSPACE(*p))
3197 				++p;
3198 			if (*p != '\0')
3199 			{
3200 				unsigned short port;
3201 
3202 				port = htons(atoi(p));
3203 				if (AF_INET == haproxy)
3204 					RealHostAddr.sin.sin_port = port;
3205 #  if NETINET6
3206 				if (AF_INET6 == haproxy)
3207 					RealHostAddr.sin6.sin6_port = port;
3208 #  endif
3209 			}
3210 		}
3211 		SM_FREE(RealHostName);
3212 		return D_XCNCT;
3213 	}
3214 #endif
3215 
3216 	/* more parameters? */
3217 	if (delim != ' ')
3218 		return D_XCNCT;
3219 
3220 	for (b = ++p, i = 0;
3221 	     *p != '\0' && isascii(*p) && (isalnum(*p) || *p == '.' || *p == '-');
3222 	     p++, i++)
3223 		;
3224 	if (i == 0)
3225 		return D_XCNCT;
3226 	delim = *p;
3227 	if (i > MAXNAME)	/* EAI:ok */
3228 		b[MAXNAME] = '\0';	/* EAI:ok */
3229 	else
3230 		b[i] = '\0';
3231 	SM_FREE(RealHostName);
3232 	RealHostName = newstr(b);
3233 	if (tTd(75, 2))
3234 		sm_syslog(LOG_INFO, NOQID, "x-connect: host=%s", b);
3235 	*p = delim;
3236 
3237 	b = p;
3238 	if (*p != ' ')
3239 		return D_XCNCT;
3240 
3241 	while (*p != '\0' && SM_ISSPACE(*p))
3242 		p++;
3243 
3244 	if (tTd(75, 4))
3245 	{
3246 		char *e;
3247 
3248 		e = strpbrk(p, "\r\n");
3249 		if (e != NULL)
3250 			*e = '\0';
3251 		sm_syslog(LOG_INFO, NOQID, "x-connect: rest=%s", p);
3252 	}
3253 	if (*p == 'M')
3254 		return D_XCNCT_M;
3255 
3256 	return D_XCNCT;
3257 }
3258 #endif /* _FFR_XCNCT */
3259