/*
 * Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2008, 2009, 2010, 2011,
 *               2012, 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"

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


#define FIRST_SHMEMID  (0)

typedef enum { keytype_ipv4 = 1, keytype_ipv6 } keystate_data_type;

typedef struct {
   keystate_data_type type;

   union {
      struct in_addr  ipv4;
      struct in6_addr ipv6;
   } data;
} keystate_data_t;

static unsigned long
mem2shmem(struct config *config, const unsigned long firstid);
/*
 * Deallocates from all rules starting with "firstrule" ordinary memory for
 * objects that should be in shared memory and allocates shared memory
 * for it instead.
 * "firstid" is the id to use for the first allocation.  The id of
 * subsequent allocations is incremented by one.
 *
 * Returns the id of the last id used.
 */

static void
keystate_removeindex(keystate_t *keystate, const size_t index);
/*
 * Removes the keystate entry with index "index".
 */

static keystate_data_t *
keystate_data(const keystate_t *keystate, const size_t index,
              keystate_data_t *key);
/*
 * Returns the keystate data at index "index" corresponding to the key
 * set in keystate.
 */

static const char *
keydata2string(const keystate_data_t *keydata, char *buf, size_t buflen);
/*
 * Stores a string representation of keydata "keydata" in "buf" and
 * returns a pointer to buf.
 *
 * If "buf" is NULL, the representation is written to a local static buffer
 * and a pointer to it is returned.
 */

static size_t
keystate_clientcount(const keystate_t *keystate, const size_t index);
/*
 * Returns the current clientcount for the keystate entry with index "index".
 */

static ssize_t
keystate_timer(const shmem_object_t *shmem);
/*
 * is there a relevant timer we can use for calculating how often to
 * scan for old keystate entries to expire and remove in "shmem"?
 *
 * Returns the timer as number of seconds if so, or -1 if not.
 */

static int
should_do_expirescan(const shmem_object_t *shmem);
/*
 * Should we do a scan for old keyentries in shmem at this time?
 * Returns true if so, false otherwise.
 */

void
shmem_setup(void)
{
   const char *function = "shmem_setup()";
   char *p;

   /*
    * Main shmem-stuff.  sockd.conf and shmemfd used for locking access
    * to shmem-stuff.
    */

   SASSERTX(sockscf.shmemfd == -1);
   SASSERTX(pidismother(sockscf.state.pid) == 1);

   STRCPY_ASSERTSIZE(sockscf.shmem_fnamebase, SOCKD_SHMEMFILE);

   /*
    * First check that this works ok.  If e.g., user has changed some
    * config-files and made strings too long, it might fail.
    */
   if ((sockscf.shmemfd = socks_mklock(sockscf.shmem_fnamebase,
                                       sockscf.shmem_fnamebase,
                                       sizeof(sockscf.shmem_fnamebase))) == -1)
      serr("%s: socks_mklock() failed to create shmemfile using base %s",
           function, sockscf.shmem_fnamebase);

   if ((p = sockd_getshmemname((unsigned long)FIRST_SHMEMID, key_from)) == NULL)
      serrx("%s: failed to generate shmem filename based on fnamebase "
            "\"%s\" and id %d",
            function, sockscf.shmem_fnamebase, FIRST_SHMEMID);

   if (strlen(p) >= sizeof(sockscf.shmem_fnamebase))
      serrx("%s: shmem filename is %ld bytes too long for this system.  Please "
            "reduce the length of the SOCKD_SHMEMFILE define and recompile",
            function,
            (unsigned long)(strlen(p) + 1) - sizeof(sockscf.shmem_fnamebase));

   if ((sockscf.shmeminfo = sockd_mmap(NULL,
                                       sizeof(*sockscf.shmeminfo),
                                       PROT_READ | PROT_WRITE,
                                       MAP_SHARED,
                                       sockscf.shmemfd,
                                       1)) == MAP_FAILED)
      serr("%s: failed to mmap shmeminfo", function);

   /* can unlink this file; all children will inherit the fd. */
   if (unlink(sockscf.shmem_fnamebase) != 0)
      serr("%s: failed to unlink shmemfile %s",
           function, sockscf.shmem_fnamebase);

   SASSERTX(sockscf.shmemconfigfd == -1);
   if ((sockscf.shmemconfigfd
   = socks_mklock(SOCKD_SHMEMFILE, NULL, 0)) == -1)
      serr("%s: socks_mklock() failed to create shmemfile using base %s",
           function, SOCKD_SHMEMFILE);

   /*
    * Hostcache also uses shmem, but has it's own area.
    */
   hostcachesetup();

#if HAVE_LDAP
   /*
    * And so does the LDAP module.
    */
   ldapcachesetup();
#endif /* HAVE_LDAP */

}

void
shmem_idupdate(config)
   struct config *config;
{
   static size_t lastshmid = FIRST_SHMEMID + 1;

   lastshmid = mem2shmem(config, lastshmid);
   ++lastshmid;
}

void
sockd_shmdt(rule, objects)
   rule_t *rule;
   const int objects;
{
   const char *function = "sockd_shmdt()";

   if ((objects & SHMEM_BW) && rule->bw_shmid)
      HANDLE_SHMDT(rule, bw, bw_shmid);

   if ((objects & SHMEM_MONITOR) && rule->mstats_shmid)
      HANDLE_SHMDT(rule, mstats, mstats_shmid);

   if ((objects & SHMEM_SS) && rule->ss_shmid)
      HANDLE_SHMDT(rule, ss, ss_shmid);
}

int
sockd_shmat(rule, objects)
   rule_t *rule;
   int objects;
{
   const char *function = "sockd_shmat()";
   int haveerror = 0;

   if ((objects & SHMEM_BW) && rule->bw_shmid != 0) {
      HANDLE_SHMAT(rule, bw, bw_shmid);

      if (rule->bw_shmid) {
#if DEBUG /* memory-mapped file contents may not be saved in coredumps. */
         shmem_object_t _shmem = *rule->bw;
#endif /* DEBUG */

         SASSERTX(rule->bw->type == SHMEM_BW);
      }
      else {
         SASSERTX(rule->bw == NULL);

         haveerror = 1;
         objects  &= ~SHMEM_BW;
      }
   }
#if DEBUG
   else
      slog(LOG_DEBUG, "%s: no bw_shmid we need to (re)attach to for rule #%lu",
           function, (unsigned long)rule->number);
#endif /* DEBUG */

   if ((objects & SHMEM_MONITOR) && rule->mstats_shmid != 0) {
      /*
       * monitor-files are deleted and reset on every sighup, so can
       * be deleted even if mother still exists.
       */
      HANDLE_SHMAT(rule, mstats, mstats_shmid);

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

         SASSERTX(rule->mstats->type == SHMEM_MONITOR);
      }
      else {
         SASSERTX(rule->mstats == NULL);

         haveerror = 1;
         objects  &= ~SHMEM_MONITOR;
      }
   }
