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