📄 daemon.c
字号:
/*
* Copyright (c) 1998-2000 Sendmail, Inc. and its suppliers.
* All rights reserved.
* Copyright (c) 1983, 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>
#ifndef lint
# ifdef DAEMON
static char id[] = "@(#)$Id: daemon.c,v 8.401.4.18 2000/09/21 21:52:16 ca Exp $ (with daemon mode)";
# else /* DAEMON */
static char id[] = "@(#)$Id: daemon.c,v 8.401.4.18 2000/09/21 21:52:16 ca Exp $ (without daemon mode)";
# endif /* DAEMON */
#endif /* ! lint */
#if defined(SOCK_STREAM) || defined(__GNU_LIBRARY__)
# define USE_SOCK_STREAM 1
#endif /* defined(SOCK_STREAM) || defined(__GNU_LIBRARY__) */
#if DAEMON || defined(USE_SOCK_STREAM)
# if NETINET || NETINET6
# include <arpa/inet.h>
# endif /* NETINET || NETINET6 */
# if NAMED_BIND
# ifndef NO_DATA
# define NO_DATA NO_ADDRESS
# endif /* ! NO_DATA */
# endif /* NAMED_BIND */
#endif /* DAEMON || defined(USE_SOCK_STREAM) */
#if DAEMON
# if STARTTLS
# include <openssl/rand.h>
# endif /* STARTTLS */
# include <sys/time.h>
# if IP_SRCROUTE && NETINET
# include <netinet/in_systm.h>
# include <netinet/ip.h>
# if HAS_IN_H
# include <netinet/in.h>
# ifndef IPOPTION
# define IPOPTION ip_opts
# define IP_LIST ip_opts
# define IP_DST ip_dst
# endif /* ! IPOPTION */
# else /* HAS_IN_H */
# include <netinet/ip_var.h>
# ifndef IPOPTION
# define IPOPTION ipoption
# define IP_LIST ipopt_list
# define IP_DST ipopt_dst
# endif /* ! IPOPTION */
# endif /* HAS_IN_H */
# endif /* IP_SRCROUTE && NETINET */
/* structure to describe a daemon */
struct daemon
{
int d_socket; /* fd for socket */
SOCKADDR d_addr; /* socket for incoming */
u_short d_port; /* port number */
int d_listenqueue; /* size of listen queue */
int d_tcprcvbufsize; /* size of TCP receive buffer */
int d_tcpsndbufsize; /* size of TCP send buffer */
time_t d_refuse_connections_until;
bool d_firsttime;
int d_socksize;
BITMAP256 d_flags; /* flags; see sendmail.h */
char *d_mflags; /* flags for use in macro */
char *d_name; /* user-supplied name */
};
typedef struct daemon DAEMON_T;
static void connecttimeout __P((void));
static int opendaemonsocket __P((struct daemon *, bool));
static u_short setupdaemon __P((SOCKADDR *));
/*
** DAEMON.C -- routines to use when running as a daemon.
**
** This entire file is highly dependent on the 4.2 BSD
** interprocess communication primitives. No attempt has
** been made to make this file portable to Version 7,
** Version 6, MPX files, etc. If you should try such a
** thing yourself, I recommend chucking the entire file
** and starting from scratch. Basic semantics are:
**
** getrequests(e)
** Opens a port and initiates a connection.
** Returns in a child. Must set InChannel and
** OutChannel appropriately.
** clrdaemon()
** Close any open files associated with getting
** the connection; this is used when running the queue,
** etc., to avoid having extra file descriptors during
** the queue run and to avoid confusing the network
** code (if it cares).
** makeconnection(host, port, outfile, infile, e)
** Make a connection to the named host on the given
** port. Set *outfile and *infile to the files
** appropriate for communication. Returns zero on
** success, else an exit status describing the
** error.
** host_map_lookup(map, hbuf, avp, pstat)
** Convert the entry in hbuf into a canonical form.
*/
static DAEMON_T Daemons[MAXDAEMONS];
static int ndaemons = 0; /* actual number of daemons */
/* options for client */
static int TcpRcvBufferSize = 0; /* size of TCP receive buffer */
static int TcpSndBufferSize = 0; /* size of TCP send buffer */
/*
** GETREQUESTS -- open mail IPC port and get requests.
**
** Parameters:
** e -- the current envelope.
**
** Returns:
** pointer to flags.
**
** Side Effects:
** Waits until some interesting activity occurs. When
** it does, a child is created to process it, and the
** parent waits for completion. Return from this
** routine is always in the child. The file pointers
** "InChannel" and "OutChannel" should be set to point
** to the communication channel.
*/
BITMAP256 *
getrequests(e)
ENVELOPE *e;
{
int t;
time_t last_disk_space_check = 0;
int idx, curdaemon = -1;
int i, olddaemon = 0;
# if XDEBUG
bool j_has_dot;
# endif /* XDEBUG */
char status[MAXLINE];
SOCKADDR sa;
SOCKADDR_LEN_T len = sizeof sa;
# if NETUNIX
extern int ControlSocket;
# endif /* NETUNIX */
extern ENVELOPE BlankEnvelope;
#define D(x,idx) x[idx]
for (idx = 0; idx < ndaemons; idx++)
{
Daemons[idx].d_port = setupdaemon(&(Daemons[idx].d_addr));
Daemons[idx].d_firsttime = TRUE;
Daemons[idx].d_refuse_connections_until = (time_t) 0;
}
/*
** Try to actually open the connection.
*/
if (tTd(15, 1))
{
for (idx = 0; idx < ndaemons; idx++)
dprintf("getrequests: daemon %s: port %d\n",
Daemons[idx].d_name,
ntohs(Daemons[idx].d_port));
}
/* get a socket for the SMTP connection */
for (idx = 0; idx < ndaemons; idx++)
Daemons[idx].d_socksize = opendaemonsocket(&Daemons[idx], TRUE);
if (opencontrolsocket() < 0)
sm_syslog(LOG_WARNING, NOQID,
"daemon could not open control socket %s: %s",
ControlSocketName, errstring(errno));
(void) setsignal(SIGCHLD, reapchild);
/* write the pid to file */
log_sendmail_pid(e);
# if XDEBUG
{
char jbuf[MAXHOSTNAMELEN];
expand("\201j", jbuf, sizeof jbuf, e);
j_has_dot = strchr(jbuf, '.') != NULL;
}
# endif /* XDEBUG */
/* Add parent process as first item */
proc_list_add(getpid(), "Sendmail daemon", PROC_DAEMON);
if (tTd(15, 1))
{
for (idx = 0; idx < ndaemons; idx++)
dprintf("getrequests: daemon %s: %d\n",
Daemons[idx].d_name,
Daemons[idx].d_socket);
}
for (;;)
{
register pid_t pid;
auto SOCKADDR_LEN_T lotherend;
bool timedout = FALSE;
bool control = FALSE;
int save_errno;
int pipefd[2];
# if STARTTLS
long seed;
time_t timenow;
# endif /* STARTTLS */
/* see if we are rejecting connections */
(void) blocksignal(SIGALRM);
for (idx = 0; idx < ndaemons; idx++)
{
if (curtime() < Daemons[idx].d_refuse_connections_until)
continue;
if (refuseconnections(Daemons[idx].d_name, e, idx))
{
if (Daemons[idx].d_socket >= 0)
{
/* close socket so peer fails quickly */
(void) close(Daemons[idx].d_socket);
Daemons[idx].d_socket = -1;
}
/* refuse connections for next 15 seconds */
Daemons[idx].d_refuse_connections_until = curtime() + 15;
}
else if (Daemons[idx].d_socket < 0 ||
Daemons[idx].d_firsttime)
{
if (!Daemons[idx].d_firsttime && LogLevel >= 9)
sm_syslog(LOG_INFO, NOQID,
"accepting connections again for daemon %s",
Daemons[idx].d_name);
/* arrange to (re)open the socket if needed */
(void) opendaemonsocket(&Daemons[idx], FALSE);
Daemons[idx].d_firsttime = FALSE;
}
}
if (curtime() >= last_disk_space_check)
{
if (!enoughdiskspace(MinBlocksFree + 1, FALSE))
{
if (!bitnset(D_ETRNONLY, Daemons[idx].d_flags))
{
/* log only if not logged before */
if (LogLevel >= 9)
sm_syslog(LOG_INFO, NOQID,
"rejecting new messages: min free: %ld",
MinBlocksFree);
sm_setproctitle(TRUE, e,
"rejecting new messages: min free: %ld",
MinBlocksFree);
setbitn(D_ETRNONLY, Daemons[idx].d_flags);
}
}
else if (bitnset(D_ETRNONLY, Daemons[idx].d_flags))
{
/* log only if not logged before */
if (LogLevel >= 9)
sm_syslog(LOG_INFO, NOQID,
"accepting new messages (again)");
/* title will be set below */
clrbitn(D_ETRNONLY, Daemons[idx].d_flags);
}
/* only check disk space once a minute */
last_disk_space_check = curtime() + 60;
}
# if XDEBUG
/* check for disaster */
{
char jbuf[MAXHOSTNAMELEN];
expand("\201j", jbuf, sizeof jbuf, e);
if (!wordinclass(jbuf, 'w'))
{
dumpstate("daemon lost $j");
sm_syslog(LOG_ALERT, NOQID,
"daemon process doesn't have $j in $=w; see syslog");
abort();
}
else if (j_has_dot && strchr(jbuf, '.') == NULL)
{
dumpstate("daemon $j lost dot");
sm_syslog(LOG_ALERT, NOQID,
"daemon process $j lost dot; see syslog");
abort();
}
}
# endif /* XDEBUG */
# if 0
/*
** Andrew Sun <asun@ieps-sun.ml.com> claims that this will
** fix the SVr4 problem. But it seems to have gone away,
** so is it worth doing this?
*/
if (DaemonSocket >= 0 &&
SetNonBlocking(DaemonSocket, FALSE) < 0)
log an error here;
# endif /* 0 */
(void) releasesignal(SIGALRM);
for (;;)
{
int highest = -1;
fd_set readfds;
struct timeval timeout;
FD_ZERO(&readfds);
for (idx = 0; idx < ndaemons; idx++)
{
/* wait for a connection */
if (Daemons[idx].d_socket >= 0)
{
if (!bitnset(D_ETRNONLY, Daemons[idx].d_flags))
{
sm_setproctitle(TRUE, e,
"accepting connections");
}
if (Daemons[idx].d_socket > highest)
highest = Daemons[idx].d_socket;
FD_SET((u_int)Daemons[idx].d_socket, &readfds);
}
}
# if NETUNIX
if (ControlSocket >= 0)
{
if (ControlSocket > highest)
highest = ControlSocket;
FD_SET(ControlSocket, &readfds);
}
# endif /* NETUNIX */
/*
** if one socket is closed, set the timeout
** to 5 seconds (so it might get reopened soon),
** otherwise (all sockets open) 60.
*/
idx = 0;
while (idx < ndaemons && Daemons[idx].d_socket >= 0)
idx++;
if (idx < ndaemons)
timeout.tv_sec = 5;
else
timeout.tv_sec = 60;
timeout.tv_usec = 0;
t = select(highest + 1, FDSET_CAST &readfds,
NULL, NULL, &timeout);
if (DoQueueRun)
(void) runqueue(TRUE, FALSE);
if (t <= 0)
{
timedout = TRUE;
break;
}
control = FALSE;
errno = 0;
curdaemon = -1;
/* look "round-robin" for an active socket */
if ((idx = olddaemon + 1) >= ndaemons)
idx = 0;
for (i = 0; i < ndaemons; i++)
{
if (Daemons[idx].d_socket >= 0 &&
FD_ISSET(Daemons[idx].d_socket, &readfds))
{
lotherend = Daemons[idx].d_socksize;
t = accept(Daemons[idx].d_socket,
(struct sockaddr *)&RealHostAddr,
&lotherend);
olddaemon = curdaemon = idx;
break;
}
if (++idx >= ndaemons)
idx = 0;
}
# if NETUNIX
if (curdaemon == -1 && ControlSocket >= 0 &&
FD_ISSET(ControlSocket, &readfds))
{
struct sockaddr_un sa_un;
lotherend = sizeof sa_un;
t = accept(ControlSocket,
(struct sockaddr *)&sa_un,
&lotherend);
control = TRUE;
}
# endif /* NETUNIX */
if (t >= 0 || errno != EINTR)
break;
}
if (timedout)
{
timedout = FALSE;
continue;
}
save_errno = errno;
(void) blocksignal(SIGALRM);
if (t < 0)
{
errno = save_errno;
syserr("getrequests: accept");
/* arrange to re-open the socket next time around */
(void) close(Daemons[curdaemon].d_socket);
Daemons[curdaemon].d_socket = -1;
# if SO_REUSEADDR_IS_BROKEN
/*
** Give time for bound socket to be released.
** This creates a denial-of-service if you can
** force accept() to fail on affected systems.
*/
Daemons[curdaemon].d_refuse_connections_until = curtime() + 15;
# endif /* SO_REUSEADDR_IS_BROKEN */
continue;
}
if (!control)
{
/* set some daemon related macros */
switch (Daemons[curdaemon].d_addr.sa.sa_family)
{
case AF_UNSPEC:
define(macid("{daemon_family}", NULL),
"unspec", &BlankEnvelope);
break;
# if NETINET
case AF_INET:
define(macid("{daemon_family}", NULL),
"inet", &BlankEnvelope);
break;
# endif /* NETINET */
# if NETINET6
case AF_INET6:
define(macid("{daemon_family}", NULL),
"inet6", &BlankEnvelope);
break;
# endif /* NETINET6 */
# if NETISO
case AF_ISO:
define(macid("{daemon_family}", NULL),
"iso", &BlankEnvelope);
break;
# endif /* NETISO */
# if NETNS
case AF_NS:
define(macid("{daemon_family}", NULL),
"ns", &BlankEnvelope);
break;
# endif /* NETNS */
# if NETX25
case AF_CCITT:
define(macid("{daemon_family}", NULL),
"x.25", &BlankEnvelope);
break;
# endif /* NETX25 */
}
define(macid("{daemon_name}", NULL),
Daemons[curdaemon].d_name, &BlankEnvelope);
if (Daemons[curdaemon].d_mflags != NULL)
define(macid("{daemon_flags}", NULL),
Daemons[curdaemon].d_mflags,
&BlankEnvelope);
else
define(macid("{daemon_flags}", NULL),
"", &BlankEnvelope);
}
/*
** Create a subprocess to process the mail.
*/
if (tTd(15, 2))
dprintf("getrequests: forking (fd = %d)\n", t);
/*
** advance state of PRNG
** this is necessary because otherwise all child processes
** will produce the same PRN sequence and hence the selection
** of a queue directory (and other things, e.g., MX selection)
** are not "really" random.
*/
# if STARTTLS
seed = get_random();
RAND_seed((void *) &last_disk_space_check,
sizeof last_disk_space_check);
timenow = curtime();
RAND_seed((void *) &timenow, sizeof timenow);
RAND_seed((void *) &seed, sizeof seed);
# else /* STARTTLS */
(void) get_random();
# endif /* STARTTLS */
/*
** Create a pipe to keep the child from writing to the
** socket until after the parent has closed it. Otherwise
** the parent may hang if the child has closed it first.
*/
if (pipe(pipefd) < 0)
pipefd[0] = pipefd[1] = -1;
(void) blocksignal(SIGCHLD);
pid = fork();
if (pid < 0)
{
syserr("daemon: cannot fork");
if (pipefd[0] != -1)
{
(void) close(pipefd[0]);
(void) close(pipefd[1]);
}
(void) releasesignal(SIGCHLD);
(void) sleep(10);
(void) close(t);
continue;
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -