#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
#if defined(USE_SOCK_STREAM)
# if NETINET || NETINET6
# include <arpa/inet.h>
# endif
# if NAMED_BIND
# ifndef NO_DATA
# define NO_DATA NO_ADDRESS
# endif
# endif
#endif
#if STARTTLS
# include <openssl/rand.h>
#endif
#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
# else
# include <netinet/ip_var.h>
# ifndef IPOPTION
# define IPOPTION ipoption
# define IP_LIST ipopt_list
# define IP_DST ipopt_dst
# endif
# endif
#endif
#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));
static int NDaemons = 0;
static time_t NextDiskSpaceCheck = 0;
BITMAP256 *
getrequests(e)
ENVELOPE *e;
{
int t;
int idx, curdaemon = -1;
int i, olddaemon = 0;
#if XDEBUG
bool j_has_dot;
#endif
char status[MAXLINE];
SOCKADDR sa;
SOCKADDR_LEN_T len = sizeof(sa);
#if _FFR_QUEUE_RUN_PARANOIA
time_t lastrun;
#endif
# if NETUNIX
extern int ControlSocket;
# endif
extern ENVELOPE BlankEnvelope;
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;
}
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));
}
}
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));
(void) sm_signal(SIGCHLD, reapchild);
log_sendmail_pid(e);
#if XDEBUG
{
char jbuf[MAXHOSTNAMELEN];
expand("\201j", jbuf, sizeof(jbuf), e);
j_has_dot = strchr(jbuf, '.') != NULL;
}
#endif
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
(void) sm_blocksignal(SIGALRM);
CHECK_RESTART;
for (idx = 0; idx < NDaemons; idx++)
{
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)
{
(void) close(Daemons[idx].d_socket);
Daemons[idx].d_socket = -1;
}
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);
(void) opendaemonsocket(&Daemons[idx], false);
Daemons[idx].d_firsttime = false;
}
}
CHECK_RESTART;
getrequests_checkdiskspace(e);
#if XDEBUG
{
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
#if 0
if (DaemonSocket >= 0 &&
SetNonBlocking(DaemonSocket, false) < 0)
log an error here;
#endif
(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++)
{
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
timeout.tv_sec = 5;
timeout.tv_usec = 0;
t = select(highest + 1, FDSET_CAST &readfds,
NULL, NULL, &timeout);
CHECK_RESTART;
curdaemon = -1;
if (doqueuerun())
{
(void) runqueue(true, false, false, false);
#if _FFR_QUEUE_RUN_PARANOIA
lastrun = now;
#endif
}
#if _FFR_QUEUE_RUN_PARANOIA
else if (CheckQueueRunners > 0 && QueueIntvl > 0 &&
lastrun + QueueIntvl + CheckQueueRunners < now)
{
(void) checkqueuerunner();
lastrun = now;
}
#endif
if (t <= 0)
{
timedout = true;
break;
}
control = false;
errno = 0;
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 (t >= 0 &&
(lotherend == 0 ||
# ifdef BSD4_4_SOCKADDR
RealHostAddr.sa.sa_len == 0 ||
# endif
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 (t >= 0 &&
(lotherend == 0 ||
# ifdef BSD4_4_SOCKADDR
sa_un.sun_len == 0 ||
# endif
sa_un.sun_family != AF_UNIX))
{
(void) close(t);
t = -1;
errno = EINVAL;
}
if (t >= 0)
control = true;
}
#else
if (curdaemon == -1)
{
continue;
}
#endif
if (t >= 0 || errno != EINTR)
break;
}
if (timedout)
{
timedout = false;
continue;
}
save_errno = errno;
(void) sm_blocksignal(SIGALRM);
if (t < 0)
{
errno = save_errno;
if (save_errno == EINTR
#ifdef EAGAIN
|| save_errno == EAGAIN
#endif
#ifdef ECONNABORTED
|| save_errno == ECONNABORTED
#endif
#ifdef EWOULDBLOCK
|| save_errno == EWOULDBLOCK
#endif
)
continue;
syserr("getrequests: accept");
if (curdaemon >= 0)
{
(void) close(Daemons[curdaemon].d_socket);
Daemons[curdaemon].d_socket = -1;
#if SO_REUSEADDR_IS_BROKEN
Daemons[curdaemon].d_refuse_connections_until =
curtime() + 15;
#endif
}
continue;
}
if (!control)
{
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
#endif
#if NETINET
case AF_INET:
macdefine(&BlankEnvelope.e_macro, A_PERM,
macid("{daemon_family}"), "inet");
break;
#endif
#if NETINET6
case AF_INET6:
macdefine(&BlankEnvelope.e_macro, A_PERM,
macid("{daemon_family}"), "inet6");
break;
#endif
#if NETISO
case AF_ISO:
macdefine(&BlankEnvelope.e_macro, A_PERM,
macid("{daemon_family}"), "iso");
break;
#endif
#if NETNS
case AF_NS:
macdefine(&BlankEnvelope.e_macro, A_PERM,
macid("{daemon_family}"), "ns");
break;
#endif
#if NETX25
case AF_CCITT:
macdefine(&BlankEnvelope.e_macro, A_PERM,
macid("{daemon_family}"), "x.25");
break;
#endif
}
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}"), "");
}
connection_rate_check(&RealHostAddr, NULL);
if (tTd(15, 2))
sm_dprintf("getrequests: forking (fd = %d)\n", t);
#if STARTTLS
seed = get_random();
RAND_seed((void *) &NextDiskSpaceCheck,
sizeof(NextDiskSpaceCheck));
RAND_seed((void *) &now, sizeof(now));
RAND_seed((void *) &seed, sizeof(seed));
#else
(void) get_random();
#endif
#if NAMED_BIND
if (FallbackMX != NULL)
(void) getfallbackmxrr(FallbackMX);
#endif
if (tTd(93, 100))
{
pid = 0;
pipefd[0] = pipefd[1] = -1;
}
else
{
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;
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);
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();
if (control)
{
proc_list_add(CurrentPid,
"console socket child",
PROC_CONTROL_CHILD, 0, -1, NULL);
}
else
{
proc_list_clear();
(void) sm_signal(SIGCHLD, reapchild);
proc_list_add(CurrentPid, "daemon child",
PROC_DAEMON_CHILD, 0, -1, NULL);
QueueIntvl = 0;
#if _FFR_SS_PER_DAEMON
if (Daemons[curdaemon].d_supersafe !=
DPO_NOTSET)
SuperSafe = Daemons[curdaemon].
d_supersafe;
#endif
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;
(void) close(pipefd[1]);
while (read(pipefd[0], &c, 1) < 0 &&
errno == EINTR)
continue;
(void) close(pipefd[0]);
}
if (control)
{
control_command(t, e);
exit(EX_SOFTWARE);
}
p = hostnamebyanyaddr(&RealHostAddr);
if (strlen(p) > MAXNAME)
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
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);
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;
}
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);
if (pipefd[0] != -1)
{
(void) close(pipefd[0]);
pipefd[0] = -1;
}
(void) close(t);
if (pipefd[1] != -1)
{
(void) close(pipefd[1]);
pipefd[1] = -1;
}
}
if (tTd(15, 2))
sm_dprintf("getreq: returning\n");
#if MILTER
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
return &Daemons[curdaemon].d_flags;
}
static void
getrequests_checkdiskspace(e)
ENVELOPE *e;
{
bool logged = false;
int idx;
time_t now;
now = curtime();
if (now < NextDiskSpaceCheck)
return;
if (!enoughdiskspace(0, NULL))
{
for (idx = 0; idx < NDaemons; ++idx)
{
if (bitnset(D_ETRNONLY, Daemons[idx].d_flags))
continue;
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;
if (!logged)
{
if (LogLevel > 8)
sm_syslog(LOG_INFO, NOQID,
"accepting new messages (again)");
logged = true;
}
clrbitn(D_ETRNONLY, Daemons[idx].d_flags);
}
}
NextDiskSpaceCheck = now + 60;
}
#define MAXOPENTRIES 10
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;
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;
}
(void) unlink(d->d_addr.sunix.sun_path);
}
# endif
#endif
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;
}
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
#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
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
#endif
#if NETINET
case AF_INET:
socksize = sizeof(d->d_addr.sin);
break;
#endif
#if NETINET6
case AF_INET6:
socksize = sizeof(d->d_addr.sin6);
break;
#endif
#if NETISO
case AF_ISO:
socksize = sizeof(d->d_addr.siso);
break;
#endif
default:
socksize = sizeof(d->d_addr);
break;
}
if (bind(d->d_socket, &d->d_addr.sa, socksize) < 0)
{
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);
return -1;
}
static unsigned short
setupdaemon(daemonaddr)
SOCKADDR *daemonaddr;
{
unsigned short port;
if (daemonaddr->sa.sa_family == AF_UNSPEC)
{
memset(daemonaddr, '\0', sizeof(*daemonaddr));
#if NETINET
daemonaddr->sa.sa_family = AF_INET;
#endif
}
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
#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
default:
port = 0;
break;
}
if (port == 0)
{
#ifdef NO_GETSERVBYNAME
port = htons(25);
#else
{
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
}
switch (daemonaddr->sa.sa_family)
{
#if NETINET
case AF_INET:
daemonaddr->sin.sin_port = port;
break;
#endif
#if NETINET6
case AF_INET6:
daemonaddr->sin6.sin6_port = port;
break;
#endif
default:
break;
}
return port;
}
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;
}
}
char *
getmodifiers(v, modifiers)
char *v;
BITMAP256 modifiers;
{
int l;
char *h, *f, *flags;
l = 3 * strlen(v) + 3;
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;
}
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;
}
static void
setsockaddroptions(p, d)
char *p;
DAEMON_T *d;
{
#if NETISO
short portno;
#endif
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
#if _FFR_SS_PER_DAEMON
d->d_supersafe = DPO_NOTSET;
#endif
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':
#if !_FFR_DPO_CS
case 'a':
#endif
addr = v;
break;
case 'c':
d->d_maxchildren = atoi(v);
break;
case 'D':
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':
d->d_delayLA = atoi(v);
break;
case 'F':
#if !_FFR_DPO_CS
case 'f':
#endif
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
#endif
#if NETINET
else if (sm_strcasecmp(v, "inet") == 0)
d->d_addr.sa.sa_family = AF_INET;
#endif
#if NETINET6
else if (sm_strcasecmp(v, "inet6") == 0)
d->d_addr.sa.sa_family = AF_INET6;
#endif
#if NETISO
else if (sm_strcasecmp(v, "iso") == 0)
d->d_addr.sa.sa_family = AF_ISO;
#endif
#if NETNS
else if (sm_strcasecmp(v, "ns") == 0)
d->d_addr.sa.sa_family = AF_NS;
#endif
#if NETX25
else if (sm_strcasecmp(v, "x.25") == 0)
d->d_addr.sa.sa_family = AF_CCITT;
#endif
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
d->d_inputfilterlist = v;
break;
#endif
case 'L':
#if !_FFR_DPO_CS
case 'l':
#endif
d->d_listenqueue = atoi(v);
break;
case 'M':
#if !_FFR_DPO_CS
case 'm':
#endif
d->d_mflags = getmodifiers(v, d->d_flags);
break;
case 'N':
#if !_FFR_DPO_CS
case 'n':
#endif
d->d_name = v;
break;
case 'P':
#if !_FFR_DPO_CS
case 'p':
#endif
port = v;
break;
case 'q':
d->d_queueLA = atoi(v);
break;
case 'R':
d->d_tcprcvbufsize = atoi(v);
break;
case 'r':
d->d_refuseLA = atoi(v);
break;
case 'S':
#if !_FFR_DPO_CS
case 's':
#endif
d->d_tcpsndbufsize = atoi(v);
break;
#if _FFR_SS_PER_DAEMON
case 'T':
if (tolower(*v) == 'i')
d->d_supersafe = SAFE_INTERACTIVE;
else if (tolower(*v) == 'p')
# if MILTER
d->d_supersafe = SAFE_REALLY_POSTMILTER;
# else
(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
"Warning: SuperSafe=PostMilter requires Milter support (-DMILTER)\n");
# endif
else
d->d_supersafe = atobool(v) ? SAFE_REALLY
: SAFE_NO;
break;
#endif
default:
syserr("554 5.3.5 PortOptions parameter \"%s\" unknown",
f);
}
}
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;
}
(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
#endif
#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
}
}
break;
#endif
#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
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
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
}
break;
#endif
#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
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
}
break;
#endif
#if NETISO
case AF_ISO:
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
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
}
memmove(TSEL(&d->d_addr.siso),
(char *) &portno, 2);
break;
#endif
default:
syserr("554 5.3.5 Port= option unsupported for family %d",
d->d_addr.sa.sa_family);
break;
}
}
}
#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
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;
}
void
initdaemon()
{
if (NDaemons == 0)
{
Daemons[NDaemons].d_socket = -1;
Daemons[NDaemons].d_listenqueue = DEF_LISTENQUEUE;
Daemons[NDaemons].d_name = "Daemon0";
NDaemons = 1;
}
}
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);
family = d.d_addr.sa.sa_family;
STRUCTCOPY(d, ClientSettings[family]);
setbitn(D_ISSET, ClientSettings[family].d_flags);
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);
}
}
static int
addr_family(addr)
char *addr;
{
#if NETINET6
SOCKADDR clt_addr;
#endif
#if NETINET
if (inet_addr(addr) != INADDR_NONE)
{
if (tTd(16, 9))
sm_dprintf("addr_family(%s): INET\n", addr);
return AF_INET;
}
#endif
#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
#if _FFR_DAEMON_NETUNIX
# if NETUNIX
if (*addr == '/')
{
if (tTd(16, 9))
sm_dprintf("addr_family(%s): LOCAL\n", addr);
return AF_UNIX;
}
# endif
#endif
if (tTd(16, 9))
sm_dprintf("addr_family(%s): UNSPEC\n", addr);
return AF_UNSPEC;
}
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
void
setup_daemon_milters()
{
int idx;
if (OpMode == MD_SMTP)
{
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
static jmp_buf CtxConnectTimeout;
SOCKADDR CurHostAddr;
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
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;
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
clt_bind = false;
if (bitnset(D_BINDIF, d_flags) &&
(p = macvalue(macid("{if_addr}"), e)) != NULL &&
*p != '\0')
{
#if NETINET6
char p6[INET6_ADDRSTRLEN];
#endif
memset(&clt_addr, '\0', sizeof(clt_addr));
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
#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
#if 0
default:
syserr("554 5.3.5 Address= option unsupported for family %d",
clt_addr.sa.sa_family);
break;
#endif
}
if (clt_bind)
family = clt_addr.sa.sa_family;
}
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
#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
#if NETISO
case AF_ISO:
socksize = sizeof(clt_addr.siso);
clt_bind = true;
break;
#endif
default:
break;
}
}
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
#if NETINET6
struct sockaddr_in6 hid6;
#endif
*p = '\0';
#if NETINET6
memset(&hid6, '\0', sizeof(hid6));
#endif
#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
#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
{
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
p[-1] = '\0';
hp = sm_gethostbyname(&host[1],
family);
p[-1] = '.';
#if NAMED_BIND
_res.options = oldopts;
#endif
}
*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
{
{
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
*p = '\0';
hp = sm_gethostbyname(host, family);
*p = '.';
#if NAMED_BIND
_res.options = oldopts;
#endif
}
}
gothostent:
if (hp == NULL || hp->h_addr == NULL)
{
#if NAMED_BIND
# if NETINET6
if (WorkAroundBrokenAAAA && family == AF_INET6 &&
errno == ETIMEDOUT)
{
if (tTd(16, 10))
sm_dprintf("makeconnection: WorkAroundBrokenAAAA: Trying AF_INET lookup (AF_INET6 failed)\n");
}
else
# endif
{
if (errno == ETIMEDOUT ||
# if _FFR_GETHBN_ExFILE
# ifdef EMFILE
errno == EMFILE ||
# endif
# ifdef ENFILE
errno == ENFILE ||
# endif
# endif
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
#if NETINET6
if (family == AF_INET6)
{
family = AF_INET;
goto v4retry;
}
if (v6found)
goto v6tempfail;
#endif
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
#if NETINET6
case AF_INET6:
memmove(&addr.sin6.sin6_addr,
hp->h_addr,
IN6ADDRSZ);
break;
#endif
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;
}
if (port == 0)
{
#ifdef NO_GETSERVBYNAME
port = htons(25);
#else
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
}
#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)
{
goto nextaddr;
}
#endif
switch (addr.sa.sa_family)
{
#if NETINET
case AF_INET:
addr.sin.sin_port = port;
addrlen = sizeof(struct sockaddr_in);
break;
#endif
#if NETINET6
case AF_INET6:
addr.sin6.sin6_port = port;
addrlen = sizeof(struct sockaddr_in6);
break;
#endif
#if NETISO
case AF_ISO:
memmove(TSEL((struct sockaddr_iso *) &addr), (char *) &port, 2);
addrlen = sizeof(struct sockaddr_iso);
break;
#endif
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
return EX_NOHOST;
}
#if XLA
if (!xla_noqueue_ok(host))
{
# if NETINET6
if (hp != NULL)
freehostent(hp);
# endif
return EX_TEMPFAIL;
}
#endif
for (;;)
{
if (tTd(16, 1))
sm_dprintf("makeconnection (%s [%s].%d (%d))\n",
host, anynet_ntoa(&addr), ntohs(port),
(int) addr.sa.sa_family);
CurHostAddr = addr;
#if HASRRESVPORT
if (bitnset(M_SECURE_PORT, mci->mci_mailer->m_flags))
{
int rport = IPPORT_RESERVED - 1;
s = rresvport(&rport);
}
else
#endif
{
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
mci_setstat(mci, EX_TEMPFAIL, "4.4.5", NULL);
#if NETINET6
if (hp != NULL)
freehostent(hp);
#endif
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
#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
if (tTd(16, 1))
sm_dprintf("makeconnection: fd=%d\n", s);
if (tTd(16, 101))
{
int on = 1;
(void) setsockopt(s, SOL_SOCKET, SO_DEBUG,
(char *)&on, sizeof(on));
}
if (e->e_xfp != NULL)
(void) sm_io_flush(e->e_xfp, SM_TIME_DEFAULT);
errno = 0;
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
#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
}
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
errno = save_errno;
return EX_TEMPFAIL;
}
}
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
#if NETINET6
case AF_INET6:
memmove(&addr.sin6.sin6_addr,
&ConnectOnlyTo.sin6.sin6_addr,
IN6ADDRSZ);
break;
#endif
}
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;
(void) close(s);
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
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
#if NETINET6
case AF_INET6:
memmove(&addr.sin6.sin6_addr,
hp->h_addr_list[addrno++],
IN6ADDRSZ);
break;
#endif
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
#if NETINET6
if (errno > 0)
#endif
save_errno = errno;
if (tTd(16, 1))
sm_dprintf("Connect failed (%s)\n",
sm_errstring(save_errno));
#if XLA
xla_host_end(host);
#endif
mci_setstat(mci, EX_TEMPFAIL, "4.4.1", NULL);
#if NETINET6
if (hp != NULL)
freehostent(hp);
#endif
errno = save_errno;
return EX_TEMPFAIL;
}
#if NETINET6
if (hp != NULL)
{
freehostent(hp);
hp = NULL;
}
#endif
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);
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}"), "");
if (bitnset(D_IFNHELO, ClientSettings[addr.sa.sa_family].d_flags))
{
setbitn(D_IFNHELO, d_flags);
}
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)
{
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);
}
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;
{
errno = ETIMEDOUT;
longjmp(CtxConnectTimeout, 1);
}
#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;
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;
}
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);
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));
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;
}
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;
}
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
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
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
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;
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
#endif
}
}
finis(false, true, EX_OK);
}
#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;
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);
}
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
close_sendmail_pid();
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);
}
sm_close_on_exec(STDERR_FILENO + 1, DtableSize);
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
sm_allsignals(false);
(void) execve(SaveArgv[0], (ARGV_T) SaveArgv, (ARGV_T) ExternalEnviron);
save_errno = errno;
sm_allsignals(true);
(void) sm_signal(SIGALRM, oalrm);
#ifdef SIGUSR1
(void) sm_signal(SIGUSR1, ousr1);
#endif
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);
}
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)
{
hp = sm_gethostbyname(hostbuf, AF_INET);
}
#endif
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
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 (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;
}
static int
addrcmp(hp, ha, sa)
struct hostent *hp;
char *ha;
SOCKADDR *sa;
{
#if NETINET6
unsigned char *a;
#endif
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
#if NETINET6
case AF_INET6:
a = (unsigned char *) &sa->sin6.sin6_addr;
if (hp->h_addrtype == AF_INET6)
return memcmp(ha, a, IN6ADDRSZ);
if (hp->h_addrtype == AF_INET &&
IN6_IS_ADDR_V4MAPPED(&sa->sin6.sin6_addr))
return memcmp(a + IN6ADDRSZ - INADDRSZ, ha, INADDRSZ);
break;
#endif
}
return -1;
}
static jmp_buf CtxAuthTimeout;
static void
authtimeout(ignore)
int ignore;
{
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
# if NETINET6
static unsigned short port6 = 0;
# endif
#endif
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)
{
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)
{
RealHostName = newstr(hostnamebyanyaddr(&RealHostAddr));
if (strlen(RealHostName) > MAXNAME)
RealHostName[MAXNAME] = '\0';
}
if (anynet_ntoa(&RealHostAddr)[0] != '[' &&
RealHostName[0] != '[')
{
int family;
family = RealHostAddr.sa.sa_family;
#if NETINET6 && NEEDSGETIPNODE
if (family == AF_INET6 &&
IN6_IS_ADDR_V4MAPPED(&RealHostAddr.sin6.sin6_addr))
family = AF_INET;
#endif
hp = sm_gethostbyname(RealHostName, family);
if (hp == NULL)
{
*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
}
}
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)
{
goto noident;
}
port = RealHostAddr.sin.sin_port;
(void) sm_snprintf(ibuf, sizeof(ibuf), "%d,%d\r\n",
ntohs(RealHostAddr.sin.sin_port),
ntohs(la.sin.sin_port));
la.sin.sin_port = 0;
# ifdef NO_GETSERVBYNAME
RealHostAddr.sin.sin_port = htons(113);
# else
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
#endif
#if NETINET6
case AF_INET6:
if (getsockname(fd, &la.sa, &lalen) < 0 ||
lalen <= 0 ||
la.sa.sa_family != AF_INET6)
{
goto noident;
}
port = RealHostAddr.sin6.sin6_port;
(void) sm_snprintf(ibuf, sizeof(ibuf), "%d,%d\r\n",
ntohs(RealHostAddr.sin6.sin6_port),
ntohs(la.sin6.sin6_port));
la.sin6.sin6_port = 0;
# ifdef NO_GETSERVBYNAME
RealHostAddr.sin6.sin6_port = htons(113);
# else
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
#endif
default:
goto noident;
}
s = -1;
if (setjmp(CtxAuthTimeout) != 0)
{
if (s >= 0)
(void) close(s);
goto noident;
}
ev = sm_setevent(TimeOuts.to_ident, authtimeout, 0);
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);
if (write(s, ibuf, strlen(ibuf)) < 0)
goto closeident;
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);
p = strchr(ibuf, ':');
if (p == NULL)
{
goto noident;
}
while (isascii(*++p) && isspace(*p))
continue;
if (sm_strncasecmp(p, "userid", 6) != 0)
{
goto noident;
}
p += 6;
while (isascii(*p) && isspace(*p))
p++;
if (*p++ != ':')
{
goto noident;
}
while (isascii(*p) && isspace(*p))
p++;
ostype = p;
p = strchr(p, ':');
if (p == NULL)
{
goto noident;
}
else
{
char *charset;
*p = '\0';
charset = strchr(ostype, ',');
if (charset != NULL)
*charset = '\0';
}
while (isascii(*++p) && isspace(*p))
continue;
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:
switch (RealHostAddr.sa.sa_family)
{
#if NETINET
case AF_INET:
if (port > 0)
RealHostAddr.sin.sin_port = port;
break;
#endif
#if NETINET6
case AF_INET6:
if (port > 0)
RealHostAddr.sin6.sin6_port = port;
break;
#endif
}
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
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:
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 = &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:
o += o[1];
break;
}
}
(void) sm_snprintf(p, SPACELEFT(hbuf, p), "]");
goto postipsr;
}
noipsr:
#endif
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
switch (RealHostAddr.sa.sa_family)
{
#if NETINET
case AF_INET:
if (port > 0)
RealHostAddr.sin.sin_port = port;
break;
#endif
#if NETINET6
case AF_INET6:
if (port > 0)
RealHostAddr.sin6.sin6_port = port;
break;
#endif
}
if (tTd(9, 1))
sm_dprintf("getauthinfo: %s\n", hbuf);
return hbuf;
}
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
#if NETINET6
struct in6_addr in6_addr;
#endif
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
char hbuf[MAXNAME + 1];
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 (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 (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
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
#if NETINET6
if (hp == NULL &&
anynet_pton(AF_INET6, &name[1], &in6_addr) == 1)
hp = sm_gethostbyaddr((char *)&in6_addr,
IN6ADDRSZ, AF_INET6);
#endif
*cp = ']';
if (hp != NULL)
{
ans = denlstring((char *) hp->h_name, true, true);
#if NETINET6
if (ans == hp->h_name)
{
static char n[MAXNAME + 1];
(void) sm_strlcpy(n, ans, sizeof(n));
ans = n;
}
freehostent(hp);
hp = NULL;
#endif
}
}
#if NAMED_BIND
if (map->map_timeout > 0)
_res.retrans = retrans;
if (map->map_retry > 0)
_res.retry = retry;
#endif
s->s_namecanon.nc_flags |= NCF_VALID;
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;
}
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
if (tTd(9, 1))
sm_dprintf("FAIL\n");
*statp = EX_NOHOST;
#endif
s->s_namecanon.nc_stat = *statp;
return NULL;
}
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':
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
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;
d = dst;
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);
if (ap != NULL)
ap = d;
}
return ap;
}
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
#ifdef USE_SOCK_STREAM
# if NETLINK
# include <net/if_dl.h>
# endif
char *
anynet_ntoa(sap)
register SOCKADDR *sap;
{
register char *bp;
register char *ap;
int l;
static char buf[100];
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
# if NETINET
case AF_INET:
return (char *) inet_ntoa(sap->sin.sin_addr);
# endif
# if NETINET6
case AF_INET6:
ap = anynet_ntop(&sap->sin6.sin6_addr, buf, sizeof(buf));
if (ap != NULL)
return ap;
break;
# endif
# if NETLINK
case AF_LINK:
(void) sm_snprintf(buf, sizeof(buf), "[LINK: %s]",
link_ntoa((struct sockaddr_dl *) &sap->sa));
return buf;
# endif
default:
break;
}
(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 && SPACELEFT(buf, bp) > 3; )
{
(void) sm_snprintf(bp, SPACELEFT(buf, bp), "%02x:",
*ap++ & 0377);
bp += 3;
}
*--bp = '\0';
return buf;
}
char *
hostnamebyanyaddr(sap)
register SOCKADDR *sap;
{
register struct hostent *hp;
# if NAMED_BIND
int saveretry;
# endif
# if NETINET6
struct in6_addr in6_addr;
# endif
# if NAMED_BIND
saveretry = _res.retry;
if (_res.retry * _res.retrans > 20)
_res.retry = 20 / _res.retrans;
# endif
switch (sap->sa.sa_family)
{
# if NETINET
case AF_INET:
hp = sm_gethostbyaddr((char *) &sap->sin.sin_addr,
INADDRSZ, AF_INET);
break;
# endif
# if NETINET6
case AF_INET6:
hp = sm_gethostbyaddr((char *) &sap->sin6.sin6_addr,
IN6ADDRSZ, AF_INET6);
break;
# endif
# if NETISO
case AF_ISO:
hp = sm_gethostbyaddr((char *) &sap->siso.siso_addr,
sizeof(sap->siso.siso_addr), AF_ISO);
break;
# endif
# if NETUNIX
case AF_UNIX:
hp = NULL;
break;
# endif
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
# if NETINET || NETINET6
if (hp != NULL && hp->h_name[0] != '['
# if NETINET6
&& inet_pton(AF_INET6, hp->h_name, &in6_addr) != 1
# endif
# if NETINET
&& inet_addr(hp->h_name) == INADDR_NONE
# endif
)
{
char *name;
name = denlstring((char *) hp->h_name, true, true);
# if NETINET6
if (name == hp->h_name)
{
static char n[MAXNAME + 1];
(void) sm_strlcpy(n, name, sizeof(n));
name = n;
}
freehostent(hp);
# endif
return name;
}
# endif
# if NETINET6
if (hp != NULL)
{
freehostent(hp);
hp = NULL;
}
# endif
# if NETUNIX
if (sap->sa.sa_family == AF_UNIX && sap->sunix.sun_path[0] == '\0')
return "localhost";
# endif
{
static char buf[203];
(void) sm_snprintf(buf, sizeof(buf), "[%.200s]",
anynet_ntoa(sap));
return buf;
}
}
#endif