/*
 * Copyright (c) 2013, 2014, 2016
 *      Inferno Nettverk A/S, Norway.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. The above copyright notice, this list of conditions and the following
 *    disclaimer must appear in all copies of the software, derivative works
 *    or modified versions, and any portions thereof, aswell as in all
 *    supporting documentation.
 * 2. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by
 *      Inferno Nettverk A/S, Norway.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Inferno Nettverk A/S requests users of this software to return to
 *
 *  Software Distribution Coordinator  or  sdc@inet.no
 *  Inferno Nettverk A/S
 *  Oslo Research Park
 *  Gaustadalléen 21
 *  NO-0349 Oslo
 *  Norway
 *
 * any improvements or extensions that they make and grant Inferno Nettverk A/S
 * the rights to redistribute these changes.
 *
 */

#include "common.h"
#include "monitor.h"
#include "config_parse.h"

static const char rcsid[] =
"$Id: monitor.c,v 1.125.4.8.2.2 2017/01/31 08:17:38 karls Exp $";


static void showalarms(const monitor_if_t *iface);
static void siginfo(int sig, siginfo_t *sip, void *scp);

static void
alarmcheck_disconnect(const monitor_t *monitor, alarm_disconnect_t *alarm,
                      const size_t alarmside, const struct timeval *tnow,
                      int *alarmtriggered);
/*
 * Checks the disconnect alarm "alarm", part of monitor "monitor".
 * Triggers or resets the alarm if appropriate.
 * "alarmside" is the side "alarm" belongs to (used for debug printing only).
 * "tnow" is the time now.
 *
 * If "alarmtriggered" is set upon return, a disconnect alarm was triggered
 * on this call.  If not, no disconnect alarm was triggered.
 */

/* bitmask. */
#define OP_CONNECT         (1)
#define OP_DISCONNECT      (2)
#define OP_REMOVE_SESSION  (4)

static char *op2string(const size_t op);


static void
disconnect_alarm_body(const int weclosedfirst, const size_t op,
                      rule_t *alarm, const size_t sides,
                      const clientinfo_t *cinfo,
                      const char *reason, const int lock);
/*
 * The operation for the different op values is given by the "op" value,
 * which should be a bitmask of one or more of the following.:
 *  - OP_CONNECT:  adds a connect to the disconnect alarm referenced
 *                 by shmem. "reason" should be NULL in this case.
 *
 *  - OP_DISCONNECT: adds a disconnect, decrementing the connect counter at
 *                   the same time.  In this case, "reason" must be a string
 *                   describing the reason for the disconnect, and
 *                   "weclosedfirst" indicates whether the disconnect is
 *                   considered a response to our close towards peer or not.
 *
 *  - OP_REMOVE_SESSION: decrements the session counter.
 */


static void
alarmcheck_data(const monitor_t *monitor, alarm_data_side_t *alarm,
                const size_t alarmside, const size_t sessionc,
                const struct timeval *tnow);
/*
 * Checks the data alarm "alarm", part of monitor "monitor".
 * Triggers or resets the alarm if appropriate.
 * "tnow" is the time now.
 */

static void
alarm_data_toggle(const monitor_t *monitor, const size_t alarmside,
                  alarm_data_side_t *alarm, const struct timeval *tnow);
/*
 * toggles the data-alarm "alarm", part of the monitor "monitor", on or off,
 * with appropriate log messages.
 */

static void checkmonitors(void);
/*
 * Checks all monitors, turns on/off alarms, resets, etc.
 */

static struct timeval *
timetillcheck(const monitor_t *monitor, const struct timeval *tnow,
            struct timeval *tfirstcheck, size_t *timedout);
/*
 * Calculates the time remaining till the next check specified in "monitor"
 * should be done, or NULL if no timeout is configured for this monitor.
 * The latter would likely only happen if there is a configuration error
 * by the user.
 *
 * The time till the first timeout is saved in "tfirstcheck.
 *
 * "tnow" is the current time.
 *
 * "timedout", if not NULL, is a bitmask indicating which, if any, of the
 * alarm elements for "monitor" have already timed out and should be checked
 * now.
 *
 * Returns "tfirstcheck", filled out correctly.
 */

static struct timeval *
gettimeout(struct timeval *timeout);
/*
 * Returns how long it is until the next monitor should be checked, or
 * NULL if there is no monitor that should be checked.
 */

static void proctitleupdate(void);
/*
 * Updates the process title.  "monitorc" is the number of objects
 * we are monitoring.
 */

static struct {
   size_t         alarmsactive;    /* number of alarms currently active.      */
   size_t         monitorc;        /* number of monitors we are monitoring.   */
} state;

monitor_t *
addmonitor(newmonitor)
   const monitor_t *newmonitor;
{
   const char *function = "addmonitor()";
   monitor_stats_t *mstats;
   monitor_t *monitor;

   SASSERTX(newmonitor->mstats != NULL);

   if (newmonitor->alarmsconfigured == 0)
      yywarnx("monitor has no alarms configured.  Is this intended?");

   if (newmonitor->alarm_data_aggregate & ALARM_INTERNAL) {
      SASSERTX(newmonitor->alarm_data_aggregate & ALARM_EXTERNAL);

      if (newmonitor->alarm_data_aggregate & ALARM_RECV)
         SASSERTX(newmonitor->alarm_data_aggregate & ALARM_SEND);
   }
   else if (newmonitor->alarm_data_aggregate & ALARM_RECV) {
      SASSERTX(newmonitor->alarm_data_aggregate & ALARM_SEND);
      SASSERTX(!(newmonitor->alarm_data_aggregate & ALARM_INTERNAL));
      SASSERTX(!(newmonitor->alarm_data_aggregate & ALARM_EXTERNAL));
   }

   mstats = &newmonitor->mstats->object.monitor;

   if (newmonitor->alarmsconfigured & ALARM_DATA)
      SASSERTX(mstats->internal.alarm.data.recv.isconfigured
      ||       mstats->internal.alarm.data.send.isconfigured
      ||       mstats->external.alarm.data.recv.isconfigured
      ||       mstats->external.alarm.data.send.isconfigured);

   if (newmonitor->alarmsconfigured & ALARM_DISCONNECT)
      SASSERTX(mstats->internal.alarm.disconnect.isconfigured
      ||       mstats->external.alarm.disconnect.isconfigured);


   if (mstats->internal.alarm.data.recv.isconfigured) {
      /* check data alarm settings, recv side. */
   }

   if (mstats->internal.alarm.data.send.isconfigured) {
      /* check data alarm settings, send side. */
   }

   if (mstats->internal.alarm.disconnect.isconfigured) {
      /* check disconnect alarm settings, internal side. */
      if (mstats->internal.alarm.disconnect.limit.disconnectc
      >   mstats->internal.alarm.disconnect.limit.sessionc)
         yyerrorx("internal session limit value (%lu) cannot be lower "
                  "than disconnect limit (%lu)",
                  (unsigned long)
                     mstats->internal.alarm.disconnect.limit.disconnectc,
                  (unsigned long)
                     mstats->internal.alarm.disconnect.limit.sessionc);
    }

   if (mstats->external.alarm.disconnect.isconfigured) {
      /* check disconnect alarm settings, external side. */
      if (mstats->external.alarm.disconnect.limit.disconnectc
      >   mstats->external.alarm.disconnect.limit.sessionc)
         yyerrorx("external session limit value (%lu) cannot be lower "
                  "than disconnect limit (%lu)",
                  (unsigned long)
                     mstats->external.alarm.disconnect.limit.disconnectc,
                  (unsigned long)
                     mstats->external.alarm.disconnect.limit.sessionc);
   }

   if ((monitor = malloc(sizeof(*monitor))) == NULL)
      yyerror("could not allocate %lu bytes for monitor",
              (unsigned long)sizeof(*monitor));

   *monitor                      = *newmonitor;
   monitor->type                 = object_monitor;
   monitor->mstats_isinheritable = 1;  /* always. */

   setcommandprotocol(monitor->type,
                      &monitor->state.command,
                      &monitor->state.protocol);

   if (sockscf.monitor == NULL) { /* first monitor. */
      monitor->number = 1;
      sockscf.monitor = monitor;
   }
   else { /* append this monitor to the end of our list. */
      monitor_t *last;

      last = sockscf.monitor;
      while (last->next != NULL)
         last = last->next;

      monitor->number = last->number + 1;
      last->next = monitor;
   }

   INIT_MSTATE(&monitor->mstats->mstate, monitor->type, monitor->number);
   monitor->next = NULL;

   return monitor;
}

