summaryrefslogtreecommitdiffstats
path: root/contrib/sendmail/src/daemon.c
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/sendmail/src/daemon.c')
-rw-r--r--contrib/sendmail/src/daemon.c4491
1 files changed, 4491 insertions, 0 deletions
diff --git a/contrib/sendmail/src/daemon.c b/contrib/sendmail/src/daemon.c
new file mode 100644
index 0000000..983ad2f
--- /dev/null
+++ b/contrib/sendmail/src/daemon.c
@@ -0,0 +1,4491 @@
+/*
+ * 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 */
OpenPOWER on IntegriCloud