mci.c revision 98125
138032Speter/*
294337Sgshapiro * Copyright (c) 1998-2002 Sendmail, Inc. and its suppliers.
364565Sgshapiro *	All rights reserved.
438032Speter * Copyright (c) 1995-1997 Eric P. Allman.  All rights reserved.
538032Speter * Copyright (c) 1988, 1993
638032Speter *	The Regents of the University of California.  All rights reserved.
738032Speter *
838032Speter * By using this file, you agree to the terms and conditions set
938032Speter * forth in the LICENSE file which can be found at the top level of
1038032Speter * the sendmail distribution.
1138032Speter *
1238032Speter */
1338032Speter
1464565Sgshapiro/* $FreeBSD: head/contrib/sendmail/src/mci.c 98125 2002-06-11 21:16:51Z gshapiro $ */
1564565Sgshapiro
1664565Sgshapiro#include <sendmail.h>
1764565Sgshapiro
1898125SgshapiroSM_RCSID("@(#)$Id: mci.c,v 8.205 2002/05/24 18:53:48 gshapiro Exp $")
1964565Sgshapiro
2064565Sgshapiro#if NETINET || NETINET6
2164565Sgshapiro# include <arpa/inet.h>
2264565Sgshapiro#endif /* NETINET || NETINET6 */
2364565Sgshapiro
2438032Speter#include <dirent.h>
2538032Speter
2664565Sgshapirostatic int	mci_generate_persistent_path __P((const char *, char *,
2764565Sgshapiro						  int, bool));
2864565Sgshapirostatic bool	mci_load_persistent __P((MCI *));
2964565Sgshapirostatic void	mci_uncache __P((MCI **, bool));
3064565Sgshapirostatic int	mci_lock_host_statfile __P((MCI *));
3190795Sgshapirostatic int	mci_read_persistent __P((SM_FILE_T *, MCI *));
3264565Sgshapiro
3338032Speter/*
3438032Speter**  Mail Connection Information (MCI) Caching Module.
3538032Speter**
3638032Speter**	There are actually two separate things cached.  The first is
3738032Speter**	the set of all open connections -- these are stored in a
3838032Speter**	(small) list.  The second is stored in the symbol table; it
3938032Speter**	has the overall status for all hosts, whether or not there
4038032Speter**	is a connection open currently.
4138032Speter**
4238032Speter**	There should never be too many connections open (since this
4338032Speter**	could flood the socket table), nor should a connection be
4438032Speter**	allowed to sit idly for too long.
4538032Speter**
4638032Speter**	MaxMciCache is the maximum number of open connections that
4738032Speter**	will be supported.
4838032Speter**
4938032Speter**	MciCacheTimeout is the time (in seconds) that a connection
5038032Speter**	is permitted to survive without activity.
5138032Speter**
5238032Speter**	We actually try any cached connections by sending a NOOP
5338032Speter**	before we use them; if the NOOP fails we close down the
5438032Speter**	connection and reopen it.  Note that this means that a
5538032Speter**	server SMTP that doesn't support NOOP will hose the
5638032Speter**	algorithm -- but that doesn't seem too likely.
5738032Speter**
5838032Speter**	The persistent MCI code is donated by Mark Lovell and Paul
5938032Speter**	Vixie.  It is based on the long term host status code in KJS
6038032Speter**	written by Paul but has been adapted by Mark to fit into the
6138032Speter**	MCI structure.
6238032Speter*/
6338032Speter
6464565Sgshapirostatic MCI	**MciCache;		/* the open connection cache */
6538032Speter
6690795Sgshapiro/*
6738032Speter**  MCI_CACHE -- enter a connection structure into the open connection cache
6838032Speter**
6938032Speter**	This may cause something else to be flushed.
7038032Speter**
7138032Speter**	Parameters:
7238032Speter**		mci -- the connection to cache.
7338032Speter**
7438032Speter**	Returns:
7538032Speter**		none.
7638032Speter*/
7738032Speter
7838032Spetervoid
7938032Spetermci_cache(mci)
8038032Speter	register MCI *mci;
8138032Speter{
8238032Speter	register MCI **mcislot;
8338032Speter
8438032Speter	/*
8538032Speter	**  Find the best slot.  This may cause expired connections
8638032Speter	**  to be closed.
8738032Speter	*/
8838032Speter
8938032Speter	mcislot = mci_scan(mci);
9038032Speter	if (mcislot == NULL)
9138032Speter	{
9238032Speter		/* we don't support caching */
9338032Speter		return;
9438032Speter	}
9538032Speter
9638032Speter	if (mci->mci_host == NULL)
9738032Speter		return;
9838032Speter
9938032Speter	/* if this is already cached, we are done */
10038032Speter	if (bitset(MCIF_CACHED, mci->mci_flags))
10138032Speter		return;
10238032Speter
10338032Speter	/* otherwise we may have to clear the slot */
10438032Speter	if (*mcislot != NULL)
10590795Sgshapiro		mci_uncache(mcislot, true);
10638032Speter
10738032Speter	if (tTd(42, 5))
10890795Sgshapiro		sm_dprintf("mci_cache: caching %p (%s) in slot %d\n",
10990795Sgshapiro			   mci, mci->mci_host, (int) (mcislot - MciCache));
11038032Speter	if (tTd(91, 100))
11138032Speter		sm_syslog(LOG_DEBUG, CurEnv->e_id,
11264565Sgshapiro			  "mci_cache: caching %lx (%.100s) in slot %d",
11390795Sgshapiro			  (unsigned long) mci, mci->mci_host,
11490795Sgshapiro			  (int) (mcislot - MciCache));
11538032Speter
11638032Speter	*mcislot = mci;
11738032Speter	mci->mci_flags |= MCIF_CACHED;
11838032Speter}
11990795Sgshapiro/*
12038032Speter**  MCI_SCAN -- scan the cache, flush junk, and return best slot
12138032Speter**
12238032Speter**	Parameters:
12338032Speter**		savemci -- never flush this one.  Can be null.
12438032Speter**
12538032Speter**	Returns:
12638032Speter**		The LRU (or empty) slot.
12738032Speter*/
12838032Speter
12938032SpeterMCI **
13038032Spetermci_scan(savemci)
13138032Speter	MCI *savemci;
13238032Speter{
13338032Speter	time_t now;
13438032Speter	register MCI **bestmci;
13538032Speter	register MCI *mci;
13638032Speter	register int i;
13738032Speter
13838032Speter	if (MaxMciCache <= 0)
13938032Speter	{
14038032Speter		/* we don't support caching */
14138032Speter		return NULL;
14238032Speter	}
14338032Speter
14438032Speter	if (MciCache == NULL)
14538032Speter	{
14638032Speter		/* first call */
14790795Sgshapiro		MciCache = (MCI **) sm_pmalloc_x(MaxMciCache * sizeof *MciCache);
14864565Sgshapiro		memset((char *) MciCache, '\0', MaxMciCache * sizeof *MciCache);
14964565Sgshapiro		return &MciCache[0];
15038032Speter	}
15138032Speter
15238032Speter	now = curtime();
15338032Speter	bestmci = &MciCache[0];
15438032Speter	for (i = 0; i < MaxMciCache; i++)
15538032Speter	{
15638032Speter		mci = MciCache[i];
15738032Speter		if (mci == NULL || mci->mci_state == MCIS_CLOSED)
15838032Speter		{
15938032Speter			bestmci = &MciCache[i];
16038032Speter			continue;
16138032Speter		}
16294337Sgshapiro		if ((mci->mci_lastuse + MciCacheTimeout <= now ||
16364565Sgshapiro		     (mci->mci_mailer != NULL &&
16464565Sgshapiro		      mci->mci_mailer->m_maxdeliveries > 0 &&
16564565Sgshapiro		      mci->mci_deliveries + 1 >= mci->mci_mailer->m_maxdeliveries))&&
16664565Sgshapiro		    mci != savemci)
16738032Speter		{
16864565Sgshapiro			/* connection idle too long or too many deliveries */
16938032Speter			bestmci = &MciCache[i];
17064565Sgshapiro
17164565Sgshapiro			/* close it */
17290795Sgshapiro			mci_uncache(bestmci, true);
17338032Speter			continue;
17438032Speter		}
17538032Speter		if (*bestmci == NULL)
17638032Speter			continue;
17738032Speter		if (mci->mci_lastuse < (*bestmci)->mci_lastuse)
17838032Speter			bestmci = &MciCache[i];
17938032Speter	}
18038032Speter	return bestmci;
18138032Speter}
18290795Sgshapiro/*
18338032Speter**  MCI_UNCACHE -- remove a connection from a slot.
18438032Speter**
18538032Speter**	May close a connection.
18638032Speter**
18738032Speter**	Parameters:
18838032Speter**		mcislot -- the slot to empty.
18990795Sgshapiro**		doquit -- if true, send QUIT protocol on this connection.
19090795Sgshapiro**			  if false, we are assumed to be in a forked child;
19138032Speter**				all we want to do is close the file(s).
19238032Speter**
19338032Speter**	Returns:
19438032Speter**		none.
19538032Speter*/
19638032Speter
19764565Sgshapirostatic void
19838032Spetermci_uncache(mcislot, doquit)
19938032Speter	register MCI **mcislot;
20038032Speter	bool doquit;
20138032Speter{
20238032Speter	register MCI *mci;
20338032Speter	extern ENVELOPE BlankEnvelope;
20438032Speter
20538032Speter	mci = *mcislot;
20638032Speter	if (mci == NULL)
20738032Speter		return;
20838032Speter	*mcislot = NULL;
20938032Speter	if (mci->mci_host == NULL)
21038032Speter		return;
21138032Speter
21238032Speter	mci_unlock_host(mci);
21338032Speter
21438032Speter	if (tTd(42, 5))
21590795Sgshapiro		sm_dprintf("mci_uncache: uncaching %p (%s) from slot %d (%d)\n",
21690795Sgshapiro			   mci, mci->mci_host, (int) (mcislot - MciCache),
21790795Sgshapiro			   doquit);
21838032Speter	if (tTd(91, 100))
21938032Speter		sm_syslog(LOG_DEBUG, CurEnv->e_id,
22064565Sgshapiro			  "mci_uncache: uncaching %lx (%.100s) from slot %d (%d)",
22190795Sgshapiro			  (unsigned long) mci, mci->mci_host,
22290795Sgshapiro			  (int) (mcislot - MciCache), doquit);
22338032Speter
22464565Sgshapiro	mci->mci_deliveries = 0;
22538032Speter	if (doquit)
22638032Speter	{
22738032Speter		message("Closing connection to %s", mci->mci_host);
22838032Speter
22938032Speter		mci->mci_flags &= ~MCIF_CACHED;
23038032Speter
23138032Speter		/* only uses the envelope to flush the transcript file */
23238032Speter		if (mci->mci_state != MCIS_CLOSED)
23338032Speter			smtpquit(mci->mci_mailer, mci, &BlankEnvelope);
23490795Sgshapiro#if XLA
23538032Speter		xla_host_end(mci->mci_host);
23690795Sgshapiro#endif /* XLA */
23738032Speter	}
23838032Speter	else
23938032Speter	{
24038032Speter		if (mci->mci_in != NULL)
24190795Sgshapiro			(void) sm_io_close(mci->mci_in, SM_TIME_DEFAULT);
24238032Speter		if (mci->mci_out != NULL)
24390795Sgshapiro			(void) sm_io_close(mci->mci_out, SM_TIME_DEFAULT);
24438032Speter		mci->mci_in = mci->mci_out = NULL;
24538032Speter		mci->mci_state = MCIS_CLOSED;
24638032Speter		mci->mci_exitstat = EX_OK;
24738032Speter		mci->mci_errno = 0;
24838032Speter		mci->mci_flags = 0;
24990795Sgshapiro
25090795Sgshapiro		mci->mci_retryrcpt = false;
25190795Sgshapiro		mci->mci_tolist = NULL;
25290795Sgshapiro#if PIPELINING
25390795Sgshapiro		mci->mci_okrcpts = 0;
25490795Sgshapiro#endif /* PIPELINING */
25538032Speter	}
25690795Sgshapiro
25790795Sgshapiro	SM_FREE_CLR(mci->mci_status);
25890795Sgshapiro	SM_FREE_CLR(mci->mci_rstatus);
25990795Sgshapiro	SM_FREE_CLR(mci->mci_heloname);
26090795Sgshapiro	if (mci->mci_rpool != NULL)
26190795Sgshapiro	{
26290795Sgshapiro		sm_rpool_free(mci->mci_rpool);
26390795Sgshapiro		mci->mci_macro.mac_rpool = NULL;
26490795Sgshapiro		mci->mci_rpool = NULL;
26590795Sgshapiro	}
26638032Speter}
26790795Sgshapiro/*
26838032Speter**  MCI_FLUSH -- flush the entire cache
26938032Speter**
27038032Speter**	Parameters:
27190795Sgshapiro**		doquit -- if true, send QUIT protocol.
27290795Sgshapiro**			  if false, just close the connection.
27338032Speter**		allbut -- but leave this one open.
27438032Speter**
27538032Speter**	Returns:
27638032Speter**		none.
27738032Speter*/
27838032Speter
27938032Spetervoid
28038032Spetermci_flush(doquit, allbut)
28138032Speter	bool doquit;
28238032Speter	MCI *allbut;
28338032Speter{
28438032Speter	register int i;
28538032Speter
28638032Speter	if (MciCache == NULL)
28738032Speter		return;
28838032Speter
28938032Speter	for (i = 0; i < MaxMciCache; i++)
29071348Sgshapiro	{
29138032Speter		if (allbut != MciCache[i])
29238032Speter			mci_uncache(&MciCache[i], doquit);
29371348Sgshapiro	}
29438032Speter}
29590795Sgshapiro/*
29638032Speter**  MCI_GET -- get information about a particular host
29790795Sgshapiro**
29890795Sgshapiro**	Parameters:
29990795Sgshapiro**		host -- host to look for.
30090795Sgshapiro**		m -- mailer.
30190795Sgshapiro**
30290795Sgshapiro**	Returns:
30390795Sgshapiro**		mci for this host (might be new).
30438032Speter*/
30538032Speter
30638032SpeterMCI *
30738032Spetermci_get(host, m)
30838032Speter	char *host;
30938032Speter	MAILER *m;
31038032Speter{
31138032Speter	register MCI *mci;
31238032Speter	register STAB *s;
31338032Speter	extern SOCKADDR CurHostAddr;
31438032Speter
31538032Speter	/* clear CurHostAddr so we don't get a bogus address with this name */
31664565Sgshapiro	memset(&CurHostAddr, '\0', sizeof CurHostAddr);
31738032Speter
31838032Speter	/* clear out any expired connections */
31938032Speter	(void) mci_scan(NULL);
32038032Speter
32138032Speter	if (m->m_mno < 0)
32271348Sgshapiro		syserr("!negative mno %d (%s)", m->m_mno, m->m_name);
32364565Sgshapiro
32438032Speter	s = stab(host, ST_MCI + m->m_mno, ST_ENTER);
32538032Speter	mci = &s->s_mci;
32638032Speter
32790795Sgshapiro	/* initialize per-message data */
32890795Sgshapiro	mci->mci_retryrcpt = false;
32990795Sgshapiro	mci->mci_tolist = NULL;
33090795Sgshapiro#if PIPELINING
33190795Sgshapiro	mci->mci_okrcpts = 0;
33290795Sgshapiro#endif /* PIPELINING */
33390795Sgshapiro
33490795Sgshapiro	if (mci->mci_rpool == NULL)
33590795Sgshapiro		mci->mci_rpool = sm_rpool_new_x(NULL);
33690795Sgshapiro
33790795Sgshapiro	if (mci->mci_macro.mac_rpool == NULL)
33890795Sgshapiro		mci->mci_macro.mac_rpool = mci->mci_rpool;
33990795Sgshapiro
34064565Sgshapiro	/*
34190795Sgshapiro	**  We don't need to load the persistent data if we have data
34264565Sgshapiro	**  already loaded in the cache.
34364565Sgshapiro	*/
34464565Sgshapiro
34564565Sgshapiro	if (mci->mci_host == NULL &&
34664565Sgshapiro	    (mci->mci_host = s->s_name) != NULL &&
34764565Sgshapiro	    !mci_load_persistent(mci))
34838032Speter	{
34938032Speter		if (tTd(42, 2))
35090795Sgshapiro			sm_dprintf("mci_get(%s %s): lock failed\n",
35164565Sgshapiro				host, m->m_name);
35238032Speter		mci->mci_exitstat = EX_TEMPFAIL;
35338032Speter		mci->mci_state = MCIS_CLOSED;
35438032Speter		mci->mci_statfile = NULL;
35538032Speter		return mci;
35638032Speter	}
35738032Speter
35838032Speter	if (tTd(42, 2))
35938032Speter	{
36090795Sgshapiro		sm_dprintf("mci_get(%s %s): mci_state=%d, _flags=%lx, _exitstat=%d, _errno=%d\n",
36138032Speter			host, m->m_name, mci->mci_state, mci->mci_flags,
36238032Speter			mci->mci_exitstat, mci->mci_errno);
36338032Speter	}
36438032Speter
36538032Speter	if (mci->mci_state == MCIS_OPEN)
36638032Speter	{
36738032Speter		/* poke the connection to see if it's still alive */
36838032Speter		(void) smtpprobe(mci);
36938032Speter
37038032Speter		/* reset the stored state in the event of a timeout */
37138032Speter		if (mci->mci_state != MCIS_OPEN)
37238032Speter		{
37338032Speter			mci->mci_errno = 0;
37438032Speter			mci->mci_exitstat = EX_OK;
37538032Speter			mci->mci_state = MCIS_CLOSED;
37638032Speter		}
37738032Speter		else
37838032Speter		{
37990795Sgshapiro			/* get peer host address */
38038032Speter			/* (this should really be in the mci struct) */
38138032Speter			SOCKADDR_LEN_T socklen = sizeof CurHostAddr;
38238032Speter
38390795Sgshapiro			(void) getpeername(sm_io_getinfo(mci->mci_in,
38490795Sgshapiro							 SM_IO_WHAT_FD, NULL),
38538032Speter				(struct sockaddr *) &CurHostAddr, &socklen);
38638032Speter		}
38738032Speter	}
38838032Speter	if (mci->mci_state == MCIS_CLOSED)
38938032Speter	{
39038032Speter		time_t now = curtime();
39138032Speter
39238032Speter		/* if this info is stale, ignore it */
39394337Sgshapiro		if (mci->mci_lastuse + MciInfoTimeout <= now)
39438032Speter		{
39538032Speter			mci->mci_lastuse = now;
39638032Speter			mci->mci_errno = 0;
39738032Speter			mci->mci_exitstat = EX_OK;
39838032Speter		}
39938032Speter	}
40038032Speter
40138032Speter	return mci;
40238032Speter}
40390795Sgshapiro/*
40490795Sgshapiro**  MCI_NEW -- allocate new MCI structure
40590795Sgshapiro**
40690795Sgshapiro**	Parameters:
40790795Sgshapiro**		rpool -- if non-NULL: allocate from that rpool.
40890795Sgshapiro**
40990795Sgshapiro**	Returns:
41090795Sgshapiro**		mci (new).
41190795Sgshapiro*/
41290795Sgshapiro
41390795SgshapiroMCI *
41490795Sgshapiromci_new(rpool)
41590795Sgshapiro	SM_RPOOL_T *rpool;
41690795Sgshapiro{
41790795Sgshapiro	register MCI *mci;
41890795Sgshapiro
41990795Sgshapiro	if (rpool == NULL)
42090795Sgshapiro		mci = (MCI *) sm_malloc_x(sizeof *mci);
42190795Sgshapiro	else
42290795Sgshapiro		mci = (MCI *) sm_rpool_malloc_x(rpool, sizeof *mci);
42390795Sgshapiro	memset((char *) mci, '\0', sizeof *mci);
42490795Sgshapiro	mci->mci_rpool = sm_rpool_new_x(NULL);
42590795Sgshapiro	mci->mci_macro.mac_rpool = mci->mci_rpool;
42690795Sgshapiro	return mci;
42790795Sgshapiro}
42890795Sgshapiro/*
42964565Sgshapiro**  MCI_MATCH -- check connection cache for a particular host
43090795Sgshapiro**
43190795Sgshapiro**	Parameters:
43290795Sgshapiro**		host -- host to look for.
43390795Sgshapiro**		m -- mailer.
43490795Sgshapiro**
43590795Sgshapiro**	Returns:
43690795Sgshapiro**		true iff open connection exists.
43764565Sgshapiro*/
43864565Sgshapiro
43964565Sgshapirobool
44064565Sgshapiromci_match(host, m)
44164565Sgshapiro	char *host;
44264565Sgshapiro	MAILER *m;
44364565Sgshapiro{
44464565Sgshapiro	register MCI *mci;
44564565Sgshapiro	register STAB *s;
44664565Sgshapiro
44771348Sgshapiro	if (m->m_mno < 0 || m->m_mno > MAXMAILERS)
44890795Sgshapiro		return false;
44964565Sgshapiro	s = stab(host, ST_MCI + m->m_mno, ST_FIND);
45064565Sgshapiro	if (s == NULL)
45190795Sgshapiro		return false;
45264565Sgshapiro
45364565Sgshapiro	mci = &s->s_mci;
45490795Sgshapiro	return mci->mci_state == MCIS_OPEN;
45564565Sgshapiro}
45690795Sgshapiro/*
45738032Speter**  MCI_SETSTAT -- set status codes in MCI structure.
45838032Speter**
45938032Speter**	Parameters:
46038032Speter**		mci -- the MCI structure to set.
46138032Speter**		xstat -- the exit status code.
46238032Speter**		dstat -- the DSN status code.
46338032Speter**		rstat -- the SMTP status code.
46438032Speter**
46538032Speter**	Returns:
46638032Speter**		none.
46738032Speter*/
46838032Speter
46938032Spetervoid
47038032Spetermci_setstat(mci, xstat, dstat, rstat)
47138032Speter	MCI *mci;
47238032Speter	int xstat;
47338032Speter	char *dstat;
47438032Speter	char *rstat;
47538032Speter{
47638032Speter	/* protocol errors should never be interpreted as sticky */
47738032Speter	if (xstat != EX_NOTSTICKY && xstat != EX_PROTOCOL)
47838032Speter		mci->mci_exitstat = xstat;
47938032Speter
48090795Sgshapiro	SM_FREE_CLR(mci->mci_status);
48190795Sgshapiro	if (dstat != NULL)
48290795Sgshapiro		mci->mci_status = sm_strdup_x(dstat);
48390795Sgshapiro
48490795Sgshapiro	SM_FREE_CLR(mci->mci_rstatus);
48538032Speter	if (rstat != NULL)
48690795Sgshapiro		mci->mci_rstatus = sm_strdup_x(rstat);
48738032Speter}
48890795Sgshapiro/*
48938032Speter**  MCI_DUMP -- dump the contents of an MCI structure.
49038032Speter**
49138032Speter**	Parameters:
49238032Speter**		mci -- the MCI structure to dump.
49338032Speter**
49438032Speter**	Returns:
49538032Speter**		none.
49638032Speter**
49738032Speter**	Side Effects:
49838032Speter**		none.
49938032Speter*/
50038032Speter
50138032Speterstruct mcifbits
50238032Speter{
50338032Speter	int	mcif_bit;	/* flag bit */
50438032Speter	char	*mcif_name;	/* flag name */
50538032Speter};
50664565Sgshapirostatic struct mcifbits	MciFlags[] =
50738032Speter{
50838032Speter	{ MCIF_VALID,		"VALID"		},
50938032Speter	{ MCIF_CACHED,		"CACHED"	},
51038032Speter	{ MCIF_ESMTP,		"ESMTP"		},
51138032Speter	{ MCIF_EXPN,		"EXPN"		},
51238032Speter	{ MCIF_SIZE,		"SIZE"		},
51338032Speter	{ MCIF_8BITMIME,	"8BITMIME"	},
51438032Speter	{ MCIF_7BIT,		"7BIT"		},
51538032Speter	{ MCIF_INHEADER,	"INHEADER"	},
51638032Speter	{ MCIF_CVT8TO7,		"CVT8TO7"	},
51738032Speter	{ MCIF_DSN,		"DSN"		},
51838032Speter	{ MCIF_8BITOK,		"8BITOK"	},
51938032Speter	{ MCIF_CVT7TO8,		"CVT7TO8"	},
52038032Speter	{ MCIF_INMIME,		"INMIME"	},
52190795Sgshapiro	{ MCIF_AUTH,		"AUTH"		},
52290795Sgshapiro	{ MCIF_AUTHACT,		"AUTHACT"	},
52390795Sgshapiro	{ MCIF_ENHSTAT,		"ENHSTAT"	},
52490795Sgshapiro	{ MCIF_PIPELINED,	"PIPELINED"	},
52590795Sgshapiro#if STARTTLS
52690795Sgshapiro	{ MCIF_TLS,		"TLS"		},
52790795Sgshapiro	{ MCIF_TLSACT,		"TLSACT"	},
52890795Sgshapiro#endif /* STARTTLS */
52990795Sgshapiro	{ MCIF_DLVR_BY,		"DLVR_BY"	},
53071348Sgshapiro	{ 0,			NULL		}
53138032Speter};
53238032Speter
53338032Spetervoid
53438032Spetermci_dump(mci, logit)
53538032Speter	register MCI *mci;
53638032Speter	bool logit;
53738032Speter{
53838032Speter	register char *p;
53938032Speter	char *sep;
54038032Speter	char buf[4000];
54138032Speter
54238032Speter	sep = logit ? " " : "\n\t";
54338032Speter	p = buf;
54490795Sgshapiro	(void) sm_snprintf(p, SPACELEFT(buf, p), "MCI@%p: ", mci);
54538032Speter	p += strlen(p);
54638032Speter	if (mci == NULL)
54738032Speter	{
54890795Sgshapiro		(void) sm_snprintf(p, SPACELEFT(buf, p), "NULL");
54938032Speter		goto printit;
55038032Speter	}
55190795Sgshapiro	(void) sm_snprintf(p, SPACELEFT(buf, p), "flags=%lx", mci->mci_flags);
55238032Speter	p += strlen(p);
55338032Speter	if (mci->mci_flags != 0)
55438032Speter	{
55538032Speter		struct mcifbits *f;
55638032Speter
55738032Speter		*p++ = '<';
55838032Speter		for (f = MciFlags; f->mcif_bit != 0; f++)
55938032Speter		{
56038032Speter			if (!bitset(f->mcif_bit, mci->mci_flags))
56138032Speter				continue;
56290795Sgshapiro			(void) sm_strlcpyn(p, SPACELEFT(buf, p), 2,
56390795Sgshapiro					   f->mcif_name, ",");
56438032Speter			p += strlen(p);
56538032Speter		}
56638032Speter		p[-1] = '>';
56738032Speter	}
56890795Sgshapiro
56990795Sgshapiro	/* Note: sm_snprintf() takes care of NULL arguments for %s */
57090795Sgshapiro	(void) sm_snprintf(p, SPACELEFT(buf, p),
57138032Speter		",%serrno=%d, herrno=%d, exitstat=%d, state=%d, pid=%d,%s",
57238032Speter		sep, mci->mci_errno, mci->mci_herrno,
57364565Sgshapiro		mci->mci_exitstat, mci->mci_state, (int) mci->mci_pid, sep);
57438032Speter	p += strlen(p);
57590795Sgshapiro	(void) sm_snprintf(p, SPACELEFT(buf, p),
57638032Speter		"maxsize=%ld, phase=%s, mailer=%s,%s",
57790795Sgshapiro		mci->mci_maxsize, mci->mci_phase,
57838032Speter		mci->mci_mailer == NULL ? "NULL" : mci->mci_mailer->m_name,
57938032Speter		sep);
58038032Speter	p += strlen(p);
58190795Sgshapiro	(void) sm_snprintf(p, SPACELEFT(buf, p),
58238032Speter		"status=%s, rstatus=%s,%s",
58390795Sgshapiro		mci->mci_status, mci->mci_rstatus, sep);
58438032Speter	p += strlen(p);
58590795Sgshapiro	(void) sm_snprintf(p, SPACELEFT(buf, p),
58638032Speter		"host=%s, lastuse=%s",
58790795Sgshapiro		mci->mci_host, ctime(&mci->mci_lastuse));
58838032Speterprintit:
58938032Speter	if (logit)
59038032Speter		sm_syslog(LOG_DEBUG, CurEnv->e_id, "%.1000s", buf);
59138032Speter	else
59290795Sgshapiro		(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%s\n", buf);
59338032Speter}
59490795Sgshapiro/*
59538032Speter**  MCI_DUMP_ALL -- print the entire MCI cache
59638032Speter**
59738032Speter**	Parameters:
59838032Speter**		logit -- if set, log the result instead of printing
59938032Speter**			to stdout.
60038032Speter**
60138032Speter**	Returns:
60238032Speter**		none.
60338032Speter*/
60438032Speter
60538032Spetervoid
60638032Spetermci_dump_all(logit)
60738032Speter	bool logit;
60838032Speter{
60938032Speter	register int i;
61038032Speter
61138032Speter	if (MciCache == NULL)
61238032Speter		return;
61338032Speter
61438032Speter	for (i = 0; i < MaxMciCache; i++)
61538032Speter		mci_dump(MciCache[i], logit);
61638032Speter}
61790795Sgshapiro/*
61842580Speter**  MCI_LOCK_HOST -- Lock host while sending.
61938032Speter**
62038032Speter**	If we are contacting a host, we'll need to
62138032Speter**	update the status information in the host status
62238032Speter**	file, and if we want to do that, we ought to have
62338032Speter**	locked it. This has the (according to some)
62438032Speter**	desirable effect of serializing connectivity with
62590795Sgshapiro**	remote hosts -- i.e.: one connection to a given
62638032Speter**	host at a time.
62738032Speter**
62838032Speter**	Parameters:
62938032Speter**		mci -- containing the host we want to lock.
63038032Speter**
63138032Speter**	Returns:
63264565Sgshapiro**		EX_OK	    -- got the lock.
63338032Speter**		EX_TEMPFAIL -- didn't get the lock.
63438032Speter*/
63538032Speter
63638032Speterint
63738032Spetermci_lock_host(mci)
63838032Speter	MCI *mci;
63938032Speter{
64038032Speter	if (mci == NULL)
64138032Speter	{
64238032Speter		if (tTd(56, 1))
64390795Sgshapiro			sm_dprintf("mci_lock_host: NULL mci\n");
64438032Speter		return EX_OK;
64538032Speter	}
64638032Speter
64738032Speter	if (!SingleThreadDelivery)
64838032Speter		return EX_OK;
64938032Speter
65038032Speter	return mci_lock_host_statfile(mci);
65138032Speter}
65238032Speter
65364565Sgshapirostatic int
65438032Spetermci_lock_host_statfile(mci)
65538032Speter	MCI *mci;
65638032Speter{
65764565Sgshapiro	int save_errno = errno;
65838032Speter	int retVal = EX_OK;
65998125Sgshapiro	char fname[MAXPATHLEN];
66038032Speter
66138032Speter	if (HostStatDir == NULL || mci->mci_host == NULL)
66238032Speter		return EX_OK;
66338032Speter
66438032Speter	if (tTd(56, 2))
66590795Sgshapiro		sm_dprintf("mci_lock_host: attempting to lock %s\n",
66690795Sgshapiro			   mci->mci_host);
66738032Speter
66890795Sgshapiro	if (mci_generate_persistent_path(mci->mci_host, fname, sizeof fname,
66990795Sgshapiro					 true) < 0)
67038032Speter	{
67138032Speter		/* of course this should never happen */
67238032Speter		if (tTd(56, 2))
67390795Sgshapiro			sm_dprintf("mci_lock_host: Failed to generate host path for %s\n",
67490795Sgshapiro				   mci->mci_host);
67538032Speter
67638032Speter		retVal = EX_TEMPFAIL;
67738032Speter		goto cleanup;
67838032Speter	}
67938032Speter
68038032Speter	mci->mci_statfile = safefopen(fname, O_RDWR, FileMode,
68164565Sgshapiro				      SFF_NOLOCK|SFF_NOLINK|SFF_OPENASROOT|SFF_REGONLY|SFF_SAFEDIRPATH|SFF_CREAT);
68238032Speter
68338032Speter	if (mci->mci_statfile == NULL)
68438032Speter	{
68590795Sgshapiro		syserr("mci_lock_host: cannot create host lock file %s", fname);
68638032Speter		goto cleanup;
68738032Speter	}
68838032Speter
68990795Sgshapiro	if (!lockfile(sm_io_getinfo(mci->mci_statfile, SM_IO_WHAT_FD, NULL),
69090795Sgshapiro		      fname, "", LOCK_EX|LOCK_NB))
69138032Speter	{
69238032Speter		if (tTd(56, 2))
69390795Sgshapiro			sm_dprintf("mci_lock_host: couldn't get lock on %s\n",
69464565Sgshapiro				fname);
69590795Sgshapiro		(void) sm_io_close(mci->mci_statfile, SM_TIME_DEFAULT);
69638032Speter		mci->mci_statfile = NULL;
69738032Speter		retVal = EX_TEMPFAIL;
69838032Speter		goto cleanup;
69938032Speter	}
70038032Speter
70138032Speter	if (tTd(56, 12) && mci->mci_statfile != NULL)
70290795Sgshapiro		sm_dprintf("mci_lock_host: Sanity check -- lock is good\n");
70338032Speter
70438032Spetercleanup:
70564565Sgshapiro	errno = save_errno;
70638032Speter	return retVal;
70738032Speter}
70890795Sgshapiro/*
70938032Speter**  MCI_UNLOCK_HOST -- unlock host
71038032Speter**
71138032Speter**	Clean up the lock on a host, close the file, let
71238032Speter**	someone else use it.
71338032Speter**
71438032Speter**	Parameters:
71538032Speter**		mci -- us.
71638032Speter**
71738032Speter**	Returns:
71838032Speter**		nothing.
71938032Speter*/
72038032Speter
72138032Spetervoid
72238032Spetermci_unlock_host(mci)
72338032Speter	MCI *mci;
72438032Speter{
72564565Sgshapiro	int save_errno = errno;
72638032Speter
72738032Speter	if (mci == NULL)
72838032Speter	{
72938032Speter		if (tTd(56, 1))
73090795Sgshapiro			sm_dprintf("mci_unlock_host: NULL mci\n");
73138032Speter		return;
73238032Speter	}
73338032Speter
73438032Speter	if (HostStatDir == NULL || mci->mci_host == NULL)
73538032Speter		return;
73638032Speter
73738032Speter	if (!SingleThreadDelivery && mci_lock_host_statfile(mci) == EX_TEMPFAIL)
73838032Speter	{
73938032Speter		if (tTd(56, 1))
74090795Sgshapiro			sm_dprintf("mci_unlock_host: stat file already locked\n");
74138032Speter	}
74238032Speter	else
74338032Speter	{
74438032Speter		if (tTd(56, 2))
74590795Sgshapiro			sm_dprintf("mci_unlock_host: store prior to unlock\n");
74638032Speter		mci_store_persistent(mci);
74738032Speter	}
74838032Speter
74938032Speter	if (mci->mci_statfile != NULL)
75038032Speter	{
75190795Sgshapiro		(void) sm_io_close(mci->mci_statfile, SM_TIME_DEFAULT);
75238032Speter		mci->mci_statfile = NULL;
75338032Speter	}
75438032Speter
75564565Sgshapiro	errno = save_errno;
75638032Speter}
75790795Sgshapiro/*
75842580Speter**  MCI_LOAD_PERSISTENT -- load persistent host info
75938032Speter**
76038032Speter**	Load information about host that is kept
76138032Speter**	in common for all running sendmails.
76238032Speter**
76338032Speter**	Parameters:
76490795Sgshapiro**		mci -- the host/connection to load persistent info for.
76538032Speter**
76638032Speter**	Returns:
76790795Sgshapiro**		true -- lock was successful
76890795Sgshapiro**		false -- lock failed
76938032Speter*/
77038032Speter
77164565Sgshapirostatic bool
77238032Spetermci_load_persistent(mci)
77338032Speter	MCI *mci;
77438032Speter{
77564565Sgshapiro	int save_errno = errno;
77690795Sgshapiro	bool locked = true;
77790795Sgshapiro	SM_FILE_T *fp;
77898125Sgshapiro	char fname[MAXPATHLEN];
77938032Speter
78038032Speter	if (mci == NULL)
78138032Speter	{
78238032Speter		if (tTd(56, 1))
78390795Sgshapiro			sm_dprintf("mci_load_persistent: NULL mci\n");
78490795Sgshapiro		return true;
78538032Speter	}
78638032Speter
78738032Speter	if (IgnoreHostStatus || HostStatDir == NULL || mci->mci_host == NULL)
78890795Sgshapiro		return true;
78964565Sgshapiro
79038032Speter	/* Already have the persistent information in memory */
79138032Speter	if (SingleThreadDelivery && mci->mci_statfile != NULL)
79290795Sgshapiro		return true;
79338032Speter
79438032Speter	if (tTd(56, 1))
79590795Sgshapiro		sm_dprintf("mci_load_persistent: Attempting to load persistent information for %s\n",
79690795Sgshapiro			   mci->mci_host);
79764565Sgshapiro
79890795Sgshapiro	if (mci_generate_persistent_path(mci->mci_host, fname, sizeof fname,
79990795Sgshapiro					 false) < 0)
80038032Speter	{
80138032Speter		/* Not much we can do if the file isn't there... */
80238032Speter		if (tTd(56, 1))
80390795Sgshapiro			sm_dprintf("mci_load_persistent: Couldn't generate host path\n");
80438032Speter		goto cleanup;
80538032Speter	}
80638032Speter
80738032Speter	fp = safefopen(fname, O_RDONLY, FileMode,
80838032Speter		       SFF_NOLOCK|SFF_NOLINK|SFF_OPENASROOT|SFF_REGONLY|SFF_SAFEDIRPATH);
80938032Speter	if (fp == NULL)
81038032Speter	{
81138032Speter		/* I can't think of any reason this should ever happen */
81238032Speter		if (tTd(56, 1))
81390795Sgshapiro			sm_dprintf("mci_load_persistent: open(%s): %s\n",
81490795Sgshapiro				fname, sm_errstring(errno));
81538032Speter		goto cleanup;
81638032Speter	}
81738032Speter
81838032Speter	FileName = fname;
81990795Sgshapiro	locked = lockfile(sm_io_getinfo(fp, SM_IO_WHAT_FD, NULL), fname, "",
82090795Sgshapiro			  LOCK_SH|LOCK_NB);
82164565Sgshapiro	if (locked)
82264565Sgshapiro	{
82364565Sgshapiro		(void) mci_read_persistent(fp, mci);
82490795Sgshapiro		(void) lockfile(sm_io_getinfo(fp, SM_IO_WHAT_FD, NULL), fname,
82590795Sgshapiro				"", LOCK_UN);
82664565Sgshapiro	}
82738032Speter	FileName = NULL;
82890795Sgshapiro	(void) sm_io_close(fp, SM_TIME_DEFAULT);
82938032Speter
83038032Spetercleanup:
83164565Sgshapiro	errno = save_errno;
83238032Speter	return locked;
83338032Speter}
83490795Sgshapiro/*
83538032Speter**  MCI_READ_PERSISTENT -- read persistent host status file
83638032Speter**
83738032Speter**	Parameters:
83838032Speter**		fp -- the file pointer to read.
83938032Speter**		mci -- the pointer to fill in.
84038032Speter**
84138032Speter**	Returns:
84238032Speter**		-1 -- if the file was corrupt.
84338032Speter**		0 -- otherwise.
84438032Speter**
84538032Speter**	Warning:
84638032Speter**		This code makes the assumption that this data
84738032Speter**		will be read in an atomic fashion, and that the data
84838032Speter**		was written in an atomic fashion.  Any other functioning
84938032Speter**		may lead to some form of insanity.  This should be
85038032Speter**		perfectly safe due to underlying stdio buffering.
85138032Speter*/
85238032Speter
85364565Sgshapirostatic int
85438032Spetermci_read_persistent(fp, mci)
85590795Sgshapiro	SM_FILE_T *fp;
85638032Speter	register MCI *mci;
85738032Speter{
85838032Speter	int ver;
85938032Speter	register char *p;
86038032Speter	int saveLineNumber = LineNumber;
86138032Speter	char buf[MAXLINE];
86238032Speter
86338032Speter	if (fp == NULL)
86438032Speter		syserr("mci_read_persistent: NULL fp");
86538032Speter	if (mci == NULL)
86638032Speter		syserr("mci_read_persistent: NULL mci");
86738032Speter	if (tTd(56, 93))
86838032Speter	{
86990795Sgshapiro		sm_dprintf("mci_read_persistent: fp=%lx, mci=",
87090795Sgshapiro			   (unsigned long) fp);
87138032Speter	}
87238032Speter
87390795Sgshapiro	SM_FREE_CLR(mci->mci_status);
87490795Sgshapiro	SM_FREE_CLR(mci->mci_rstatus);
87538032Speter
87690795Sgshapiro	sm_io_rewind(fp, SM_TIME_DEFAULT);
87738032Speter	ver = -1;
87838032Speter	LineNumber = 0;
87990795Sgshapiro	while (sm_io_fgets(fp, SM_TIME_DEFAULT, buf, sizeof buf) != NULL)
88038032Speter	{
88138032Speter		LineNumber++;
88238032Speter		p = strchr(buf, '\n');
88338032Speter		if (p != NULL)
88438032Speter			*p = '\0';
88538032Speter		switch (buf[0])
88638032Speter		{
88738032Speter		  case 'V':		/* version stamp */
88838032Speter			ver = atoi(&buf[1]);
88938032Speter			if (ver < 0 || ver > 0)
89038032Speter				syserr("Unknown host status version %d: %d max",
89138032Speter					ver, 0);
89238032Speter			break;
89338032Speter
89438032Speter		  case 'E':		/* UNIX error number */
89538032Speter			mci->mci_errno = atoi(&buf[1]);
89638032Speter			break;
89738032Speter
89838032Speter		  case 'H':		/* DNS error number */
89938032Speter			mci->mci_herrno = atoi(&buf[1]);
90038032Speter			break;
90138032Speter
90238032Speter		  case 'S':		/* UNIX exit status */
90338032Speter			mci->mci_exitstat = atoi(&buf[1]);
90438032Speter			break;
90538032Speter
90638032Speter		  case 'D':		/* DSN status */
90738032Speter			mci->mci_status = newstr(&buf[1]);
90838032Speter			break;
90938032Speter
91038032Speter		  case 'R':		/* SMTP status */
91138032Speter			mci->mci_rstatus = newstr(&buf[1]);
91238032Speter			break;
91338032Speter
91438032Speter		  case 'U':		/* last usage time */
91538032Speter			mci->mci_lastuse = atol(&buf[1]);
91638032Speter			break;
91738032Speter
91838032Speter		  case '.':		/* end of file */
91990795Sgshapiro			if (tTd(56, 93))
92090795Sgshapiro				mci_dump(mci, false);
92138032Speter			return 0;
92238032Speter
92338032Speter		  default:
92438032Speter			sm_syslog(LOG_CRIT, NOQID,
92538032Speter				  "%s: line %d: Unknown host status line \"%s\"",
92638032Speter				  FileName == NULL ? mci->mci_host : FileName,
92738032Speter				  LineNumber, buf);
92838032Speter			LineNumber = saveLineNumber;
92938032Speter			return -1;
93038032Speter		}
93138032Speter	}
93238032Speter	LineNumber = saveLineNumber;
93390795Sgshapiro	if (tTd(56, 93))
93490795Sgshapiro		sm_dprintf("incomplete (missing dot for EOF)\n");
93538032Speter	if (ver < 0)
93638032Speter		return -1;
93738032Speter	return 0;
93838032Speter}
93990795Sgshapiro/*
94038032Speter**  MCI_STORE_PERSISTENT -- Store persistent MCI information
94138032Speter**
94238032Speter**	Store information about host that is kept
94338032Speter**	in common for all running sendmails.
94438032Speter**
94538032Speter**	Parameters:
94638032Speter**		mci -- the host/connection to store persistent info for.
94738032Speter**
94838032Speter**	Returns:
94938032Speter**		none.
95038032Speter*/
95138032Speter
95238032Spetervoid
95338032Spetermci_store_persistent(mci)
95438032Speter	MCI *mci;
95538032Speter{
95664565Sgshapiro	int save_errno = errno;
95738032Speter
95838032Speter	if (mci == NULL)
95938032Speter	{
96038032Speter		if (tTd(56, 1))
96190795Sgshapiro			sm_dprintf("mci_store_persistent: NULL mci\n");
96238032Speter		return;
96338032Speter	}
96438032Speter
96538032Speter	if (HostStatDir == NULL || mci->mci_host == NULL)
96638032Speter		return;
96738032Speter
96838032Speter	if (tTd(56, 1))
96990795Sgshapiro		sm_dprintf("mci_store_persistent: Storing information for %s\n",
97090795Sgshapiro			   mci->mci_host);
97138032Speter
97238032Speter	if (mci->mci_statfile == NULL)
97338032Speter	{
97438032Speter		if (tTd(56, 1))
97590795Sgshapiro			sm_dprintf("mci_store_persistent: no statfile\n");
97638032Speter		return;
97738032Speter	}
97838032Speter
97990795Sgshapiro	sm_io_rewind(mci->mci_statfile, SM_TIME_DEFAULT);
98038032Speter#if !NOFTRUNCATE
98190795Sgshapiro	(void) ftruncate(sm_io_getinfo(mci->mci_statfile, SM_IO_WHAT_FD, NULL),
98290795Sgshapiro			 (off_t) 0);
98364565Sgshapiro#endif /* !NOFTRUNCATE */
98438032Speter
98590795Sgshapiro	(void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT, "V0\n");
98690795Sgshapiro	(void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT, "E%d\n",
98790795Sgshapiro			     mci->mci_errno);
98890795Sgshapiro	(void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT, "H%d\n",
98990795Sgshapiro			     mci->mci_herrno);
99090795Sgshapiro	(void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT, "S%d\n",
99190795Sgshapiro			     mci->mci_exitstat);
99238032Speter	if (mci->mci_status != NULL)
99390795Sgshapiro		(void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT,
99490795Sgshapiro				     "D%.80s\n",
99590795Sgshapiro				     denlstring(mci->mci_status, true, false));
99638032Speter	if (mci->mci_rstatus != NULL)
99790795Sgshapiro		(void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT,
99890795Sgshapiro				     "R%.80s\n",
99990795Sgshapiro				     denlstring(mci->mci_rstatus, true, false));
100090795Sgshapiro	(void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT, "U%ld\n",
100190795Sgshapiro			     (long)(mci->mci_lastuse));
100290795Sgshapiro	(void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT, ".\n");
100338032Speter
100490795Sgshapiro	(void) sm_io_flush(mci->mci_statfile, SM_TIME_DEFAULT);
100538032Speter
100664565Sgshapiro	errno = save_errno;
100738032Speter	return;
100838032Speter}
100990795Sgshapiro/*
101038032Speter**  MCI_TRAVERSE_PERSISTENT -- walk persistent status tree
101138032Speter**
101238032Speter**	Recursively find all the mci host files in `pathname'.  Default to
101338032Speter**		main host status directory if no path is provided.
101438032Speter**	Call (*action)(pathname, host) for each file found.
101538032Speter**
101638032Speter**	Note: all information is collected in a list before it is processed.
101738032Speter**	This may not be the best way to do it, but it seems safest, since
101838032Speter**	the file system would be touched while we are attempting to traverse
101938032Speter**	the directory tree otherwise (during purges).
102038032Speter**
102138032Speter**	Parameters:
102238032Speter**		action -- function to call on each node.  If returns < 0,
102338032Speter**			return immediately.
102438032Speter**		pathname -- root of tree.  If null, use main host status
102538032Speter**			directory.
102638032Speter**
102738032Speter**	Returns:
102838032Speter**		< 0 -- if any action routine returns a negative value, that
102938032Speter**			value is returned.
103038032Speter**		0 -- if we successfully went to completion.
103164565Sgshapiro**		> 0 -- return status from action()
103238032Speter*/
103338032Speter
103438032Speterint
103538032Spetermci_traverse_persistent(action, pathname)
103638032Speter	int (*action)();
103738032Speter	char *pathname;
103838032Speter{
103938032Speter	struct stat statbuf;
104038032Speter	DIR *d;
104138032Speter	int ret;
104238032Speter
104338032Speter	if (pathname == NULL)
104438032Speter		pathname = HostStatDir;
104538032Speter	if (pathname == NULL)
104638032Speter		return -1;
104738032Speter
104838032Speter	if (tTd(56, 1))
104990795Sgshapiro		sm_dprintf("mci_traverse: pathname is %s\n", pathname);
105038032Speter
105138032Speter	ret = stat(pathname, &statbuf);
105238032Speter	if (ret < 0)
105338032Speter	{
105438032Speter		if (tTd(56, 2))
105590795Sgshapiro			sm_dprintf("mci_traverse: Failed to stat %s: %s\n",
105690795Sgshapiro				pathname, sm_errstring(errno));
105738032Speter		return ret;
105838032Speter	}
105938032Speter	if (S_ISDIR(statbuf.st_mode))
106038032Speter	{
106190795Sgshapiro		bool leftone, removedone;
106290795Sgshapiro		size_t len;
106390795Sgshapiro		char *newptr;
106438032Speter		struct dirent *e;
106598125Sgshapiro		char newpath[MAXPATHLEN];
106638032Speter
106738032Speter		if ((d = opendir(pathname)) == NULL)
106838032Speter		{
106938032Speter			if (tTd(56, 2))
107090795Sgshapiro				sm_dprintf("mci_traverse: opendir %s: %s\n",
107190795Sgshapiro					pathname, sm_errstring(errno));
107238032Speter			return -1;
107338032Speter		}
107490795Sgshapiro		len = sizeof(newpath) - MAXNAMLEN - 3;
107590795Sgshapiro		if (sm_strlcpy(newpath, pathname, len) >= len)
107638032Speter		{
107738032Speter			if (tTd(56, 2))
107890795Sgshapiro				sm_dprintf("mci_traverse: path \"%s\" too long",
107938032Speter					pathname);
108038032Speter			return -1;
108138032Speter		}
108238032Speter		newptr = newpath + strlen(newpath);
108338032Speter		*newptr++ = '/';
108438032Speter
108564565Sgshapiro		/*
108664565Sgshapiro		**  repeat until no file has been removed
108764565Sgshapiro		**  this may become ugly when several files "expire"
108864565Sgshapiro		**  during these loops, but it's better than doing
108964565Sgshapiro		**  a rewinddir() inside the inner loop
109064565Sgshapiro		*/
109190795Sgshapiro
109264565Sgshapiro		do
109338032Speter		{
109490795Sgshapiro			leftone = removedone = false;
109564565Sgshapiro			while ((e = readdir(d)) != NULL)
109664565Sgshapiro			{
109764565Sgshapiro				if (e->d_name[0] == '.')
109864565Sgshapiro					continue;
109938032Speter
110090795Sgshapiro				(void) sm_strlcpy(newptr, e->d_name,
110164565Sgshapiro					       sizeof newpath -
110264565Sgshapiro					       (newptr - newpath));
110338032Speter
110477352Sgshapiro				if (StopRequest)
110577352Sgshapiro					stop_sendmail();
110664565Sgshapiro				ret = mci_traverse_persistent(action, newpath);
110764565Sgshapiro				if (ret < 0)
110864565Sgshapiro					break;
110964565Sgshapiro				if (ret == 1)
111090795Sgshapiro					leftone = true;
111164565Sgshapiro				if (!removedone && ret == 0 &&
111264565Sgshapiro				    action == mci_purge_persistent)
111390795Sgshapiro					removedone = true;
111464565Sgshapiro			}
111538032Speter			if (ret < 0)
111638032Speter				break;
111790795Sgshapiro
111838032Speter			/*
111938032Speter			**  The following appears to be
112038032Speter			**  necessary during purges, since
112138032Speter			**  we modify the directory structure
112238032Speter			*/
112390795Sgshapiro
112464565Sgshapiro			if (removedone)
112538032Speter				rewinddir(d);
112664565Sgshapiro			if (tTd(56, 40))
112790795Sgshapiro				sm_dprintf("mci_traverse: path %s: ret %d removed %d left %d\n",
112864565Sgshapiro					pathname, ret, removedone, leftone);
112964565Sgshapiro		} while (removedone);
113038032Speter
113138032Speter		/* purge (or whatever) the directory proper */
113264565Sgshapiro		if (!leftone)
113364565Sgshapiro		{
113464565Sgshapiro			*--newptr = '\0';
113564565Sgshapiro			ret = (*action)(newpath, NULL);
113664565Sgshapiro		}
113764565Sgshapiro		(void) closedir(d);
113838032Speter	}
113938032Speter	else if (S_ISREG(statbuf.st_mode))
114038032Speter	{
114138032Speter		char *end = pathname + strlen(pathname) - 1;
114238032Speter		char *start;
114338032Speter		char *scan;
114438032Speter		char host[MAXHOSTNAMELEN];
114538032Speter		char *hostptr = host;
114664565Sgshapiro
114738032Speter		/*
114838032Speter		**  Reconstruct the host name from the path to the
114938032Speter		**  persistent information.
115038032Speter		*/
115138032Speter
115238032Speter		do
115338032Speter		{
115438032Speter			if (hostptr != host)
115538032Speter				*(hostptr++) = '.';
115638032Speter			start = end;
115738032Speter			while (*(start - 1) != '/')
115838032Speter				start--;
115938032Speter
116038032Speter			if (*end == '.')
116138032Speter				end--;
116238032Speter
116338032Speter			for (scan = start; scan <= end; scan++)
116438032Speter				*(hostptr++) = *scan;
116538032Speter
116638032Speter			end = start - 2;
116738032Speter		} while (*end == '.');
116838032Speter
116938032Speter		*hostptr = '\0';
117038032Speter
117138032Speter		/*
117242580Speter		**  Do something with the file containing the persistent
117342580Speter		**  information.
117438032Speter		*/
117590795Sgshapiro
117638032Speter		ret = (*action)(pathname, host);
117738032Speter	}
117838032Speter
117938032Speter	return ret;
118038032Speter}
118190795Sgshapiro/*
118264565Sgshapiro**  MCI_PRINT_PERSISTENT -- print persistent info
118338032Speter**
118438032Speter**	Dump the persistent information in the file 'pathname'
118538032Speter**
118638032Speter**	Parameters:
118738032Speter**		pathname -- the pathname to the status file.
118838032Speter**		hostname -- the corresponding host name.
118938032Speter**
119038032Speter**	Returns:
119138032Speter**		0
119238032Speter*/
119338032Speter
119438032Speterint
119538032Spetermci_print_persistent(pathname, hostname)
119638032Speter	char *pathname;
119738032Speter	char *hostname;
119838032Speter{
119990795Sgshapiro	static bool initflag = false;
120090795Sgshapiro	SM_FILE_T *fp;
120138032Speter	int width = Verbose ? 78 : 25;
120238032Speter	bool locked;
120338032Speter	MCI mcib;
120438032Speter
120538032Speter	/* skip directories */
120638032Speter	if (hostname == NULL)
120738032Speter		return 0;
120838032Speter
120977352Sgshapiro	if (StopRequest)
121077352Sgshapiro		stop_sendmail();
121177352Sgshapiro
121238032Speter	if (!initflag)
121338032Speter	{
121490795Sgshapiro		initflag = true;
121590795Sgshapiro		(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
121690795Sgshapiro				     " -------------- Hostname --------------- How long ago ---------Results---------\n");
121738032Speter	}
121838032Speter
121990795Sgshapiro	fp = safefopen(pathname, O_RDONLY, FileMode,
122038032Speter		       SFF_NOLOCK|SFF_NOLINK|SFF_OPENASROOT|SFF_REGONLY|SFF_SAFEDIRPATH);
122138032Speter
122238032Speter	if (fp == NULL)
122338032Speter	{
122438032Speter		if (tTd(56, 1))
122590795Sgshapiro			sm_dprintf("mci_print_persistent: cannot open %s: %s\n",
122690795Sgshapiro				pathname, sm_errstring(errno));
122738032Speter		return 0;
122838032Speter	}
122938032Speter
123038032Speter	FileName = pathname;
123164565Sgshapiro	memset(&mcib, '\0', sizeof mcib);
123238032Speter	if (mci_read_persistent(fp, &mcib) < 0)
123338032Speter	{
123438032Speter		syserr("%s: could not read status file", pathname);
123590795Sgshapiro		(void) sm_io_close(fp, SM_TIME_DEFAULT);
123638032Speter		FileName = NULL;
123738032Speter		return 0;
123838032Speter	}
123938032Speter
124090795Sgshapiro	locked = !lockfile(sm_io_getinfo(fp, SM_IO_WHAT_FD, NULL), pathname,
124190795Sgshapiro			   "", LOCK_SH|LOCK_NB);
124290795Sgshapiro	(void) sm_io_close(fp, SM_TIME_DEFAULT);
124338032Speter	FileName = NULL;
124438032Speter
124590795Sgshapiro	(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%c%-39s %12s ",
124690795Sgshapiro			     locked ? '*' : ' ', hostname,
124790795Sgshapiro			     pintvl(curtime() - mcib.mci_lastuse, true));
124838032Speter	if (mcib.mci_rstatus != NULL)
124990795Sgshapiro		(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%.*s\n", width,
125090795Sgshapiro				     mcib.mci_rstatus);
125138032Speter	else if (mcib.mci_exitstat == EX_TEMPFAIL && mcib.mci_errno != 0)
125290795Sgshapiro		(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
125390795Sgshapiro				     "Deferred: %.*s\n", width - 10,
125490795Sgshapiro				     sm_errstring(mcib.mci_errno));
125538032Speter	else if (mcib.mci_exitstat != 0)
125638032Speter	{
125790795Sgshapiro		char *exmsg = sm_sysexmsg(mcib.mci_exitstat);
125838032Speter
125990795Sgshapiro		if (exmsg == NULL)
126038032Speter		{
126138032Speter			char buf[80];
126238032Speter
126390795Sgshapiro			(void) sm_snprintf(buf, sizeof buf,
126490795Sgshapiro				"Unknown mailer error %d",
126538032Speter				mcib.mci_exitstat);
126690795Sgshapiro			(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%.*s\n",
126790795Sgshapiro					     width, buf);
126838032Speter		}
126938032Speter		else
127090795Sgshapiro			(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%.*s\n",
127190795Sgshapiro					     width, &exmsg[5]);
127238032Speter	}
127338032Speter	else if (mcib.mci_errno == 0)
127490795Sgshapiro		(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "OK\n");
127538032Speter	else
127690795Sgshapiro		(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "OK: %.*s\n",
127790795Sgshapiro				     width - 4, sm_errstring(mcib.mci_errno));
127838032Speter
127938032Speter	return 0;
128038032Speter}
128190795Sgshapiro/*
128238032Speter**  MCI_PURGE_PERSISTENT -- Remove a persistence status file.
128338032Speter**
128438032Speter**	Parameters:
128538032Speter**		pathname -- path to the status file.
128638032Speter**		hostname -- name of host corresponding to that file.
128738032Speter**			NULL if this is a directory (domain).
128838032Speter**
128938032Speter**	Returns:
129064565Sgshapiro**		0 -- ok
129164565Sgshapiro**		1 -- file not deleted (too young, incorrect format)
129264565Sgshapiro**		< 0 -- some error occurred
129338032Speter*/
129438032Speter
129538032Speterint
129638032Spetermci_purge_persistent(pathname, hostname)
129738032Speter	char *pathname;
129838032Speter	char *hostname;
129938032Speter{
130064565Sgshapiro	struct stat statbuf;
130138032Speter	char *end = pathname + strlen(pathname) - 1;
130264565Sgshapiro	int ret;
130338032Speter
130438032Speter	if (tTd(56, 1))
130590795Sgshapiro		sm_dprintf("mci_purge_persistent: purging %s\n", pathname);
130638032Speter
130764565Sgshapiro	ret = stat(pathname, &statbuf);
130864565Sgshapiro	if (ret < 0)
130964565Sgshapiro	{
131064565Sgshapiro		if (tTd(56, 2))
131190795Sgshapiro			sm_dprintf("mci_purge_persistent: Failed to stat %s: %s\n",
131290795Sgshapiro				pathname, sm_errstring(errno));
131364565Sgshapiro		return ret;
131464565Sgshapiro	}
131594337Sgshapiro	if (curtime() - statbuf.st_mtime <= MciInfoTimeout)
131664565Sgshapiro		return 1;
131738032Speter	if (hostname != NULL)
131838032Speter	{
131938032Speter		/* remove the file */
132090795Sgshapiro		ret = unlink(pathname);
132190795Sgshapiro		if (ret < 0)
132238032Speter		{
132390795Sgshapiro			if (LogLevel > 8)
132490795Sgshapiro				sm_syslog(LOG_ERR, NOQID,
132590795Sgshapiro					  "mci_purge_persistent: failed to unlink %s: %s",
132690795Sgshapiro					  pathname, sm_errstring(errno));
132738032Speter			if (tTd(56, 2))
132890795Sgshapiro				sm_dprintf("mci_purge_persistent: failed to unlink %s: %s\n",
132990795Sgshapiro					pathname, sm_errstring(errno));
133090795Sgshapiro			return ret;
133138032Speter		}
133238032Speter	}
133338032Speter	else
133438032Speter	{
133538032Speter		/* remove the directory */
133638032Speter		if (*end != '.')
133764565Sgshapiro			return 1;
133838032Speter
133938032Speter		if (tTd(56, 1))
134090795Sgshapiro			sm_dprintf("mci_purge_persistent: dpurge %s\n", pathname);
134138032Speter
134290795Sgshapiro		ret = rmdir(pathname);
134390795Sgshapiro		if (ret < 0)
134438032Speter		{
134538032Speter			if (tTd(56, 2))
134690795Sgshapiro				sm_dprintf("mci_purge_persistent: rmdir %s: %s\n",
134790795Sgshapiro					pathname, sm_errstring(errno));
134890795Sgshapiro			return ret;
134938032Speter		}
135038032Speter	}
135138032Speter
135238032Speter	return 0;
135338032Speter}
135490795Sgshapiro/*
135542580Speter**  MCI_GENERATE_PERSISTENT_PATH -- generate path from hostname
135638032Speter**
135738032Speter**	Given `host', convert from a.b.c to $QueueDir/.hoststat/c./b./a,
135838032Speter**	putting the result into `path'.  if `createflag' is set, intervening
135938032Speter**	directories will be created as needed.
136038032Speter**
136138032Speter**	Parameters:
136238032Speter**		host -- host name to convert from.
136338032Speter**		path -- place to store result.
136438032Speter**		pathlen -- length of path buffer.
136538032Speter**		createflag -- if set, create intervening directories as
136638032Speter**			needed.
136738032Speter**
136838032Speter**	Returns:
136938032Speter**		0 -- success
137038032Speter**		-1 -- failure
137138032Speter*/
137238032Speter
137364565Sgshapirostatic int
137438032Spetermci_generate_persistent_path(host, path, pathlen, createflag)
137538032Speter	const char *host;
137638032Speter	char *path;
137738032Speter	int pathlen;
137838032Speter	bool createflag;
137938032Speter{
138038032Speter	char *elem, *p, *x, ch;
138138032Speter	int ret = 0;
138238032Speter	int len;
138338032Speter	char t_host[MAXHOSTNAMELEN];
138464565Sgshapiro#if NETINET6
138564565Sgshapiro	struct in6_addr in6_addr;
138664565Sgshapiro#endif /* NETINET6 */
138738032Speter
138838032Speter	/*
138938032Speter	**  Rationality check the arguments.
139038032Speter	*/
139138032Speter
139238032Speter	if (host == NULL)
139338032Speter	{
139438032Speter		syserr("mci_generate_persistent_path: null host");
139538032Speter		return -1;
139638032Speter	}
139738032Speter	if (path == NULL)
139838032Speter	{
139938032Speter		syserr("mci_generate_persistent_path: null path");
140038032Speter		return -1;
140138032Speter	}
140238032Speter
140338032Speter	if (tTd(56, 80))
140490795Sgshapiro		sm_dprintf("mci_generate_persistent_path(%s): ", host);
140538032Speter
140638032Speter	if (*host == '\0' || *host == '.')
140738032Speter		return -1;
140838032Speter
140938032Speter	/* make certain this is not a bracketed host number */
141038032Speter	if (strlen(host) > sizeof t_host - 1)
141138032Speter		return -1;
141238032Speter	if (host[0] == '[')
141390795Sgshapiro		(void) sm_strlcpy(t_host, host + 1, sizeof t_host);
141464565Sgshapiro	else
141590795Sgshapiro		(void) sm_strlcpy(t_host, host, sizeof t_host);
141638032Speter
141738032Speter	/*
141838032Speter	**  Delete any trailing dots from the hostname.
141938032Speter	**  Leave 'elem' pointing at the \0.
142038032Speter	*/
142138032Speter
142238032Speter	elem = t_host + strlen(t_host);
142338032Speter	while (elem > t_host &&
142438032Speter	       (elem[-1] == '.' || (host[0] == '[' && elem[-1] == ']')))
142538032Speter		*--elem = '\0';
142638032Speter
142738032Speter	/* check for bogus bracketed address */
142890795Sgshapiro	if (host[0] == '[')
142990795Sgshapiro	{
143090795Sgshapiro		bool good = false;
143164565Sgshapiro# if NETINET6
143290795Sgshapiro		if (anynet_pton(AF_INET6, t_host, &in6_addr) == 1)
143390795Sgshapiro			good = true;
143464565Sgshapiro# endif /* NETINET6 */
143564565Sgshapiro# if NETINET
143690795Sgshapiro		if (inet_addr(t_host) != INADDR_NONE)
143790795Sgshapiro			good = true;
143864565Sgshapiro# endif /* NETINET */
143990795Sgshapiro		if (!good)
144090795Sgshapiro			return -1;
144190795Sgshapiro	}
144238032Speter
144338032Speter	/* check for what will be the final length of the path */
144438032Speter	len = strlen(HostStatDir) + 2;
144538032Speter	for (p = (char *) t_host; *p != '\0'; p++)
144638032Speter	{
144738032Speter		if (*p == '.')
144838032Speter			len++;
144938032Speter		len++;
145038032Speter		if (p[0] == '.' && p[1] == '.')
145138032Speter			return -1;
145238032Speter	}
145338032Speter	if (len > pathlen || len < 1)
145438032Speter		return -1;
145590795Sgshapiro	(void) sm_strlcpy(path, HostStatDir, pathlen);
145638032Speter	p = path + strlen(path);
145738032Speter	while (elem > t_host)
145838032Speter	{
145938032Speter		if (!path_is_dir(path, createflag))
146038032Speter		{
146138032Speter			ret = -1;
146238032Speter			break;
146338032Speter		}
146438032Speter		elem--;
146538032Speter		while (elem >= t_host && *elem != '.')
146638032Speter			elem--;
146738032Speter		*p++ = '/';
146838032Speter		x = elem + 1;
146938032Speter		while ((ch = *x++) != '\0' && ch != '.')
147038032Speter		{
147138032Speter			if (isascii(ch) && isupper(ch))
147238032Speter				ch = tolower(ch);
147338032Speter			if (ch == '/')
147438032Speter				ch = ':';	/* / -> : */
147538032Speter			*p++ = ch;
147638032Speter		}
147738032Speter		if (elem >= t_host)
147838032Speter			*p++ = '.';
147938032Speter		*p = '\0';
148038032Speter	}
148138032Speter	if (tTd(56, 80))
148238032Speter	{
148338032Speter		if (ret < 0)
148490795Sgshapiro			sm_dprintf("FAILURE %d\n", ret);
148538032Speter		else
148690795Sgshapiro			sm_dprintf("SUCCESS %s\n", path);
148738032Speter	}
148864565Sgshapiro	return ret;
148938032Speter}
1490