void
showmonitor(_monitor)
   const monitor_t *_monitor;
{
   const char *function = "showmonitor()";
   monitor_t monitor = *_monitor;
   rule_t rule;

   slog(LOG_DEBUG, "monitor #%lu, line #%lu, shmid %lu",
        (unsigned long)monitor.number,
        (unsigned long)monitor.linenumber,
        (unsigned long)monitor.mstats_shmid);

   slog(LOG_DEBUG, "src: %s",
        ruleaddr2string(&monitor.src,
                        ADDRINFO_PORT | ADDRINFO_ATYPE,
                        NULL,
                        0));

   slog(LOG_DEBUG, "dst: %s",
        ruleaddr2string(&monitor.dst,
                        ADDRINFO_PORT | ADDRINFO_ATYPE,
                        NULL,
                        0));

#if HAVE_SOCKS_HOSTID
   if (monitor.hostid.atype != SOCKS_ADDR_NOTSET)
      slog(LOG_DEBUG, "hostindex: %d, hostid: %s",
           monitor.hostindex, ruleaddr2string(&monitor.hostid, 0, NULL, 0));
#endif /* HAVE_SOCKS_HOSTID */

   bzero(&rule, sizeof(rule));
   COPY_MONITORFIELDS(&monitor, &rule);
   (void)sockd_shmat(&rule, SHMEM_MONITOR);
   COPY_MONITORFIELDS(&rule, &monitor);

   if (monitor.mstats_shmid != 0) {
      const monitor_stats_t *mstats;

      SASSERTX(monitor.mstats != NULL);

      mstats = &monitor.mstats->object.monitor;

      if (mstats->internal.alarm.data.recv.isconfigured
      ||  mstats->internal.alarm.data.send.isconfigured
      ||  mstats->internal.alarm.disconnect.isconfigured) {
         slog(LOG_DEBUG, "alarms on internal interface:");
         showalarms(&mstats->internal);
      }

      if (mstats->external.alarm.data.recv.isconfigured
      ||  mstats->external.alarm.data.send.isconfigured
      ||  mstats->external.alarm.disconnect.isconfigured) {
         slog(LOG_DEBUG, "alarms on external interface:");
         showalarms(&monitor.mstats->object.monitor.external);
      }

      slog(LOG_DEBUG,
           "alarm_data_aggregate: %lu, alarm_disconnect_aggregate: %lu",
           (unsigned long)monitor.alarm_data_aggregate,
           (unsigned long)monitor.alarm_disconnect_aggregate);
   }

   sockd_shmdt(&rule, SHMEM_MONITOR);
   COPY_MONITORFIELDS(&rule, &monitor);

#if 0
   /*
    * Not implemented.
    */

   showlist(monitor.user, "user: ");
   showlist(monitor.group, "group: ");

#if HAVE_PAM
   if (methodisset(AUTHMETHOD_PAM,
                   monitor.state.methodv,
                   monitor.state.methodc))
      slog(LOG_DEBUG, "pam.servicename: %s", monitor.state.pamservicename);
#endif /* HAVE_PAM */

#if HAVE_BSDAUTH
   if (methodisset(AUTHMETHOD_BSDAUTH,
                   monitor.state.methodv,
                   monitor.state.methodc))
      slog(LOG_DEBUG, "bsdauth.stylename: %s", monitor.state.bsdauthstylename);
#endif /* HAVE_BSDAUTH */

#if HAVE_LDAP
   showlist(monitor.ldapgroup, "ldap.group: ");
   if (monitor.ldapgroup) {
      if (*monitor.state.ldap.domain != NUL)
         slog(LOG_DEBUG, "ldap.domain: %s",monitor.state.ldap.domain);

      slog(LOG_DEBUG, "ldap.auto.off: %s",
      monitor.state.ldap.auto_off ? "yes" : "no");
#if HAVE_OPENLDAP
      slog(LOG_DEBUG, "ldap.debug: %d", monitor.state.ldap.debug);
#endif
      slog(LOG_DEBUG, "ldap.keeprealm: %s",
      monitor.state.ldap.keeprealm ? "yes" : "no");

      if (*monitor.state.ldap.keytab != NUL)
         slog(LOG_DEBUG, "ldap.keytab: %s", monitor.state.ldap.keytab);

      showlist(monitor.state.ldap.ldapurl, "ldap.url: ");

      showlist(monitor.state.ldap.ldapbasedn, "ldap.basedn: ");

      if (*monitor.state.ldap.filter != NUL)
         slog(LOG_DEBUG, "ldap.filter: %s", monitor.state.ldap.filter);

      if (*monitor.state.ldap.filter_AD != NUL)
         slog(LOG_DEBUG, "ldap.filter.ad: %s", monitor.state.ldap.filter_AD);

      if (*monitor.state.ldap.attribute != NUL)
         slog(LOG_DEBUG, "ldap.attribute: %s", monitor.state.ldap.attribute);

      if (*monitor.state.ldap.attribute_AD != NUL)
         slog(LOG_DEBUG, "ldap.attribute.ad: %s",
              monitor.state.ldap.attribute_AD);

      slog(LOG_DEBUG, "ldap.mdepth: %d", monitor.state.ldap.mdepth);
      slog(LOG_DEBUG, "ldap.port: %d", monitor.state.ldap.port);
      slog(LOG_DEBUG, "ldap.ssl: %s", monitor.state.ldap.ssl ? "yes" : "no");
      slog(LOG_DEBUG, "ldap.certcheck: %s",
           monitor.state.ldap.certcheck ? "yes" : "no");

      if (*monitor.state.ldap.certfile != NUL)
         slog(LOG_DEBUG, "ldap.certfile: %s", monitor.state.ldap.certfile);

      if (*monitor.state.ldap.certpath != NUL)
         slog(LOG_DEBUG, "ldap.certpath: %s", monitor.state.ldap.certpath);
   }
#endif /* HAVE_LDAP */
#endif /* not implemented */

   showstate(&monitor.state);
}