#if DEBUG
   else
      slog(LOG_DEBUG,
           "%s: no mstats_shmid we need to (re)attach to for rule #%lu",
           function, (unsigned long)rule->number);
#endif /* DEBUG */

   if ((objects & SHMEM_SS) && rule->ss_shmid != 0) {
      HANDLE_SHMAT(rule, ss, ss_shmid);

      if (rule->ss_shmid) {
#if DEBUG /* memory-mapped file contents may not be saved in coredumps. */
         shmem_object_t _shmem = *rule->ss;
#endif /* DEBUG */

         SASSERTX(rule->ss->type == SHMEM_SS);
      }
      else {
         SASSERTX(rule->ss == NULL);

         haveerror = 1;
         objects  &= ~SHMEM_SS;
      }
   }
#if DEBUG
   else
      slog(LOG_DEBUG, "%s: no ss_shmid we need to (re)attach to for rule #%lu",
            function, (unsigned long)rule->number);
#endif /* DEBUG */

   if (haveerror)
      return -1;
   else
      return 0;
}

int
shmem_userule(rule, cinfo, emsg, emsglen)
   rule_t *rule;
   const clientinfo_t *cinfo;
   char *emsg;
   const size_t emsglen;
{
   const char *function = "shmem_userule()";
   size_t attached_to;

   slog(LOG_DEBUG, "%s: cinfo: %s",
        function, clientinfo2string(cinfo, NULL, 0));

   log_ruleinfo_shmid(rule, function, NULL);

   /*
    * Do sessions first, since that's the one that can fail.
    */

   attached_to = 0;

   if (rule->ss_shmid != 0 && rule->ss == NULL)
      if (sockd_shmat(rule, SHMEM_SS) == 0)
         attached_to |= SHMEM_SS;

   if (rule->ss_shmid != 0) {
      if (!session_use(rule->ss,
                       cinfo,
                       sockscf.shmemfd,
                       emsg,
                       emsglen)) {
         SASSERTX(rule->ss_shmid == (rule)->ss->mstate.shmid);

         sockd_shmdt(rule, attached_to);
         return -1;
      }
   }

   if (rule->bw_shmid != 0 && rule->bw == NULL)
      if (sockd_shmat(rule, SHMEM_BW) == 0)
         attached_to |= SHMEM_BW;

   if (rule->bw_shmid != 0)
      bw_use(rule->bw, cinfo, sockscf.shmemfd);

   if (rule->mstats_shmid != 0 && rule->mstats == NULL)
      if (sockd_shmat(rule, SHMEM_MONITOR) == 0)
         attached_to |= SHMEM_MONITOR;

   if (rule->mstats_shmid != 0)
      monitor_use(rule->mstats, cinfo, sockscf.shmemfd);

   sockd_shmdt(rule, attached_to);

   return 0;
}


char *
sockd_getshmemname(id, key)
   const unsigned long id;
   const statekey_t key;
{
/*   const char *function = "sockd_getshmemname()"; */
   static char name[PATH_MAX];

   SASSERTX(*sockscf.shmem_fnamebase != NUL);

   if (key == key_unset)
      snprintf(name, sizeof(name), "%s.%lu", sockscf.shmem_fnamebase, id);
   else
      snprintf(name, sizeof(name), "%s.%lu.%d",
               sockscf.shmem_fnamebase, id, (int)key);

   return name;
}

void *
sockd_mmap(mapping, size, prot, flags, fd, docreate)
   void *mapping;
   size_t size;
   const int prot;
   const int flags;
   const int fd;
   const int docreate;
{
   const char *function = "sockd_mmap()";
   const void *oldmapping = mapping;

   if (size == 0)
      return MAP_FAILED;

   if (docreate) {
      if (ftruncate(fd, (off_t)size) == -1) {
         swarn("%s: ftruncate() to offset %lu failed",
               function, (unsigned long)size);

         return MAP_FAILED;
      }
   }

   slog(LOG_DEBUG, "%s: mmap(2)'ing %lu bytes from fd %d, mapping = %p",
        function, (unsigned long)size, fd, mapping);

   if ((mapping = mmap(mapping,
                       size,
                       prot,
                       flags,
                       fd,
                       (off_t)0)) == MAP_FAILED)
      swarn("%s: %smmap(2) of %lu bytes from fd %d failed",
            function, oldmapping == NULL ? "" : "re-", (unsigned long)size, fd);

   return mapping;
}

int
shmem_use(shmem, cinfo, lock, mapisopen)
   shmem_object_t *shmem;
   const clientinfo_t *cinfo;
   const int lock;
   const int mapisopen;
{
   const char *function = "shmem_use()";
   const int doclosemap = (mapisopen ? 0 : 1);
#if DEBUG /* memory-mapped file contents may not be saved in coredumps. */
   shmem_object_t _shmem = *shmem;
#endif /* DEBUG */
   ssize_t statecount = -1;

   SASSERTX(shmem != NULL);
   SASSERTX(shmem->mstate.number > 0);

   if (sockscf.option.debug >= DEBUG_VERBOSE)
      slog(LOG_DEBUG,
           "%s: pre-use: lock = %d, # of clients = %lu, rule = %lu, "
           "shmem = %p, keystate = %p, mapisopen = %d, cinfo = %s, shmid %ld, "
           "key %d",
           function,
           lock,
           (unsigned long)shmem->mstate.clients,
           (unsigned long)shmem->mstate.number,
           shmem,
           shmem->keystate.keyv,
           mapisopen,
           clientinfo2string(cinfo, NULL, 0),
           shmem->mstate.shmid,
           (int)shmem->keystate.key);

   switch (shmem->type) {
      case SHMEM_BW:
      case SHMEM_MONITOR:
      case SHMEM_SS:
         break;

      default:
         SERRX(shmem->type);
   }

   socks_lock(lock, (off_t)shmem->mstate.shmid, 1, 1, 1);

   MUNPROTECT_SHMEMHEADER(shmem);

   ++shmem->mstate.clients;

   MPROTECT_SHMEMHEADER(shmem);
   if (shmem->keystate.key != key_unset) {
      size_t sizemapped;
      ssize_t i;
      int rc;

      if (!mapisopen) {
         MUNPROTECT_SHMEMHEADER(shmem);

         rc = keystate_openmap(shmem->mstate.shmid,
                               &shmem->keystate,
                               &sizemapped);

         MPROTECT_SHMEMHEADER(shmem);

         if (rc != 0) {
            socks_unlock(lock, (off_t)shmem->mstate.shmid, 1);

            slog(LOG_DEBUG, "%s: keystate_openmap() of shmid %lu failed: %s",
                 function, (unsigned long)shmem->mstate.shmid, strerror(errno));
            return -1;
         }
      }

      /*
       * increment the current count for address, or add an entry for the addr.
       */
      if ((i = keystate_index(shmem, cinfo, should_do_expirescan(shmem))) >= 0)
         slog(LOG_DEBUG, "%s: entry exists at index #%lu",
              function, (unsigned long)i);
      else {
         /*
          * a bit more complex.  Need to expand the array and add the new
          * entry; remap with a larger size first.
          */
         const char *fname = sockd_getshmemname(shmem->mstate.shmid,
                                                shmem->keystate.key);
         void *newmap;
         int fd;

         slog(LOG_DEBUG,
              "%s: need to expand keyv array to %lu entries for cinfo %s",
              function,
              (unsigned long)shmem->keystate.keyc + 1,
              clientinfo2string(cinfo, NULL, 0));

         if ((fd = open(fname, O_RDWR)) == -1) {
            swarn("%s: could not open shmemfile %s", function, fname);

            if (doclosemap) {
               MUNPROTECT_SHMEMHEADER(shmem);

               keystate_closemap(shmem->mstate.shmid,
                                 &shmem->keystate,
                                 sizemapped,
                                 -1);

               MPROTECT_SHMEMHEADER(shmem);
            }

            socks_unlock(lock, (off_t)shmem->mstate.shmid, 1);

            return -1;
         }

         sizemapped
         = (shmem->keystate.keyc + 1) * sizeof(*shmem->keystate.keyv);

         MUNPROTECT_SHMEMHEADER(shmem);

         newmap = sockd_mmap(shmem->keystate.keyv,
                             sizemapped,
                             PROT_READ | PROT_WRITE,
                             MAP_SHARED,
                             fd,
                             1);

         MPROTECT_SHMEMHEADER(shmem);

         close(fd);

         if (newmap == MAP_FAILED) {
            swarn("%s: failed to mmap(2) shmeminfo of size %lu from "
                  "file %s on fd %d",
                  function, (unsigned long)sizemapped, fname, fd);

            socks_unlock(lock, (off_t)shmem->mstate.shmid, 1);
            return -1;
         }

         shmem->keystate.keyv = newmap;

         i = shmem->keystate.keyc;

         MUNPROTECT_SHMEMHEADER(shmem);

         ++shmem->keystate.keyc;

         MPROTECT_SHMEMHEADER(shmem);
      }

      SASSERTX((size_t)i < shmem->keystate.keyc);

      MUNPROTECT_SHMEMHEADER(shmem);
      switch (shmem->keystate.key) {
         case key_from: {
            void *dst;

            switch (cinfo->from.ss_family) {
               case AF_INET:
                  dst = &shmem->keystate.keyv[i].data.from.addr.ipv4;
                  break;

               case AF_INET6:
                  dst = &shmem->keystate.keyv[i].data.from.addr.ipv6;
                  break;

               default:
                  SERRX(cinfo->from.ss_family);
            }

            shmem->keystate.keyv[i].data.from.safamily  = cinfo->from.ss_family;
            memcpy(dst,
                   GET_SOCKADDRADDR(&cinfo->from),
                   inaddrlen(cinfo->from.ss_family));
            ++shmem->keystate.keyv[i].data.from.addrc;

            statecount = (ssize_t)shmem->keystate.keyv[i].data.from.addrc;

            slog(LOG_DEBUG, "%s: updated entry for address %s at index #%lu",
                 function,
                 sockaddr2string2(&cinfo->from, 0, NULL, 0),
                 (unsigned long)i);

            break;
         }

#if HAVE_SOCKS_HOSTID
         case key_hostid:
            SASSERTX(shmem->keystate.keyinfo.hostindex <= cinfo->hostidc);

            shmem->keystate.keyv[i].data.hostid.ipv4
            = cinfo->hostidv[shmem->keystate.keyinfo.hostindex - 1];

            ++shmem->keystate.keyv[i].data.hostid.addrc;

            statecount = (ssize_t)shmem->keystate.keyv[i].data.hostid.addrc;

            break;
#endif /* HAVE_SOCKS_HOSTID */

         default:
            SERRX(shmem->keystate.key);
      }

      if (doclosemap) {
         MUNPROTECT_SHMEMHEADER(shmem);

         keystate_closemap(shmem->mstate.shmid,
                           &shmem->keystate,
                           sizemapped,
                           i);

         MPROTECT_SHMEMHEADER(shmem);
      }
   }
   MUNPROTECT_SHMEMHEADER(shmem);

   if (sockscf.option.debug >= DEBUG_VERBOSE)
      slog(LOG_DEBUG,
           "%s: post-use: lock = %d, clients = %lu (%ld), shmem = %p, "
           "shmid %ld, cinfo = %s",
           function,
           lock,
           (unsigned long)shmem->mstate.clients,
           (long)statecount,
           shmem,
           shmem->mstate.shmid,
           clientinfo2string(cinfo, NULL, 0));

   socks_unlock(lock, (off_t)shmem->mstate.shmid, 1);
   return 0;
}

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

   SASSERTX(shmem != NULL);
   SASSERTX(shmem->mstate.number > 0);

   if (sockscf.option.debug >= DEBUG_VERBOSE)
      slog(LOG_DEBUG, "%s: pre-use: lock = %d, # of clients = %lu, rule = %lu, "
                      "shmem = %p, cinfo = %s, shmid %ld, key %d",
                      function,
                      lock,
                      (unsigned long)shmem->mstate.clients,
                      (unsigned long)shmem->mstate.number,
                      shmem,
                      clientinfo2string(cinfo, NULL, 0),
                      shmem->mstate.shmid,
                      (int)shmem->keystate.key);

   switch (shmem->type) {
      case SHMEM_BW:
      case SHMEM_MONITOR:
      case SHMEM_SS:
         break;

      default:
         SERRX(shmem->type);
   }

   socks_lock(lock, (off_t)shmem->mstate.shmid, 1, 1, 1);

   if (shmem->keystate.key != key_unset) {
#if DEBUG /* memory-mapped file contents may not be saved in coredumps. */
      keystate_t _keystate;
#endif /* DEBUG */
      ssize_t i;
      size_t sizemapped;
      int rc;

      SASSERTX(shmem->keystate.keyv == NULL);
      SASSERTX(shmem->keystate.keyc > 0);

      MUNPROTECT_SHMEMHEADER(shmem);

      rc = keystate_openmap(shmem->mstate.shmid, &shmem->keystate, &sizemapped);

      MPROTECT_SHMEMHEADER(shmem);

      if (rc != 0) {
         socks_unlock(lock, (off_t)shmem->mstate.shmid, 1);

         slog(LOG_DEBUG, "%s: keystate_openmap() of shmid %lu failed: %s",
              function, (unsigned long)shmem->mstate.shmid, strerror(errno));

         return -1;
      }

#if DEBUG /* memory-mapped file contents may not be saved in coredumps. */
      _keystate = shmem->keystate;
#endif /* DEBUG */

      SASSERTX(sizemapped > 0);
      SASSERTX(shmem->keystate.keyv != NULL);
      SASSERTX(shmem->keystate.keyc > 0);

      i = keystate_index(shmem, cinfo, should_do_expirescan(shmem));
      SASSERTX(i >= 0);
      SASSERTX((size_t)i < shmem->keystate.keyc);

      MUNPROTECT_SHMEMHEADER(shmem);

      switch (shmem->keystate.key) {
         case key_from:
            SASSERTX(shmem->keystate.keyv[i].data.from.addrc > 0);
            --shmem->keystate.keyv[i].data.from.addrc;
            statecount = (ssize_t)shmem->keystate.keyv[i].data.from.addrc;
            break;

#if HAVE_SOCKS_HOSTID
         case key_hostid:
            SASSERTX(shmem->keystate.keyv[i].data.hostid.addrc > 0);
            --shmem->keystate.keyv[i].data.hostid.addrc;
            statecount = (ssize_t)shmem->keystate.keyv[i].data.hostid.addrc;
            break;
#endif /* HAVE_SOCKS_HOSTID */

         default:
            SERRX(shmem->keystate.key);
      }

      keystate_closemap(shmem->mstate.shmid, &shmem->keystate, sizemapped, i);

      MPROTECT_SHMEMHEADER(shmem);
   }

   MUNPROTECT_SHMEMHEADER(shmem);

   SASSERTX(shmem->mstate.clients > 0);
   --shmem->mstate.clients;

   MPROTECT_SHMEMHEADER(shmem);

   if (sockscf.option.debug >= DEBUG_VERBOSE)
      slog(LOG_DEBUG,
           "%s: post-use: lock = %d, clients = %lu (%ld), shmem = %p, "
           "shmid = %ld, cinfo = %s",
           function,
           lock,
           (unsigned long)shmem->mstate.clients,
           (long)statecount,
           shmem,
           shmem->mstate.shmid,
           clientinfo2string(cinfo, NULL, 0));

   socks_unlock(lock, (off_t)shmem->mstate.shmid, 1);
   return 0;
}


