138032Speter/*
2223067Sgshapiro * Copyright (c) 1998-2005, 2010 Sendmail, Inc. and its suppliers.
364565Sgshapiro *	All rights reserved.
438032Speter * Copyright (c) 1995-1997 Eric P. Allman.  All rights reserved.
538032Speter * Copyright (c) 1988, 1993
638032Speter *	The Regents of the University of California.  All rights reserved.
738032Speter *
838032Speter * By using this file, you agree to the terms and conditions set
938032Speter * forth in the LICENSE file which can be found at the top level of
1038032Speter * the sendmail distribution.
1138032Speter *
1238032Speter */
1338032Speter
1464565Sgshapiro#include <sendmail.h>
1564565Sgshapiro
16249729SgshapiroSM_RCSID("@(#)$Id: mci.c,v 8.224 2013/03/12 15:24:53 ca Exp $")
1764565Sgshapiro
1864565Sgshapiro#if NETINET || NETINET6
1964565Sgshapiro# include <arpa/inet.h>
2064565Sgshapiro#endif /* NETINET || NETINET6 */
2164565Sgshapiro
2238032Speter#include <dirent.h>
2338032Speter
2464565Sgshapirostatic int	mci_generate_persistent_path __P((const char *, char *,
2564565Sgshapiro						  int, bool));
2664565Sgshapirostatic bool	mci_load_persistent __P((MCI *));
2764565Sgshapirostatic void	mci_uncache __P((MCI **, bool));
2864565Sgshapirostatic int	mci_lock_host_statfile __P((MCI *));
2990795Sgshapirostatic int	mci_read_persistent __P((SM_FILE_T *, MCI *));
3064565Sgshapiro
3138032Speter/*
3238032Speter**  Mail Connection Information (MCI) Caching Module.
3338032Speter**
3438032Speter**	There are actually two separate things cached.  The first is
3538032Speter**	the set of all open connections -- these are stored in a
3638032Speter**	(small) list.  The second is stored in the symbol table; it
3738032Speter**	has the overall status for all hosts, whether or not there
3838032Speter**	is a connection open currently.
3938032Speter**
4038032Speter**	There should never be too many connections open (since this
4138032Speter**	could flood the socket table), nor should a connection be
4238032Speter**	allowed to sit idly for too long.
4338032Speter**
4438032Speter**	MaxMciCache is the maximum number of open connections that
4538032Speter**	will be supported.
4638032Speter**
4738032Speter**	MciCacheTimeout is the time (in seconds) that a connection
4838032Speter**	is permitted to survive without activity.
4938032Speter**
50157006Sgshapiro**	We actually try any cached connections by sending a RSET
51157006Sgshapiro**	before we use them; if the RSET fails we close down the
52157006Sgshapiro**	connection and reopen it (see smtpprobe()).
5338032Speter**
5438032Speter**	The persistent MCI code is donated by Mark Lovell and Paul
5538032Speter**	Vixie.  It is based on the long term host status code in KJS
5638032Speter**	written by Paul but has been adapted by Mark to fit into the
5738032Speter**	MCI structure.
5838032Speter*/
5938032Speter
6064565Sgshapirostatic MCI	**MciCache;		/* the open connection cache */
6138032Speter
6290795Sgshapiro/*
6338032Speter**  MCI_CACHE -- enter a connection structure into the open connection cache
6438032Speter**
6538032Speter**	This may cause something else to be flushed.
6638032Speter**
6738032Speter**	Parameters:
6838032Speter**		mci -- the connection to cache.
6938032Speter**
7038032Speter**	Returns:
7138032Speter**		none.
7238032Speter*/
7338032Speter
7438032Spetervoid
7538032Spetermci_cache(mci)
7638032Speter	register MCI *mci;
7738032Speter{
7838032Speter	register MCI **mcislot;
7938032Speter
8038032Speter	/*
8138032Speter	**  Find the best slot.  This may cause expired connections
8238032Speter	**  to be closed.
8338032Speter	*/
8438032Speter
8538032Speter	mcislot = mci_scan(mci);
8638032Speter	if (mcislot == NULL)
8738032Speter	{
8838032Speter		/* we don't support caching */
8938032Speter		return;
9038032Speter	}
9138032Speter
9238032Speter	if (mci->mci_host == NULL)
9338032Speter		return;
9438032Speter
9538032Speter	/* if this is already cached, we are done */
9638032Speter	if (bitset(MCIF_CACHED, mci->mci_flags))
9738032Speter		return;
9838032Speter
9938032Speter	/* otherwise we may have to clear the slot */
10038032Speter	if (*mcislot != NULL)
10190795Sgshapiro		mci_uncache(mcislot, true);
10238032Speter
10338032Speter	if (tTd(42, 5))
10490795Sgshapiro		sm_dprintf("mci_cache: caching %p (%s) in slot %d\n",
10590795Sgshapiro			   mci, mci->mci_host, (int) (mcislot - MciCache));
10638032Speter	if (tTd(91, 100))
10738032Speter		sm_syslog(LOG_DEBUG, CurEnv->e_id,
10864565Sgshapiro			  "mci_cache: caching %lx (%.100s) in slot %d",
10990795Sgshapiro			  (unsigned long) mci, mci->mci_host,
11090795Sgshapiro			  (int) (mcislot - MciCache));
11138032Speter
11238032Speter	*mcislot = mci;
11338032Speter	mci->mci_flags |= MCIF_CACHED;
11438032Speter}
11590795Sgshapiro/*
11638032Speter**  MCI_SCAN -- scan the cache, flush junk, and return best slot
11738032Speter**
11838032Speter**	Parameters:
11938032Speter**		savemci -- never flush this one.  Can be null.
12038032Speter**
12138032Speter**	Returns:
12238032Speter**		The LRU (or empty) slot.
12338032Speter*/
12438032Speter
12538032SpeterMCI **
12638032Spetermci_scan(savemci)
12738032Speter	MCI *savemci;
12838032Speter{
12938032Speter	time_t now;
13038032Speter	register MCI **bestmci;
13138032Speter	register MCI *mci;
13238032Speter	register int i;
13338032Speter
13438032Speter	if (MaxMciCache <= 0)
13538032Speter	{
13638032Speter		/* we don't support caching */
13738032Speter		return NULL;
13838032Speter	}
13938032Speter
14038032Speter	if (MciCache == NULL)
14138032Speter	{
14238032Speter		/* first call */
143168520Sgshapiro		MciCache = (MCI **) sm_pmalloc_x(MaxMciCache * sizeof(*MciCache));
144168520Sgshapiro		memset((char *) MciCache, '\0', MaxMciCache * sizeof(*MciCache));
14564565Sgshapiro		return &MciCache[0];
14638032Speter	}
14738032Speter
14838032Speter	now = curtime();
14938032Speter	bestmci = &MciCache[0];
15038032Speter	for (i = 0; i < MaxMciCache; i++)
15138032Speter	{
15238032Speter		mci = MciCache[i];
15338032Speter		if (mci == NULL || mci->mci_state == MCIS_CLOSED)
15438032Speter		{
15538032Speter			bestmci = &MciCache[i];
15638032Speter			continue;
15738032Speter		}
15894337Sgshapiro		if ((mci->mci_lastuse + MciCacheTimeout <= now ||
15964565Sgshapiro		     (mci->mci_mailer != NULL &&
16064565Sgshapiro		      mci->mci_mailer->m_maxdeliveries > 0 &&
16164565Sgshapiro		      mci->mci_deliveries + 1 >= mci->mci_mailer->m_maxdeliveries))&&
16264565Sgshapiro		    mci != savemci)
16338032Speter		{
16464565Sgshapiro			/* connection idle too long or too many deliveries */
16538032Speter			bestmci = &MciCache[i];
16664565Sgshapiro
16764565Sgshapiro			/* close it */
16890795Sgshapiro			mci_uncache(bestmci, true);
16938032Speter			continue;
17038032Speter		}
17138032Speter		if (*bestmci == NULL)
17238032Speter			continue;
17338032Speter		if (mci->mci_lastuse < (*bestmci)->mci_lastuse)
17438032Speter			bestmci = &MciCache[i];
17538032Speter	}
17638032Speter	return bestmci;
17738032Speter}
17890795Sgshapiro/*
17938032Speter**  MCI_UNCACHE -- remove a connection from a slot.
18038032Speter**
18138032Speter**	May close a connection.
18238032Speter**
18338032Speter**	Parameters:
18438032Speter**		mcislot -- the slot to empty.
18590795Sgshapiro**		doquit -- if true, send QUIT protocol on this connection.
18690795Sgshapiro**			  if false, we are assumed to be in a forked child;
18738032Speter**				all we want to do is close the file(s).
18838032Speter**
18938032Speter**	Returns:
19038032Speter**		none.
19138032Speter*/
19238032Speter
19364565Sgshapirostatic void
19438032Spetermci_uncache(mcislot, doquit)
19538032Speter	register MCI **mcislot;
19638032Speter	bool doquit;
19738032Speter{
19838032Speter	register MCI *mci;
19938032Speter	extern ENVELOPE BlankEnvelope;
20038032Speter
20138032Speter	mci = *mcislot;
20238032Speter	if (mci == NULL)
20338032Speter		return;
20438032Speter	*mcislot = NULL;
20538032Speter	if (mci->mci_host == NULL)
20638032Speter		return;
20738032Speter
20838032Speter	mci_unlock_host(mci);
20938032Speter
21038032Speter	if (tTd(42, 5))
21190795Sgshapiro		sm_dprintf("mci_uncache: uncaching %p (%s) from slot %d (%d)\n",
21290795Sgshapiro			   mci, mci->mci_host, (int) (mcislot - MciCache),
21390795Sgshapiro			   doquit);
21438032Speter	if (tTd(91, 100))
21538032Speter		sm_syslog(LOG_DEBUG, CurEnv->e_id,
21664565Sgshapiro			  "mci_uncache: uncaching %lx (%.100s) from slot %d (%d)",
21790795Sgshapiro			  (unsigned long) mci, mci->mci_host,
21890795Sgshapiro			  (int) (mcislot - MciCache), doquit);
21938032Speter
22064565Sgshapiro	mci->mci_deliveries = 0;
22138032Speter	if (doquit)
22238032Speter	{
22338032Speter		message("Closing connection to %s", mci->mci_host);
22438032Speter
22538032Speter		mci->mci_flags &= ~MCIF_CACHED;
22638032Speter
22738032Speter		/* only uses the envelope to flush the transcript file */
22838032Speter		if (mci->mci_state != MCIS_CLOSED)
22938032Speter			smtpquit(mci->mci_mailer, mci, &BlankEnvelope);
23090795Sgshapiro#if XLA
23138032Speter		xla_host_end(mci->mci_host);
23290795Sgshapiro#endif /* XLA */
23338032Speter	}
23438032Speter	else
23538032Speter	{
23638032Speter		if (mci->mci_in != NULL)
23790795Sgshapiro			(void) sm_io_close(mci->mci_in, SM_TIME_DEFAULT);
23838032Speter		if (mci->mci_out != NULL)
23990795Sgshapiro			(void) sm_io_close(mci->mci_out, SM_TIME_DEFAULT);
24038032Speter		mci->mci_in = mci->mci_out = NULL;
24138032Speter		mci->mci_state = MCIS_CLOSED;
24238032Speter		mci->mci_exitstat = EX_OK;
24338032Speter		mci->mci_errno = 0;
24438032Speter		mci->mci_flags = 0;
24590795Sgshapiro
24690795Sgshapiro		mci->mci_retryrcpt = false;
24790795Sgshapiro		mci->mci_tolist = NULL;
24890795Sgshapiro#if PIPELINING
24990795Sgshapiro		mci->mci_okrcpts = 0;
25090795Sgshapiro#endif /* PIPELINING */
25138032Speter	}
25290795Sgshapiro
25390795Sgshapiro	SM_FREE_CLR(mci->mci_status);
25490795Sgshapiro	SM_FREE_CLR(mci->mci_rstatus);
25590795Sgshapiro	SM_FREE_CLR(mci->mci_heloname);
25690795Sgshapiro	if (mci->mci_rpool != NULL)
25790795Sgshapiro	{
25890795Sgshapiro		sm_rpool_free(mci->mci_rpool);
25990795Sgshapiro		mci->mci_macro.mac_rpool = NULL;
26090795Sgshapiro		mci->mci_rpool = NULL;
26190795Sgshapiro	}
26238032Speter}
26390795Sgshapiro/*
26438032Speter**  MCI_FLUSH -- flush the entire cache
26538032Speter**
26638032Speter**	Parameters:
26790795Sgshapiro**		doquit -- if true, send QUIT protocol.
26890795Sgshapiro**			  if false, just close the connection.
26938032Speter**		allbut -- but leave this one open.
27038032Speter**
27138032Speter**	Returns:
27238032Speter**		none.
27338032Speter*/
27438032Speter
27538032Spetervoid
27638032Spetermci_flush(doquit, allbut)
27738032Speter	bool doquit;
27838032Speter	MCI *allbut;
27938032Speter{
28038032Speter	register int i;
28138032Speter
28238032Speter	if (MciCache == NULL)
28338032Speter		return;
28438032Speter
28538032Speter	for (i = 0; i < MaxMciCache; i++)
28671348Sgshapiro	{
28738032Speter		if (allbut != MciCache[i])
28838032Speter			mci_uncache(&MciCache[i], doquit);
28971348Sgshapiro	}
29038032Speter}
291223067Sgshapiro
29290795Sgshapiro/*
293223067Sgshapiro**  MCI_CLR_EXTENSIONS -- clear knowledge about SMTP extensions
294223067Sgshapiro**
295223067Sgshapiro**	Parameters:
296223067Sgshapiro**		mci -- the connection to clear.
297223067Sgshapiro**
298223067Sgshapiro**	Returns:
299223067Sgshapiro**		none.
300223067Sgshapiro*/
301223067Sgshapiro
302223067Sgshapirovoid
303223067Sgshapiromci_clr_extensions(mci)
304223067Sgshapiro	MCI *mci;
305223067Sgshapiro{
306223067Sgshapiro	if (mci == NULL)
307223067Sgshapiro		return;
308223067Sgshapiro
309223067Sgshapiro	mci->mci_flags &= ~MCIF_EXTENS;
310223067Sgshapiro	mci->mci_maxsize = 0;
311223067Sgshapiro	mci->mci_min_by = 0;
312223067Sgshapiro#if SASL
313223067Sgshapiro	mci->mci_saslcap = NULL;
314223067Sgshapiro#endif /* SASL */
315223067Sgshapiro}
316223067Sgshapiro
317223067Sgshapiro/*
31838032Speter**  MCI_GET -- get information about a particular host
31990795Sgshapiro**
32090795Sgshapiro**	Parameters:
32190795Sgshapiro**		host -- host to look for.
32290795Sgshapiro**		m -- mailer.
32390795Sgshapiro**
32490795Sgshapiro**	Returns:
32590795Sgshapiro**		mci for this host (might be new).
32638032Speter*/
32738032Speter
32838032SpeterMCI *
32938032Spetermci_get(host, m)
33038032Speter	char *host;
33138032Speter	MAILER *m;
33238032Speter{
33338032Speter	register MCI *mci;
33438032Speter	register STAB *s;
33538032Speter	extern SOCKADDR CurHostAddr;
33638032Speter
33738032Speter	/* clear CurHostAddr so we don't get a bogus address with this name */
338168520Sgshapiro	memset(&CurHostAddr, '\0', sizeof(CurHostAddr));
33938032Speter
34038032Speter	/* clear out any expired connections */
34138032Speter	(void) mci_scan(NULL);
34238032Speter
34338032Speter	if (m->m_mno < 0)
34471348Sgshapiro		syserr("!negative mno %d (%s)", m->m_mno, m->m_name);
34564565Sgshapiro
34638032Speter	s = stab(host, ST_MCI + m->m_mno, ST_ENTER);
34738032Speter	mci = &s->s_mci;
34838032Speter
34990795Sgshapiro	/* initialize per-message data */
35090795Sgshapiro	mci->mci_retryrcpt = false;
35190795Sgshapiro	mci->mci_tolist = NULL;
35290795Sgshapiro#if PIPELINING
35390795Sgshapiro	mci->mci_okrcpts = 0;
35490795Sgshapiro#endif /* PIPELINING */
35590795Sgshapiro
35690795Sgshapiro	if (mci->mci_rpool == NULL)
35790795Sgshapiro		mci->mci_rpool = sm_rpool_new_x(NULL);
35890795Sgshapiro
35990795Sgshapiro	if (mci->mci_macro.mac_rpool == NULL)
36090795Sgshapiro		mci->mci_macro.mac_rpool = mci->mci_rpool;
36190795Sgshapiro
36264565Sgshapiro	/*
36390795Sgshapiro	**  We don't need to load the persistent data if we have data
36464565Sgshapiro	**  already loaded in the cache.
36564565Sgshapiro	*/
36664565Sgshapiro
36764565Sgshapiro	if (mci->mci_host == NULL &&
36864565Sgshapiro	    (mci->mci_host = s->s_name) != NULL &&
36964565Sgshapiro	    !mci_load_persistent(mci))
37038032Speter	{
37138032Speter		if (tTd(42, 2))
37290795Sgshapiro			sm_dprintf("mci_get(%s %s): lock failed\n",
37364565Sgshapiro				host, m->m_name);
37438032Speter		mci->mci_exitstat = EX_TEMPFAIL;
37538032Speter		mci->mci_state = MCIS_CLOSED;
37638032Speter		mci->mci_statfile = NULL;
37738032Speter		return mci;
37838032Speter	}
37938032Speter
38038032Speter	if (tTd(42, 2))
38138032Speter	{
38290795Sgshapiro		sm_dprintf("mci_get(%s %s): mci_state=%d, _flags=%lx, _exitstat=%d, _errno=%d\n",
38338032Speter			host, m->m_name, mci->mci_state, mci->mci_flags,
38438032Speter			mci->mci_exitstat, mci->mci_errno);
38538032Speter	}
38638032Speter
38738032Speter	if (mci->mci_state == MCIS_OPEN)
38838032Speter	{
38938032Speter		/* poke the connection to see if it's still alive */
39038032Speter		(void) smtpprobe(mci);
39138032Speter
39238032Speter		/* reset the stored state in the event of a timeout */
39338032Speter		if (mci->mci_state != MCIS_OPEN)
39438032Speter		{
39538032Speter			mci->mci_errno = 0;
39638032Speter			mci->mci_exitstat = EX_OK;
39738032Speter			mci->mci_state = MCIS_CLOSED;
39838032Speter		}
39938032Speter		else
40038032Speter		{
40190795Sgshapiro			/* get peer host address */
40238032Speter			/* (this should really be in the mci struct) */
403168520Sgshapiro			SOCKADDR_LEN_T socklen = sizeof(CurHostAddr);
40438032Speter
40590795Sgshapiro			(void) getpeername(sm_io_getinfo(mci->mci_in,
40690795Sgshapiro							 SM_IO_WHAT_FD, NULL),
40738032Speter				(struct sockaddr *) &CurHostAddr, &socklen);
40838032Speter		}
40938032Speter	}
41038032Speter	if (mci->mci_state == MCIS_CLOSED)
41138032Speter	{
41238032Speter		time_t now = curtime();
41338032Speter
41438032Speter		/* if this info is stale, ignore it */
41594337Sgshapiro		if (mci->mci_lastuse + MciInfoTimeout <= now)
41638032Speter		{
41738032Speter			mci->mci_lastuse = now;
41838032Speter			mci->mci_errno = 0;
41938032Speter			mci->mci_exitstat = EX_OK;
42038032Speter		}
42138032Speter	}
42238032Speter
42338032Speter	return mci;
42438032Speter}
425141887Sgshapiro
42690795Sgshapiro/*
427141887Sgshapiro**  MCI_CLOSE -- (forcefully) close files used for a connection.
428141887Sgshapiro**	Note: this is a last resort, usually smtpquit() or endmailer()
429141887Sgshapiro**		should be used to close a connection.
430141887Sgshapiro**
431141887Sgshapiro**	Parameters:
432141887Sgshapiro**		mci -- the connection to close.
433141887Sgshapiro**		where -- where has this been called?
434141887Sgshapiro**
435141887Sgshapiro**	Returns:
436141887Sgshapiro**		none.
437141887Sgshapiro*/
438141887Sgshapiro
439141887Sgshapirovoid
440141887Sgshapiromci_close(mci, where)
441141887Sgshapiro	MCI *mci;
442141887Sgshapiro	char *where;
443141887Sgshapiro{
444141887Sgshapiro	bool dumped;
445141887Sgshapiro
446141887Sgshapiro	if (mci == NULL)
447141887Sgshapiro		return;
448141887Sgshapiro	dumped = false;
449141887Sgshapiro	if (mci->mci_out != NULL)
450141887Sgshapiro	{
451141887Sgshapiro		if (tTd(56, 1))
452141887Sgshapiro		{
453141887Sgshapiro			sm_dprintf("mci_close: mci_out!=NULL, where=%s\n",
454141887Sgshapiro				where);
455141887Sgshapiro			mci_dump(sm_debug_file(), mci, false);
456141887Sgshapiro			dumped = true;
457141887Sgshapiro		}
458141887Sgshapiro		(void) sm_io_close(mci->mci_out, SM_TIME_DEFAULT);
459141887Sgshapiro		mci->mci_out = NULL;
460141887Sgshapiro	}
461141887Sgshapiro	if (mci->mci_in != NULL)
462141887Sgshapiro	{
463141887Sgshapiro		if (tTd(56, 1))
464141887Sgshapiro		{
465141887Sgshapiro			sm_dprintf("mci_close: mci_in!=NULL, where=%s\n",
466141887Sgshapiro				where);
467141887Sgshapiro			if (!dumped)
468141887Sgshapiro				mci_dump(sm_debug_file(), mci, false);
469141887Sgshapiro		}
470141887Sgshapiro		(void) sm_io_close(mci->mci_in, SM_TIME_DEFAULT);
471141887Sgshapiro		mci->mci_in = NULL;
472141887Sgshapiro	}
473141887Sgshapiro	mci->mci_state = MCIS_CLOSED;
474141887Sgshapiro}
475141887Sgshapiro
476141887Sgshapiro/*
47790795Sgshapiro**  MCI_NEW -- allocate new MCI structure
47890795Sgshapiro**
47990795Sgshapiro**	Parameters:
48090795Sgshapiro**		rpool -- if non-NULL: allocate from that rpool.
48190795Sgshapiro**
48290795Sgshapiro**	Returns:
48390795Sgshapiro**		mci (new).
48490795Sgshapiro*/
48590795Sgshapiro
48690795SgshapiroMCI *
48790795Sgshapiromci_new(rpool)
48890795Sgshapiro	SM_RPOOL_T *rpool;
48990795Sgshapiro{
49090795Sgshapiro	register MCI *mci;
49190795Sgshapiro
49290795Sgshapiro	if (rpool == NULL)
493168520Sgshapiro		mci = (MCI *) sm_malloc_x(sizeof(*mci));
49490795Sgshapiro	else
495168520Sgshapiro		mci = (MCI *) sm_rpool_malloc_x(rpool, sizeof(*mci));
496168520Sgshapiro	memset((char *) mci, '\0', sizeof(*mci));
49790795Sgshapiro	mci->mci_rpool = sm_rpool_new_x(NULL);
49890795Sgshapiro	mci->mci_macro.mac_rpool = mci->mci_rpool;
49990795Sgshapiro	return mci;
50090795Sgshapiro}
50190795Sgshapiro/*
50264565Sgshapiro**  MCI_MATCH -- check connection cache for a particular host
50390795Sgshapiro**
50490795Sgshapiro**	Parameters:
50590795Sgshapiro**		host -- host to look for.
50690795Sgshapiro**		m -- mailer.
50790795Sgshapiro**
50890795Sgshapiro**	Returns:
50990795Sgshapiro**		true iff open connection exists.
51064565Sgshapiro*/
51164565Sgshapiro
51264565Sgshapirobool
51364565Sgshapiromci_match(host, m)
51464565Sgshapiro	char *host;
51564565Sgshapiro	MAILER *m;
51664565Sgshapiro{
51764565Sgshapiro	register MCI *mci;
51864565Sgshapiro	register STAB *s;
51964565Sgshapiro
52071348Sgshapiro	if (m->m_mno < 0 || m->m_mno > MAXMAILERS)
52190795Sgshapiro		return false;
52264565Sgshapiro	s = stab(host, ST_MCI + m->m_mno, ST_FIND);
52364565Sgshapiro	if (s == NULL)
52490795Sgshapiro		return false;
52564565Sgshapiro
52664565Sgshapiro	mci = &s->s_mci;
52790795Sgshapiro	return mci->mci_state == MCIS_OPEN;
52864565Sgshapiro}
52990795Sgshapiro/*
53038032Speter**  MCI_SETSTAT -- set status codes in MCI structure.
53138032Speter**
53238032Speter**	Parameters:
53338032Speter**		mci -- the MCI structure to set.
53438032Speter**		xstat -- the exit status code.
53538032Speter**		dstat -- the DSN status code.
53638032Speter**		rstat -- the SMTP status code.
53738032Speter**
53838032Speter**	Returns:
53938032Speter**		none.
54038032Speter*/
54138032Speter
54238032Spetervoid
54338032Spetermci_setstat(mci, xstat, dstat, rstat)
54438032Speter	MCI *mci;
54538032Speter	int xstat;
54638032Speter	char *dstat;
54738032Speter	char *rstat;
54838032Speter{
54938032Speter	/* protocol errors should never be interpreted as sticky */
55038032Speter	if (xstat != EX_NOTSTICKY && xstat != EX_PROTOCOL)
55138032Speter		mci->mci_exitstat = xstat;
55238032Speter
55390795Sgshapiro	SM_FREE_CLR(mci->mci_status);
55490795Sgshapiro	if (dstat != NULL)
55590795Sgshapiro		mci->mci_status = sm_strdup_x(dstat);
55690795Sgshapiro
55790795Sgshapiro	SM_FREE_CLR(mci->mci_rstatus);
55838032Speter	if (rstat != NULL)
55990795Sgshapiro		mci->mci_rstatus = sm_strdup_x(rstat);
56038032Speter}
56190795Sgshapiro/*
56238032Speter**  MCI_DUMP -- dump the contents of an MCI structure.
56338032Speter**
56438032Speter**	Parameters:
565132946Sgshapiro**		fp -- output file pointer
56638032Speter**		mci -- the MCI structure to dump.
56738032Speter**
56838032Speter**	Returns:
56938032Speter**		none.
57038032Speter**
57138032Speter**	Side Effects:
57238032Speter**		none.
57338032Speter*/
57438032Speter
57538032Speterstruct mcifbits
57638032Speter{
57738032Speter	int	mcif_bit;	/* flag bit */
57838032Speter	char	*mcif_name;	/* flag name */
57938032Speter};
58064565Sgshapirostatic struct mcifbits	MciFlags[] =
58138032Speter{
58238032Speter	{ MCIF_VALID,		"VALID"		},
58338032Speter	{ MCIF_CACHED,		"CACHED"	},
58438032Speter	{ MCIF_ESMTP,		"ESMTP"		},
58538032Speter	{ MCIF_EXPN,		"EXPN"		},
58638032Speter	{ MCIF_SIZE,		"SIZE"		},
58738032Speter	{ MCIF_8BITMIME,	"8BITMIME"	},
58838032Speter	{ MCIF_7BIT,		"7BIT"		},
58938032Speter	{ MCIF_INHEADER,	"INHEADER"	},
59038032Speter	{ MCIF_CVT8TO7,		"CVT8TO7"	},
59138032Speter	{ MCIF_DSN,		"DSN"		},
59238032Speter	{ MCIF_8BITOK,		"8BITOK"	},
59338032Speter	{ MCIF_CVT7TO8,		"CVT7TO8"	},
59438032Speter	{ MCIF_INMIME,		"INMIME"	},
59590795Sgshapiro	{ MCIF_AUTH,		"AUTH"		},
596223067Sgshapiro	{ MCIF_AUTH2,		"AUTH2"		},
59790795Sgshapiro	{ MCIF_AUTHACT,		"AUTHACT"	},
59890795Sgshapiro	{ MCIF_ENHSTAT,		"ENHSTAT"	},
59990795Sgshapiro	{ MCIF_PIPELINED,	"PIPELINED"	},
60090795Sgshapiro#if STARTTLS
60190795Sgshapiro	{ MCIF_TLS,		"TLS"		},
60290795Sgshapiro	{ MCIF_TLSACT,		"TLSACT"	},
60390795Sgshapiro#endif /* STARTTLS */
60490795Sgshapiro	{ MCIF_DLVR_BY,		"DLVR_BY"	},
60571348Sgshapiro	{ 0,			NULL		}
60638032Speter};
60738032Speter
60838032Spetervoid
609132946Sgshapiromci_dump(fp, mci, logit)
610132946Sgshapiro	SM_FILE_T *fp;
61138032Speter	register MCI *mci;
61238032Speter	bool logit;
61338032Speter{
61438032Speter	register char *p;
61538032Speter	char *sep;
61638032Speter	char buf[4000];
61738032Speter
61838032Speter	sep = logit ? " " : "\n\t";
61938032Speter	p = buf;
62090795Sgshapiro	(void) sm_snprintf(p, SPACELEFT(buf, p), "MCI@%p: ", mci);
62138032Speter	p += strlen(p);
62238032Speter	if (mci == NULL)
62338032Speter	{
62490795Sgshapiro		(void) sm_snprintf(p, SPACELEFT(buf, p), "NULL");
62538032Speter		goto printit;
62638032Speter	}
62790795Sgshapiro	(void) sm_snprintf(p, SPACELEFT(buf, p), "flags=%lx", mci->mci_flags);
62838032Speter	p += strlen(p);
629120259Sgshapiro
630120259Sgshapiro	/*
631120259Sgshapiro	**  The following check is just for paranoia.  It protects the
632120259Sgshapiro	**  assignment in the if() clause. If there's not some minimum
633120259Sgshapiro	**  amount of space we can stop right now. The check will not
634120259Sgshapiro	**  trigger as long as sizeof(buf)=4000.
635120259Sgshapiro	*/
636120259Sgshapiro
637120259Sgshapiro	if (p >= buf + sizeof(buf) - 4)
638120259Sgshapiro		goto printit;
63938032Speter	if (mci->mci_flags != 0)
64038032Speter	{
64138032Speter		struct mcifbits *f;
64238032Speter
643120259Sgshapiro		*p++ = '<';	/* protected above */
64438032Speter		for (f = MciFlags; f->mcif_bit != 0; f++)
64538032Speter		{
64638032Speter			if (!bitset(f->mcif_bit, mci->mci_flags))
64738032Speter				continue;
64890795Sgshapiro			(void) sm_strlcpyn(p, SPACELEFT(buf, p), 2,
64990795Sgshapiro					   f->mcif_name, ",");
65038032Speter			p += strlen(p);
65138032Speter		}
65238032Speter		p[-1] = '>';
65338032Speter	}
65490795Sgshapiro
65590795Sgshapiro	/* Note: sm_snprintf() takes care of NULL arguments for %s */
65690795Sgshapiro	(void) sm_snprintf(p, SPACELEFT(buf, p),
65738032Speter		",%serrno=%d, herrno=%d, exitstat=%d, state=%d, pid=%d,%s",
65838032Speter		sep, mci->mci_errno, mci->mci_herrno,
65964565Sgshapiro		mci->mci_exitstat, mci->mci_state, (int) mci->mci_pid, sep);
66038032Speter	p += strlen(p);
66190795Sgshapiro	(void) sm_snprintf(p, SPACELEFT(buf, p),
66238032Speter		"maxsize=%ld, phase=%s, mailer=%s,%s",
66390795Sgshapiro		mci->mci_maxsize, mci->mci_phase,
66438032Speter		mci->mci_mailer == NULL ? "NULL" : mci->mci_mailer->m_name,
66538032Speter		sep);
66638032Speter	p += strlen(p);
66790795Sgshapiro	(void) sm_snprintf(p, SPACELEFT(buf, p),
66838032Speter		"status=%s, rstatus=%s,%s",
66990795Sgshapiro		mci->mci_status, mci->mci_rstatus, sep);
67038032Speter	p += strlen(p);
67190795Sgshapiro	(void) sm_snprintf(p, SPACELEFT(buf, p),
67238032Speter		"host=%s, lastuse=%s",
67390795Sgshapiro		mci->mci_host, ctime(&mci->mci_lastuse));
67438032Speterprintit:
67538032Speter	if (logit)
67638032Speter		sm_syslog(LOG_DEBUG, CurEnv->e_id, "%.1000s", buf);
67738032Speter	else
678132946Sgshapiro		(void) sm_io_fprintf(fp, SM_TIME_DEFAULT, "%s\n", buf);
67938032Speter}
68090795Sgshapiro/*
68138032Speter**  MCI_DUMP_ALL -- print the entire MCI cache
68238032Speter**
68338032Speter**	Parameters:
684132946Sgshapiro**		fp -- output file pointer
68538032Speter**		logit -- if set, log the result instead of printing
68638032Speter**			to stdout.
68738032Speter**
68838032Speter**	Returns:
68938032Speter**		none.
69038032Speter*/
69138032Speter
69238032Spetervoid
693132946Sgshapiromci_dump_all(fp, logit)
694132946Sgshapiro	SM_FILE_T *fp;
69538032Speter	bool logit;
69638032Speter{
69738032Speter	register int i;
69838032Speter
69938032Speter	if (MciCache == NULL)
70038032Speter		return;
70138032Speter
70238032Speter	for (i = 0; i < MaxMciCache; i++)
703132946Sgshapiro		mci_dump(fp, MciCache[i], logit);
70438032Speter}
70590795Sgshapiro/*
70642580Speter**  MCI_LOCK_HOST -- Lock host while sending.
70738032Speter**
70838032Speter**	If we are contacting a host, we'll need to
70938032Speter**	update the status information in the host status
71038032Speter**	file, and if we want to do that, we ought to have
71138032Speter**	locked it. This has the (according to some)
71238032Speter**	desirable effect of serializing connectivity with
71390795Sgshapiro**	remote hosts -- i.e.: one connection to a given
71438032Speter**	host at a time.
71538032Speter**
71638032Speter**	Parameters:
71738032Speter**		mci -- containing the host we want to lock.
71838032Speter**
71938032Speter**	Returns:
72064565Sgshapiro**		EX_OK	    -- got the lock.
72138032Speter**		EX_TEMPFAIL -- didn't get the lock.
72238032Speter*/
72338032Speter
72438032Speterint
72538032Spetermci_lock_host(mci)
72638032Speter	MCI *mci;
72738032Speter{
72838032Speter	if (mci == NULL)
72938032Speter	{
73038032Speter		if (tTd(56, 1))
73190795Sgshapiro			sm_dprintf("mci_lock_host: NULL mci\n");
73238032Speter		return EX_OK;
73338032Speter	}
73438032Speter
73538032Speter	if (!SingleThreadDelivery)
73638032Speter		return EX_OK;
73738032Speter
73838032Speter	return mci_lock_host_statfile(mci);
73938032Speter}
74038032Speter
74164565Sgshapirostatic int
74238032Spetermci_lock_host_statfile(mci)
74338032Speter	MCI *mci;
74438032Speter{
74564565Sgshapiro	int save_errno = errno;
74638032Speter	int retVal = EX_OK;
74798125Sgshapiro	char fname[MAXPATHLEN];
74838032Speter
74938032Speter	if (HostStatDir == NULL || mci->mci_host == NULL)
75038032Speter		return EX_OK;
75138032Speter
75238032Speter	if (tTd(56, 2))
75390795Sgshapiro		sm_dprintf("mci_lock_host: attempting to lock %s\n",
75490795Sgshapiro			   mci->mci_host);
75538032Speter
756168520Sgshapiro	if (mci_generate_persistent_path(mci->mci_host, fname, sizeof(fname),
75790795Sgshapiro					 true) < 0)
75838032Speter	{
75938032Speter		/* of course this should never happen */
76038032Speter		if (tTd(56, 2))
76190795Sgshapiro			sm_dprintf("mci_lock_host: Failed to generate host path for %s\n",
76290795Sgshapiro				   mci->mci_host);
76338032Speter
76438032Speter		retVal = EX_TEMPFAIL;
76538032Speter		goto cleanup;
76638032Speter	}
76738032Speter
76838032Speter	mci->mci_statfile = safefopen(fname, O_RDWR, FileMode,
76964565Sgshapiro				      SFF_NOLOCK|SFF_NOLINK|SFF_OPENASROOT|SFF_REGONLY|SFF_SAFEDIRPATH|SFF_CREAT);
77038032Speter
77138032Speter	if (mci->mci_statfile == NULL)
77238032Speter	{
77390795Sgshapiro		syserr("mci_lock_host: cannot create host lock file %s", fname);
77438032Speter		goto cleanup;
77538032Speter	}
77638032Speter
77790795Sgshapiro	if (!lockfile(sm_io_getinfo(mci->mci_statfile, SM_IO_WHAT_FD, NULL),
77890795Sgshapiro		      fname, "", LOCK_EX|LOCK_NB))
77938032Speter	{
78038032Speter		if (tTd(56, 2))
78190795Sgshapiro			sm_dprintf("mci_lock_host: couldn't get lock on %s\n",
78264565Sgshapiro				fname);
78390795Sgshapiro		(void) sm_io_close(mci->mci_statfile, SM_TIME_DEFAULT);
78438032Speter		mci->mci_statfile = NULL;
78538032Speter		retVal = EX_TEMPFAIL;
78638032Speter		goto cleanup;
78738032Speter	}
78838032Speter
78938032Speter	if (tTd(56, 12) && mci->mci_statfile != NULL)
79090795Sgshapiro		sm_dprintf("mci_lock_host: Sanity check -- lock is good\n");
79138032Speter
79238032Spetercleanup:
79364565Sgshapiro	errno = save_errno;
79438032Speter	return retVal;
79538032Speter}
79690795Sgshapiro/*
79738032Speter**  MCI_UNLOCK_HOST -- unlock host
79838032Speter**
79938032Speter**	Clean up the lock on a host, close the file, let
80038032Speter**	someone else use it.
80138032Speter**
80238032Speter**	Parameters:
80338032Speter**		mci -- us.
80438032Speter**
80538032Speter**	Returns:
80638032Speter**		nothing.
80738032Speter*/
80838032Speter
80938032Spetervoid
81038032Spetermci_unlock_host(mci)
81138032Speter	MCI *mci;
81238032Speter{
81364565Sgshapiro	int save_errno = errno;
81438032Speter
81538032Speter	if (mci == NULL)
81638032Speter	{
81738032Speter		if (tTd(56, 1))
81890795Sgshapiro			sm_dprintf("mci_unlock_host: NULL mci\n");
81938032Speter		return;
82038032Speter	}
82138032Speter
82238032Speter	if (HostStatDir == NULL || mci->mci_host == NULL)
82338032Speter		return;
82438032Speter
82538032Speter	if (!SingleThreadDelivery && mci_lock_host_statfile(mci) == EX_TEMPFAIL)
82638032Speter	{
82738032Speter		if (tTd(56, 1))
82890795Sgshapiro			sm_dprintf("mci_unlock_host: stat file already locked\n");
82938032Speter	}
83038032Speter	else
83138032Speter	{
83238032Speter		if (tTd(56, 2))
83390795Sgshapiro			sm_dprintf("mci_unlock_host: store prior to unlock\n");
83438032Speter		mci_store_persistent(mci);
83538032Speter	}
83638032Speter
83738032Speter	if (mci->mci_statfile != NULL)
83838032Speter	{
83990795Sgshapiro		(void) sm_io_close(mci->mci_statfile, SM_TIME_DEFAULT);
84038032Speter		mci->mci_statfile = NULL;
84138032Speter	}
84238032Speter
84364565Sgshapiro	errno = save_errno;
84438032Speter}
84590795Sgshapiro/*
84642580Speter**  MCI_LOAD_PERSISTENT -- load persistent host info
84738032Speter**
84838032Speter**	Load information about host that is kept
84938032Speter**	in common for all running sendmails.
85038032Speter**
85138032Speter**	Parameters:
85290795Sgshapiro**		mci -- the host/connection to load persistent info for.
85338032Speter**
85438032Speter**	Returns:
85590795Sgshapiro**		true -- lock was successful
85690795Sgshapiro**		false -- lock failed
85738032Speter*/
85838032Speter
85964565Sgshapirostatic bool
86038032Spetermci_load_persistent(mci)
86138032Speter	MCI *mci;
86238032Speter{
86364565Sgshapiro	int save_errno = errno;
86490795Sgshapiro	bool locked = true;
86590795Sgshapiro	SM_FILE_T *fp;
86698125Sgshapiro	char fname[MAXPATHLEN];
86738032Speter
86838032Speter	if (mci == NULL)
86938032Speter	{
87038032Speter		if (tTd(56, 1))
87190795Sgshapiro			sm_dprintf("mci_load_persistent: NULL mci\n");
87290795Sgshapiro		return true;
87338032Speter	}
87438032Speter
87538032Speter	if (IgnoreHostStatus || HostStatDir == NULL || mci->mci_host == NULL)
87690795Sgshapiro		return true;
87764565Sgshapiro
87838032Speter	/* Already have the persistent information in memory */
87938032Speter	if (SingleThreadDelivery && mci->mci_statfile != NULL)
88090795Sgshapiro		return true;
88138032Speter
88238032Speter	if (tTd(56, 1))
88390795Sgshapiro		sm_dprintf("mci_load_persistent: Attempting to load persistent information for %s\n",
88490795Sgshapiro			   mci->mci_host);
88564565Sgshapiro
886168520Sgshapiro	if (mci_generate_persistent_path(mci->mci_host, fname, sizeof(fname),
88790795Sgshapiro					 false) < 0)
88838032Speter	{
88938032Speter		/* Not much we can do if the file isn't there... */
89038032Speter		if (tTd(56, 1))
89190795Sgshapiro			sm_dprintf("mci_load_persistent: Couldn't generate host path\n");
89238032Speter		goto cleanup;
89338032Speter	}
89438032Speter
89538032Speter	fp = safefopen(fname, O_RDONLY, FileMode,
89638032Speter		       SFF_NOLOCK|SFF_NOLINK|SFF_OPENASROOT|SFF_REGONLY|SFF_SAFEDIRPATH);
89738032Speter	if (fp == NULL)
89838032Speter	{
89938032Speter		/* I can't think of any reason this should ever happen */
90038032Speter		if (tTd(56, 1))
90190795Sgshapiro			sm_dprintf("mci_load_persistent: open(%s): %s\n",
90290795Sgshapiro				fname, sm_errstring(errno));
90338032Speter		goto cleanup;
90438032Speter	}
90538032Speter
90638032Speter	FileName = fname;
90790795Sgshapiro	locked = lockfile(sm_io_getinfo(fp, SM_IO_WHAT_FD, NULL), fname, "",
90890795Sgshapiro			  LOCK_SH|LOCK_NB);
90964565Sgshapiro	if (locked)
91064565Sgshapiro	{
91164565Sgshapiro		(void) mci_read_persistent(fp, mci);
91290795Sgshapiro		(void) lockfile(sm_io_getinfo(fp, SM_IO_WHAT_FD, NULL), fname,
91390795Sgshapiro				"", LOCK_UN);
91464565Sgshapiro	}
91538032Speter	FileName = NULL;
91690795Sgshapiro	(void) sm_io_close(fp, SM_TIME_DEFAULT);
91738032Speter
91838032Spetercleanup:
91964565Sgshapiro	errno = save_errno;
92038032Speter	return locked;
92138032Speter}
92290795Sgshapiro/*
92338032Speter**  MCI_READ_PERSISTENT -- read persistent host status file
92438032Speter**
92538032Speter**	Parameters:
92638032Speter**		fp -- the file pointer to read.
92738032Speter**		mci -- the pointer to fill in.
92838032Speter**
92938032Speter**	Returns:
93038032Speter**		-1 -- if the file was corrupt.
93138032Speter**		0 -- otherwise.
93238032Speter**
93338032Speter**	Warning:
93438032Speter**		This code makes the assumption that this data
93538032Speter**		will be read in an atomic fashion, and that the data
93638032Speter**		was written in an atomic fashion.  Any other functioning
93738032Speter**		may lead to some form of insanity.  This should be
93838032Speter**		perfectly safe due to underlying stdio buffering.
93938032Speter*/
94038032Speter
94164565Sgshapirostatic int
94238032Spetermci_read_persistent(fp, mci)
94390795Sgshapiro	SM_FILE_T *fp;
94438032Speter	register MCI *mci;
94538032Speter{
94638032Speter	int ver;
94738032Speter	register char *p;
94838032Speter	int saveLineNumber = LineNumber;
94938032Speter	char buf[MAXLINE];
95038032Speter
95138032Speter	if (fp == NULL)
952159613Sgshapiro	{
95338032Speter		syserr("mci_read_persistent: NULL fp");
954159613Sgshapiro		/* NOTREACHED */
955159613Sgshapiro		return -1;
956159613Sgshapiro	}
95738032Speter	if (mci == NULL)
958159613Sgshapiro	{
95938032Speter		syserr("mci_read_persistent: NULL mci");
960159613Sgshapiro		/* NOTREACHED */
961159613Sgshapiro		return -1;
962159613Sgshapiro	}
96338032Speter	if (tTd(56, 93))
96438032Speter	{
96590795Sgshapiro		sm_dprintf("mci_read_persistent: fp=%lx, mci=",
96690795Sgshapiro			   (unsigned long) fp);
96738032Speter	}
96838032Speter
96990795Sgshapiro	SM_FREE_CLR(mci->mci_status);
97090795Sgshapiro	SM_FREE_CLR(mci->mci_rstatus);
97138032Speter
97290795Sgshapiro	sm_io_rewind(fp, SM_TIME_DEFAULT);
97338032Speter	ver = -1;
97438032Speter	LineNumber = 0;
975249729Sgshapiro	while (sm_io_fgets(fp, SM_TIME_DEFAULT, buf, sizeof(buf)) >= 0)
97638032Speter	{
97738032Speter		LineNumber++;
97838032Speter		p = strchr(buf, '\n');
97938032Speter		if (p != NULL)
98038032Speter			*p = '\0';
98138032Speter		switch (buf[0])
98238032Speter		{
98338032Speter		  case 'V':		/* version stamp */
98438032Speter			ver = atoi(&buf[1]);
98538032Speter			if (ver < 0 || ver > 0)
98638032Speter				syserr("Unknown host status version %d: %d max",
98738032Speter					ver, 0);
98838032Speter			break;
98938032Speter
99038032Speter		  case 'E':		/* UNIX error number */
99138032Speter			mci->mci_errno = atoi(&buf[1]);
99238032Speter			break;
99338032Speter
99438032Speter		  case 'H':		/* DNS error number */
99538032Speter			mci->mci_herrno = atoi(&buf[1]);
99638032Speter			break;
99738032Speter
99838032Speter		  case 'S':		/* UNIX exit status */
99938032Speter			mci->mci_exitstat = atoi(&buf[1]);
100038032Speter			break;
100138032Speter
100238032Speter		  case 'D':		/* DSN status */
100338032Speter			mci->mci_status = newstr(&buf[1]);
100438032Speter			break;
100538032Speter
100638032Speter		  case 'R':		/* SMTP status */
100738032Speter			mci->mci_rstatus = newstr(&buf[1]);
100838032Speter			break;
100938032Speter
101038032Speter		  case 'U':		/* last usage time */
101138032Speter			mci->mci_lastuse = atol(&buf[1]);
101238032Speter			break;
101338032Speter
101438032Speter		  case '.':		/* end of file */
101590795Sgshapiro			if (tTd(56, 93))
1016132946Sgshapiro				mci_dump(sm_debug_file(), mci, false);
101738032Speter			return 0;
101838032Speter
101938032Speter		  default:
102038032Speter			sm_syslog(LOG_CRIT, NOQID,
102138032Speter				  "%s: line %d: Unknown host status line \"%s\"",
102238032Speter				  FileName == NULL ? mci->mci_host : FileName,
102338032Speter				  LineNumber, buf);
102438032Speter			LineNumber = saveLineNumber;
102538032Speter			return -1;
102638032Speter		}
102738032Speter	}
102838032Speter	LineNumber = saveLineNumber;
102990795Sgshapiro	if (tTd(56, 93))
103090795Sgshapiro		sm_dprintf("incomplete (missing dot for EOF)\n");
103138032Speter	if (ver < 0)
103238032Speter		return -1;
103338032Speter	return 0;
103438032Speter}
103590795Sgshapiro/*
103638032Speter**  MCI_STORE_PERSISTENT -- Store persistent MCI information
103738032Speter**
103838032Speter**	Store information about host that is kept
103938032Speter**	in common for all running sendmails.
104038032Speter**
104138032Speter**	Parameters:
104238032Speter**		mci -- the host/connection to store persistent info for.
104338032Speter**
104438032Speter**	Returns:
104538032Speter**		none.
104638032Speter*/
104738032Speter
104838032Spetervoid
104938032Spetermci_store_persistent(mci)
105038032Speter	MCI *mci;
105138032Speter{
105264565Sgshapiro	int save_errno = errno;
105338032Speter
105438032Speter	if (mci == NULL)
105538032Speter	{
105638032Speter		if (tTd(56, 1))
105790795Sgshapiro			sm_dprintf("mci_store_persistent: NULL mci\n");
105838032Speter		return;
105938032Speter	}
106038032Speter
106138032Speter	if (HostStatDir == NULL || mci->mci_host == NULL)
106238032Speter		return;
106338032Speter
106438032Speter	if (tTd(56, 1))
106590795Sgshapiro		sm_dprintf("mci_store_persistent: Storing information for %s\n",
106690795Sgshapiro			   mci->mci_host);
106738032Speter
106838032Speter	if (mci->mci_statfile == NULL)
106938032Speter	{
107038032Speter		if (tTd(56, 1))
107190795Sgshapiro			sm_dprintf("mci_store_persistent: no statfile\n");
107238032Speter		return;
107338032Speter	}
107438032Speter
107590795Sgshapiro	sm_io_rewind(mci->mci_statfile, SM_TIME_DEFAULT);
107638032Speter#if !NOFTRUNCATE
107790795Sgshapiro	(void) ftruncate(sm_io_getinfo(mci->mci_statfile, SM_IO_WHAT_FD, NULL),
107890795Sgshapiro			 (off_t) 0);
107964565Sgshapiro#endif /* !NOFTRUNCATE */
108038032Speter
108190795Sgshapiro	(void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT, "V0\n");
108290795Sgshapiro	(void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT, "E%d\n",
108390795Sgshapiro			     mci->mci_errno);
108490795Sgshapiro	(void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT, "H%d\n",
108590795Sgshapiro			     mci->mci_herrno);
108690795Sgshapiro	(void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT, "S%d\n",
108790795Sgshapiro			     mci->mci_exitstat);
108838032Speter	if (mci->mci_status != NULL)
108990795Sgshapiro		(void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT,
109090795Sgshapiro				     "D%.80s\n",
109190795Sgshapiro				     denlstring(mci->mci_status, true, false));
109238032Speter	if (mci->mci_rstatus != NULL)
109390795Sgshapiro		(void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT,
109490795Sgshapiro				     "R%.80s\n",
109590795Sgshapiro				     denlstring(mci->mci_rstatus, true, false));
109690795Sgshapiro	(void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT, "U%ld\n",
109790795Sgshapiro			     (long)(mci->mci_lastuse));
109890795Sgshapiro	(void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT, ".\n");
109938032Speter
110090795Sgshapiro	(void) sm_io_flush(mci->mci_statfile, SM_TIME_DEFAULT);
110138032Speter
110264565Sgshapiro	errno = save_errno;
110338032Speter	return;
110438032Speter}
110590795Sgshapiro/*
110638032Speter**  MCI_TRAVERSE_PERSISTENT -- walk persistent status tree
110738032Speter**
110838032Speter**	Recursively find all the mci host files in `pathname'.  Default to
110938032Speter**		main host status directory if no path is provided.
111038032Speter**	Call (*action)(pathname, host) for each file found.
111138032Speter**
111238032Speter**	Note: all information is collected in a list before it is processed.
111338032Speter**	This may not be the best way to do it, but it seems safest, since
111438032Speter**	the file system would be touched while we are attempting to traverse
111538032Speter**	the directory tree otherwise (during purges).
111638032Speter**
111738032Speter**	Parameters:
111838032Speter**		action -- function to call on each node.  If returns < 0,
111938032Speter**			return immediately.
112038032Speter**		pathname -- root of tree.  If null, use main host status
112138032Speter**			directory.
112238032Speter**
112338032Speter**	Returns:
112438032Speter**		< 0 -- if any action routine returns a negative value, that
112538032Speter**			value is returned.
112638032Speter**		0 -- if we successfully went to completion.
112764565Sgshapiro**		> 0 -- return status from action()
112838032Speter*/
112938032Speter
113038032Speterint
113138032Spetermci_traverse_persistent(action, pathname)
1132141862Sgshapiro	int (*action)__P((char *, char *));
113338032Speter	char *pathname;
113438032Speter{
113538032Speter	struct stat statbuf;
113638032Speter	DIR *d;
113738032Speter	int ret;
113838032Speter
113938032Speter	if (pathname == NULL)
114038032Speter		pathname = HostStatDir;
114138032Speter	if (pathname == NULL)
114238032Speter		return -1;
114338032Speter
114438032Speter	if (tTd(56, 1))
114590795Sgshapiro		sm_dprintf("mci_traverse: pathname is %s\n", pathname);
114638032Speter
114738032Speter	ret = stat(pathname, &statbuf);
114838032Speter	if (ret < 0)
114938032Speter	{
115038032Speter		if (tTd(56, 2))
115190795Sgshapiro			sm_dprintf("mci_traverse: Failed to stat %s: %s\n",
115290795Sgshapiro				pathname, sm_errstring(errno));
115338032Speter		return ret;
115438032Speter	}
115538032Speter	if (S_ISDIR(statbuf.st_mode))
115638032Speter	{
115790795Sgshapiro		bool leftone, removedone;
115890795Sgshapiro		size_t len;
115990795Sgshapiro		char *newptr;
116038032Speter		struct dirent *e;
116198125Sgshapiro		char newpath[MAXPATHLEN];
1162157006Sgshapiro#if MAXPATHLEN <= MAXNAMLEN - 3
1163157006Sgshapiro ERROR "MAXPATHLEN <= MAXNAMLEN - 3"
1164157006Sgshapiro#endif /* MAXPATHLEN  <= MAXNAMLEN - 3 */
116538032Speter
116638032Speter		if ((d = opendir(pathname)) == NULL)
116738032Speter		{
116838032Speter			if (tTd(56, 2))
116990795Sgshapiro				sm_dprintf("mci_traverse: opendir %s: %s\n",
117090795Sgshapiro					pathname, sm_errstring(errno));
117138032Speter			return -1;
117238032Speter		}
1173182352Sgshapiro
1174182352Sgshapiro		/*
1175182352Sgshapiro		**  Reserve space for trailing '/', at least one
1176182352Sgshapiro		**  character, and '\0'
1177182352Sgshapiro		*/
1178182352Sgshapiro
1179182352Sgshapiro		len = sizeof(newpath) - 3;
118090795Sgshapiro		if (sm_strlcpy(newpath, pathname, len) >= len)
118138032Speter		{
1182182352Sgshapiro			int save_errno = errno;
1183182352Sgshapiro
118438032Speter			if (tTd(56, 2))
118590795Sgshapiro				sm_dprintf("mci_traverse: path \"%s\" too long",
118638032Speter					pathname);
1187182352Sgshapiro			(void) closedir(d);
1188182352Sgshapiro			errno = save_errno;
118938032Speter			return -1;
119038032Speter		}
119138032Speter		newptr = newpath + strlen(newpath);
119238032Speter		*newptr++ = '/';
1193182352Sgshapiro		len = sizeof(newpath) - (newptr - newpath);
119438032Speter
119564565Sgshapiro		/*
119664565Sgshapiro		**  repeat until no file has been removed
119764565Sgshapiro		**  this may become ugly when several files "expire"
119864565Sgshapiro		**  during these loops, but it's better than doing
119964565Sgshapiro		**  a rewinddir() inside the inner loop
120064565Sgshapiro		*/
120190795Sgshapiro
120264565Sgshapiro		do
120338032Speter		{
120490795Sgshapiro			leftone = removedone = false;
120564565Sgshapiro			while ((e = readdir(d)) != NULL)
120664565Sgshapiro			{
120764565Sgshapiro				if (e->d_name[0] == '.')
120864565Sgshapiro					continue;
120938032Speter
1210182352Sgshapiro				if (sm_strlcpy(newptr, e->d_name, len) >= len)
1211182352Sgshapiro				{
1212182352Sgshapiro					/* Skip truncated copies */
1213182352Sgshapiro					if (tTd(56, 4))
1214182352Sgshapiro					{
1215182352Sgshapiro						*newptr = '\0';
1216182352Sgshapiro						sm_dprintf("mci_traverse: path \"%s%s\" too long",
1217182352Sgshapiro							   newpath, e->d_name);
1218182352Sgshapiro					}
1219182352Sgshapiro					continue;
1220182352Sgshapiro				}
122138032Speter
122277352Sgshapiro				if (StopRequest)
122377352Sgshapiro					stop_sendmail();
122464565Sgshapiro				ret = mci_traverse_persistent(action, newpath);
122564565Sgshapiro				if (ret < 0)
122664565Sgshapiro					break;
122764565Sgshapiro				if (ret == 1)
122890795Sgshapiro					leftone = true;
122964565Sgshapiro				if (!removedone && ret == 0 &&
123064565Sgshapiro				    action == mci_purge_persistent)
123190795Sgshapiro					removedone = true;
123264565Sgshapiro			}
123338032Speter			if (ret < 0)
123438032Speter				break;
123590795Sgshapiro
123638032Speter			/*
123738032Speter			**  The following appears to be
123838032Speter			**  necessary during purges, since
123938032Speter			**  we modify the directory structure
124038032Speter			*/
124190795Sgshapiro
124264565Sgshapiro			if (removedone)
124338032Speter				rewinddir(d);
124464565Sgshapiro			if (tTd(56, 40))
124590795Sgshapiro				sm_dprintf("mci_traverse: path %s: ret %d removed %d left %d\n",
124664565Sgshapiro					pathname, ret, removedone, leftone);
124764565Sgshapiro		} while (removedone);
124838032Speter
124938032Speter		/* purge (or whatever) the directory proper */
125064565Sgshapiro		if (!leftone)
125164565Sgshapiro		{
125264565Sgshapiro			*--newptr = '\0';
125364565Sgshapiro			ret = (*action)(newpath, NULL);
125464565Sgshapiro		}
125564565Sgshapiro		(void) closedir(d);
125638032Speter	}
125738032Speter	else if (S_ISREG(statbuf.st_mode))
125838032Speter	{
125938032Speter		char *end = pathname + strlen(pathname) - 1;
126038032Speter		char *start;
126138032Speter		char *scan;
126238032Speter		char host[MAXHOSTNAMELEN];
126338032Speter		char *hostptr = host;
126464565Sgshapiro
126538032Speter		/*
126638032Speter		**  Reconstruct the host name from the path to the
126738032Speter		**  persistent information.
126838032Speter		*/
126938032Speter
127038032Speter		do
127138032Speter		{
127238032Speter			if (hostptr != host)
127338032Speter				*(hostptr++) = '.';
127438032Speter			start = end;
1275120259Sgshapiro			while (start > pathname && *(start - 1) != '/')
127638032Speter				start--;
127738032Speter
127838032Speter			if (*end == '.')
127938032Speter				end--;
128038032Speter
128138032Speter			for (scan = start; scan <= end; scan++)
128238032Speter				*(hostptr++) = *scan;
128338032Speter
128438032Speter			end = start - 2;
1285120259Sgshapiro		} while (end > pathname && *end == '.');
128638032Speter
128738032Speter		*hostptr = '\0';
128838032Speter
128938032Speter		/*
129042580Speter		**  Do something with the file containing the persistent
129142580Speter		**  information.
129238032Speter		*/
129390795Sgshapiro
129438032Speter		ret = (*action)(pathname, host);
129538032Speter	}
129638032Speter
129738032Speter	return ret;
129838032Speter}
129990795Sgshapiro/*
130064565Sgshapiro**  MCI_PRINT_PERSISTENT -- print persistent info
130138032Speter**
130238032Speter**	Dump the persistent information in the file 'pathname'
130338032Speter**
130438032Speter**	Parameters:
130538032Speter**		pathname -- the pathname to the status file.
130638032Speter**		hostname -- the corresponding host name.
130738032Speter**
130838032Speter**	Returns:
130938032Speter**		0
131038032Speter*/
131138032Speter
131238032Speterint
131338032Spetermci_print_persistent(pathname, hostname)
131438032Speter	char *pathname;
131538032Speter	char *hostname;
131638032Speter{
131790795Sgshapiro	static bool initflag = false;
131890795Sgshapiro	SM_FILE_T *fp;
131938032Speter	int width = Verbose ? 78 : 25;
132038032Speter	bool locked;
132138032Speter	MCI mcib;
132238032Speter
132338032Speter	/* skip directories */
132438032Speter	if (hostname == NULL)
132538032Speter		return 0;
132638032Speter
132777352Sgshapiro	if (StopRequest)
132877352Sgshapiro		stop_sendmail();
132977352Sgshapiro
133038032Speter	if (!initflag)
133138032Speter	{
133290795Sgshapiro		initflag = true;
133390795Sgshapiro		(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
133490795Sgshapiro				     " -------------- Hostname --------------- How long ago ---------Results---------\n");
133538032Speter	}
133638032Speter
133790795Sgshapiro	fp = safefopen(pathname, O_RDONLY, FileMode,
133838032Speter		       SFF_NOLOCK|SFF_NOLINK|SFF_OPENASROOT|SFF_REGONLY|SFF_SAFEDIRPATH);
133938032Speter
134038032Speter	if (fp == NULL)
134138032Speter	{
134238032Speter		if (tTd(56, 1))
134390795Sgshapiro			sm_dprintf("mci_print_persistent: cannot open %s: %s\n",
134490795Sgshapiro				pathname, sm_errstring(errno));
134538032Speter		return 0;
134638032Speter	}
134738032Speter
134838032Speter	FileName = pathname;
1349168520Sgshapiro	memset(&mcib, '\0', sizeof(mcib));
135038032Speter	if (mci_read_persistent(fp, &mcib) < 0)
135138032Speter	{
135238032Speter		syserr("%s: could not read status file", pathname);
135390795Sgshapiro		(void) sm_io_close(fp, SM_TIME_DEFAULT);
135438032Speter		FileName = NULL;
135538032Speter		return 0;
135638032Speter	}
135738032Speter
135890795Sgshapiro	locked = !lockfile(sm_io_getinfo(fp, SM_IO_WHAT_FD, NULL), pathname,
135990795Sgshapiro			   "", LOCK_SH|LOCK_NB);
136090795Sgshapiro	(void) sm_io_close(fp, SM_TIME_DEFAULT);
136138032Speter	FileName = NULL;
136238032Speter
136390795Sgshapiro	(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%c%-39s %12s ",
136490795Sgshapiro			     locked ? '*' : ' ', hostname,
136590795Sgshapiro			     pintvl(curtime() - mcib.mci_lastuse, true));
136638032Speter	if (mcib.mci_rstatus != NULL)
136790795Sgshapiro		(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%.*s\n", width,
136890795Sgshapiro				     mcib.mci_rstatus);
136938032Speter	else if (mcib.mci_exitstat == EX_TEMPFAIL && mcib.mci_errno != 0)
137090795Sgshapiro		(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
137190795Sgshapiro				     "Deferred: %.*s\n", width - 10,
137290795Sgshapiro				     sm_errstring(mcib.mci_errno));
137338032Speter	else if (mcib.mci_exitstat != 0)
137438032Speter	{
137590795Sgshapiro		char *exmsg = sm_sysexmsg(mcib.mci_exitstat);
137638032Speter
137790795Sgshapiro		if (exmsg == NULL)
137838032Speter		{
137938032Speter			char buf[80];
138038032Speter
1381168520Sgshapiro			(void) sm_snprintf(buf, sizeof(buf),
138290795Sgshapiro				"Unknown mailer error %d",
138338032Speter				mcib.mci_exitstat);
138490795Sgshapiro			(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%.*s\n",
138590795Sgshapiro					     width, buf);
138638032Speter		}
138738032Speter		else
138890795Sgshapiro			(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%.*s\n",
138990795Sgshapiro					     width, &exmsg[5]);
139038032Speter	}
139138032Speter	else if (mcib.mci_errno == 0)
139290795Sgshapiro		(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "OK\n");
139338032Speter	else
139490795Sgshapiro		(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "OK: %.*s\n",
139590795Sgshapiro				     width - 4, sm_errstring(mcib.mci_errno));
139638032Speter
139738032Speter	return 0;
139838032Speter}
139990795Sgshapiro/*
140038032Speter**  MCI_PURGE_PERSISTENT -- Remove a persistence status file.
140138032Speter**
140238032Speter**	Parameters:
140338032Speter**		pathname -- path to the status file.
140438032Speter**		hostname -- name of host corresponding to that file.
140538032Speter**			NULL if this is a directory (domain).
140638032Speter**
140738032Speter**	Returns:
140864565Sgshapiro**		0 -- ok
140964565Sgshapiro**		1 -- file not deleted (too young, incorrect format)
141064565Sgshapiro**		< 0 -- some error occurred
141138032Speter*/
141238032Speter
141338032Speterint
141438032Spetermci_purge_persistent(pathname, hostname)
141538032Speter	char *pathname;
141638032Speter	char *hostname;
141738032Speter{
141864565Sgshapiro	struct stat statbuf;
141938032Speter	char *end = pathname + strlen(pathname) - 1;
142064565Sgshapiro	int ret;
142138032Speter
142238032Speter	if (tTd(56, 1))
142390795Sgshapiro		sm_dprintf("mci_purge_persistent: purging %s\n", pathname);
142438032Speter
142564565Sgshapiro	ret = stat(pathname, &statbuf);
142664565Sgshapiro	if (ret < 0)
142764565Sgshapiro	{
142864565Sgshapiro		if (tTd(56, 2))
142990795Sgshapiro			sm_dprintf("mci_purge_persistent: Failed to stat %s: %s\n",
143090795Sgshapiro				pathname, sm_errstring(errno));
143164565Sgshapiro		return ret;
143264565Sgshapiro	}
143394337Sgshapiro	if (curtime() - statbuf.st_mtime <= MciInfoTimeout)
143464565Sgshapiro		return 1;
143538032Speter	if (hostname != NULL)
143638032Speter	{
143738032Speter		/* remove the file */
143890795Sgshapiro		ret = unlink(pathname);
143990795Sgshapiro		if (ret < 0)
144038032Speter		{
144190795Sgshapiro			if (LogLevel > 8)
144290795Sgshapiro				sm_syslog(LOG_ERR, NOQID,
144390795Sgshapiro					  "mci_purge_persistent: failed to unlink %s: %s",
144490795Sgshapiro					  pathname, sm_errstring(errno));
144538032Speter			if (tTd(56, 2))
144690795Sgshapiro				sm_dprintf("mci_purge_persistent: failed to unlink %s: %s\n",
144790795Sgshapiro					pathname, sm_errstring(errno));
144890795Sgshapiro			return ret;
144938032Speter		}
145038032Speter	}
145138032Speter	else
145238032Speter	{
145338032Speter		/* remove the directory */
145438032Speter		if (*end != '.')
145564565Sgshapiro			return 1;
145638032Speter
145738032Speter		if (tTd(56, 1))
145890795Sgshapiro			sm_dprintf("mci_purge_persistent: dpurge %s\n", pathname);
145938032Speter
146090795Sgshapiro		ret = rmdir(pathname);
146190795Sgshapiro		if (ret < 0)
146238032Speter		{
146338032Speter			if (tTd(56, 2))
146490795Sgshapiro				sm_dprintf("mci_purge_persistent: rmdir %s: %s\n",
146590795Sgshapiro					pathname, sm_errstring(errno));
146690795Sgshapiro			return ret;
146738032Speter		}
146838032Speter	}
146938032Speter
147038032Speter	return 0;
147138032Speter}
147290795Sgshapiro/*
147342580Speter**  MCI_GENERATE_PERSISTENT_PATH -- generate path from hostname
147438032Speter**
1475120259Sgshapiro**	Given `host', convert from a.b.c to $HostStatDir/c./b./a,
147638032Speter**	putting the result into `path'.  if `createflag' is set, intervening
147738032Speter**	directories will be created as needed.
147838032Speter**
147938032Speter**	Parameters:
148038032Speter**		host -- host name to convert from.
148138032Speter**		path -- place to store result.
148238032Speter**		pathlen -- length of path buffer.
148338032Speter**		createflag -- if set, create intervening directories as
148438032Speter**			needed.
148538032Speter**
148638032Speter**	Returns:
148738032Speter**		0 -- success
148838032Speter**		-1 -- failure
148938032Speter*/
149038032Speter
149164565Sgshapirostatic int
149238032Spetermci_generate_persistent_path(host, path, pathlen, createflag)
149338032Speter	const char *host;
149438032Speter	char *path;
149538032Speter	int pathlen;
149638032Speter	bool createflag;
149738032Speter{
149838032Speter	char *elem, *p, *x, ch;
149938032Speter	int ret = 0;
150038032Speter	int len;
150138032Speter	char t_host[MAXHOSTNAMELEN];
150264565Sgshapiro#if NETINET6
150364565Sgshapiro	struct in6_addr in6_addr;
150464565Sgshapiro#endif /* NETINET6 */
150538032Speter
150638032Speter	/*
150738032Speter	**  Rationality check the arguments.
150838032Speter	*/
150938032Speter
151038032Speter	if (host == NULL)
151138032Speter	{
151238032Speter		syserr("mci_generate_persistent_path: null host");
151338032Speter		return -1;
151438032Speter	}
151538032Speter	if (path == NULL)
151638032Speter	{
151738032Speter		syserr("mci_generate_persistent_path: null path");
151838032Speter		return -1;
151938032Speter	}
152038032Speter
152138032Speter	if (tTd(56, 80))
152290795Sgshapiro		sm_dprintf("mci_generate_persistent_path(%s): ", host);
152338032Speter
152438032Speter	if (*host == '\0' || *host == '.')
152538032Speter		return -1;
152638032Speter
152738032Speter	/* make certain this is not a bracketed host number */
1528168520Sgshapiro	if (strlen(host) > sizeof(t_host) - 1)
152938032Speter		return -1;
153038032Speter	if (host[0] == '[')
1531168520Sgshapiro		(void) sm_strlcpy(t_host, host + 1, sizeof(t_host));
153264565Sgshapiro	else
1533168520Sgshapiro		(void) sm_strlcpy(t_host, host, sizeof(t_host));
153438032Speter
153538032Speter	/*
153638032Speter	**  Delete any trailing dots from the hostname.
153738032Speter	**  Leave 'elem' pointing at the \0.
153838032Speter	*/
153938032Speter
154038032Speter	elem = t_host + strlen(t_host);
154138032Speter	while (elem > t_host &&
154238032Speter	       (elem[-1] == '.' || (host[0] == '[' && elem[-1] == ']')))
154338032Speter		*--elem = '\0';
154438032Speter
154538032Speter	/* check for bogus bracketed address */
154690795Sgshapiro	if (host[0] == '[')
154790795Sgshapiro	{
154890795Sgshapiro		bool good = false;
154964565Sgshapiro# if NETINET6
155090795Sgshapiro		if (anynet_pton(AF_INET6, t_host, &in6_addr) == 1)
155190795Sgshapiro			good = true;
155264565Sgshapiro# endif /* NETINET6 */
155364565Sgshapiro# if NETINET
155490795Sgshapiro		if (inet_addr(t_host) != INADDR_NONE)
155590795Sgshapiro			good = true;
155664565Sgshapiro# endif /* NETINET */
155790795Sgshapiro		if (!good)
155890795Sgshapiro			return -1;
155990795Sgshapiro	}
156038032Speter
156138032Speter	/* check for what will be the final length of the path */
156238032Speter	len = strlen(HostStatDir) + 2;
156338032Speter	for (p = (char *) t_host; *p != '\0'; p++)
156438032Speter	{
156538032Speter		if (*p == '.')
156638032Speter			len++;
156738032Speter		len++;
156838032Speter		if (p[0] == '.' && p[1] == '.')
156938032Speter			return -1;
157038032Speter	}
157138032Speter	if (len > pathlen || len < 1)
157238032Speter		return -1;
157390795Sgshapiro	(void) sm_strlcpy(path, HostStatDir, pathlen);
157438032Speter	p = path + strlen(path);
157538032Speter	while (elem > t_host)
157638032Speter	{
157738032Speter		if (!path_is_dir(path, createflag))
157838032Speter		{
157938032Speter			ret = -1;
158038032Speter			break;
158138032Speter		}
158238032Speter		elem--;
158338032Speter		while (elem >= t_host && *elem != '.')
158438032Speter			elem--;
158538032Speter		*p++ = '/';
158638032Speter		x = elem + 1;
158738032Speter		while ((ch = *x++) != '\0' && ch != '.')
158838032Speter		{
158938032Speter			if (isascii(ch) && isupper(ch))
159038032Speter				ch = tolower(ch);
159138032Speter			if (ch == '/')
159238032Speter				ch = ':';	/* / -> : */
159338032Speter			*p++ = ch;
159438032Speter		}
159538032Speter		if (elem >= t_host)
159638032Speter			*p++ = '.';
159738032Speter		*p = '\0';
159838032Speter	}
159938032Speter	if (tTd(56, 80))
160038032Speter	{
160138032Speter		if (ret < 0)
160290795Sgshapiro			sm_dprintf("FAILURE %d\n", ret);
160338032Speter		else
160490795Sgshapiro			sm_dprintf("SUCCESS %s\n", path);
160538032Speter	}
160664565Sgshapiro	return ret;
160738032Speter}
1608