mirror of
https://github.com/opnsense/src.git
synced 2026-02-14 08:13:38 -05:00
4491 lines
97 KiB
C
4491 lines
97 KiB
C
/*
|
|
* Copyright (c) 1998-2007, 2009 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>
|
|
#include "map.h"
|
|
|
|
SM_RCSID("@(#)$Id: daemon.c,v 8.683 2009/12/18 01:12:40 ca Exp $")
|
|
|
|
#if defined(SOCK_STREAM) || defined(__GNU_LIBRARY__)
|
|
# define USE_SOCK_STREAM 1
|
|
#endif /* defined(SOCK_STREAM) || defined(__GNU_LIBRARY__) */
|
|
|
|
#if 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 /* defined(USE_SOCK_STREAM) */
|
|
|
|
#if STARTTLS
|
|
# include <openssl/rand.h>
|
|
#endif /* STARTTLS */
|
|
|
|
#include <sm/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 */
|
|
|
|
#include <sm/fdset.h>
|
|
|
|
#define DAEMON_C 1
|
|
#include <daemon.h>
|
|
|
|
static void connecttimeout __P((int));
|
|
static int opendaemonsocket __P((DAEMON_T *, bool));
|
|
static unsigned short setupdaemon __P((SOCKADDR *));
|
|
static void getrequests_checkdiskspace __P((ENVELOPE *e));
|
|
static void setsockaddroptions __P((char *, DAEMON_T *));
|
|
static void printdaemonflags __P((DAEMON_T *));
|
|
static int addr_family __P((char *));
|
|
static int addrcmp __P((struct hostent *, char *, SOCKADDR *));
|
|
static void authtimeout __P((int));
|
|
|
|
/*
|
|
** 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, mci, e, enough)
|
|
** Make a connection to the named host on the given
|
|
** port. 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 int NDaemons = 0; /* actual number of daemons */
|
|
|
|
static time_t NextDiskSpaceCheck = 0;
|
|
|
|
/*
|
|
** 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.
|
|
** May restart persistent queue runners if they have ended
|
|
** for some reason.
|
|
*/
|
|
|
|
BITMAP256 *
|
|
getrequests(e)
|
|
ENVELOPE *e;
|
|
{
|
|
int t;
|
|
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 _FFR_QUEUE_RUN_PARANOIA
|
|
time_t lastrun;
|
|
#endif /* _FFR_QUEUE_RUN_PARANOIA */
|
|
# if NETUNIX
|
|
extern int ControlSocket;
|
|
# endif /* NETUNIX */
|
|
extern ENVELOPE BlankEnvelope;
|
|
|
|
|
|
/* initialize data for function that generates queue ids */
|
|
init_qid_alg();
|
|
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++)
|
|
{
|
|
sm_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, sm_errstring(errno));
|
|
|
|
/* If there are any queue runners released reapchild() co-ord's */
|
|
(void) sm_signal(SIGCHLD, reapchild);
|
|
|
|
/* write the pid to file, command line args to syslog */
|
|
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(CurrentPid, "Sendmail daemon", PROC_DAEMON, 0, -1, NULL);
|
|
|
|
if (tTd(15, 1))
|
|
{
|
|
for (idx = 0; idx < NDaemons; idx++)
|
|
sm_dprintf("getrequests: daemon %s: socket %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];
|
|
time_t now;
|
|
#if STARTTLS
|
|
long seed;
|
|
#endif /* STARTTLS */
|
|
|
|
/* see if we are rejecting connections */
|
|
(void) sm_blocksignal(SIGALRM);
|
|
CHECK_RESTART;
|
|
|
|
for (idx = 0; idx < NDaemons; idx++)
|
|
{
|
|
/*
|
|
** XXX do this call outside the loop?
|
|
** no: refuse_connections may sleep().
|
|
*/
|
|
|
|
now = curtime();
|
|
if (now < Daemons[idx].d_refuse_connections_until)
|
|
continue;
|
|
if (bitnset(D_DISABLE, Daemons[idx].d_flags))
|
|
continue;
|
|
if (refuseconnections(e, idx, curdaemon == 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 = now + 15;
|
|
}
|
|
else if (Daemons[idx].d_socket < 0 ||
|
|
Daemons[idx].d_firsttime)
|
|
{
|
|
if (!Daemons[idx].d_firsttime && LogLevel > 8)
|
|
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;
|
|
}
|
|
}
|
|
|
|
/* May have been sleeping above, check again */
|
|
CHECK_RESTART;
|
|
|
|
getrequests_checkdiskspace(e);
|
|
|
|
#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) sm_releasesignal(SIGALRM);
|
|
|
|
for (;;)
|
|
{
|
|
bool setproc = false;
|
|
int highest = -1;
|
|
fd_set readfds;
|
|
struct timeval timeout;
|
|
|
|
CHECK_RESTART;
|
|
FD_ZERO(&readfds);
|
|
for (idx = 0; idx < NDaemons; idx++)
|
|
{
|
|
/* wait for a connection */
|
|
if (Daemons[idx].d_socket >= 0)
|
|
{
|
|
if (!setproc &&
|
|
!bitnset(D_ETRNONLY,
|
|
Daemons[idx].d_flags))
|
|
{
|
|
sm_setproctitle(true, e,
|
|
"accepting connections");
|
|
setproc = true;
|
|
}
|
|
if (Daemons[idx].d_socket > highest)
|
|
highest = Daemons[idx].d_socket;
|
|
SM_FD_SET(Daemons[idx].d_socket,
|
|
&readfds);
|
|
}
|
|
}
|
|
|
|
#if NETUNIX
|
|
if (ControlSocket >= 0)
|
|
{
|
|
if (ControlSocket > highest)
|
|
highest = ControlSocket;
|
|
SM_FD_SET(ControlSocket, &readfds);
|
|
}
|
|
#endif /* NETUNIX */
|
|
|
|
timeout.tv_sec = 5;
|
|
timeout.tv_usec = 0;
|
|
|
|
t = select(highest + 1, FDSET_CAST &readfds,
|
|
NULL, NULL, &timeout);
|
|
|
|
/* Did someone signal while waiting? */
|
|
CHECK_RESTART;
|
|
|
|
curdaemon = -1;
|
|
if (doqueuerun())
|
|
{
|
|
(void) runqueue(true, false, false, false);
|
|
#if _FFR_QUEUE_RUN_PARANOIA
|
|
lastrun = now;
|
|
#endif /* _FFR_QUEUE_RUN_PARANOIA */
|
|
}
|
|
#if _FFR_QUEUE_RUN_PARANOIA
|
|
else if (CheckQueueRunners > 0 && QueueIntvl > 0 &&
|
|
lastrun + QueueIntvl + CheckQueueRunners < now)
|
|
{
|
|
|
|
/*
|
|
** set lastrun unconditionally to avoid
|
|
** calling checkqueuerunner() all the time.
|
|
** That's also why we currently ignore the
|
|
** result of the function call.
|
|
*/
|
|
|
|
(void) checkqueuerunner();
|
|
lastrun = now;
|
|
}
|
|
#endif /* _FFR_QUEUE_RUN_PARANOIA */
|
|
|
|
if (t <= 0)
|
|
{
|
|
timedout = true;
|
|
break;
|
|
}
|
|
|
|
control = false;
|
|
errno = 0;
|
|
|
|
/* 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 &&
|
|
SM_FD_ISSET(Daemons[idx].d_socket,
|
|
&readfds))
|
|
{
|
|
lotherend = Daemons[idx].d_socksize;
|
|
memset(&RealHostAddr, '\0',
|
|
sizeof(RealHostAddr));
|
|
t = accept(Daemons[idx].d_socket,
|
|
(struct sockaddr *)&RealHostAddr,
|
|
&lotherend);
|
|
|
|
/*
|
|
** If remote side closes before
|
|
** accept() finishes, sockaddr
|
|
** might not be fully filled in.
|
|
*/
|
|
|
|
if (t >= 0 &&
|
|
(lotherend == 0 ||
|
|
# ifdef BSD4_4_SOCKADDR
|
|
RealHostAddr.sa.sa_len == 0 ||
|
|
# endif /* BSD4_4_SOCKADDR */
|
|
RealHostAddr.sa.sa_family != Daemons[idx].d_addr.sa.sa_family))
|
|
{
|
|
(void) close(t);
|
|
t = -1;
|
|
errno = EINVAL;
|
|
}
|
|
olddaemon = curdaemon = idx;
|
|
break;
|
|
}
|
|
if (++idx >= NDaemons)
|
|
idx = 0;
|
|
}
|
|
#if NETUNIX
|
|
if (curdaemon == -1 && ControlSocket >= 0 &&
|
|
SM_FD_ISSET(ControlSocket, &readfds))
|
|
{
|
|
struct sockaddr_un sa_un;
|
|
|
|
lotherend = sizeof(sa_un);
|
|
memset(&sa_un, '\0', sizeof(sa_un));
|
|
t = accept(ControlSocket,
|
|
(struct sockaddr *)&sa_un,
|
|
&lotherend);
|
|
|
|
/*
|
|
** If remote side closes before
|
|
** accept() finishes, sockaddr
|
|
** might not be fully filled in.
|
|
*/
|
|
|
|
if (t >= 0 &&
|
|
(lotherend == 0 ||
|
|
# ifdef BSD4_4_SOCKADDR
|
|
sa_un.sun_len == 0 ||
|
|
# endif /* BSD4_4_SOCKADDR */
|
|
sa_un.sun_family != AF_UNIX))
|
|
{
|
|
(void) close(t);
|
|
t = -1;
|
|
errno = EINVAL;
|
|
}
|
|
if (t >= 0)
|
|
control = true;
|
|
}
|
|
#else /* NETUNIX */
|
|
if (curdaemon == -1)
|
|
{
|
|
/* No daemon to service */
|
|
continue;
|
|
}
|
|
#endif /* NETUNIX */
|
|
if (t >= 0 || errno != EINTR)
|
|
break;
|
|
}
|
|
if (timedout)
|
|
{
|
|
timedout = false;
|
|
continue;
|
|
}
|
|
save_errno = errno;
|
|
(void) sm_blocksignal(SIGALRM);
|
|
if (t < 0)
|
|
{
|
|
errno = save_errno;
|
|
|
|
/* let's ignore these temporary errors */
|
|
if (save_errno == EINTR
|
|
#ifdef EAGAIN
|
|
|| save_errno == EAGAIN
|
|
#endif /* EAGAIN */
|
|
#ifdef ECONNABORTED
|
|
|| save_errno == ECONNABORTED
|
|
#endif /* ECONNABORTED */
|
|
#ifdef EWOULDBLOCK
|
|
|| save_errno == EWOULDBLOCK
|
|
#endif /* EWOULDBLOCK */
|
|
)
|
|
continue;
|
|
|
|
syserr("getrequests: accept");
|
|
|
|
if (curdaemon >= 0)
|
|
{
|
|
/* arrange to re-open 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:
|
|
macdefine(&BlankEnvelope.e_macro, A_PERM,
|
|
macid("{daemon_family}"), "unspec");
|
|
break;
|
|
#if _FFR_DAEMON_NETUNIX
|
|
# if NETUNIX
|
|
case AF_UNIX:
|
|
macdefine(&BlankEnvelope.e_macro, A_PERM,
|
|
macid("{daemon_family}"), "local");
|
|
break;
|
|
# endif /* NETUNIX */
|
|
#endif /* _FFR_DAEMON_NETUNIX */
|
|
#if NETINET
|
|
case AF_INET:
|
|
macdefine(&BlankEnvelope.e_macro, A_PERM,
|
|
macid("{daemon_family}"), "inet");
|
|
break;
|
|
#endif /* NETINET */
|
|
#if NETINET6
|
|
case AF_INET6:
|
|
macdefine(&BlankEnvelope.e_macro, A_PERM,
|
|
macid("{daemon_family}"), "inet6");
|
|
break;
|
|
#endif /* NETINET6 */
|
|
#if NETISO
|
|
case AF_ISO:
|
|
macdefine(&BlankEnvelope.e_macro, A_PERM,
|
|
macid("{daemon_family}"), "iso");
|
|
break;
|
|
#endif /* NETISO */
|
|
#if NETNS
|
|
case AF_NS:
|
|
macdefine(&BlankEnvelope.e_macro, A_PERM,
|
|
macid("{daemon_family}"), "ns");
|
|
break;
|
|
#endif /* NETNS */
|
|
#if NETX25
|
|
case AF_CCITT:
|
|
macdefine(&BlankEnvelope.e_macro, A_PERM,
|
|
macid("{daemon_family}"), "x.25");
|
|
break;
|
|
#endif /* NETX25 */
|
|
}
|
|
macdefine(&BlankEnvelope.e_macro, A_PERM,
|
|
macid("{daemon_name}"),
|
|
Daemons[curdaemon].d_name);
|
|
if (Daemons[curdaemon].d_mflags != NULL)
|
|
macdefine(&BlankEnvelope.e_macro, A_PERM,
|
|
macid("{daemon_flags}"),
|
|
Daemons[curdaemon].d_mflags);
|
|
else
|
|
macdefine(&BlankEnvelope.e_macro, A_PERM,
|
|
macid("{daemon_flags}"), "");
|
|
}
|
|
|
|
/*
|
|
** If connection rate is exceeded here, connection shall be
|
|
** refused later by a new call after fork() by the
|
|
** validate_connection() function. Closing the connection
|
|
** at this point violates RFC 2821.
|
|
** Do NOT remove this call, its side effects are needed.
|
|
*/
|
|
|
|
connection_rate_check(&RealHostAddr, NULL);
|
|
|
|
/*
|
|
** Create a subprocess to process the mail.
|
|
*/
|
|
|
|
if (tTd(15, 2))
|
|
sm_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
|
|
/* XXX get some better "random" data? */
|
|
seed = get_random();
|
|
RAND_seed((void *) &NextDiskSpaceCheck,
|
|
sizeof(NextDiskSpaceCheck));
|
|
RAND_seed((void *) &now, sizeof(now));
|
|
RAND_seed((void *) &seed, sizeof(seed));
|
|
#else /* STARTTLS */
|
|
(void) get_random();
|
|
#endif /* STARTTLS */
|
|
|
|
#if NAMED_BIND
|
|
/*
|
|
** Update MX records for FallbackMX.
|
|
** Let's hope this is fast otherwise we screw up the
|
|
** response time.
|
|
*/
|
|
|
|
if (FallbackMX != NULL)
|
|
(void) getfallbackmxrr(FallbackMX);
|
|
#endif /* NAMED_BIND */
|
|
|
|
if (tTd(93, 100))
|
|
{
|
|
/* don't fork, handle connection in this process */
|
|
pid = 0;
|
|
pipefd[0] = pipefd[1] = -1;
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
** 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) sm_blocksignal(SIGCHLD);
|
|
pid = fork();
|
|
if (pid < 0)
|
|
{
|
|
syserr("daemon: cannot fork");
|
|
if (pipefd[0] != -1)
|
|
{
|
|
(void) close(pipefd[0]);
|
|
(void) close(pipefd[1]);
|
|
}
|
|
(void) sm_releasesignal(SIGCHLD);
|
|
(void) sleep(10);
|
|
(void) close(t);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (pid == 0)
|
|
{
|
|
char *p;
|
|
SM_FILE_T *inchannel, *outchannel = NULL;
|
|
|
|
/*
|
|
** CHILD -- return to caller.
|
|
** Collect verified idea of sending host.
|
|
** Verify calling user id if possible here.
|
|
*/
|
|
|
|
/* Reset global flags */
|
|
RestartRequest = NULL;
|
|
RestartWorkGroup = false;
|
|
ShutdownRequest = NULL;
|
|
PendingSignal = 0;
|
|
CurrentPid = getpid();
|
|
close_sendmail_pid();
|
|
|
|
(void) sm_releasesignal(SIGALRM);
|
|
(void) sm_releasesignal(SIGCHLD);
|
|
(void) sm_signal(SIGCHLD, SIG_DFL);
|
|
(void) sm_signal(SIGHUP, SIG_DFL);
|
|
(void) sm_signal(SIGTERM, intsig);
|
|
|
|
/* turn on profiling */
|
|
/* SM_PROF(0); */
|
|
|
|
/*
|
|
** Initialize exception stack and default exception
|
|
** handler for child process.
|
|
*/
|
|
|
|
sm_exc_newthread(fatal_error);
|
|
|
|
if (!control)
|
|
{
|
|
macdefine(&BlankEnvelope.e_macro, A_TEMP,
|
|
macid("{daemon_addr}"),
|
|
anynet_ntoa(&Daemons[curdaemon].d_addr));
|
|
(void) sm_snprintf(status, sizeof(status), "%d",
|
|
ntohs(Daemons[curdaemon].d_port));
|
|
macdefine(&BlankEnvelope.e_macro, A_TEMP,
|
|
macid("{daemon_port}"), status);
|
|
}
|
|
|
|
for (idx = 0; idx < NDaemons; idx++)
|
|
{
|
|
if (Daemons[idx].d_socket >= 0)
|
|
(void) close(Daemons[idx].d_socket);
|
|
Daemons[idx].d_socket = -1;
|
|
}
|
|
clrcontrol();
|
|
|
|
/* Avoid SMTP daemon actions if control command */
|
|
if (control)
|
|
{
|
|
/* Add control socket process */
|
|
proc_list_add(CurrentPid,
|
|
"console socket child",
|
|
PROC_CONTROL_CHILD, 0, -1, NULL);
|
|
}
|
|
else
|
|
{
|
|
proc_list_clear();
|
|
|
|
/* clean up background delivery children */
|
|
(void) sm_signal(SIGCHLD, reapchild);
|
|
|
|
/* Add parent process as first child item */
|
|
proc_list_add(CurrentPid, "daemon child",
|
|
PROC_DAEMON_CHILD, 0, -1, NULL);
|
|
/* don't schedule queue runs if ETRN */
|
|
QueueIntvl = 0;
|
|
|
|
/*
|
|
** Hack: override global variables if
|
|
** the corresponding DaemonPortOption
|
|
** is set.
|
|
*/
|
|
#if _FFR_SS_PER_DAEMON
|
|
if (Daemons[curdaemon].d_supersafe !=
|
|
DPO_NOTSET)
|
|
SuperSafe = Daemons[curdaemon].
|
|
d_supersafe;
|
|
#endif /* _FFR_SS_PER_DAEMON */
|
|
if (Daemons[curdaemon].d_dm != DM_NOTSET)
|
|
set_delivery_mode(
|
|
Daemons[curdaemon].d_dm, e);
|
|
|
|
if (Daemons[curdaemon].d_refuseLA !=
|
|
DPO_NOTSET)
|
|
RefuseLA = Daemons[curdaemon].
|
|
d_refuseLA;
|
|
if (Daemons[curdaemon].d_queueLA != DPO_NOTSET)
|
|
QueueLA = Daemons[curdaemon].d_queueLA;
|
|
if (Daemons[curdaemon].d_delayLA != DPO_NOTSET)
|
|
DelayLA = Daemons[curdaemon].d_delayLA;
|
|
if (Daemons[curdaemon].d_maxchildren !=
|
|
DPO_NOTSET)
|
|
MaxChildren = Daemons[curdaemon].
|
|
d_maxchildren;
|
|
|
|
sm_setproctitle(true, e, "startup with %s",
|
|
anynet_ntoa(&RealHostAddr));
|
|
}
|
|
|
|
if (pipefd[0] != -1)
|
|
{
|
|
auto char c;
|
|
|
|
/*
|
|
** Wait for the parent to close the write end
|
|
** of the pipe, which we will see as an EOF.
|
|
** This guarantees that we won't write to the
|
|
** socket until after the parent has closed
|
|
** the pipe.
|
|
*/
|
|
|
|
/* close the write end of the pipe */
|
|
(void) close(pipefd[1]);
|
|
|
|
/* we shouldn't be interrupted, but ... */
|
|
while (read(pipefd[0], &c, 1) < 0 &&
|
|
errno == EINTR)
|
|
continue;
|
|
(void) close(pipefd[0]);
|
|
}
|
|
|
|
/* control socket processing */
|
|
if (control)
|
|
{
|
|
control_command(t, e);
|
|
/* NOTREACHED */
|
|
exit(EX_SOFTWARE);
|
|
}
|
|
|
|
/* determine host name */
|
|
p = hostnamebyanyaddr(&RealHostAddr);
|
|
if (strlen(p) > MAXNAME) /* XXX - 1 ? */
|
|
p[MAXNAME] = '\0';
|
|
RealHostName = newstr(p);
|
|
if (RealHostName[0] == '[')
|
|
{
|
|
macdefine(&BlankEnvelope.e_macro, A_PERM,
|
|
macid("{client_resolve}"),
|
|
h_errno == TRY_AGAIN ? "TEMP" : "FAIL");
|
|
}
|
|
else
|
|
{
|
|
macdefine(&BlankEnvelope.e_macro, A_PERM,
|
|
macid("{client_resolve}"), "OK");
|
|
}
|
|
sm_setproctitle(true, e, "startup with %s", p);
|
|
markstats(e, NULL, STATS_CONNECT);
|
|
|
|
if ((inchannel = sm_io_open(SmFtStdiofd,
|
|
SM_TIME_DEFAULT,
|
|
(void *) &t,
|
|
SM_IO_RDONLY_B,
|
|
NULL)) == NULL ||
|
|
(t = dup(t)) < 0 ||
|
|
(outchannel = sm_io_open(SmFtStdiofd,
|
|
SM_TIME_DEFAULT,
|
|
(void *) &t,
|
|
SM_IO_WRONLY_B,
|
|
NULL)) == NULL)
|
|
{
|
|
syserr("cannot open SMTP server channel, fd=%d",
|
|
t);
|
|
finis(false, true, EX_OK);
|
|
}
|
|
sm_io_automode(inchannel, outchannel);
|
|
|
|
InChannel = inchannel;
|
|
OutChannel = outchannel;
|
|
DisConnected = false;
|
|
|
|
#if XLA
|
|
if (!xla_host_ok(RealHostName))
|
|
{
|
|
message("421 4.4.5 Too many SMTP sessions for this host");
|
|
finis(false, true, EX_OK);
|
|
}
|
|
#endif /* XLA */
|
|
/* find out name for interface of connection */
|
|
if (getsockname(sm_io_getinfo(InChannel, SM_IO_WHAT_FD,
|
|
NULL), &sa.sa, &len) == 0)
|
|
{
|
|
p = hostnamebyanyaddr(&sa);
|
|
if (tTd(15, 9))
|
|
sm_dprintf("getreq: got name %s\n", p);
|
|
macdefine(&BlankEnvelope.e_macro, A_TEMP,
|
|
macid("{if_name}"), p);
|
|
|
|
/*
|
|
** Do this only if it is not the loopback
|
|
** interface.
|
|
*/
|
|
|
|
if (!isloopback(sa))
|
|
{
|
|
char *addr;
|
|
char family[5];
|
|
|
|
addr = anynet_ntoa(&sa);
|
|
(void) sm_snprintf(family,
|
|
sizeof(family),
|
|
"%d", sa.sa.sa_family);
|
|
macdefine(&BlankEnvelope.e_macro,
|
|
A_TEMP,
|
|
macid("{if_addr}"), addr);
|
|
macdefine(&BlankEnvelope.e_macro,
|
|
A_TEMP,
|
|
macid("{if_family}"), family);
|
|
if (tTd(15, 7))
|
|
sm_dprintf("getreq: got addr %s and family %s\n",
|
|
addr, family);
|
|
}
|
|
else
|
|
{
|
|
macdefine(&BlankEnvelope.e_macro,
|
|
A_PERM,
|
|
macid("{if_addr}"), NULL);
|
|
macdefine(&BlankEnvelope.e_macro,
|
|
A_PERM,
|
|
macid("{if_family}"), NULL);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (tTd(15, 7))
|
|
sm_dprintf("getreq: getsockname failed\n");
|
|
macdefine(&BlankEnvelope.e_macro, A_PERM,
|
|
macid("{if_name}"), NULL);
|
|
macdefine(&BlankEnvelope.e_macro, A_PERM,
|
|
macid("{if_addr}"), NULL);
|
|
macdefine(&BlankEnvelope.e_macro, A_PERM,
|
|
macid("{if_family}"), NULL);
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* parent -- keep track of children */
|
|
if (control)
|
|
{
|
|
(void) sm_snprintf(status, sizeof(status),
|
|
"control socket server child");
|
|
proc_list_add(pid, status, PROC_CONTROL, 0, -1, NULL);
|
|
}
|
|
else
|
|
{
|
|
(void) sm_snprintf(status, sizeof(status),
|
|
"SMTP server child for %s",
|
|
anynet_ntoa(&RealHostAddr));
|
|
proc_list_add(pid, status, PROC_DAEMON, 0, -1,
|
|
&RealHostAddr);
|
|
}
|
|
(void) sm_releasesignal(SIGCHLD);
|
|
|
|
/* close the read end of the synchronization pipe */
|
|
if (pipefd[0] != -1)
|
|
{
|
|
(void) close(pipefd[0]);
|
|
pipefd[0] = -1;
|
|
}
|
|
|
|
/* close the port so that others will hang (for a while) */
|
|
(void) close(t);
|
|
|
|
/* release the child by closing the read end of the sync pipe */
|
|
if (pipefd[1] != -1)
|
|
{
|
|
(void) close(pipefd[1]);
|
|
pipefd[1] = -1;
|
|
}
|
|
}
|
|
if (tTd(15, 2))
|
|
sm_dprintf("getreq: returning\n");
|
|
|
|
#if MILTER
|
|
/* set the filters for this daemon */
|
|
if (Daemons[curdaemon].d_inputfilterlist != NULL)
|
|
{
|
|
for (i = 0;
|
|
(i < MAXFILTERS &&
|
|
Daemons[curdaemon].d_inputfilters[i] != NULL);
|
|
i++)
|
|
{
|
|
InputFilters[i] = Daemons[curdaemon].d_inputfilters[i];
|
|
}
|
|
if (i < MAXFILTERS)
|
|
InputFilters[i] = NULL;
|
|
}
|
|
#endif /* MILTER */
|
|
return &Daemons[curdaemon].d_flags;
|
|
}
|
|
|
|
/*
|
|
** GETREQUESTS_CHECKDISKSPACE -- check available diskspace.
|
|
**
|
|
** Parameters:
|
|
** e -- envelope.
|
|
**
|
|
** Returns:
|
|
** none.
|
|
**
|
|
** Side Effects:
|
|
** Modifies Daemon flags (D_ETRNONLY) if not enough disk space.
|
|
*/
|
|
|
|
static void
|
|
getrequests_checkdiskspace(e)
|
|
ENVELOPE *e;
|
|
{
|
|
bool logged = false;
|
|
int idx;
|
|
time_t now;
|
|
|
|
now = curtime();
|
|
if (now < NextDiskSpaceCheck)
|
|
return;
|
|
|
|
/* Check if there is available disk space in all queue groups. */
|
|
if (!enoughdiskspace(0, NULL))
|
|
{
|
|
for (idx = 0; idx < NDaemons; ++idx)
|
|
{
|
|
if (bitnset(D_ETRNONLY, Daemons[idx].d_flags))
|
|
continue;
|
|
|
|
/* log only if not logged before */
|
|
if (!logged)
|
|
{
|
|
if (LogLevel > 8)
|
|
sm_syslog(LOG_INFO, NOQID,
|
|
"rejecting new messages: min free: %ld",
|
|
MinBlocksFree);
|
|
sm_setproctitle(true, e,
|
|
"rejecting new messages: min free: %ld",
|
|
MinBlocksFree);
|
|
logged = true;
|
|
}
|
|
setbitn(D_ETRNONLY, Daemons[idx].d_flags);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (idx = 0; idx < NDaemons; ++idx)
|
|
{
|
|
if (!bitnset(D_ETRNONLY, Daemons[idx].d_flags))
|
|
continue;
|
|
|
|
/* log only if not logged before */
|
|
if (!logged)
|
|
{
|
|
if (LogLevel > 8)
|
|
sm_syslog(LOG_INFO, NOQID,
|
|
"accepting new messages (again)");
|
|
logged = true;
|
|
}
|
|
|
|
/* title will be set later */
|
|
clrbitn(D_ETRNONLY, Daemons[idx].d_flags);
|
|
}
|
|
}
|
|
|
|
/* only check disk space once a minute */
|
|
NextDiskSpaceCheck = now + 60;
|
|
}
|
|
|
|
/*
|
|
** OPENDAEMONSOCKET -- open SMTP socket
|
|
**
|
|
** Deals with setting all appropriate options.
|
|
**
|
|
** Parameters:
|
|
** d -- the structure for the daemon to open.
|
|
** firsttime -- set if this is the initial open.
|
|
**
|
|
** Returns:
|
|
** Size in bytes of the daemon socket addr.
|
|
**
|
|
** Side Effects:
|
|
** Leaves DaemonSocket set to the open socket.
|
|
** Exits if the socket cannot be created.
|
|
*/
|
|
|
|
#define MAXOPENTRIES 10 /* maximum number of tries to open connection */
|
|
|
|
static int
|
|
opendaemonsocket(d, firsttime)
|
|
DAEMON_T *d;
|
|
bool firsttime;
|
|
{
|
|
int on = 1;
|
|
int fdflags;
|
|
SOCKADDR_LEN_T socksize = 0;
|
|
int ntries = 0;
|
|
int save_errno;
|
|
|
|
if (tTd(15, 2))
|
|
sm_dprintf("opendaemonsocket(%s)\n", d->d_name);
|
|
|
|
do
|
|
{
|
|
if (ntries > 0)
|
|
(void) sleep(5);
|
|
if (firsttime || d->d_socket < 0)
|
|
{
|
|
#if _FFR_DAEMON_NETUNIX
|
|
# if NETUNIX
|
|
if (d->d_addr.sa.sa_family == AF_UNIX)
|
|
{
|
|
int rval;
|
|
long sff = SFF_SAFEDIRPATH|SFF_OPENASROOT|SFF_NOLINK|SFF_ROOTOK|SFF_EXECOK|SFF_CREAT;
|
|
|
|
/* if not safe, don't use it */
|
|
rval = safefile(d->d_addr.sunix.sun_path,
|
|
RunAsUid, RunAsGid,
|
|
RunAsUserName, sff,
|
|
S_IRUSR|S_IWUSR, NULL);
|
|
if (rval != 0)
|
|
{
|
|
save_errno = errno;
|
|
syserr("opendaemonsocket: daemon %s: unsafe domain socket %s",
|
|
d->d_name,
|
|
d->d_addr.sunix.sun_path);
|
|
goto fail;
|
|
}
|
|
|
|
/* Don't try to overtake an existing socket */
|
|
(void) unlink(d->d_addr.sunix.sun_path);
|
|
}
|
|
# endif /* NETUNIX */
|
|
#endif /* _FFR_DOMAIN_NETUNIX */
|
|
d->d_socket = socket(d->d_addr.sa.sa_family,
|
|
SOCK_STREAM, 0);
|
|
if (d->d_socket < 0)
|
|
{
|
|
save_errno = errno;
|
|
syserr("opendaemonsocket: daemon %s: can't create server SMTP socket",
|
|
d->d_name);
|
|
fail:
|
|
if (bitnset(D_OPTIONAL, d->d_flags) &&
|
|
(!transienterror(save_errno) ||
|
|
ntries >= MAXOPENTRIES - 1))
|
|
{
|
|
syserr("opendaemonsocket: daemon %s: optional socket disabled",
|
|
d->d_name);
|
|
setbitn(D_DISABLE, d->d_flags);
|
|
d->d_socket = -1;
|
|
return -1;
|
|
}
|
|
severe:
|
|
if (LogLevel > 0)
|
|
sm_syslog(LOG_ALERT, NOQID,
|
|
"daemon %s: problem creating SMTP socket",
|
|
d->d_name);
|
|
d->d_socket = -1;
|
|
continue;
|
|
}
|
|
|
|
if (SM_FD_SETSIZE > 0 && d->d_socket >= SM_FD_SETSIZE)
|
|
{
|
|
save_errno = EINVAL;
|
|
syserr("opendaemonsocket: daemon %s: server SMTP socket (%d) too large",
|
|
d->d_name, d->d_socket);
|
|
goto fail;
|
|
}
|
|
|
|
/* turn on network debugging? */
|
|
if (tTd(15, 101))
|
|
(void) setsockopt(d->d_socket, SOL_SOCKET,
|
|
SO_DEBUG, (char *)&on,
|
|
sizeof(on));
|
|
|
|
(void) setsockopt(d->d_socket, SOL_SOCKET,
|
|
SO_REUSEADDR, (char *)&on, sizeof(on));
|
|
(void) setsockopt(d->d_socket, SOL_SOCKET,
|
|
SO_KEEPALIVE, (char *)&on, sizeof(on));
|
|
|
|
#ifdef SO_RCVBUF
|
|
if (d->d_tcprcvbufsize > 0)
|
|
{
|
|
if (setsockopt(d->d_socket, SOL_SOCKET,
|
|
SO_RCVBUF,
|
|
(char *) &d->d_tcprcvbufsize,
|
|
sizeof(d->d_tcprcvbufsize)) < 0)
|
|
syserr("opendaemonsocket: daemon %s: setsockopt(SO_RCVBUF)", d->d_name);
|
|
}
|
|
#endif /* SO_RCVBUF */
|
|
#ifdef SO_SNDBUF
|
|
if (d->d_tcpsndbufsize > 0)
|
|
{
|
|
if (setsockopt(d->d_socket, SOL_SOCKET,
|
|
SO_SNDBUF,
|
|
(char *) &d->d_tcpsndbufsize,
|
|
sizeof(d->d_tcpsndbufsize)) < 0)
|
|
syserr("opendaemonsocket: daemon %s: setsockopt(SO_SNDBUF)", d->d_name);
|
|
}
|
|
#endif /* SO_SNDBUF */
|
|
|
|
if ((fdflags = fcntl(d->d_socket, F_GETFD, 0)) == -1 ||
|
|
fcntl(d->d_socket, F_SETFD,
|
|
fdflags | FD_CLOEXEC) == -1)
|
|
{
|
|
save_errno = errno;
|
|
syserr("opendaemonsocket: daemon %s: failed to %s close-on-exec flag: %s",
|
|
d->d_name,
|
|
fdflags == -1 ? "get" : "set",
|
|
sm_errstring(save_errno));
|
|
(void) close(d->d_socket);
|
|
goto severe;
|
|
}
|
|
|
|
switch (d->d_addr.sa.sa_family)
|
|
{
|
|
#if _FFR_DAEMON_NETUNIX
|
|
# ifdef NETUNIX
|
|
case AF_UNIX:
|
|
socksize = sizeof(d->d_addr.sunix);
|
|
break;
|
|
# endif /* NETUNIX */
|
|
#endif /* _FFR_DAEMON_NETUNIX */
|
|
#if NETINET
|
|
case AF_INET:
|
|
socksize = sizeof(d->d_addr.sin);
|
|
break;
|
|
#endif /* NETINET */
|
|
|
|
#if NETINET6
|
|
case AF_INET6:
|
|
socksize = sizeof(d->d_addr.sin6);
|
|
break;
|
|
#endif /* NETINET6 */
|
|
|
|
#if NETISO
|
|
case AF_ISO:
|
|
socksize = sizeof(d->d_addr.siso);
|
|
break;
|
|
#endif /* NETISO */
|
|
|
|
default:
|
|
socksize = sizeof(d->d_addr);
|
|
break;
|
|
}
|
|
|
|
if (bind(d->d_socket, &d->d_addr.sa, socksize) < 0)
|
|
{
|
|
/* probably another daemon already */
|
|
save_errno = errno;
|
|
syserr("opendaemonsocket: daemon %s: cannot bind",
|
|
d->d_name);
|
|
(void) close(d->d_socket);
|
|
goto fail;
|
|
}
|
|
}
|
|
if (!firsttime &&
|
|
listen(d->d_socket, d->d_listenqueue) < 0)
|
|
{
|
|
save_errno = errno;
|
|
syserr("opendaemonsocket: daemon %s: cannot listen",
|
|
d->d_name);
|
|
(void) close(d->d_socket);
|
|
goto severe;
|
|
}
|
|
return socksize;
|
|
} while (ntries++ < MAXOPENTRIES && transienterror(save_errno));
|
|
syserr("!opendaemonsocket: daemon %s: server SMTP socket wedged: exiting",
|
|
d->d_name);
|
|
/* NOTREACHED */
|
|
return -1; /* avoid compiler warning on IRIX */
|
|
}
|
|
/*
|
|
** SETUPDAEMON -- setup socket for daemon
|
|
**
|
|
** Parameters:
|
|
** daemonaddr -- socket for daemon
|
|
**
|
|
** Returns:
|
|
** port number on which daemon should run
|
|
**
|
|
*/
|
|
|
|
static unsigned short
|
|
setupdaemon(daemonaddr)
|
|
SOCKADDR *daemonaddr;
|
|
{
|
|
unsigned short port;
|
|
|
|
/*
|
|
** Set up the address for the mailer.
|
|
*/
|
|
|
|
if (daemonaddr->sa.sa_family == AF_UNSPEC)
|
|
{
|
|
memset(daemonaddr, '\0', sizeof(*daemonaddr));
|
|
#if NETINET
|
|
daemonaddr->sa.sa_family = AF_INET;
|
|
#endif /* NETINET */
|
|
}
|
|
|
|
switch (daemonaddr->sa.sa_family)
|
|
{
|
|
#if NETINET
|
|
case AF_INET:
|
|
if (daemonaddr->sin.sin_addr.s_addr == 0)
|
|
daemonaddr->sin.sin_addr.s_addr =
|
|
LocalDaemon ? htonl(INADDR_LOOPBACK) : INADDR_ANY;
|
|
port = daemonaddr->sin.sin_port;
|
|
break;
|
|
#endif /* NETINET */
|
|
|
|
#if NETINET6
|
|
case AF_INET6:
|
|
if (IN6_IS_ADDR_UNSPECIFIED(&daemonaddr->sin6.sin6_addr))
|
|
daemonaddr->sin6.sin6_addr =
|
|
LocalDaemon ? in6addr_loopback : in6addr_any;
|
|
port = daemonaddr->sin6.sin6_port;
|
|
break;
|
|
#endif /* NETINET6 */
|
|
|
|
default:
|
|
/* unknown protocol */
|
|
port = 0;
|
|
break;
|
|
}
|
|
if (port == 0)
|
|
{
|
|
#ifdef NO_GETSERVBYNAME
|
|
port = htons(25);
|
|
#else /* NO_GETSERVBYNAME */
|
|
{
|
|
register struct servent *sp;
|
|
|
|
sp = getservbyname("smtp", "tcp");
|
|
if (sp == NULL)
|
|
{
|
|
syserr("554 5.3.5 service \"smtp\" unknown");
|
|
port = htons(25);
|
|
}
|
|
else
|
|
port = sp->s_port;
|
|
}
|
|
#endif /* NO_GETSERVBYNAME */
|
|
}
|
|
|
|
switch (daemonaddr->sa.sa_family)
|
|
{
|
|
#if NETINET
|
|
case AF_INET:
|
|
daemonaddr->sin.sin_port = port;
|
|
break;
|
|
#endif /* NETINET */
|
|
|
|
#if NETINET6
|
|
case AF_INET6:
|
|
daemonaddr->sin6.sin6_port = port;
|
|
break;
|
|
#endif /* NETINET6 */
|
|
|
|
default:
|
|
/* unknown protocol */
|
|
break;
|
|
}
|
|
return port;
|
|
}
|
|
/*
|
|
** CLRDAEMON -- reset the daemon connection
|
|
**
|
|
** Parameters:
|
|
** none.
|
|
**
|
|
** Returns:
|
|
** none.
|
|
**
|
|
** Side Effects:
|
|
** releases any resources used by the passive daemon.
|
|
*/
|
|
|
|
void
|
|
clrdaemon()
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < NDaemons; i++)
|
|
{
|
|
if (Daemons[i].d_socket >= 0)
|
|
(void) close(Daemons[i].d_socket);
|
|
Daemons[i].d_socket = -1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
** GETMODIFIERS -- get modifier flags
|
|
**
|
|
** Parameters:
|
|
** v -- the modifiers (input text line).
|
|
** modifiers -- pointer to flag field to represent modifiers.
|
|
**
|
|
** Returns:
|
|
** (xallocat()ed) string representation of modifiers.
|
|
**
|
|
** Side Effects:
|
|
** fills in modifiers.
|
|
*/
|
|
|
|
char *
|
|
getmodifiers(v, modifiers)
|
|
char *v;
|
|
BITMAP256 modifiers;
|
|
{
|
|
int l;
|
|
char *h, *f, *flags;
|
|
|
|
/* maximum length of flags: upper case Option -> "OO " */
|
|
l = 3 * strlen(v) + 3;
|
|
|
|
/* is someone joking? */
|
|
if (l < 0 || l > 256)
|
|
{
|
|
if (LogLevel > 2)
|
|
sm_syslog(LOG_ERR, NOQID,
|
|
"getmodifiers too long, ignored");
|
|
return NULL;
|
|
}
|
|
flags = xalloc(l);
|
|
f = flags;
|
|
clrbitmap(modifiers);
|
|
for (h = v; *h != '\0'; h++)
|
|
{
|
|
if (isascii(*h) && !isspace(*h) && isprint(*h))
|
|
{
|
|
setbitn(*h, modifiers);
|
|
if (flags != f)
|
|
*flags++ = ' ';
|
|
*flags++ = *h;
|
|
if (isupper(*h))
|
|
*flags++ = *h;
|
|
}
|
|
}
|
|
*flags++ = '\0';
|
|
return f;
|
|
}
|
|
|
|
/*
|
|
** CHKDAEMONMODIFIERS -- check whether all daemons have set a flag.
|
|
**
|
|
** Parameters:
|
|
** flag -- the flag to test.
|
|
**
|
|
** Returns:
|
|
** true iff all daemons have set flag.
|
|
*/
|
|
|
|
bool
|
|
chkdaemonmodifiers(flag)
|
|
int flag;
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < NDaemons; i++)
|
|
if (!bitnset((char) flag, Daemons[i].d_flags))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
** SETSOCKADDROPTIONS -- set options for SOCKADDR (daemon or client)
|
|
**
|
|
** Parameters:
|
|
** p -- the options line.
|
|
** d -- the daemon structure to fill in.
|
|
**
|
|
** Returns:
|
|
** none.
|
|
*/
|
|
|
|
static void
|
|
setsockaddroptions(p, d)
|
|
char *p;
|
|
DAEMON_T *d;
|
|
{
|
|
#if NETISO
|
|
short portno;
|
|
#endif /* NETISO */
|
|
char *port = NULL;
|
|
char *addr = NULL;
|
|
|
|
#if NETINET
|
|
if (d->d_addr.sa.sa_family == AF_UNSPEC)
|
|
d->d_addr.sa.sa_family = AF_INET;
|
|
#endif /* NETINET */
|
|
#if _FFR_SS_PER_DAEMON
|
|
d->d_supersafe = DPO_NOTSET;
|
|
#endif /* _FFR_SS_PER_DAEMON */
|
|
d->d_dm = DM_NOTSET;
|
|
d->d_refuseLA = DPO_NOTSET;
|
|
d->d_queueLA = DPO_NOTSET;
|
|
d->d_delayLA = DPO_NOTSET;
|
|
d->d_maxchildren = DPO_NOTSET;
|
|
|
|
while (p != NULL)
|
|
{
|
|
register char *f;
|
|
register char *v;
|
|
|
|
while (isascii(*p) && isspace(*p))
|
|
p++;
|
|
if (*p == '\0')
|
|
break;
|
|
f = p;
|
|
p = strchr(p, ',');
|
|
if (p != NULL)
|
|
*p++ = '\0';
|
|
v = strchr(f, '=');
|
|
if (v == NULL)
|
|
continue;
|
|
while (isascii(*++v) && isspace(*v))
|
|
continue;
|
|
|
|
switch (*f)
|
|
{
|
|
case 'A': /* address */
|
|
#if !_FFR_DPO_CS
|
|
case 'a':
|
|
#endif /* !_FFR_DPO_CS */
|
|
addr = v;
|
|
break;
|
|
|
|
case 'c':
|
|
d->d_maxchildren = atoi(v);
|
|
break;
|
|
|
|
case 'D': /* DeliveryMode */
|
|
switch (*v)
|
|
{
|
|
case SM_QUEUE:
|
|
case SM_DEFER:
|
|
case SM_DELIVER:
|
|
case SM_FORK:
|
|
d->d_dm = *v;
|
|
break;
|
|
default:
|
|
syserr("554 5.3.5 Unknown delivery mode %c",
|
|
*v);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case 'd': /* delayLA */
|
|
d->d_delayLA = atoi(v);
|
|
break;
|
|
|
|
case 'F': /* address family */
|
|
#if !_FFR_DPO_CS
|
|
case 'f':
|
|
#endif /* !_FFR_DPO_CS */
|
|
if (isascii(*v) && isdigit(*v))
|
|
d->d_addr.sa.sa_family = atoi(v);
|
|
#if _FFR_DAEMON_NETUNIX
|
|
# ifdef NETUNIX
|
|
else if (sm_strcasecmp(v, "unix") == 0 ||
|
|
sm_strcasecmp(v, "local") == 0)
|
|
d->d_addr.sa.sa_family = AF_UNIX;
|
|
# endif /* NETUNIX */
|
|
#endif /* _FFR_DAEMON_NETUNIX */
|
|
#if NETINET
|
|
else if (sm_strcasecmp(v, "inet") == 0)
|
|
d->d_addr.sa.sa_family = AF_INET;
|
|
#endif /* NETINET */
|
|
#if NETINET6
|
|
else if (sm_strcasecmp(v, "inet6") == 0)
|
|
d->d_addr.sa.sa_family = AF_INET6;
|
|
#endif /* NETINET6 */
|
|
#if NETISO
|
|
else if (sm_strcasecmp(v, "iso") == 0)
|
|
d->d_addr.sa.sa_family = AF_ISO;
|
|
#endif /* NETISO */
|
|
#if NETNS
|
|
else if (sm_strcasecmp(v, "ns") == 0)
|
|
d->d_addr.sa.sa_family = AF_NS;
|
|
#endif /* NETNS */
|
|
#if NETX25
|
|
else if (sm_strcasecmp(v, "x.25") == 0)
|
|
d->d_addr.sa.sa_family = AF_CCITT;
|
|
#endif /* NETX25 */
|
|
else
|
|
syserr("554 5.3.5 Unknown address family %s in Family=option",
|
|
v);
|
|
break;
|
|
|
|
#if MILTER
|
|
case 'I':
|
|
# if !_FFR_DPO_CS
|
|
case 'i':
|
|
# endif /* !_FFR_DPO_CS */
|
|
d->d_inputfilterlist = v;
|
|
break;
|
|
#endif /* MILTER */
|
|
|
|
case 'L': /* listen queue size */
|
|
#if !_FFR_DPO_CS
|
|
case 'l':
|
|
#endif /* !_FFR_DPO_CS */
|
|
d->d_listenqueue = atoi(v);
|
|
break;
|
|
|
|
case 'M': /* modifiers (flags) */
|
|
#if !_FFR_DPO_CS
|
|
case 'm':
|
|
#endif /* !_FFR_DPO_CS */
|
|
d->d_mflags = getmodifiers(v, d->d_flags);
|
|
break;
|
|
|
|
case 'N': /* name */
|
|
#if !_FFR_DPO_CS
|
|
case 'n':
|
|
#endif /* !_FFR_DPO_CS */
|
|
d->d_name = v;
|
|
break;
|
|
|
|
case 'P': /* port */
|
|
#if !_FFR_DPO_CS
|
|
case 'p':
|
|
#endif /* !_FFR_DPO_CS */
|
|
port = v;
|
|
break;
|
|
|
|
case 'q':
|
|
d->d_queueLA = atoi(v);
|
|
break;
|
|
|
|
case 'R': /* receive buffer size */
|
|
d->d_tcprcvbufsize = atoi(v);
|
|
break;
|
|
|
|
case 'r':
|
|
d->d_refuseLA = atoi(v);
|
|
break;
|
|
|
|
case 'S': /* send buffer size */
|
|
#if !_FFR_DPO_CS
|
|
case 's':
|
|
#endif /* !_FFR_DPO_CS */
|
|
d->d_tcpsndbufsize = atoi(v);
|
|
break;
|
|
|
|
#if _FFR_SS_PER_DAEMON
|
|
case 'T': /* SuperSafe */
|
|
if (tolower(*v) == 'i')
|
|
d->d_supersafe = SAFE_INTERACTIVE;
|
|
else if (tolower(*v) == 'p')
|
|
# if MILTER
|
|
d->d_supersafe = SAFE_REALLY_POSTMILTER;
|
|
# else /* MILTER */
|
|
(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
|
|
"Warning: SuperSafe=PostMilter requires Milter support (-DMILTER)\n");
|
|
# endif /* MILTER */
|
|
else
|
|
d->d_supersafe = atobool(v) ? SAFE_REALLY
|
|
: SAFE_NO;
|
|
break;
|
|
#endif /* _FFR_SS_PER_DAEMON */
|
|
|
|
default:
|
|
syserr("554 5.3.5 PortOptions parameter \"%s\" unknown",
|
|
f);
|
|
}
|
|
}
|
|
|
|
/* Check addr and port after finding family */
|
|
if (addr != NULL)
|
|
{
|
|
switch (d->d_addr.sa.sa_family)
|
|
{
|
|
#if _FFR_DAEMON_NETUNIX
|
|
# if NETUNIX
|
|
case AF_UNIX:
|
|
if (strlen(addr) >= sizeof(d->d_addr.sunix.sun_path))
|
|
{
|
|
errno = ENAMETOOLONG;
|
|
syserr("setsockaddroptions: domain socket name too long: %s > %d",
|
|
addr, sizeof(d->d_addr.sunix.sun_path));
|
|
break;
|
|
}
|
|
|
|
/* file safety check done in opendaemonsocket() */
|
|
(void) memset(&d->d_addr.sunix.sun_path, '\0',
|
|
sizeof(d->d_addr.sunix.sun_path));
|
|
(void) sm_strlcpy((char *)&d->d_addr.sunix.sun_path,
|
|
addr,
|
|
sizeof(d->d_addr.sunix.sun_path));
|
|
break;
|
|
# endif /* NETUNIX */
|
|
#endif /* _FFR_DAEMON_NETUNIX */
|
|
#if NETINET
|
|
case AF_INET:
|
|
if (!isascii(*addr) || !isdigit(*addr) ||
|
|
((d->d_addr.sin.sin_addr.s_addr = inet_addr(addr))
|
|
== INADDR_NONE))
|
|
{
|
|
register struct hostent *hp;
|
|
|
|
hp = sm_gethostbyname(addr, AF_INET);
|
|
if (hp == NULL)
|
|
syserr("554 5.3.0 host \"%s\" unknown",
|
|
addr);
|
|
else
|
|
{
|
|
while (*(hp->h_addr_list) != NULL &&
|
|
hp->h_addrtype != AF_INET)
|
|
hp->h_addr_list++;
|
|
if (*(hp->h_addr_list) == NULL)
|
|
syserr("554 5.3.0 host \"%s\" unknown",
|
|
addr);
|
|
else
|
|
memmove(&d->d_addr.sin.sin_addr,
|
|
*(hp->h_addr_list),
|
|
INADDRSZ);
|
|
# if NETINET6
|
|
freehostent(hp);
|
|
hp = NULL;
|
|
# endif /* NETINET6 */
|
|
}
|
|
}
|
|
break;
|
|
#endif /* NETINET */
|
|
|
|
#if NETINET6
|
|
case AF_INET6:
|
|
if (anynet_pton(AF_INET6, addr,
|
|
&d->d_addr.sin6.sin6_addr) != 1)
|
|
{
|
|
register struct hostent *hp;
|
|
|
|
hp = sm_gethostbyname(addr, AF_INET6);
|
|
if (hp == NULL)
|
|
syserr("554 5.3.0 host \"%s\" unknown",
|
|
addr);
|
|
else
|
|
{
|
|
while (*(hp->h_addr_list) != NULL &&
|
|
hp->h_addrtype != AF_INET6)
|
|
hp->h_addr_list++;
|
|
if (*(hp->h_addr_list) == NULL)
|
|
syserr("554 5.3.0 host \"%s\" unknown",
|
|
addr);
|
|
else
|
|
memmove(&d->d_addr.sin6.sin6_addr,
|
|
*(hp->h_addr_list),
|
|
IN6ADDRSZ);
|
|
freehostent(hp);
|
|
hp = NULL;
|
|
}
|
|
}
|
|
break;
|
|
#endif /* NETINET6 */
|
|
|
|
default:
|
|
syserr("554 5.3.5 address= option unsupported for family %d",
|
|
d->d_addr.sa.sa_family);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (port != NULL)
|
|
{
|
|
switch (d->d_addr.sa.sa_family)
|
|
{
|
|
#if NETINET
|
|
case AF_INET:
|
|
if (isascii(*port) && isdigit(*port))
|
|
d->d_addr.sin.sin_port = htons((unsigned short)
|
|
atoi((const char *) port));
|
|
else
|
|
{
|
|
# ifdef NO_GETSERVBYNAME
|
|
syserr("554 5.3.5 invalid port number: %s",
|
|
port);
|
|
# else /* NO_GETSERVBYNAME */
|
|
register struct servent *sp;
|
|
|
|
sp = getservbyname(port, "tcp");
|
|
if (sp == NULL)
|
|
syserr("554 5.3.5 service \"%s\" unknown",
|
|
port);
|
|
else
|
|
d->d_addr.sin.sin_port = sp->s_port;
|
|
# endif /* NO_GETSERVBYNAME */
|
|
}
|
|
break;
|
|
#endif /* NETINET */
|
|
|
|
#if NETINET6
|
|
case AF_INET6:
|
|
if (isascii(*port) && isdigit(*port))
|
|
d->d_addr.sin6.sin6_port = htons((unsigned short)
|
|
atoi(port));
|
|
else
|
|
{
|
|
# ifdef NO_GETSERVBYNAME
|
|
syserr("554 5.3.5 invalid port number: %s",
|
|
port);
|
|
# else /* NO_GETSERVBYNAME */
|
|
register struct servent *sp;
|
|
|
|
sp = getservbyname(port, "tcp");
|
|
if (sp == NULL)
|
|
syserr("554 5.3.5 service \"%s\" unknown",
|
|
port);
|
|
else
|
|
d->d_addr.sin6.sin6_port = sp->s_port;
|
|
# endif /* NO_GETSERVBYNAME */
|
|
}
|
|
break;
|
|
#endif /* NETINET6 */
|
|
|
|
#if NETISO
|
|
case AF_ISO:
|
|
/* assume two byte transport selector */
|
|
if (isascii(*port) && isdigit(*port))
|
|
portno = htons((unsigned short) atoi(port));
|
|
else
|
|
{
|
|
# ifdef NO_GETSERVBYNAME
|
|
syserr("554 5.3.5 invalid port number: %s",
|
|
port);
|
|
# else /* NO_GETSERVBYNAME */
|
|
register struct servent *sp;
|
|
|
|
sp = getservbyname(port, "tcp");
|
|
if (sp == NULL)
|
|
syserr("554 5.3.5 service \"%s\" unknown",
|
|
port);
|
|
else
|
|
portno = sp->s_port;
|
|
# endif /* NO_GETSERVBYNAME */
|
|
}
|
|
memmove(TSEL(&d->d_addr.siso),
|
|
(char *) &portno, 2);
|
|
break;
|
|
#endif /* NETISO */
|
|
|
|
default:
|
|
syserr("554 5.3.5 Port= option unsupported for family %d",
|
|
d->d_addr.sa.sa_family);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
/*
|
|
** SETDAEMONOPTIONS -- set options for running the MTA daemon
|
|
**
|
|
** Parameters:
|
|
** p -- the options line.
|
|
**
|
|
** Returns:
|
|
** true if successful, false otherwise.
|
|
**
|
|
** Side Effects:
|
|
** increments number of daemons.
|
|
*/
|
|
|
|
#define DEF_LISTENQUEUE 10
|
|
|
|
struct dflags
|
|
{
|
|
char *d_name;
|
|
int d_flag;
|
|
};
|
|
|
|
static struct dflags DaemonFlags[] =
|
|
{
|
|
{ "AUTHREQ", D_AUTHREQ },
|
|
{ "BINDIF", D_BINDIF },
|
|
{ "CANONREQ", D_CANONREQ },
|
|
{ "IFNHELO", D_IFNHELO },
|
|
{ "FQMAIL", D_FQMAIL },
|
|
{ "FQRCPT", D_FQRCPT },
|
|
{ "SMTPS", D_SMTPS },
|
|
{ "UNQUALOK", D_UNQUALOK },
|
|
{ "NOAUTH", D_NOAUTH },
|
|
{ "NOCANON", D_NOCANON },
|
|
{ "NOETRN", D_NOETRN },
|
|
{ "NOTLS", D_NOTLS },
|
|
{ "ETRNONLY", D_ETRNONLY },
|
|
{ "OPTIONAL", D_OPTIONAL },
|
|
{ "DISABLE", D_DISABLE },
|
|
{ "ISSET", D_ISSET },
|
|
{ NULL, 0 }
|
|
};
|
|
|
|
static void
|
|
printdaemonflags(d)
|
|
DAEMON_T *d;
|
|
{
|
|
register struct dflags *df;
|
|
bool first = true;
|
|
|
|
for (df = DaemonFlags; df->d_name != NULL; df++)
|
|
{
|
|
if (!bitnset(df->d_flag, d->d_flags))
|
|
continue;
|
|
if (first)
|
|
sm_dprintf("<%s", df->d_name);
|
|
else
|
|
sm_dprintf(",%s", df->d_name);
|
|
first = false;
|
|
}
|
|
if (!first)
|
|
sm_dprintf(">");
|
|
}
|
|
|
|
bool
|
|
setdaemonoptions(p)
|
|
register char *p;
|
|
{
|
|
if (NDaemons >= MAXDAEMONS)
|
|
return false;
|
|
Daemons[NDaemons].d_socket = -1;
|
|
Daemons[NDaemons].d_listenqueue = DEF_LISTENQUEUE;
|
|
clrbitmap(Daemons[NDaemons].d_flags);
|
|
setsockaddroptions(p, &Daemons[NDaemons]);
|
|
|
|
#if MILTER
|
|
if (Daemons[NDaemons].d_inputfilterlist != NULL)
|
|
Daemons[NDaemons].d_inputfilterlist = newstr(Daemons[NDaemons].d_inputfilterlist);
|
|
#endif /* MILTER */
|
|
|
|
if (Daemons[NDaemons].d_name != NULL)
|
|
Daemons[NDaemons].d_name = newstr(Daemons[NDaemons].d_name);
|
|
else
|
|
{
|
|
char num[30];
|
|
|
|
(void) sm_snprintf(num, sizeof(num), "Daemon%d", NDaemons);
|
|
Daemons[NDaemons].d_name = newstr(num);
|
|
}
|
|
|
|
if (tTd(37, 1))
|
|
{
|
|
sm_dprintf("Daemon %s flags: ", Daemons[NDaemons].d_name);
|
|
printdaemonflags(&Daemons[NDaemons]);
|
|
sm_dprintf("\n");
|
|
}
|
|
++NDaemons;
|
|
return true;
|
|
}
|
|
/*
|
|
** INITDAEMON -- initialize daemon if not yet done.
|
|
**
|
|
** Parameters:
|
|
** none
|
|
**
|
|
** Returns:
|
|
** none
|
|
**
|
|
** Side Effects:
|
|
** initializes structure for one daemon.
|
|
*/
|
|
|
|
void
|
|
initdaemon()
|
|
{
|
|
if (NDaemons == 0)
|
|
{
|
|
Daemons[NDaemons].d_socket = -1;
|
|
Daemons[NDaemons].d_listenqueue = DEF_LISTENQUEUE;
|
|
Daemons[NDaemons].d_name = "Daemon0";
|
|
NDaemons = 1;
|
|
}
|
|
}
|
|
/*
|
|
** SETCLIENTOPTIONS -- set options for running the client
|
|
**
|
|
** Parameters:
|
|
** p -- the options line.
|
|
**
|
|
** Returns:
|
|
** none.
|
|
*/
|
|
|
|
static DAEMON_T ClientSettings[AF_MAX + 1];
|
|
|
|
void
|
|
setclientoptions(p)
|
|
register char *p;
|
|
{
|
|
int family;
|
|
DAEMON_T d;
|
|
|
|
memset(&d, '\0', sizeof(d));
|
|
setsockaddroptions(p, &d);
|
|
|
|
/* grab what we need */
|
|
family = d.d_addr.sa.sa_family;
|
|
STRUCTCOPY(d, ClientSettings[family]);
|
|
setbitn(D_ISSET, ClientSettings[family].d_flags); /* mark as set */
|
|
if (d.d_name != NULL)
|
|
ClientSettings[family].d_name = newstr(d.d_name);
|
|
else
|
|
{
|
|
char num[30];
|
|
|
|
(void) sm_snprintf(num, sizeof(num), "Client%d", family);
|
|
ClientSettings[family].d_name = newstr(num);
|
|
}
|
|
}
|
|
/*
|
|
** ADDR_FAMILY -- determine address family from address
|
|
**
|
|
** Parameters:
|
|
** addr -- the string representation of the address
|
|
**
|
|
** Returns:
|
|
** AF_INET, AF_INET6 or AF_UNSPEC
|
|
**
|
|
** Side Effects:
|
|
** none.
|
|
*/
|
|
|
|
static int
|
|
addr_family(addr)
|
|
char *addr;
|
|
{
|
|
#if NETINET6
|
|
SOCKADDR clt_addr;
|
|
#endif /* NETINET6 */
|
|
|
|
#if NETINET
|
|
if (inet_addr(addr) != INADDR_NONE)
|
|
{
|
|
if (tTd(16, 9))
|
|
sm_dprintf("addr_family(%s): INET\n", addr);
|
|
return AF_INET;
|
|
}
|
|
#endif /* NETINET */
|
|
#if NETINET6
|
|
if (anynet_pton(AF_INET6, addr, &clt_addr.sin6.sin6_addr) == 1)
|
|
{
|
|
if (tTd(16, 9))
|
|
sm_dprintf("addr_family(%s): INET6\n", addr);
|
|
return AF_INET6;
|
|
}
|
|
#endif /* NETINET6 */
|
|
#if _FFR_DAEMON_NETUNIX
|
|
# if NETUNIX
|
|
if (*addr == '/')
|
|
{
|
|
if (tTd(16, 9))
|
|
sm_dprintf("addr_family(%s): LOCAL\n", addr);
|
|
return AF_UNIX;
|
|
}
|
|
# endif /* NETUNIX */
|
|
#endif /* _FFR_DAEMON_NETUNIX */
|
|
if (tTd(16, 9))
|
|
sm_dprintf("addr_family(%s): UNSPEC\n", addr);
|
|
return AF_UNSPEC;
|
|
}
|
|
|
|
/*
|
|
** CHKCLIENTMODIFIERS -- check whether all clients have set a flag.
|
|
**
|
|
** Parameters:
|
|
** flag -- the flag to test.
|
|
**
|
|
** Returns:
|
|
** true iff all configured clients have set the flag.
|
|
*/
|
|
|
|
bool
|
|
chkclientmodifiers(flag)
|
|
int flag;
|
|
{
|
|
int i;
|
|
bool flagisset;
|
|
|
|
flagisset = false;
|
|
for (i = 0; i < AF_MAX; i++)
|
|
{
|
|
if (bitnset(D_ISSET, ClientSettings[i].d_flags))
|
|
{
|
|
if (!bitnset((char) flag, ClientSettings[i].d_flags))
|
|
return false;
|
|
flagisset = true;
|
|
}
|
|
}
|
|
return flagisset;
|
|
}
|
|
|
|
#if MILTER
|
|
/*
|
|
** SETUP_DAEMON_FILTERS -- Parse per-socket filters
|
|
**
|
|
** Parameters:
|
|
** none
|
|
**
|
|
** Returns:
|
|
** none
|
|
*/
|
|
|
|
void
|
|
setup_daemon_milters()
|
|
{
|
|
int idx;
|
|
|
|
if (OpMode == MD_SMTP)
|
|
{
|
|
/* no need to configure the daemons */
|
|
return;
|
|
}
|
|
|
|
for (idx = 0; idx < NDaemons; idx++)
|
|
{
|
|
if (Daemons[idx].d_inputfilterlist != NULL)
|
|
{
|
|
milter_config(Daemons[idx].d_inputfilterlist,
|
|
Daemons[idx].d_inputfilters,
|
|
MAXFILTERS);
|
|
}
|
|
}
|
|
}
|
|
#endif /* MILTER */
|
|
/*
|
|
** MAKECONNECTION -- make a connection to an SMTP socket on a machine.
|
|
**
|
|
** Parameters:
|
|
** host -- the name of the host.
|
|
** port -- the port number to connect to.
|
|
** mci -- a pointer to the mail connection information
|
|
** structure to be filled in.
|
|
** e -- the current envelope.
|
|
** enough -- time at which to stop further connection attempts.
|
|
** (0 means no limit)
|
|
**
|
|
** Returns:
|
|
** An exit code telling whether the connection could be
|
|
** made and if not why not.
|
|
**
|
|
** Side Effects:
|
|
** none.
|
|
*/
|
|
|
|
static jmp_buf CtxConnectTimeout;
|
|
|
|
SOCKADDR CurHostAddr; /* address of current host */
|
|
|
|
int
|
|
makeconnection(host, port, mci, e, enough)
|
|
char *host;
|
|
volatile unsigned int port;
|
|
register MCI *mci;
|
|
ENVELOPE *e;
|
|
time_t enough;
|
|
{
|
|
register volatile int addrno = 0;
|
|
volatile int s;
|
|
register struct hostent *volatile hp = (struct hostent *) NULL;
|
|
SOCKADDR addr;
|
|
SOCKADDR clt_addr;
|
|
int save_errno = 0;
|
|
volatile SOCKADDR_LEN_T addrlen;
|
|
volatile bool firstconnect = true;
|
|
SM_EVENT *volatile ev = NULL;
|
|
#if NETINET6
|
|
volatile bool v6found = false;
|
|
#endif /* NETINET6 */
|
|
volatile int family = InetMode;
|
|
SOCKADDR_LEN_T len;
|
|
volatile SOCKADDR_LEN_T socksize = 0;
|
|
volatile bool clt_bind;
|
|
BITMAP256 d_flags;
|
|
char *p;
|
|
extern ENVELOPE BlankEnvelope;
|
|
|
|
/* retranslate {daemon_flags} into bitmap */
|
|
clrbitmap(d_flags);
|
|
if ((p = macvalue(macid("{daemon_flags}"), e)) != NULL)
|
|
{
|
|
for (; *p != '\0'; p++)
|
|
{
|
|
if (!(isascii(*p) && isspace(*p)))
|
|
setbitn(bitidx(*p), d_flags);
|
|
}
|
|
}
|
|
|
|
#if NETINET6
|
|
v4retry:
|
|
#endif /* NETINET6 */
|
|
clt_bind = false;
|
|
|
|
/* Set up the address for outgoing connection. */
|
|
if (bitnset(D_BINDIF, d_flags) &&
|
|
(p = macvalue(macid("{if_addr}"), e)) != NULL &&
|
|
*p != '\0')
|
|
{
|
|
#if NETINET6
|
|
char p6[INET6_ADDRSTRLEN];
|
|
#endif /* NETINET6 */
|
|
|
|
memset(&clt_addr, '\0', sizeof(clt_addr));
|
|
|
|
/* infer the address family from the address itself */
|
|
clt_addr.sa.sa_family = addr_family(p);
|
|
switch (clt_addr.sa.sa_family)
|
|
{
|
|
#if NETINET
|
|
case AF_INET:
|
|
clt_addr.sin.sin_addr.s_addr = inet_addr(p);
|
|
if (clt_addr.sin.sin_addr.s_addr != INADDR_NONE &&
|
|
clt_addr.sin.sin_addr.s_addr !=
|
|
htonl(INADDR_LOOPBACK))
|
|
{
|
|
clt_bind = true;
|
|
socksize = sizeof(struct sockaddr_in);
|
|
}
|
|
break;
|
|
#endif /* NETINET */
|
|
|
|
#if NETINET6
|
|
case AF_INET6:
|
|
if (inet_addr(p) != INADDR_NONE)
|
|
(void) sm_snprintf(p6, sizeof(p6),
|
|
"IPv6:::ffff:%s", p);
|
|
else
|
|
(void) sm_strlcpy(p6, p, sizeof(p6));
|
|
if (anynet_pton(AF_INET6, p6,
|
|
&clt_addr.sin6.sin6_addr) == 1 &&
|
|
!IN6_IS_ADDR_LOOPBACK(&clt_addr.sin6.sin6_addr))
|
|
{
|
|
clt_bind = true;
|
|
socksize = sizeof(struct sockaddr_in6);
|
|
}
|
|
break;
|
|
#endif /* NETINET6 */
|
|
|
|
#if 0
|
|
default:
|
|
syserr("554 5.3.5 Address= option unsupported for family %d",
|
|
clt_addr.sa.sa_family);
|
|
break;
|
|
#endif /* 0 */
|
|
}
|
|
if (clt_bind)
|
|
family = clt_addr.sa.sa_family;
|
|
}
|
|
|
|
/* D_BINDIF not set or not available, fallback to ClientPortOptions */
|
|
if (!clt_bind)
|
|
{
|
|
STRUCTCOPY(ClientSettings[family].d_addr, clt_addr);
|
|
switch (clt_addr.sa.sa_family)
|
|
{
|
|
#if NETINET
|
|
case AF_INET:
|
|
if (clt_addr.sin.sin_addr.s_addr == 0)
|
|
clt_addr.sin.sin_addr.s_addr = LocalDaemon ?
|
|
htonl(INADDR_LOOPBACK) : INADDR_ANY;
|
|
else
|
|
clt_bind = true;
|
|
if (clt_addr.sin.sin_port != 0)
|
|
clt_bind = true;
|
|
socksize = sizeof(struct sockaddr_in);
|
|
break;
|
|
#endif /* NETINET */
|
|
#if NETINET6
|
|
case AF_INET6:
|
|
if (IN6_IS_ADDR_UNSPECIFIED(&clt_addr.sin6.sin6_addr))
|
|
clt_addr.sin6.sin6_addr = LocalDaemon ?
|
|
in6addr_loopback : in6addr_any;
|
|
else
|
|
clt_bind = true;
|
|
socksize = sizeof(struct sockaddr_in6);
|
|
if (clt_addr.sin6.sin6_port != 0)
|
|
clt_bind = true;
|
|
break;
|
|
#endif /* NETINET6 */
|
|
#if NETISO
|
|
case AF_ISO:
|
|
socksize = sizeof(clt_addr.siso);
|
|
clt_bind = true;
|
|
break;
|
|
#endif /* NETISO */
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Set up the address for the mailer.
|
|
** Accept "[a.b.c.d]" syntax for host name.
|
|
*/
|
|
|
|
SM_SET_H_ERRNO(0);
|
|
errno = 0;
|
|
memset(&CurHostAddr, '\0', sizeof(CurHostAddr));
|
|
memset(&addr, '\0', sizeof(addr));
|
|
SmtpPhase = mci->mci_phase = "initial connection";
|
|
CurHostName = host;
|
|
|
|
if (host[0] == '[')
|
|
{
|
|
p = strchr(host, ']');
|
|
if (p != NULL)
|
|
{
|
|
#if NETINET
|
|
unsigned long hid = INADDR_NONE;
|
|
#endif /* NETINET */
|
|
#if NETINET6
|
|
struct sockaddr_in6 hid6;
|
|
#endif /* NETINET6 */
|
|
|
|
*p = '\0';
|
|
#if NETINET6
|
|
memset(&hid6, '\0', sizeof(hid6));
|
|
#endif /* NETINET6 */
|
|
#if NETINET
|
|
if (family == AF_INET &&
|
|
(hid = inet_addr(&host[1])) != INADDR_NONE)
|
|
{
|
|
addr.sin.sin_family = AF_INET;
|
|
addr.sin.sin_addr.s_addr = hid;
|
|
}
|
|
else
|
|
#endif /* NETINET */
|
|
#if NETINET6
|
|
if (family == AF_INET6 &&
|
|
anynet_pton(AF_INET6, &host[1],
|
|
&hid6.sin6_addr) == 1)
|
|
{
|
|
addr.sin6.sin6_family = AF_INET6;
|
|
addr.sin6.sin6_addr = hid6.sin6_addr;
|
|
}
|
|
else
|
|
#endif /* NETINET6 */
|
|
{
|
|
/* try it as a host name (avoid MX lookup) */
|
|
hp = sm_gethostbyname(&host[1], family);
|
|
if (hp == NULL && p[-1] == '.')
|
|
{
|
|
#if NAMED_BIND
|
|
int oldopts = _res.options;
|
|
|
|
_res.options &= ~(RES_DEFNAMES|RES_DNSRCH);
|
|
#endif /* NAMED_BIND */
|
|
p[-1] = '\0';
|
|
hp = sm_gethostbyname(&host[1],
|
|
family);
|
|
p[-1] = '.';
|
|
#if NAMED_BIND
|
|
_res.options = oldopts;
|
|
#endif /* NAMED_BIND */
|
|
}
|
|
*p = ']';
|
|
goto gothostent;
|
|
}
|
|
*p = ']';
|
|
}
|
|
if (p == NULL)
|
|
{
|
|
extern char MsgBuf[];
|
|
|
|
usrerrenh("5.1.2",
|
|
"553 Invalid numeric domain spec \"%s\"",
|
|
host);
|
|
mci_setstat(mci, EX_NOHOST, "5.1.2", MsgBuf);
|
|
errno = EINVAL;
|
|
return EX_NOHOST;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* contortion to get around SGI cc complaints */
|
|
{
|
|
p = &host[strlen(host) - 1];
|
|
hp = sm_gethostbyname(host, family);
|
|
if (hp == NULL && *p == '.')
|
|
{
|
|
#if NAMED_BIND
|
|
int oldopts = _res.options;
|
|
|
|
_res.options &= ~(RES_DEFNAMES|RES_DNSRCH);
|
|
#endif /* NAMED_BIND */
|
|
*p = '\0';
|
|
hp = sm_gethostbyname(host, family);
|
|
*p = '.';
|
|
#if NAMED_BIND
|
|
_res.options = oldopts;
|
|
#endif /* NAMED_BIND */
|
|
}
|
|
}
|
|
gothostent:
|
|
if (hp == NULL || hp->h_addr == NULL)
|
|
{
|
|
#if NAMED_BIND
|
|
/* check for name server timeouts */
|
|
# if NETINET6
|
|
if (WorkAroundBrokenAAAA && family == AF_INET6 &&
|
|
errno == ETIMEDOUT)
|
|
{
|
|
/*
|
|
** An attempt with family AF_INET may
|
|
** succeed By skipping the next section
|
|
** of code, we will try AF_INET before
|
|
** failing.
|
|
*/
|
|
|
|
if (tTd(16, 10))
|
|
sm_dprintf("makeconnection: WorkAroundBrokenAAAA: Trying AF_INET lookup (AF_INET6 failed)\n");
|
|
}
|
|
else
|
|
# endif /* NETINET6 */
|
|
{
|
|
if (errno == ETIMEDOUT ||
|
|
# if _FFR_GETHBN_ExFILE
|
|
# ifdef EMFILE
|
|
errno == EMFILE ||
|
|
# endif /* EMFILE */
|
|
# ifdef ENFILE
|
|
errno == ENFILE ||
|
|
# endif /* ENFILE */
|
|
# endif /* _FFR_GETHBN_ExFILE */
|
|
h_errno == TRY_AGAIN ||
|
|
(errno == ECONNREFUSED && UseNameServer))
|
|
{
|
|
save_errno = errno;
|
|
mci_setstat(mci, EX_TEMPFAIL,
|
|
"4.4.3", NULL);
|
|
errno = save_errno;
|
|
return EX_TEMPFAIL;
|
|
}
|
|
}
|
|
#endif /* NAMED_BIND */
|
|
#if NETINET6
|
|
/*
|
|
** Try v6 first, then fall back to v4.
|
|
** If we found a v6 address, but no v4
|
|
** addresses, then TEMPFAIL.
|
|
*/
|
|
|
|
if (family == AF_INET6)
|
|
{
|
|
family = AF_INET;
|
|
goto v4retry;
|
|
}
|
|
if (v6found)
|
|
goto v6tempfail;
|
|
#endif /* NETINET6 */
|
|
save_errno = errno;
|
|
mci_setstat(mci, EX_NOHOST, "5.1.2", NULL);
|
|
errno = save_errno;
|
|
return EX_NOHOST;
|
|
}
|
|
addr.sa.sa_family = hp->h_addrtype;
|
|
switch (hp->h_addrtype)
|
|
{
|
|
#if NETINET
|
|
case AF_INET:
|
|
memmove(&addr.sin.sin_addr,
|
|
hp->h_addr,
|
|
INADDRSZ);
|
|
break;
|
|
#endif /* NETINET */
|
|
|
|
#if NETINET6
|
|
case AF_INET6:
|
|
memmove(&addr.sin6.sin6_addr,
|
|
hp->h_addr,
|
|
IN6ADDRSZ);
|
|
break;
|
|
#endif /* NETINET6 */
|
|
|
|
default:
|
|
if (hp->h_length > sizeof(addr.sa.sa_data))
|
|
{
|
|
syserr("makeconnection: long sa_data: family %d len %d",
|
|
hp->h_addrtype, hp->h_length);
|
|
mci_setstat(mci, EX_NOHOST, "5.1.2", NULL);
|
|
errno = EINVAL;
|
|
return EX_NOHOST;
|
|
}
|
|
memmove(addr.sa.sa_data, hp->h_addr, hp->h_length);
|
|
break;
|
|
}
|
|
addrno = 1;
|
|
}
|
|
|
|
/*
|
|
** Determine the port number.
|
|
*/
|
|
|
|
if (port == 0)
|
|
{
|
|
#ifdef NO_GETSERVBYNAME
|
|
port = htons(25);
|
|
#else /* NO_GETSERVBYNAME */
|
|
register struct servent *sp = getservbyname("smtp", "tcp");
|
|
|
|
if (sp == NULL)
|
|
{
|
|
if (LogLevel > 2)
|
|
sm_syslog(LOG_ERR, NOQID,
|
|
"makeconnection: service \"smtp\" unknown");
|
|
port = htons(25);
|
|
}
|
|
else
|
|
port = sp->s_port;
|
|
#endif /* NO_GETSERVBYNAME */
|
|
}
|
|
|
|
#if NETINET6
|
|
if (addr.sa.sa_family == AF_INET6 &&
|
|
IN6_IS_ADDR_V4MAPPED(&addr.sin6.sin6_addr) &&
|
|
ClientSettings[AF_INET].d_addr.sa.sa_family != 0)
|
|
{
|
|
/*
|
|
** Ignore mapped IPv4 address since
|
|
** there is a ClientPortOptions setting
|
|
** for IPv4.
|
|
*/
|
|
|
|
goto nextaddr;
|
|
}
|
|
#endif /* NETINET6 */
|
|
|
|
switch (addr.sa.sa_family)
|
|
{
|
|
#if NETINET
|
|
case AF_INET:
|
|
addr.sin.sin_port = port;
|
|
addrlen = sizeof(struct sockaddr_in);
|
|
break;
|
|
#endif /* NETINET */
|
|
|
|
#if NETINET6
|
|
case AF_INET6:
|
|
addr.sin6.sin6_port = port;
|
|
addrlen = sizeof(struct sockaddr_in6);
|
|
break;
|
|
#endif /* NETINET6 */
|
|
|
|
#if NETISO
|
|
case AF_ISO:
|
|
/* assume two byte transport selector */
|
|
memmove(TSEL((struct sockaddr_iso *) &addr), (char *) &port, 2);
|
|
addrlen = sizeof(struct sockaddr_iso);
|
|
break;
|
|
#endif /* NETISO */
|
|
|
|
default:
|
|
syserr("Can't connect to address family %d", addr.sa.sa_family);
|
|
mci_setstat(mci, EX_NOHOST, "5.1.2", NULL);
|
|
errno = EINVAL;
|
|
#if NETINET6
|
|
if (hp != NULL)
|
|
freehostent(hp);
|
|
#endif /* NETINET6 */
|
|
return EX_NOHOST;
|
|
}
|
|
|
|
/*
|
|
** Try to actually open the connection.
|
|
*/
|
|
|
|
#if XLA
|
|
/* if too many connections, don't bother trying */
|
|
if (!xla_noqueue_ok(host))
|
|
{
|
|
# if NETINET6
|
|
if (hp != NULL)
|
|
freehostent(hp);
|
|
# endif /* NETINET6 */
|
|
return EX_TEMPFAIL;
|
|
}
|
|
#endif /* XLA */
|
|
|
|
for (;;)
|
|
{
|
|
if (tTd(16, 1))
|
|
sm_dprintf("makeconnection (%s [%s].%d (%d))\n",
|
|
host, anynet_ntoa(&addr), ntohs(port),
|
|
(int) addr.sa.sa_family);
|
|
|
|
/* save for logging */
|
|
CurHostAddr = addr;
|
|
|
|
#if HASRRESVPORT
|
|
if (bitnset(M_SECURE_PORT, mci->mci_mailer->m_flags))
|
|
{
|
|
int rport = IPPORT_RESERVED - 1;
|
|
|
|
s = rresvport(&rport);
|
|
}
|
|
else
|
|
#endif /* HASRRESVPORT */
|
|
{
|
|
s = socket(addr.sa.sa_family, SOCK_STREAM, 0);
|
|
}
|
|
if (s < 0)
|
|
{
|
|
save_errno = errno;
|
|
syserr("makeconnection: cannot create socket");
|
|
#if XLA
|
|
xla_host_end(host);
|
|
#endif /* XLA */
|
|
mci_setstat(mci, EX_TEMPFAIL, "4.4.5", NULL);
|
|
#if NETINET6
|
|
if (hp != NULL)
|
|
freehostent(hp);
|
|
#endif /* NETINET6 */
|
|
errno = save_errno;
|
|
return EX_TEMPFAIL;
|
|
}
|
|
|
|
#ifdef SO_SNDBUF
|
|
if (ClientSettings[family].d_tcpsndbufsize > 0)
|
|
{
|
|
if (setsockopt(s, SOL_SOCKET, SO_SNDBUF,
|
|
(char *) &ClientSettings[family].d_tcpsndbufsize,
|
|
sizeof(ClientSettings[family].d_tcpsndbufsize)) < 0)
|
|
syserr("makeconnection: setsockopt(SO_SNDBUF)");
|
|
}
|
|
#endif /* SO_SNDBUF */
|
|
#ifdef SO_RCVBUF
|
|
if (ClientSettings[family].d_tcprcvbufsize > 0)
|
|
{
|
|
if (setsockopt(s, SOL_SOCKET, SO_RCVBUF,
|
|
(char *) &ClientSettings[family].d_tcprcvbufsize,
|
|
sizeof(ClientSettings[family].d_tcprcvbufsize)) < 0)
|
|
syserr("makeconnection: setsockopt(SO_RCVBUF)");
|
|
}
|
|
#endif /* SO_RCVBUF */
|
|
|
|
if (tTd(16, 1))
|
|
sm_dprintf("makeconnection: fd=%d\n", s);
|
|
|
|
/* turn on network debugging? */
|
|
if (tTd(16, 101))
|
|
{
|
|
int on = 1;
|
|
|
|
(void) setsockopt(s, SOL_SOCKET, SO_DEBUG,
|
|
(char *)&on, sizeof(on));
|
|
}
|
|
if (e->e_xfp != NULL) /* for debugging */
|
|
(void) sm_io_flush(e->e_xfp, SM_TIME_DEFAULT);
|
|
errno = 0; /* for debugging */
|
|
|
|
if (clt_bind)
|
|
{
|
|
int on = 1;
|
|
|
|
switch (clt_addr.sa.sa_family)
|
|
{
|
|
#if NETINET
|
|
case AF_INET:
|
|
if (clt_addr.sin.sin_port != 0)
|
|
(void) setsockopt(s, SOL_SOCKET,
|
|
SO_REUSEADDR,
|
|
(char *) &on,
|
|
sizeof(on));
|
|
break;
|
|
#endif /* NETINET */
|
|
|
|
#if NETINET6
|
|
case AF_INET6:
|
|
if (clt_addr.sin6.sin6_port != 0)
|
|
(void) setsockopt(s, SOL_SOCKET,
|
|
SO_REUSEADDR,
|
|
(char *) &on,
|
|
sizeof(on));
|
|
break;
|
|
#endif /* NETINET6 */
|
|
}
|
|
|
|
if (bind(s, &clt_addr.sa, socksize) < 0)
|
|
{
|
|
save_errno = errno;
|
|
(void) close(s);
|
|
errno = save_errno;
|
|
syserr("makeconnection: cannot bind socket [%s]",
|
|
anynet_ntoa(&clt_addr));
|
|
#if NETINET6
|
|
if (hp != NULL)
|
|
freehostent(hp);
|
|
#endif /* NETINET6 */
|
|
errno = save_errno;
|
|
return EX_TEMPFAIL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Linux seems to hang in connect for 90 minutes (!!!).
|
|
** Time out the connect to avoid this problem.
|
|
*/
|
|
|
|
if (setjmp(CtxConnectTimeout) == 0)
|
|
{
|
|
int i;
|
|
|
|
if (e->e_ntries <= 0 && TimeOuts.to_iconnect != 0)
|
|
ev = sm_setevent(TimeOuts.to_iconnect,
|
|
connecttimeout, 0);
|
|
else if (TimeOuts.to_connect != 0)
|
|
ev = sm_setevent(TimeOuts.to_connect,
|
|
connecttimeout, 0);
|
|
else
|
|
ev = NULL;
|
|
|
|
switch (ConnectOnlyTo.sa.sa_family)
|
|
{
|
|
#if NETINET
|
|
case AF_INET:
|
|
addr.sin.sin_addr.s_addr = ConnectOnlyTo.sin.sin_addr.s_addr;
|
|
break;
|
|
#endif /* NETINET */
|
|
|
|
#if NETINET6
|
|
case AF_INET6:
|
|
memmove(&addr.sin6.sin6_addr,
|
|
&ConnectOnlyTo.sin6.sin6_addr,
|
|
IN6ADDRSZ);
|
|
break;
|
|
#endif /* NETINET6 */
|
|
}
|
|
if (tTd(16, 1))
|
|
sm_dprintf("Connecting to [%s]...\n", anynet_ntoa(&addr));
|
|
i = connect(s, (struct sockaddr *) &addr, addrlen);
|
|
save_errno = errno;
|
|
if (ev != NULL)
|
|
sm_clrevent(ev);
|
|
if (i >= 0)
|
|
break;
|
|
}
|
|
else
|
|
save_errno = errno;
|
|
|
|
/* couldn't connect.... figure out why */
|
|
(void) close(s);
|
|
|
|
/* if running demand-dialed connection, try again */
|
|
if (DialDelay > 0 && firstconnect &&
|
|
bitnset(M_DIALDELAY, mci->mci_mailer->m_flags))
|
|
{
|
|
if (tTd(16, 1))
|
|
sm_dprintf("Connect failed (%s); trying again...\n",
|
|
sm_errstring(save_errno));
|
|
firstconnect = false;
|
|
(void) sleep(DialDelay);
|
|
continue;
|
|
}
|
|
|
|
if (LogLevel > 13)
|
|
sm_syslog(LOG_INFO, e->e_id,
|
|
"makeconnection (%s [%s]) failed: %s",
|
|
host, anynet_ntoa(&addr),
|
|
sm_errstring(save_errno));
|
|
|
|
#if NETINET6
|
|
nextaddr:
|
|
#endif /* NETINET6 */
|
|
if (hp != NULL && hp->h_addr_list[addrno] != NULL &&
|
|
(enough == 0 || curtime() < enough))
|
|
{
|
|
if (tTd(16, 1))
|
|
sm_dprintf("Connect failed (%s); trying new address....\n",
|
|
sm_errstring(save_errno));
|
|
switch (addr.sa.sa_family)
|
|
{
|
|
#if NETINET
|
|
case AF_INET:
|
|
memmove(&addr.sin.sin_addr,
|
|
hp->h_addr_list[addrno++],
|
|
INADDRSZ);
|
|
break;
|
|
#endif /* NETINET */
|
|
|
|
#if NETINET6
|
|
case AF_INET6:
|
|
memmove(&addr.sin6.sin6_addr,
|
|
hp->h_addr_list[addrno++],
|
|
IN6ADDRSZ);
|
|
break;
|
|
#endif /* NETINET6 */
|
|
|
|
default:
|
|
memmove(addr.sa.sa_data,
|
|
hp->h_addr_list[addrno++],
|
|
hp->h_length);
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
errno = save_errno;
|
|
|
|
#if NETINET6
|
|
if (family == AF_INET6)
|
|
{
|
|
if (tTd(16, 1))
|
|
sm_dprintf("Connect failed (%s); retrying with AF_INET....\n",
|
|
sm_errstring(save_errno));
|
|
v6found = true;
|
|
family = AF_INET;
|
|
if (hp != NULL)
|
|
{
|
|
freehostent(hp);
|
|
hp = NULL;
|
|
}
|
|
goto v4retry;
|
|
}
|
|
v6tempfail:
|
|
#endif /* NETINET6 */
|
|
/* couldn't open connection */
|
|
#if NETINET6
|
|
/* Don't clobber an already saved errno from v4retry */
|
|
if (errno > 0)
|
|
#endif /* NETINET6 */
|
|
save_errno = errno;
|
|
if (tTd(16, 1))
|
|
sm_dprintf("Connect failed (%s)\n",
|
|
sm_errstring(save_errno));
|
|
#if XLA
|
|
xla_host_end(host);
|
|
#endif /* XLA */
|
|
mci_setstat(mci, EX_TEMPFAIL, "4.4.1", NULL);
|
|
#if NETINET6
|
|
if (hp != NULL)
|
|
freehostent(hp);
|
|
#endif /* NETINET6 */
|
|
errno = save_errno;
|
|
return EX_TEMPFAIL;
|
|
}
|
|
|
|
#if NETINET6
|
|
if (hp != NULL)
|
|
{
|
|
freehostent(hp);
|
|
hp = NULL;
|
|
}
|
|
#endif /* NETINET6 */
|
|
|
|
/* connection ok, put it into canonical form */
|
|
mci->mci_out = NULL;
|
|
if ((mci->mci_out = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT,
|
|
(void *) &s,
|
|
SM_IO_WRONLY_B, NULL)) == NULL ||
|
|
(s = dup(s)) < 0 ||
|
|
(mci->mci_in = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT,
|
|
(void *) &s,
|
|
SM_IO_RDONLY_B, NULL)) == NULL)
|
|
{
|
|
save_errno = errno;
|
|
syserr("cannot open SMTP client channel, fd=%d", s);
|
|
mci_setstat(mci, EX_TEMPFAIL, "4.4.5", NULL);
|
|
if (mci->mci_out != NULL)
|
|
(void) sm_io_close(mci->mci_out, SM_TIME_DEFAULT);
|
|
(void) close(s);
|
|
errno = save_errno;
|
|
return EX_TEMPFAIL;
|
|
}
|
|
sm_io_automode(mci->mci_out, mci->mci_in);
|
|
|
|
/* set {client_flags} */
|
|
if (ClientSettings[addr.sa.sa_family].d_mflags != NULL)
|
|
{
|
|
macdefine(&mci->mci_macro, A_PERM,
|
|
macid("{client_flags}"),
|
|
ClientSettings[addr.sa.sa_family].d_mflags);
|
|
}
|
|
else
|
|
macdefine(&mci->mci_macro, A_PERM,
|
|
macid("{client_flags}"), "");
|
|
|
|
/* "add" {client_flags} to bitmap */
|
|
if (bitnset(D_IFNHELO, ClientSettings[addr.sa.sa_family].d_flags))
|
|
{
|
|
/* look for just this one flag */
|
|
setbitn(D_IFNHELO, d_flags);
|
|
}
|
|
|
|
/* find out name for Interface through which we connect */
|
|
len = sizeof(addr);
|
|
if (getsockname(s, &addr.sa, &len) == 0)
|
|
{
|
|
char *name;
|
|
char family[5];
|
|
|
|
macdefine(&BlankEnvelope.e_macro, A_TEMP,
|
|
macid("{if_addr_out}"), anynet_ntoa(&addr));
|
|
(void) sm_snprintf(family, sizeof(family), "%d",
|
|
addr.sa.sa_family);
|
|
macdefine(&BlankEnvelope.e_macro, A_TEMP,
|
|
macid("{if_family_out}"), family);
|
|
|
|
name = hostnamebyanyaddr(&addr);
|
|
macdefine(&BlankEnvelope.e_macro, A_TEMP,
|
|
macid("{if_name_out}"), name);
|
|
if (LogLevel > 11)
|
|
{
|
|
/* log connection information */
|
|
sm_syslog(LOG_INFO, e->e_id,
|
|
"SMTP outgoing connect on %.40s", name);
|
|
}
|
|
if (bitnset(D_IFNHELO, d_flags))
|
|
{
|
|
if (name[0] != '[' && strchr(name, '.') != NULL)
|
|
mci->mci_heloname = newstr(name);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
macdefine(&BlankEnvelope.e_macro, A_PERM,
|
|
macid("{if_name_out}"), NULL);
|
|
macdefine(&BlankEnvelope.e_macro, A_PERM,
|
|
macid("{if_addr_out}"), NULL);
|
|
macdefine(&BlankEnvelope.e_macro, A_PERM,
|
|
macid("{if_family_out}"), NULL);
|
|
}
|
|
|
|
/* Use the configured HeloName as appropriate */
|
|
if (HeloName != NULL && HeloName[0] != '\0')
|
|
mci->mci_heloname = newstr(HeloName);
|
|
|
|
mci_setstat(mci, EX_OK, NULL, NULL);
|
|
return EX_OK;
|
|
}
|
|
|
|
static void
|
|
connecttimeout(ignore)
|
|
int ignore;
|
|
{
|
|
/*
|
|
** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
|
|
** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
|
|
** DOING.
|
|
*/
|
|
|
|
errno = ETIMEDOUT;
|
|
longjmp(CtxConnectTimeout, 1);
|
|
}
|
|
/*
|
|
** MAKECONNECTION_DS -- make a connection to a domain socket.
|
|
**
|
|
** Parameters:
|
|
** mux_path -- the path of the socket to connect to.
|
|
** mci -- a pointer to the mail connection information
|
|
** structure to be filled in.
|
|
**
|
|
** Returns:
|
|
** An exit code telling whether the connection could be
|
|
** made and if not why not.
|
|
**
|
|
** Side Effects:
|
|
** none.
|
|
*/
|
|
|
|
#if NETUNIX
|
|
int
|
|
makeconnection_ds(mux_path, mci)
|
|
char *mux_path;
|
|
register MCI *mci;
|
|
{
|
|
int sock;
|
|
int rval, save_errno;
|
|
long sff = SFF_SAFEDIRPATH|SFF_OPENASROOT|SFF_NOLINK|SFF_ROOTOK|SFF_EXECOK;
|
|
struct sockaddr_un unix_addr;
|
|
|
|
/* if not safe, don't connect */
|
|
rval = safefile(mux_path, RunAsUid, RunAsGid, RunAsUserName,
|
|
sff, S_IRUSR|S_IWUSR, NULL);
|
|
|
|
if (rval != 0)
|
|
{
|
|
syserr("makeconnection_ds: unsafe domain socket %s",
|
|
mux_path);
|
|
mci_setstat(mci, EX_TEMPFAIL, "4.3.5", NULL);
|
|
errno = rval;
|
|
return EX_TEMPFAIL;
|
|
}
|
|
|
|
/* prepare address structure */
|
|
memset(&unix_addr, '\0', sizeof(unix_addr));
|
|
unix_addr.sun_family = AF_UNIX;
|
|
|
|
if (strlen(mux_path) >= sizeof(unix_addr.sun_path))
|
|
{
|
|
syserr("makeconnection_ds: domain socket name %s too long",
|
|
mux_path);
|
|
|
|
/* XXX why TEMPFAIL but 5.x.y ? */
|
|
mci_setstat(mci, EX_TEMPFAIL, "5.3.5", NULL);
|
|
errno = ENAMETOOLONG;
|
|
return EX_UNAVAILABLE;
|
|
}
|
|
(void) sm_strlcpy(unix_addr.sun_path, mux_path,
|
|
sizeof(unix_addr.sun_path));
|
|
|
|
/* initialize domain socket */
|
|
sock = socket(AF_UNIX, SOCK_STREAM, 0);
|
|
if (sock == -1)
|
|
{
|
|
save_errno = errno;
|
|
syserr("makeconnection_ds: could not create domain socket %s",
|
|
mux_path);
|
|
mci_setstat(mci, EX_TEMPFAIL, "4.4.5", NULL);
|
|
errno = save_errno;
|
|
return EX_TEMPFAIL;
|
|
}
|
|
|
|
/* connect to server */
|
|
if (connect(sock, (struct sockaddr *) &unix_addr,
|
|
sizeof(unix_addr)) == -1)
|
|
{
|
|
save_errno = errno;
|
|
syserr("Could not connect to socket %s", mux_path);
|
|
mci_setstat(mci, EX_TEMPFAIL, "4.4.1", NULL);
|
|
(void) close(sock);
|
|
errno = save_errno;
|
|
return EX_TEMPFAIL;
|
|
}
|
|
|
|
/* connection ok, put it into canonical form */
|
|
mci->mci_out = NULL;
|
|
if ((mci->mci_out = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT,
|
|
(void *) &sock, SM_IO_WRONLY_B, NULL))
|
|
== NULL
|
|
|| (sock = dup(sock)) < 0 ||
|
|
(mci->mci_in = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT,
|
|
(void *) &sock, SM_IO_RDONLY_B, NULL))
|
|
== NULL)
|
|
{
|
|
save_errno = errno;
|
|
syserr("cannot open SMTP client channel, fd=%d", sock);
|
|
mci_setstat(mci, EX_TEMPFAIL, "4.4.5", NULL);
|
|
if (mci->mci_out != NULL)
|
|
(void) sm_io_close(mci->mci_out, SM_TIME_DEFAULT);
|
|
(void) close(sock);
|
|
errno = save_errno;
|
|
return EX_TEMPFAIL;
|
|
}
|
|
sm_io_automode(mci->mci_out, mci->mci_in);
|
|
|
|
mci_setstat(mci, EX_OK, NULL, NULL);
|
|
errno = 0;
|
|
return EX_OK;
|
|
}
|
|
#endif /* NETUNIX */
|
|
/*
|
|
** SHUTDOWN_DAEMON -- Performs a clean shutdown of the daemon
|
|
**
|
|
** Parameters:
|
|
** none.
|
|
**
|
|
** Returns:
|
|
** none.
|
|
**
|
|
** Side Effects:
|
|
** closes control socket, exits.
|
|
*/
|
|
|
|
void
|
|
shutdown_daemon()
|
|
{
|
|
int i;
|
|
char *reason;
|
|
|
|
sm_allsignals(true);
|
|
|
|
reason = ShutdownRequest;
|
|
ShutdownRequest = NULL;
|
|
PendingSignal = 0;
|
|
|
|
if (LogLevel > 9)
|
|
sm_syslog(LOG_INFO, CurEnv->e_id, "stopping daemon, reason=%s",
|
|
reason == NULL ? "implicit call" : reason);
|
|
|
|
FileName = NULL;
|
|
closecontrolsocket(true);
|
|
#if XLA
|
|
xla_all_end();
|
|
#endif /* XLA */
|
|
|
|
for (i = 0; i < NDaemons; i++)
|
|
{
|
|
if (Daemons[i].d_socket >= 0)
|
|
{
|
|
(void) close(Daemons[i].d_socket);
|
|
Daemons[i].d_socket = -1;
|
|
|
|
#if _FFR_DAEMON_NETUNIX
|
|
# if NETUNIX
|
|
/* Remove named sockets */
|
|
if (Daemons[i].d_addr.sa.sa_family == AF_UNIX)
|
|
{
|
|
int rval;
|
|
long sff = SFF_SAFEDIRPATH|SFF_OPENASROOT|SFF_NOLINK|SFF_MUSTOWN|SFF_EXECOK|SFF_CREAT;
|
|
|
|
/* if not safe, don't use it */
|
|
rval = safefile(Daemons[i].d_addr.sunix.sun_path,
|
|
RunAsUid, RunAsGid,
|
|
RunAsUserName, sff,
|
|
S_IRUSR|S_IWUSR, NULL);
|
|
if (rval == 0 &&
|
|
unlink(Daemons[i].d_addr.sunix.sun_path) < 0)
|
|
{
|
|
sm_syslog(LOG_WARNING, NOQID,
|
|
"Could not remove daemon %s socket: %s: %s",
|
|
Daemons[i].d_name,
|
|
Daemons[i].d_addr.sunix.sun_path,
|
|
sm_errstring(errno));
|
|
}
|
|
}
|
|
# endif /* NETUNIX */
|
|
#endif /* _FFR_DAEMON_NETUNIX */
|
|
}
|
|
}
|
|
|
|
finis(false, true, EX_OK);
|
|
}
|
|
/*
|
|
** RESTART_DAEMON -- Performs a clean restart of the daemon
|
|
**
|
|
** Parameters:
|
|
** none.
|
|
**
|
|
** Returns:
|
|
** none.
|
|
**
|
|
** Side Effects:
|
|
** restarts the daemon or exits if restart fails.
|
|
*/
|
|
|
|
/* Make a non-DFL/IGN signal a noop */
|
|
#define SM_NOOP_SIGNAL(sig, old) \
|
|
do \
|
|
{ \
|
|
(old) = sm_signal((sig), sm_signal_noop); \
|
|
if ((old) == SIG_IGN || (old) == SIG_DFL) \
|
|
(void) sm_signal((sig), (old)); \
|
|
} while (0)
|
|
|
|
void
|
|
restart_daemon()
|
|
{
|
|
bool drop;
|
|
int save_errno;
|
|
char *reason;
|
|
sigfunc_t ignore, oalrm, ousr1;
|
|
extern int DtableSize;
|
|
|
|
/* clear the events to turn off SIGALRMs */
|
|
sm_clear_events();
|
|
sm_allsignals(true);
|
|
|
|
reason = RestartRequest;
|
|
RestartRequest = NULL;
|
|
PendingSignal = 0;
|
|
|
|
if (SaveArgv[0][0] != '/')
|
|
{
|
|
if (LogLevel > 3)
|
|
sm_syslog(LOG_INFO, NOQID,
|
|
"could not restart: need full path");
|
|
finis(false, true, EX_OSFILE);
|
|
/* NOTREACHED */
|
|
}
|
|
if (LogLevel > 3)
|
|
sm_syslog(LOG_INFO, NOQID, "restarting %s due to %s",
|
|
SaveArgv[0],
|
|
reason == NULL ? "implicit call" : reason);
|
|
|
|
closecontrolsocket(true);
|
|
#if SM_CONF_SHM
|
|
cleanup_shm(DaemonPid == getpid());
|
|
#endif /* SM_CONF_SHM */
|
|
|
|
/* close locked pid file */
|
|
close_sendmail_pid();
|
|
|
|
/*
|
|
** Want to drop to the user who started the process in all cases
|
|
** *but* when running as "smmsp" for the clientmqueue queue run
|
|
** daemon. In that case, UseMSP will be true, RunAsUid should not
|
|
** be root, and RealUid should be either 0 or RunAsUid.
|
|
*/
|
|
|
|
drop = !(UseMSP && RunAsUid != 0 &&
|
|
(RealUid == 0 || RealUid == RunAsUid));
|
|
|
|
if (drop_privileges(drop) != EX_OK)
|
|
{
|
|
if (LogLevel > 0)
|
|
sm_syslog(LOG_ALERT, NOQID,
|
|
"could not drop privileges: %s",
|
|
sm_errstring(errno));
|
|
finis(false, true, EX_OSERR);
|
|
/* NOTREACHED */
|
|
}
|
|
|
|
sm_close_on_exec(STDERR_FILENO + 1, DtableSize);
|
|
|
|
/*
|
|
** Need to allow signals before execve() to make them "harmless".
|
|
** However, the default action can be "terminate", so it isn't
|
|
** really harmless. Setting signals to IGN will cause them to be
|
|
** ignored in the new process to, so that isn't a good alternative.
|
|
*/
|
|
|
|
SM_NOOP_SIGNAL(SIGALRM, oalrm);
|
|
SM_NOOP_SIGNAL(SIGCHLD, ignore);
|
|
SM_NOOP_SIGNAL(SIGHUP, ignore);
|
|
SM_NOOP_SIGNAL(SIGINT, ignore);
|
|
SM_NOOP_SIGNAL(SIGPIPE, ignore);
|
|
SM_NOOP_SIGNAL(SIGTERM, ignore);
|
|
#ifdef SIGUSR1
|
|
SM_NOOP_SIGNAL(SIGUSR1, ousr1);
|
|
#endif /* SIGUSR1 */
|
|
|
|
/* Turn back on signals */
|
|
sm_allsignals(false);
|
|
|
|
(void) execve(SaveArgv[0], (ARGV_T) SaveArgv, (ARGV_T) ExternalEnviron);
|
|
save_errno = errno;
|
|
|
|
/* block signals again and restore needed signals */
|
|
sm_allsignals(true);
|
|
|
|
/* For finis() events */
|
|
(void) sm_signal(SIGALRM, oalrm);
|
|
|
|
#ifdef SIGUSR1
|
|
/* For debugging finis() */
|
|
(void) sm_signal(SIGUSR1, ousr1);
|
|
#endif /* SIGUSR1 */
|
|
|
|
errno = save_errno;
|
|
if (LogLevel > 0)
|
|
sm_syslog(LOG_ALERT, NOQID, "could not exec %s: %s",
|
|
SaveArgv[0], sm_errstring(errno));
|
|
finis(false, true, EX_OSFILE);
|
|
/* NOTREACHED */
|
|
}
|
|
/*
|
|
** MYHOSTNAME -- return the name of this host.
|
|
**
|
|
** Parameters:
|
|
** hostbuf -- a place to return the name of this host.
|
|
** size -- the size of hostbuf.
|
|
**
|
|
** Returns:
|
|
** A list of aliases for this host.
|
|
**
|
|
** Side Effects:
|
|
** Adds numeric codes to $=w.
|
|
*/
|
|
|
|
struct hostent *
|
|
myhostname(hostbuf, size)
|
|
char hostbuf[];
|
|
int size;
|
|
{
|
|
register struct hostent *hp;
|
|
|
|
if (gethostname(hostbuf, size) < 0 || hostbuf[0] == '\0')
|
|
(void) sm_strlcpy(hostbuf, "localhost", size);
|
|
hp = sm_gethostbyname(hostbuf, InetMode);
|
|
#if NETINET && NETINET6
|
|
if (hp == NULL && InetMode == AF_INET6)
|
|
{
|
|
/*
|
|
** It's possible that this IPv6 enabled machine doesn't
|
|
** actually have any IPv6 interfaces and, therefore, no
|
|
** IPv6 addresses. Fall back to AF_INET.
|
|
*/
|
|
|
|
hp = sm_gethostbyname(hostbuf, AF_INET);
|
|
}
|
|
#endif /* NETINET && NETINET6 */
|
|
if (hp == NULL)
|
|
return NULL;
|
|
if (strchr(hp->h_name, '.') != NULL || strchr(hostbuf, '.') == NULL)
|
|
(void) cleanstrcpy(hostbuf, hp->h_name, size);
|
|
|
|
#if NETINFO
|
|
if (strchr(hostbuf, '.') == NULL)
|
|
{
|
|
char *domainname;
|
|
|
|
domainname = ni_propval("/locations", NULL, "resolver",
|
|
"domain", '\0');
|
|
if (domainname != NULL &&
|
|
strlen(domainname) + strlen(hostbuf) + 1 < size)
|
|
(void) sm_strlcat2(hostbuf, ".", domainname, size);
|
|
}
|
|
#endif /* NETINFO */
|
|
|
|
/*
|
|
** If there is still no dot in the name, try looking for a
|
|
** dotted alias.
|
|
*/
|
|
|
|
if (strchr(hostbuf, '.') == NULL)
|
|
{
|
|
char **ha;
|
|
|
|
for (ha = hp->h_aliases; ha != NULL && *ha != NULL; ha++)
|
|
{
|
|
if (strchr(*ha, '.') != NULL)
|
|
{
|
|
(void) cleanstrcpy(hostbuf, *ha, size - 1);
|
|
hostbuf[size - 1] = '\0';
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
** If _still_ no dot, wait for a while and try again -- it is
|
|
** possible that some service is starting up. This can result
|
|
** in excessive delays if the system is badly configured, but
|
|
** there really isn't a way around that, particularly given that
|
|
** the config file hasn't been read at this point.
|
|
** All in all, a bit of a mess.
|
|
*/
|
|
|
|
if (strchr(hostbuf, '.') == NULL &&
|
|
!getcanonname(hostbuf, size, true, NULL))
|
|
{
|
|
sm_syslog(LocalDaemon ? LOG_WARNING : LOG_CRIT, NOQID,
|
|
"My unqualified host name (%s) unknown; sleeping for retry",
|
|
hostbuf);
|
|
message("My unqualified host name (%s) unknown; sleeping for retry",
|
|
hostbuf);
|
|
(void) sleep(60);
|
|
if (!getcanonname(hostbuf, size, true, NULL))
|
|
{
|
|
sm_syslog(LocalDaemon ? LOG_WARNING : LOG_ALERT, NOQID,
|
|
"unable to qualify my own domain name (%s) -- using short name",
|
|
hostbuf);
|
|
message("WARNING: unable to qualify my own domain name (%s) -- using short name",
|
|
hostbuf);
|
|
}
|
|
}
|
|
return hp;
|
|
}
|
|
/*
|
|
** ADDRCMP -- compare two host addresses
|
|
**
|
|
** Parameters:
|
|
** hp -- hostent structure for the first address
|
|
** ha -- actual first address
|
|
** sa -- second address
|
|
**
|
|
** Returns:
|
|
** 0 -- if ha and sa match
|
|
** else -- they don't match
|
|
*/
|
|
|
|
static int
|
|
addrcmp(hp, ha, sa)
|
|
struct hostent *hp;
|
|
char *ha;
|
|
SOCKADDR *sa;
|
|
{
|
|
#if NETINET6
|
|
unsigned char *a;
|
|
#endif /* NETINET6 */
|
|
|
|
switch (sa->sa.sa_family)
|
|
{
|
|
#if NETINET
|
|
case AF_INET:
|
|
if (hp->h_addrtype == AF_INET)
|
|
return memcmp(ha, (char *) &sa->sin.sin_addr, INADDRSZ);
|
|
break;
|
|
#endif /* NETINET */
|
|
|
|
#if NETINET6
|
|
case AF_INET6:
|
|
a = (unsigned char *) &sa->sin6.sin6_addr;
|
|
|
|
/* Straight binary comparison */
|
|
if (hp->h_addrtype == AF_INET6)
|
|
return memcmp(ha, a, IN6ADDRSZ);
|
|
|
|
/* If IPv4-mapped IPv6 address, compare the IPv4 section */
|
|
if (hp->h_addrtype == AF_INET &&
|
|
IN6_IS_ADDR_V4MAPPED(&sa->sin6.sin6_addr))
|
|
return memcmp(a + IN6ADDRSZ - INADDRSZ, ha, INADDRSZ);
|
|
break;
|
|
#endif /* NETINET6 */
|
|
}
|
|
return -1;
|
|
}
|
|
/*
|
|
** GETAUTHINFO -- get the real host name associated with a file descriptor
|
|
**
|
|
** Uses RFC1413 protocol to try to get info from the other end.
|
|
**
|
|
** Parameters:
|
|
** fd -- the descriptor
|
|
** may_be_forged -- an outage that is set to true if the
|
|
** forward lookup of RealHostName does not match
|
|
** RealHostAddr; set to false if they do match.
|
|
**
|
|
** Returns:
|
|
** The user@host information associated with this descriptor.
|
|
*/
|
|
|
|
static jmp_buf CtxAuthTimeout;
|
|
|
|
static void
|
|
authtimeout(ignore)
|
|
int ignore;
|
|
{
|
|
/*
|
|
** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
|
|
** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
|
|
** DOING.
|
|
*/
|
|
|
|
errno = ETIMEDOUT;
|
|
longjmp(CtxAuthTimeout, 1);
|
|
}
|
|
|
|
char *
|
|
getauthinfo(fd, may_be_forged)
|
|
int fd;
|
|
bool *may_be_forged;
|
|
{
|
|
unsigned short SM_NONVOLATILE port = 0;
|
|
SOCKADDR_LEN_T falen;
|
|
register char *volatile p = NULL;
|
|
SOCKADDR la;
|
|
SOCKADDR_LEN_T lalen;
|
|
#ifndef NO_GETSERVBYNAME
|
|
register struct servent *sp;
|
|
# if NETINET
|
|
static unsigned short port4 = 0;
|
|
# endif /* NETINET */
|
|
# if NETINET6
|
|
static unsigned short port6 = 0;
|
|
# endif /* NETINET6 */
|
|
#endif /* ! NO_GETSERVBYNAME */
|
|
volatile int s;
|
|
int i = 0;
|
|
size_t len;
|
|
SM_EVENT *ev;
|
|
int nleft;
|
|
struct hostent *hp;
|
|
char *ostype = NULL;
|
|
char **ha;
|
|
char ibuf[MAXNAME + 1];
|
|
static char hbuf[MAXNAME + MAXAUTHINFO + 11];
|
|
|
|
*may_be_forged = false;
|
|
falen = sizeof(RealHostAddr);
|
|
if (isatty(fd) || (i = getpeername(fd, &RealHostAddr.sa, &falen)) < 0 ||
|
|
falen <= 0 || RealHostAddr.sa.sa_family == 0)
|
|
{
|
|
if (i < 0)
|
|
{
|
|
/*
|
|
** ENOTSOCK is OK: bail on anything else, but reset
|
|
** errno in this case, so a mis-report doesn't
|
|
** happen later.
|
|
*/
|
|
|
|
if (errno != ENOTSOCK)
|
|
return NULL;
|
|
errno = 0;
|
|
}
|
|
(void) sm_strlcpyn(hbuf, sizeof(hbuf), 2, RealUserName,
|
|
"@localhost");
|
|
if (tTd(9, 1))
|
|
sm_dprintf("getauthinfo: %s\n", hbuf);
|
|
return hbuf;
|
|
}
|
|
|
|
if (RealHostName == NULL)
|
|
{
|
|
/* translate that to a host name */
|
|
RealHostName = newstr(hostnamebyanyaddr(&RealHostAddr));
|
|
if (strlen(RealHostName) > MAXNAME)
|
|
RealHostName[MAXNAME] = '\0'; /* XXX - 1 ? */
|
|
}
|
|
|
|
/* cross check RealHostName with forward DNS lookup */
|
|
if (anynet_ntoa(&RealHostAddr)[0] != '[' &&
|
|
RealHostName[0] != '[')
|
|
{
|
|
int family;
|
|
|
|
family = RealHostAddr.sa.sa_family;
|
|
#if NETINET6 && NEEDSGETIPNODE
|
|
/*
|
|
** If RealHostAddr is an IPv6 connection with an
|
|
** IPv4-mapped address, we need RealHostName's IPv4
|
|
** address(es) for addrcmp() to compare against
|
|
** RealHostAddr.
|
|
**
|
|
** Actually, we only need to do this for systems
|
|
** which NEEDSGETIPNODE since the real getipnodebyname()
|
|
** already does V4MAPPED address via the AI_V4MAPPEDCFG
|
|
** flag. A better fix to this problem is to add this
|
|
** functionality to our stub getipnodebyname().
|
|
*/
|
|
|
|
if (family == AF_INET6 &&
|
|
IN6_IS_ADDR_V4MAPPED(&RealHostAddr.sin6.sin6_addr))
|
|
family = AF_INET;
|
|
#endif /* NETINET6 && NEEDSGETIPNODE */
|
|
|
|
/* try to match the reverse against the forward lookup */
|
|
hp = sm_gethostbyname(RealHostName, family);
|
|
if (hp == NULL)
|
|
{
|
|
/* XXX: Could be a temporary error on forward lookup */
|
|
*may_be_forged = true;
|
|
}
|
|
else
|
|
{
|
|
for (ha = hp->h_addr_list; *ha != NULL; ha++)
|
|
{
|
|
if (addrcmp(hp, *ha, &RealHostAddr) == 0)
|
|
break;
|
|
}
|
|
*may_be_forged = *ha == NULL;
|
|
#if NETINET6
|
|
freehostent(hp);
|
|
hp = NULL;
|
|
#endif /* NETINET6 */
|
|
}
|
|
}
|
|
|
|
if (TimeOuts.to_ident == 0)
|
|
goto noident;
|
|
|
|
lalen = sizeof(la);
|
|
switch (RealHostAddr.sa.sa_family)
|
|
{
|
|
#if NETINET
|
|
case AF_INET:
|
|
if (getsockname(fd, &la.sa, &lalen) < 0 ||
|
|
lalen <= 0 ||
|
|
la.sa.sa_family != AF_INET)
|
|
{
|
|
/* no ident info */
|
|
goto noident;
|
|
}
|
|
port = RealHostAddr.sin.sin_port;
|
|
|
|
/* create ident query */
|
|
(void) sm_snprintf(ibuf, sizeof(ibuf), "%d,%d\r\n",
|
|
ntohs(RealHostAddr.sin.sin_port),
|
|
ntohs(la.sin.sin_port));
|
|
|
|
/* create local address */
|
|
la.sin.sin_port = 0;
|
|
|
|
/* create foreign address */
|
|
# ifdef NO_GETSERVBYNAME
|
|
RealHostAddr.sin.sin_port = htons(113);
|
|
# else /* NO_GETSERVBYNAME */
|
|
|
|
/*
|
|
** getservbyname() consumes about 5% of the time
|
|
** when receiving a small message (almost all of the time
|
|
** spent in this routine).
|
|
** Hence we store the port in a static variable
|
|
** to save this time.
|
|
** The portnumber shouldn't change very often...
|
|
** This code makes the assumption that the port number
|
|
** is not 0.
|
|
*/
|
|
|
|
if (port4 == 0)
|
|
{
|
|
sp = getservbyname("auth", "tcp");
|
|
if (sp != NULL)
|
|
port4 = sp->s_port;
|
|
else
|
|
port4 = htons(113);
|
|
}
|
|
RealHostAddr.sin.sin_port = port4;
|
|
break;
|
|
# endif /* NO_GETSERVBYNAME */
|
|
#endif /* NETINET */
|
|
|
|
#if NETINET6
|
|
case AF_INET6:
|
|
if (getsockname(fd, &la.sa, &lalen) < 0 ||
|
|
lalen <= 0 ||
|
|
la.sa.sa_family != AF_INET6)
|
|
{
|
|
/* no ident info */
|
|
goto noident;
|
|
}
|
|
port = RealHostAddr.sin6.sin6_port;
|
|
|
|
/* create ident query */
|
|
(void) sm_snprintf(ibuf, sizeof(ibuf), "%d,%d\r\n",
|
|
ntohs(RealHostAddr.sin6.sin6_port),
|
|
ntohs(la.sin6.sin6_port));
|
|
|
|
/* create local address */
|
|
la.sin6.sin6_port = 0;
|
|
|
|
/* create foreign address */
|
|
# ifdef NO_GETSERVBYNAME
|
|
RealHostAddr.sin6.sin6_port = htons(113);
|
|
# else /* NO_GETSERVBYNAME */
|
|
if (port6 == 0)
|
|
{
|
|
sp = getservbyname("auth", "tcp");
|
|
if (sp != NULL)
|
|
port6 = sp->s_port;
|
|
else
|
|
port6 = htons(113);
|
|
}
|
|
RealHostAddr.sin6.sin6_port = port6;
|
|
break;
|
|
# endif /* NO_GETSERVBYNAME */
|
|
#endif /* NETINET6 */
|
|
default:
|
|
/* no ident info */
|
|
goto noident;
|
|
}
|
|
|
|
s = -1;
|
|
if (setjmp(CtxAuthTimeout) != 0)
|
|
{
|
|
if (s >= 0)
|
|
(void) close(s);
|
|
goto noident;
|
|
}
|
|
|
|
/* put a timeout around the whole thing */
|
|
ev = sm_setevent(TimeOuts.to_ident, authtimeout, 0);
|
|
|
|
/* connect to foreign IDENT server using same address as SMTP socket */
|
|
s = socket(la.sa.sa_family, SOCK_STREAM, 0);
|
|
if (s < 0)
|
|
{
|
|
sm_clrevent(ev);
|
|
goto noident;
|
|
}
|
|
if (bind(s, &la.sa, lalen) < 0 ||
|
|
connect(s, &RealHostAddr.sa, lalen) < 0)
|
|
goto closeident;
|
|
|
|
if (tTd(9, 10))
|
|
sm_dprintf("getauthinfo: sent %s", ibuf);
|
|
|
|
/* send query */
|
|
if (write(s, ibuf, strlen(ibuf)) < 0)
|
|
goto closeident;
|
|
|
|
/* get result */
|
|
p = &ibuf[0];
|
|
nleft = sizeof(ibuf) - 1;
|
|
while ((i = read(s, p, nleft)) > 0)
|
|
{
|
|
char *s;
|
|
|
|
p += i;
|
|
nleft -= i;
|
|
*p = '\0';
|
|
if ((s = strchr(ibuf, '\n')) != NULL)
|
|
{
|
|
if (p > s + 1)
|
|
{
|
|
p = s + 1;
|
|
*p = '\0';
|
|
}
|
|
break;
|
|
}
|
|
if (nleft <= 0)
|
|
break;
|
|
}
|
|
(void) close(s);
|
|
sm_clrevent(ev);
|
|
if (i < 0 || p == &ibuf[0])
|
|
goto noident;
|
|
|
|
if (p >= &ibuf[2] && *--p == '\n' && *--p == '\r')
|
|
p--;
|
|
*++p = '\0';
|
|
|
|
if (tTd(9, 3))
|
|
sm_dprintf("getauthinfo: got %s\n", ibuf);
|
|
|
|
/* parse result */
|
|
p = strchr(ibuf, ':');
|
|
if (p == NULL)
|
|
{
|
|
/* malformed response */
|
|
goto noident;
|
|
}
|
|
while (isascii(*++p) && isspace(*p))
|
|
continue;
|
|
if (sm_strncasecmp(p, "userid", 6) != 0)
|
|
{
|
|
/* presumably an error string */
|
|
goto noident;
|
|
}
|
|
p += 6;
|
|
while (isascii(*p) && isspace(*p))
|
|
p++;
|
|
if (*p++ != ':')
|
|
{
|
|
/* either useridxx or malformed response */
|
|
goto noident;
|
|
}
|
|
|
|
/* p now points to the OSTYPE field */
|
|
while (isascii(*p) && isspace(*p))
|
|
p++;
|
|
ostype = p;
|
|
p = strchr(p, ':');
|
|
if (p == NULL)
|
|
{
|
|
/* malformed response */
|
|
goto noident;
|
|
}
|
|
else
|
|
{
|
|
char *charset;
|
|
|
|
*p = '\0';
|
|
charset = strchr(ostype, ',');
|
|
if (charset != NULL)
|
|
*charset = '\0';
|
|
}
|
|
|
|
/* 1413 says don't do this -- but it's broken otherwise */
|
|
while (isascii(*++p) && isspace(*p))
|
|
continue;
|
|
|
|
/* p now points to the authenticated name -- copy carefully */
|
|
if (sm_strncasecmp(ostype, "other", 5) == 0 &&
|
|
(ostype[5] == ' ' || ostype[5] == '\0'))
|
|
{
|
|
(void) sm_strlcpy(hbuf, "IDENT:", sizeof(hbuf));
|
|
cleanstrcpy(&hbuf[6], p, MAXAUTHINFO);
|
|
}
|
|
else
|
|
cleanstrcpy(hbuf, p, MAXAUTHINFO);
|
|
len = strlen(hbuf);
|
|
(void) sm_strlcpyn(&hbuf[len], sizeof(hbuf) - len, 2, "@",
|
|
RealHostName == NULL ? "localhost" : RealHostName);
|
|
goto postident;
|
|
|
|
closeident:
|
|
(void) close(s);
|
|
sm_clrevent(ev);
|
|
|
|
noident:
|
|
/* put back the original incoming port */
|
|
switch (RealHostAddr.sa.sa_family)
|
|
{
|
|
#if NETINET
|
|
case AF_INET:
|
|
if (port > 0)
|
|
RealHostAddr.sin.sin_port = port;
|
|
break;
|
|
#endif /* NETINET */
|
|
|
|
#if NETINET6
|
|
case AF_INET6:
|
|
if (port > 0)
|
|
RealHostAddr.sin6.sin6_port = port;
|
|
break;
|
|
#endif /* NETINET6 */
|
|
}
|
|
|
|
if (RealHostName == NULL)
|
|
{
|
|
if (tTd(9, 1))
|
|
sm_dprintf("getauthinfo: NULL\n");
|
|
return NULL;
|
|
}
|
|
(void) sm_strlcpy(hbuf, RealHostName, sizeof(hbuf));
|
|
|
|
postident:
|
|
#if IP_SRCROUTE
|
|
# ifndef GET_IPOPT_DST
|
|
# define GET_IPOPT_DST(dst) (dst)
|
|
# endif /* ! GET_IPOPT_DST */
|
|
/*
|
|
** Extract IP source routing information.
|
|
**
|
|
** Format of output for a connection from site a through b
|
|
** through c to d:
|
|
** loose: @site-c@site-b:site-a
|
|
** strict: !@site-c@site-b:site-a
|
|
**
|
|
** o - pointer within ipopt_list structure.
|
|
** q - pointer within ls/ss rr route data
|
|
** p - pointer to hbuf
|
|
*/
|
|
|
|
if (RealHostAddr.sa.sa_family == AF_INET)
|
|
{
|
|
SOCKOPT_LEN_T ipoptlen;
|
|
int j;
|
|
unsigned char *q;
|
|
unsigned char *o;
|
|
int l;
|
|
struct IPOPTION ipopt;
|
|
|
|
ipoptlen = sizeof(ipopt);
|
|
if (getsockopt(fd, IPPROTO_IP, IP_OPTIONS,
|
|
(char *) &ipopt, &ipoptlen) < 0)
|
|
goto noipsr;
|
|
if (ipoptlen == 0)
|
|
goto noipsr;
|
|
o = (unsigned char *) ipopt.IP_LIST;
|
|
while (o != NULL && o < (unsigned char *) &ipopt + ipoptlen)
|
|
{
|
|
switch (*o)
|
|
{
|
|
case IPOPT_EOL:
|
|
o = NULL;
|
|
break;
|
|
|
|
case IPOPT_NOP:
|
|
o++;
|
|
break;
|
|
|
|
case IPOPT_SSRR:
|
|
case IPOPT_LSRR:
|
|
/*
|
|
** Source routing.
|
|
** o[0] is the option type (loose/strict).
|
|
** o[1] is the length of this option,
|
|
** including option type and
|
|
** length.
|
|
** o[2] is the pointer into the route
|
|
** data.
|
|
** o[3] begins the route data.
|
|
*/
|
|
|
|
p = &hbuf[strlen(hbuf)];
|
|
l = sizeof(hbuf) - (hbuf - p) - 6;
|
|
(void) sm_snprintf(p, SPACELEFT(hbuf, p),
|
|
" [%s@%.*s",
|
|
*o == IPOPT_SSRR ? "!" : "",
|
|
l > 240 ? 120 : l / 2,
|
|
inet_ntoa(GET_IPOPT_DST(ipopt.IP_DST)));
|
|
i = strlen(p);
|
|
p += i;
|
|
l -= strlen(p);
|
|
|
|
j = o[1] / sizeof(struct in_addr) - 1;
|
|
|
|
/* q skips length and router pointer to data */
|
|
q = &o[3];
|
|
for ( ; j >= 0; j--)
|
|
{
|
|
struct in_addr addr;
|
|
|
|
memcpy(&addr, q, sizeof(addr));
|
|
(void) sm_snprintf(p,
|
|
SPACELEFT(hbuf, p),
|
|
"%c%.*s",
|
|
j != 0 ? '@' : ':',
|
|
l > 240 ? 120 :
|
|
j == 0 ? l : l / 2,
|
|
inet_ntoa(addr));
|
|
i = strlen(p);
|
|
p += i;
|
|
l -= i + 1;
|
|
q += sizeof(struct in_addr);
|
|
}
|
|
o += o[1];
|
|
break;
|
|
|
|
default:
|
|
/* Skip over option */
|
|
o += o[1];
|
|
break;
|
|
}
|
|
}
|
|
(void) sm_snprintf(p, SPACELEFT(hbuf, p), "]");
|
|
goto postipsr;
|
|
}
|
|
|
|
noipsr:
|
|
#endif /* IP_SRCROUTE */
|
|
if (RealHostName != NULL && RealHostName[0] != '[')
|
|
{
|
|
p = &hbuf[strlen(hbuf)];
|
|
(void) sm_snprintf(p, SPACELEFT(hbuf, p), " [%.100s]",
|
|
anynet_ntoa(&RealHostAddr));
|
|
}
|
|
if (*may_be_forged)
|
|
{
|
|
p = &hbuf[strlen(hbuf)];
|
|
(void) sm_strlcpy(p, " (may be forged)", SPACELEFT(hbuf, p));
|
|
macdefine(&BlankEnvelope.e_macro, A_PERM,
|
|
macid("{client_resolve}"), "FORGED");
|
|
}
|
|
|
|
#if IP_SRCROUTE
|
|
postipsr:
|
|
#endif /* IP_SRCROUTE */
|
|
|
|
/* put back the original incoming port */
|
|
switch (RealHostAddr.sa.sa_family)
|
|
{
|
|
#if NETINET
|
|
case AF_INET:
|
|
if (port > 0)
|
|
RealHostAddr.sin.sin_port = port;
|
|
break;
|
|
#endif /* NETINET */
|
|
|
|
#if NETINET6
|
|
case AF_INET6:
|
|
if (port > 0)
|
|
RealHostAddr.sin6.sin6_port = port;
|
|
break;
|
|
#endif /* NETINET6 */
|
|
}
|
|
|
|
if (tTd(9, 1))
|
|
sm_dprintf("getauthinfo: %s\n", hbuf);
|
|
return hbuf;
|
|
}
|
|
/*
|
|
** HOST_MAP_LOOKUP -- turn a hostname into canonical form
|
|
**
|
|
** Parameters:
|
|
** map -- a pointer to this map.
|
|
** name -- the (presumably unqualified) hostname.
|
|
** av -- unused -- for compatibility with other mapping
|
|
** functions.
|
|
** statp -- an exit status (out parameter) -- set to
|
|
** EX_TEMPFAIL if the name server is unavailable.
|
|
**
|
|
** Returns:
|
|
** The mapping, if found.
|
|
** NULL if no mapping found.
|
|
**
|
|
** Side Effects:
|
|
** Looks up the host specified in hbuf. If it is not
|
|
** the canonical name for that host, return the canonical
|
|
** name (unless MF_MATCHONLY is set, which will cause the
|
|
** status only to be returned).
|
|
*/
|
|
|
|
char *
|
|
host_map_lookup(map, name, av, statp)
|
|
MAP *map;
|
|
char *name;
|
|
char **av;
|
|
int *statp;
|
|
{
|
|
register struct hostent *hp;
|
|
#if NETINET
|
|
struct in_addr in_addr;
|
|
#endif /* NETINET */
|
|
#if NETINET6
|
|
struct in6_addr in6_addr;
|
|
#endif /* NETINET6 */
|
|
char *cp, *ans = NULL;
|
|
register STAB *s;
|
|
time_t now;
|
|
#if NAMED_BIND
|
|
time_t SM_NONVOLATILE retrans = 0;
|
|
int SM_NONVOLATILE retry = 0;
|
|
#endif /* NAMED_BIND */
|
|
char hbuf[MAXNAME + 1];
|
|
|
|
/*
|
|
** See if we have already looked up this name. If so, just
|
|
** return it (unless expired).
|
|
*/
|
|
|
|
now = curtime();
|
|
s = stab(name, ST_NAMECANON, ST_ENTER);
|
|
if (bitset(NCF_VALID, s->s_namecanon.nc_flags) &&
|
|
s->s_namecanon.nc_exp >= now)
|
|
{
|
|
if (tTd(9, 1))
|
|
sm_dprintf("host_map_lookup(%s) => CACHE %s\n",
|
|
name,
|
|
s->s_namecanon.nc_cname == NULL
|
|
? "NULL"
|
|
: s->s_namecanon.nc_cname);
|
|
errno = s->s_namecanon.nc_errno;
|
|
SM_SET_H_ERRNO(s->s_namecanon.nc_herrno);
|
|
*statp = s->s_namecanon.nc_stat;
|
|
if (*statp == EX_TEMPFAIL)
|
|
{
|
|
CurEnv->e_status = "4.4.3";
|
|
message("851 %s: Name server timeout",
|
|
shortenstring(name, 33));
|
|
}
|
|
if (*statp != EX_OK)
|
|
return NULL;
|
|
if (s->s_namecanon.nc_cname == NULL)
|
|
{
|
|
syserr("host_map_lookup(%s): bogus NULL cache entry, errno=%d, h_errno=%d",
|
|
name,
|
|
s->s_namecanon.nc_errno,
|
|
s->s_namecanon.nc_herrno);
|
|
return NULL;
|
|
}
|
|
if (bitset(MF_MATCHONLY, map->map_mflags))
|
|
cp = map_rewrite(map, name, strlen(name), NULL);
|
|
else
|
|
cp = map_rewrite(map,
|
|
s->s_namecanon.nc_cname,
|
|
strlen(s->s_namecanon.nc_cname),
|
|
av);
|
|
return cp;
|
|
}
|
|
|
|
/*
|
|
** If we are running without a regular network connection (usually
|
|
** dial-on-demand) and we are just queueing, we want to avoid DNS
|
|
** lookups because those could try to connect to a server.
|
|
*/
|
|
|
|
if (CurEnv->e_sendmode == SM_DEFER &&
|
|
bitset(MF_DEFER, map->map_mflags))
|
|
{
|
|
if (tTd(9, 1))
|
|
sm_dprintf("host_map_lookup(%s) => DEFERRED\n", name);
|
|
*statp = EX_TEMPFAIL;
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
** If first character is a bracket, then it is an address
|
|
** lookup. Address is copied into a temporary buffer to
|
|
** strip the brackets and to preserve name if address is
|
|
** unknown.
|
|
*/
|
|
|
|
if (tTd(9, 1))
|
|
sm_dprintf("host_map_lookup(%s) => ", name);
|
|
#if NAMED_BIND
|
|
if (map->map_timeout > 0)
|
|
{
|
|
retrans = _res.retrans;
|
|
_res.retrans = map->map_timeout;
|
|
}
|
|
if (map->map_retry > 0)
|
|
{
|
|
retry = _res.retry;
|
|
_res.retry = map->map_retry;
|
|
}
|
|
#endif /* NAMED_BIND */
|
|
|
|
/* set default TTL */
|
|
s->s_namecanon.nc_exp = now + SM_DEFAULT_TTL;
|
|
if (*name != '[')
|
|
{
|
|
int ttl;
|
|
|
|
(void) sm_strlcpy(hbuf, name, sizeof(hbuf));
|
|
if (getcanonname(hbuf, sizeof(hbuf) - 1, !HasWildcardMX, &ttl))
|
|
{
|
|
ans = hbuf;
|
|
if (ttl > 0)
|
|
s->s_namecanon.nc_exp = now + SM_MIN(ttl,
|
|
SM_DEFAULT_TTL);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ((cp = strchr(name, ']')) == NULL)
|
|
{
|
|
if (tTd(9, 1))
|
|
sm_dprintf("FAILED\n");
|
|
return NULL;
|
|
}
|
|
*cp = '\0';
|
|
|
|
hp = NULL;
|
|
#if NETINET
|
|
if ((in_addr.s_addr = inet_addr(&name[1])) != INADDR_NONE)
|
|
hp = sm_gethostbyaddr((char *)&in_addr,
|
|
INADDRSZ, AF_INET);
|
|
#endif /* NETINET */
|
|
#if NETINET6
|
|
if (hp == NULL &&
|
|
anynet_pton(AF_INET6, &name[1], &in6_addr) == 1)
|
|
hp = sm_gethostbyaddr((char *)&in6_addr,
|
|
IN6ADDRSZ, AF_INET6);
|
|
#endif /* NETINET6 */
|
|
*cp = ']';
|
|
|
|
if (hp != NULL)
|
|
{
|
|
/* found a match -- copy out */
|
|
ans = denlstring((char *) hp->h_name, true, true);
|
|
#if NETINET6
|
|
if (ans == hp->h_name)
|
|
{
|
|
static char n[MAXNAME + 1];
|
|
|
|
/* hp->h_name is about to disappear */
|
|
(void) sm_strlcpy(n, ans, sizeof(n));
|
|
ans = n;
|
|
}
|
|
freehostent(hp);
|
|
hp = NULL;
|
|
#endif /* NETINET6 */
|
|
}
|
|
}
|
|
#if NAMED_BIND
|
|
if (map->map_timeout > 0)
|
|
_res.retrans = retrans;
|
|
if (map->map_retry > 0)
|
|
_res.retry = retry;
|
|
#endif /* NAMED_BIND */
|
|
|
|
s->s_namecanon.nc_flags |= NCF_VALID; /* will be soon */
|
|
|
|
/* Found an answer */
|
|
if (ans != NULL)
|
|
{
|
|
s->s_namecanon.nc_stat = *statp = EX_OK;
|
|
if (s->s_namecanon.nc_cname != NULL)
|
|
sm_free(s->s_namecanon.nc_cname);
|
|
s->s_namecanon.nc_cname = sm_strdup_x(ans);
|
|
if (bitset(MF_MATCHONLY, map->map_mflags))
|
|
cp = map_rewrite(map, name, strlen(name), NULL);
|
|
else
|
|
cp = map_rewrite(map, ans, strlen(ans), av);
|
|
if (tTd(9, 1))
|
|
sm_dprintf("FOUND %s\n", ans);
|
|
return cp;
|
|
}
|
|
|
|
|
|
/* No match found */
|
|
s->s_namecanon.nc_errno = errno;
|
|
#if NAMED_BIND
|
|
s->s_namecanon.nc_herrno = h_errno;
|
|
if (tTd(9, 1))
|
|
sm_dprintf("FAIL (%d)\n", h_errno);
|
|
switch (h_errno)
|
|
{
|
|
case TRY_AGAIN:
|
|
if (UseNameServer)
|
|
{
|
|
CurEnv->e_status = "4.4.3";
|
|
message("851 %s: Name server timeout",
|
|
shortenstring(name, 33));
|
|
}
|
|
*statp = EX_TEMPFAIL;
|
|
break;
|
|
|
|
case HOST_NOT_FOUND:
|
|
case NO_DATA:
|
|
*statp = EX_NOHOST;
|
|
break;
|
|
|
|
case NO_RECOVERY:
|
|
*statp = EX_SOFTWARE;
|
|
break;
|
|
|
|
default:
|
|
*statp = EX_UNAVAILABLE;
|
|
break;
|
|
}
|
|
#else /* NAMED_BIND */
|
|
if (tTd(9, 1))
|
|
sm_dprintf("FAIL\n");
|
|
*statp = EX_NOHOST;
|
|
#endif /* NAMED_BIND */
|
|
s->s_namecanon.nc_stat = *statp;
|
|
return NULL;
|
|
}
|
|
/*
|
|
** HOST_MAP_INIT -- initialize host class structures
|
|
**
|
|
** Parameters:
|
|
** map -- a pointer to this map.
|
|
** args -- argument string.
|
|
**
|
|
** Returns:
|
|
** true.
|
|
*/
|
|
|
|
bool
|
|
host_map_init(map, args)
|
|
MAP *map;
|
|
char *args;
|
|
{
|
|
register char *p = args;
|
|
|
|
for (;;)
|
|
{
|
|
while (isascii(*p) && isspace(*p))
|
|
p++;
|
|
if (*p != '-')
|
|
break;
|
|
switch (*++p)
|
|
{
|
|
case 'a':
|
|
map->map_app = ++p;
|
|
break;
|
|
|
|
case 'T':
|
|
map->map_tapp = ++p;
|
|
break;
|
|
|
|
case 'm':
|
|
map->map_mflags |= MF_MATCHONLY;
|
|
break;
|
|
|
|
case 't':
|
|
map->map_mflags |= MF_NODEFER;
|
|
break;
|
|
|
|
case 'S': /* only for consistency */
|
|
map->map_spacesub = *++p;
|
|
break;
|
|
|
|
case 'D':
|
|
map->map_mflags |= MF_DEFER;
|
|
break;
|
|
|
|
case 'd':
|
|
{
|
|
char *h;
|
|
|
|
while (isascii(*++p) && isspace(*p))
|
|
continue;
|
|
h = strchr(p, ' ');
|
|
if (h != NULL)
|
|
*h = '\0';
|
|
map->map_timeout = convtime(p, 's');
|
|
if (h != NULL)
|
|
*h = ' ';
|
|
}
|
|
break;
|
|
|
|
case 'r':
|
|
while (isascii(*++p) && isspace(*p))
|
|
continue;
|
|
map->map_retry = atoi(p);
|
|
break;
|
|
}
|
|
while (*p != '\0' && !(isascii(*p) && isspace(*p)))
|
|
p++;
|
|
if (*p != '\0')
|
|
*p++ = '\0';
|
|
}
|
|
if (map->map_app != NULL)
|
|
map->map_app = newstr(map->map_app);
|
|
if (map->map_tapp != NULL)
|
|
map->map_tapp = newstr(map->map_tapp);
|
|
return true;
|
|
}
|
|
|
|
#if NETINET6
|
|
/*
|
|
** ANYNET_NTOP -- convert an IPv6 network address to printable form.
|
|
**
|
|
** Parameters:
|
|
** s6a -- a pointer to an in6_addr structure.
|
|
** dst -- buffer to store result in
|
|
** dst_len -- size of dst buffer
|
|
**
|
|
** Returns:
|
|
** A printable version of that structure.
|
|
*/
|
|
|
|
char *
|
|
anynet_ntop(s6a, dst, dst_len)
|
|
struct in6_addr *s6a;
|
|
char *dst;
|
|
size_t dst_len;
|
|
{
|
|
register char *ap;
|
|
|
|
if (IN6_IS_ADDR_V4MAPPED(s6a))
|
|
ap = (char *) inet_ntop(AF_INET,
|
|
&s6a->s6_addr[IN6ADDRSZ - INADDRSZ],
|
|
dst, dst_len);
|
|
else
|
|
{
|
|
char *d;
|
|
size_t sz;
|
|
|
|
/* Save pointer to beginning of string */
|
|
d = dst;
|
|
|
|
/* Add IPv6: protocol tag */
|
|
sz = sm_strlcpy(dst, "IPv6:", dst_len);
|
|
if (sz >= dst_len)
|
|
return NULL;
|
|
dst += sz;
|
|
dst_len -= sz;
|
|
ap = (char *) inet_ntop(AF_INET6, s6a, dst, dst_len);
|
|
|
|
/* Restore pointer to beginning of string */
|
|
if (ap != NULL)
|
|
ap = d;
|
|
}
|
|
return ap;
|
|
}
|
|
|
|
/*
|
|
** ANYNET_PTON -- convert printed form to network address.
|
|
**
|
|
** Wrapper for inet_pton() which handles IPv6: labels.
|
|
**
|
|
** Parameters:
|
|
** family -- address family
|
|
** src -- string
|
|
** dst -- destination address structure
|
|
**
|
|
** Returns:
|
|
** 1 if the address was valid
|
|
** 0 if the address wasn't parseable
|
|
** -1 if error
|
|
*/
|
|
|
|
int
|
|
anynet_pton(family, src, dst)
|
|
int family;
|
|
const char *src;
|
|
void *dst;
|
|
{
|
|
if (family == AF_INET6 && sm_strncasecmp(src, "IPv6:", 5) == 0)
|
|
src += 5;
|
|
return inet_pton(family, src, dst);
|
|
}
|
|
#endif /* NETINET6 */
|
|
/*
|
|
** ANYNET_NTOA -- convert a network address to printable form.
|
|
**
|
|
** Parameters:
|
|
** sap -- a pointer to a sockaddr structure.
|
|
**
|
|
** Returns:
|
|
** A printable version of that sockaddr.
|
|
*/
|
|
|
|
#ifdef USE_SOCK_STREAM
|
|
|
|
# if NETLINK
|
|
# include <net/if_dl.h>
|
|
# endif /* NETLINK */
|
|
|
|
char *
|
|
anynet_ntoa(sap)
|
|
register SOCKADDR *sap;
|
|
{
|
|
register char *bp;
|
|
register char *ap;
|
|
int l;
|
|
static char buf[100];
|
|
|
|
/* check for null/zero family */
|
|
if (sap == NULL)
|
|
return "NULLADDR";
|
|
if (sap->sa.sa_family == 0)
|
|
return "0";
|
|
|
|
switch (sap->sa.sa_family)
|
|
{
|
|
# if NETUNIX
|
|
case AF_UNIX:
|
|
if (sap->sunix.sun_path[0] != '\0')
|
|
(void) sm_snprintf(buf, sizeof(buf), "[UNIX: %.64s]",
|
|
sap->sunix.sun_path);
|
|
else
|
|
(void) sm_strlcpy(buf, "[UNIX: localhost]", sizeof(buf));
|
|
return buf;
|
|
# endif /* NETUNIX */
|
|
|
|
# if NETINET
|
|
case AF_INET:
|
|
return (char *) inet_ntoa(sap->sin.sin_addr);
|
|
# endif /* NETINET */
|
|
|
|
# if NETINET6
|
|
case AF_INET6:
|
|
ap = anynet_ntop(&sap->sin6.sin6_addr, buf, sizeof(buf));
|
|
if (ap != NULL)
|
|
return ap;
|
|
break;
|
|
# endif /* NETINET6 */
|
|
|
|
# if NETLINK
|
|
case AF_LINK:
|
|
(void) sm_snprintf(buf, sizeof(buf), "[LINK: %s]",
|
|
link_ntoa((struct sockaddr_dl *) &sap->sa));
|
|
return buf;
|
|
# endif /* NETLINK */
|
|
default:
|
|
/* this case is needed when nothing is #defined */
|
|
/* in order to keep the switch syntactically correct */
|
|
break;
|
|
}
|
|
|
|
/* unknown family -- just dump bytes */
|
|
(void) sm_snprintf(buf, sizeof(buf), "Family %d: ", sap->sa.sa_family);
|
|
bp = &buf[strlen(buf)];
|
|
ap = sap->sa.sa_data;
|
|
for (l = sizeof(sap->sa.sa_data); --l >= 0; )
|
|
{
|
|
(void) sm_snprintf(bp, SPACELEFT(buf, bp), "%02x:",
|
|
*ap++ & 0377);
|
|
bp += 3;
|
|
}
|
|
*--bp = '\0';
|
|
return buf;
|
|
}
|
|
/*
|
|
** HOSTNAMEBYANYADDR -- return name of host based on address
|
|
**
|
|
** Parameters:
|
|
** sap -- SOCKADDR pointer
|
|
**
|
|
** Returns:
|
|
** text representation of host name.
|
|
**
|
|
** Side Effects:
|
|
** none.
|
|
*/
|
|
|
|
char *
|
|
hostnamebyanyaddr(sap)
|
|
register SOCKADDR *sap;
|
|
{
|
|
register struct hostent *hp;
|
|
# if NAMED_BIND
|
|
int saveretry;
|
|
# endif /* NAMED_BIND */
|
|
# if NETINET6
|
|
struct in6_addr in6_addr;
|
|
# endif /* NETINET6 */
|
|
|
|
# if NAMED_BIND
|
|
/* shorten name server timeout to avoid higher level timeouts */
|
|
saveretry = _res.retry;
|
|
if (_res.retry * _res.retrans > 20)
|
|
_res.retry = 20 / _res.retrans;
|
|
# endif /* NAMED_BIND */
|
|
|
|
switch (sap->sa.sa_family)
|
|
{
|
|
# if NETINET
|
|
case AF_INET:
|
|
hp = sm_gethostbyaddr((char *) &sap->sin.sin_addr,
|
|
INADDRSZ, AF_INET);
|
|
break;
|
|
# endif /* NETINET */
|
|
|
|
# if NETINET6
|
|
case AF_INET6:
|
|
hp = sm_gethostbyaddr((char *) &sap->sin6.sin6_addr,
|
|
IN6ADDRSZ, AF_INET6);
|
|
break;
|
|
# endif /* NETINET6 */
|
|
|
|
# if NETISO
|
|
case AF_ISO:
|
|
hp = sm_gethostbyaddr((char *) &sap->siso.siso_addr,
|
|
sizeof(sap->siso.siso_addr), AF_ISO);
|
|
break;
|
|
# endif /* NETISO */
|
|
|
|
# if NETUNIX
|
|
case AF_UNIX:
|
|
hp = NULL;
|
|
break;
|
|
# endif /* NETUNIX */
|
|
|
|
default:
|
|
hp = sm_gethostbyaddr(sap->sa.sa_data, sizeof(sap->sa.sa_data),
|
|
sap->sa.sa_family);
|
|
break;
|
|
}
|
|
|
|
# if NAMED_BIND
|
|
_res.retry = saveretry;
|
|
# endif /* NAMED_BIND */
|
|
|
|
# if NETINET || NETINET6
|
|
if (hp != NULL && hp->h_name[0] != '['
|
|
# if NETINET6
|
|
&& inet_pton(AF_INET6, hp->h_name, &in6_addr) != 1
|
|
# endif /* NETINET6 */
|
|
# if NETINET
|
|
&& inet_addr(hp->h_name) == INADDR_NONE
|
|
# endif /* NETINET */
|
|
)
|
|
{
|
|
char *name;
|
|
|
|
name = denlstring((char *) hp->h_name, true, true);
|
|
# if NETINET6
|
|
if (name == hp->h_name)
|
|
{
|
|
static char n[MAXNAME + 1];
|
|
|
|
/* Copy the string, hp->h_name is about to disappear */
|
|
(void) sm_strlcpy(n, name, sizeof(n));
|
|
name = n;
|
|
}
|
|
freehostent(hp);
|
|
# endif /* NETINET6 */
|
|
return name;
|
|
}
|
|
# endif /* NETINET || NETINET6 */
|
|
|
|
# if NETINET6
|
|
if (hp != NULL)
|
|
{
|
|
freehostent(hp);
|
|
hp = NULL;
|
|
}
|
|
# endif /* NETINET6 */
|
|
|
|
# if NETUNIX
|
|
if (sap->sa.sa_family == AF_UNIX && sap->sunix.sun_path[0] == '\0')
|
|
return "localhost";
|
|
# endif /* NETUNIX */
|
|
{
|
|
static char buf[203];
|
|
|
|
(void) sm_snprintf(buf, sizeof(buf), "[%.200s]",
|
|
anynet_ntoa(sap));
|
|
return buf;
|
|
}
|
|
}
|
|
#endif /* USE_SOCK_STREAM */
|