int
keystate_hasexpired(shmem, keyindex, timenow)
   const shmem_object_t *shmem;
   const size_t keyindex;
   const struct timeval *timenow;
{
   const char *function = "keystate_hasexpired()";
   struct timeval tdiff;
   char buf[64];
   int expired = 0;

   SASSERTX(keyindex < shmem->keystate.keyc);

   switch (shmem->type) {
      case SHMEM_SS: {

         if (!shmem->object.ss.throttle_perstate_isset)
            break; /* only throttle-limit can expire. */

         timersub(timenow,
                  &shmem->keystate.keyv[keyindex].info.ss.starttime,
                  &tdiff);

         if (tdiff.tv_sec >= shmem->object.ss.throttle_perstate.limit.seconds)
            expired = 1;

         break;
      }

      default:
         break;
   }

   if (expired) {
      if (!timerisset(&shmem->keystate.keyv[keyindex].info.ss.starttime))
         snprintf(buf, sizeof(buf), "yes (init)");
      else
         snprintf(buf, sizeof(buf), "yes, %ld.%06lds ago",
                  (long)tdiff.tv_sec, (long)tdiff.tv_usec);
   }

   if (sockscf.option.debug) {
      const char *keystring;
      keystate_data_t key;

      keystate_data(&shmem->keystate, keyindex, &key);
      keystring = keydata2string(&key, NULL, 0);

      slog(LOG_DEBUG,
           "%s: keystate address %s, index %lu, starttime = %ld.%06ld, "
           "clients = %lu, expired: %s",
           function,
           keystring,
           (unsigned long)keyindex,
           (long)shmem->keystate.keyv[keyindex].info.ss.starttime.tv_sec,
           (long)shmem->keystate.keyv[keyindex].info.ss.starttime.tv_usec,
           (unsigned long)keystate_clientcount(&shmem->keystate, keyindex),
           expired ? buf : "no");
   }

   return expired;
}

