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