mci.c revision 141887
138032Speter/*
2111826Sgshapiro * Copyright (c) 1998-2003 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 *
12120259Sgshapiro * $FreeBSD: head/contrib/sendmail/src/mci.c 141887 2005-02-14 08:04:08Z gshapiro $
1338032Speter */
1438032Speter
1564565Sgshapiro#include <sendmail.h>
1664565Sgshapiro
17141862SgshapiroSM_RCSID("@(#)$Id: mci.c,v 8.212 2004/08/04 21:11:31 ca Exp $")
1864565Sgshapiro
1964565Sgshapiro#if NETINET || NETINET6
2064565Sgshapiro# include <arpa/inet.h>
2164565Sgshapiro#endif /* NETINET || NETINET6 */
2264565Sgshapiro
2338032Speter#include <dirent.h>
2438032Speter
2564565Sgshapirostatic int	mci_generate_persistent_path __P((const char *, char *,
2664565Sgshapiro						  int, bool));
2764565Sgshapirostatic bool	mci_load_persistent __P((MCI *));
2864565Sgshapirostatic void	mci_uncache __P((MCI **, bool));
2964565Sgshapirostatic int	mci_lock_host_statfile __P((MCI *));
3090795Sgshapirostatic int	mci_read_persistent __P((SM_FILE_T *, MCI *));
3164565Sgshapiro
3238032Speter/*
3338032Speter**  Mail Connection Information (MCI) Caching Module.
3438032Speter**
3538032Speter**	There are actually two separate things cached.  The first is
3638032Speter**	the set of all open connections -- these are stored in a
3738032Speter**	(small) list.  The second is stored in the symbol table; it
3838032Speter**	has the overall status for all hosts, whether or not there
3938032Speter**	is a connection open currently.
4038032Speter**
4138032Speter**	There should never be too many connections open (since this
4238032Speter**	could flood the socket table), nor should a connection be
4338032Speter**	allowed to sit idly for too long.
4438032Speter**
4538032Speter**	MaxMciCache is the maximum number of open connections that
4638032Speter**	will be supported.
4738032Speter**
4838032Speter**	MciCacheTimeout is the time (in seconds) that a connection
4938032Speter**	is permitted to survive without activity.
5038032Speter**
5138032Speter**	We actually try any cached connections by sending a NOOP
5238032Speter**	before we use them; if the NOOP fails we close down the
5338032Speter**	connection and reopen it.  Note that this means that a
5438032Speter**	server SMTP that doesn't support NOOP will hose the
5538032Speter**	algorithm -- but that doesn't seem too likely.
5638032Speter**
5738032Speter**	The persistent MCI code is donated by Mark Lovell and Paul
5838032Speter**	Vixie.  It is based on the long term host status code in KJS
5938032Speter**	written by Paul but has been adapted by Mark to fit into the
6038032Speter**	MCI structure.
6138032Speter*/
6238032Speter
6364565Sgshapirostatic MCI	**MciCache;		/* the open connection cache */
6438032Speter
6590795Sgshapiro/*
6638032Speter**  MCI_CACHE -- enter a connection structure into the open connection cache
6738032Speter**
6838032Speter**	This may cause something else to be flushed.
6938032Speter**
7038032Speter**	Parameters:
7138032Speter**		mci -- the connection to cache.
7238032Speter**
7338032Speter**	Returns:
7438032Speter**		none.
7538032Speter*/
7638032Speter
7738032Spetervoid
7838032Spetermci_cache(mci)
7938032Speter	register MCI *mci;
8038032Speter{
8138032Speter	register MCI **mcislot;
8238032Speter
8338032Speter	/*
8438032Speter	**  Find the best slot.  This may cause expired connections
8538032Speter	**  to be closed.
8638032Speter	*/
8738032Speter
8838032Speter	mcislot = mci_scan(mci);
8938032Speter	if (mcislot == NULL)
9038032Speter	{
9138032Speter		/* we don't support caching */
9238032Speter		return;
9338032Speter	}
9438032Speter
9538032Speter	if (mci->mci_host == NULL)
9638032Speter		return;
9738032Speter
9838032Speter	/* if this is already cached, we are done */
9938032Speter	if (bitset(MCIF_CACHED, mci->mci_flags))
10038032Speter		return;
10138032Speter
10238032Speter	/* otherwise we may have to clear the slot */
10338032Speter	if (*mcislot != NULL)
10490795Sgshapiro		mci_uncache(mcislot, true);
10538032Speter
10638032Speter	if (tTd(42, 5))
10790795Sgshapiro		sm_dprintf("mci_cache: caching %p (%s) in slot %d\n",
10890795Sgshapiro			   mci, mci->mci_host, (int) (mcislot - MciCache));
10938032Speter	if (tTd(91, 100))
11038032Speter		sm_syslog(LOG_DEBUG, CurEnv->e_id,
11164565Sgshapiro			  "mci_cache: caching %lx (%.100s) in slot %d",
11290795Sgshapiro			  (unsigned long) mci, mci->mci_host,
11390795Sgshapiro			  (int) (mcislot - MciCache));
11438032Speter
11538032Speter	*mcislot = mci;
11638032Speter	mci->mci_flags |= MCIF_CACHED;
11738032Speter}
11890795Sgshapiro/*
11938032Speter**  MCI_SCAN -- scan the cache, flush junk, and return best slot
12038032Speter**
12138032Speter**	Parameters:
12238032Speter**		savemci -- never flush this one.  Can be null.
12338032Speter**
12438032Speter**	Returns:
12538032Speter**		The LRU (or empty) slot.
12638032Speter*/
12738032Speter
12838032SpeterMCI **
12938032Spetermci_scan(savemci)
13038032Speter	MCI *savemci;
13138032Speter{
13238032Speter	time_t now;
13338032Speter	register MCI **bestmci;
13438032Speter	register MCI *mci;
13538032Speter	register int i;
13638032Speter
13738032Speter	if (MaxMciCache <= 0)
13838032Speter	{
13938032Speter		/* we don't support caching */
14038032Speter		return NULL;
14138032Speter	}
14238032Speter
14338032Speter	if (MciCache == NULL)
14438032Speter	{
14538032Speter		/* first call */
14690795Sgshapiro		MciCache = (MCI **) sm_pmalloc_x(MaxMciCache * sizeof *MciCache);
14764565Sgshapiro		memset((char *) MciCache, '\0', MaxMciCache * sizeof *MciCache);
14864565Sgshapiro		return &MciCache[0];
14938032Speter	}
15038032Speter
15138032Speter	now = curtime();
15238032Speter	bestmci = &MciCache[0];
15338032Speter	for (i = 0; i < MaxMciCache; i++)
15438032Speter	{
15538032Speter		mci = MciCache[i];
15638032Speter		if (mci == NULL || mci->mci_state == MCIS_CLOSED)
15738032Speter		{
15838032Speter			bestmci = &MciCache[i];
15938032Speter			continue;
16038032Speter		}
16194337Sgshapiro		if ((mci->mci_lastuse + MciCacheTimeout <= now ||
16264565Sgshapiro		     (mci->mci_mailer != NULL &&
16364565Sgshapiro		      mci->mci_mailer->m_maxdeliveries > 0 &&
16464565Sgshapiro		      mci->mci_deliveries + 1 >= mci->mci_mailer->m_maxdeliveries))&&
16564565Sgshapiro		    mci != savemci)
16638032Speter		{
16764565Sgshapiro			/* connection idle too long or too many deliveries */
16838032Speter			bestmci = &MciCache[i];
16964565Sgshapiro
17064565Sgshapiro			/* close it */
17190795Sgshapiro			mci_uncache(bestmci, true);
17238032Speter			continue;
17338032Speter		}
17438032Speter		if (*bestmci == NULL)
17538032Speter			continue;
17638032Speter		if (mci->mci_lastuse < (*bestmci)->mci_lastuse)
17738032Speter			bestmci = &MciCache[i];
17838032Speter	}
17938032Speter	return bestmci;
18038032Speter}
18190795Sgshapiro/*
18238032Speter**  MCI_UNCACHE -- remove a connection from a slot.
18338032Speter**
18438032Speter**	May close a connection.
18538032Speter**
18638032Speter**	Parameters:
18738032Speter**		mcislot -- the slot to empty.
18890795Sgshapiro**		doquit -- if true, send QUIT protocol on this connection.
18990795Sgshapiro**			  if false, we are assumed to be in a forked child;
19038032Speter**				all we want to do is close the file(s).
19138032Speter**
19238032Speter**	Returns:
19338032Speter**		none.
19438032Speter*/
19538032Speter
19664565Sgshapirostatic void
19738032Spetermci_uncache(mcislot, doquit)
19838032Speter	register MCI **mcislot;
19938032Speter	bool doquit;
20038032Speter{
20138032Speter	register MCI *mci;
20238032Speter	extern ENVELOPE BlankEnvelope;
20338032Speter
20438032Speter	mci = *mcislot;
20538032Speter	if (mci == NULL)
20638032Speter		return;
20738032Speter	*mcislot = NULL;
20838032Speter	if (mci->mci_host == NULL)
20938032Speter		return;
21038032Speter
21138032Speter	mci_unlock_host(mci);
21238032Speter
21338032Speter	if (tTd(42, 5))
21490795Sgshapiro		sm_dprintf("mci_uncache: uncaching %p (%s) from slot %d (%d)\n",
21590795Sgshapiro			   mci, mci->mci_host, (int) (mcislot - MciCache),
21690795Sgshapiro			   doquit);
21738032Speter	if (tTd(91, 100))
21838032Speter		sm_syslog(LOG_DEBUG, CurEnv->e_id,
21964565Sgshapiro			  "mci_uncache: uncaching %lx (%.100s) from slot %d (%d)",
22090795Sgshapiro			  (unsigned long) mci, mci->mci_host,
22190795Sgshapiro			  (int) (mcislot - MciCache), doquit);
22238032Speter
22364565Sgshapiro	mci->mci_deliveries = 0;
22438032Speter	if (doquit)
22538032Speter	{
22638032Speter		message("Closing connection to %s", mci->mci_host);
22738032Speter
22838032Speter		mci->mci_flags &= ~MCIF_CACHED;
22938032Speter
23038032Speter		/* only uses the envelope to flush the transcript file */
23138032Speter		if (mci->mci_state != MCIS_CLOSED)
23238032Speter			smtpquit(mci->mci_mailer, mci, &BlankEnvelope);
23390795Sgshapiro#if XLA
23438032Speter		xla_host_end(mci->mci_host);
23590795Sgshapiro#endif /* XLA */
23638032Speter	}
23738032Speter	else
23838032Speter	{
23938032Speter		if (mci->mci_in != NULL)
24090795Sgshapiro			(void) sm_io_close(mci->mci_in, SM_TIME_DEFAULT);
24138032Speter		if (mci->mci_out != NULL)
24290795Sgshapiro			(void) sm_io_close(mci->mci_out, SM_TIME_DEFAULT);
24338032Speter		mci->mci_in = mci->mci_out = NULL;
24438032Speter		mci->mci_state = MCIS_CLOSED;
24538032Speter		mci->mci_exitstat = EX_OK;
24638032Speter		mci->mci_errno = 0;
24738032Speter		mci->mci_flags = 0;
24890795Sgshapiro
24990795Sgshapiro		mci->mci_retryrcpt = false;
25090795Sgshapiro		mci->mci_tolist = NULL;
25190795Sgshapiro#if PIPELINING
25290795Sgshapiro		mci->mci_okrcpts = 0;
25390795Sgshapiro#endif /* PIPELINING */
25438032Speter	}
25590795Sgshapiro
25690795Sgshapiro	SM_FREE_CLR(mci->mci_status);
25790795Sgshapiro	SM_FREE_CLR(mci->mci_rstatus);
25890795Sgshapiro	SM_FREE_CLR(mci->mci_heloname);
25990795Sgshapiro	if (mci->mci_rpool != NULL)
26090795Sgshapiro	{
26190795Sgshapiro		sm_rpool_free(mci->mci_rpool);
26290795Sgshapiro		mci->mci_macro.mac_rpool = NULL;
26390795Sgshapiro		mci->mci_rpool = NULL;
26490795Sgshapiro	}
26538032Speter}
26690795Sgshapiro/*
26738032Speter**  MCI_FLUSH -- flush the entire cache
26838032Speter**
26938032Speter**	Parameters:
27090795Sgshapiro**		doquit -- if true, send QUIT protocol.
27190795Sgshapiro**			  if false, just close the connection.
27238032Speter**		allbut -- but leave this one open.
27338032Speter**
27438032Speter**	Returns:
27538032Speter**		none.
27638032Speter*/
27738032Speter
27838032Spetervoid
27938032Spetermci_flush(doquit, allbut)
28038032Speter	bool doquit;
28138032Speter	MCI *allbut;
28238032Speter{
28338032Speter	register int i;
28438032Speter
28538032Speter	if (MciCache == NULL)
28638032Speter		return;
28738032Speter
28838032Speter	for (i = 0; i < MaxMciCache; i++)
28971348Sgshapiro	{
29038032Speter		if (allbut != MciCache[i])
29138032Speter			mci_uncache(&MciCache[i], doquit);
29271348Sgshapiro	}
29338032Speter}
29490795Sgshapiro/*
29538032Speter**  MCI_GET -- get information about a particular host
29690795Sgshapiro**
29790795Sgshapiro**	Parameters:
29890795Sgshapiro**		host -- host to look for.
29990795Sgshapiro**		m -- mailer.
30090795Sgshapiro**
30190795Sgshapiro**	Returns:
30290795Sgshapiro**		mci for this host (might be new).
30338032Speter*/
30438032Speter
30538032SpeterMCI *
30638032Spetermci_get(host, m)
30738032Speter	char *host;
30838032Speter	MAILER *m;
30938032Speter{
31038032Speter	register MCI *mci;
31138032Speter	register STAB *s;
31238032Speter	extern SOCKADDR CurHostAddr;
31338032Speter
31438032Speter	/* clear CurHostAddr so we don't get a bogus address with this name */
31564565Sgshapiro	memset(&CurHostAddr, '\0', sizeof CurHostAddr);
31638032Speter
31738032Speter	/* clear out any expired connections */
31838032Speter	(void) mci_scan(NULL);
31938032Speter
32038032Speter	if (m->m_mno < 0)
32171348Sgshapiro		syserr("!negative mno %d (%s)", m->m_mno, m->m_name);
32264565Sgshapiro
32338032Speter	s = stab(host, ST_MCI + m->m_mno, ST_ENTER);
32438032Speter	mci = &s->s_mci;
32538032Speter
32690795Sgshapiro	/* initialize per-message data */
32790795Sgshapiro	mci->mci_retryrcpt = false;
32890795Sgshapiro	mci->mci_tolist = NULL;
32990795Sgshapiro#if PIPELINING
33090795Sgshapiro	mci->mci_okrcpts = 0;
33190795Sgshapiro#endif /* PIPELINING */
33290795Sgshapiro
33390795Sgshapiro	if (mci->mci_rpool == NULL)
33490795Sgshapiro		mci->mci_rpool = sm_rpool_new_x(NULL);
33590795Sgshapiro
33690795Sgshapiro	if (mci->mci_macro.mac_rpool == NULL)
33790795Sgshapiro		mci->mci_macro.mac_rpool = mci->mci_rpool;
33890795Sgshapiro
33964565Sgshapiro	/*
34090795Sgshapiro	**  We don't need to load the persistent data if we have data
34164565Sgshapiro	**  already loaded in the cache.
34264565Sgshapiro	*/
34364565Sgshapiro
34464565Sgshapiro	if (mci->mci_host == NULL &&
34564565Sgshapiro	    (mci->mci_host = s->s_name) != NULL &&
34664565Sgshapiro	    !mci_load_persistent(mci))
34738032Speter	{
34838032Speter		if (tTd(42, 2))
34990795Sgshapiro			sm_dprintf("mci_get(%s %s): lock failed\n",
35064565Sgshapiro				host, m->m_name);
35138032Speter		mci->mci_exitstat = EX_TEMPFAIL;
35238032Speter		mci->mci_state = MCIS_CLOSED;
35338032Speter		mci->mci_statfile = NULL;
35438032Speter		return mci;
35538032Speter	}
35638032Speter
35738032Speter	if (tTd(42, 2))
35838032Speter	{
35990795Sgshapiro		sm_dprintf("mci_get(%s %s): mci_state=%d, _flags=%lx, _exitstat=%d, _errno=%d\n",
36038032Speter			host, m->m_name, mci->mci_state, mci->mci_flags,
36138032Speter			mci->mci_exitstat, mci->mci_errno);
36238032Speter	}
36338032Speter
36438032Speter	if (mci->mci_state == MCIS_OPEN)
36538032Speter	{
36638032Speter		/* poke the connection to see if it's still alive */
36738032Speter		(void) smtpprobe(mci);
36838032Speter
36938032Speter		/* reset the stored state in the event of a timeout */
37038032Speter		if (mci->mci_state != MCIS_OPEN)
37138032Speter		{
37238032Speter			mci->mci_errno = 0;
37338032Speter			mci->mci_exitstat = EX_OK;
37438032Speter			mci->mci_state = MCIS_CLOSED;
37538032Speter		}
37638032Speter		else
37738032Speter		{
37890795Sgshapiro			/* get peer host address */
37938032Speter			/* (this should really be in the mci struct) */
38038032Speter			SOCKADDR_LEN_T socklen = sizeof CurHostAddr;
38138032Speter
38290795Sgshapiro			(void) getpeername(sm_io_getinfo(mci->mci_in,
38390795Sgshapiro							 SM_IO_WHAT_FD, NULL),
38438032Speter				(struct sockaddr *) &CurHostAddr, &socklen);
38538032Speter		}
38638032Speter	}
38738032Speter	if (mci->mci_state == MCIS_CLOSED)
38838032Speter	{
38938032Speter		time_t now = curtime();
39038032Speter
39138032Speter		/* if this info is stale, ignore it */
39294337Sgshapiro		if (mci->mci_lastuse + MciInfoTimeout <= now)
39338032Speter		{
39438032Speter			mci->mci_lastuse = now;
39538032Speter			mci->mci_errno = 0;
39638032Speter			mci->mci_exitstat = EX_OK;
39738032Speter		}
39838032Speter	}
39938032Speter
40038032Speter	return mci;
40138032Speter}
402141887Sgshapiro
40390795Sgshapiro/*
404141887Sgshapiro**  MCI_CLOSE -- (forcefully) close files used for a connection.
405141887Sgshapiro**	Note: this is a last resort, usually smtpquit() or endmailer()
406141887Sgshapiro**		should be used to close a connection.
407141887Sgshapiro**
408141887Sgshapiro**	Parameters:
409141887Sgshapiro**		mci -- the connection to close.
410141887Sgshapiro**		where -- where has this been called?
411141887Sgshapiro**
412141887Sgshapiro**	Returns:
413141887Sgshapiro**		none.
414141887Sgshapiro*/
415141887Sgshapiro
416141887Sgshapirovoid
417141887Sgshapiromci_close(mci, where)
418141887Sgshapiro	MCI *mci;
419141887Sgshapiro	char *where;
420141887Sgshapiro{
421141887Sgshapiro	bool dumped;
422141887Sgshapiro
423141887Sgshapiro	if (mci == NULL)
424141887Sgshapiro		return;
425141887Sgshapiro	dumped = false;
426141887Sgshapiro	if (mci->mci_out != NULL)
427141887Sgshapiro	{
428141887Sgshapiro		if (tTd(56, 1))
429141887Sgshapiro		{
430141887Sgshapiro			sm_dprintf("mci_close: mci_out!=NULL, where=%s\n",
431141887Sgshapiro				where);
432141887Sgshapiro			mci_dump(sm_debug_file(), mci, false);
433141887Sgshapiro			dumped = true;
434141887Sgshapiro		}
435141887Sgshapiro		(void) sm_io_close(mci->mci_out, SM_TIME_DEFAULT);
436141887Sgshapiro		mci->mci_out = NULL;
437141887Sgshapiro	}
438141887Sgshapiro	if (mci->mci_in != NULL)
439141887Sgshapiro	{
440141887Sgshapiro		if (tTd(56, 1))
441141887Sgshapiro		{
442141887Sgshapiro			sm_dprintf("mci_close: mci_in!=NULL, where=%s\n",
443141887Sgshapiro				where);
444141887Sgshapiro			if (!dumped)
445141887Sgshapiro				mci_dump(sm_debug_file(), mci, false);
446141887Sgshapiro		}
447141887Sgshapiro		(void) sm_io_close(mci->mci_in, SM_TIME_DEFAULT);
448141887Sgshapiro		mci->mci_in = NULL;
449141887Sgshapiro	}
450141887Sgshapiro	mci->mci_state = MCIS_CLOSED;
451141887Sgshapiro}
452141887Sgshapiro
453141887Sgshapiro/*
45490795Sgshapiro**  MCI_NEW -- allocate new MCI structure
45590795Sgshapiro**
45690795Sgshapiro**	Parameters:
45790795Sgshapiro**		rpool -- if non-NULL: allocate from that rpool.
45890795Sgshapiro**
45990795Sgshapiro**	Returns:
46090795Sgshapiro**		mci (new).
46190795Sgshapiro*/
46290795Sgshapiro
46390795SgshapiroMCI *
46490795Sgshapiromci_new(rpool)
46590795Sgshapiro	SM_RPOOL_T *rpool;
46690795Sgshapiro{
46790795Sgshapiro	register MCI *mci;
46890795Sgshapiro
46990795Sgshapiro	if (rpool == NULL)
47090795Sgshapiro		mci = (MCI *) sm_malloc_x(sizeof *mci);
47190795Sgshapiro	else
47290795Sgshapiro		mci = (MCI *) sm_rpool_malloc_x(rpool, sizeof *mci);
47390795Sgshapiro	memset((char *) mci, '\0', sizeof *mci);
47490795Sgshapiro	mci->mci_rpool = sm_rpool_new_x(NULL);
47590795Sgshapiro	mci->mci_macro.mac_rpool = mci->mci_rpool;
47690795Sgshapiro	return mci;
47790795Sgshapiro}
47890795Sgshapiro/*
47964565Sgshapiro**  MCI_MATCH -- check connection cache for a particular host
48090795Sgshapiro**
48190795Sgshapiro**	Parameters:
48290795Sgshapiro**		host -- host to look for.
48390795Sgshapiro**		m -- mailer.
48490795Sgshapiro**
48590795Sgshapiro**	Returns:
48690795Sgshapiro**		true iff open connection exists.
48764565Sgshapiro*/
48864565Sgshapiro
48964565Sgshapirobool
49064565Sgshapiromci_match(host, m)
49164565Sgshapiro	char *host;
49264565Sgshapiro	MAILER *m;
49364565Sgshapiro{
49464565Sgshapiro	register MCI *mci;
49564565Sgshapiro	register STAB *s;
49664565Sgshapiro
49771348Sgshapiro	if (m->m_mno < 0 || m->m_mno > MAXMAILERS)
49890795Sgshapiro		return false;
49964565Sgshapiro	s = stab(host, ST_MCI + m->m_mno, ST_FIND);
50064565Sgshapiro	if (s == NULL)
50190795Sgshapiro		return false;
50264565Sgshapiro
50364565Sgshapiro	mci = &s->s_mci;
50490795Sgshapiro	return mci->mci_state == MCIS_OPEN;
50564565Sgshapiro}
50690795Sgshapiro/*
50738032Speter**  MCI_SETSTAT -- set status codes in MCI structure.
50838032Speter**
50938032Speter**	Parameters:
51038032Speter**		mci -- the MCI structure to set.
51138032Speter**		xstat -- the exit status code.
51238032Speter**		dstat -- the DSN status code.
51338032Speter**		rstat -- the SMTP status code.
51438032Speter**
51538032Speter**	Returns:
51638032Speter**		none.
51738032Speter*/
51838032Speter
51938032Spetervoid
52038032Spetermci_setstat(mci, xstat, dstat, rstat)
52138032Speter	MCI *mci;
52238032Speter	int xstat;
52338032Speter	char *dstat;
52438032Speter	char *rstat;
52538032Speter{
52638032Speter	/* protocol errors should never be interpreted as sticky */
52738032Speter	if (xstat != EX_NOTSTICKY && xstat != EX_PROTOCOL)
52838032Speter		mci->mci_exitstat = xstat;
52938032Speter
53090795Sgshapiro	SM_FREE_CLR(mci->mci_status);
53190795Sgshapiro	if (dstat != NULL)
53290795Sgshapiro		mci->mci_status = sm_strdup_x(dstat);
53390795Sgshapiro
53490795Sgshapiro	SM_FREE_CLR(mci->mci_rstatus);
53538032Speter	if (rstat != NULL)
53690795Sgshapiro		mci->mci_rstatus = sm_strdup_x(rstat);
53738032Speter}
53890795Sgshapiro/*
53938032Speter**  MCI_DUMP -- dump the contents of an MCI structure.
54038032Speter**
54138032Speter**	Parameters:
542132946Sgshapiro**		fp -- output file pointer
54338032Speter**		mci -- the MCI structure to dump.
54438032Speter**
54538032Speter**	Returns:
54638032Speter**		none.
54738032Speter**
54838032Speter**	Side Effects:
54938032Speter**		none.
55038032Speter*/
55138032Speter
55238032Speterstruct mcifbits
55338032Speter{
55438032Speter	int	mcif_bit;	/* flag bit */
55538032Speter	char	*mcif_name;	/* flag name */
55638032Speter};
55764565Sgshapirostatic struct mcifbits	MciFlags[] =
55838032Speter{
55938032Speter	{ MCIF_VALID,		"VALID"		},
56038032Speter	{ MCIF_CACHED,		"CACHED"	},
56138032Speter	{ MCIF_ESMTP,		"ESMTP"		},
56238032Speter	{ MCIF_EXPN,		"EXPN"		},
56338032Speter	{ MCIF_SIZE,		"SIZE"		},
56438032Speter	{ MCIF_8BITMIME,	"8BITMIME"	},
56538032Speter	{ MCIF_7BIT,		"7BIT"		},
56638032Speter	{ MCIF_INHEADER,	"INHEADER"	},
56738032Speter	{ MCIF_CVT8TO7,		"CVT8TO7"	},
56838032Speter	{ MCIF_DSN,		"DSN"		},
56938032Speter	{ MCIF_8BITOK,		"8BITOK"	},
57038032Speter	{ MCIF_CVT7TO8,		"CVT7TO8"	},
57138032Speter	{ MCIF_INMIME,		"INMIME"	},
57290795Sgshapiro	{ MCIF_AUTH,		"AUTH"		},
57390795Sgshapiro	{ MCIF_AUTHACT,		"AUTHACT"	},
57490795Sgshapiro	{ MCIF_ENHSTAT,		"ENHSTAT"	},
57590795Sgshapiro	{ MCIF_PIPELINED,	"PIPELINED"	},
57690795Sgshapiro#if STARTTLS
57790795Sgshapiro	{ MCIF_TLS,		"TLS"		},
57890795Sgshapiro	{ MCIF_TLSACT,		"TLSACT"	},
57990795Sgshapiro#endif /* STARTTLS */
58090795Sgshapiro	{ MCIF_DLVR_BY,		"DLVR_BY"	},
58171348Sgshapiro	{ 0,			NULL		}
58238032Speter};
58338032Speter
58438032Spetervoid
585132946Sgshapiromci_dump(fp, mci, logit)
586132946Sgshapiro	SM_FILE_T *fp;
58738032Speter	register MCI *mci;
58838032Speter	bool logit;
58938032Speter{
59038032Speter	register char *p;
59138032Speter	char *sep;
59238032Speter	char buf[4000];
59338032Speter
59438032Speter	sep = logit ? " " : "\n\t";
59538032Speter	p = buf;
59690795Sgshapiro	(void) sm_snprintf(p, SPACELEFT(buf, p), "MCI@%p: ", mci);
59738032Speter	p += strlen(p);
59838032Speter	if (mci == NULL)
59938032Speter	{
60090795Sgshapiro		(void) sm_snprintf(p, SPACELEFT(buf, p), "NULL");
60138032Speter		goto printit;
60238032Speter	}
60390795Sgshapiro	(void) sm_snprintf(p, SPACELEFT(buf, p), "flags=%lx", mci->mci_flags);
60438032Speter	p += strlen(p);
605120259Sgshapiro
606120259Sgshapiro	/*
607120259Sgshapiro	**  The following check is just for paranoia.  It protects the
608120259Sgshapiro	**  assignment in the if() clause. If there's not some minimum
609120259Sgshapiro	**  amount of space we can stop right now. The check will not
610120259Sgshapiro	**  trigger as long as sizeof(buf)=4000.
611120259Sgshapiro	*/
612120259Sgshapiro
613120259Sgshapiro	if (p >= buf + sizeof(buf) - 4)
614120259Sgshapiro		goto printit;
61538032Speter	if (mci->mci_flags != 0)
61638032Speter	{
61738032Speter		struct mcifbits *f;
61838032Speter
619120259Sgshapiro		*p++ = '<';	/* protected above */
62038032Speter		for (f = MciFlags; f->mcif_bit != 0; f++)
62138032Speter		{
62238032Speter			if (!bitset(f->mcif_bit, mci->mci_flags))
62338032Speter				continue;
62490795Sgshapiro			(void) sm_strlcpyn(p, SPACELEFT(buf, p), 2,
62590795Sgshapiro					   f->mcif_name, ",");
62638032Speter			p += strlen(p);
62738032Speter		}
62838032Speter		p[-1] = '>';
62938032Speter	}
63090795Sgshapiro
63190795Sgshapiro	/* Note: sm_snprintf() takes care of NULL arguments for %s */
63290795Sgshapiro	(void) sm_snprintf(p, SPACELEFT(buf, p),
63338032Speter		",%serrno=%d, herrno=%d, exitstat=%d, state=%d, pid=%d,%s",
63438032Speter		sep, mci->mci_errno, mci->mci_herrno,
63564565Sgshapiro		mci->mci_exitstat, mci->mci_state, (int) mci->mci_pid, sep);
63638032Speter	p += strlen(p);
63790795Sgshapiro	(void) sm_snprintf(p, SPACELEFT(buf, p),
63838032Speter		"maxsize=%ld, phase=%s, mailer=%s,%s",
63990795Sgshapiro		mci->mci_maxsize, mci->mci_phase,
64038032Speter		mci->mci_mailer == NULL ? "NULL" : mci->mci_mailer->m_name,
64138032Speter		sep);
64238032Speter	p += strlen(p);
64390795Sgshapiro	(void) sm_snprintf(p, SPACELEFT(buf, p),
64438032Speter		"status=%s, rstatus=%s,%s",
64590795Sgshapiro		mci->mci_status, mci->mci_rstatus, sep);
64638032Speter	p += strlen(p);
64790795Sgshapiro	(void) sm_snprintf(p, SPACELEFT(buf, p),
64838032Speter		"host=%s, lastuse=%s",
64990795Sgshapiro		mci->mci_host, ctime(&mci->mci_lastuse));
65038032Speterprintit:
65138032Speter	if (logit)
65238032Speter		sm_syslog(LOG_DEBUG, CurEnv->e_id, "%.1000s", buf);
65338032Speter	else
654132946Sgshapiro		(void) sm_io_fprintf(fp, SM_TIME_DEFAULT, "%s\n", buf);
65538032Speter}
65690795Sgshapiro/*
65738032Speter**  MCI_DUMP_ALL -- print the entire MCI cache
65838032Speter**
65938032Speter**	Parameters:
660132946Sgshapiro**		fp -- output file pointer
66138032Speter**		logit -- if set, log the result instead of printing
66238032Speter**			to stdout.
66338032Speter**
66438032Speter**	Returns:
66538032Speter**		none.
66638032Speter*/
66738032Speter
66838032Spetervoid
669132946Sgshapiromci_dump_all(fp, logit)
670132946Sgshapiro	SM_FILE_T *fp;
67138032Speter	bool logit;
67238032Speter{
67338032Speter	register int i;
67438032Speter
67538032Speter	if (MciCache == NULL)
67638032Speter		return;
67738032Speter
67838032Speter	for (i = 0; i < MaxMciCache; i++)
679132946Sgshapiro		mci_dump(fp, MciCache[i], logit);
68038032Speter}
68190795Sgshapiro/*
68242580Speter**  MCI_LOCK_HOST -- Lock host while sending.
68338032Speter**
68438032Speter**	If we are contacting a host, we'll need to
68538032Speter**	update the status information in the host status
68638032Speter**	file, and if we want to do that, we ought to have
68738032Speter**	locked it. This has the (according to some)
68838032Speter**	desirable effect of serializing connectivity with
68990795Sgshapiro**	remote hosts -- i.e.: one connection to a given
69038032Speter**	host at a time.
69138032Speter**
69238032Speter**	Parameters:
69338032Speter**		mci -- containing the host we want to lock.
69438032Speter**
69538032Speter**	Returns:
69664565Sgshapiro**		EX_OK	    -- got the lock.
69738032Speter**		EX_TEMPFAIL -- didn't get the lock.
69838032Speter*/
69938032Speter
70038032Speterint
70138032Spetermci_lock_host(mci)
70238032Speter	MCI *mci;
70338032Speter{
70438032Speter	if (mci == NULL)
70538032Speter	{
70638032Speter		if (tTd(56, 1))
70790795Sgshapiro			sm_dprintf("mci_lock_host: NULL mci\n");
70838032Speter		return EX_OK;
70938032Speter	}
71038032Speter
71138032Speter	if (!SingleThreadDelivery)
71238032Speter		return EX_OK;
71338032Speter
71438032Speter	return mci_lock_host_statfile(mci);
71538032Speter}
71638032Speter
71764565Sgshapirostatic int
71838032Spetermci_lock_host_statfile(mci)
71938032Speter	MCI *mci;
72038032Speter{
72164565Sgshapiro	int save_errno = errno;
72238032Speter	int retVal = EX_OK;
72398125Sgshapiro	char fname[MAXPATHLEN];
72438032Speter
72538032Speter	if (HostStatDir == NULL || mci->mci_host == NULL)
72638032Speter		return EX_OK;
72738032Speter
72838032Speter	if (tTd(56, 2))
72990795Sgshapiro		sm_dprintf("mci_lock_host: attempting to lock %s\n",
73090795Sgshapiro			   mci->mci_host);
73138032Speter
73290795Sgshapiro	if (mci_generate_persistent_path(mci->mci_host, fname, sizeof fname,
73390795Sgshapiro					 true) < 0)
73438032Speter	{
73538032Speter		/* of course this should never happen */
73638032Speter		if (tTd(56, 2))
73790795Sgshapiro			sm_dprintf("mci_lock_host: Failed to generate host path for %s\n",
73890795Sgshapiro				   mci->mci_host);
73938032Speter
74038032Speter		retVal = EX_TEMPFAIL;
74138032Speter		goto cleanup;
74238032Speter	}
74338032Speter
74438032Speter	mci->mci_statfile = safefopen(fname, O_RDWR, FileMode,
74564565Sgshapiro				      SFF_NOLOCK|SFF_NOLINK|SFF_OPENASROOT|SFF_REGONLY|SFF_SAFEDIRPATH|SFF_CREAT);
74638032Speter
74738032Speter	if (mci->mci_statfile == NULL)
74838032Speter	{
74990795Sgshapiro		syserr("mci_lock_host: cannot create host lock file %s", fname);
75038032Speter		goto cleanup;
75138032Speter	}
75238032Speter
75390795Sgshapiro	if (!lockfile(sm_io_getinfo(mci->mci_statfile, SM_IO_WHAT_FD, NULL),
75490795Sgshapiro		      fname, "", LOCK_EX|LOCK_NB))
75538032Speter	{
75638032Speter		if (tTd(56, 2))
75790795Sgshapiro			sm_dprintf("mci_lock_host: couldn't get lock on %s\n",
75864565Sgshapiro				fname);
75990795Sgshapiro		(void) sm_io_close(mci->mci_statfile, SM_TIME_DEFAULT);
76038032Speter		mci->mci_statfile = NULL;
76138032Speter		retVal = EX_TEMPFAIL;
76238032Speter		goto cleanup;
76338032Speter	}
76438032Speter
76538032Speter	if (tTd(56, 12) && mci->mci_statfile != NULL)
76690795Sgshapiro		sm_dprintf("mci_lock_host: Sanity check -- lock is good\n");
76738032Speter
76838032Spetercleanup:
76964565Sgshapiro	errno = save_errno;
77038032Speter	return retVal;
77138032Speter}
77290795Sgshapiro/*
77338032Speter**  MCI_UNLOCK_HOST -- unlock host
77438032Speter**
77538032Speter**	Clean up the lock on a host, close the file, let
77638032Speter**	someone else use it.
77738032Speter**
77838032Speter**	Parameters:
77938032Speter**		mci -- us.
78038032Speter**
78138032Speter**	Returns:
78238032Speter**		nothing.
78338032Speter*/
78438032Speter
78538032Spetervoid
78638032Spetermci_unlock_host(mci)
78738032Speter	MCI *mci;
78838032Speter{
78964565Sgshapiro	int save_errno = errno;
79038032Speter
79138032Speter	if (mci == NULL)
79238032Speter	{
79338032Speter		if (tTd(56, 1))
79490795Sgshapiro			sm_dprintf("mci_unlock_host: NULL mci\n");
79538032Speter		return;
79638032Speter	}
79738032Speter
79838032Speter	if (HostStatDir == NULL || mci->mci_host == NULL)
79938032Speter		return;
80038032Speter
80138032Speter	if (!SingleThreadDelivery && mci_lock_host_statfile(mci) == EX_TEMPFAIL)
80238032Speter	{
80338032Speter		if (tTd(56, 1))
80490795Sgshapiro			sm_dprintf("mci_unlock_host: stat file already locked\n");
80538032Speter	}
80638032Speter	else
80738032Speter	{
80838032Speter		if (tTd(56, 2))
80990795Sgshapiro			sm_dprintf("mci_unlock_host: store prior to unlock\n");
81038032Speter		mci_store_persistent(mci);
81138032Speter	}
81238032Speter
81338032Speter	if (mci->mci_statfile != NULL)
81438032Speter	{
81590795Sgshapiro		(void) sm_io_close(mci->mci_statfile, SM_TIME_DEFAULT);
81638032Speter		mci->mci_statfile = NULL;
81738032Speter	}
81838032Speter
81964565Sgshapiro	errno = save_errno;
82038032Speter}
82190795Sgshapiro/*
82242580Speter**  MCI_LOAD_PERSISTENT -- load persistent host info
82338032Speter**
82438032Speter**	Load information about host that is kept
82538032Speter**	in common for all running sendmails.
82638032Speter**
82738032Speter**	Parameters:
82890795Sgshapiro**		mci -- the host/connection to load persistent info for.
82938032Speter**
83038032Speter**	Returns:
83190795Sgshapiro**		true -- lock was successful
83290795Sgshapiro**		false -- lock failed
83338032Speter*/
83438032Speter
83564565Sgshapirostatic bool
83638032Spetermci_load_persistent(mci)
83738032Speter	MCI *mci;
83838032Speter{
83964565Sgshapiro	int save_errno = errno;
84090795Sgshapiro	bool locked = true;
84190795Sgshapiro	SM_FILE_T *fp;
84298125Sgshapiro	char fname[MAXPATHLEN];
84338032Speter
84438032Speter	if (mci == NULL)
84538032Speter	{
84638032Speter		if (tTd(56, 1))
84790795Sgshapiro			sm_dprintf("mci_load_persistent: NULL mci\n");
84890795Sgshapiro		return true;
84938032Speter	}
85038032Speter
85138032Speter	if (IgnoreHostStatus || HostStatDir == NULL || mci->mci_host == NULL)
85290795Sgshapiro		return true;
85364565Sgshapiro
85438032Speter	/* Already have the persistent information in memory */
85538032Speter	if (SingleThreadDelivery && mci->mci_statfile != NULL)
85690795Sgshapiro		return true;
85738032Speter
85838032Speter	if (tTd(56, 1))
85990795Sgshapiro		sm_dprintf("mci_load_persistent: Attempting to load persistent information for %s\n",
86090795Sgshapiro			   mci->mci_host);
86164565Sgshapiro
86290795Sgshapiro	if (mci_generate_persistent_path(mci->mci_host, fname, sizeof fname,
86390795Sgshapiro					 false) < 0)
86438032Speter	{
86538032Speter		/* Not much we can do if the file isn't there... */
86638032Speter		if (tTd(56, 1))
86790795Sgshapiro			sm_dprintf("mci_load_persistent: Couldn't generate host path\n");
86838032Speter		goto cleanup;
86938032Speter	}
87038032Speter
87138032Speter	fp = safefopen(fname, O_RDONLY, FileMode,
87238032Speter		       SFF_NOLOCK|SFF_NOLINK|SFF_OPENASROOT|SFF_REGONLY|SFF_SAFEDIRPATH);
87338032Speter	if (fp == NULL)
87438032Speter	{
87538032Speter		/* I can't think of any reason this should ever happen */
87638032Speter		if (tTd(56, 1))
87790795Sgshapiro			sm_dprintf("mci_load_persistent: open(%s): %s\n",
87890795Sgshapiro				fname, sm_errstring(errno));
87938032Speter		goto cleanup;
88038032Speter	}
88138032Speter
88238032Speter	FileName = fname;
88390795Sgshapiro	locked = lockfile(sm_io_getinfo(fp, SM_IO_WHAT_FD, NULL), fname, "",
88490795Sgshapiro			  LOCK_SH|LOCK_NB);
88564565Sgshapiro	if (locked)
88664565Sgshapiro	{
88764565Sgshapiro		(void) mci_read_persistent(fp, mci);
88890795Sgshapiro		(void) lockfile(sm_io_getinfo(fp, SM_IO_WHAT_FD, NULL), fname,
88990795Sgshapiro				"", LOCK_UN);
89064565Sgshapiro	}
89138032Speter	FileName = NULL;
89290795Sgshapiro	(void) sm_io_close(fp, SM_TIME_DEFAULT);
89338032Speter
89438032Spetercleanup:
89564565Sgshapiro	errno = save_errno;
89638032Speter	return locked;
89738032Speter}
89890795Sgshapiro/*
89938032Speter**  MCI_READ_PERSISTENT -- read persistent host status file
90038032Speter**
90138032Speter**	Parameters:
90238032Speter**		fp -- the file pointer to read.
90338032Speter**		mci -- the pointer to fill in.
90438032Speter**
90538032Speter**	Returns:
90638032Speter**		-1 -- if the file was corrupt.
90738032Speter**		0 -- otherwise.
90838032Speter**
90938032Speter**	Warning:
91038032Speter**		This code makes the assumption that this data
91138032Speter**		will be read in an atomic fashion, and that the data
91238032Speter**		was written in an atomic fashion.  Any other functioning
91338032Speter**		may lead to some form of insanity.  This should be
91438032Speter**		perfectly safe due to underlying stdio buffering.
91538032Speter*/
91638032Speter
91764565Sgshapirostatic int
91838032Spetermci_read_persistent(fp, mci)
91990795Sgshapiro	SM_FILE_T *fp;
92038032Speter	register MCI *mci;
92138032Speter{
92238032Speter	int ver;
92338032Speter	register char *p;
92438032Speter	int saveLineNumber = LineNumber;
92538032Speter	char buf[MAXLINE];
92638032Speter
92738032Speter	if (fp == NULL)
92838032Speter		syserr("mci_read_persistent: NULL fp");
92938032Speter	if (mci == NULL)
93038032Speter		syserr("mci_read_persistent: NULL mci");
93138032Speter	if (tTd(56, 93))
93238032Speter	{
93390795Sgshapiro		sm_dprintf("mci_read_persistent: fp=%lx, mci=",
93490795Sgshapiro			   (unsigned long) fp);
93538032Speter	}
93638032Speter
93790795Sgshapiro	SM_FREE_CLR(mci->mci_status);
93890795Sgshapiro	SM_FREE_CLR(mci->mci_rstatus);
93938032Speter
94090795Sgshapiro	sm_io_rewind(fp, SM_TIME_DEFAULT);
94138032Speter	ver = -1;
94238032Speter	LineNumber = 0;
94390795Sgshapiro	while (sm_io_fgets(fp, SM_TIME_DEFAULT, buf, sizeof buf) != NULL)
94438032Speter	{
94538032Speter		LineNumber++;
94638032Speter		p = strchr(buf, '\n');
94738032Speter		if (p != NULL)
94838032Speter			*p = '\0';
94938032Speter		switch (buf[0])
95038032Speter		{
95138032Speter		  case 'V':		/* version stamp */
95238032Speter			ver = atoi(&buf[1]);
95338032Speter			if (ver < 0 || ver > 0)
95438032Speter				syserr("Unknown host status version %d: %d max",
95538032Speter					ver, 0);
95638032Speter			break;
95738032Speter
95838032Speter		  case 'E':		/* UNIX error number */
95938032Speter			mci->mci_errno = atoi(&buf[1]);
96038032Speter			break;
96138032Speter
96238032Speter		  case 'H':		/* DNS error number */
96338032Speter			mci->mci_herrno = atoi(&buf[1]);
96438032Speter			break;
96538032Speter
96638032Speter		  case 'S':		/* UNIX exit status */
96738032Speter			mci->mci_exitstat = atoi(&buf[1]);
96838032Speter			break;
96938032Speter
97038032Speter		  case 'D':		/* DSN status */
97138032Speter			mci->mci_status = newstr(&buf[1]);
97238032Speter			break;
97338032Speter
97438032Speter		  case 'R':		/* SMTP status */
97538032Speter			mci->mci_rstatus = newstr(&buf[1]);
97638032Speter			break;
97738032Speter
97838032Speter		  case 'U':		/* last usage time */
97938032Speter			mci->mci_lastuse = atol(&buf[1]);
98038032Speter			break;
98138032Speter
98238032Speter		  case '.':		/* end of file */
98390795Sgshapiro			if (tTd(56, 93))
984132946Sgshapiro				mci_dump(sm_debug_file(), mci, false);
98538032Speter			return 0;
98638032Speter
98738032Speter		  default:
98838032Speter			sm_syslog(LOG_CRIT, NOQID,
98938032Speter				  "%s: line %d: Unknown host status line \"%s\"",
99038032Speter				  FileName == NULL ? mci->mci_host : FileName,
99138032Speter				  LineNumber, buf);
99238032Speter			LineNumber = saveLineNumber;
99338032Speter			return -1;
99438032Speter		}
99538032Speter	}
99638032Speter	LineNumber = saveLineNumber;
99790795Sgshapiro	if (tTd(56, 93))
99890795Sgshapiro		sm_dprintf("incomplete (missing dot for EOF)\n");
99938032Speter	if (ver < 0)
100038032Speter		return -1;
100138032Speter	return 0;
100238032Speter}
100390795Sgshapiro/*
100438032Speter**  MCI_STORE_PERSISTENT -- Store persistent MCI information
100538032Speter**
100638032Speter**	Store information about host that is kept
100738032Speter**	in common for all running sendmails.
100838032Speter**
100938032Speter**	Parameters:
101038032Speter**		mci -- the host/connection to store persistent info for.
101138032Speter**
101238032Speter**	Returns:
101338032Speter**		none.
101438032Speter*/
101538032Speter
101638032Spetervoid
101738032Spetermci_store_persistent(mci)
101838032Speter	MCI *mci;
101938032Speter{
102064565Sgshapiro	int save_errno = errno;
102138032Speter
102238032Speter	if (mci == NULL)
102338032Speter	{
102438032Speter		if (tTd(56, 1))
102590795Sgshapiro			sm_dprintf("mci_store_persistent: NULL mci\n");
102638032Speter		return;
102738032Speter	}
102838032Speter
102938032Speter	if (HostStatDir == NULL || mci->mci_host == NULL)
103038032Speter		return;
103138032Speter
103238032Speter	if (tTd(56, 1))
103390795Sgshapiro		sm_dprintf("mci_store_persistent: Storing information for %s\n",
103490795Sgshapiro			   mci->mci_host);
103538032Speter
103638032Speter	if (mci->mci_statfile == NULL)
103738032Speter	{
103838032Speter		if (tTd(56, 1))
103990795Sgshapiro			sm_dprintf("mci_store_persistent: no statfile\n");
104038032Speter		return;
104138032Speter	}
104238032Speter
104390795Sgshapiro	sm_io_rewind(mci->mci_statfile, SM_TIME_DEFAULT);
104438032Speter#if !NOFTRUNCATE
104590795Sgshapiro	(void) ftruncate(sm_io_getinfo(mci->mci_statfile, SM_IO_WHAT_FD, NULL),
104690795Sgshapiro			 (off_t) 0);
104764565Sgshapiro#endif /* !NOFTRUNCATE */
104838032Speter
104990795Sgshapiro	(void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT, "V0\n");
105090795Sgshapiro	(void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT, "E%d\n",
105190795Sgshapiro			     mci->mci_errno);
105290795Sgshapiro	(void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT, "H%d\n",
105390795Sgshapiro			     mci->mci_herrno);
105490795Sgshapiro	(void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT, "S%d\n",
105590795Sgshapiro			     mci->mci_exitstat);
105638032Speter	if (mci->mci_status != NULL)
105790795Sgshapiro		(void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT,
105890795Sgshapiro				     "D%.80s\n",
105990795Sgshapiro				     denlstring(mci->mci_status, true, false));
106038032Speter	if (mci->mci_rstatus != NULL)
106190795Sgshapiro		(void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT,
106290795Sgshapiro				     "R%.80s\n",
106390795Sgshapiro				     denlstring(mci->mci_rstatus, true, false));
106490795Sgshapiro	(void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT, "U%ld\n",
106590795Sgshapiro			     (long)(mci->mci_lastuse));
106690795Sgshapiro	(void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT, ".\n");
106738032Speter
106890795Sgshapiro	(void) sm_io_flush(mci->mci_statfile, SM_TIME_DEFAULT);
106938032Speter
107064565Sgshapiro	errno = save_errno;
107138032Speter	return;
107238032Speter}
107390795Sgshapiro/*
107438032Speter**  MCI_TRAVERSE_PERSISTENT -- walk persistent status tree
107538032Speter**
107638032Speter**	Recursively find all the mci host files in `pathname'.  Default to
107738032Speter**		main host status directory if no path is provided.
107838032Speter**	Call (*action)(pathname, host) for each file found.
107938032Speter**
108038032Speter**	Note: all information is collected in a list before it is processed.
108138032Speter**	This may not be the best way to do it, but it seems safest, since
108238032Speter**	the file system would be touched while we are attempting to traverse
108338032Speter**	the directory tree otherwise (during purges).
108438032Speter**
108538032Speter**	Parameters:
108638032Speter**		action -- function to call on each node.  If returns < 0,
108738032Speter**			return immediately.
108838032Speter**		pathname -- root of tree.  If null, use main host status
108938032Speter**			directory.
109038032Speter**
109138032Speter**	Returns:
109238032Speter**		< 0 -- if any action routine returns a negative value, that
109338032Speter**			value is returned.
109438032Speter**		0 -- if we successfully went to completion.
109564565Sgshapiro**		> 0 -- return status from action()
109638032Speter*/
109738032Speter
109838032Speterint
109938032Spetermci_traverse_persistent(action, pathname)
1100141862Sgshapiro	int (*action)__P((char *, char *));
110138032Speter	char *pathname;
110238032Speter{
110338032Speter	struct stat statbuf;
110438032Speter	DIR *d;
110538032Speter	int ret;
110638032Speter
110738032Speter	if (pathname == NULL)
110838032Speter		pathname = HostStatDir;
110938032Speter	if (pathname == NULL)
111038032Speter		return -1;
111138032Speter
111238032Speter	if (tTd(56, 1))
111390795Sgshapiro		sm_dprintf("mci_traverse: pathname is %s\n", pathname);
111438032Speter
111538032Speter	ret = stat(pathname, &statbuf);
111638032Speter	if (ret < 0)
111738032Speter	{
111838032Speter		if (tTd(56, 2))
111990795Sgshapiro			sm_dprintf("mci_traverse: Failed to stat %s: %s\n",
112090795Sgshapiro				pathname, sm_errstring(errno));
112138032Speter		return ret;
112238032Speter	}
112338032Speter	if (S_ISDIR(statbuf.st_mode))
112438032Speter	{
112590795Sgshapiro		bool leftone, removedone;
112690795Sgshapiro		size_t len;
112790795Sgshapiro		char *newptr;
112838032Speter		struct dirent *e;
112998125Sgshapiro		char newpath[MAXPATHLEN];
113038032Speter
113138032Speter		if ((d = opendir(pathname)) == NULL)
113238032Speter		{
113338032Speter			if (tTd(56, 2))
113490795Sgshapiro				sm_dprintf("mci_traverse: opendir %s: %s\n",
113590795Sgshapiro					pathname, sm_errstring(errno));
113638032Speter			return -1;
113738032Speter		}
113890795Sgshapiro		len = sizeof(newpath) - MAXNAMLEN - 3;
113990795Sgshapiro		if (sm_strlcpy(newpath, pathname, len) >= len)
114038032Speter		{
114138032Speter			if (tTd(56, 2))
114290795Sgshapiro				sm_dprintf("mci_traverse: path \"%s\" too long",
114338032Speter					pathname);
114438032Speter			return -1;
114538032Speter		}
114638032Speter		newptr = newpath + strlen(newpath);
114738032Speter		*newptr++ = '/';
114838032Speter
114964565Sgshapiro		/*
115064565Sgshapiro		**  repeat until no file has been removed
115164565Sgshapiro		**  this may become ugly when several files "expire"
115264565Sgshapiro		**  during these loops, but it's better than doing
115364565Sgshapiro		**  a rewinddir() inside the inner loop
115464565Sgshapiro		*/
115590795Sgshapiro
115664565Sgshapiro		do
115738032Speter		{
115890795Sgshapiro			leftone = removedone = false;
115964565Sgshapiro			while ((e = readdir(d)) != NULL)
116064565Sgshapiro			{
116164565Sgshapiro				if (e->d_name[0] == '.')
116264565Sgshapiro					continue;
116338032Speter
116490795Sgshapiro				(void) sm_strlcpy(newptr, e->d_name,
116564565Sgshapiro					       sizeof newpath -
116664565Sgshapiro					       (newptr - newpath));
116738032Speter
116877352Sgshapiro				if (StopRequest)
116977352Sgshapiro					stop_sendmail();
117064565Sgshapiro				ret = mci_traverse_persistent(action, newpath);
117164565Sgshapiro				if (ret < 0)
117264565Sgshapiro					break;
117364565Sgshapiro				if (ret == 1)
117490795Sgshapiro					leftone = true;
117564565Sgshapiro				if (!removedone && ret == 0 &&
117664565Sgshapiro				    action == mci_purge_persistent)
117790795Sgshapiro					removedone = true;
117864565Sgshapiro			}
117938032Speter			if (ret < 0)
118038032Speter				break;
118190795Sgshapiro
118238032Speter			/*
118338032Speter			**  The following appears to be
118438032Speter			**  necessary during purges, since
118538032Speter			**  we modify the directory structure
118638032Speter			*/
118790795Sgshapiro
118864565Sgshapiro			if (removedone)
118938032Speter				rewinddir(d);
119064565Sgshapiro			if (tTd(56, 40))
119190795Sgshapiro				sm_dprintf("mci_traverse: path %s: ret %d removed %d left %d\n",
119264565Sgshapiro					pathname, ret, removedone, leftone);
119364565Sgshapiro		} while (removedone);
119438032Speter
119538032Speter		/* purge (or whatever) the directory proper */
119664565Sgshapiro		if (!leftone)
119764565Sgshapiro		{
119864565Sgshapiro			*--newptr = '\0';
119964565Sgshapiro			ret = (*action)(newpath, NULL);
120064565Sgshapiro		}
120164565Sgshapiro		(void) closedir(d);
120238032Speter	}
120338032Speter	else if (S_ISREG(statbuf.st_mode))
120438032Speter	{
120538032Speter		char *end = pathname + strlen(pathname) - 1;
120638032Speter		char *start;
120738032Speter		char *scan;
120838032Speter		char host[MAXHOSTNAMELEN];
120938032Speter		char *hostptr = host;
121064565Sgshapiro
121138032Speter		/*
121238032Speter		**  Reconstruct the host name from the path to the
121338032Speter		**  persistent information.
121438032Speter		*/
121538032Speter
121638032Speter		do
121738032Speter		{
121838032Speter			if (hostptr != host)
121938032Speter				*(hostptr++) = '.';
122038032Speter			start = end;
1221120259Sgshapiro			while (start > pathname && *(start - 1) != '/')
122238032Speter				start--;
122338032Speter
122438032Speter			if (*end == '.')
122538032Speter				end--;
122638032Speter
122738032Speter			for (scan = start; scan <= end; scan++)
122838032Speter				*(hostptr++) = *scan;
122938032Speter
123038032Speter			end = start - 2;
1231120259Sgshapiro		} while (end > pathname && *end == '.');
123238032Speter
123338032Speter		*hostptr = '\0';
123438032Speter
123538032Speter		/*
123642580Speter		**  Do something with the file containing the persistent
123742580Speter		**  information.
123838032Speter		*/
123990795Sgshapiro
124038032Speter		ret = (*action)(pathname, host);
124138032Speter	}
124238032Speter
124338032Speter	return ret;
124438032Speter}
124590795Sgshapiro/*
124664565Sgshapiro**  MCI_PRINT_PERSISTENT -- print persistent info
124738032Speter**
124838032Speter**	Dump the persistent information in the file 'pathname'
124938032Speter**
125038032Speter**	Parameters:
125138032Speter**		pathname -- the pathname to the status file.
125238032Speter**		hostname -- the corresponding host name.
125338032Speter**
125438032Speter**	Returns:
125538032Speter**		0
125638032Speter*/
125738032Speter
125838032Speterint
125938032Spetermci_print_persistent(pathname, hostname)
126038032Speter	char *pathname;
126138032Speter	char *hostname;
126238032Speter{
126390795Sgshapiro	static bool initflag = false;
126490795Sgshapiro	SM_FILE_T *fp;
126538032Speter	int width = Verbose ? 78 : 25;
126638032Speter	bool locked;
126738032Speter	MCI mcib;
126838032Speter
126938032Speter	/* skip directories */
127038032Speter	if (hostname == NULL)
127138032Speter		return 0;
127238032Speter
127377352Sgshapiro	if (StopRequest)
127477352Sgshapiro		stop_sendmail();
127577352Sgshapiro
127638032Speter	if (!initflag)
127738032Speter	{
127890795Sgshapiro		initflag = true;
127990795Sgshapiro		(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
128090795Sgshapiro				     " -------------- Hostname --------------- How long ago ---------Results---------\n");
128138032Speter	}
128238032Speter
128390795Sgshapiro	fp = safefopen(pathname, O_RDONLY, FileMode,
128438032Speter		       SFF_NOLOCK|SFF_NOLINK|SFF_OPENASROOT|SFF_REGONLY|SFF_SAFEDIRPATH);
128538032Speter
128638032Speter	if (fp == NULL)
128738032Speter	{
128838032Speter		if (tTd(56, 1))
128990795Sgshapiro			sm_dprintf("mci_print_persistent: cannot open %s: %s\n",
129090795Sgshapiro				pathname, sm_errstring(errno));
129138032Speter		return 0;
129238032Speter	}
129338032Speter
129438032Speter	FileName = pathname;
129564565Sgshapiro	memset(&mcib, '\0', sizeof mcib);
129638032Speter	if (mci_read_persistent(fp, &mcib) < 0)
129738032Speter	{
129838032Speter		syserr("%s: could not read status file", pathname);
129990795Sgshapiro		(void) sm_io_close(fp, SM_TIME_DEFAULT);
130038032Speter		FileName = NULL;
130138032Speter		return 0;
130238032Speter	}
130338032Speter
130490795Sgshapiro	locked = !lockfile(sm_io_getinfo(fp, SM_IO_WHAT_FD, NULL), pathname,
130590795Sgshapiro			   "", LOCK_SH|LOCK_NB);
130690795Sgshapiro	(void) sm_io_close(fp, SM_TIME_DEFAULT);
130738032Speter	FileName = NULL;
130838032Speter
130990795Sgshapiro	(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%c%-39s %12s ",
131090795Sgshapiro			     locked ? '*' : ' ', hostname,
131190795Sgshapiro			     pintvl(curtime() - mcib.mci_lastuse, true));
131238032Speter	if (mcib.mci_rstatus != NULL)
131390795Sgshapiro		(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%.*s\n", width,
131490795Sgshapiro				     mcib.mci_rstatus);
131538032Speter	else if (mcib.mci_exitstat == EX_TEMPFAIL && mcib.mci_errno != 0)
131690795Sgshapiro		(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
131790795Sgshapiro				     "Deferred: %.*s\n", width - 10,
131890795Sgshapiro				     sm_errstring(mcib.mci_errno));
131938032Speter	else if (mcib.mci_exitstat != 0)
132038032Speter	{
132190795Sgshapiro		char *exmsg = sm_sysexmsg(mcib.mci_exitstat);
132238032Speter
132390795Sgshapiro		if (exmsg == NULL)
132438032Speter		{
132538032Speter			char buf[80];
132638032Speter
132790795Sgshapiro			(void) sm_snprintf(buf, sizeof buf,
132890795Sgshapiro				"Unknown mailer error %d",
132938032Speter				mcib.mci_exitstat);
133090795Sgshapiro			(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%.*s\n",
133190795Sgshapiro					     width, buf);
133238032Speter		}
133338032Speter		else
133490795Sgshapiro			(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%.*s\n",
133590795Sgshapiro					     width, &exmsg[5]);
133638032Speter	}
133738032Speter	else if (mcib.mci_errno == 0)
133890795Sgshapiro		(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "OK\n");
133938032Speter	else
134090795Sgshapiro		(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "OK: %.*s\n",
134190795Sgshapiro				     width - 4, sm_errstring(mcib.mci_errno));
134238032Speter
134338032Speter	return 0;
134438032Speter}
134590795Sgshapiro/*
134638032Speter**  MCI_PURGE_PERSISTENT -- Remove a persistence status file.
134738032Speter**
134838032Speter**	Parameters:
134938032Speter**		pathname -- path to the status file.
135038032Speter**		hostname -- name of host corresponding to that file.
135138032Speter**			NULL if this is a directory (domain).
135238032Speter**
135338032Speter**	Returns:
135464565Sgshapiro**		0 -- ok
135564565Sgshapiro**		1 -- file not deleted (too young, incorrect format)
135664565Sgshapiro**		< 0 -- some error occurred
135738032Speter*/
135838032Speter
135938032Speterint
136038032Spetermci_purge_persistent(pathname, hostname)
136138032Speter	char *pathname;
136238032Speter	char *hostname;
136338032Speter{
136464565Sgshapiro	struct stat statbuf;
136538032Speter	char *end = pathname + strlen(pathname) - 1;
136664565Sgshapiro	int ret;
136738032Speter
136838032Speter	if (tTd(56, 1))
136990795Sgshapiro		sm_dprintf("mci_purge_persistent: purging %s\n", pathname);
137038032Speter
137164565Sgshapiro	ret = stat(pathname, &statbuf);
137264565Sgshapiro	if (ret < 0)
137364565Sgshapiro	{
137464565Sgshapiro		if (tTd(56, 2))
137590795Sgshapiro			sm_dprintf("mci_purge_persistent: Failed to stat %s: %s\n",
137690795Sgshapiro				pathname, sm_errstring(errno));
137764565Sgshapiro		return ret;
137864565Sgshapiro	}
137994337Sgshapiro	if (curtime() - statbuf.st_mtime <= MciInfoTimeout)
138064565Sgshapiro		return 1;
138138032Speter	if (hostname != NULL)
138238032Speter	{
138338032Speter		/* remove the file */
138490795Sgshapiro		ret = unlink(pathname);
138590795Sgshapiro		if (ret < 0)
138638032Speter		{
138790795Sgshapiro			if (LogLevel > 8)
138890795Sgshapiro				sm_syslog(LOG_ERR, NOQID,
138990795Sgshapiro					  "mci_purge_persistent: failed to unlink %s: %s",
139090795Sgshapiro					  pathname, sm_errstring(errno));
139138032Speter			if (tTd(56, 2))
139290795Sgshapiro				sm_dprintf("mci_purge_persistent: failed to unlink %s: %s\n",
139390795Sgshapiro					pathname, sm_errstring(errno));
139490795Sgshapiro			return ret;
139538032Speter		}
139638032Speter	}
139738032Speter	else
139838032Speter	{
139938032Speter		/* remove the directory */
140038032Speter		if (*end != '.')
140164565Sgshapiro			return 1;
140238032Speter
140338032Speter		if (tTd(56, 1))
140490795Sgshapiro			sm_dprintf("mci_purge_persistent: dpurge %s\n", pathname);
140538032Speter
140690795Sgshapiro		ret = rmdir(pathname);
140790795Sgshapiro		if (ret < 0)
140838032Speter		{
140938032Speter			if (tTd(56, 2))
141090795Sgshapiro				sm_dprintf("mci_purge_persistent: rmdir %s: %s\n",
141190795Sgshapiro					pathname, sm_errstring(errno));
141290795Sgshapiro			return ret;
141338032Speter		}
141438032Speter	}
141538032Speter
141638032Speter	return 0;
141738032Speter}
141890795Sgshapiro/*
141942580Speter**  MCI_GENERATE_PERSISTENT_PATH -- generate path from hostname
142038032Speter**
1421120259Sgshapiro**	Given `host', convert from a.b.c to $HostStatDir/c./b./a,
142238032Speter**	putting the result into `path'.  if `createflag' is set, intervening
142338032Speter**	directories will be created as needed.
142438032Speter**
142538032Speter**	Parameters:
142638032Speter**		host -- host name to convert from.
142738032Speter**		path -- place to store result.
142838032Speter**		pathlen -- length of path buffer.
142938032Speter**		createflag -- if set, create intervening directories as
143038032Speter**			needed.
143138032Speter**
143238032Speter**	Returns:
143338032Speter**		0 -- success
143438032Speter**		-1 -- failure
143538032Speter*/
143638032Speter
143764565Sgshapirostatic int
143838032Spetermci_generate_persistent_path(host, path, pathlen, createflag)
143938032Speter	const char *host;
144038032Speter	char *path;
144138032Speter	int pathlen;
144238032Speter	bool createflag;
144338032Speter{
144438032Speter	char *elem, *p, *x, ch;
144538032Speter	int ret = 0;
144638032Speter	int len;
144738032Speter	char t_host[MAXHOSTNAMELEN];
144864565Sgshapiro#if NETINET6
144964565Sgshapiro	struct in6_addr in6_addr;
145064565Sgshapiro#endif /* NETINET6 */
145138032Speter
145238032Speter	/*
145338032Speter	**  Rationality check the arguments.
145438032Speter	*/
145538032Speter
145638032Speter	if (host == NULL)
145738032Speter	{
145838032Speter		syserr("mci_generate_persistent_path: null host");
145938032Speter		return -1;
146038032Speter	}
146138032Speter	if (path == NULL)
146238032Speter	{
146338032Speter		syserr("mci_generate_persistent_path: null path");
146438032Speter		return -1;
146538032Speter	}
146638032Speter
146738032Speter	if (tTd(56, 80))
146890795Sgshapiro		sm_dprintf("mci_generate_persistent_path(%s): ", host);
146938032Speter
147038032Speter	if (*host == '\0' || *host == '.')
147138032Speter		return -1;
147238032Speter
147338032Speter	/* make certain this is not a bracketed host number */
147438032Speter	if (strlen(host) > sizeof t_host - 1)
147538032Speter		return -1;
147638032Speter	if (host[0] == '[')
147790795Sgshapiro		(void) sm_strlcpy(t_host, host + 1, sizeof t_host);
147864565Sgshapiro	else
147990795Sgshapiro		(void) sm_strlcpy(t_host, host, sizeof t_host);
148038032Speter
148138032Speter	/*
148238032Speter	**  Delete any trailing dots from the hostname.
148338032Speter	**  Leave 'elem' pointing at the \0.
148438032Speter	*/
148538032Speter
148638032Speter	elem = t_host + strlen(t_host);
148738032Speter	while (elem > t_host &&
148838032Speter	       (elem[-1] == '.' || (host[0] == '[' && elem[-1] == ']')))
148938032Speter		*--elem = '\0';
149038032Speter
149138032Speter	/* check for bogus bracketed address */
149290795Sgshapiro	if (host[0] == '[')
149390795Sgshapiro	{
149490795Sgshapiro		bool good = false;
149564565Sgshapiro# if NETINET6
149690795Sgshapiro		if (anynet_pton(AF_INET6, t_host, &in6_addr) == 1)
149790795Sgshapiro			good = true;
149864565Sgshapiro# endif /* NETINET6 */
149964565Sgshapiro# if NETINET
150090795Sgshapiro		if (inet_addr(t_host) != INADDR_NONE)
150190795Sgshapiro			good = true;
150264565Sgshapiro# endif /* NETINET */
150390795Sgshapiro		if (!good)
150490795Sgshapiro			return -1;
150590795Sgshapiro	}
150638032Speter
150738032Speter	/* check for what will be the final length of the path */
150838032Speter	len = strlen(HostStatDir) + 2;
150938032Speter	for (p = (char *) t_host; *p != '\0'; p++)
151038032Speter	{
151138032Speter		if (*p == '.')
151238032Speter			len++;
151338032Speter		len++;
151438032Speter		if (p[0] == '.' && p[1] == '.')
151538032Speter			return -1;
151638032Speter	}
151738032Speter	if (len > pathlen || len < 1)
151838032Speter		return -1;
151990795Sgshapiro	(void) sm_strlcpy(path, HostStatDir, pathlen);
152038032Speter	p = path + strlen(path);
152138032Speter	while (elem > t_host)
152238032Speter	{
152338032Speter		if (!path_is_dir(path, createflag))
152438032Speter		{
152538032Speter			ret = -1;
152638032Speter			break;
152738032Speter		}
152838032Speter		elem--;
152938032Speter		while (elem >= t_host && *elem != '.')
153038032Speter			elem--;
153138032Speter		*p++ = '/';
153238032Speter		x = elem + 1;
153338032Speter		while ((ch = *x++) != '\0' && ch != '.')
153438032Speter		{
153538032Speter			if (isascii(ch) && isupper(ch))
153638032Speter				ch = tolower(ch);
153738032Speter			if (ch == '/')
153838032Speter				ch = ':';	/* / -> : */
153938032Speter			*p++ = ch;
154038032Speter		}
154138032Speter		if (elem >= t_host)
154238032Speter			*p++ = '.';
154338032Speter		*p = '\0';
154438032Speter	}
154538032Speter	if (tTd(56, 80))
154638032Speter	{
154738032Speter		if (ret < 0)
154890795Sgshapiro			sm_dprintf("FAILURE %d\n", ret);
154938032Speter		else
155090795Sgshapiro			sm_dprintf("SUCCESS %s\n", path);
155138032Speter	}
155264565Sgshapiro	return ret;
155338032Speter}
1554