static unsigned long
mem2shmem(config, firstid)
   struct config *config;
   const unsigned long firstid;
{
   const char *function = "mem2shmem()";
   monitor_t *monitor;
   rule_t *rule;
   rule_t *rulev[]    = { config->crule,

#if HAVE_SOCKS_HOSTID
                          config->hrule,
#endif /* HAVE_SOCKS_HOSTID */

#if HAVE_SOCKS_RULES
                          config->srule
#endif /* HAVE_SOCKS_RULES */
                               };
   unsigned long nextid;
   size_t i;

   /*
    * Only main mother allocates the memory.  Children just set the
    * shmid value and use it to attach to the memory as needed later on.
    * Mother makes sure all the ids are in consecutive order starting
    * from the passed "firstid" argument, so children just need to
    * increment it to get the shmid of the next object.
    */
   SASSERTX(pidismother(sockscf.state.pid) == 1);

/*
 * Mother needs to create and fill in the correct contents initially,
 * but after that, the child-processes attach and update the segments
 * as needed.
 */
#define HANDLE_SHMCR(object, memfield, idfield, id)                            \
do {                                                                           \
   const statekey_t key = (object)->memfield->keystate.key;                    \
   const char *fname    = sockd_getshmemname(id, key_unset);                   \
   const int flags      = O_RDWR  | O_CREAT | O_EXCL;                          \
   const mode_t mode    = S_IRUSR | S_IWUSR;                                   \
   shmem_object_t *mem;                                                        \
   int fd;                                                                     \
                                                                               \
   SASSERTX(id != 0);                                                          \
   SASSERTX(fname != NULL && *fname != NUL);                                   \
                                                                               \
   SASSERTX((object)->memfield->mstate.number     > 0);                        \
   SASSERTX((object)->memfield->mstate.parenttype != object_none);             \
   SASSERTX((object)->type                        != object_none);             \
   SASSERTX((object)->memfield->type              != SHMEM_NONE);              \
                                                                               \
   if ((fd = open(fname, flags, mode)) == -1)                                  \
      serr("%s: failed to create shmemfile \"%s\"", function, fname);          \
                                                                               \
   slog(LOG_DEBUG,                                                             \
        "%s: will use filename %s for shmid %ld when creating shmem segment "  \
        "for " #memfield " in %s #%lu",                                        \
        function,                                                              \
        fname,                                                                 \
        (id),                                                                  \
        objecttype2string(object->type),                                       \
        (unsigned long)(object)->number);                                      \
                                                                               \
   if ((mem = sockd_mmap(NULL,                                                 \
                         sizeof(*(object)->memfield),                          \
                         PROT_READ | PROT_WRITE,                               \
                         MAP_SHARED,                                           \
                         fd,                                                   \
                         1)) == MAP_FAILED)                                    \
      serr("%s: sockd_mmap of size %lu failed",                                \
           function, (unsigned long)sizeof(*(object)->memfield));              \
                                                                               \
                                                                               \
   /*                                                                          \
    * replace the ordinary memory with shared memory.                          \
    */                                                                         \
   *mem               = *(object)->memfield;                                   \
   free((object)->memfield);                                                   \
   (object)->memfield = mem;                                                   \
                                                                               \
   close(fd);                                                                  \
                                                                               \
   if (key != key_unset) {                                                     \
      const char *fname = sockd_getshmemname(id, key);                         \
                                                                               \
      SASSERTX(fname != NULL && *fname != NUL);                                \
                                                                               \
      if ((fd = open(fname, flags, mode)) == -1)                               \
         serr("%s: failed to create file %s", function, fname);                \
                                                                               \
      /*                                                                       \
       * Just create the file for now.  Nothing to init.                       \
       */                                                                      \
                                                                               \
      close(fd);                                                               \
                                                                               \
      slog(LOG_DEBUG,                                                          \
           "%s: will use filename %s for shmid %lu/key %lu when creating "     \
           "shmem segment for " #memfield " in %s #%lu",                       \
           function,                                                           \
           fname,                                                              \
           (unsigned long)(id),                                                \
           (unsigned long)(key),                                               \
           objecttype2string((object)->type),                                  \
           (unsigned long)(object)->number);                                   \
   }                                                                           \
                                                                               \
   SASSERTX((object)->memfield->mstate.shmid == 0);                            \
   (object)->memfield->mstate.shmid = (id);                                    \
                                                                               \
   SASSERTX((object)->idfield == 0);                                           \
   (object)->idfield = (id);                                                   \
                                                                               \
   slog(LOG_DEBUG, "%s: allocated " #idfield " %ld for object #%lu",           \
        function, (object)->idfield, (unsigned long)(object)->number);         \
                                                                               \
   SASSERTX(munmap((object)->memfield, sizeof(*(object)->memfield)) == 0);     \
   (object)->memfield = NULL;                                                  \
} while (/* CONSTCOND */ 0)


   nextid = firstid;

   /*
    * Shmem for rules.
    */
   for (i = 0; i < ELEMENTS(rulev); ++i) {
      rule = rulev[i];

      while (rule != NULL) {
         if (rule->bw != NULL) {
            SASSERTX(rule->bw->type == SHMEM_BW);
            HANDLE_SHMCR(rule, bw, bw_shmid, nextid);
            ++nextid;
         }

         if (rule->ss != NULL) {
            SASSERTX(rule->ss->type == SHMEM_SS);
            HANDLE_SHMCR(rule, ss, ss_shmid, nextid);
            ++nextid;
         }

         rule = rule->next;
      }
   }

   /*
    * Shmem for monitors.
    */
   for (monitor = config->monitor; monitor != NULL; monitor = monitor->next) {
      if (monitor->mstats != NULL) {
         SASSERTX(monitor->mstats->type == SHMEM_MONITOR);
         HANDLE_SHMCR(monitor, mstats, mstats_shmid, nextid);
         ++nextid;
      }
   }

   slog(LOG_DEBUG,
        "%s: ok, allocated %ld shared memory id%s, first id is %lu, "
        "last id is %lu",
        function,
        (long)(nextid - firstid),
        nextid - firstid == 1 ? "" : "s",
        firstid,
        nextid);

   return nextid;
}