void
run_monitor(void)
{
   const char *function = "run_monitor()";
   struct sigaction sigact;
   fd_set *rset;

   bzero(&sigact, sizeof(sigact));
   sigact.sa_flags     = SA_RESTART | SA_SIGINFO;
   sigact.sa_sigaction = siginfo;

#if HAVE_SIGNAL_SIGINFO
   if (sigaction(SIGINFO, &sigact, NULL) != 0)
      serr("%s: sigaction(SIGINFO)", function);
#endif /* HAVE_SIGNAL_SIGINFO */

   /* same handler, for systems without SIGINFO. */
   if (sigaction(SIGUSR1, &sigact, NULL) != 0)
      serr("%s: sigaction(SIGINFO)", function);

   rset = allocate_maxsize_fdset();

   sockd_print_child_ready_message((size_t)freedescriptors(NULL, NULL));

   while (1) {
      struct timeval timeout;
      int fdbits;

      errno = 0; /* reset for each iteration. */

      proctitleupdate();

      FD_ZERO(rset);
      fdbits = -1;

      /* checked so we know if mother goes away.  */
      SASSERTX(sockscf.state.mother.ack != -1);
      FD_SET(sockscf.state.mother.ack, rset);
      fdbits = MAX(fdbits, sockscf.state.mother.ack);

      ++fdbits;
      switch (selectn(fdbits,
                      rset,
                      NULL,
                      NULL,
                      NULL,
                      NULL,
                      gettimeout(&timeout))) {
         case -1:
            SASSERT(ERRNOISTMP(errno));
            continue;

         case 0:
            break;

         default:
            if (FD_ISSET(sockscf.state.mother.ack, rset)) {
               /*
                * only eof expected.  Read and exit.
                */
               sockd_readmotherscontrolsocket(function,
                                              sockscf.state.mother.ack);

               /*
                * XXX for some reason Valgrind complains rset pointer is
                * lost, but this free(3)-call seems to make Valgrind
                * understand it is wrong.
                */
               free(rset);
               sockdexit(EXIT_FAILURE);
            }
      }

      checkmonitors();
   }

   /* NOTREACHED */
}

void
monitor_preconfigload(void)
{
   const char *function = "monitor_preconfigload()";

   slog(LOG_DEBUG, "%s", function);

   /*
    * will load new monitor objects (if any) from new config.
    * Disregard any old ones.
    */
   monitor_detachfromlist(sockscf.monitor);
}

void
monitor_postconfigload(void)
{
   const char *function = "monitor_postconfigload()";
   struct timeval tnow;
   monitor_t *monitor;

   /*
    * Permanently attach to all monitor objects and initialize the timestamps
    * in them according to the current time.
    */

   bzero(&state, sizeof(state));
   gettimeofday_monotonic(&tnow);

   for (monitor = sockscf.monitor; monitor != NULL; monitor = monitor->next) {
      monitor_stats_t *mstats;
      rule_t rule;
      int rc;

      SASSERTX(monitor->mstats_shmid != 0);
      SASSERTX(monitor->mstats       == NULL);

      bzero(&rule, sizeof(rule));
      COPY_MONITORFIELDS(monitor, &rule); /* tmp; sockd_shmat() expects rule */
      rc = sockd_shmat(&rule, SHMEM_MONITOR);
      COPY_MONITORFIELDS(&rule, monitor);

      if (rc != 0) {
         slog(LOG_DEBUG, "%s: could not attach to shmem segment of monitor #%lu",
              function, (unsigned long)monitor->number);

         continue;
      }

      SASSERTX(monitor->mstats != NULL);

      /*
       * Reset all counters and remain attached so we do not have to
       * constantly attach/detach for checking.
       */

      mstats = &monitor->mstats->object.monitor;

      socks_lock(sockscf.shmemfd, (off_t)monitor->mstats_shmid, 1, 1, 1);

#if DEBUG /* memory-mapped file contents may not be saved in coredumps. */
      monitor_stats_t _mstats = *mstats;
#endif /* DEBUG */

      MUNPROTECT_SHMEMHEADER(monitor->mstats);

      mstats->internal.alarm.data.recv.ison
      = mstats->internal.alarm.data.send.ison
      = mstats->external.alarm.data.recv.ison
      = mstats->external.alarm.data.send.ison = 0;

      mstats->internal.alarm.data.recv.bytes
      = mstats->internal.alarm.data.send.bytes
      = mstats->external.alarm.data.recv.bytes
      = mstats->external.alarm.data.send.bytes = 0;

      timerclear(&mstats->internal.alarm.data.recv.lastio);
      timerclear(&mstats->internal.alarm.data.send.lastio);
      timerclear(&mstats->external.alarm.data.recv.lastio);
      timerclear(&mstats->external.alarm.data.send.lastio);

      mstats->internal.alarm.data.recv.lastreset
      = mstats->internal.alarm.data.send.lastreset
      = mstats->external.alarm.data.recv.lastreset
      = mstats->external.alarm.data.send.lastreset
      = mstats->internal.alarm.disconnect.lastreset
      = mstats->external.alarm.disconnect.lastreset
      = tnow;

      MPROTECT_SHMEMHEADER(monitor->mstats);

      socks_unlock(sockscf.shmemfd, (off_t)monitor->mstats_shmid, 1);

      ++state.monitorc;
   }
}

void
monitor_detachfromlist(head)
   monitor_t *head;
{
   monitor_t *m;

   for (m = head; m != NULL; m = m->next) {
      rule_t rule;

      if (m->mstats_shmid == 0)
         continue;

      bzero(&rule, sizeof(rule));
      COPY_MONITORFIELDS(m, &rule); /* tmp; sockd_shmat() expects rule */
      sockd_shmdt(&rule, SHMEM_MONITOR);
      COPY_MONITORFIELDS(&rule, m); /* don't forget to update monitor-list. */
   }
}

void
alarm_inherit(from, cinfo_from, to, cinfo_to, sidesconnected)
   rule_t *from;
   const clientinfo_t *cinfo_from;
   rule_t *to;
   const clientinfo_t *cinfo_to;
   const size_t sidesconnected;
{
   const char *function = "alarm_inherit";

   slog(LOG_DEBUG, "%s: sidesconnected: %lu",
        function, (unsigned long)sidesconnected);

   log_ruleinfo_shmid(from, function, "from-rule");
   log_ruleinfo_shmid(to, function,   "to-rule");

   if (sidesconnected == 0)
      return;

   if (from->mstats_shmid == 0) { /* nothing to inherit from. */
      if (to->mstats_shmid != 0 && (to->alarmsconfigured & ALARM_DISCONNECT))
         /*
          * Add connect.
          */
         alarm_add_connect(to, sidesconnected, cinfo_to, sockscf.shmemfd);

      /* else; nothing to do.  No monitor before, no monitor now. */
   }
   else { /* disregard old monitor, inherit it, or nothing? */
      if (to->mstats_shmid == from->mstats_shmid) { /* nothing */
         slog(LOG_DEBUG, "%s: new rule uses same monitor as old one: shmid %lu",
              function, to->mstats_shmid);
      }
      else if (to->mstats_shmid == 0) { /* inherit */
         COPY_MONITORFIELDS(from, to);
      }
      else { /* disregard, old, use new. */
         SASSERTX(to->mstats_shmid   != 0);
         SASSERTX(from->mstats_shmid != 0);

         if (from->alarmsconfigured & ALARM_DISCONNECT) {
            /*
             * Remove connect.
             */
            alarm_remove_session(from,
                                 sidesconnected,
                                 cinfo_from,
                                 sockscf.shmemfd);
         }

         SASSERTX(to->mstats_shmid != 0);
         if (to->alarmsconfigured & ALARM_DISCONNECT)
            alarm_add_connect(to, sidesconnected, cinfo_to, sockscf.shmemfd);
      }
   }
}


