diff options
Diffstat (limited to 'sendmail/src/mci.c')
-rw-r--r-- | sendmail/src/mci.c | 1561 |
1 files changed, 1561 insertions, 0 deletions
diff --git a/sendmail/src/mci.c b/sendmail/src/mci.c new file mode 100644 index 0000000..ae33f66 --- /dev/null +++ b/sendmail/src/mci.c @@ -0,0 +1,1561 @@ +/* + * Copyright (c) 1998-2005 Sendmail, Inc. and its suppliers. + * All rights reserved. + * Copyright (c) 1995-1997 Eric P. Allman. All rights reserved. + * Copyright (c) 1988, 1993 + * The Regents of the University of California. All rights reserved. + * + * By using this file, you agree to the terms and conditions set + * forth in the LICENSE file which can be found at the top level of + * the sendmail distribution. + * + */ + +#include <sendmail.h> + +SM_RCSID("@(#)$Id: mci.c,v 8.218 2006/08/15 23:24:57 ca Exp $") + +#if NETINET || NETINET6 +# include <arpa/inet.h> +#endif /* NETINET || NETINET6 */ + +#include <dirent.h> + +static int mci_generate_persistent_path __P((const char *, char *, + int, bool)); +static bool mci_load_persistent __P((MCI *)); +static void mci_uncache __P((MCI **, bool)); +static int mci_lock_host_statfile __P((MCI *)); +static int mci_read_persistent __P((SM_FILE_T *, MCI *)); + +/* +** Mail Connection Information (MCI) Caching Module. +** +** There are actually two separate things cached. The first is +** the set of all open connections -- these are stored in a +** (small) list. The second is stored in the symbol table; it +** has the overall status for all hosts, whether or not there +** is a connection open currently. +** +** There should never be too many connections open (since this +** could flood the socket table), nor should a connection be +** allowed to sit idly for too long. +** +** MaxMciCache is the maximum number of open connections that +** will be supported. +** +** MciCacheTimeout is the time (in seconds) that a connection +** is permitted to survive without activity. +** +** We actually try any cached connections by sending a RSET +** before we use them; if the RSET fails we close down the +** connection and reopen it (see smtpprobe()). +** +** The persistent MCI code is donated by Mark Lovell and Paul +** Vixie. It is based on the long term host status code in KJS +** written by Paul but has been adapted by Mark to fit into the +** MCI structure. +*/ + +static MCI **MciCache; /* the open connection cache */ + +/* +** MCI_CACHE -- enter a connection structure into the open connection cache +** +** This may cause something else to be flushed. +** +** Parameters: +** mci -- the connection to cache. +** +** Returns: +** none. +*/ + +void +mci_cache(mci) + register MCI *mci; +{ + register MCI **mcislot; + + /* + ** Find the best slot. This may cause expired connections + ** to be closed. + */ + + mcislot = mci_scan(mci); + if (mcislot == NULL) + { + /* we don't support caching */ + return; + } + + if (mci->mci_host == NULL) + return; + + /* if this is already cached, we are done */ + if (bitset(MCIF_CACHED, mci->mci_flags)) + return; + + /* otherwise we may have to clear the slot */ + if (*mcislot != NULL) + mci_uncache(mcislot, true); + + if (tTd(42, 5)) + sm_dprintf("mci_cache: caching %p (%s) in slot %d\n", + mci, mci->mci_host, (int) (mcislot - MciCache)); + if (tTd(91, 100)) + sm_syslog(LOG_DEBUG, CurEnv->e_id, + "mci_cache: caching %lx (%.100s) in slot %d", + (unsigned long) mci, mci->mci_host, + (int) (mcislot - MciCache)); + + *mcislot = mci; + mci->mci_flags |= MCIF_CACHED; +} +/* +** MCI_SCAN -- scan the cache, flush junk, and return best slot +** +** Parameters: +** savemci -- never flush this one. Can be null. +** +** Returns: +** The LRU (or empty) slot. +*/ + +MCI ** +mci_scan(savemci) + MCI *savemci; +{ + time_t now; + register MCI **bestmci; + register MCI *mci; + register int i; + + if (MaxMciCache <= 0) + { + /* we don't support caching */ + return NULL; + } + + if (MciCache == NULL) + { + /* first call */ + MciCache = (MCI **) sm_pmalloc_x(MaxMciCache * sizeof(*MciCache)); + memset((char *) MciCache, '\0', MaxMciCache * sizeof(*MciCache)); + return &MciCache[0]; + } + + now = curtime(); + bestmci = &MciCache[0]; + for (i = 0; i < MaxMciCache; i++) + { + mci = MciCache[i]; + if (mci == NULL || mci->mci_state == MCIS_CLOSED) + { + bestmci = &MciCache[i]; + continue; + } + if ((mci->mci_lastuse + MciCacheTimeout <= now || + (mci->mci_mailer != NULL && + mci->mci_mailer->m_maxdeliveries > 0 && + mci->mci_deliveries + 1 >= mci->mci_mailer->m_maxdeliveries))&& + mci != savemci) + { + /* connection idle too long or too many deliveries */ + bestmci = &MciCache[i]; + + /* close it */ + mci_uncache(bestmci, true); + continue; + } + if (*bestmci == NULL) + continue; + if (mci->mci_lastuse < (*bestmci)->mci_lastuse) + bestmci = &MciCache[i]; + } + return bestmci; +} +/* +** MCI_UNCACHE -- remove a connection from a slot. +** +** May close a connection. +** +** Parameters: +** mcislot -- the slot to empty. +** doquit -- if true, send QUIT protocol on this connection. +** if false, we are assumed to be in a forked child; +** all we want to do is close the file(s). +** +** Returns: +** none. +*/ + +static void +mci_uncache(mcislot, doquit) + register MCI **mcislot; + bool doquit; +{ + register MCI *mci; + extern ENVELOPE BlankEnvelope; + + mci = *mcislot; + if (mci == NULL) + return; + *mcislot = NULL; + if (mci->mci_host == NULL) + return; + + mci_unlock_host(mci); + + if (tTd(42, 5)) + sm_dprintf("mci_uncache: uncaching %p (%s) from slot %d (%d)\n", + mci, mci->mci_host, (int) (mcislot - MciCache), + doquit); + if (tTd(91, 100)) + sm_syslog(LOG_DEBUG, CurEnv->e_id, + "mci_uncache: uncaching %lx (%.100s) from slot %d (%d)", + (unsigned long) mci, mci->mci_host, + (int) (mcislot - MciCache), doquit); + + mci->mci_deliveries = 0; + if (doquit) + { + message("Closing connection to %s", mci->mci_host); + + mci->mci_flags &= ~MCIF_CACHED; + + /* only uses the envelope to flush the transcript file */ + if (mci->mci_state != MCIS_CLOSED) + smtpquit(mci->mci_mailer, mci, &BlankEnvelope); +#if XLA + xla_host_end(mci->mci_host); +#endif /* XLA */ + } + else + { + if (mci->mci_in != NULL) + (void) sm_io_close(mci->mci_in, SM_TIME_DEFAULT); + if (mci->mci_out != NULL) + (void) sm_io_close(mci->mci_out, SM_TIME_DEFAULT); + mci->mci_in = mci->mci_out = NULL; + mci->mci_state = MCIS_CLOSED; + mci->mci_exitstat = EX_OK; + mci->mci_errno = 0; + mci->mci_flags = 0; + + mci->mci_retryrcpt = false; + mci->mci_tolist = NULL; +#if PIPELINING + mci->mci_okrcpts = 0; +#endif /* PIPELINING */ + } + + SM_FREE_CLR(mci->mci_status); + SM_FREE_CLR(mci->mci_rstatus); + SM_FREE_CLR(mci->mci_heloname); + if (mci->mci_rpool != NULL) + { + sm_rpool_free(mci->mci_rpool); + mci->mci_macro.mac_rpool = NULL; + mci->mci_rpool = NULL; + } +} +/* +** MCI_FLUSH -- flush the entire cache +** +** Parameters: +** doquit -- if true, send QUIT protocol. +** if false, just close the connection. +** allbut -- but leave this one open. +** +** Returns: +** none. +*/ + +void +mci_flush(doquit, allbut) + bool doquit; + MCI *allbut; +{ + register int i; + + if (MciCache == NULL) + return; + + for (i = 0; i < MaxMciCache; i++) + { + if (allbut != MciCache[i]) + mci_uncache(&MciCache[i], doquit); + } +} +/* +** MCI_GET -- get information about a particular host +** +** Parameters: +** host -- host to look for. +** m -- mailer. +** +** Returns: +** mci for this host (might be new). +*/ + +MCI * +mci_get(host, m) + char *host; + MAILER *m; +{ + register MCI *mci; + register STAB *s; + extern SOCKADDR CurHostAddr; + + /* clear CurHostAddr so we don't get a bogus address with this name */ + memset(&CurHostAddr, '\0', sizeof(CurHostAddr)); + + /* clear out any expired connections */ + (void) mci_scan(NULL); + + if (m->m_mno < 0) + syserr("!negative mno %d (%s)", m->m_mno, m->m_name); + + s = stab(host, ST_MCI + m->m_mno, ST_ENTER); + mci = &s->s_mci; + + /* initialize per-message data */ + mci->mci_retryrcpt = false; + mci->mci_tolist = NULL; +#if PIPELINING + mci->mci_okrcpts = 0; +#endif /* PIPELINING */ + + if (mci->mci_rpool == NULL) + mci->mci_rpool = sm_rpool_new_x(NULL); + + if (mci->mci_macro.mac_rpool == NULL) + mci->mci_macro.mac_rpool = mci->mci_rpool; + + /* + ** We don't need to load the persistent data if we have data + ** already loaded in the cache. + */ + + if (mci->mci_host == NULL && + (mci->mci_host = s->s_name) != NULL && + !mci_load_persistent(mci)) + { + if (tTd(42, 2)) + sm_dprintf("mci_get(%s %s): lock failed\n", + host, m->m_name); + mci->mci_exitstat = EX_TEMPFAIL; + mci->mci_state = MCIS_CLOSED; + mci->mci_statfile = NULL; + return mci; + } + + if (tTd(42, 2)) + { + sm_dprintf("mci_get(%s %s): mci_state=%d, _flags=%lx, _exitstat=%d, _errno=%d\n", + host, m->m_name, mci->mci_state, mci->mci_flags, + mci->mci_exitstat, mci->mci_errno); + } + + if (mci->mci_state == MCIS_OPEN) + { + /* poke the connection to see if it's still alive */ + (void) smtpprobe(mci); + + /* reset the stored state in the event of a timeout */ + if (mci->mci_state != MCIS_OPEN) + { + mci->mci_errno = 0; + mci->mci_exitstat = EX_OK; + mci->mci_state = MCIS_CLOSED; + } + else + { + /* get peer host address */ + /* (this should really be in the mci struct) */ + SOCKADDR_LEN_T socklen = sizeof(CurHostAddr); + + (void) getpeername(sm_io_getinfo(mci->mci_in, + SM_IO_WHAT_FD, NULL), + (struct sockaddr *) &CurHostAddr, &socklen); + } + } + if (mci->mci_state == MCIS_CLOSED) + { + time_t now = curtime(); + + /* if this info is stale, ignore it */ + if (mci->mci_lastuse + MciInfoTimeout <= now) + { + mci->mci_lastuse = now; + mci->mci_errno = 0; + mci->mci_exitstat = EX_OK; + } + } + + return mci; +} + +/* +** MCI_CLOSE -- (forcefully) close files used for a connection. +** Note: this is a last resort, usually smtpquit() or endmailer() +** should be used to close a connection. +** +** Parameters: +** mci -- the connection to close. +** where -- where has this been called? +** +** Returns: +** none. +*/ + +void +mci_close(mci, where) + MCI *mci; + char *where; +{ + bool dumped; + + if (mci == NULL) + return; + dumped = false; + if (mci->mci_out != NULL) + { + if (tTd(56, 1)) + { + sm_dprintf("mci_close: mci_out!=NULL, where=%s\n", + where); + mci_dump(sm_debug_file(), mci, false); + dumped = true; + } + (void) sm_io_close(mci->mci_out, SM_TIME_DEFAULT); + mci->mci_out = NULL; + } + if (mci->mci_in != NULL) + { + if (tTd(56, 1)) + { + sm_dprintf("mci_close: mci_in!=NULL, where=%s\n", + where); + if (!dumped) + mci_dump(sm_debug_file(), mci, false); + } + (void) sm_io_close(mci->mci_in, SM_TIME_DEFAULT); + mci->mci_in = NULL; + } + mci->mci_state = MCIS_CLOSED; +} + +/* +** MCI_NEW -- allocate new MCI structure +** +** Parameters: +** rpool -- if non-NULL: allocate from that rpool. +** +** Returns: +** mci (new). +*/ + +MCI * +mci_new(rpool) + SM_RPOOL_T *rpool; +{ + register MCI *mci; + + if (rpool == NULL) + mci = (MCI *) sm_malloc_x(sizeof(*mci)); + else + mci = (MCI *) sm_rpool_malloc_x(rpool, sizeof(*mci)); + memset((char *) mci, '\0', sizeof(*mci)); + mci->mci_rpool = sm_rpool_new_x(NULL); + mci->mci_macro.mac_rpool = mci->mci_rpool; + return mci; +} +/* +** MCI_MATCH -- check connection cache for a particular host +** +** Parameters: +** host -- host to look for. +** m -- mailer. +** +** Returns: +** true iff open connection exists. +*/ + +bool +mci_match(host, m) + char *host; + MAILER *m; +{ + register MCI *mci; + register STAB *s; + + if (m->m_mno < 0 || m->m_mno > MAXMAILERS) + return false; + s = stab(host, ST_MCI + m->m_mno, ST_FIND); + if (s == NULL) + return false; + + mci = &s->s_mci; + return mci->mci_state == MCIS_OPEN; +} +/* +** MCI_SETSTAT -- set status codes in MCI structure. +** +** Parameters: +** mci -- the MCI structure to set. +** xstat -- the exit status code. +** dstat -- the DSN status code. +** rstat -- the SMTP status code. +** +** Returns: +** none. +*/ + +void +mci_setstat(mci, xstat, dstat, rstat) + MCI *mci; + int xstat; + char *dstat; + char *rstat; +{ + /* protocol errors should never be interpreted as sticky */ + if (xstat != EX_NOTSTICKY && xstat != EX_PROTOCOL) + mci->mci_exitstat = xstat; + + SM_FREE_CLR(mci->mci_status); + if (dstat != NULL) + mci->mci_status = sm_strdup_x(dstat); + + SM_FREE_CLR(mci->mci_rstatus); + if (rstat != NULL) + mci->mci_rstatus = sm_strdup_x(rstat); +} +/* +** MCI_DUMP -- dump the contents of an MCI structure. +** +** Parameters: +** fp -- output file pointer +** mci -- the MCI structure to dump. +** +** Returns: +** none. +** +** Side Effects: +** none. +*/ + +struct mcifbits +{ + int mcif_bit; /* flag bit */ + char *mcif_name; /* flag name */ +}; +static struct mcifbits MciFlags[] = +{ + { MCIF_VALID, "VALID" }, + { MCIF_CACHED, "CACHED" }, + { MCIF_ESMTP, "ESMTP" }, + { MCIF_EXPN, "EXPN" }, + { MCIF_SIZE, "SIZE" }, + { MCIF_8BITMIME, "8BITMIME" }, + { MCIF_7BIT, "7BIT" }, + { MCIF_INHEADER, "INHEADER" }, + { MCIF_CVT8TO7, "CVT8TO7" }, + { MCIF_DSN, "DSN" }, + { MCIF_8BITOK, "8BITOK" }, + { MCIF_CVT7TO8, "CVT7TO8" }, + { MCIF_INMIME, "INMIME" }, + { MCIF_AUTH, "AUTH" }, + { MCIF_AUTHACT, "AUTHACT" }, + { MCIF_ENHSTAT, "ENHSTAT" }, + { MCIF_PIPELINED, "PIPELINED" }, +#if STARTTLS + { MCIF_TLS, "TLS" }, + { MCIF_TLSACT, "TLSACT" }, +#endif /* STARTTLS */ + { MCIF_DLVR_BY, "DLVR_BY" }, + { 0, NULL } +}; + +void +mci_dump(fp, mci, logit) + SM_FILE_T *fp; + register MCI *mci; + bool logit; +{ + register char *p; + char *sep; + char buf[4000]; + + sep = logit ? " " : "\n\t"; + p = buf; + (void) sm_snprintf(p, SPACELEFT(buf, p), "MCI@%p: ", mci); + p += strlen(p); + if (mci == NULL) + { + (void) sm_snprintf(p, SPACELEFT(buf, p), "NULL"); + goto printit; + } + (void) sm_snprintf(p, SPACELEFT(buf, p), "flags=%lx", mci->mci_flags); + p += strlen(p); + + /* + ** The following check is just for paranoia. It protects the + ** assignment in the if() clause. If there's not some minimum + ** amount of space we can stop right now. The check will not + ** trigger as long as sizeof(buf)=4000. + */ + + if (p >= buf + sizeof(buf) - 4) + goto printit; + if (mci->mci_flags != 0) + { + struct mcifbits *f; + + *p++ = '<'; /* protected above */ + for (f = MciFlags; f->mcif_bit != 0; f++) + { + if (!bitset(f->mcif_bit, mci->mci_flags)) + continue; + (void) sm_strlcpyn(p, SPACELEFT(buf, p), 2, + f->mcif_name, ","); + p += strlen(p); + } + p[-1] = '>'; + } + + /* Note: sm_snprintf() takes care of NULL arguments for %s */ + (void) sm_snprintf(p, SPACELEFT(buf, p), + ",%serrno=%d, herrno=%d, exitstat=%d, state=%d, pid=%d,%s", + sep, mci->mci_errno, mci->mci_herrno, + mci->mci_exitstat, mci->mci_state, (int) mci->mci_pid, sep); + p += strlen(p); + (void) sm_snprintf(p, SPACELEFT(buf, p), + "maxsize=%ld, phase=%s, mailer=%s,%s", + mci->mci_maxsize, mci->mci_phase, + mci->mci_mailer == NULL ? "NULL" : mci->mci_mailer->m_name, + sep); + p += strlen(p); + (void) sm_snprintf(p, SPACELEFT(buf, p), + "status=%s, rstatus=%s,%s", + mci->mci_status, mci->mci_rstatus, sep); + p += strlen(p); + (void) sm_snprintf(p, SPACELEFT(buf, p), + "host=%s, lastuse=%s", + mci->mci_host, ctime(&mci->mci_lastuse)); +printit: + if (logit) + sm_syslog(LOG_DEBUG, CurEnv->e_id, "%.1000s", buf); + else + (void) sm_io_fprintf(fp, SM_TIME_DEFAULT, "%s\n", buf); +} +/* +** MCI_DUMP_ALL -- print the entire MCI cache +** +** Parameters: +** fp -- output file pointer +** logit -- if set, log the result instead of printing +** to stdout. +** +** Returns: +** none. +*/ + +void +mci_dump_all(fp, logit) + SM_FILE_T *fp; + bool logit; +{ + register int i; + + if (MciCache == NULL) + return; + + for (i = 0; i < MaxMciCache; i++) + mci_dump(fp, MciCache[i], logit); +} +/* +** MCI_LOCK_HOST -- Lock host while sending. +** +** If we are contacting a host, we'll need to +** update the status information in the host status +** file, and if we want to do that, we ought to have +** locked it. This has the (according to some) +** desirable effect of serializing connectivity with +** remote hosts -- i.e.: one connection to a given +** host at a time. +** +** Parameters: +** mci -- containing the host we want to lock. +** +** Returns: +** EX_OK -- got the lock. +** EX_TEMPFAIL -- didn't get the lock. +*/ + +int +mci_lock_host(mci) + MCI *mci; +{ + if (mci == NULL) + { + if (tTd(56, 1)) + sm_dprintf("mci_lock_host: NULL mci\n"); + return EX_OK; + } + + if (!SingleThreadDelivery) + return EX_OK; + + return mci_lock_host_statfile(mci); +} + +static int +mci_lock_host_statfile(mci) + MCI *mci; +{ + int save_errno = errno; + int retVal = EX_OK; + char fname[MAXPATHLEN]; + + if (HostStatDir == NULL || mci->mci_host == NULL) + return EX_OK; + + if (tTd(56, 2)) + sm_dprintf("mci_lock_host: attempting to lock %s\n", + mci->mci_host); + + if (mci_generate_persistent_path(mci->mci_host, fname, sizeof(fname), + true) < 0) + { + /* of course this should never happen */ + if (tTd(56, 2)) + sm_dprintf("mci_lock_host: Failed to generate host path for %s\n", + mci->mci_host); + + retVal = EX_TEMPFAIL; + goto cleanup; + } + + mci->mci_statfile = safefopen(fname, O_RDWR, FileMode, + SFF_NOLOCK|SFF_NOLINK|SFF_OPENASROOT|SFF_REGONLY|SFF_SAFEDIRPATH|SFF_CREAT); + + if (mci->mci_statfile == NULL) + { + syserr("mci_lock_host: cannot create host lock file %s", fname); + goto cleanup; + } + + if (!lockfile(sm_io_getinfo(mci->mci_statfile, SM_IO_WHAT_FD, NULL), + fname, "", LOCK_EX|LOCK_NB)) + { + if (tTd(56, 2)) + sm_dprintf("mci_lock_host: couldn't get lock on %s\n", + fname); + (void) sm_io_close(mci->mci_statfile, SM_TIME_DEFAULT); + mci->mci_statfile = NULL; + retVal = EX_TEMPFAIL; + goto cleanup; + } + + if (tTd(56, 12) && mci->mci_statfile != NULL) + sm_dprintf("mci_lock_host: Sanity check -- lock is good\n"); + +cleanup: + errno = save_errno; + return retVal; +} +/* +** MCI_UNLOCK_HOST -- unlock host +** +** Clean up the lock on a host, close the file, let +** someone else use it. +** +** Parameters: +** mci -- us. +** +** Returns: +** nothing. +*/ + +void +mci_unlock_host(mci) + MCI *mci; +{ + int save_errno = errno; + + if (mci == NULL) + { + if (tTd(56, 1)) + sm_dprintf("mci_unlock_host: NULL mci\n"); + return; + } + + if (HostStatDir == NULL || mci->mci_host == NULL) + return; + + if (!SingleThreadDelivery && mci_lock_host_statfile(mci) == EX_TEMPFAIL) + { + if (tTd(56, 1)) + sm_dprintf("mci_unlock_host: stat file already locked\n"); + } + else + { + if (tTd(56, 2)) + sm_dprintf("mci_unlock_host: store prior to unlock\n"); + mci_store_persistent(mci); + } + + if (mci->mci_statfile != NULL) + { + (void) sm_io_close(mci->mci_statfile, SM_TIME_DEFAULT); + mci->mci_statfile = NULL; + } + + errno = save_errno; +} +/* +** MCI_LOAD_PERSISTENT -- load persistent host info +** +** Load information about host that is kept +** in common for all running sendmails. +** +** Parameters: +** mci -- the host/connection to load persistent info for. +** +** Returns: +** true -- lock was successful +** false -- lock failed +*/ + +static bool +mci_load_persistent(mci) + MCI *mci; +{ + int save_errno = errno; + bool locked = true; + SM_FILE_T *fp; + char fname[MAXPATHLEN]; + + if (mci == NULL) + { + if (tTd(56, 1)) + sm_dprintf("mci_load_persistent: NULL mci\n"); + return true; + } + + if (IgnoreHostStatus || HostStatDir == NULL || mci->mci_host == NULL) + return true; + + /* Already have the persistent information in memory */ + if (SingleThreadDelivery && mci->mci_statfile != NULL) + return true; + + if (tTd(56, 1)) + sm_dprintf("mci_load_persistent: Attempting to load persistent information for %s\n", + mci->mci_host); + + if (mci_generate_persistent_path(mci->mci_host, fname, sizeof(fname), + false) < 0) + { + /* Not much we can do if the file isn't there... */ + if (tTd(56, 1)) + sm_dprintf("mci_load_persistent: Couldn't generate host path\n"); + goto cleanup; + } + + fp = safefopen(fname, O_RDONLY, FileMode, + SFF_NOLOCK|SFF_NOLINK|SFF_OPENASROOT|SFF_REGONLY|SFF_SAFEDIRPATH); + if (fp == NULL) + { + /* I can't think of any reason this should ever happen */ + if (tTd(56, 1)) + sm_dprintf("mci_load_persistent: open(%s): %s\n", + fname, sm_errstring(errno)); + goto cleanup; + } + + FileName = fname; + locked = lockfile(sm_io_getinfo(fp, SM_IO_WHAT_FD, NULL), fname, "", + LOCK_SH|LOCK_NB); + if (locked) + { + (void) mci_read_persistent(fp, mci); + (void) lockfile(sm_io_getinfo(fp, SM_IO_WHAT_FD, NULL), fname, + "", LOCK_UN); + } + FileName = NULL; + (void) sm_io_close(fp, SM_TIME_DEFAULT); + +cleanup: + errno = save_errno; + return locked; +} +/* +** MCI_READ_PERSISTENT -- read persistent host status file +** +** Parameters: +** fp -- the file pointer to read. +** mci -- the pointer to fill in. +** +** Returns: +** -1 -- if the file was corrupt. +** 0 -- otherwise. +** +** Warning: +** This code makes the assumption that this data +** will be read in an atomic fashion, and that the data +** was written in an atomic fashion. Any other functioning +** may lead to some form of insanity. This should be +** perfectly safe due to underlying stdio buffering. +*/ + +static int +mci_read_persistent(fp, mci) + SM_FILE_T *fp; + register MCI *mci; +{ + int ver; + register char *p; + int saveLineNumber = LineNumber; + char buf[MAXLINE]; + + if (fp == NULL) + { + syserr("mci_read_persistent: NULL fp"); + /* NOTREACHED */ + return -1; + } + if (mci == NULL) + { + syserr("mci_read_persistent: NULL mci"); + /* NOTREACHED */ + return -1; + } + if (tTd(56, 93)) + { + sm_dprintf("mci_read_persistent: fp=%lx, mci=", + (unsigned long) fp); + } + + SM_FREE_CLR(mci->mci_status); + SM_FREE_CLR(mci->mci_rstatus); + + sm_io_rewind(fp, SM_TIME_DEFAULT); + ver = -1; + LineNumber = 0; + while (sm_io_fgets(fp, SM_TIME_DEFAULT, buf, sizeof(buf)) != NULL) + { + LineNumber++; + p = strchr(buf, '\n'); + if (p != NULL) + *p = '\0'; + switch (buf[0]) + { + case 'V': /* version stamp */ + ver = atoi(&buf[1]); + if (ver < 0 || ver > 0) + syserr("Unknown host status version %d: %d max", + ver, 0); + break; + + case 'E': /* UNIX error number */ + mci->mci_errno = atoi(&buf[1]); + break; + + case 'H': /* DNS error number */ + mci->mci_herrno = atoi(&buf[1]); + break; + + case 'S': /* UNIX exit status */ + mci->mci_exitstat = atoi(&buf[1]); + break; + + case 'D': /* DSN status */ + mci->mci_status = newstr(&buf[1]); + break; + + case 'R': /* SMTP status */ + mci->mci_rstatus = newstr(&buf[1]); + break; + + case 'U': /* last usage time */ + mci->mci_lastuse = atol(&buf[1]); + break; + + case '.': /* end of file */ + if (tTd(56, 93)) + mci_dump(sm_debug_file(), mci, false); + return 0; + + default: + sm_syslog(LOG_CRIT, NOQID, + "%s: line %d: Unknown host status line \"%s\"", + FileName == NULL ? mci->mci_host : FileName, + LineNumber, buf); + LineNumber = saveLineNumber; + return -1; + } + } + LineNumber = saveLineNumber; + if (tTd(56, 93)) + sm_dprintf("incomplete (missing dot for EOF)\n"); + if (ver < 0) + return -1; + return 0; +} +/* +** MCI_STORE_PERSISTENT -- Store persistent MCI information +** +** Store information about host that is kept +** in common for all running sendmails. +** +** Parameters: +** mci -- the host/connection to store persistent info for. +** +** Returns: +** none. +*/ + +void +mci_store_persistent(mci) + MCI *mci; +{ + int save_errno = errno; + + if (mci == NULL) + { + if (tTd(56, 1)) + sm_dprintf("mci_store_persistent: NULL mci\n"); + return; + } + + if (HostStatDir == NULL || mci->mci_host == NULL) + return; + + if (tTd(56, 1)) + sm_dprintf("mci_store_persistent: Storing information for %s\n", + mci->mci_host); + + if (mci->mci_statfile == NULL) + { + if (tTd(56, 1)) + sm_dprintf("mci_store_persistent: no statfile\n"); + return; + } + + sm_io_rewind(mci->mci_statfile, SM_TIME_DEFAULT); +#if !NOFTRUNCATE + (void) ftruncate(sm_io_getinfo(mci->mci_statfile, SM_IO_WHAT_FD, NULL), + (off_t) 0); +#endif /* !NOFTRUNCATE */ + + (void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT, "V0\n"); + (void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT, "E%d\n", + mci->mci_errno); + (void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT, "H%d\n", + mci->mci_herrno); + (void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT, "S%d\n", + mci->mci_exitstat); + if (mci->mci_status != NULL) + (void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT, + "D%.80s\n", + denlstring(mci->mci_status, true, false)); + if (mci->mci_rstatus != NULL) + (void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT, + "R%.80s\n", + denlstring(mci->mci_rstatus, true, false)); + (void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT, "U%ld\n", + (long)(mci->mci_lastuse)); + (void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT, ".\n"); + + (void) sm_io_flush(mci->mci_statfile, SM_TIME_DEFAULT); + + errno = save_errno; + return; +} +/* +** MCI_TRAVERSE_PERSISTENT -- walk persistent status tree +** +** Recursively find all the mci host files in `pathname'. Default to +** main host status directory if no path is provided. +** Call (*action)(pathname, host) for each file found. +** +** Note: all information is collected in a list before it is processed. +** This may not be the best way to do it, but it seems safest, since +** the file system would be touched while we are attempting to traverse +** the directory tree otherwise (during purges). +** +** Parameters: +** action -- function to call on each node. If returns < 0, +** return immediately. +** pathname -- root of tree. If null, use main host status +** directory. +** +** Returns: +** < 0 -- if any action routine returns a negative value, that +** value is returned. +** 0 -- if we successfully went to completion. +** > 0 -- return status from action() +*/ + +int +mci_traverse_persistent(action, pathname) + int (*action)__P((char *, char *)); + char *pathname; +{ + struct stat statbuf; + DIR *d; + int ret; + + if (pathname == NULL) + pathname = HostStatDir; + if (pathname == NULL) + return -1; + + if (tTd(56, 1)) + sm_dprintf("mci_traverse: pathname is %s\n", pathname); + + ret = stat(pathname, &statbuf); + if (ret < 0) + { + if (tTd(56, 2)) + sm_dprintf("mci_traverse: Failed to stat %s: %s\n", + pathname, sm_errstring(errno)); + return ret; + } + if (S_ISDIR(statbuf.st_mode)) + { + bool leftone, removedone; + size_t len; + char *newptr; + struct dirent *e; + char newpath[MAXPATHLEN]; +#if MAXPATHLEN <= MAXNAMLEN - 3 + ERROR "MAXPATHLEN <= MAXNAMLEN - 3" +#endif /* MAXPATHLEN <= MAXNAMLEN - 3 */ + + if ((d = opendir(pathname)) == NULL) + { + if (tTd(56, 2)) + sm_dprintf("mci_traverse: opendir %s: %s\n", + pathname, sm_errstring(errno)); + return -1; + } + len = sizeof(newpath) - MAXNAMLEN - 3; + if (sm_strlcpy(newpath, pathname, len) >= len) + { + if (tTd(56, 2)) + sm_dprintf("mci_traverse: path \"%s\" too long", + pathname); + return -1; + } + newptr = newpath + strlen(newpath); + *newptr++ = '/'; + + /* + ** repeat until no file has been removed + ** this may become ugly when several files "expire" + ** during these loops, but it's better than doing + ** a rewinddir() inside the inner loop + */ + + do + { + leftone = removedone = false; + while ((e = readdir(d)) != NULL) + { + if (e->d_name[0] == '.') + continue; + + (void) sm_strlcpy(newptr, e->d_name, + sizeof(newpath) - + (newptr - newpath)); + + if (StopRequest) + stop_sendmail(); + ret = mci_traverse_persistent(action, newpath); + if (ret < 0) + break; + if (ret == 1) + leftone = true; + if (!removedone && ret == 0 && + action == mci_purge_persistent) + removedone = true; + } + if (ret < 0) + break; + + /* + ** The following appears to be + ** necessary during purges, since + ** we modify the directory structure + */ + + if (removedone) + rewinddir(d); + if (tTd(56, 40)) + sm_dprintf("mci_traverse: path %s: ret %d removed %d left %d\n", + pathname, ret, removedone, leftone); + } while (removedone); + + /* purge (or whatever) the directory proper */ + if (!leftone) + { + *--newptr = '\0'; + ret = (*action)(newpath, NULL); + } + (void) closedir(d); + } + else if (S_ISREG(statbuf.st_mode)) + { + char *end = pathname + strlen(pathname) - 1; + char *start; + char *scan; + char host[MAXHOSTNAMELEN]; + char *hostptr = host; + + /* + ** Reconstruct the host name from the path to the + ** persistent information. + */ + + do + { + if (hostptr != host) + *(hostptr++) = '.'; + start = end; + while (start > pathname && *(start - 1) != '/') + start--; + + if (*end == '.') + end--; + + for (scan = start; scan <= end; scan++) + *(hostptr++) = *scan; + + end = start - 2; + } while (end > pathname && *end == '.'); + + *hostptr = '\0'; + + /* + ** Do something with the file containing the persistent + ** information. + */ + + ret = (*action)(pathname, host); + } + + return ret; +} +/* +** MCI_PRINT_PERSISTENT -- print persistent info +** +** Dump the persistent information in the file 'pathname' +** +** Parameters: +** pathname -- the pathname to the status file. +** hostname -- the corresponding host name. +** +** Returns: +** 0 +*/ + +int +mci_print_persistent(pathname, hostname) + char *pathname; + char *hostname; +{ + static bool initflag = false; + SM_FILE_T *fp; + int width = Verbose ? 78 : 25; + bool locked; + MCI mcib; + + /* skip directories */ + if (hostname == NULL) + return 0; + + if (StopRequest) + stop_sendmail(); + + if (!initflag) + { + initflag = true; + (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, + " -------------- Hostname --------------- How long ago ---------Results---------\n"); + } + + fp = safefopen(pathname, O_RDONLY, FileMode, + SFF_NOLOCK|SFF_NOLINK|SFF_OPENASROOT|SFF_REGONLY|SFF_SAFEDIRPATH); + + if (fp == NULL) + { + if (tTd(56, 1)) + sm_dprintf("mci_print_persistent: cannot open %s: %s\n", + pathname, sm_errstring(errno)); + return 0; + } + + FileName = pathname; + memset(&mcib, '\0', sizeof(mcib)); + if (mci_read_persistent(fp, &mcib) < 0) + { + syserr("%s: could not read status file", pathname); + (void) sm_io_close(fp, SM_TIME_DEFAULT); + FileName = NULL; + return 0; + } + + locked = !lockfile(sm_io_getinfo(fp, SM_IO_WHAT_FD, NULL), pathname, + "", LOCK_SH|LOCK_NB); + (void) sm_io_close(fp, SM_TIME_DEFAULT); + FileName = NULL; + + (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%c%-39s %12s ", + locked ? '*' : ' ', hostname, + pintvl(curtime() - mcib.mci_lastuse, true)); + if (mcib.mci_rstatus != NULL) + (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%.*s\n", width, + mcib.mci_rstatus); + else if (mcib.mci_exitstat == EX_TEMPFAIL && mcib.mci_errno != 0) + (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, + "Deferred: %.*s\n", width - 10, + sm_errstring(mcib.mci_errno)); + else if (mcib.mci_exitstat != 0) + { + char *exmsg = sm_sysexmsg(mcib.mci_exitstat); + + if (exmsg == NULL) + { + char buf[80]; + + (void) sm_snprintf(buf, sizeof(buf), + "Unknown mailer error %d", + mcib.mci_exitstat); + (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%.*s\n", + width, buf); + } + else + (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%.*s\n", + width, &exmsg[5]); + } + else if (mcib.mci_errno == 0) + (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "OK\n"); + else + (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "OK: %.*s\n", + width - 4, sm_errstring(mcib.mci_errno)); + + return 0; +} +/* +** MCI_PURGE_PERSISTENT -- Remove a persistence status file. +** +** Parameters: +** pathname -- path to the status file. +** hostname -- name of host corresponding to that file. +** NULL if this is a directory (domain). +** +** Returns: +** 0 -- ok +** 1 -- file not deleted (too young, incorrect format) +** < 0 -- some error occurred +*/ + +int +mci_purge_persistent(pathname, hostname) + char *pathname; + char *hostname; +{ + struct stat statbuf; + char *end = pathname + strlen(pathname) - 1; + int ret; + + if (tTd(56, 1)) + sm_dprintf("mci_purge_persistent: purging %s\n", pathname); + + ret = stat(pathname, &statbuf); + if (ret < 0) + { + if (tTd(56, 2)) + sm_dprintf("mci_purge_persistent: Failed to stat %s: %s\n", + pathname, sm_errstring(errno)); + return ret; + } + if (curtime() - statbuf.st_mtime <= MciInfoTimeout) + return 1; + if (hostname != NULL) + { + /* remove the file */ + ret = unlink(pathname); + if (ret < 0) + { + if (LogLevel > 8) + sm_syslog(LOG_ERR, NOQID, + "mci_purge_persistent: failed to unlink %s: %s", + pathname, sm_errstring(errno)); + if (tTd(56, 2)) + sm_dprintf("mci_purge_persistent: failed to unlink %s: %s\n", + pathname, sm_errstring(errno)); + return ret; + } + } + else + { + /* remove the directory */ + if (*end != '.') + return 1; + + if (tTd(56, 1)) + sm_dprintf("mci_purge_persistent: dpurge %s\n", pathname); + + ret = rmdir(pathname); + if (ret < 0) + { + if (tTd(56, 2)) + sm_dprintf("mci_purge_persistent: rmdir %s: %s\n", + pathname, sm_errstring(errno)); + return ret; + } + } + + return 0; +} +/* +** MCI_GENERATE_PERSISTENT_PATH -- generate path from hostname +** +** Given `host', convert from a.b.c to $HostStatDir/c./b./a, +** putting the result into `path'. if `createflag' is set, intervening +** directories will be created as needed. +** +** Parameters: +** host -- host name to convert from. +** path -- place to store result. +** pathlen -- length of path buffer. +** createflag -- if set, create intervening directories as +** needed. +** +** Returns: +** 0 -- success +** -1 -- failure +*/ + +static int +mci_generate_persistent_path(host, path, pathlen, createflag) + const char *host; + char *path; + int pathlen; + bool createflag; +{ + char *elem, *p, *x, ch; + int ret = 0; + int len; + char t_host[MAXHOSTNAMELEN]; +#if NETINET6 + struct in6_addr in6_addr; +#endif /* NETINET6 */ + + /* + ** Rationality check the arguments. + */ + + if (host == NULL) + { + syserr("mci_generate_persistent_path: null host"); + return -1; + } + if (path == NULL) + { + syserr("mci_generate_persistent_path: null path"); + return -1; + } + + if (tTd(56, 80)) + sm_dprintf("mci_generate_persistent_path(%s): ", host); + + if (*host == '\0' || *host == '.') + return -1; + + /* make certain this is not a bracketed host number */ + if (strlen(host) > sizeof(t_host) - 1) + return -1; + if (host[0] == '[') + (void) sm_strlcpy(t_host, host + 1, sizeof(t_host)); + else + (void) sm_strlcpy(t_host, host, sizeof(t_host)); + + /* + ** Delete any trailing dots from the hostname. + ** Leave 'elem' pointing at the \0. + */ + + elem = t_host + strlen(t_host); + while (elem > t_host && + (elem[-1] == '.' || (host[0] == '[' && elem[-1] == ']'))) + *--elem = '\0'; + + /* check for bogus bracketed address */ + if (host[0] == '[') + { + bool good = false; +# if NETINET6 + if (anynet_pton(AF_INET6, t_host, &in6_addr) == 1) + good = true; +# endif /* NETINET6 */ +# if NETINET + if (inet_addr(t_host) != INADDR_NONE) + good = true; +# endif /* NETINET */ + if (!good) + return -1; + } + + /* check for what will be the final length of the path */ + len = strlen(HostStatDir) + 2; + for (p = (char *) t_host; *p != '\0'; p++) + { + if (*p == '.') + len++; + len++; + if (p[0] == '.' && p[1] == '.') + return -1; + } + if (len > pathlen || len < 1) + return -1; + (void) sm_strlcpy(path, HostStatDir, pathlen); + p = path + strlen(path); + while (elem > t_host) + { + if (!path_is_dir(path, createflag)) + { + ret = -1; + break; + } + elem--; + while (elem >= t_host && *elem != '.') + elem--; + *p++ = '/'; + x = elem + 1; + while ((ch = *x++) != '\0' && ch != '.') + { + if (isascii(ch) && isupper(ch)) + ch = tolower(ch); + if (ch == '/') + ch = ':'; /* / -> : */ + *p++ = ch; + } + if (elem >= t_host) + *p++ = '.'; + *p = '\0'; + } + if (tTd(56, 80)) + { + if (ret < 0) + sm_dprintf("FAILURE %d\n", ret); + else + sm_dprintf("SUCCESS %s\n", path); + } + return ret; +} |