static void
keystate_removeindex(keystate, index)
   keystate_t *keystate;
   const size_t index;
{
   const char *function = "keystate_removeindex()";
   sa_family_t safamily;
   void *addr;

   switch (keystate->key) {
      case key_from:
         switch (keystate->keyv[index].data.from.safamily) {
            case AF_INET:
               safamily = AF_INET;
               addr     = &keystate->keyv[index].data.from.addr.ipv4;
               break;

            case AF_INET6:
               safamily = AF_INET6;
               addr     = &keystate->keyv[index].data.from.addr.ipv6;
               break;

            default:
               SERRX(keystate->keyv[index].data.from.safamily);
         }

         break;


#if HAVE_SOCKS_HOSTID
      case key_hostid:
         safamily = AF_INET6;
         addr     = &keystate->keyv[index].data.hostid.ipv4;
         break;
#endif /* HAVE_SOCKS_HOSTID */

      default:
         SERRX(keystate->key);
   }

   if (sockscf.option.debug) {
      char ntop[MAXSOCKADDRSTRING];

      if (inet_ntop(safamily, addr, ntop, sizeof(ntop)) == NULL) {
         swarn("%s: inet_ntop(3) failed on safamily %s, addr %s",
              function,
              safamily2string(safamily),
              addr2hexstring(addr, safamily, NULL, 0));
      }

      slog(LOG_DEBUG,
           "%s: removing entry for address %s (key: %s) at index #%lu "
           "from keystate. Will have %lu entries in keyv afterwards",
           function,
           ntop,
           statekey2string(keystate->key),
           (unsigned long)index,
           (unsigned long)keystate->keyc - 1);
   }


   /*
    * bzero the removed entry, so that if somebody overwrites it before
    * we close and reopen the mmap(2)-ed file, it will be zero.
    */
   bzero(&keystate->keyv[index], sizeof(*keystate->keyv));

   if (keystate->keyc > 1)
      memmove(&keystate->keyv[index],
              &keystate->keyv[index + 1],
              sizeof(*keystate->keyv) * (keystate->keyc - (index + 1)));

   --keystate->keyc;

   if (sockscf.option.debug) {
      size_t i;

      for (i = 0; i < keystate->keyc; ++i) {
         const char *keystring;
         keystate_data_t key;

         keystate_data(keystate, i, &key);
         keystring = keydata2string(&key, NULL, 0);

         slog(LOG_DEBUG, "%s: entry #%lu: %s",
              function, (unsigned long)i, keystring);
      }
   }
}

ssize_t
keystate_index(shmem, cinfo, doexpirescan)
   shmem_object_t *shmem;
   const clientinfo_t *cinfo;
   const int doexpirescan;
{
   const char *function = "keystate_index()";
   static ssize_t lastmatched = -1;
   const void *datatomatch;
   keystate_data_t keydata;
   keystate_data_type datatype;
   struct timeval timenow;
   ssize_t matchedi;
   size_t i, datalen;
   char extrainfo[128] = { NUL };

   if (doexpirescan)
      gettimeofday_monotonic(&timenow);

   SASSERTX(shmem->keystate.key != key_unset);

   switch (shmem->keystate.key) {
      case key_from:
         switch (cinfo->from.ss_family) {
            case AF_INET:
               datatype = keytype_ipv4;
               break;

            case AF_INET6:
               datatype = keytype_ipv6;
               break;

            default:
               SERRX(cinfo->from.ss_family);
         }

         datatomatch = GET_SOCKADDRADDR(&cinfo->from);
         datalen     = inaddrlen(cinfo->from.ss_family);
         break;

#if HAVE_SOCKS_HOSTID
      case key_hostid:
         SASSERTX(shmem->keystate.keyinfo.hostindex <= cinfo->hostidc);

         datatomatch = &cinfo->hostidv[shmem->keystate.keyinfo.hostindex - 1];
         datatype    = keytype_ipv4;
         datalen     = sizeof(keydata.data.ipv4);

         snprintf(extrainfo, sizeof(extrainfo), "(hostindex = %u)",
                  (unsigned)shmem->keystate.keyinfo.hostindex);

         break;
#endif /* HAVE_SOCKS_HOSTID */

      default:
         SERRX(shmem->keystate.key);
   }

   if (lastmatched != -1 && lastmatched < (ssize_t)shmem->keystate.keyc) {
      keystate_data(&shmem->keystate, lastmatched, &keydata);

      switch (keydata.type) {
         case keytype_ipv4:
            if (memcmp(&keydata.data.ipv4, datatomatch, datalen) == 0)
               matchedi = lastmatched;
            else
               matchedi = -1;
            break;

         case keytype_ipv6:
            if (memcmp(&keydata.data.ipv6, datatomatch, datalen) == 0)
               matchedi = lastmatched;
            else
               matchedi = -1;
            break;

         default:
            SERRX(keydata.type);
      }
   }
   else
      matchedi = -1;

   if (sockscf.option.debug) {
      char ntop[MAXSOCKADDRSTRING];

      switch (datatype) {
         case keytype_ipv4:
            if (inet_ntop(AF_INET, datatomatch, ntop, sizeof(ntop)) == NULL) {
               swarn("%s: inet_ntop(3) failed on %s %x",
                    function,
                    atype2string(AF_INET),
                    ((const struct in_addr *)datatomatch)->s_addr);

               snprintf(ntop, sizeof(ntop), "<unknown>");
            }
            break;

         case keytype_ipv6:
            if (inet_ntop(AF_INET6, datatomatch, ntop, sizeof(ntop)) == NULL) {
               swarn("%s: inet_ntop(3) failed on %s " IP6_FMTSTR,
                    function,
                    atype2string(AF_INET6),
                    IP6_ELEMENTS((const struct in6_addr *)datatomatch));

               snprintf(ntop, sizeof(ntop), "<unknown>");
            }
            break;

         default:
            SERRX(datatype);
      }

      slog(LOG_DEBUG,
           "%s: trying to find a match for key %s, address %s%s%s in keyv "
           "array with %lu entries.  Doexpirescan = %d, matchedi = %ld%s",
           function,
           statekey2string(shmem->keystate.key),
           ntop,
           *extrainfo == NUL ? "" : " ",
           *extrainfo == NUL ? "" : extrainfo,
           (unsigned long)shmem->keystate.keyc,
           doexpirescan,
           (long)matchedi,
           matchedi == -1 ?
               "" : doexpirescan ?
                 ".  Not scanning since index from last time matches" :  "");
   }

   if (matchedi != -1)
      return matchedi;

   i  = 0;
   while (i < shmem->keystate.keyc) {
      keystate_data_t keydata;
      int matches;

      if (doexpirescan && keystate_hasexpired(shmem, i, &timenow)) {
         MUNPROTECT_SHMEMHEADER(shmem);

         if (keystate_clientcount(&shmem->keystate, i) == 0) {
            keystate_removeindex(&shmem->keystate, i);
            continue;
         }
         else /* can't remove as long as long as entry is in use; just reset. */
            RESET_THROTTLE(&shmem->object.ss.throttle, timenow);

         MPROTECT_SHMEMHEADER(shmem);
      }

      keystate_data(&shmem->keystate, i, &keydata);

      if (keydata.type == datatype) {
         switch (keydata.type) {
            case keytype_ipv4:
               matches
               = (memcmp(&keydata.data.ipv4, datatomatch, datalen) == 0);
               break;

            case keytype_ipv6:
               matches
               = (memcmp(&keydata.data.ipv6, datatomatch, datalen) == 0);
               break;

            default:
               SERRX(keydata.type);
         }

         if (sockscf.option.debug)
            slog(LOG_DEBUG,
                 "%s: compared against %s at index #%lu using %lu bytes: %s",
                 function,
                 keydata2string(&keydata, NULL, 0),
                 (unsigned long)i,
                 (unsigned long)datalen,
                 matches ? "matches" : "no match");
      }
      else /* can't possibly match. */
         matches = 0;

      if (matches) {
         SASSERTX(matchedi == -1);
         matchedi = lastmatched = (ssize_t)i;

         if (!doexpirescan)
            break;
      }

      ++i;
   }

   if (doexpirescan) {
      MUNPROTECT_SHMEMHEADER(shmem);

      shmem->keystate.lastexpirescan = timenow.tv_sec;

      MPROTECT_SHMEMHEADER(shmem);
   }
   return matchedi;
}