const monitor_t *
monitormatch(src, dst, auth, state)
   const sockshost_t *src;
   const sockshost_t *dst;
   const authmethod_t *auth;
   const connectionstate_t *state;
{
   const char *function = "monitormatch()";
   monitor_t *monitor;
   char srcstr[MAXSOCKSHOSTSTRING], dststr[sizeof(srcstr)];

   slog(LOG_DEBUG, "%s: %s -> %s",
        function,
        src == NULL ? "N/A" : sockshost2string(src, srcstr, sizeof(srcstr)),
        dst == NULL ? "N/A" : sockshost2string(dst, dststr, sizeof(dststr)));

   for (monitor = sockscf.monitor; monitor != NULL; monitor = monitor->next) {
      if (!protocol_matches(state->protocol, &monitor->state.protocol)) {
         slog(LOG_DEBUG, "%s: monitor #%lu monitors protocols %s, but not %s",
              function,
              (unsigned long)monitor->number,
              protocols2string(&monitor->state.protocol, NULL, 0),
              protocol2string(state->protocol));

         continue;
      }

      if (!command_matches(state->command, &monitor->state.command)) {
         slog(LOG_DEBUG, "%s: monitor #%lu monitor commands %s, but not %s",
              function,
              (unsigned long)monitor->number,
              commands2string(&monitor->state.command, NULL, 0),
              command2string(state->command));

         continue;
      }

#if HAVE_SOCKS_HOSTID
      if (monitor->hostid.atype != SOCKS_ADDR_NOTSET) {
         slog(LOG_DEBUG,
              "%s: monitor #%lu requires hostid to be present on the "
              "connection ... checking ...",
              function,
              (unsigned long)monitor->number);

         if (!hostidmatches(state->hostidc,
                            state->hostidv,
                            monitor->hostindex,
                            &monitor->hostid,
                            object_monitor,
                            monitor->number))
            continue;
      }
#endif /* HAVE_SOCKS_HOSTID */

      if (src != NULL)
         if (!addrmatch(&monitor->src, src, NULL, state->protocol, 0))
            continue;

      if (dst != NULL)
         if (!addrmatch(&monitor->dst, dst, NULL, state->protocol, 0))
            continue;

      return monitor;
   }

   return NULL;
}

void
monitor_use(mstats, cinfo, lock)
   shmem_object_t *mstats;
   const clientinfo_t *cinfo;
   const int lock;
{
   const char *function = "monitor_use()";
#if DEBUG /* memory-mapped file contents may not be saved in coredumps. */
   shmem_object_t _shmem = *mstats;
#endif /* DEBUG */

   SASSERTX(mstats != NULL);

   slog(LOG_DEBUG,
        "%s: cinfo = %s, lock = %d, mstats object: %p, current clientc: %ld.  "
        "Should become one more",
        function,
        clientinfo2string(cinfo, NULL, 0),
        lock,
        mstats,
        (unsigned long)mstats->mstate.clients);

   shmem_use(mstats, cinfo, lock, 0);
}

void
monitor_move(oldmonitor, newmonitor, stayattached, sidesconnected, cinfo, lock)
   monitor_t *oldmonitor;
   monitor_t *newmonitor;
   const int stayattached;
   const size_t sidesconnected;
   const clientinfo_t *cinfo;
   const int lock;
{
   const char *function = "monitor_move()";
   rule_t alarm;

   slog(LOG_DEBUG,
        "%s: shmid %lu (%p) -> shmid %lu (%p).  Old alarmsconfigured: %lu, "
        "new alarmsconfigured: %lu, sides connected: %lu",
        function,
        (unsigned long)oldmonitor->mstats_shmid,
        oldmonitor->mstats,
        (unsigned long)newmonitor->mstats_shmid,
        newmonitor->mstats,
        (unsigned long)oldmonitor->alarmsconfigured,
        (unsigned long)newmonitor->alarmsconfigured,
        (unsigned long)sidesconnected);

   /*
    * First: clean up old monitorstate.
    */

   bzero(&alarm, sizeof(alarm));
   COPY_MONITORFIELDS(oldmonitor, &alarm);

   if (alarm.mstats_shmid != 0 && alarm.mstats == NULL)
      if (sockd_shmat(&alarm, SHMEM_MONITOR) != 0)
         COPY_MONITORFIELDS(&alarm, oldmonitor);

   if (alarm.mstats_shmid != 0) {
      SASSERTX(alarm.mstats != NULL);

      socks_lock(lock, (off_t)alarm.mstats_shmid, 1, 1, 1);

      if (alarm.alarmsconfigured & ALARM_DISCONNECT)
         alarm_remove_session(&alarm, sidesconnected, cinfo, -1);

      monitor_unuse(alarm.mstats, cinfo, -1);

      socks_unlock(lock, (off_t)alarm.mstats_shmid, 1);

      SASSERTX(alarm.mstats != NULL);

      /*
       * Regardless of whether we were attached when called or not,
       * no need to remain attached to old monitor state now as we
       * are moving to the new monitor.
       */
      sockd_shmdt(&alarm, SHMEM_MONITOR);
   }

   CLEAR_MONITORFIELDS(oldmonitor);

   /*
    * Then: update to new monitorstate.
    */

   bzero(&alarm, sizeof(alarm));
   COPY_MONITORFIELDS(newmonitor, &alarm);

   if (alarm.mstats_shmid != 0 && alarm.mstats == NULL)
      if (sockd_shmat(&alarm, SHMEM_MONITOR) != 0)
         COPY_MONITORFIELDS(&alarm, newmonitor);

   if (alarm.mstats_shmid != 0) {
      SASSERTX(alarm.mstats != NULL);

      socks_lock(lock, (off_t)alarm.mstats_shmid, 1, 1, 1);

      monitor_use(alarm.mstats, cinfo, -1);

      if (alarm.alarmsconfigured & ALARM_DISCONNECT)
         alarm_add_connect(&alarm, sidesconnected, cinfo, -1);

      socks_unlock(lock, (off_t)alarm.mstats_shmid, 1);

      SASSERTX(alarm.mstats != NULL);

      if (stayattached)
         SASSERTX(alarm.mstats == newmonitor->mstats);
      else
         sockd_shmdt(&alarm, SHMEM_MONITOR);
   }
   else
      CLEAR_MONITORFIELDS(newmonitor);
}


void
monitor_unuse(mstats, cinfo, lock)
   shmem_object_t *mstats;
   const clientinfo_t *cinfo;
   const int lock;
{
   const char *function = "monitor_unuse()";
#if DEBUG /* memory-mapped file contents may not be saved in coredumps. */
   shmem_object_t _shmem = *mstats;
#endif /* DEBUG */

   SASSERTX(mstats != NULL);

   slog(LOG_DEBUG,
        "%s: cinfo %s, lock %d, mstats object %p, shmid %lu, "
        "current clientc %lu.  Should become one less",
        function,
        clientinfo2string(cinfo, NULL, 0),
        lock,
        mstats,
        (unsigned long)mstats->mstate.shmid,
        (unsigned long)mstats->mstate.clients);

   shmem_unuse(mstats, cinfo, lock);
}

void
alarm_remove_session(alarm, sides, cinfo, lock)
   rule_t *alarm;
   const size_t sides;
   const clientinfo_t *cinfo;
   const int lock;
{

   disconnect_alarm_body(0,
                         OP_REMOVE_SESSION,
                         alarm,
                         sides,
                         cinfo,
                         NULL,
                         lock);
}

void
alarm_add_connect(alarm, sides, cinfo, lock)
   rule_t *alarm;
   const size_t sides;
   const clientinfo_t *cinfo;
   const int lock;
{

   disconnect_alarm_body(0,
                         OP_CONNECT,
                         alarm,
                         sides,
                         cinfo,
                         NULL,
                         lock);
}

void
alarm_add_disconnect(weclosedfirst, alarm, sides, cinfo, reason, lock)
   const int weclosedfirst;
   rule_t *alarm;
   const size_t sides;
   const clientinfo_t *cinfo;
   const char *reason;
   const int lock;
{

   SASSERTX(reason != NULL);
   disconnect_alarm_body(weclosedfirst,
                         OP_DISCONNECT,
                         alarm,
                         sides,
                         cinfo,
                         reason,
                         lock);
}

