mci.c revision 71348
1/*
2 * Copyright (c) 1998-2000 Sendmail, Inc. and its suppliers.
3 *	All rights reserved.
4 * Copyright (c) 1995-1997 Eric P. Allman.  All rights reserved.
5 * Copyright (c) 1988, 1993
6 *	The Regents of the University of California.  All rights reserved.
7 *
8 * By using this file, you agree to the terms and conditions set
9 * forth in the LICENSE file which can be found at the top level of
10 * the sendmail distribution.
11 *
12 */
13
14#ifndef lint
15static char id[] = "@(#)$Id: mci.c,v 8.133.10.7 2000/12/12 00:39:34 ca Exp $";
16#endif /* ! lint */
17
18/* $FreeBSD: head/contrib/sendmail/src/mci.c 71348 2001-01-21 22:21:43Z gshapiro $ */
19
20#include <sendmail.h>
21
22
23#if NETINET || NETINET6
24# include <arpa/inet.h>
25#endif /* NETINET || NETINET6 */
26
27#include <dirent.h>
28
29static int	mci_generate_persistent_path __P((const char *, char *,
30						  int, bool));
31static bool	mci_load_persistent __P((MCI *));
32static void	mci_uncache __P((MCI **, bool));
33static int	mci_lock_host_statfile __P((MCI *));
34static int	mci_read_persistent __P((FILE *, MCI *));
35
36/*
37**  Mail Connection Information (MCI) Caching Module.
38**
39**	There are actually two separate things cached.  The first is
40**	the set of all open connections -- these are stored in a
41**	(small) list.  The second is stored in the symbol table; it
42**	has the overall status for all hosts, whether or not there
43**	is a connection open currently.
44**
45**	There should never be too many connections open (since this
46**	could flood the socket table), nor should a connection be
47**	allowed to sit idly for too long.
48**
49**	MaxMciCache is the maximum number of open connections that
50**	will be supported.
51**
52**	MciCacheTimeout is the time (in seconds) that a connection
53**	is permitted to survive without activity.
54**
55**	We actually try any cached connections by sending a NOOP
56**	before we use them; if the NOOP fails we close down the
57**	connection and reopen it.  Note that this means that a
58**	server SMTP that doesn't support NOOP will hose the
59**	algorithm -- but that doesn't seem too likely.
60**
61**	The persistent MCI code is donated by Mark Lovell and Paul
62**	Vixie.  It is based on the long term host status code in KJS
63**	written by Paul but has been adapted by Mark to fit into the
64**	MCI structure.
65*/
66
67static MCI	**MciCache;		/* the open connection cache */
68
69/*
70**  MCI_CACHE -- enter a connection structure into the open connection cache
71**
72**	This may cause something else to be flushed.
73**
74**	Parameters:
75**		mci -- the connection to cache.
76**
77**	Returns:
78**		none.
79*/
80
81void
82mci_cache(mci)
83	register MCI *mci;
84{
85	register MCI **mcislot;
86
87	/*
88	**  Find the best slot.  This may cause expired connections
89	**  to be closed.
90	*/
91
92	mcislot = mci_scan(mci);
93	if (mcislot == NULL)
94	{
95		/* we don't support caching */
96		return;
97	}
98
99	if (mci->mci_host == NULL)
100		return;
101
102	/* if this is already cached, we are done */
103	if (bitset(MCIF_CACHED, mci->mci_flags))
104		return;
105
106	/* otherwise we may have to clear the slot */
107	if (*mcislot != NULL)
108		mci_uncache(mcislot, TRUE);
109
110	if (tTd(42, 5))
111		dprintf("mci_cache: caching %lx (%s) in slot %d\n",
112			(u_long) mci, mci->mci_host,
113			(int)(mcislot - MciCache));
114	if (tTd(91, 100))
115		sm_syslog(LOG_DEBUG, CurEnv->e_id,
116			  "mci_cache: caching %lx (%.100s) in slot %d",
117			  (u_long) mci, mci->mci_host, mcislot - MciCache);
118
119	*mcislot = mci;
120	mci->mci_flags |= MCIF_CACHED;
121}
122/*
123**  MCI_SCAN -- scan the cache, flush junk, and return best slot
124**
125**	Parameters:
126**		savemci -- never flush this one.  Can be null.
127**
128**	Returns:
129**		The LRU (or empty) slot.
130*/
131
132MCI **
133mci_scan(savemci)
134	MCI *savemci;
135{
136	time_t now;
137	register MCI **bestmci;
138	register MCI *mci;
139	register int i;
140
141	if (MaxMciCache <= 0)
142	{
143		/* we don't support caching */
144		return NULL;
145	}
146
147	if (MciCache == NULL)
148	{
149		/* first call */
150		MciCache = (MCI **) xalloc(MaxMciCache * sizeof *MciCache);
151		memset((char *) MciCache, '\0', MaxMciCache * sizeof *MciCache);
152		return &MciCache[0];
153	}
154
155	now = curtime();
156	bestmci = &MciCache[0];
157	for (i = 0; i < MaxMciCache; i++)
158	{
159		mci = MciCache[i];
160		if (mci == NULL || mci->mci_state == MCIS_CLOSED)
161		{
162			bestmci = &MciCache[i];
163			continue;
164		}
165		if ((mci->mci_lastuse + MciCacheTimeout < now ||
166		     (mci->mci_mailer != NULL &&
167		      mci->mci_mailer->m_maxdeliveries > 0 &&
168		      mci->mci_deliveries + 1 >= mci->mci_mailer->m_maxdeliveries))&&
169		    mci != savemci)
170		{
171			/* connection idle too long or too many deliveries */
172			bestmci = &MciCache[i];
173
174			/* close it */
175			mci_uncache(bestmci, TRUE);
176			continue;
177		}
178		if (*bestmci == NULL)
179			continue;
180		if (mci->mci_lastuse < (*bestmci)->mci_lastuse)
181			bestmci = &MciCache[i];
182	}
183	return bestmci;
184}
185/*
186**  MCI_UNCACHE -- remove a connection from a slot.
187**
188**	May close a connection.
189**
190**	Parameters:
191**		mcislot -- the slot to empty.
192**		doquit -- if TRUE, send QUIT protocol on this connection.
193**			  if FALSE, we are assumed to be in a forked child;
194**				all we want to do is close the file(s).
195**
196**	Returns:
197**		none.
198*/
199
200static void
201mci_uncache(mcislot, doquit)
202	register MCI **mcislot;
203	bool doquit;
204{
205	register MCI *mci;
206	extern ENVELOPE BlankEnvelope;
207
208	mci = *mcislot;
209	if (mci == NULL)
210		return;
211	*mcislot = NULL;
212	if (mci->mci_host == NULL)
213		return;
214
215	mci_unlock_host(mci);
216
217	if (tTd(42, 5))
218		dprintf("mci_uncache: uncaching %lx (%s) from slot %d (%d)\n",
219			(u_long) mci, mci->mci_host,
220			(int)(mcislot - MciCache), doquit);
221	if (tTd(91, 100))
222		sm_syslog(LOG_DEBUG, CurEnv->e_id,
223			  "mci_uncache: uncaching %lx (%.100s) from slot %d (%d)",
224			  (u_long) mci, mci->mci_host,
225			  mcislot - MciCache, doquit);
226
227	mci->mci_deliveries = 0;
228#if SMTP
229	if (doquit)
230	{
231		message("Closing connection to %s", mci->mci_host);
232
233		mci->mci_flags &= ~MCIF_CACHED;
234
235		/* only uses the envelope to flush the transcript file */
236		if (mci->mci_state != MCIS_CLOSED)
237			smtpquit(mci->mci_mailer, mci, &BlankEnvelope);
238# ifdef XLA
239		xla_host_end(mci->mci_host);
240# endif /* XLA */
241	}
242	else
243#endif /* SMTP */
244	{
245		if (mci->mci_in != NULL)
246			(void) fclose(mci->mci_in);
247		if (mci->mci_out != NULL)
248			(void) fclose(mci->mci_out);
249		mci->mci_in = mci->mci_out = NULL;
250		mci->mci_state = MCIS_CLOSED;
251		mci->mci_exitstat = EX_OK;
252		mci->mci_errno = 0;
253		mci->mci_flags = 0;
254	}
255}
256/*
257**  MCI_FLUSH -- flush the entire cache
258**
259**	Parameters:
260**		doquit -- if TRUE, send QUIT protocol.
261**			  if FALSE, just close the connection.
262**		allbut -- but leave this one open.
263**
264**	Returns:
265**		none.
266*/
267
268void
269mci_flush(doquit, allbut)
270	bool doquit;
271	MCI *allbut;
272{
273	register int i;
274
275	if (MciCache == NULL)
276		return;
277
278	for (i = 0; i < MaxMciCache; i++)
279	{
280		if (allbut != MciCache[i])
281			mci_uncache(&MciCache[i], doquit);
282	}
283}
284/*
285**  MCI_GET -- get information about a particular host
286*/
287
288MCI *
289mci_get(host, m)
290	char *host;
291	MAILER *m;
292{
293	register MCI *mci;
294	register STAB *s;
295
296#if DAEMON
297	extern SOCKADDR CurHostAddr;
298
299	/* clear CurHostAddr so we don't get a bogus address with this name */
300	memset(&CurHostAddr, '\0', sizeof CurHostAddr);
301#endif /* DAEMON */
302
303	/* clear out any expired connections */
304	(void) mci_scan(NULL);
305
306	if (m->m_mno < 0)
307		syserr("!negative mno %d (%s)", m->m_mno, m->m_name);
308
309	s = stab(host, ST_MCI + m->m_mno, ST_ENTER);
310	mci = &s->s_mci;
311
312	/*
313	**  We don't need to load the peristent data if we have data
314	**  already loaded in the cache.
315	*/
316
317	if (mci->mci_host == NULL &&
318	    (mci->mci_host = s->s_name) != NULL &&
319	    !mci_load_persistent(mci))
320	{
321		if (tTd(42, 2))
322			dprintf("mci_get(%s %s): lock failed\n",
323				host, m->m_name);
324		mci->mci_exitstat = EX_TEMPFAIL;
325		mci->mci_state = MCIS_CLOSED;
326		mci->mci_statfile = NULL;
327		return mci;
328	}
329
330	if (tTd(42, 2))
331	{
332		dprintf("mci_get(%s %s): mci_state=%d, _flags=%lx, _exitstat=%d, _errno=%d\n",
333			host, m->m_name, mci->mci_state, mci->mci_flags,
334			mci->mci_exitstat, mci->mci_errno);
335	}
336
337#if SMTP
338	if (mci->mci_state == MCIS_OPEN)
339	{
340		/* poke the connection to see if it's still alive */
341		(void) smtpprobe(mci);
342
343		/* reset the stored state in the event of a timeout */
344		if (mci->mci_state != MCIS_OPEN)
345		{
346			mci->mci_errno = 0;
347			mci->mci_exitstat = EX_OK;
348			mci->mci_state = MCIS_CLOSED;
349		}
350# if DAEMON
351		else
352		{
353			/* get peer host address for logging reasons only */
354			/* (this should really be in the mci struct) */
355			SOCKADDR_LEN_T socklen = sizeof CurHostAddr;
356
357			(void) getpeername(fileno(mci->mci_in),
358				(struct sockaddr *) &CurHostAddr, &socklen);
359		}
360# endif /* DAEMON */
361	}
362#endif /* SMTP */
363	if (mci->mci_state == MCIS_CLOSED)
364	{
365		time_t now = curtime();
366
367		/* if this info is stale, ignore it */
368		if (now > mci->mci_lastuse + MciInfoTimeout)
369		{
370			mci->mci_lastuse = now;
371			mci->mci_errno = 0;
372			mci->mci_exitstat = EX_OK;
373		}
374	}
375
376	return mci;
377}
378/*
379**  MCI_MATCH -- check connection cache for a particular host
380*/
381
382bool
383mci_match(host, m)
384	char *host;
385	MAILER *m;
386{
387	register MCI *mci;
388	register STAB *s;
389
390	if (m->m_mno < 0 || m->m_mno > MAXMAILERS)
391		return FALSE;
392	s = stab(host, ST_MCI + m->m_mno, ST_FIND);
393	if (s == NULL)
394		return FALSE;
395
396	mci = &s->s_mci;
397	if (mci->mci_state == MCIS_OPEN)
398		return TRUE;
399	return FALSE;
400}
401/*
402**  MCI_SETSTAT -- set status codes in MCI structure.
403**
404**	Parameters:
405**		mci -- the MCI structure to set.
406**		xstat -- the exit status code.
407**		dstat -- the DSN status code.
408**		rstat -- the SMTP status code.
409**
410**	Returns:
411**		none.
412*/
413
414void
415mci_setstat(mci, xstat, dstat, rstat)
416	MCI *mci;
417	int xstat;
418	char *dstat;
419	char *rstat;
420{
421	/* protocol errors should never be interpreted as sticky */
422	if (xstat != EX_NOTSTICKY && xstat != EX_PROTOCOL)
423		mci->mci_exitstat = xstat;
424
425	mci->mci_status = dstat;
426	if (mci->mci_rstatus != NULL)
427		free(mci->mci_rstatus);
428	if (rstat != NULL)
429		rstat = newstr(rstat);
430	mci->mci_rstatus = rstat;
431}
432/*
433**  MCI_DUMP -- dump the contents of an MCI structure.
434**
435**	Parameters:
436**		mci -- the MCI structure to dump.
437**
438**	Returns:
439**		none.
440**
441**	Side Effects:
442**		none.
443*/
444
445struct mcifbits
446{
447	int	mcif_bit;	/* flag bit */
448	char	*mcif_name;	/* flag name */
449};
450static struct mcifbits	MciFlags[] =
451{
452	{ MCIF_VALID,		"VALID"		},
453	{ MCIF_TEMP,		"TEMP"		},
454	{ MCIF_CACHED,		"CACHED"	},
455	{ MCIF_ESMTP,		"ESMTP"		},
456	{ MCIF_EXPN,		"EXPN"		},
457	{ MCIF_SIZE,		"SIZE"		},
458	{ MCIF_8BITMIME,	"8BITMIME"	},
459	{ MCIF_7BIT,		"7BIT"		},
460	{ MCIF_MULTSTAT,	"MULTSTAT"	},
461	{ MCIF_INHEADER,	"INHEADER"	},
462	{ MCIF_CVT8TO7,		"CVT8TO7"	},
463	{ MCIF_DSN,		"DSN"		},
464	{ MCIF_8BITOK,		"8BITOK"	},
465	{ MCIF_CVT7TO8,		"CVT7TO8"	},
466	{ MCIF_INMIME,		"INMIME"	},
467	{ 0,			NULL		}
468};
469
470
471void
472mci_dump(mci, logit)
473	register MCI *mci;
474	bool logit;
475{
476	register char *p;
477	char *sep;
478	char buf[4000];
479
480	sep = logit ? " " : "\n\t";
481	p = buf;
482	snprintf(p, SPACELEFT(buf, p), "MCI@%lx: ",
483		sizeof(void *) == sizeof(u_long) ?
484		(u_long)(void *)mci : (u_long)(u_int)(void *)mci);
485	p += strlen(p);
486	if (mci == NULL)
487	{
488		snprintf(p, SPACELEFT(buf, p), "NULL");
489		goto printit;
490	}
491	snprintf(p, SPACELEFT(buf, p), "flags=%lx", mci->mci_flags);
492	p += strlen(p);
493	if (mci->mci_flags != 0)
494	{
495		struct mcifbits *f;
496
497		*p++ = '<';
498		for (f = MciFlags; f->mcif_bit != 0; f++)
499		{
500			if (!bitset(f->mcif_bit, mci->mci_flags))
501				continue;
502			snprintf(p, SPACELEFT(buf, p), "%s,", f->mcif_name);
503			p += strlen(p);
504		}
505		p[-1] = '>';
506	}
507	snprintf(p, SPACELEFT(buf, p),
508		",%serrno=%d, herrno=%d, exitstat=%d, state=%d, pid=%d,%s",
509		sep, mci->mci_errno, mci->mci_herrno,
510		mci->mci_exitstat, mci->mci_state, (int) mci->mci_pid, sep);
511	p += strlen(p);
512	snprintf(p, SPACELEFT(buf, p),
513		"maxsize=%ld, phase=%s, mailer=%s,%s",
514		mci->mci_maxsize,
515		mci->mci_phase == NULL ? "NULL" : mci->mci_phase,
516		mci->mci_mailer == NULL ? "NULL" : mci->mci_mailer->m_name,
517		sep);
518	p += strlen(p);
519	snprintf(p, SPACELEFT(buf, p),
520		"status=%s, rstatus=%s,%s",
521		mci->mci_status == NULL ? "NULL" : mci->mci_status,
522		mci->mci_rstatus == NULL ? "NULL" : mci->mci_rstatus,
523		sep);
524	p += strlen(p);
525	snprintf(p, SPACELEFT(buf, p),
526		"host=%s, lastuse=%s",
527		mci->mci_host == NULL ? "NULL" : mci->mci_host,
528		ctime(&mci->mci_lastuse));
529printit:
530	if (logit)
531		sm_syslog(LOG_DEBUG, CurEnv->e_id, "%.1000s", buf);
532	else
533		printf("%s\n", buf);
534}
535/*
536**  MCI_DUMP_ALL -- print the entire MCI cache
537**
538**	Parameters:
539**		logit -- if set, log the result instead of printing
540**			to stdout.
541**
542**	Returns:
543**		none.
544*/
545
546void
547mci_dump_all(logit)
548	bool logit;
549{
550	register int i;
551
552	if (MciCache == NULL)
553		return;
554
555	for (i = 0; i < MaxMciCache; i++)
556		mci_dump(MciCache[i], logit);
557}
558/*
559**  MCI_LOCK_HOST -- Lock host while sending.
560**
561**	If we are contacting a host, we'll need to
562**	update the status information in the host status
563**	file, and if we want to do that, we ought to have
564**	locked it. This has the (according to some)
565**	desirable effect of serializing connectivity with
566**	remote hosts -- i.e.: one connection to a give
567**	host at a time.
568**
569**	Parameters:
570**		mci -- containing the host we want to lock.
571**
572**	Returns:
573**		EX_OK	    -- got the lock.
574**		EX_TEMPFAIL -- didn't get the lock.
575*/
576
577int
578mci_lock_host(mci)
579	MCI *mci;
580{
581	if (mci == NULL)
582	{
583		if (tTd(56, 1))
584			dprintf("mci_lock_host: NULL mci\n");
585		return EX_OK;
586	}
587
588	if (!SingleThreadDelivery)
589		return EX_OK;
590
591	return mci_lock_host_statfile(mci);
592}
593
594static int
595mci_lock_host_statfile(mci)
596	MCI *mci;
597{
598	int save_errno = errno;
599	int retVal = EX_OK;
600	char fname[MAXPATHLEN + 1];
601
602	if (HostStatDir == NULL || mci->mci_host == NULL)
603		return EX_OK;
604
605	if (tTd(56, 2))
606		dprintf("mci_lock_host: attempting to lock %s\n",
607			mci->mci_host);
608
609	if (mci_generate_persistent_path(mci->mci_host, fname, sizeof fname, TRUE) < 0)
610	{
611		/* of course this should never happen */
612		if (tTd(56, 2))
613			dprintf("mci_lock_host: Failed to generate host path for %s\n",
614				mci->mci_host);
615
616		retVal = EX_TEMPFAIL;
617		goto cleanup;
618	}
619
620	mci->mci_statfile = safefopen(fname, O_RDWR, FileMode,
621				      SFF_NOLOCK|SFF_NOLINK|SFF_OPENASROOT|SFF_REGONLY|SFF_SAFEDIRPATH|SFF_CREAT);
622
623	if (mci->mci_statfile == NULL)
624	{
625		syserr("mci_lock_host: cannot create host lock file %s",
626			       fname);
627		goto cleanup;
628	}
629
630	if (!lockfile(fileno(mci->mci_statfile), fname, "", LOCK_EX|LOCK_NB))
631	{
632		if (tTd(56, 2))
633			dprintf("mci_lock_host: couldn't get lock on %s\n",
634				fname);
635		(void) fclose(mci->mci_statfile);
636		mci->mci_statfile = NULL;
637		retVal = EX_TEMPFAIL;
638		goto cleanup;
639	}
640
641	if (tTd(56, 12) && mci->mci_statfile != NULL)
642		dprintf("mci_lock_host: Sanity check -- lock is good\n");
643
644cleanup:
645	errno = save_errno;
646	return retVal;
647}
648/*
649**  MCI_UNLOCK_HOST -- unlock host
650**
651**	Clean up the lock on a host, close the file, let
652**	someone else use it.
653**
654**	Parameters:
655**		mci -- us.
656**
657**	Returns:
658**		nothing.
659*/
660
661void
662mci_unlock_host(mci)
663	MCI *mci;
664{
665	int save_errno = errno;
666
667	if (mci == NULL)
668	{
669		if (tTd(56, 1))
670			dprintf("mci_unlock_host: NULL mci\n");
671		return;
672	}
673
674	if (HostStatDir == NULL || mci->mci_host == NULL)
675		return;
676
677	if (!SingleThreadDelivery && mci_lock_host_statfile(mci) == EX_TEMPFAIL)
678	{
679		if (tTd(56, 1))
680			dprintf("mci_unlock_host: stat file already locked\n");
681	}
682	else
683	{
684		if (tTd(56, 2))
685			dprintf("mci_unlock_host: store prior to unlock\n");
686
687		mci_store_persistent(mci);
688	}
689
690	if (mci->mci_statfile != NULL)
691	{
692		(void) fclose(mci->mci_statfile);
693		mci->mci_statfile = NULL;
694	}
695
696	errno = save_errno;
697}
698/*
699**  MCI_LOAD_PERSISTENT -- load persistent host info
700**
701**	Load information about host that is kept
702**	in common for all running sendmails.
703**
704**	Parameters:
705**		mci -- the host/connection to load persistent info
706**			   for.
707**
708**	Returns:
709**		TRUE -- lock was successful
710**		FALSE -- lock failed
711*/
712
713static bool
714mci_load_persistent(mci)
715	MCI *mci;
716{
717	int save_errno = errno;
718	bool locked = TRUE;
719	FILE *fp;
720	char fname[MAXPATHLEN + 1];
721
722	if (mci == NULL)
723	{
724		if (tTd(56, 1))
725			dprintf("mci_load_persistent: NULL mci\n");
726		return TRUE;
727	}
728
729	if (IgnoreHostStatus || HostStatDir == NULL || mci->mci_host == NULL)
730		return TRUE;
731
732	/* Already have the persistent information in memory */
733	if (SingleThreadDelivery && mci->mci_statfile != NULL)
734		return TRUE;
735
736	if (tTd(56, 1))
737		dprintf("mci_load_persistent: Attempting to load persistent information for %s\n",
738			mci->mci_host);
739
740	if (mci_generate_persistent_path(mci->mci_host, fname, sizeof fname, FALSE) < 0)
741	{
742		/* Not much we can do if the file isn't there... */
743		if (tTd(56, 1))
744			dprintf("mci_load_persistent: Couldn't generate host path\n");
745		goto cleanup;
746	}
747
748	fp = safefopen(fname, O_RDONLY, FileMode,
749		       SFF_NOLOCK|SFF_NOLINK|SFF_OPENASROOT|SFF_REGONLY|SFF_SAFEDIRPATH);
750	if (fp == NULL)
751	{
752		/* I can't think of any reason this should ever happen */
753		if (tTd(56, 1))
754			dprintf("mci_load_persistent: open(%s): %s\n",
755				fname, errstring(errno));
756		goto cleanup;
757	}
758
759	FileName = fname;
760	locked = lockfile(fileno(fp), fname, "", LOCK_SH|LOCK_NB);
761	if (locked)
762	{
763		(void) mci_read_persistent(fp, mci);
764		(void) lockfile(fileno(fp), fname, "", LOCK_UN);
765	}
766	FileName = NULL;
767	(void) fclose(fp);
768
769cleanup:
770	errno = save_errno;
771	return locked;
772}
773/*
774**  MCI_READ_PERSISTENT -- read persistent host status file
775**
776**	Parameters:
777**		fp -- the file pointer to read.
778**		mci -- the pointer to fill in.
779**
780**	Returns:
781**		-1 -- if the file was corrupt.
782**		0 -- otherwise.
783**
784**	Warning:
785**		This code makes the assumption that this data
786**		will be read in an atomic fashion, and that the data
787**		was written in an atomic fashion.  Any other functioning
788**		may lead to some form of insanity.  This should be
789**		perfectly safe due to underlying stdio buffering.
790*/
791
792static int
793mci_read_persistent(fp, mci)
794	FILE *fp;
795	register MCI *mci;
796{
797	int ver;
798	register char *p;
799	int saveLineNumber = LineNumber;
800	char buf[MAXLINE];
801
802	if (fp == NULL)
803		syserr("mci_read_persistent: NULL fp");
804	if (mci == NULL)
805		syserr("mci_read_persistent: NULL mci");
806	if (tTd(56, 93))
807	{
808		dprintf("mci_read_persistent: fp=%lx, mci=", (u_long) fp);
809		mci_dump(mci, FALSE);
810	}
811
812	mci->mci_status = NULL;
813	if (mci->mci_rstatus != NULL)
814		free(mci->mci_rstatus);
815	mci->mci_rstatus = NULL;
816
817	rewind(fp);
818	ver = -1;
819	LineNumber = 0;
820	while (fgets(buf, sizeof buf, fp) != NULL)
821	{
822		LineNumber++;
823		p = strchr(buf, '\n');
824		if (p != NULL)
825			*p = '\0';
826		switch (buf[0])
827		{
828		  case 'V':		/* version stamp */
829			ver = atoi(&buf[1]);
830			if (ver < 0 || ver > 0)
831				syserr("Unknown host status version %d: %d max",
832					ver, 0);
833			break;
834
835		  case 'E':		/* UNIX error number */
836			mci->mci_errno = atoi(&buf[1]);
837			break;
838
839		  case 'H':		/* DNS error number */
840			mci->mci_herrno = atoi(&buf[1]);
841			break;
842
843		  case 'S':		/* UNIX exit status */
844			mci->mci_exitstat = atoi(&buf[1]);
845			break;
846
847		  case 'D':		/* DSN status */
848			mci->mci_status = newstr(&buf[1]);
849			break;
850
851		  case 'R':		/* SMTP status */
852			mci->mci_rstatus = newstr(&buf[1]);
853			break;
854
855		  case 'U':		/* last usage time */
856			mci->mci_lastuse = atol(&buf[1]);
857			break;
858
859		  case '.':		/* end of file */
860			return 0;
861
862		  default:
863			sm_syslog(LOG_CRIT, NOQID,
864				  "%s: line %d: Unknown host status line \"%s\"",
865				  FileName == NULL ? mci->mci_host : FileName,
866				  LineNumber, buf);
867			LineNumber = saveLineNumber;
868			return -1;
869		}
870	}
871	LineNumber = saveLineNumber;
872	if (ver < 0)
873		return -1;
874	return 0;
875}
876/*
877**  MCI_STORE_PERSISTENT -- Store persistent MCI information
878**
879**	Store information about host that is kept
880**	in common for all running sendmails.
881**
882**	Parameters:
883**		mci -- the host/connection to store persistent info for.
884**
885**	Returns:
886**		none.
887*/
888
889void
890mci_store_persistent(mci)
891	MCI *mci;
892{
893	int save_errno = errno;
894
895	if (mci == NULL)
896	{
897		if (tTd(56, 1))
898			dprintf("mci_store_persistent: NULL mci\n");
899		return;
900	}
901
902	if (HostStatDir == NULL || mci->mci_host == NULL)
903		return;
904
905	if (tTd(56, 1))
906		dprintf("mci_store_persistent: Storing information for %s\n",
907			mci->mci_host);
908
909	if (mci->mci_statfile == NULL)
910	{
911		if (tTd(56, 1))
912			dprintf("mci_store_persistent: no statfile\n");
913		return;
914	}
915
916	rewind(mci->mci_statfile);
917#if !NOFTRUNCATE
918	(void) ftruncate(fileno(mci->mci_statfile), (off_t) 0);
919#endif /* !NOFTRUNCATE */
920
921	fprintf(mci->mci_statfile, "V0\n");
922	fprintf(mci->mci_statfile, "E%d\n", mci->mci_errno);
923	fprintf(mci->mci_statfile, "H%d\n", mci->mci_herrno);
924	fprintf(mci->mci_statfile, "S%d\n", mci->mci_exitstat);
925	if (mci->mci_status != NULL)
926		fprintf(mci->mci_statfile, "D%.80s\n",
927			denlstring(mci->mci_status, TRUE, FALSE));
928	if (mci->mci_rstatus != NULL)
929		fprintf(mci->mci_statfile, "R%.80s\n",
930			denlstring(mci->mci_rstatus, TRUE, FALSE));
931	fprintf(mci->mci_statfile, "U%ld\n", (long)(mci->mci_lastuse));
932	fprintf(mci->mci_statfile, ".\n");
933
934	(void) fflush(mci->mci_statfile);
935
936	errno = save_errno;
937	return;
938}
939/*
940**  MCI_TRAVERSE_PERSISTENT -- walk persistent status tree
941**
942**	Recursively find all the mci host files in `pathname'.  Default to
943**		main host status directory if no path is provided.
944**	Call (*action)(pathname, host) for each file found.
945**
946**	Note: all information is collected in a list before it is processed.
947**	This may not be the best way to do it, but it seems safest, since
948**	the file system would be touched while we are attempting to traverse
949**	the directory tree otherwise (during purges).
950**
951**	Parameters:
952**		action -- function to call on each node.  If returns < 0,
953**			return immediately.
954**		pathname -- root of tree.  If null, use main host status
955**			directory.
956**
957**	Returns:
958**		< 0 -- if any action routine returns a negative value, that
959**			value is returned.
960**		0 -- if we successfully went to completion.
961**		> 0 -- return status from action()
962*/
963
964int
965mci_traverse_persistent(action, pathname)
966	int (*action)();
967	char *pathname;
968{
969	struct stat statbuf;
970	DIR *d;
971	int ret;
972
973	if (pathname == NULL)
974		pathname = HostStatDir;
975	if (pathname == NULL)
976		return -1;
977
978	if (tTd(56, 1))
979		dprintf("mci_traverse: pathname is %s\n", pathname);
980
981	ret = stat(pathname, &statbuf);
982	if (ret < 0)
983	{
984		if (tTd(56, 2))
985			dprintf("mci_traverse: Failed to stat %s: %s\n",
986				pathname, errstring(errno));
987		return ret;
988	}
989	if (S_ISDIR(statbuf.st_mode))
990	{
991		struct dirent *e;
992		char *newptr;
993		char newpath[MAXPATHLEN + 1];
994		bool leftone, removedone;
995
996		if ((d = opendir(pathname)) == NULL)
997		{
998			if (tTd(56, 2))
999				dprintf("mci_traverse: opendir %s: %s\n",
1000					pathname, errstring(errno));
1001			return -1;
1002		}
1003
1004		if (strlen(pathname) >= sizeof newpath - MAXNAMLEN - 3)
1005		{
1006			if (tTd(56, 2))
1007				dprintf("mci_traverse: path \"%s\" too long",
1008					pathname);
1009			return -1;
1010		}
1011		(void) strlcpy(newpath, pathname, sizeof newpath);
1012		newptr = newpath + strlen(newpath);
1013		*newptr++ = '/';
1014
1015		/*
1016		**  repeat until no file has been removed
1017		**  this may become ugly when several files "expire"
1018		**  during these loops, but it's better than doing
1019		**  a rewinddir() inside the inner loop
1020		*/
1021		do
1022		{
1023			leftone = removedone = FALSE;
1024			while ((e = readdir(d)) != NULL)
1025			{
1026				if (e->d_name[0] == '.')
1027					continue;
1028
1029				(void) strlcpy(newptr, e->d_name,
1030					       sizeof newpath -
1031					       (newptr - newpath));
1032
1033				ret = mci_traverse_persistent(action, newpath);
1034				if (ret < 0)
1035					break;
1036				if (ret == 1)
1037					leftone = TRUE;
1038				if (!removedone && ret == 0 &&
1039				    action == mci_purge_persistent)
1040					removedone = TRUE;
1041			}
1042			if (ret < 0)
1043				break;
1044			/*
1045			**  The following appears to be
1046			**  necessary during purges, since
1047			**  we modify the directory structure
1048			*/
1049			if (removedone)
1050				rewinddir(d);
1051			if (tTd(56, 40))
1052				dprintf("mci_traverse: path %s: ret %d removed %d left %d\n",
1053					pathname, ret, removedone, leftone);
1054		} while (removedone);
1055
1056		/* purge (or whatever) the directory proper */
1057		if (!leftone)
1058		{
1059			*--newptr = '\0';
1060			ret = (*action)(newpath, NULL);
1061		}
1062		(void) closedir(d);
1063	}
1064	else if (S_ISREG(statbuf.st_mode))
1065	{
1066		char *end = pathname + strlen(pathname) - 1;
1067		char *start;
1068		char *scan;
1069		char host[MAXHOSTNAMELEN];
1070		char *hostptr = host;
1071
1072		/*
1073		**  Reconstruct the host name from the path to the
1074		**  persistent information.
1075		*/
1076
1077		do
1078		{
1079			if (hostptr != host)
1080				*(hostptr++) = '.';
1081			start = end;
1082			while (*(start - 1) != '/')
1083				start--;
1084
1085			if (*end == '.')
1086				end--;
1087
1088			for (scan = start; scan <= end; scan++)
1089				*(hostptr++) = *scan;
1090
1091			end = start - 2;
1092		} while (*end == '.');
1093
1094		*hostptr = '\0';
1095
1096		/*
1097		**  Do something with the file containing the persistent
1098		**  information.
1099		*/
1100		ret = (*action)(pathname, host);
1101	}
1102
1103	return ret;
1104}
1105/*
1106**  MCI_PRINT_PERSISTENT -- print persistent info
1107**
1108**	Dump the persistent information in the file 'pathname'
1109**
1110**	Parameters:
1111**		pathname -- the pathname to the status file.
1112**		hostname -- the corresponding host name.
1113**
1114**	Returns:
1115**		0
1116*/
1117
1118int
1119mci_print_persistent(pathname, hostname)
1120	char *pathname;
1121	char *hostname;
1122{
1123	static int initflag = FALSE;
1124	FILE *fp;
1125	int width = Verbose ? 78 : 25;
1126	bool locked;
1127	MCI mcib;
1128
1129	/* skip directories */
1130	if (hostname == NULL)
1131		return 0;
1132
1133	if (!initflag)
1134	{
1135		initflag = TRUE;
1136		printf(" -------------- Hostname --------------- How long ago ---------Results---------\n");
1137	}
1138
1139	fp = safefopen(pathname, O_RDWR, FileMode,
1140		       SFF_NOLOCK|SFF_NOLINK|SFF_OPENASROOT|SFF_REGONLY|SFF_SAFEDIRPATH);
1141
1142	if (fp == NULL)
1143	{
1144		if (tTd(56, 1))
1145			dprintf("mci_print_persistent: cannot open %s: %s\n",
1146				pathname, errstring(errno));
1147		return 0;
1148	}
1149
1150	FileName = pathname;
1151	memset(&mcib, '\0', sizeof mcib);
1152	if (mci_read_persistent(fp, &mcib) < 0)
1153	{
1154		syserr("%s: could not read status file", pathname);
1155		(void) fclose(fp);
1156		FileName = NULL;
1157		return 0;
1158	}
1159
1160	locked = !lockfile(fileno(fp), pathname, "", LOCK_EX|LOCK_NB);
1161	(void) fclose(fp);
1162	FileName = NULL;
1163
1164	printf("%c%-39s %12s ",
1165		locked ? '*' : ' ', hostname,
1166		pintvl(curtime() - mcib.mci_lastuse, TRUE));
1167	if (mcib.mci_rstatus != NULL)
1168		printf("%.*s\n", width, mcib.mci_rstatus);
1169	else if (mcib.mci_exitstat == EX_TEMPFAIL && mcib.mci_errno != 0)
1170		printf("Deferred: %.*s\n", width - 10, errstring(mcib.mci_errno));
1171	else if (mcib.mci_exitstat != 0)
1172	{
1173		int i = mcib.mci_exitstat - EX__BASE;
1174		extern int N_SysEx;
1175		extern char *SysExMsg[];
1176
1177		if (i < 0 || i >= N_SysEx)
1178		{
1179			char buf[80];
1180
1181			snprintf(buf, sizeof buf, "Unknown mailer error %d",
1182				mcib.mci_exitstat);
1183			printf("%.*s\n", width, buf);
1184		}
1185		else
1186			printf("%.*s\n", width, &(SysExMsg[i])[5]);
1187	}
1188	else if (mcib.mci_errno == 0)
1189		printf("OK\n");
1190	else
1191		printf("OK: %.*s\n", width - 4, errstring(mcib.mci_errno));
1192
1193	return 0;
1194}
1195/*
1196**  MCI_PURGE_PERSISTENT -- Remove a persistence status file.
1197**
1198**	Parameters:
1199**		pathname -- path to the status file.
1200**		hostname -- name of host corresponding to that file.
1201**			NULL if this is a directory (domain).
1202**
1203**	Returns:
1204**		0 -- ok
1205**		1 -- file not deleted (too young, incorrect format)
1206**		< 0 -- some error occurred
1207*/
1208
1209int
1210mci_purge_persistent(pathname, hostname)
1211	char *pathname;
1212	char *hostname;
1213{
1214	struct stat statbuf;
1215	char *end = pathname + strlen(pathname) - 1;
1216	int ret;
1217
1218	if (tTd(56, 1))
1219		dprintf("mci_purge_persistent: purging %s\n", pathname);
1220
1221	ret = stat(pathname, &statbuf);
1222	if (ret < 0)
1223	{
1224		if (tTd(56, 2))
1225			dprintf("mci_purge_persistent: Failed to stat %s: %s\n",
1226				pathname, errstring(errno));
1227		return ret;
1228	}
1229	if (curtime() - statbuf.st_mtime < MciInfoTimeout)
1230		return 1;
1231	if (hostname != NULL)
1232	{
1233		/* remove the file */
1234		if (unlink(pathname) < 0)
1235		{
1236			if (tTd(56, 2))
1237				dprintf("mci_purge_persistent: failed to unlink %s: %s\n",
1238					pathname, errstring(errno));
1239		}
1240	}
1241	else
1242	{
1243		/* remove the directory */
1244		if (*end != '.')
1245			return 1;
1246
1247		if (tTd(56, 1))
1248			dprintf("mci_purge_persistent: dpurge %s\n", pathname);
1249
1250		if (rmdir(pathname) < 0)
1251		{
1252			if (tTd(56, 2))
1253				dprintf("mci_purge_persistent: rmdir %s: %s\n",
1254					pathname, errstring(errno));
1255		}
1256
1257	}
1258
1259	return 0;
1260}
1261/*
1262**  MCI_GENERATE_PERSISTENT_PATH -- generate path from hostname
1263**
1264**	Given `host', convert from a.b.c to $QueueDir/.hoststat/c./b./a,
1265**	putting the result into `path'.  if `createflag' is set, intervening
1266**	directories will be created as needed.
1267**
1268**	Parameters:
1269**		host -- host name to convert from.
1270**		path -- place to store result.
1271**		pathlen -- length of path buffer.
1272**		createflag -- if set, create intervening directories as
1273**			needed.
1274**
1275**	Returns:
1276**		0 -- success
1277**		-1 -- failure
1278*/
1279
1280static int
1281mci_generate_persistent_path(host, path, pathlen, createflag)
1282	const char *host;
1283	char *path;
1284	int pathlen;
1285	bool createflag;
1286{
1287	char *elem, *p, *x, ch;
1288	int ret = 0;
1289	int len;
1290	char t_host[MAXHOSTNAMELEN];
1291#if NETINET6
1292	struct in6_addr in6_addr;
1293#endif /* NETINET6 */
1294
1295	/*
1296	**  Rationality check the arguments.
1297	*/
1298
1299	if (host == NULL)
1300	{
1301		syserr("mci_generate_persistent_path: null host");
1302		return -1;
1303	}
1304	if (path == NULL)
1305	{
1306		syserr("mci_generate_persistent_path: null path");
1307		return -1;
1308	}
1309
1310	if (tTd(56, 80))
1311		dprintf("mci_generate_persistent_path(%s): ", host);
1312
1313	if (*host == '\0' || *host == '.')
1314		return -1;
1315
1316	/* make certain this is not a bracketed host number */
1317	if (strlen(host) > sizeof t_host - 1)
1318		return -1;
1319	if (host[0] == '[')
1320		(void) strlcpy(t_host, host + 1, sizeof t_host);
1321	else
1322		(void) strlcpy(t_host, host, sizeof t_host);
1323
1324	/*
1325	**  Delete any trailing dots from the hostname.
1326	**  Leave 'elem' pointing at the \0.
1327	*/
1328
1329	elem = t_host + strlen(t_host);
1330	while (elem > t_host &&
1331	       (elem[-1] == '.' || (host[0] == '[' && elem[-1] == ']')))
1332		*--elem = '\0';
1333
1334#if NETINET || NETINET6
1335	/* check for bogus bracketed address */
1336	if (host[0] == '['
1337# if NETINET6
1338	    && inet_pton(AF_INET6, t_host, &in6_addr) != 1
1339# endif /* NETINET6 */
1340# if NETINET
1341	    && inet_addr(t_host) == INADDR_NONE
1342# endif /* NETINET */
1343	    )
1344		return -1;
1345#endif /* NETINET || NETINET6 */
1346
1347	/* check for what will be the final length of the path */
1348	len = strlen(HostStatDir) + 2;
1349	for (p = (char *) t_host; *p != '\0'; p++)
1350	{
1351		if (*p == '.')
1352			len++;
1353		len++;
1354		if (p[0] == '.' && p[1] == '.')
1355			return -1;
1356	}
1357	if (len > pathlen || len < 1)
1358		return -1;
1359
1360	(void) strlcpy(path, HostStatDir, pathlen);
1361	p = path + strlen(path);
1362
1363	while (elem > t_host)
1364	{
1365		if (!path_is_dir(path, createflag))
1366		{
1367			ret = -1;
1368			break;
1369		}
1370		elem--;
1371		while (elem >= t_host && *elem != '.')
1372			elem--;
1373		*p++ = '/';
1374		x = elem + 1;
1375		while ((ch = *x++) != '\0' && ch != '.')
1376		{
1377			if (isascii(ch) && isupper(ch))
1378				ch = tolower(ch);
1379			if (ch == '/')
1380				ch = ':';	/* / -> : */
1381			*p++ = ch;
1382		}
1383		if (elem >= t_host)
1384			*p++ = '.';
1385		*p = '\0';
1386	}
1387
1388	if (tTd(56, 80))
1389	{
1390		if (ret < 0)
1391			dprintf("FAILURE %d\n", ret);
1392		else
1393			dprintf("SUCCESS %s\n", path);
1394	}
1395
1396	return ret;
1397}
1398