int
keystate_openmap(id, keystate, sizemapped)
   const unsigned long id;
   keystate_t *keystate;
   size_t *sizemapped;
{
   const char *function = "keystate_openmap()";
   const char *fname    = sockd_getshmemname(id, keystate->key);
   size_t _sizemapped;

   SASSERTX(fname != NULL && *fname != NUL);
   SASSERTX(keystate->key != key_unset);

   slog(LOG_DEBUG, "%s: shmid %ld, key %d",
        function, id, (int)keystate->key);

   if (sizemapped == NULL)
      sizemapped = &_sizemapped;

   *sizemapped = keystate->keyc * sizeof(*keystate->keyv);

   if (*sizemapped == 0) {
      SASSERTX(keystate->keyc == 0);
      SASSERTX(keystate->keyv == NULL);
   }
   else {
      const int fd = open(fname, O_RDWR);

      if (fd == -1) {
         slog(sockd_motherexists() ? LOG_WARNING: LOG_DEBUG,
               "%s: could not open shmemfile %s generated from "
               "id %ld, key %d: %s",
               function,
               fname,
               id,
               (int)keystate->key,
               getppid() == 1 ?
                    "probably because mother has exited and deleted it already"
                  : strerror(errno));

         return -1;
      }

      if ((keystate->keyv = sockd_mmap(NULL,
                                       *sizemapped,
                                       PROT_READ | PROT_WRITE,
                                       MAP_SHARED,
                                       fd,
                                       0)) == MAP_FAILED) {
         swarn("%s: could not mmap shmemfile %s of size %lu generated from "
               "id %ld, key %d",
               function,
               fname,
               (unsigned long)*sizemapped,
               id,
               (int)keystate->key);

         keystate->keyv = NULL;
         close(fd);

         return -1;
      }

      close(fd);
   }

   slog(LOG_DEBUG, "%s: mapped %lu bytes for shmid %ld, key %d, at address %p",
                   function,
                   (unsigned long)*sizemapped,
                   id,
                   (int)keystate->key,
                   keystate->keyv);

   return 0;
}

void
keystate_closemap(id, keystate, mappedsize, changedindex)
   const unsigned long id;
   keystate_t *keystate;
   const size_t mappedsize;
   const ssize_t changedindex;
{
   const char *function = "keystate_closemap()";
   const char *fname    = sockd_getshmemname(id, keystate->key);
   size_t newsize;

   newsize = keystate->keyc * sizeof(*keystate->keyv);

   slog(LOG_DEBUG,
        "%s: mapped size for keystate of id %lu is %lu, newsize is %lu, "
        "changedindex: %ld",
        function,
        (unsigned long)id,
        (unsigned long)mappedsize,
        (unsigned long)newsize,
        (long)changedindex);

   if (sockscf.option.debug) {
      keystate_data_t keydata;
      size_t i;

      for (i = 0; i < keystate->keyc; ++i)
         slog(LOG_DEBUG, "%s: entry #%lu: %s",
              function,
              (unsigned long)i,
              keydata2string(keystate_data(keystate, i, &keydata), NULL, 0));
   }

   if (mappedsize == 0)
      SASSERTX(keystate->keyv == NULL);
   else {
#if !HAVE_UNIFIED_BUFFERCACHE
      /*
       * With a unified buffer cache, modifications to the mmap(2)-ed file will
       * be visible by other processes that open(2) and mmap(2) the same file
       * without having to msync(2) the memory to file.  I.e., this will work
       * Process1:
       *    t1: open(2) file F.
       *    t2: mmap(2) file F.
       *    t3: <modify mmap(2)-ed memory>
       *
       * Process2:
       *    t4: open(2) file F.
       *    t5: mmap(2) file F.
       *    t6: <see same thing Process1 sees>
       *
       * This code is for platforms where it does not work.  Currently
       * only OpenBSD AFAIK (5.1 at least).
       */
      void *addr;
      size_t len;
      int needmsync;

      SASSERTX(keystate->keyv != NULL);

      if (mappedsize < newsize) {
         needmsync = 1;

         /*
          * don't know what has changed, have to msync everything.
          */

         addr = keystate->keyv;
         len  = newsize;
      }
      else if (changedindex != -1) {
         needmsync = 1;

         addr = (void *)ROUNDDOWN((uintptr_t)&keystate->keyv[changedindex],
                                  sockscf.state.pagesize);

         SASSERTX((uintptr_t)keystate->keyv <= (uintptr_t)addr);

         len = ((uintptr_t)&keystate->keyv[changedindex] - (uintptr_t)addr)
               + sizeof(keystate->keyv[changedindex]);
      }
      else {
         SASSERTX(mappedsize == newsize);
         needmsync = 0;
      }

      if (needmsync) {
         const int rc = msync(addr, len, MS_SYNC);

         slog(rc == 0 ? LOG_DEBUG : LOG_WARNING,
              "%s: msync(%p, %lu, MS_SYNC), based on mappedsize %lu, "
              "newsize %lu, changedindex %ld, keyv %p %s (%s)",
              function,
              addr,
              len,
              (unsigned long)mappedsize,
              (unsigned long)newsize,
              (long)changedindex,
              keystate->keyv,
              rc == 0 ? "is ok" : "failed",
              strerror(errno));
      }
#endif /* !HAVE_UNIFIED_BUFFERCACHE */

      SASSERTX(keystate->keyv != NULL);

      if (munmap(keystate->keyv, mappedsize) != 0)
         swarn("%s: munmap(2) of keystate.keyv (%p) of size %lu failed",
               function, keystate->keyv, (unsigned long)mappedsize);

      keystate->keyv = NULL;
   }

   if (newsize != mappedsize) {
      if (truncate(fname, (off_t)newsize) == 0)
         slog(LOG_DEBUG, "%s: truncated shmemfile %s to size %lu",
                         function, fname, (unsigned long)newsize);
      else
         swarn("%s: could not truncate shmemfile %s to size %lu",
               function, fname, (unsigned long)newsize);
   }
}