static void
disconnect_alarm_body(weclosedfirst, op, alarm, sides, cinfo, reason, lock)
   const int weclosedfirst;
   const size_t op;
   rule_t *alarm;
   const size_t sides;
   const clientinfo_t *cinfo;
   const char *reason;
   const int lock;
{
   const char *function = "disconnect_alarm_body()";
   const size_t sidev[] = { sides & ALARM_INTERNAL ? ALARM_INTERNAL : 0,
                            sides & ALARM_EXTERNAL ? ALARM_EXTERNAL : 0 };

   const size_t opv[]   = { (op & OP_CONNECT)        ? OP_CONNECT        : 0,
                            (op & OP_DISCONNECT)     ? OP_DISCONNECT     : 0,
                            (op & OP_REMOVE_SESSION) ? OP_REMOVE_SESSION : 0 };

   alarm_disconnect_t *disconnect;
   size_t sidec, opc, locktaken;
   int didattach;

   slog(LOG_DEBUG, "%s: op %lu, sides %lu, shmid %lu, shmem %p, cinfo %s",
        function,
        (unsigned long)op,
        (unsigned long)sides,
        (unsigned long)alarm->mstats_shmid,
        alarm->mstats,
        clientinfo2string(cinfo, NULL, 0));

   SASSERTX(alarm->mstats_shmid != 0);

   if (alarm->mstats == NULL) {
      if (sockd_shmat(alarm, SHMEM_MONITOR) != 0)
         return;

      didattach = 1;
   }
   else
      didattach = 0;

   SASSERTX(alarm->mstats != NULL);

#if DEBUG /* memory-mapped file contents may not be saved in coredumps. */
   shmem_object_t _shmem = *alarm->mstats;
#endif /* DEBUG */

   locktaken = 0;

   for (opc = 0; opc < ELEMENTS(opv); ++opc) {
      if (opv[opc] == 0)
         continue;

      for (sidec = 0; sidec < ELEMENTS(sidev); ++sidec) {
         if (sidev[sidec] == 0)
            continue;

         if (sidev[sidec] == ALARM_INTERNAL)
            disconnect
            = &alarm->mstats->object.monitor.internal.alarm.disconnect;
         else {
            SASSERTX(sidev[sidec] == ALARM_EXTERNAL);

            disconnect
            = &alarm->mstats->object.monitor.external.alarm.disconnect;
         }

         slog(LOG_DEBUG,
              "%s: cinfo %s, operation %s, %s alarm configured in monitor "
              "shmid %lu for %s side.  Reason: %s.  Weclosedfirst?  %s.  "
              "Current sessionc: %lu, peer_disconnectc: %lu",
              function,
              clientinfo2string(cinfo, NULL, 0),
              op2string(opv[opc]),
              disconnect->isconfigured ? "Disconnect" : "No disconnect",
              alarm->mstats_shmid,
              alarmside2string(sidev[sidec]),
              reason == NULL ? "N/A" : reason,
              (opv[opc] & OP_DISCONNECT) ?
                  (weclosedfirst ? "Yes" : "No") : "N/A",
              (unsigned long)disconnect->sessionc,
              (unsigned long)disconnect->peer_disconnectc);

         if (!disconnect->isconfigured)
            continue;

         if (!locktaken) {
            socks_lock(lock, (off_t)alarm->mstats_shmid, 1, 1, 1);
            locktaken = 1;
         }

         MUNPROTECT_SHMEMHEADER(alarm->mstats);

         switch (opv[opc]) {
            case OP_CONNECT:
               SASSERTX(reason == NULL);

               disconnect->sessionc += 1;
               break;

            case OP_DISCONNECT:
               SASSERTX(reason != NULL);
               SASSERTX(disconnect->sessionc > 0);

               disconnect->sessionc -= 1;

               if (weclosedfirst)
                  disconnect->self_disconnectc += 1;
               else
                  disconnect->peer_disconnectc += 1;

               break;

            case OP_REMOVE_SESSION:
               SASSERTX(reason == NULL);
               SASSERTX(disconnect->sessionc > 0);

               disconnect->sessionc -= 1;
               break;

            default:
               SERRX(opv[opc]);

         }

         MPROTECT_SHMEMHEADER(alarm->mstats);
      }
   }

   if (locktaken)
      socks_unlock(lock, (off_t)alarm->mstats_shmid, 1);

   if (didattach)
      sockd_shmdt(alarm, SHMEM_MONITOR);
}

