mci.c revision d4660949aa62dd6a963f4913b7120b383cf473c4
/*
* 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.
*
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <sendmail.h>
#endif /* NETINET || NETINET6 */
#include <dirent.h>
static int mci_generate_persistent_path __P((const char *, char *,
int, bool));
/*
** 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.
*/
/*
** 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
{
/*
** Find the best slot. This may cause expired connections
** to be closed.
*/
{
/* we don't support caching */
return;
}
return;
/* if this is already cached, we are done */
return;
/* otherwise we may have to clear the slot */
mci_uncache(mcislot, true);
sm_dprintf("mci_cache: caching %p (%s) in slot %d\n",
"mci_cache: caching %lx (%.100s) in slot %d",
}
/*
** 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 **
{
register int i;
if (MaxMciCache <= 0)
{
/* we don't support caching */
return NULL;
}
{
/* first call */
return &MciCache[0];
}
for (i = 0; i < MaxMciCache; i++)
{
{
continue;
}
{
/* connection idle too long or too many deliveries */
/* close it */
mci_uncache(bestmci, true);
continue;
}
continue;
}
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
bool doquit;
{
extern ENVELOPE BlankEnvelope;
return;
return;
sm_dprintf("mci_uncache: uncaching %p (%s) from slot %d (%d)\n",
doquit);
"mci_uncache: uncaching %lx (%.100s) from slot %d (%d)",
mci->mci_deliveries = 0;
if (doquit)
{
/* only uses the envelope to flush the transcript file */
#if XLA
#endif /* XLA */
}
else
{
mci->mci_retryrcpt = false;
#if PIPELINING
mci->mci_okrcpts = 0;
#endif /* PIPELINING */
}
{
}
}
/*
** 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
bool doquit;
{
register int i;
return;
for (i = 0; i < MaxMciCache; i++)
{
}
}
/*
** 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 *
char *host;
MAILER *m;
{
register STAB *s;
extern SOCKADDR CurHostAddr;
/* clear CurHostAddr so we don't get a bogus address with this name */
/* clear out any expired connections */
if (m->m_mno < 0)
/* initialize per-message data */
mci->mci_retryrcpt = false;
#if PIPELINING
mci->mci_okrcpts = 0;
#endif /* PIPELINING */
/*
** We don't need to load the persistent data if we have data
** already loaded in the cache.
*/
{
sm_dprintf("mci_get(%s %s): lock failed\n",
return mci;
}
{
sm_dprintf("mci_get(%s %s): mci_state=%d, _flags=%lx, _exitstat=%d, _errno=%d\n",
}
{
/* poke the connection to see if it's still alive */
/* reset the stored state in the event of a timeout */
{
}
else
{
/* get peer host address */
/* (this should really be in the mci struct) */
}
}
{
/* if this info is stale, ignore it */
{
}
}
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
char *where;
{
bool dumped;
return;
dumped = false;
{
{
sm_dprintf("mci_close: mci_out!=NULL, where=%s\n",
where);
dumped = true;
}
}
{
{
sm_dprintf("mci_close: mci_in!=NULL, where=%s\n",
where);
if (!dumped)
}
}
}
/*
** MCI_NEW -- allocate new MCI structure
**
** Parameters:
** rpool -- if non-NULL: allocate from that rpool.
**
** Returns:
** mci (new).
*/
MCI *
{
else
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
char *host;
MAILER *m;
{
register STAB *s;
return false;
if (s == NULL)
return false;
}
/*
** 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
int xstat;
char *dstat;
char *rstat;
{
/* protocol errors should never be interpreted as sticky */
}
/*
** 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 */
};
{
{ 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
bool logit;
{
register char *p;
char *sep;
char buf[4000];
p = buf;
p += strlen(p);
{
goto printit;
}
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.
*/
goto printit;
{
struct mcifbits *f;
*p++ = '<'; /* protected above */
{
continue;
f->mcif_name, ",");
p += strlen(p);
}
p[-1] = '>';
}
/* Note: sm_snprintf() takes care of NULL arguments for %s */
",%serrno=%d, herrno=%d, exitstat=%d, state=%d, pid=%d,%s",
p += strlen(p);
"maxsize=%ld, phase=%s, mailer=%s,%s",
sep);
p += strlen(p);
"status=%s, rstatus=%s,%s",
p += strlen(p);
"host=%s, lastuse=%s",
if (logit)
else
}
/*
** 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
bool logit;
{
register int i;
return;
for (i = 0; i < MaxMciCache; i++)
}
/*
** 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
{
{
sm_dprintf("mci_lock_host: NULL mci\n");
return EX_OK;
}
if (!SingleThreadDelivery)
return EX_OK;
return mci_lock_host_statfile(mci);
}
static int
{
int save_errno = errno;
char fname[MAXPATHLEN];
return EX_OK;
sm_dprintf("mci_lock_host: attempting to lock %s\n",
true) < 0)
{
/* of course this should never happen */
sm_dprintf("mci_lock_host: Failed to generate host path for %s\n",
goto cleanup;
}
{
goto cleanup;
}
{
sm_dprintf("mci_lock_host: couldn't get lock on %s\n",
fname);
goto cleanup;
}
sm_dprintf("mci_lock_host: Sanity check -- lock is good\n");
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
{
int save_errno = errno;
{
sm_dprintf("mci_unlock_host: NULL mci\n");
return;
}
return;
{
sm_dprintf("mci_unlock_host: stat file already locked\n");
}
else
{
sm_dprintf("mci_unlock_host: store prior to unlock\n");
}
{
}
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
{
int save_errno = errno;
bool locked = true;
char fname[MAXPATHLEN];
{
sm_dprintf("mci_load_persistent: NULL mci\n");
return true;
}
return true;
/* Already have the persistent information in memory */
return true;
sm_dprintf("mci_load_persistent: Attempting to load persistent information for %s\n",
false) < 0)
{
/* Not much we can do if the file isn't there... */
sm_dprintf("mci_load_persistent: Couldn't generate host path\n");
goto cleanup;
}
{
/* I can't think of any reason this should ever happen */
sm_dprintf("mci_load_persistent: open(%s): %s\n",
goto cleanup;
}
if (locked)
{
"", LOCK_UN);
}
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
{
int ver;
register char *p;
int saveLineNumber = LineNumber;
{
syserr("mci_read_persistent: NULL fp");
/* NOTREACHED */
return -1;
}
{
syserr("mci_read_persistent: NULL mci");
/* NOTREACHED */
return -1;
}
{
sm_dprintf("mci_read_persistent: fp=%lx, mci=",
(unsigned long) fp);
}
ver = -1;
LineNumber = 0;
{
LineNumber++;
if (p != NULL)
*p = '\0';
switch (buf[0])
{
case 'V': /* version stamp */
syserr("Unknown host status version %d: %d max",
ver, 0);
break;
case 'E': /* UNIX error number */
break;
case 'H': /* DNS error number */
break;
case 'S': /* UNIX exit status */
break;
case 'D': /* DSN status */
break;
case 'R': /* SMTP status */
break;
case 'U': /* last usage time */
break;
case '.': /* end of file */
return 0;
default:
"%s: line %d: Unknown host status line \"%s\"",
LineNumber, buf);
return -1;
}
}
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
{
int save_errno = errno;
{
sm_dprintf("mci_store_persistent: NULL mci\n");
return;
}
return;
sm_dprintf("mci_store_persistent: Storing information for %s\n",
{
sm_dprintf("mci_store_persistent: no statfile\n");
return;
}
#if !NOFTRUNCATE
(off_t) 0);
#endif /* !NOFTRUNCATE */
mci->mci_herrno);
mci->mci_exitstat);
"D%.80s\n",
"R%.80s\n",
(long)(mci->mci_lastuse));
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
char *pathname;
{
DIR *d;
int ret;
return -1;
if (ret < 0)
{
sm_dprintf("mci_traverse: Failed to stat %s: %s\n",
return ret;
}
{
bool leftone, removedone;
char *newptr;
struct dirent *e;
char newpath[MAXPATHLEN];
ERROR "MAXPATHLEN <= MAXNAMLEN - 3"
#endif /* MAXPATHLEN <= MAXNAMLEN - 3 */
{
sm_dprintf("mci_traverse: opendir %s: %s\n",
return -1;
}
/*
** Reserve space for trailing '/', at least one
** character, and '\0'
*/
{
int save_errno = errno;
sm_dprintf("mci_traverse: path \"%s\" too long",
pathname);
(void) closedir(d);
errno = save_errno;
return -1;
}
*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;
{
if (e->d_name[0] == '.')
continue;
{
/* Skip truncated copies */
{
*newptr = '\0';
sm_dprintf("mci_traverse: path \"%s%s\" too long",
}
continue;
}
if (StopRequest)
if (ret < 0)
break;
if (ret == 1)
leftone = true;
if (!removedone && ret == 0 &&
removedone = true;
}
if (ret < 0)
break;
/*
** The following appears to be
** necessary during purges, since
** we modify the directory structure
*/
if (removedone)
rewinddir(d);
sm_dprintf("mci_traverse: path %s: ret %d removed %d left %d\n",
} while (removedone);
/* purge (or whatever) the directory proper */
if (!leftone)
{
*--newptr = '\0';
}
(void) closedir(d);
}
{
char *start;
char *scan;
char host[MAXHOSTNAMELEN];
/*
** Reconstruct the host name from the path to the
** persistent information.
*/
do
{
*(hostptr++) = '.';
start--;
if (*end == '.')
end--;
*hostptr = '\0';
/*
** Do something with the file containing the persistent
** information.
*/
}
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
char *pathname;
char *hostname;
{
static bool initflag = false;
bool locked;
/* skip directories */
return 0;
if (StopRequest)
if (!initflag)
{
initflag = true;
" -------------- Hostname --------------- How long ago ---------Results---------\n");
}
{
sm_dprintf("mci_print_persistent: cannot open %s: %s\n",
return 0;
}
{
return 0;
}
else if (mcib.mci_exitstat != 0)
{
{
char buf[80];
"Unknown mailer error %d",
}
else
}
else
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
char *pathname;
char *hostname;
{
int ret;
if (ret < 0)
{
sm_dprintf("mci_purge_persistent: Failed to stat %s: %s\n",
return ret;
}
return 1;
{
/* remove the file */
if (ret < 0)
{
if (LogLevel > 8)
"mci_purge_persistent: failed to unlink %s: %s",
sm_dprintf("mci_purge_persistent: failed to unlink %s: %s\n",
return ret;
}
}
else
{
/* remove the directory */
if (*end != '.')
return 1;
if (ret < 0)
{
sm_dprintf("mci_purge_persistent: rmdir %s: %s\n",
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
const char *host;
char *path;
int pathlen;
bool createflag;
{
int ret = 0;
int len;
char t_host[MAXHOSTNAMELEN];
#if NETINET6
#endif /* NETINET6 */
/*
** Rationality check the arguments.
*/
{
syserr("mci_generate_persistent_path: null host");
return -1;
}
{
syserr("mci_generate_persistent_path: null path");
return -1;
}
return -1;
/* make certain this is not a bracketed host number */
return -1;
if (host[0] == '[')
else
/*
** Delete any trailing dots from the hostname.
** Leave 'elem' pointing at the \0.
*/
*--elem = '\0';
/* check for bogus bracketed address */
if (host[0] == '[')
{
bool good = false;
# if NETINET6
good = true;
# endif /* NETINET6 */
# if NETINET
good = true;
# endif /* NETINET */
if (!good)
return -1;
}
/* check for what will be the final length of the path */
for (p = (char *) t_host; *p != '\0'; p++)
{
if (*p == '.')
len++;
len++;
if (p[0] == '.' && p[1] == '.')
return -1;
}
return -1;
{
{
ret = -1;
break;
}
elem--;
elem--;
*p++ = '/';
x = elem + 1;
{
if (ch == '/')
*p++ = ch;
}
*p++ = '.';
*p = '\0';
}
{
if (ret < 0)
else
}
return ret;
}