#if DIAGNOSTIC && !SOCKS_CLIENT /* for internal debugging/testing. */
void
shmemcheck(void)
{
   const int errno_s = errno;
   rule_t *rulev[]    = { sockscf.crule,

#if HAVE_SOCKS_HOSTID
                          sockscf.hrule,
#endif /* HAVE_SOCKS_HOSTID */

#if HAVE_SOCKS_RULES
                          sockscf.srule
#endif /* HAVE_SOCKS_RULES */
                               };
   size_t i;

   /*
    * Shmem for existing rules.
    */
   for (i = 0; i < ELEMENTS(rulev); ++i) {
      rule_t *_rule = rulev[i];

      while (_rule != NULL) {
         rule_t rule = *_rule;

         /*
          * sockd_shm{at,dt} will do the checking.
          */
         sockd_shmat(&rule, SHMEM_ALL);
         sockd_shmdt(&rule, SHMEM_ALL);

         _rule = _rule->next;
      }
   }

   if (pidismother(sockscf.state.pid) == 1) {
      /*
       * Shmem for old rules.
       */
      for (i = 0; i < sockscf.oldshmemc; ++i) {
         rule_t rule;

         bzero(&rule, sizeof(rule));

         switch (sockscf.oldshmemv[i].type) {
            case SHMEM_BW:
               rule.bw_shmid = sockscf.oldshmemv[i].id;
               break;

            case SHMEM_MONITOR:
               rule.mstats_shmid = sockscf.oldshmemv[i].id;
               break;

            case SHMEM_SS:
               rule.ss_shmid = sockscf.oldshmemv[i].id;
               break;

            default:
               SERRX(sockscf.oldshmemv[i].type);
         }

         (void)sockd_shmat(&rule, sockscf.oldshmemv[i].type);
         (void)sockd_shmdt(&rule, sockscf.oldshmemv[i].type);
      }
   }

   errno = errno_s;
}
#endif /* DIAGNOSTIC && !SOCKS_CLIENT */

static int
should_do_expirescan(shmem)
   const shmem_object_t *shmem;
{
   ssize_t timer;

   if ((timer = keystate_timer(shmem)) != -1
   && socks_difftime(time_monotonic(NULL),
                     shmem->keystate.lastexpirescan) >= timer)
      return 1;

   return 0;
}

static ssize_t
keystate_timer(shmem)
   const shmem_object_t *shmem;
{

   switch (shmem->type) {
      case SHMEM_SS:
         if (shmem->object.ss.throttle_perstate_isset)
            return (size_t)shmem->object.ss.throttle_perstate.limit.seconds;
         else
            return -1;

      default:
         return -1;
   }
}

static keystate_data_t *
keystate_data(keystate, index, keydata)
   const keystate_t *keystate;
   const size_t index;
   keystate_data_t *keydata;
{

   SASSERTX(index < keystate->keyc);
   switch (keystate->key) {
      case key_from:
         switch (keystate->keyv[index].data.from.safamily) {
            case AF_INET:
               keydata->type      = keytype_ipv4;
               keydata->data.ipv4 = keystate->keyv[index].data.from.addr.ipv4;
               break;

            case AF_INET6:
               keydata->type      = keytype_ipv6;
               keydata->data.ipv6 = keystate->keyv[index].data.from.addr.ipv6;
               break;

            default:
               SERRX(keystate->keyv[index].data.from.safamily);
         }

         break;


#if HAVE_SOCKS_HOSTID
      case key_hostid:
         keydata->type      = keytype_ipv4;
         keydata->data.ipv4 = keystate->keyv[index].data.hostid.ipv4;
         break;
#endif /* HAVE_SOCKS_HOSTID */

      default:
         SERRX(keystate->key);
   }

   return keydata;
}

static const char *
keydata2string(keydata, buf, buflen)
   const keystate_data_t *keydata;
   char *buf;
   size_t buflen;

{
   const char *function = "keydata2string()";
   const void *addr;
   sa_family_t safamily;

   if (buf == NULL) {
      static char _buf[MAXSOCKADDRSTRING];

      buf    = _buf;
      buflen = sizeof(_buf);
   }

   switch (keydata->type) {
      case keytype_ipv4:
         safamily = AF_INET;
         addr     = &keydata->data.ipv4;
         break;

      case keytype_ipv6:
         safamily = AF_INET6;
         addr     = &keydata->data.ipv6;
         break;

      default:
         SERRX(keydata->type);
   }

   if (inet_ntop(safamily, addr, buf, buflen) == NULL) {
      addr2hexstring(addr, safamily, buf, buflen);
      swarn("%s: inet_ntop(3) failed on safamily %s, addr %s",
           function, safamily2string(safamily), buf);
   }

   return buf;
}

static size_t
keystate_clientcount(keystate, index)
   const keystate_t *keystate;
   const size_t index;
{

   SASSERTX(index < keystate->keyc);

   switch (keystate->key) {
      case key_from:
         return keystate->keyv[index].data.from.addrc;

#if HAVE_SOCKS_HOSTID
      case key_hostid:
         return keystate->keyv[index].data.hostid.addrc;
#endif /* HAVE_SOCKS_HOSTID */

      default:
         SERRX(keystate->key);
   }

   /* NOTREACHED */
}