static void
checkmonitors(void)
{
   const char *function = "checkmonitors()";
   const struct timeval tzero = { 0, 0 };
   struct timeval tnow;
   monitor_t *monitor;

   slog(LOG_DEBUG, "%s: [", function);

   gettimeofday_monotonic(&tnow);

   for (monitor = sockscf.monitor; monitor != NULL; monitor = monitor->next) {
      monitor_stats_t *mstats;
      struct timeval timeout;
      size_t sessionc, hastimedout;
      int rang;

      if  (monitor->mstats_shmid == 0)
         continue;

      SASSERTX(monitor->mstats != NULL);

      (void)timetillcheck(monitor, &tnow, &timeout, &hastimedout);

      if (hastimedout == 0)
         continue;

      SASSERTX(!timercmp(&timeout, &tzero, >));
      SASSERTX(monitor->alarmsconfigured != 0);

      slog(LOG_DEBUG,
           "%s: time to check alarm on monitor #%lu, shmid %lu.  "
           "Configured alarms: %lu, hastimedout: %lu",
           function,
           (unsigned long)monitor->number,
           (unsigned long)monitor->mstats_shmid,
           (unsigned long)monitor->alarmsconfigured,
           (unsigned long)hastimedout);

      mstats = &monitor->mstats->object.monitor;
#if DEBUG /* memory-mapped file contents may not be saved in coredumps. */
      monitor_stats_t _mstats = *mstats;
#endif /* DEBUG */

      sessionc = monitor->mstats->mstate.clients,

      /*
       * Should have taken the lock before checking the timeout, but
       * seems wasteful as the only difference should be that on
       * boundary conditions, we might trigger an alarm or not trigger
       * an alarm, deciding on the exact timing of when another process
       * gets to update the alarm counters.
       */
      socks_lock(sockscf.shmemfd, (off_t)monitor->mstats_shmid, 1, 1, 1);

      if (hastimedout
      & (  ALARM_INTERNAL_RECV
         | ALARM_INTERNAL_SEND
         | ALARM_EXTERNAL_RECV
         | ALARM_EXTERNAL_SEND)) {
         if (monitor->alarm_data_aggregate != 0) {
            /*
             * Don't go through the interfaces individually, but instead
             * aggregate their values together into one.
             */
            alarm_data_side_t agg;
            struct timeval lio;
            size_t bytes, alarmsides;

            alarmsides = 0;
            bytes      = 0;
            timerclear(&lio);

            if (mstats->internal.alarm.data.recv.isconfigured) {
               agg         = mstats->internal.alarm.data.recv;
               bytes      += mstats->internal.alarm.data.recv.bytes;
               alarmsides |= ALARM_INTERNAL_RECV;

               if (timercmp(&mstats->internal.alarm.data.recv.lastio, &lio, >))
                  lio = mstats->internal.alarm.data.recv.lastio;
            }

            if (mstats->internal.alarm.data.send.isconfigured) {
               agg         = mstats->internal.alarm.data.send;
               bytes      += mstats->internal.alarm.data.send.bytes;
               alarmsides |= ALARM_INTERNAL_SEND;

               if (timercmp(&mstats->internal.alarm.data.send.lastio, &lio, >))
                  lio = mstats->internal.alarm.data.send.lastio;
            }

            if (mstats->external.alarm.data.recv.isconfigured) {
               agg         = mstats->external.alarm.data.recv;
               bytes      += mstats->external.alarm.data.recv.bytes;
               alarmsides |= ALARM_EXTERNAL_RECV;

               if (timercmp(&mstats->external.alarm.data.recv.lastio, &lio, >))
                  lio = mstats->external.alarm.data.recv.lastio;
            }

            if (mstats->external.alarm.data.send.isconfigured) {
               agg         = mstats->external.alarm.data.send;
               bytes      += mstats->external.alarm.data.send.bytes;
               alarmsides |= ALARM_EXTERNAL_SEND;

               if (timercmp(&mstats->external.alarm.data.send.lastio, &lio, >))
                  lio = mstats->external.alarm.data.send.lastio;
            }

            agg.bytes  = bytes;
            agg.lastio = lio;

            switch (monitor->alarm_data_aggregate) {
               case ALARM_INTERNAL | ALARM_EXTERNAL | ALARM_RECV |ALARM_SEND:
                  alarmsides = (size_t)-1; /* all. */
                  break;

               case ALARM_INTERNAL | ALARM_EXTERNAL:
                  if (alarmsides & (ALARM_INTERNAL_RECV | ALARM_EXTERNAL_RECV))
                     alarmsides = ALARM_RECV;
                  else {
                     SASSERTX(alarmsides
                              & (ALARM_INTERNAL_SEND | ALARM_INTERNAL_RECV));
                     alarmsides = ALARM_SEND;
                  }
                  break;

               case ALARM_RECV | ALARM_SEND:
                  if (alarmsides & (ALARM_INTERNAL_RECV | ALARM_INTERNAL_SEND))
                     alarmsides = ALARM_INTERNAL;
                  else {
                     SASSERTX(alarmsides
                              & (ALARM_EXTERNAL_RECV | ALARM_EXTERNAL_SEND));
                     alarmsides = ALARM_EXTERNAL;
                  }
                  break;

               default:
                  SERRX(monitor->alarm_data_aggregate);
            }

            alarmcheck_data(monitor, &agg, alarmsides, sessionc, &tnow);

            /*
             * Now reset all to the updated values in agg.
             */

            if (mstats->internal.alarm.data.recv.isconfigured)
               mstats->internal.alarm.data.recv = agg;

            if (mstats->internal.alarm.data.send.isconfigured)
               mstats->internal.alarm.data.send = agg;

            if (mstats->external.alarm.data.recv.isconfigured)
               mstats->external.alarm.data.recv = agg;

            if (mstats->external.alarm.data.send.isconfigured)
               mstats->external.alarm.data.send = agg;
         }
         else {
            if (hastimedout & ALARM_EXTERNAL_RECV) {
               alarm_data_side_t *alarm = &mstats->external.alarm.data.recv;

               alarmcheck_data(monitor,
                               alarm,
                               ALARM_EXTERNAL_RECV,
                               sessionc,
                               &tnow);
            }

            if (hastimedout & ALARM_EXTERNAL_SEND) {
               alarm_data_side_t *alarm = &mstats->external.alarm.data.send;

               alarmcheck_data(monitor,
                               alarm,
                               ALARM_EXTERNAL_SEND,
                               sessionc,
                               &tnow);
            }

            if (hastimedout & ALARM_INTERNAL_RECV) {
               alarm_data_side_t *alarm = &mstats->internal.alarm.data.recv;

               alarmcheck_data(monitor,
                               alarm,
                               ALARM_INTERNAL_RECV,
                               sessionc,
                               &tnow);
            }

            if (hastimedout & ALARM_INTERNAL_SEND) {
               alarm_data_side_t *alarm = &mstats->internal.alarm.data.send;

               alarmcheck_data(monitor,
                               alarm,
                               ALARM_INTERNAL_SEND,
                               sessionc,
                               &tnow);
            }
         }
      }

#define RESET_DISCONNECT(_alarm, _tnow)                                        \
do {                                                                           \
   (_alarm)->lastreset        = (_tnow);                                       \
   (_alarm)->peer_disconnectc = 0;                                             \
   (_alarm)->self_disconnectc = 0;                                             \
} while (/* CONSTCOND */ 0)

      MUNPROTECT_SHMEMHEADER(monitor->mstats);

      if (hastimedout & (ALARM_INTERNAL | ALARM_EXTERNAL)) {
         if (monitor->alarm_disconnect_aggregate != 0) {
            alarm_disconnect_t agg;

            SASSERTX(mstats->internal.alarm.disconnect.isconfigured);
            SASSERTX(mstats->external.alarm.disconnect.isconfigured);

            agg = mstats->internal.alarm.disconnect;

            agg.peer_disconnectc
            += mstats->external.alarm.disconnect.peer_disconnectc;

            agg.self_disconnectc
            += mstats->external.alarm.disconnect.self_disconnectc;

            alarmcheck_disconnect(monitor,
                                  &agg,
                                  (size_t)-1, /* all. */
                                  &tnow,
                                  &rang);

            RESET_DISCONNECT(&mstats->internal.alarm.disconnect, tnow);
            RESET_DISCONNECT(&mstats->external.alarm.disconnect, tnow);
         }
         else {
            if (hastimedout & ALARM_EXTERNAL) {
               alarm_disconnect_t *alarm = &mstats->external.alarm.disconnect;

               alarmcheck_disconnect(monitor,
                                     alarm,
                                     ALARM_EXTERNAL,
                                     &tnow,
                                     &rang);

               RESET_DISCONNECT(alarm, tnow);
            }

            if (hastimedout & ALARM_INTERNAL) {
               alarm_disconnect_t *alarm = &mstats->internal.alarm.disconnect;

               alarmcheck_disconnect(monitor,
                                     alarm,
                                     ALARM_INTERNAL,
                                     &tnow,
                                     &rang);

               RESET_DISCONNECT(alarm, tnow);
            }
         }

      }

      MPROTECT_SHMEMHEADER(monitor->mstats);

      socks_unlock(sockscf.shmemfd, (off_t)monitor->mstats_shmid, 1);
   }

   slog(LOG_DEBUG, "%s: ]", function);
}

static void
alarmcheck_disconnect(monitor, alarm, alarmside, tnow, alarmtriggered)
   const monitor_t *monitor;
   alarm_disconnect_t *alarm;
   const size_t alarmside;
   const struct timeval *tnow;
   int *alarmtriggered;
{
   const char *function = "alarmcheck_disconnect()";
   struct timeval tdiff;
   size_t sessions_this_period;
   char src[MAXRULEADDRSTRING], dst[sizeof(src)];

   *alarmtriggered = 0;

   timersub(tnow, &(alarm)->lastreset, &tdiff);

   /* we should only be called when enough time has passed. */
   SASSERTX(tdiff.tv_sec >= alarm->limit.seconds);

   slog(LOG_DEBUG,
        "%s: disconnect alarm on side %s in monitor #%lu was last reset "
        "%ld.%06lds ago.  Limit is %lds, current sessionc/peer_disconnectc/"
        "self_disconnectc is %lu/%lu/%lu",
        function,
        alarmside == (size_t)-1 ? "<all>" : alarmside2string(alarmside),
        (unsigned long)monitor->number,
        (long)tdiff.tv_sec,
        (long)tdiff.tv_usec,
        (long)alarm->limit.seconds,
        (unsigned long)alarm->sessionc,
        (unsigned long)alarm->peer_disconnectc,
        (unsigned long)alarm->self_disconnectc);

   if (alarm->peer_disconnectc < alarm->limit.disconnectc)
      return; /* no alarm.  */

   /*
    * Else: enough disconnects by absolute numbers to trigger alarm.
    * Enough disconnects by ratio also?
    */

   sessions_this_period
   = alarm->sessionc + alarm->peer_disconnectc + alarm->self_disconnectc;

   if (sessions_this_period == 0)
      return;

   slog(LOG_DEBUG, "%s: comparing %lu/%lu (%lu) vs %lu/%lu (%lu)",
        function,
        (unsigned long)alarm->peer_disconnectc,
        (unsigned long)sessions_this_period,
        (unsigned long)(alarm->peer_disconnectc  / sessions_this_period),
        (unsigned long)alarm->limit.disconnectc,
        (unsigned long)alarm->limit.sessionc,
        (unsigned long)(alarm->limit.disconnectc / alarm->limit.sessionc));

   if ((alarm->peer_disconnectc  / sessions_this_period)
   >=  (alarm->limit.disconnectc / alarm->limit.sessionc)) {
      slog(LOG_ALARM,
           "monitor(%lu): alarm/disconnect ]: %s -> %s%s%s: %lu/%lu "
           "disconnects during last %lds.  Session count: %lu",
           (unsigned long)monitor->number,
           ruleaddr2string(&monitor->src, ADDRINFO_PORT, src, sizeof(src)),
           ruleaddr2string(&monitor->dst, ADDRINFO_PORT, dst, sizeof(dst)),
           alarmside == (size_t)-1 ? "" : " ",
           alarmside == (size_t)-1 ? "" : alarmside2string(alarmside),
           (unsigned long)alarm->peer_disconnectc,
           (unsigned long)sessions_this_period,
           (long)tdiff.tv_sec,
           (unsigned long)alarm->sessionc);

      *alarmtriggered = 1;
   }
}

