138032Speter/*
2261363Sgshapiro * Copyright (c) 1998-2005, 2010 Proofpoint, 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
16266692SgshapiroSM_RCSID("@(#)$Id: mci.c,v 8.225 2013-11-22 20:51:56 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 */
355285303Sgshapiro	mci->mci_flags &= ~MCIF_NOTSTICKY;
35690795Sgshapiro
35790795Sgshapiro	if (mci->mci_rpool == NULL)
35890795Sgshapiro		mci->mci_rpool = sm_rpool_new_x(NULL);
35990795Sgshapiro
36090795Sgshapiro	if (mci->mci_macro.mac_rpool == NULL)
36190795Sgshapiro		mci->mci_macro.mac_rpool = mci->mci_rpool;
36290795Sgshapiro
36364565Sgshapiro	/*
36490795Sgshapiro	**  We don't need to load the persistent data if we have data
36564565Sgshapiro	**  already loaded in the cache.
36664565Sgshapiro	*/
36764565Sgshapiro
36864565Sgshapiro	if (mci->mci_host == NULL &&
36964565Sgshapiro	    (mci->mci_host = s->s_name) != NULL &&
37064565Sgshapiro	    !mci_load_persistent(mci))
37138032Speter	{
37238032Speter		if (tTd(42, 2))
37390795Sgshapiro			sm_dprintf("mci_get(%s %s): lock failed\n",
37464565Sgshapiro				host, m->m_name);
37538032Speter		mci->mci_exitstat = EX_TEMPFAIL;
37638032Speter		mci->mci_state = MCIS_CLOSED;
37738032Speter		mci->mci_statfile = NULL;
37838032Speter		return mci;
37938032Speter	}
38038032Speter
38138032Speter	if (tTd(42, 2))
38238032Speter	{
38390795Sgshapiro		sm_dprintf("mci_get(%s %s): mci_state=%d, _flags=%lx, _exitstat=%d, _errno=%d\n",
38438032Speter			host, m->m_name, mci->mci_state, mci->mci_flags,
38538032Speter			mci->mci_exitstat, mci->mci_errno);
38638032Speter	}
38738032Speter
38838032Speter	if (mci->mci_state == MCIS_OPEN)
38938032Speter	{
39038032Speter		/* poke the connection to see if it's still alive */
39138032Speter		(void) smtpprobe(mci);
39238032Speter
39338032Speter		/* reset the stored state in the event of a timeout */
39438032Speter		if (mci->mci_state != MCIS_OPEN)
39538032Speter		{
39638032Speter			mci->mci_errno = 0;
39738032Speter			mci->mci_exitstat = EX_OK;
39838032Speter			mci->mci_state = MCIS_CLOSED;
39938032Speter		}
40038032Speter		else
40138032Speter		{
40290795Sgshapiro			/* get peer host address */
40338032Speter			/* (this should really be in the mci struct) */
404168520Sgshapiro			SOCKADDR_LEN_T socklen = sizeof(CurHostAddr);
40538032Speter
40690795Sgshapiro			(void) getpeername(sm_io_getinfo(mci->mci_in,
40790795Sgshapiro							 SM_IO_WHAT_FD, NULL),
40838032Speter				(struct sockaddr *) &CurHostAddr, &socklen);
40938032Speter		}
41038032Speter	}
41138032Speter	if (mci->mci_state == MCIS_CLOSED)
41238032Speter	{
41338032Speter		time_t now = curtime();
41438032Speter
41538032Speter		/* if this info is stale, ignore it */
41694337Sgshapiro		if (mci->mci_lastuse + MciInfoTimeout <= now)
41738032Speter		{
41838032Speter			mci->mci_lastuse = now;
41938032Speter			mci->mci_errno = 0;
42038032Speter			mci->mci_exitstat = EX_OK;
42138032Speter		}
42238032Speter	}
42338032Speter
42438032Speter	return mci;
42538032Speter}
426141887Sgshapiro
42790795Sgshapiro/*
428141887Sgshapiro**  MCI_CLOSE -- (forcefully) close files used for a connection.
429141887Sgshapiro**	Note: this is a last resort, usually smtpquit() or endmailer()
430141887Sgshapiro**		should be used to close a connection.
431141887Sgshapiro**
432141887Sgshapiro**	Parameters:
433141887Sgshapiro**		mci -- the connection to close.
434141887Sgshapiro**		where -- where has this been called?
435141887Sgshapiro**
436141887Sgshapiro**	Returns:
437141887Sgshapiro**		none.
438141887Sgshapiro*/
439141887Sgshapiro
440141887Sgshapirovoid
441141887Sgshapiromci_close(mci, where)
442141887Sgshapiro	MCI *mci;
443141887Sgshapiro	char *where;
444141887Sgshapiro{
445141887Sgshapiro	bool dumped;
446141887Sgshapiro
447141887Sgshapiro	if (mci == NULL)
448141887Sgshapiro		return;
449141887Sgshapiro	dumped = false;
450141887Sgshapiro	if (mci->mci_out != NULL)
451141887Sgshapiro	{
452141887Sgshapiro		if (tTd(56, 1))
453141887Sgshapiro		{
454141887Sgshapiro			sm_dprintf("mci_close: mci_out!=NULL, where=%s\n",
455141887Sgshapiro				where);
456141887Sgshapiro			mci_dump(sm_debug_file(), mci, false);
457141887Sgshapiro			dumped = true;
458141887Sgshapiro		}
459141887Sgshapiro		(void) sm_io_close(mci->mci_out, SM_TIME_DEFAULT);
460141887Sgshapiro		mci->mci_out = NULL;
461141887Sgshapiro	}
462141887Sgshapiro	if (mci->mci_in != NULL)
463141887Sgshapiro	{
464141887Sgshapiro		if (tTd(56, 1))
465141887Sgshapiro		{
466141887Sgshapiro			sm_dprintf("mci_close: mci_in!=NULL, where=%s\n",
467141887Sgshapiro				where);
468141887Sgshapiro			if (!dumped)
469141887Sgshapiro				mci_dump(sm_debug_file(), mci, false);
470141887Sgshapiro		}
471141887Sgshapiro		(void) sm_io_close(mci->mci_in, SM_TIME_DEFAULT);
472141887Sgshapiro		mci->mci_in = NULL;
473141887Sgshapiro	}
474141887Sgshapiro	mci->mci_state = MCIS_CLOSED;
475141887Sgshapiro}
476141887Sgshapiro
477141887Sgshapiro/*
47890795Sgshapiro**  MCI_NEW -- allocate new MCI structure
47990795Sgshapiro**
48090795Sgshapiro**	Parameters:
48190795Sgshapiro**		rpool -- if non-NULL: allocate from that rpool.
48290795Sgshapiro**
48390795Sgshapiro**	Returns:
48490795Sgshapiro**		mci (new).
48590795Sgshapiro*/
48690795Sgshapiro
48790795SgshapiroMCI *
48890795Sgshapiromci_new(rpool)
48990795Sgshapiro	SM_RPOOL_T *rpool;
49090795Sgshapiro{
49190795Sgshapiro	register MCI *mci;
49290795Sgshapiro
49390795Sgshapiro	if (rpool == NULL)
494168520Sgshapiro		mci = (MCI *) sm_malloc_x(sizeof(*mci));
49590795Sgshapiro	else
496168520Sgshapiro		mci = (MCI *) sm_rpool_malloc_x(rpool, sizeof(*mci));
497168520Sgshapiro	memset((char *) mci, '\0', sizeof(*mci));
49890795Sgshapiro	mci->mci_rpool = sm_rpool_new_x(NULL);
49990795Sgshapiro	mci->mci_macro.mac_rpool = mci->mci_rpool;
50090795Sgshapiro	return mci;
50190795Sgshapiro}
50290795Sgshapiro/*
50364565Sgshapiro**  MCI_MATCH -- check connection cache for a particular host
50490795Sgshapiro**
50590795Sgshapiro**	Parameters:
50690795Sgshapiro**		host -- host to look for.
50790795Sgshapiro**		m -- mailer.
50890795Sgshapiro**
50990795Sgshapiro**	Returns:
51090795Sgshapiro**		true iff open connection exists.
51164565Sgshapiro*/
51264565Sgshapiro
51364565Sgshapirobool
51464565Sgshapiromci_match(host, m)
51564565Sgshapiro	char *host;
51664565Sgshapiro	MAILER *m;
51764565Sgshapiro{
51864565Sgshapiro	register MCI *mci;
51964565Sgshapiro	register STAB *s;
52064565Sgshapiro
52171348Sgshapiro	if (m->m_mno < 0 || m->m_mno > MAXMAILERS)
52290795Sgshapiro		return false;
52364565Sgshapiro	s = stab(host, ST_MCI + m->m_mno, ST_FIND);
52464565Sgshapiro	if (s == NULL)
52590795Sgshapiro		return false;
52664565Sgshapiro
52764565Sgshapiro	mci = &s->s_mci;
52890795Sgshapiro	return mci->mci_state == MCIS_OPEN;
52964565Sgshapiro}
53090795Sgshapiro/*
53138032Speter**  MCI_SETSTAT -- set status codes in MCI structure.
53238032Speter**
53338032Speter**	Parameters:
53438032Speter**		mci -- the MCI structure to set.
53538032Speter**		xstat -- the exit status code.
53638032Speter**		dstat -- the DSN status code.
53738032Speter**		rstat -- the SMTP status code.
53838032Speter**
53938032Speter**	Returns:
54038032Speter**		none.
54138032Speter*/
54238032Speter
54338032Spetervoid
54438032Spetermci_setstat(mci, xstat, dstat, rstat)
54538032Speter	MCI *mci;
54638032Speter	int xstat;
54738032Speter	char *dstat;
54838032Speter	char *rstat;
54938032Speter{
55038032Speter	/* protocol errors should never be interpreted as sticky */
55138032Speter	if (xstat != EX_NOTSTICKY && xstat != EX_PROTOCOL)
55238032Speter		mci->mci_exitstat = xstat;
55338032Speter
55490795Sgshapiro	SM_FREE_CLR(mci->mci_status);
55590795Sgshapiro	if (dstat != NULL)
55690795Sgshapiro		mci->mci_status = sm_strdup_x(dstat);
55790795Sgshapiro
55890795Sgshapiro	SM_FREE_CLR(mci->mci_rstatus);
55938032Speter	if (rstat != NULL)
56090795Sgshapiro		mci->mci_rstatus = sm_strdup_x(rstat);
56138032Speter}
56290795Sgshapiro/*
56338032Speter**  MCI_DUMP -- dump the contents of an MCI structure.
56438032Speter**
56538032Speter**	Parameters:
566132946Sgshapiro**		fp -- output file pointer
56738032Speter**		mci -- the MCI structure to dump.
56838032Speter**
56938032Speter**	Returns:
57038032Speter**		none.
57138032Speter**
57238032Speter**	Side Effects:
57338032Speter**		none.
57438032Speter*/
57538032Speter
57638032Speterstruct mcifbits
57738032Speter{
57838032Speter	int	mcif_bit;	/* flag bit */
57938032Speter	char	*mcif_name;	/* flag name */
58038032Speter};
58164565Sgshapirostatic struct mcifbits	MciFlags[] =
58238032Speter{
58338032Speter	{ MCIF_VALID,		"VALID"		},
58438032Speter	{ MCIF_CACHED,		"CACHED"	},
58538032Speter	{ MCIF_ESMTP,		"ESMTP"		},
58638032Speter	{ MCIF_EXPN,		"EXPN"		},
58738032Speter	{ MCIF_SIZE,		"SIZE"		},
58838032Speter	{ MCIF_8BITMIME,	"8BITMIME"	},
58938032Speter	{ MCIF_7BIT,		"7BIT"		},
59038032Speter	{ MCIF_INHEADER,	"INHEADER"	},
59138032Speter	{ MCIF_CVT8TO7,		"CVT8TO7"	},
59238032Speter	{ MCIF_DSN,		"DSN"		},
59338032Speter	{ MCIF_8BITOK,		"8BITOK"	},
59438032Speter	{ MCIF_CVT7TO8,		"CVT7TO8"	},
59538032Speter	{ MCIF_INMIME,		"INMIME"	},
59690795Sgshapiro	{ MCIF_AUTH,		"AUTH"		},
597223067Sgshapiro	{ MCIF_AUTH2,		"AUTH2"		},
59890795Sgshapiro	{ MCIF_AUTHACT,		"AUTHACT"	},
59990795Sgshapiro	{ MCIF_ENHSTAT,		"ENHSTAT"	},
60090795Sgshapiro	{ MCIF_PIPELINED,	"PIPELINED"	},
60190795Sgshapiro#if STARTTLS
60290795Sgshapiro	{ MCIF_TLS,		"TLS"		},
60390795Sgshapiro	{ MCIF_TLSACT,		"TLSACT"	},
60490795Sgshapiro#endif /* STARTTLS */
60590795Sgshapiro	{ MCIF_DLVR_BY,		"DLVR_BY"	},
60671348Sgshapiro	{ 0,			NULL		}
60738032Speter};
60838032Speter
60938032Spetervoid
610132946Sgshapiromci_dump(fp, mci, logit)
611132946Sgshapiro	SM_FILE_T *fp;
61238032Speter	register MCI *mci;
61338032Speter	bool logit;
61438032Speter{
61538032Speter	register char *p;
61638032Speter	char *sep;
61738032Speter	char buf[4000];
61838032Speter
61938032Speter	sep = logit ? " " : "\n\t";
62038032Speter	p = buf;
62190795Sgshapiro	(void) sm_snprintf(p, SPACELEFT(buf, p), "MCI@%p: ", mci);
62238032Speter	p += strlen(p);
62338032Speter	if (mci == NULL)
62438032Speter	{
62590795Sgshapiro		(void) sm_snprintf(p, SPACELEFT(buf, p), "NULL");
62638032Speter		goto printit;
62738032Speter	}
62890795Sgshapiro	(void) sm_snprintf(p, SPACELEFT(buf, p), "flags=%lx", mci->mci_flags);
62938032Speter	p += strlen(p);
630120259Sgshapiro
631120259Sgshapiro	/*
632120259Sgshapiro	**  The following check is just for paranoia.  It protects the
633120259Sgshapiro	**  assignment in the if() clause. If there's not some minimum
634120259Sgshapiro	**  amount of space we can stop right now. The check will not
635120259Sgshapiro	**  trigger as long as sizeof(buf)=4000.
636120259Sgshapiro	*/
637120259Sgshapiro
638120259Sgshapiro	if (p >= buf + sizeof(buf) - 4)
639120259Sgshapiro		goto printit;
64038032Speter	if (mci->mci_flags != 0)
64138032Speter	{
64238032Speter		struct mcifbits *f;
64338032Speter
644120259Sgshapiro		*p++ = '<';	/* protected above */
64538032Speter		for (f = MciFlags; f->mcif_bit != 0; f++)
64638032Speter		{
64738032Speter			if (!bitset(f->mcif_bit, mci->mci_flags))
64838032Speter				continue;
64990795Sgshapiro			(void) sm_strlcpyn(p, SPACELEFT(buf, p), 2,
65090795Sgshapiro					   f->mcif_name, ",");
65138032Speter			p += strlen(p);
65238032Speter		}
65338032Speter		p[-1] = '>';
65438032Speter	}
65590795Sgshapiro
65690795Sgshapiro	/* Note: sm_snprintf() takes care of NULL arguments for %s */
65790795Sgshapiro	(void) sm_snprintf(p, SPACELEFT(buf, p),
65838032Speter		",%serrno=%d, herrno=%d, exitstat=%d, state=%d, pid=%d,%s",
65938032Speter		sep, mci->mci_errno, mci->mci_herrno,
66064565Sgshapiro		mci->mci_exitstat, mci->mci_state, (int) mci->mci_pid, sep);
66138032Speter	p += strlen(p);
66290795Sgshapiro	(void) sm_snprintf(p, SPACELEFT(buf, p),
66338032Speter		"maxsize=%ld, phase=%s, mailer=%s,%s",
66490795Sgshapiro		mci->mci_maxsize, mci->mci_phase,
66538032Speter		mci->mci_mailer == NULL ? "NULL" : mci->mci_mailer->m_name,
66638032Speter		sep);
66738032Speter	p += strlen(p);
66890795Sgshapiro	(void) sm_snprintf(p, SPACELEFT(buf, p),
66938032Speter		"status=%s, rstatus=%s,%s",
67090795Sgshapiro		mci->mci_status, mci->mci_rstatus, sep);
67138032Speter	p += strlen(p);
67290795Sgshapiro	(void) sm_snprintf(p, SPACELEFT(buf, p),
67338032Speter		"host=%s, lastuse=%s",
67490795Sgshapiro		mci->mci_host, ctime(&mci->mci_lastuse));
67538032Speterprintit:
67638032Speter	if (logit)
67738032Speter		sm_syslog(LOG_DEBUG, CurEnv->e_id, "%.1000s", buf);
67838032Speter	else
679132946Sgshapiro		(void) sm_io_fprintf(fp, SM_TIME_DEFAULT, "%s\n", buf);
68038032Speter}
68190795Sgshapiro/*
68238032Speter**  MCI_DUMP_ALL -- print the entire MCI cache
68338032Speter**
68438032Speter**	Parameters:
685132946Sgshapiro**		fp -- output file pointer
68638032Speter**		logit -- if set, log the result instead of printing
68738032Speter**			to stdout.
68838032Speter**
68938032Speter**	Returns:
69038032Speter**		none.
69138032Speter*/
69238032Speter
69338032Spetervoid
694132946Sgshapiromci_dump_all(fp, logit)
695132946Sgshapiro	SM_FILE_T *fp;
69638032Speter	bool logit;
69738032Speter{
69838032Speter	register int i;
69938032Speter
70038032Speter	if (MciCache == NULL)
70138032Speter		return;
70238032Speter
70338032Speter	for (i = 0; i < MaxMciCache; i++)
704132946Sgshapiro		mci_dump(fp, MciCache[i], logit);
70538032Speter}
70690795Sgshapiro/*
70742580Speter**  MCI_LOCK_HOST -- Lock host while sending.
70838032Speter**
70938032Speter**	If we are contacting a host, we'll need to
71038032Speter**	update the status information in the host status
71138032Speter**	file, and if we want to do that, we ought to have
71238032Speter**	locked it. This has the (according to some)
71338032Speter**	desirable effect of serializing connectivity with
71490795Sgshapiro**	remote hosts -- i.e.: one connection to a given
71538032Speter**	host at a time.
71638032Speter**
71738032Speter**	Parameters:
71838032Speter**		mci -- containing the host we want to lock.
71938032Speter**
72038032Speter**	Returns:
72164565Sgshapiro**		EX_OK	    -- got the lock.
72238032Speter**		EX_TEMPFAIL -- didn't get the lock.
72338032Speter*/
72438032Speter
72538032Speterint
72638032Spetermci_lock_host(mci)
72738032Speter	MCI *mci;
72838032Speter{
72938032Speter	if (mci == NULL)
73038032Speter	{
73138032Speter		if (tTd(56, 1))
73290795Sgshapiro			sm_dprintf("mci_lock_host: NULL mci\n");
73338032Speter		return EX_OK;
73438032Speter	}
73538032Speter
73638032Speter	if (!SingleThreadDelivery)
73738032Speter		return EX_OK;
73838032Speter
73938032Speter	return mci_lock_host_statfile(mci);
74038032Speter}
74138032Speter
74264565Sgshapirostatic int
74338032Spetermci_lock_host_statfile(mci)
74438032Speter	MCI *mci;
74538032Speter{
74664565Sgshapiro	int save_errno = errno;
74738032Speter	int retVal = EX_OK;
74898125Sgshapiro	char fname[MAXPATHLEN];
74938032Speter
75038032Speter	if (HostStatDir == NULL || mci->mci_host == NULL)
75138032Speter		return EX_OK;
75238032Speter
75338032Speter	if (tTd(56, 2))
75490795Sgshapiro		sm_dprintf("mci_lock_host: attempting to lock %s\n",
75590795Sgshapiro			   mci->mci_host);
75638032Speter
757168520Sgshapiro	if (mci_generate_persistent_path(mci->mci_host, fname, sizeof(fname),
75890795Sgshapiro					 true) < 0)
75938032Speter	{
76038032Speter		/* of course this should never happen */
76138032Speter		if (tTd(56, 2))
76290795Sgshapiro			sm_dprintf("mci_lock_host: Failed to generate host path for %s\n",
76390795Sgshapiro				   mci->mci_host);
76438032Speter
76538032Speter		retVal = EX_TEMPFAIL;
76638032Speter		goto cleanup;
76738032Speter	}
76838032Speter
76938032Speter	mci->mci_statfile = safefopen(fname, O_RDWR, FileMode,
77064565Sgshapiro				      SFF_NOLOCK|SFF_NOLINK|SFF_OPENASROOT|SFF_REGONLY|SFF_SAFEDIRPATH|SFF_CREAT);
77138032Speter
77238032Speter	if (mci->mci_statfile == NULL)
77338032Speter	{
77490795Sgshapiro		syserr("mci_lock_host: cannot create host lock file %s", fname);
77538032Speter		goto cleanup;
77638032Speter	}
77738032Speter
77890795Sgshapiro	if (!lockfile(sm_io_getinfo(mci->mci_statfile, SM_IO_WHAT_FD, NULL),
77990795Sgshapiro		      fname, "", LOCK_EX|LOCK_NB))
78038032Speter	{
78138032Speter		if (tTd(56, 2))
78290795Sgshapiro			sm_dprintf("mci_lock_host: couldn't get lock on %s\n",
78364565Sgshapiro				fname);
78490795Sgshapiro		(void) sm_io_close(mci->mci_statfile, SM_TIME_DEFAULT);
78538032Speter		mci->mci_statfile = NULL;
78638032Speter		retVal = EX_TEMPFAIL;
78738032Speter		goto cleanup;
78838032Speter	}
78938032Speter
79038032Speter	if (tTd(56, 12) && mci->mci_statfile != NULL)
79190795Sgshapiro		sm_dprintf("mci_lock_host: Sanity check -- lock is good\n");
79238032Speter
79338032Spetercleanup:
79464565Sgshapiro	errno = save_errno;
79538032Speter	return retVal;
79638032Speter}
79790795Sgshapiro/*
79838032Speter**  MCI_UNLOCK_HOST -- unlock host
79938032Speter**
80038032Speter**	Clean up the lock on a host, close the file, let
80138032Speter**	someone else use it.
80238032Speter**
80338032Speter**	Parameters:
80438032Speter**		mci -- us.
80538032Speter**
80638032Speter**	Returns:
80738032Speter**		nothing.
80838032Speter*/
80938032Speter
81038032Spetervoid
81138032Spetermci_unlock_host(mci)
81238032Speter	MCI *mci;
81338032Speter{
81464565Sgshapiro	int save_errno = errno;
81538032Speter
81638032Speter	if (mci == NULL)
81738032Speter	{
81838032Speter		if (tTd(56, 1))
81990795Sgshapiro			sm_dprintf("mci_unlock_host: NULL mci\n");
82038032Speter		return;
82138032Speter	}
82238032Speter
82338032Speter	if (HostStatDir == NULL || mci->mci_host == NULL)
82438032Speter		return;
82538032Speter
82638032Speter	if (!SingleThreadDelivery && mci_lock_host_statfile(mci) == EX_TEMPFAIL)
82738032Speter	{
82838032Speter		if (tTd(56, 1))
82990795Sgshapiro			sm_dprintf("mci_unlock_host: stat file already locked\n");
83038032Speter	}
83138032Speter	else
83238032Speter	{
83338032Speter		if (tTd(56, 2))
83490795Sgshapiro			sm_dprintf("mci_unlock_host: store prior to unlock\n");
83538032Speter		mci_store_persistent(mci);
83638032Speter	}
83738032Speter
83838032Speter	if (mci->mci_statfile != NULL)
83938032Speter	{
84090795Sgshapiro		(void) sm_io_close(mci->mci_statfile, SM_TIME_DEFAULT);
84138032Speter		mci->mci_statfile = NULL;
84238032Speter	}
84338032Speter
84464565Sgshapiro	errno = save_errno;
84538032Speter}
84690795Sgshapiro/*
84742580Speter**  MCI_LOAD_PERSISTENT -- load persistent host info
84838032Speter**
84938032Speter**	Load information about host that is kept
85038032Speter**	in common for all running sendmails.
85138032Speter**
85238032Speter**	Parameters:
85390795Sgshapiro**		mci -- the host/connection to load persistent info for.
85438032Speter**
85538032Speter**	Returns:
85690795Sgshapiro**		true -- lock was successful
85790795Sgshapiro**		false -- lock failed
85838032Speter*/
85938032Speter
86064565Sgshapirostatic bool
86138032Spetermci_load_persistent(mci)
86238032Speter	MCI *mci;
86338032Speter{
86464565Sgshapiro	int save_errno = errno;
86590795Sgshapiro	bool locked = true;
86690795Sgshapiro	SM_FILE_T *fp;
86798125Sgshapiro	char fname[MAXPATHLEN];
86838032Speter
86938032Speter	if (mci == NULL)
87038032Speter	{
87138032Speter		if (tTd(56, 1))
87290795Sgshapiro			sm_dprintf("mci_load_persistent: NULL mci\n");
87390795Sgshapiro		return true;
87438032Speter	}
87538032Speter
87638032Speter	if (IgnoreHostStatus || HostStatDir == NULL || mci->mci_host == NULL)
87790795Sgshapiro		return true;
87864565Sgshapiro
87938032Speter	/* Already have the persistent information in memory */
88038032Speter	if (SingleThreadDelivery && mci->mci_statfile != NULL)
88190795Sgshapiro		return true;
88238032Speter
88338032Speter	if (tTd(56, 1))
88490795Sgshapiro		sm_dprintf("mci_load_persistent: Attempting to load persistent information for %s\n",
88590795Sgshapiro			   mci->mci_host);
88664565Sgshapiro
887168520Sgshapiro	if (mci_generate_persistent_path(mci->mci_host, fname, sizeof(fname),
88890795Sgshapiro					 false) < 0)
88938032Speter	{
89038032Speter		/* Not much we can do if the file isn't there... */
89138032Speter		if (tTd(56, 1))
89290795Sgshapiro			sm_dprintf("mci_load_persistent: Couldn't generate host path\n");
89338032Speter		goto cleanup;
89438032Speter	}
89538032Speter
89638032Speter	fp = safefopen(fname, O_RDONLY, FileMode,
89738032Speter		       SFF_NOLOCK|SFF_NOLINK|SFF_OPENASROOT|SFF_REGONLY|SFF_SAFEDIRPATH);
89838032Speter	if (fp == NULL)
89938032Speter	{
90038032Speter		/* I can't think of any reason this should ever happen */
90138032Speter		if (tTd(56, 1))
90290795Sgshapiro			sm_dprintf("mci_load_persistent: open(%s): %s\n",
90390795Sgshapiro				fname, sm_errstring(errno));
90438032Speter		goto cleanup;
90538032Speter	}
90638032Speter
90738032Speter	FileName = fname;
90890795Sgshapiro	locked = lockfile(sm_io_getinfo(fp, SM_IO_WHAT_FD, NULL), fname, "",
90990795Sgshapiro			  LOCK_SH|LOCK_NB);
91064565Sgshapiro	if (locked)
91164565Sgshapiro	{
91264565Sgshapiro		(void) mci_read_persistent(fp, mci);
91390795Sgshapiro		(void) lockfile(sm_io_getinfo(fp, SM_IO_WHAT_FD, NULL), fname,
91490795Sgshapiro				"", LOCK_UN);
91564565Sgshapiro	}
91638032Speter	FileName = NULL;
91790795Sgshapiro	(void) sm_io_close(fp, SM_TIME_DEFAULT);
91838032Speter
91938032Spetercleanup:
92064565Sgshapiro	errno = save_errno;
92138032Speter	return locked;
92238032Speter}
92390795Sgshapiro/*
92438032Speter**  MCI_READ_PERSISTENT -- read persistent host status file
92538032Speter**
92638032Speter**	Parameters:
92738032Speter**		fp -- the file pointer to read.
92838032Speter**		mci -- the pointer to fill in.
92938032Speter**
93038032Speter**	Returns:
93138032Speter**		-1 -- if the file was corrupt.
93238032Speter**		0 -- otherwise.
93338032Speter**
93438032Speter**	Warning:
93538032Speter**		This code makes the assumption that this data
93638032Speter**		will be read in an atomic fashion, and that the data
93738032Speter**		was written in an atomic fashion.  Any other functioning
93838032Speter**		may lead to some form of insanity.  This should be
93938032Speter**		perfectly safe due to underlying stdio buffering.
94038032Speter*/
94138032Speter
94264565Sgshapirostatic int
94338032Spetermci_read_persistent(fp, mci)
94490795Sgshapiro	SM_FILE_T *fp;
94538032Speter	register MCI *mci;
94638032Speter{
94738032Speter	int ver;
94838032Speter	register char *p;
94938032Speter	int saveLineNumber = LineNumber;
95038032Speter	char buf[MAXLINE];
95138032Speter
95238032Speter	if (fp == NULL)
953159613Sgshapiro	{
95438032Speter		syserr("mci_read_persistent: NULL fp");
955159613Sgshapiro		/* NOTREACHED */
956159613Sgshapiro		return -1;
957159613Sgshapiro	}
95838032Speter	if (mci == NULL)
959159613Sgshapiro	{
96038032Speter		syserr("mci_read_persistent: NULL mci");
961159613Sgshapiro		/* NOTREACHED */
962159613Sgshapiro		return -1;
963159613Sgshapiro	}
96438032Speter	if (tTd(56, 93))
96538032Speter	{
96690795Sgshapiro		sm_dprintf("mci_read_persistent: fp=%lx, mci=",
96790795Sgshapiro			   (unsigned long) fp);
96838032Speter	}
96938032Speter
97090795Sgshapiro	SM_FREE_CLR(mci->mci_status);
97190795Sgshapiro	SM_FREE_CLR(mci->mci_rstatus);
97238032Speter
97390795Sgshapiro	sm_io_rewind(fp, SM_TIME_DEFAULT);
97438032Speter	ver = -1;
97538032Speter	LineNumber = 0;
976249729Sgshapiro	while (sm_io_fgets(fp, SM_TIME_DEFAULT, buf, sizeof(buf)) >= 0)
97738032Speter	{
97838032Speter		LineNumber++;
97938032Speter		p = strchr(buf, '\n');
98038032Speter		if (p != NULL)
98138032Speter			*p = '\0';
98238032Speter		switch (buf[0])
98338032Speter		{
98438032Speter		  case 'V':		/* version stamp */
98538032Speter			ver = atoi(&buf[1]);
98638032Speter			if (ver < 0 || ver > 0)
98738032Speter				syserr("Unknown host status version %d: %d max",
98838032Speter					ver, 0);
98938032Speter			break;
99038032Speter
99138032Speter		  case 'E':		/* UNIX error number */
99238032Speter			mci->mci_errno = atoi(&buf[1]);
99338032Speter			break;
99438032Speter
99538032Speter		  case 'H':		/* DNS error number */
99638032Speter			mci->mci_herrno = atoi(&buf[1]);
99738032Speter			break;
99838032Speter
99938032Speter		  case 'S':		/* UNIX exit status */
100038032Speter			mci->mci_exitstat = atoi(&buf[1]);
100138032Speter			break;
100238032Speter
100338032Speter		  case 'D':		/* DSN status */
100438032Speter			mci->mci_status = newstr(&buf[1]);
100538032Speter			break;
100638032Speter
100738032Speter		  case 'R':		/* SMTP status */
100838032Speter			mci->mci_rstatus = newstr(&buf[1]);
100938032Speter			break;
101038032Speter
101138032Speter		  case 'U':		/* last usage time */
101238032Speter			mci->mci_lastuse = atol(&buf[1]);
101338032Speter			break;
101438032Speter
101538032Speter		  case '.':		/* end of file */
101690795Sgshapiro			if (tTd(56, 93))
1017132946Sgshapiro				mci_dump(sm_debug_file(), mci, false);
101838032Speter			return 0;
101938032Speter
102038032Speter		  default:
102138032Speter			sm_syslog(LOG_CRIT, NOQID,
102238032Speter				  "%s: line %d: Unknown host status line \"%s\"",
102338032Speter				  FileName == NULL ? mci->mci_host : FileName,
102438032Speter				  LineNumber, buf);
102538032Speter			LineNumber = saveLineNumber;
102638032Speter			return -1;
102738032Speter		}
102838032Speter	}
102938032Speter	LineNumber = saveLineNumber;
103090795Sgshapiro	if (tTd(56, 93))
103190795Sgshapiro		sm_dprintf("incomplete (missing dot for EOF)\n");
103238032Speter	if (ver < 0)
103338032Speter		return -1;
103438032Speter	return 0;
103538032Speter}
103690795Sgshapiro/*
103738032Speter**  MCI_STORE_PERSISTENT -- Store persistent MCI information
103838032Speter**
103938032Speter**	Store information about host that is kept
104038032Speter**	in common for all running sendmails.
104138032Speter**
104238032Speter**	Parameters:
104338032Speter**		mci -- the host/connection to store persistent info for.
104438032Speter**
104538032Speter**	Returns:
104638032Speter**		none.
104738032Speter*/
104838032Speter
104938032Spetervoid
105038032Spetermci_store_persistent(mci)
105138032Speter	MCI *mci;
105238032Speter{
105364565Sgshapiro	int save_errno = errno;
105438032Speter
105538032Speter	if (mci == NULL)
105638032Speter	{
105738032Speter		if (tTd(56, 1))
105890795Sgshapiro			sm_dprintf("mci_store_persistent: NULL mci\n");
105938032Speter		return;
106038032Speter	}
106138032Speter
106238032Speter	if (HostStatDir == NULL || mci->mci_host == NULL)
106338032Speter		return;
106438032Speter
106538032Speter	if (tTd(56, 1))
106690795Sgshapiro		sm_dprintf("mci_store_persistent: Storing information for %s\n",
106790795Sgshapiro			   mci->mci_host);
106838032Speter
106938032Speter	if (mci->mci_statfile == NULL)
107038032Speter	{
107138032Speter		if (tTd(56, 1))
107290795Sgshapiro			sm_dprintf("mci_store_persistent: no statfile\n");
107338032Speter		return;
107438032Speter	}
107538032Speter
107690795Sgshapiro	sm_io_rewind(mci->mci_statfile, SM_TIME_DEFAULT);
107738032Speter#if !NOFTRUNCATE
107890795Sgshapiro	(void) ftruncate(sm_io_getinfo(mci->mci_statfile, SM_IO_WHAT_FD, NULL),
107990795Sgshapiro			 (off_t) 0);
108064565Sgshapiro#endif /* !NOFTRUNCATE */
108138032Speter
108290795Sgshapiro	(void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT, "V0\n");
108390795Sgshapiro	(void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT, "E%d\n",
108490795Sgshapiro			     mci->mci_errno);
108590795Sgshapiro	(void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT, "H%d\n",
108690795Sgshapiro			     mci->mci_herrno);
108790795Sgshapiro	(void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT, "S%d\n",
108890795Sgshapiro			     mci->mci_exitstat);
108938032Speter	if (mci->mci_status != NULL)
109090795Sgshapiro		(void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT,
109190795Sgshapiro				     "D%.80s\n",
109290795Sgshapiro				     denlstring(mci->mci_status, true, false));
109338032Speter	if (mci->mci_rstatus != NULL)
109490795Sgshapiro		(void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT,
109590795Sgshapiro				     "R%.80s\n",
109690795Sgshapiro				     denlstring(mci->mci_rstatus, true, false));
109790795Sgshapiro	(void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT, "U%ld\n",
109890795Sgshapiro			     (long)(mci->mci_lastuse));
109990795Sgshapiro	(void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT, ".\n");
110038032Speter
110190795Sgshapiro	(void) sm_io_flush(mci->mci_statfile, SM_TIME_DEFAULT);
110238032Speter
110364565Sgshapiro	errno = save_errno;
110438032Speter	return;
110538032Speter}
110690795Sgshapiro/*
110738032Speter**  MCI_TRAVERSE_PERSISTENT -- walk persistent status tree
110838032Speter**
110938032Speter**	Recursively find all the mci host files in `pathname'.  Default to
111038032Speter**		main host status directory if no path is provided.
111138032Speter**	Call (*action)(pathname, host) for each file found.
111238032Speter**
111338032Speter**	Note: all information is collected in a list before it is processed.
111438032Speter**	This may not be the best way to do it, but it seems safest, since
111538032Speter**	the file system would be touched while we are attempting to traverse
111638032Speter**	the directory tree otherwise (during purges).
111738032Speter**
111838032Speter**	Parameters:
111938032Speter**		action -- function to call on each node.  If returns < 0,
112038032Speter**			return immediately.
112138032Speter**		pathname -- root of tree.  If null, use main host status
112238032Speter**			directory.
112338032Speter**
112438032Speter**	Returns:
112538032Speter**		< 0 -- if any action routine returns a negative value, that
112638032Speter**			value is returned.
112738032Speter**		0 -- if we successfully went to completion.
112864565Sgshapiro**		> 0 -- return status from action()
112938032Speter*/
113038032Speter
113138032Speterint
113238032Spetermci_traverse_persistent(action, pathname)
1133141862Sgshapiro	int (*action)__P((char *, char *));
113438032Speter	char *pathname;
113538032Speter{
113638032Speter	struct stat statbuf;
113738032Speter	DIR *d;
113838032Speter	int ret;
113938032Speter
114038032Speter	if (pathname == NULL)
114138032Speter		pathname = HostStatDir;
114238032Speter	if (pathname == NULL)
114338032Speter		return -1;
114438032Speter
114538032Speter	if (tTd(56, 1))
114690795Sgshapiro		sm_dprintf("mci_traverse: pathname is %s\n", pathname);
114738032Speter
114838032Speter	ret = stat(pathname, &statbuf);
114938032Speter	if (ret < 0)
115038032Speter	{
115138032Speter		if (tTd(56, 2))
115290795Sgshapiro			sm_dprintf("mci_traverse: Failed to stat %s: %s\n",
115390795Sgshapiro				pathname, sm_errstring(errno));
115438032Speter		return ret;
115538032Speter	}
115638032Speter	if (S_ISDIR(statbuf.st_mode))
115738032Speter	{
115890795Sgshapiro		bool leftone, removedone;
115990795Sgshapiro		size_t len;
116090795Sgshapiro		char *newptr;
116138032Speter		struct dirent *e;
116298125Sgshapiro		char newpath[MAXPATHLEN];
1163157006Sgshapiro#if MAXPATHLEN <= MAXNAMLEN - 3
1164157006Sgshapiro ERROR "MAXPATHLEN <= MAXNAMLEN - 3"
1165157006Sgshapiro#endif /* MAXPATHLEN  <= MAXNAMLEN - 3 */
116638032Speter
116738032Speter		if ((d = opendir(pathname)) == NULL)
116838032Speter		{
116938032Speter			if (tTd(56, 2))
117090795Sgshapiro				sm_dprintf("mci_traverse: opendir %s: %s\n",
117190795Sgshapiro					pathname, sm_errstring(errno));
117238032Speter			return -1;
117338032Speter		}
1174182352Sgshapiro
1175182352Sgshapiro		/*
1176182352Sgshapiro		**  Reserve space for trailing '/', at least one
1177182352Sgshapiro		**  character, and '\0'
1178182352Sgshapiro		*/
1179182352Sgshapiro
1180182352Sgshapiro		len = sizeof(newpath) - 3;
118190795Sgshapiro		if (sm_strlcpy(newpath, pathname, len) >= len)
118238032Speter		{
1183182352Sgshapiro			int save_errno = errno;
1184182352Sgshapiro
118538032Speter			if (tTd(56, 2))
118690795Sgshapiro				sm_dprintf("mci_traverse: path \"%s\" too long",
118738032Speter					pathname);
1188182352Sgshapiro			(void) closedir(d);
1189182352Sgshapiro			errno = save_errno;
119038032Speter			return -1;
119138032Speter		}
119238032Speter		newptr = newpath + strlen(newpath);
119338032Speter		*newptr++ = '/';
1194182352Sgshapiro		len = sizeof(newpath) - (newptr - newpath);
119538032Speter
119664565Sgshapiro		/*
119764565Sgshapiro		**  repeat until no file has been removed
119864565Sgshapiro		**  this may become ugly when several files "expire"
119964565Sgshapiro		**  during these loops, but it's better than doing
120064565Sgshapiro		**  a rewinddir() inside the inner loop
120164565Sgshapiro		*/
120290795Sgshapiro
120364565Sgshapiro		do
120438032Speter		{
120590795Sgshapiro			leftone = removedone = false;
120664565Sgshapiro			while ((e = readdir(d)) != NULL)
120764565Sgshapiro			{
120864565Sgshapiro				if (e->d_name[0] == '.')
120964565Sgshapiro					continue;
121038032Speter
1211182352Sgshapiro				if (sm_strlcpy(newptr, e->d_name, len) >= len)
1212182352Sgshapiro				{
1213182352Sgshapiro					/* Skip truncated copies */
1214182352Sgshapiro					if (tTd(56, 4))
1215182352Sgshapiro					{
1216182352Sgshapiro						*newptr = '\0';
1217182352Sgshapiro						sm_dprintf("mci_traverse: path \"%s%s\" too long",
1218182352Sgshapiro							   newpath, e->d_name);
1219182352Sgshapiro					}
1220182352Sgshapiro					continue;
1221182352Sgshapiro				}
122238032Speter
122377352Sgshapiro				if (StopRequest)
122477352Sgshapiro					stop_sendmail();
122564565Sgshapiro				ret = mci_traverse_persistent(action, newpath);
122664565Sgshapiro				if (ret < 0)
122764565Sgshapiro					break;
122864565Sgshapiro				if (ret == 1)
122990795Sgshapiro					leftone = true;
123064565Sgshapiro				if (!removedone && ret == 0 &&
123164565Sgshapiro				    action == mci_purge_persistent)
123290795Sgshapiro					removedone = true;
123364565Sgshapiro			}
123438032Speter			if (ret < 0)
123538032Speter				break;
123690795Sgshapiro
123738032Speter			/*
123838032Speter			**  The following appears to be
123938032Speter			**  necessary during purges, since
124038032Speter			**  we modify the directory structure
124138032Speter			*/
124290795Sgshapiro
124364565Sgshapiro			if (removedone)
124438032Speter				rewinddir(d);
124564565Sgshapiro			if (tTd(56, 40))
124690795Sgshapiro				sm_dprintf("mci_traverse: path %s: ret %d removed %d left %d\n",
124764565Sgshapiro					pathname, ret, removedone, leftone);
124864565Sgshapiro		} while (removedone);
124938032Speter
125038032Speter		/* purge (or whatever) the directory proper */
125164565Sgshapiro		if (!leftone)
125264565Sgshapiro		{
125364565Sgshapiro			*--newptr = '\0';
125464565Sgshapiro			ret = (*action)(newpath, NULL);
125564565Sgshapiro		}
125664565Sgshapiro		(void) closedir(d);
125738032Speter	}
125838032Speter	else if (S_ISREG(statbuf.st_mode))
125938032Speter	{
126038032Speter		char *end = pathname + strlen(pathname) - 1;
126138032Speter		char *start;
126238032Speter		char *scan;
126338032Speter		char host[MAXHOSTNAMELEN];
126438032Speter		char *hostptr = host;
126564565Sgshapiro
126638032Speter		/*
126738032Speter		**  Reconstruct the host name from the path to the
126838032Speter		**  persistent information.
126938032Speter		*/
127038032Speter
127138032Speter		do
127238032Speter		{
127338032Speter			if (hostptr != host)
127438032Speter				*(hostptr++) = '.';
127538032Speter			start = end;
1276120259Sgshapiro			while (start > pathname && *(start - 1) != '/')
127738032Speter				start--;
127838032Speter
127938032Speter			if (*end == '.')
128038032Speter				end--;
128138032Speter
128238032Speter			for (scan = start; scan <= end; scan++)
128338032Speter				*(hostptr++) = *scan;
128438032Speter
128538032Speter			end = start - 2;
1286120259Sgshapiro		} while (end > pathname && *end == '.');
128738032Speter
128838032Speter		*hostptr = '\0';
128938032Speter
129038032Speter		/*
129142580Speter		**  Do something with the file containing the persistent
129242580Speter		**  information.
129338032Speter		*/
129490795Sgshapiro
129538032Speter		ret = (*action)(pathname, host);
129638032Speter	}
129738032Speter
129838032Speter	return ret;
129938032Speter}
130090795Sgshapiro/*
130164565Sgshapiro**  MCI_PRINT_PERSISTENT -- print persistent info
130238032Speter**
130338032Speter**	Dump the persistent information in the file 'pathname'
130438032Speter**
130538032Speter**	Parameters:
130638032Speter**		pathname -- the pathname to the status file.
130738032Speter**		hostname -- the corresponding host name.
130838032Speter**
130938032Speter**	Returns:
131038032Speter**		0
131138032Speter*/
131238032Speter
131338032Speterint
131438032Spetermci_print_persistent(pathname, hostname)
131538032Speter	char *pathname;
131638032Speter	char *hostname;
131738032Speter{
131890795Sgshapiro	static bool initflag = false;
131990795Sgshapiro	SM_FILE_T *fp;
132038032Speter	int width = Verbose ? 78 : 25;
132138032Speter	bool locked;
132238032Speter	MCI mcib;
132338032Speter
132438032Speter	/* skip directories */
132538032Speter	if (hostname == NULL)
132638032Speter		return 0;
132738032Speter
132877352Sgshapiro	if (StopRequest)
132977352Sgshapiro		stop_sendmail();
133077352Sgshapiro
133138032Speter	if (!initflag)
133238032Speter	{
133390795Sgshapiro		initflag = true;
133490795Sgshapiro		(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
133590795Sgshapiro				     " -------------- Hostname --------------- How long ago ---------Results---------\n");
133638032Speter	}
133738032Speter
133890795Sgshapiro	fp = safefopen(pathname, O_RDONLY, FileMode,
133938032Speter		       SFF_NOLOCK|SFF_NOLINK|SFF_OPENASROOT|SFF_REGONLY|SFF_SAFEDIRPATH);
134038032Speter
134138032Speter	if (fp == NULL)
134238032Speter	{
134338032Speter		if (tTd(56, 1))
134490795Sgshapiro			sm_dprintf("mci_print_persistent: cannot open %s: %s\n",
134590795Sgshapiro				pathname, sm_errstring(errno));
134638032Speter		return 0;
134738032Speter	}
134838032Speter
134938032Speter	FileName = pathname;
1350168520Sgshapiro	memset(&mcib, '\0', sizeof(mcib));
135138032Speter	if (mci_read_persistent(fp, &mcib) < 0)
135238032Speter	{
135338032Speter		syserr("%s: could not read status file", pathname);
135490795Sgshapiro		(void) sm_io_close(fp, SM_TIME_DEFAULT);
135538032Speter		FileName = NULL;
135638032Speter		return 0;
135738032Speter	}
135838032Speter
135990795Sgshapiro	locked = !lockfile(sm_io_getinfo(fp, SM_IO_WHAT_FD, NULL), pathname,
136090795Sgshapiro			   "", LOCK_SH|LOCK_NB);
136190795Sgshapiro	(void) sm_io_close(fp, SM_TIME_DEFAULT);
136238032Speter	FileName = NULL;
136338032Speter
136490795Sgshapiro	(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%c%-39s %12s ",
136590795Sgshapiro			     locked ? '*' : ' ', hostname,
136690795Sgshapiro			     pintvl(curtime() - mcib.mci_lastuse, true));
136738032Speter	if (mcib.mci_rstatus != NULL)
136890795Sgshapiro		(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%.*s\n", width,
136990795Sgshapiro				     mcib.mci_rstatus);
137038032Speter	else if (mcib.mci_exitstat == EX_TEMPFAIL && mcib.mci_errno != 0)
137190795Sgshapiro		(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
137290795Sgshapiro				     "Deferred: %.*s\n", width - 10,
137390795Sgshapiro				     sm_errstring(mcib.mci_errno));
137438032Speter	else if (mcib.mci_exitstat != 0)
137538032Speter	{
137690795Sgshapiro		char *exmsg = sm_sysexmsg(mcib.mci_exitstat);
137738032Speter
137890795Sgshapiro		if (exmsg == NULL)
137938032Speter		{
138038032Speter			char buf[80];
138138032Speter
1382168520Sgshapiro			(void) sm_snprintf(buf, sizeof(buf),
138390795Sgshapiro				"Unknown mailer error %d",
138438032Speter				mcib.mci_exitstat);
138590795Sgshapiro			(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%.*s\n",
138690795Sgshapiro					     width, buf);
138738032Speter		}
138838032Speter		else
138990795Sgshapiro			(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%.*s\n",
139090795Sgshapiro					     width, &exmsg[5]);
139138032Speter	}
139238032Speter	else if (mcib.mci_errno == 0)
139390795Sgshapiro		(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "OK\n");
139438032Speter	else
139590795Sgshapiro		(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "OK: %.*s\n",
139690795Sgshapiro				     width - 4, sm_errstring(mcib.mci_errno));
139738032Speter
139838032Speter	return 0;
139938032Speter}
140090795Sgshapiro/*
140138032Speter**  MCI_PURGE_PERSISTENT -- Remove a persistence status file.
140238032Speter**
140338032Speter**	Parameters:
140438032Speter**		pathname -- path to the status file.
140538032Speter**		hostname -- name of host corresponding to that file.
140638032Speter**			NULL if this is a directory (domain).
140738032Speter**
140838032Speter**	Returns:
140964565Sgshapiro**		0 -- ok
141064565Sgshapiro**		1 -- file not deleted (too young, incorrect format)
141164565Sgshapiro**		< 0 -- some error occurred
141238032Speter*/
141338032Speter
141438032Speterint
141538032Spetermci_purge_persistent(pathname, hostname)
141638032Speter	char *pathname;
141738032Speter	char *hostname;
141838032Speter{
141964565Sgshapiro	struct stat statbuf;
142038032Speter	char *end = pathname + strlen(pathname) - 1;
142164565Sgshapiro	int ret;
142238032Speter
142338032Speter	if (tTd(56, 1))
142490795Sgshapiro		sm_dprintf("mci_purge_persistent: purging %s\n", pathname);
142538032Speter
142664565Sgshapiro	ret = stat(pathname, &statbuf);
142764565Sgshapiro	if (ret < 0)
142864565Sgshapiro	{
142964565Sgshapiro		if (tTd(56, 2))
143090795Sgshapiro			sm_dprintf("mci_purge_persistent: Failed to stat %s: %s\n",
143190795Sgshapiro				pathname, sm_errstring(errno));
143264565Sgshapiro		return ret;
143364565Sgshapiro	}
143494337Sgshapiro	if (curtime() - statbuf.st_mtime <= MciInfoTimeout)
143564565Sgshapiro		return 1;
143638032Speter	if (hostname != NULL)
143738032Speter	{
143838032Speter		/* remove the file */
143990795Sgshapiro		ret = unlink(pathname);
144090795Sgshapiro		if (ret < 0)
144138032Speter		{
144290795Sgshapiro			if (LogLevel > 8)
144390795Sgshapiro				sm_syslog(LOG_ERR, NOQID,
144490795Sgshapiro					  "mci_purge_persistent: failed to unlink %s: %s",
144590795Sgshapiro					  pathname, sm_errstring(errno));
144638032Speter			if (tTd(56, 2))
144790795Sgshapiro				sm_dprintf("mci_purge_persistent: failed to unlink %s: %s\n",
144890795Sgshapiro					pathname, sm_errstring(errno));
144990795Sgshapiro			return ret;
145038032Speter		}
145138032Speter	}
145238032Speter	else
145338032Speter	{
145438032Speter		/* remove the directory */
145538032Speter		if (*end != '.')
145664565Sgshapiro			return 1;
145738032Speter
145838032Speter		if (tTd(56, 1))
145990795Sgshapiro			sm_dprintf("mci_purge_persistent: dpurge %s\n", pathname);
146038032Speter
146190795Sgshapiro		ret = rmdir(pathname);
146290795Sgshapiro		if (ret < 0)
146338032Speter		{
146438032Speter			if (tTd(56, 2))
146590795Sgshapiro				sm_dprintf("mci_purge_persistent: rmdir %s: %s\n",
146690795Sgshapiro					pathname, sm_errstring(errno));
146790795Sgshapiro			return ret;
146838032Speter		}
146938032Speter	}
147038032Speter
147138032Speter	return 0;
147238032Speter}
147390795Sgshapiro/*
147442580Speter**  MCI_GENERATE_PERSISTENT_PATH -- generate path from hostname
147538032Speter**
1476120259Sgshapiro**	Given `host', convert from a.b.c to $HostStatDir/c./b./a,
147738032Speter**	putting the result into `path'.  if `createflag' is set, intervening
147838032Speter**	directories will be created as needed.
147938032Speter**
148038032Speter**	Parameters:
148138032Speter**		host -- host name to convert from.
148238032Speter**		path -- place to store result.
148338032Speter**		pathlen -- length of path buffer.
148438032Speter**		createflag -- if set, create intervening directories as
148538032Speter**			needed.
148638032Speter**
148738032Speter**	Returns:
148838032Speter**		0 -- success
148938032Speter**		-1 -- failure
149038032Speter*/
149138032Speter
149264565Sgshapirostatic int
149338032Spetermci_generate_persistent_path(host, path, pathlen, createflag)
149438032Speter	const char *host;
149538032Speter	char *path;
149638032Speter	int pathlen;
149738032Speter	bool createflag;
149838032Speter{
149938032Speter	char *elem, *p, *x, ch;
150038032Speter	int ret = 0;
150138032Speter	int len;
150238032Speter	char t_host[MAXHOSTNAMELEN];
150364565Sgshapiro#if NETINET6
150464565Sgshapiro	struct in6_addr in6_addr;
150564565Sgshapiro#endif /* NETINET6 */
150638032Speter
150738032Speter	/*
150838032Speter	**  Rationality check the arguments.
150938032Speter	*/
151038032Speter
151138032Speter	if (host == NULL)
151238032Speter	{
151338032Speter		syserr("mci_generate_persistent_path: null host");
151438032Speter		return -1;
151538032Speter	}
151638032Speter	if (path == NULL)
151738032Speter	{
151838032Speter		syserr("mci_generate_persistent_path: null path");
151938032Speter		return -1;
152038032Speter	}
152138032Speter
152238032Speter	if (tTd(56, 80))
152390795Sgshapiro		sm_dprintf("mci_generate_persistent_path(%s): ", host);
152438032Speter
152538032Speter	if (*host == '\0' || *host == '.')
152638032Speter		return -1;
152738032Speter
152838032Speter	/* make certain this is not a bracketed host number */
1529168520Sgshapiro	if (strlen(host) > sizeof(t_host) - 1)
153038032Speter		return -1;
153138032Speter	if (host[0] == '[')
1532168520Sgshapiro		(void) sm_strlcpy(t_host, host + 1, sizeof(t_host));
153364565Sgshapiro	else
1534168520Sgshapiro		(void) sm_strlcpy(t_host, host, sizeof(t_host));
153538032Speter
153638032Speter	/*
153738032Speter	**  Delete any trailing dots from the hostname.
153838032Speter	**  Leave 'elem' pointing at the \0.
153938032Speter	*/
154038032Speter
154138032Speter	elem = t_host + strlen(t_host);
154238032Speter	while (elem > t_host &&
154338032Speter	       (elem[-1] == '.' || (host[0] == '[' && elem[-1] == ']')))
154438032Speter		*--elem = '\0';
154538032Speter
154638032Speter	/* check for bogus bracketed address */
154790795Sgshapiro	if (host[0] == '[')
154890795Sgshapiro	{
154990795Sgshapiro		bool good = false;
155064565Sgshapiro# if NETINET6
155190795Sgshapiro		if (anynet_pton(AF_INET6, t_host, &in6_addr) == 1)
155290795Sgshapiro			good = true;
155364565Sgshapiro# endif /* NETINET6 */
155464565Sgshapiro# if NETINET
155590795Sgshapiro		if (inet_addr(t_host) != INADDR_NONE)
155690795Sgshapiro			good = true;
155764565Sgshapiro# endif /* NETINET */
155890795Sgshapiro		if (!good)
155990795Sgshapiro			return -1;
156090795Sgshapiro	}
156138032Speter
156238032Speter	/* check for what will be the final length of the path */
156338032Speter	len = strlen(HostStatDir) + 2;
156438032Speter	for (p = (char *) t_host; *p != '\0'; p++)
156538032Speter	{
156638032Speter		if (*p == '.')
156738032Speter			len++;
156838032Speter		len++;
156938032Speter		if (p[0] == '.' && p[1] == '.')
157038032Speter			return -1;
157138032Speter	}
157238032Speter	if (len > pathlen || len < 1)
157338032Speter		return -1;
157490795Sgshapiro	(void) sm_strlcpy(path, HostStatDir, pathlen);
157538032Speter	p = path + strlen(path);
157638032Speter	while (elem > t_host)
157738032Speter	{
157838032Speter		if (!path_is_dir(path, createflag))
157938032Speter		{
158038032Speter			ret = -1;
158138032Speter			break;
158238032Speter		}
158338032Speter		elem--;
158438032Speter		while (elem >= t_host && *elem != '.')
158538032Speter			elem--;
158638032Speter		*p++ = '/';
158738032Speter		x = elem + 1;
158838032Speter		while ((ch = *x++) != '\0' && ch != '.')
158938032Speter		{
159038032Speter			if (isascii(ch) && isupper(ch))
159138032Speter				ch = tolower(ch);
159238032Speter			if (ch == '/')
159338032Speter				ch = ':';	/* / -> : */
159438032Speter			*p++ = ch;
159538032Speter		}
159638032Speter		if (elem >= t_host)
159738032Speter			*p++ = '.';
159838032Speter		*p = '\0';
159938032Speter	}
160038032Speter	if (tTd(56, 80))
160138032Speter	{
160238032Speter		if (ret < 0)
160390795Sgshapiro			sm_dprintf("FAILURE %d\n", ret);
160438032Speter		else
160590795Sgshapiro			sm_dprintf("SUCCESS %s\n", path);
160638032Speter	}
160764565Sgshapiro	return ret;
160838032Speter}
1609