static void
alarmcheck_data(monitor, alarm, alarmside, sessionc, tnow)
   const monitor_t *monitor;
   alarm_data_side_t *alarm;
   const size_t alarmside;
   const size_t sessionc;
   const struct timeval *tnow;
{
   const char *function = "alarmcheck_data()";

   slog(LOG_DEBUG,
        "%s: checking monitor #%lu on side %s.  Alarm is currently %s, "
        "alarm->bytes is %lu, lastio is %ld.%06ld",
        function,
        (unsigned long)monitor->number,
        alarmside == (size_t)-1 ? "<all>" : alarmside2string(alarmside),
        alarm->ison ? "on" : "off",
        (unsigned long)alarm->bytes,
        (unsigned long)alarm->lastio.tv_sec,
        (unsigned long)alarm->lastio.tv_usec);

   MUNPROTECT_SHMEMHEADER(monitor->mstats);

   if (alarm->bytes <= alarm->limit.bytes) {
      /*
       * Alarm should trigger for this timeperiod.
       */

      if (!alarm->ison)
         alarm_data_toggle(monitor, alarmside, alarm, tnow);
      /* else; was on, leave on. */

      alarm->lastreset = *tnow;
   }
   else {
      /*
       * No alarm should trigger for this timeperiod.
       */

      if (alarm->ison) {
         alarm_data_toggle(monitor, alarmside, alarm, tnow);
         alarm->lastreset = *tnow;
      }
      else { /*
              * else; was off, leave off.
              * Next check at most one timeperiod after last i/o.
              */
         alarm->lastreset = alarm->lastio;
      }
   }

   alarm->bytes = 0;

   MPROTECT_SHMEMHEADER(monitor->mstats);
}

static void
alarm_data_toggle(monitor, alarmside, alarm, tnow)
   const monitor_t *monitor;
   const size_t alarmside;
   alarm_data_side_t *alarm;
   const struct timeval *tnow;
{
   const char *function = "alarm_data_toggle()";
   struct timeval tdiff;
   char src[MAXRULEADDRSTRING], dst[sizeof(src)], alarmduration[256];

   SASSERTX(monitor->mstats != NULL);
   SASSERTX(alarm->isconfigured);

   if (alarm->ison) {
      alarm->ison = 0;

      SASSERTX(timerisset(&alarm->alarmchange));

      timersub(tnow, &alarm->alarmchange, &tdiff);
      snprintf(alarmduration, sizeof(alarmduration),
               "Alarm duration: %lds", (long)tdiff.tv_sec);
   }
   else {
      alarm->ison    = 1;
      *alarmduration = NUL;
   }

   alarm->alarmchange = *tnow;

   timersub(tnow, &alarm->lastreset, &tdiff);

   slog(LOG_ALARM,
        "monitor(%lu): alarm/data %c: %s -> %s%s%s: %lu/%lu in %lds.  "
        "Session count: %lu%s%s",
        (unsigned long)monitor->number,
        alarm->ison ? '[' : ']',
        ruleaddr2string(&monitor->src, ADDRINFO_PORT, src, sizeof(src)),
        ruleaddr2string(&monitor->dst, ADDRINFO_PORT, dst, sizeof(dst)),
        alarmside == (size_t)-1 ? "" : " ",
        alarmside == (size_t)-1 ? "" : alarmside2string(alarmside),
        (unsigned long)alarm->bytes,
        (unsigned long)alarm->limit.bytes,
        (long)tdiff.tv_sec,
        (unsigned long)monitor->mstats->mstate.clients,
        *alarmduration == NUL ? "" : ".  ",
        alarmduration);
}

static struct timeval *
gettimeout(timeout)
   struct timeval *timeout;
{
   const char *function = "gettimeout()";
   const monitor_t *monitor;
   const struct timeval tzero = { 0, 0 };
   struct timeval tnow;

   int timeout_isset = 0;
   gettimeofday_monotonic(&tnow);

   for (monitor = sockscf.monitor; monitor != NULL; monitor = monitor->next) {
      struct timeval monitortimeout;

      if (monitor->mstats_shmid == 0)
         continue;

      SASSERTX(monitor->mstats != NULL);

      if (timetillcheck(monitor, &tnow, &monitortimeout, NULL) == NULL)
         continue;

      if (!timeout_isset || timercmp(&monitortimeout, timeout, <)) {
         *timeout      = monitortimeout;
         timeout_isset = 1;
      }
   }

   if (timeout_isset) {
      if (timercmp(timeout, &tzero, <))
         timerclear(timeout);

      return timeout;
   }
   else
      return NULL;
}

static struct timeval *
timetillcheck(monitor, tnow, tfirstcheck, hastimedout)
   const monitor_t *monitor;
   const struct timeval *tnow;
   struct timeval *tfirstcheck;
   size_t *hastimedout;
{
   const char *function = "timetillcheck()";
   const struct timeval tzero = { 0, 0 };
   struct timeval tnextcheck;
   monitor_stats_t *mstats;
   size_t hastimedout_mem;
   int tfirstcheck_isset;

   if (hastimedout == NULL)
      hastimedout = &hastimedout_mem;

   SASSERTX(monitor->mstats != NULL);
   mstats = &monitor->mstats->object.monitor;

#if DEBUG /* memory-mapped file contents may not be saved in coredumps. */
   monitor_stats_t _mstats = *mstats;
#endif /* DEBUG */

#define PRINT_DATALIMIT(monitornumber, interface, ioop, timeout)               \
do {                                                                           \
   slog(LOG_DEBUG,                                                             \
        "%s: monitor #%lu has a data-alarm configured on the %s interface "    \
        "for %s data.  Should be checked in %ld.%06lds %s%s%s",                \
        function,                                                              \
        (unsigned long)(monitornumber),                                        \
        (interface),                                                           \
        (ioop),                                                                \
        (long)(timeout)->tv_sec,                                               \
        (long)(timeout)->tv_usec,                                              \
        (long)(timeout)->tv_sec < 0 ? "("   : "",                              \
        (long)(timeout)->tv_sec < 0 ? "now" : "",                              \
        (long)(timeout)->tv_sec < 0 ? ")"   : "");                             \
} while (/* CONSTCOND */ 0)

#define PRINT_DISCONNECTLIMIT(monitornumber, interface, timeout)               \
do {                                                                           \
   slog(LOG_DEBUG,                                                             \
        "%s: monitor #%lu has a disconnect-alarm configured on the %s "        \
        "interface.  Should be checked in %ld.%06lds %s%s%s",                  \
        function,                                                              \
        (unsigned long)(monitornumber),                                        \
        (interface),                                                           \
        (long)(timeout)->tv_sec,                                               \
        (long)(timeout)->tv_usec,                                              \
        (long)(timeout)->tv_sec < 0 ? "("   : "",                              \
        (long)(timeout)->tv_sec < 0 ? "now" : "",                              \
        (long)(timeout)->tv_sec < 0 ? ")"   : "");                             \
} while (/* CONSTCOND */ 0)


#define TIMERCALC(object, tnextcheck, tfirstcheck, tnow)                       \
do {                                                                           \
   const struct timeval limit = { (object)->limit.seconds, 0 };                \
   struct timeval tshouldbechecked;                                            \
                                                                               \
   SASSERTX((object)->limit.seconds > 0);                                      \
   SASSERTX(timerisset(&limit));                                               \
                                                                               \
   timeradd(&((object)->lastreset), &limit, &tshouldbechecked);                \
   timersub(&tshouldbechecked, (tnow), (tnextcheck));                          \
                                                                               \
   slog(LOG_DEBUG,                                                             \
        "%s: TIMERCALC: tshouldbechecked = %ld.%06ld, tnow = %ld.%06ld",       \
        function,                                                              \
        (long)tshouldbechecked.tv_sec,                                         \
        (long)tshouldbechecked.tv_usec,                                        \
        (long)(tnow)->tv_sec,                                                  \
        (long)(tnow)->tv_usec);                                                \
                                                                               \
   if (!tfirstcheck_isset || timercmp((tnextcheck), (tfirstcheck), <)) {       \
      *(tfirstcheck)    = *(tnextcheck);                                       \
      tfirstcheck_isset = 1;                                                   \
   }                                                                           \
} while (/* CONSTCOND */ 0)

   timerclear(tfirstcheck);
   tfirstcheck_isset = 0; /* used by TIMERCALC() macro. */
   *hastimedout      = 0;

   if (monitor->alarmsconfigured & ALARM_DATA) {
      if (mstats->internal.alarm.data.recv.isconfigured) {
         TIMERCALC(&mstats->internal.alarm.data.recv,
                   &tnextcheck,
                   tfirstcheck,
                   tnow);

         if (timercmp(&tzero, &tnextcheck, >))
            *hastimedout |= ALARM_INTERNAL_RECV;

         PRINT_DATALIMIT(monitor->number, "internal", "receiving", &tnextcheck);
      }

      if (mstats->internal.alarm.data.send.isconfigured) {
         TIMERCALC(&mstats->internal.alarm.data.send,
                   &tnextcheck,
                   tfirstcheck,
                   tnow);

         if (timercmp(&tzero, &tnextcheck, >))
            *hastimedout |= ALARM_INTERNAL_SEND;

         PRINT_DATALIMIT(monitor->number, "internal", "sending", &tnextcheck);
      }

      if (mstats->external.alarm.data.recv.isconfigured) {
         TIMERCALC(&mstats->external.alarm.data.recv,
                   &tnextcheck,
                   tfirstcheck,
                   tnow);

         if (timercmp(&tzero, &tnextcheck, >))
            *hastimedout |= ALARM_EXTERNAL_RECV;

         PRINT_DATALIMIT(monitor->number, "external", "receiving", &tnextcheck);
      }

      if (mstats->external.alarm.data.send.isconfigured) {
         TIMERCALC(&mstats->external.alarm.data.send,
                   &tnextcheck,
                   tfirstcheck,
                   tnow);

         if (timercmp(&tzero, &tnextcheck, >))
            *hastimedout |= ALARM_EXTERNAL_SEND;

         PRINT_DATALIMIT(monitor->number, "external", "sending", &tnextcheck);
      }
   }

   if (monitor->alarmsconfigured & ALARM_DISCONNECT) {
      if (mstats->internal.alarm.disconnect.isconfigured) {
         TIMERCALC(&mstats->internal.alarm.disconnect,
                   &tnextcheck,
                   tfirstcheck,
                   tnow);

         if (timercmp(&tzero, &tnextcheck, >))
            *hastimedout |= ALARM_INTERNAL;

         PRINT_DISCONNECTLIMIT(monitor->number, "internal", &tnextcheck);
      }

      if (mstats->external.alarm.disconnect.isconfigured) {
         TIMERCALC(&mstats->external.alarm.disconnect,
                   &tnextcheck,
                   tfirstcheck,
                   tnow);

         if (timercmp(&tzero, &tnextcheck, >))
            *hastimedout |= ALARM_EXTERNAL;

         PRINT_DISCONNECTLIMIT(monitor->number, "external", &tnextcheck);
      }
   }

   if (tfirstcheck_isset)
      slog(LOG_DEBUG, "%s: first check for monitor #%lu is in %ld.%06lds",
           function,
           (unsigned long)monitor->number,
           (long)tfirstcheck->tv_sec,
           (long)tfirstcheck->tv_usec);
   else {
      slog(LOG_DEBUG, "%s: no check scheduled for monitor #%lu",
           function, (unsigned long)monitor->number);

      tfirstcheck = NULL;
   }

   return tfirstcheck;
}

static void
siginfo(sig, si, sc)
   int sig;
   siginfo_t *si;
   void *sc;
{
   const char *function = "siginfo()";
   const int errno_s = errno;
   time_t tnow;
   unsigned long days, hours, minutes, seconds;

   SIGNAL_PROLOGUE(sig, si, errno_s);

   seconds = (unsigned long)socks_difftime(time_monotonic(&tnow),
                                           sockscf.stat.boot);

   seconds2days(&seconds, &days, &hours, &minutes);

   slog(LOG_INFO, "%s up %lu day%s, %lu:%.2lu:%.2lu",
        childtype2string(sockscf.state.type),
        days, days == 1 ? "" : "s",
        hours,
        minutes,
        seconds);

   SIGNAL_EPILOGUE(sig, si, errno_s);
}

static void
proctitleupdate(void)
{

#if 1
   setproctitle("%s", childtype2string(sockscf.state.type));
#else /* XXX fix. */
   setproctitle("%s: %lu alarm%s active",
                childtype2string(sockscf.state.type),
                (unsigned long)state.alarmsactive,
                state.alarmsactive == 1 ? "" : "s");
#endif

}

static char *
op2string(op)
   const size_t op;
{

   switch (op) {
      case OP_CONNECT:
         return "connect";

      case OP_DISCONNECT:
         return "disconnect";

      case OP_REMOVE_SESSION:
         return "session-remove";
   }

   SERRX(op);
}

static void
showalarms(iface)
   const monitor_if_t *iface;
{

#define DOPRINT_DATA(attr, op)                                                 \
do {                                                                           \
   slog(LOG_DEBUG,                                                             \
        "alarm if %s less or equal to %lu byte%s every %ld second%s",          \
        (op),                                                                  \
        (unsigned long)iface->alarm.data.attr.limit.bytes,                     \
        iface->alarm.data.attr.limit.bytes == 1 ? "" : "s",                    \
        (long)iface->alarm.data.attr.limit.seconds,                            \
        iface->alarm.data.attr.limit.seconds == 1 ? "" : "s");                 \
} while (/* CONSTCOND */ 0)


   if (iface->alarm.data.recv.isconfigured)
      DOPRINT_DATA(recv, "receiving");

   if (iface->alarm.data.send.isconfigured)
      DOPRINT_DATA(send, "sending");

   if (iface->alarm.disconnect.isconfigured)
      slog(LOG_DEBUG,
           "alarm if more than %lu/%lu disconnects occur during %ld second%s",
           (unsigned long)iface->alarm.disconnect.limit.disconnectc,
           (unsigned long)iface->alarm.disconnect.limit.sessionc,
           (long)iface->alarm.disconnect.limit.seconds,
           iface->alarm.disconnect.limit.seconds == 1 ? "" : "s");
}
