root/usr/src/cmd/tsol/tnd/tnd.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 *  Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
 *  Use is subject to license terms.
 */

#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/fcntl.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <locale.h>
#include <langinfo.h>
#include <search.h>
#include <tsol/label.h>
#include <errno.h>
#include <sys/tsol/tndb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <signal.h>
#include <sys/signal.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
#include <syslog.h>
#include <ctype.h>
#include <pwd.h>
#include <grp.h>
#include <door.h>
#include <synch.h>
#include <sys/tsol/tsyscall.h>
#include <nss_dbdefs.h>
#include <libtsnet.h>
#include <zone.h>

#include "tnd.h"

static FILE *tnlog_open(char *);
static void usage();
static void parse_opts(int, char **);
static int check_debugl(int);
static void load_tp();
static void load_tp_entry();
static void tnd_serve();
static void detachfromtty();
static void terminate();
static void noop();
static char *gettime();
static int isnumber(char *);
static void poll_now();
static int nss_get_tp();
static int nss_get_rh();
static void timer();
static void load_rh_marked();
static int rhtable_search_and_update(struct tsol_rhent *ent, int duplflag);
static int is_better_match(in_addr_t newaddr, int indx, tnrh_tlb_t *tlbt);
static int walk_cache_table(in_addr_t newaddr, char *name,
    int indx, tnd_tnrhdb_t *src);
static tnrh_tlb_t *lookup_cache_table(in_addr_t addr);
static int update_cache_table(tsol_rhent_t *ent, tnd_tnrhdb_t *src);
static void update_rh_entry(int op, struct tsol_rhent *rhentp);
static int handle_unvisited_nodes();
static in_addr_t rh_index_to_mask(uint_t masklen);
static tnrh_tlb_ipv6_t *lookup_cache_table_v6(in6_addr_t addr);
static in6_addr_t *rh_index_to_mask_v6(uint_t masklen, in6_addr_t *bitmask);
static void load_rh_marked_v6();
static int
    rhtable_search_and_update_v6(struct tsol_rhent *ent, int duplflag);
static int walk_cache_table_v6(in6_addr_t newaddr, char *name,
    int indx, tnd_tnrhdb_t *src);
static int update_cache_table_v6(tsol_rhent_t *ent, tnd_tnrhdb_t *src);
static int handle_unvisited_nodes_v6();

#ifdef DEBUG
static void print_entry(tsol_rhent_t *ent, int af);
static void print_tlbt(tnrh_tlb_t *tlbt);
static void rhtable_print();
static void cachetable_print();
static void rhtable_walk(void (*action)());
static void cachetable_print_v6();
static void rhtable_print_v6();
static void rhtable_walk_v6(void (*action)());
#endif /* DEBUG */

/*
 * The following constants and structures and the functions
 * that operate on them are similar to the ip_ire.c and ip6_ire.c
 * code in the kernel.
 */
#define TNRH_TABLE_HASH_SIZE 256
#define IP_ABITS 32
#define IP_MASK_TABLE_SIZE (IP_ABITS + 1)
#define RH_HOST_MASK (in_addr_t)0xffffffffU

#define IPV6_ABITS 128
#define IPV6_MASK_TABLE_SIZE (IPV6_ABITS + 1)
#define s6_addr8 _S6_un._S6_u8
#define s6_addr32 _S6_un._S6_u32

/*
 * Exclusive-or the 6 bytes that are likely to contain the MAC
 * address. Assumes table_size does not exceed 256.
 * Assumes EUI-64 format for good hashing.
 */
#define TNRH_ADDR_HASH_V6(addr)                         \
        (((addr).s6_addr8[8] ^ (addr).s6_addr8[9] ^     \
        (addr).s6_addr8[10] ^ (addr).s6_addr8[13] ^     \
        (addr).s6_addr8[14] ^ (addr).s6_addr8[15]) % TNRH_TABLE_HASH_SIZE)

#define TNRH_ADDR_MASK_HASH_V6(addr, mask)      \
        ((((addr).s6_addr8[8] & (mask).s6_addr8[8]) ^   \
        ((addr).s6_addr8[9] & (mask).s6_addr8[9]) ^     \
        ((addr).s6_addr8[10] & (mask).s6_addr8[10]) ^   \
        ((addr).s6_addr8[13] & (mask).s6_addr8[13]) ^   \
        ((addr).s6_addr8[14] & (mask).s6_addr8[14]) ^   \
        ((addr).s6_addr8[15] & (mask).s6_addr8[15])) % TNRH_TABLE_HASH_SIZE)

/* Mask comparison: is IPv6 addr a, and'ed with mask m, equal to addr b? */
#define V6_MASK_EQ(a, m, b)     \
        ((((a).s6_addr32[0] & (m).s6_addr32[0]) == (b).s6_addr32[0]) && \
        (((a).s6_addr32[1] & (m).s6_addr32[1]) == (b).s6_addr32[1]) &&  \
        (((a).s6_addr32[2] & (m).s6_addr32[2]) == (b).s6_addr32[2]) &&  \
        (((a).s6_addr32[3] & (m).s6_addr32[3]) == (b).s6_addr32[3]))


const in6_addr_t ipv6_all_zeros = { 0, 0, 0, 0 };

/*
 * This is a table of hash tables to keep
 * all the name service entries. We don't have
 * a separate hash bucket structure, instead mantain
 * a pointer to the hash chain.
 */
tnd_tnrhdb_t **tnrh_entire_table[IP_MASK_TABLE_SIZE];
tnd_tnrhdb_t **tnrh_entire_table_v6[IPV6_MASK_TABLE_SIZE];

/* reader/writer lock for tnrh_entire_table */
rwlock_t entire_rwlp;
rwlock_t entire_rwlp_v6;


/*
 * This is a hash table which keeps fully resolved
 * tnrhdb entries <IP address, Host type>. We don't have
 * a separate hash bucket structure, instead
 * mantain a pointer to the hash chain.
 */
tnrh_tlb_t *tnrh_cache_table[TNRH_TABLE_HASH_SIZE];
tnrh_tlb_ipv6_t *tnrh_cache_table_v6[TNRH_TABLE_HASH_SIZE];

/* reader/writer lock for tnrh_cache_table */
rwlock_t cache_rwlp;
rwlock_t cache_rwlp_v6;

FILE     *logf;
int      debugl = 0;
int      poll_interval = TND_DEF_POLL_TIME;
int      delay_poll_flag = 0;

void    *tp_tree;

#define _SZ_TIME_BUF 100
char time_buf[_SZ_TIME_BUF];

#define cprint(s, param) { \
                register FILE *consl; \
\
                if ((consl = fopen("/dev/msglog", "w")) != NULL) { \
                    setbuf(consl, NULL); \
                    (void) fprintf(consl, "tnd: "); \
                    (void) fprintf(consl, s, param); \
                    (void) fclose(consl); \
                        } \
            }

#define RHENT_BUF_SIZE 300
#define TPENT_BUF_SIZE 2000

/* 128 privs * (24 bytes + 1 deliminator)= 3200 bytes + 1200 cushion */
#define STRING_PRIVS_SIZE 4800
#define ID_ENT_SIZE 500

int
main(int argc, char **argv)
{


        const ucred_t   *uc = NULL;
        const priv_set_t        *pset;
        struct sigaction act;

        /* set the locale for only the messages system (all else is clean) */
        (void) setlocale(LC_ALL, "");
#ifndef TEXT_DOMAIN                     /* Should be defined by cc -D */
#define TEXT_DOMAIN     "SYS_TEST"      /* Use this only if it weren't */
#endif
        (void) textdomain(TEXT_DOMAIN);

        if (getzoneid() != GLOBAL_ZONEID) {
                syslog(LOG_ERR, "can not run tnd from a local zone");
                exit(-1);
        }


        if (((uc = ucred_get(getpid())) == NULL) ||
            ((pset = ucred_getprivset(uc, PRIV_EFFECTIVE)) == NULL)) {
                syslog(LOG_ERR, "don't have privilege set");
                exit(-1);
        }

        if (!priv_ismember(pset, PRIV_SYS_NET_CONFIG)) {
                syslog(LOG_ERR, "don't have privilege to run tnd");
                exit(-1);
        }


        /* parse command line options */
        (void) parse_opts(argc, argv);

        /*
         * Initialize reader/writer locks. To be
         * used within this process only.
         */
        if ((rwlock_init(&entire_rwlp, USYNC_THREAD, 0) != 0) ||
            (rwlock_init(&entire_rwlp_v6, USYNC_THREAD, 0) != 0) ||
            (rwlock_init(&cache_rwlp, USYNC_THREAD, 0) != 0) ||
            (rwlock_init(&cache_rwlp_v6, USYNC_THREAD, 0) != 0)) {
                syslog(LOG_ERR, "cannot initialize lock");
                exit(-1);
        }

        /* catch the usual termination signals for graceful exit */
        (void) sigset(SIGINT, terminate);
        (void) sigset(SIGTERM, terminate);
        (void) sigset(SIGQUIT, terminate);
        (void) sigset(SIGUSR1, noop);

        act.sa_handler = timer;
        act.sa_flags = SA_RESTART;
        (void) sigemptyset(&act.sa_mask);
        (void) sigaddset(&act.sa_mask, SIGALRM);
        (void) sigaddset(&act.sa_mask, SIGHUP);
        (void) sigaction(SIGALRM, &act, NULL);
        (void) sigaction(SIGHUP, &act, NULL);

        if (debugl == MAX_TND_DEBUG) {
                (void) fprintf(logf, "%s : ", gettime());
                (void) fprintf(logf, gettext("tnd started. pid= %d\n"),
                    getpid());
                (void) fprintf(logf, "%s : ", gettime());
                (void) fprintf(logf,
                    gettext("max level debugging! not forking\n"));
                (void) fflush(logf);
        } else {
                detachfromtty();
        }

        if (!delay_poll_flag) {
                (void) sigprocmask(SIG_BLOCK, &act.sa_mask, NULL);
                timer();
                (void) sigprocmask(SIG_UNBLOCK, &act.sa_mask, NULL);
        }

        if (debugl != MAX_TND_DEBUG) {
                (void) sigsend(P_PID, getppid(), SIGUSR1);
        }

        (void) tnd_serve();

        /* NOT REACHED */
        return (0);
}


/*
 * Compare addresses after masking off unneeded bits.
 * We do this to handle addresses where prefix_len is
 * less than the bit length.
 */
static int
rhaddr_compar_mask(struct sockaddr_in *tp1, struct tnd_tnrhdb_c *tp2, int i)
{
        struct sockaddr_in *saddrp;
        in_addr_t tmpmask = rh_index_to_mask(i);

        saddrp = (struct sockaddr_in *)(&tp2->rh_ent.rh_address.ip_addr_v4);

#ifdef DEBUG
        (void) fprintf(logf, gettext("rhaddr_compar_mask mask = 0x%4x, \
            tp1 = 0x%4x, tp2 = 0x%4x\n"), tmpmask, (tp1->sin_addr),
            (saddrp->sin_addr.s_addr & tmpmask));
        (void) fprintf(logf, gettext("rhaddr_compar_mask return = %d\n"),
            (tp1->sin_addr.s_addr == (saddrp->sin_addr.s_addr & tmpmask)));
#endif
        return (tp1->sin_addr.s_addr == (saddrp->sin_addr.s_addr & tmpmask));
}


/*
 * we use this where exact match is needed.
 */
static int
rhaddr_compar(struct sockaddr_in *tp1, struct tnd_tnrhdb_c *tp2)
{
        struct sockaddr_in *saddrp;

        saddrp = (struct sockaddr_in *)(&tp2->rh_ent.rh_address.ip_addr_v4);

#ifdef DEBUG
        (void) fprintf(logf, gettext("\t tp1 saddrp IP : %s %s\n"),
            inet_ntoa(tp1->sin_addr), inet_ntoa(saddrp->sin_addr));
#endif

        return (tp1->sin_addr.s_addr == saddrp->sin_addr.s_addr);
}

/*
 * Compare v6 addresses after masking off unneeded bits.
 * We do this to handle addresses where prefix_len is
 * less than the bit length.
 */
static int
rhaddr_compar_mask_v6(struct sockaddr_in6 *tp1, struct tnd_tnrhdb_c *tp2, int i)
{
        struct sockaddr_in6 *saddrp;
        in6_addr_t tmpmask;

        (void) rh_index_to_mask_v6(i, &tmpmask);
        saddrp = (struct sockaddr_in6 *)(&tp2->rh_ent.rh_address.ip_addr_v6);
        return (V6_MASK_EQ(tp1->sin6_addr, tmpmask, saddrp->sin6_addr));
}

/*
 * we use this where v6 exact match is needed.
 */
static int
rhaddr_compar_v6(struct sockaddr_in6 *tp1, struct tnd_tnrhdb_c *tp2)
{
        struct sockaddr_in6 *saddrp;

        saddrp = (struct sockaddr_in6 *)(&tp2->rh_ent.rh_address.ip_addr_v6);
        return (IN6_ARE_ADDR_EQUAL(&tp1->sin6_addr, &saddrp->sin6_addr));
}

static int
get_hashvalue(in_addr_t addr)
{
        unsigned char *bp;

        bp = (unsigned char *) &addr;
        return ((bp[0] ^ bp[1] ^ bp[2] ^ bp[3]) % TNRH_TABLE_HASH_SIZE);
}

/*
 * Convert length for a mask to the mask.
 */
static in_addr_t
rh_index_to_mask(uint_t masklen)
{
        if (masklen == 0)
                return (0);
        return (htonl(RH_HOST_MASK << (IP_ABITS - masklen)));
}

/*
 * Convert length for a mask to the mask.
 * Returns the argument bitmask.
 */
static in6_addr_t *
rh_index_to_mask_v6(uint_t masklen, in6_addr_t *bitmask)
{
        uint32_t *ptr;

        *bitmask = ipv6_all_zeros;

        ptr = (uint32_t *)bitmask;
        while (masklen > 32) {
                *ptr++ = 0xffffffffU;
                masklen -= 32;
        }
        *ptr = htonl(0xffffffffU << (32 - masklen));
        return (bitmask);
}


static void
parse_opts(argc, argv)
        int argc;
        char **argv;
{
        char *logfile = TNDLOG;
        extern char *optarg;
        int c;

        while ((c = getopt(argc, argv, "d:f:p:n")) != EOF)
            switch (c) {
            case 'd':
                if (isnumber(optarg)) {
                    debugl = atoi(optarg);
                    if (check_debugl(debugl) == -1)
                    debugl = 1; /* default to 1 */
                } else {
                    usage();
                    exit(1);
                }
                break;
            case 'f':
                logfile = optarg;
                break;
            case 'p':
                if (isnumber(optarg)) {
                    poll_interval = atoi(optarg);
                    if (poll_interval == 0)
                        usage();
                } else {
                    usage();
                }
                break;
            case 'n':
                delay_poll_flag = 1;
                break;
            case '?':
                usage();
            }

        logf = tnlog_open(logfile);
}

static int
check_debugl(debug_level)
        int debug_level;
{
        if (debug_level > MAX_TND_DEBUG) {
            if ((debugl > 0) && (logf != NULL)) {
                (void) fprintf(logf, "%s : ", gettime());
                (void) fprintf(logf,
                    gettext("invalid debug level: %d, not changed!\n"),
                        debug_level);
                (void) fflush(logf);
            }
            cprint("invalid debug level: %d, not changed!\n",
                debug_level);
            return (-1);
        }
        return (0);
}

static FILE *
tnlog_open(logfile)
        char *logfile;
{
        FILE *fp;

        if ((fp = fopen(logfile, "a")) == NULL) {
                syslog(LOG_ERR, "unable to open logfile %s",
                        logfile);
                exit(-1);
        }
        (void) fprintf(fp, "%s : ", gettime());
        (void) fprintf(fp, gettext("tnd starting\n"));

        return (fp);
}

static void
detachfromtty()
{
        pid_t tnd_pid;

        (void) close(0);
        (void) close(1);
        (void) close(2);
        switch (tnd_pid = fork()) {
        case (pid_t)-1:
                if (debugl && (logf != NULL)) {
                        (void) fprintf(logf, "%s : ", gettime());
                        (void) fprintf(logf,
                            gettext("fork() failed: %s\n"), strerror(errno));
                        (void) fflush(logf);
                }
                cprint("fork() failed: %s\n", strerror(errno));
                break;
        case 0:
                break;
        default:
                if (debugl && (logf != NULL)) {
                        (void) fprintf(logf, "%s : ", gettime());
                        (void) fprintf(logf,
                            gettext("tnd started. pid= %d\n"), tnd_pid);
                        (void) fflush(logf);
                }
                /*
                 * Suspend parent till child signals it. We catch the signal
                 * in order to return correct exit value.
                 */

                (void) pause();
                exit(0);
        }
        (void) setsid();
        (void) open("/dev/null", O_RDWR, 0);
        (void) dup(0);
        (void) dup(0);
}

static void
usage()
{
        (void) fprintf(stderr, gettext(
            "Usage:\n\ttnd [-d debug-level][-f debug-file]"
            "[-p poll-interval]\n"));

        exit(1);
}

static int
isnumber(s)
char *s;
{
        register int c;

        /* LINTED */
        while (c = *s++)
                if (!isdigit(c))
                        return (0);
        return (1);
}


/*
 * match any entry in any tree
 *      used in tree removal
 */
/* ARGSUSED */
static int
any_compar(const void *v1, const void *v2)
{
        return (0);
}

static int
tp_compar(const void *v1, const void *v2)
{
        struct tnd_tnrhtp_c     *tp1 = (struct tnd_tnrhtp_c *)v1;
        struct tnd_tnrhtp_c     *tp2 = (struct tnd_tnrhtp_c *)v2;
        return (strcmp(tp1->tp_ent.name, tp2->tp_ent.name));
}

/*
 * Build tree of tp entries, tossing duplicates
 */
static int
nss_get_tp()
{
        tsol_tpent_t tp; /* to store result */
        tsol_tpent_t *tpp;
        struct tnd_tnrhtp_c *new, **old;
        int count = 0;

        tpp = &tp;

        tsol_settpent(1);

        while ((tpp = (tsol_tpent_t *)tsol_gettpent()) != NULL) {
                if ((new = (struct tnd_tnrhtp_c *)
                    calloc(1, sizeof (struct tnd_tnrhtp_c))) == NULL)
                        continue;
                (void) memcpy(&new->tp_ent, tpp, sizeof (tp));
                old = (struct tnd_tnrhtp_c **)tsearch(new, &tp_tree, tp_compar);
                if (*old != new)
                        free(new);
                else
                        count++;
        }
        tsol_endtpent();

        return (count);
}

/* load tp ents into kernel */
static void
load_tp()
{
        twalk(tp_tree, load_tp_entry);
}


static void
/* LINTED */
load_tp_entry(struct tnd_tnrhtp_c **tppp, VISIT visit, int level)
{
        struct tnd_tnrhtp_c *tpp;

        if (!(visit == postorder || visit == leaf))
                return;

        tpp = *tppp;
        if (tnrhtp(TNDB_LOAD, &tpp->tp_ent)) {
                if (debugl && (logf != NULL)) {
                        (void) fprintf(logf, "%s : ", gettime());
                        (void) fprintf(logf, gettext("tnrhtp() failed 0: %s\n"),
                            strerror(errno));
                        (void) fprintf(logf,
                            gettext("load of remote-host template "
                            "%s into kernel cache failed\n"),
                            tpp->tp_ent.name);
                        (void) fflush(logf);
                }
                cprint("tnrhtp() failed here 1: %s\n", strerror(errno));
        }
}

static void
tp_flush_cache()
{
        struct tnd_tnrhtp_c     dummy;
        struct tnd_tnrhtp_c     *tp;

        while (tp = tfind(&dummy, tp_tree, any_compar)) {
                (void) tdelete(tp, &tp_tree, tp_compar);
                free(tp);
        }
}

/*
 * Build/update the table of rh entries from the
 * name service sources, files, ldap etc.
 */
static int
nss_get_rh()
{
        int found_entry = 0;
        int count = 0;
        int newflag = 0;
        struct tsol_rhent rh; /* to store result */
        struct tsol_rhent *rhp;
        tsol_tpent_t tp;
        sa_family_t af;
        int v6cnt = 0;

        rhp = &rh;

        tsol_setrhent(1);
        while ((rhp = (struct tsol_rhent *)
            tsol_getrhent()) != NULL) {
                /*
                 * Check if this is a known template name
                 * Entries with missing template in kernel will be logged
                 * and not added to cache.
                 */

                (void) fprintf(logf, gettext("getrhent template name: %s\n"),
                    rhp->rh_template);

                (void) strncpy(tp.name, rhp->rh_template, TNTNAMSIZ - 1);
                if (tnrhtp(TNDB_GET, &tp) != 0) {
                        if (debugl && (logf != NULL))
                                (void) fprintf(logf,
                                    gettext("Unknown template name: %s\n"),
                                    rhp->rh_template);
                        cprint(gettext("Unknown template name: %s\n"),
                            rhp->rh_template);
                        continue;
                }
                found_entry++;          /* found a valid tnrhdb entry */
                af = rhp->rh_address.ta_family;

                if (af == AF_INET) {
#ifdef DEBUG
                        (void) fprintf(logf, gettext("nss_get_rh() v4\n"));
#endif
                        (void) rw_wrlock(&entire_rwlp);
                        (void) rw_wrlock(&cache_rwlp);

                        /*
                         * Both cache table and entire table can be modified
                         * by this function. So, get both locks.
                         */
                        newflag = rhtable_search_and_update(rhp, 1);

                        (void) rw_unlock(&cache_rwlp);
                        (void) rw_unlock(&entire_rwlp);
                } else if (af == AF_INET6) {
#ifdef DEBUG
                        (void) fprintf(logf, gettext("nss_get_rh() v6\n"));
#endif
                        v6cnt++;
                        (void) rw_wrlock(&entire_rwlp_v6);
                        (void) rw_wrlock(&cache_rwlp_v6);

                        /*
                         * Both cache table and entire table can be modified
                         * by this function. So, get both locks.
                         */
                        newflag = rhtable_search_and_update_v6(rhp, 1);

                        (void) rw_unlock(&cache_rwlp_v6);
                        (void) rw_unlock(&entire_rwlp_v6);
                }
                if (newflag)
                        count++;
        }
        tsol_endrhent();

        /*
         * If the first tsol_getrhent() failed, we bail out and
         * try again at the next poll interval, just in case the
         * name service was not reachable the first time.
         */
        if (!found_entry) {
#ifdef  DEBUG
                if (logf != NULL)
                        (void) fprintf(logf,
                            gettext("Unable to contact ldap server?\n"));
#endif
                return (count);
        }

        (void) rw_wrlock(&entire_rwlp);
        (void) rw_wrlock(&cache_rwlp);
        /*
         * Handle deletions in the name service entries
         * Both cache table and entire table can be modified
         * by this function. So, get both locks.
         */
        count += handle_unvisited_nodes();

        (void) rw_unlock(&cache_rwlp);
        (void) rw_unlock(&entire_rwlp);

        if (v6cnt > 0) {
                (void) rw_wrlock(&entire_rwlp_v6);
                (void) rw_wrlock(&cache_rwlp_v6);
                /*
                 * Handle deletions in the name service entries
                 * Both cache table and entire table can be modified
                 * by this function. So, get both locks.
                 */
                count += handle_unvisited_nodes_v6();

                (void) rw_unlock(&cache_rwlp_v6);
                (void) rw_unlock(&entire_rwlp_v6);
        }

        return (count);
}

/*
 * Check if any deletions in  the name service tables
 * affect the cache entries. We need to do this
 * in order to not flush the entrie kernel tnrhdb
 * cache every time we poll the name services.
 */
static int
handle_unvisited_nodes()
{
        int i, j, cnt = 0;
        tnrh_tlb_t *tlbt;
        tnd_tnrhdb_t *rhent, *prev;

        for (i = 0; i < TNRH_TABLE_HASH_SIZE; i++)
                if ((tlbt = tnrh_cache_table[i]) != NULL)
                        do {
                                if (tlbt->src->visited == 0) {
                                        /*
                                         * Mark for deletion of both our cache
                                         * entry and the kernel cache entry.
                                         */
                                        tlbt->reload = TNDB_DELETE;
                                        cnt++;
                                }

                                tlbt = tlbt->next;
                        } while (tlbt != NULL);

        /*
         * Remove any unvisited nodes. This can
         * happen if they are not in use by any cache entry. Then,
         * mark all nodes in entire_table, un-visited, for next iteration.
         */

        for (i = 0; i <= IP_ABITS; i++) {
                if (tnrh_entire_table[i] == NULL)
                        continue;

                for (j = 0; j < TNRH_TABLE_HASH_SIZE; j++) {
                        prev = rhent = tnrh_entire_table[i][j];

                        while (rhent != NULL) {
                                if (rhent->visited == 0) {
                                        /*
                                         * Check if start node
                                         */
                                        if (rhent == tnrh_entire_table[i][j]) {
                                                prev = tnrh_entire_table[i][j] =
                                                    rhent->rh_next;
                                        } else {
                                                /* bypass the deleted node */
                                                prev->rh_next = rhent->rh_next;
                                                prev = prev->rh_next;
                                        }

                                        free(rhent);

                                        if (prev == NULL)
                                                break;
                                        else {
                                                rhent = prev;
                                                continue;
                                        }
                                } else
                                        rhent->visited = 0;

                                prev = rhent;
                                rhent = rhent->rh_next;
                        }
                }
        }

        return (cnt);
}

/*
 * Check if any deletions in  the name service tables
 * affect the cache entries. We need to do this
 * in order to not flush the entrie kernel tnrhdb
 * cache every time we poll the name services.
 */
static int
handle_unvisited_nodes_v6()
{
        int i, j, cnt = 0;
        tnrh_tlb_ipv6_t *tlbt;
        tnd_tnrhdb_t *rhent, *prev;

        for (i = 0; i < TNRH_TABLE_HASH_SIZE; i++)
        if ((tlbt = tnrh_cache_table_v6[i]) != NULL)
        do {
                if (tlbt->src->visited == 0) {
                        /*
                         * Mark for deletion of both our cache entry
                         * and the kernel cache entry.
                         */
                        tlbt->reload = TNDB_DELETE;
                        cnt++;
                }

                tlbt = tlbt->next;
        } while (tlbt != NULL);

        /*
         * Remove any unvisited nodes. This can
         * happen if they are not in use by any cache entry. Then,
         * mark all nodes in entire_table, un-visited, for next iteration.
         */

        for (i = 0; i <= IPV6_ABITS; i++) {
        if (tnrh_entire_table_v6[i] == NULL)
                continue;

        for (j = 0; j < TNRH_TABLE_HASH_SIZE; j++) {
                prev = rhent = tnrh_entire_table_v6[i][j];

                while (rhent != NULL) {
                if (rhent->visited == 0) {      /* delete the node */
                        /* Check if start node */
                        if (rhent == tnrh_entire_table_v6[i][j]) {
                                prev = tnrh_entire_table_v6[i][j] =
                                    rhent->rh_next;
                        } else {
                                /* bypass the deleted node */
                                prev->rh_next = rhent->rh_next;
                                prev = prev->rh_next;
                        }

                        free(rhent);
                        if (prev == NULL)
                                break;
                        else {
                                rhent = prev;
                                continue;
                        }
                } else
                        rhent->visited = 0;

                prev = rhent;
                rhent = rhent->rh_next;
                }
        }
        }

        return (cnt);
}


/*
 * Search the hash chain for the address. If not found,
 * add the entry to the hash table. If necessary,
 * construct the hash table.
 * If the rh entry is in table, we may update its template name
 */
static int
rhtable_search_and_update(struct tsol_rhent *ent, int duplflag)
{
        struct sockaddr_in *saddrp;
        unsigned char hash;
        tnd_tnrhdb_t *rhent;
        int i;
        int rflag = 1;

        struct tnd_tnrhdb_c *new;

        saddrp = (struct sockaddr_in *)&ent->rh_address.ip_addr_v4;
        hash = (unsigned char) get_hashvalue(saddrp->sin_addr.s_addr);
        i = ent->rh_prefix;

#ifdef DEBUG
        (void) fprintf(logf, gettext("\trhtable_search_and_update IP address:\
                %s\n"), inet_ntoa(saddrp->sin_addr));
#endif

        if (tnrh_entire_table[i] == NULL) {
                if ((tnrh_entire_table[i] = (tnd_tnrhdb_t **)calloc(
                    TNRH_TABLE_HASH_SIZE, sizeof (tnd_tnrhdb_t *))) == NULL) {
                        return (0);
                }
        }

        rhent = tnrh_entire_table[i][hash];
#ifdef DEBUG
        (void) fprintf(logf, gettext("\tsearch_and_update i = %d hash = %d\n"),
            i, hash);
        if (rhent != NULL) {
                (void) fprintf(logf, gettext("\trhent visited  = %d\n"),
                    rhent->visited);
                print_entry(&rhent->rh_ent, AF_INET);
        } else {
                (void) fprintf(logf, gettext("\tsearch_and_update null\n"));
        }
#endif
        while (rhent != NULL) {
                if (rhaddr_compar(saddrp, rhent) == 1) {
                        /* Check if this is a duplicate entry */
                        if ((rhent->visited == 1) && duplflag)
                                return (0);

                        if (duplflag)
                                rhent->visited = 1;

                        if (strcmp(ent->rh_template,
                            rhent->rh_ent.rh_template) != 0) {
                                /*
                                 * Template is changed in the name service.
                                 * Use the new template.
                                 */
                                (void) strcpy(rhent->rh_ent.rh_template,
                                    ent->rh_template);
                                /*
                                 * Check if this modified entry
                                 * affects the cache table.
                                 */
                                rflag = update_cache_table(ent, rhent);
                                return (rflag);
                        } else
                                return (0);
                }
                rhent = rhent->rh_next;
        }

        /* Not found. Add the entry */
        new = (struct tnd_tnrhdb_c *)calloc(1,
            sizeof (struct tnd_tnrhdb_c));
        if (new == NULL)
                return (0);
        (void) memcpy(&new->rh_ent, ent, sizeof (struct tsol_rhent));
        if (duplflag)
                new->visited = 1;       /* Mark all new nodes visited */

        /* linked list. Insert in the beginning */
        new->rh_next = tnrh_entire_table[i][hash];
        tnrh_entire_table[i][hash] = new;
#ifdef DEBUG
        (void) fprintf(logf, gettext("rhtable added i = %d, hash = %d\n"),
            i, hash);
#endif

        /* Check if the new entry affects the cache table */
        rflag = update_cache_table(ent, new);

#ifdef DEBUG
        (void) fprintf(logf, gettext("search_and_update rflag=%d\n"), rflag);
#endif
        return (rflag);
}

/*
 * Search the hash chain for the address. If not found,
 * add the entry to the hash table. If necessary,
 * construct the hash table.
 */
static int
rhtable_search_and_update_v6(struct tsol_rhent *ent, int duplflag)
{
        struct sockaddr_in6 *saddrp;
        unsigned char hash;
        tnd_tnrhdb_t *rhent;
        int i;
        int rflag = 1;

        struct tnd_tnrhdb_c *new;
        in6_addr_t tmpmask6;

        saddrp = (struct sockaddr_in6 *)&ent->rh_address.ip_addr_v6;
        i = ent->rh_prefix;
        (void) rh_index_to_mask_v6(i, &tmpmask6);
        hash = (unsigned char) TNRH_ADDR_MASK_HASH_V6(saddrp->sin6_addr,
            tmpmask6);

        if (tnrh_entire_table_v6[i] == NULL) {
                if ((tnrh_entire_table_v6[i] = (tnd_tnrhdb_t **)calloc(
                    TNRH_TABLE_HASH_SIZE, sizeof (tnd_tnrhdb_t *))) == NULL) {
                        return (0);
                }
        }

        rhent = tnrh_entire_table_v6[i][hash];
        while (rhent != NULL) {
                if (rhaddr_compar_v6(saddrp, rhent) == 1) {
                        /* Check if this is a duplicate entry */
                        if ((rhent->visited == 1) && duplflag)
                                return (0);

                        if (duplflag)
                                rhent->visited = 1;

                        if (strcmp(ent->rh_template,
                            rhent->rh_ent.rh_template) != 0) {
                                /*
                                 * Template is changed in the name service.
                                 * Use the new template.
                                 */
                                (void) strcpy(rhent->rh_ent.rh_template,
                                    ent->rh_template);
                                /*
                                 * Check if this modified entry
                                 * affects the cache table.
                                 */
                                rflag = update_cache_table_v6(ent, rhent);
                                return (rflag);
                        } else
                                return (0);
                }
                rhent = rhent->rh_next;
        }

        /* Not found. Add the entry */
        new = (struct tnd_tnrhdb_c *)calloc(1, sizeof (struct tnd_tnrhdb_c));
        if (new == NULL)
                return (0);
        (void) memcpy(&new->rh_ent, ent, sizeof (struct tsol_rhent));
        if (duplflag)
                new->visited = 1;       /* Mark all new nodes visited */

        /* linked list. Insert in the beginning */
        new->rh_next = tnrh_entire_table_v6[i][hash];
        tnrh_entire_table_v6[i][hash] = new;

        /* Check if the new entry affects the cache table */
        rflag = update_cache_table_v6(ent, new);

        return (rflag);
}

/*
 * The array element i points to the hash table.
 * Search the hash chain for the address.
 */
static struct tnd_tnrhdb_c *
rhtable_lookup(struct sockaddr_in *saddrp, int i)
{
        unsigned char hash;
        tnd_tnrhdb_t *rhent;

        if (tnrh_entire_table[i] == NULL)
                return (NULL);

        hash = (unsigned char) get_hashvalue(saddrp->sin_addr.s_addr);
        rhent = tnrh_entire_table[i][hash];

#ifdef DEBUG
        (void) fprintf(logf, gettext("rhtable_lookup i = %d, hash = %d\n"),
            i, hash);
#endif

        while (rhent != NULL) {
#ifdef DEBUG
        struct sockaddr_in *saddrp2;
        saddrp2 = (struct sockaddr_in *)(&rhent->rh_ent.rh_address.ip_addr_v4);
        (void) fprintf(logf, gettext("rhtable_lookup addr = %s, tmpl = %s\n"),
            inet_ntoa(saddrp2->sin_addr), rhent->rh_ent.rh_template);
#endif
                if (rhaddr_compar_mask(saddrp, rhent, i) == 1)
                        return (rhent);
                rhent = rhent->rh_next;
        }

#ifdef DEBUG
        (void) fprintf(logf, gettext("\trhtable_lookup failed\n"));
#endif

        /* Not found */
        return (NULL);
}

/*
 * The array element i points to the hash table.
 * Search the hash chain for the address.
 */
static struct tnd_tnrhdb_c *
rhtable_lookup_v6(struct sockaddr_in6 *saddrp, in6_addr_t mask, int i)
{
        unsigned char hash;
        tnd_tnrhdb_t *rhent;

        if (tnrh_entire_table_v6[i] == NULL)
                return (NULL);

        hash = (unsigned char) TNRH_ADDR_MASK_HASH_V6(saddrp->sin6_addr, mask);
        rhent = tnrh_entire_table_v6[i][hash];

        while (rhent != NULL) {
                if (rhaddr_compar_mask_v6(saddrp, rhent, i) == 1)
                        return (rhent);
                rhent = rhent->rh_next;
        }

        /* Not found */
        return (NULL);
}

void
add_cache_entry(in_addr_t addr, char *name, int indx,
    tnd_tnrhdb_t *src)
{
        unsigned char hash;
        tnrh_tlb_t *tlbt;

        hash = (unsigned char) get_hashvalue(addr);

        /* Look if some other thread already added this entry */
        if (lookup_cache_table(addr) != NULL)
                return;
#ifdef DEBUG
        (void) fprintf(logf, gettext("\tenter add_cache_entry\n"));
#endif
        if ((tlbt = (tnrh_tlb_t *)calloc(1, sizeof (tnrh_tlb_t))) == NULL)
                return;
        tlbt->addr = addr;
        (void) strncpy(tlbt->template_name, name, TNTNAMSIZ-1);
        tlbt->masklen_used = indx;
        tlbt->reload = TNDB_LOAD;
        tlbt->src = src;

#ifdef DEBUG
        (void) fprintf(logf, gettext("adding cache entry\n"));
        print_tlbt(tlbt);
#endif
        /* Add to the chain */
        if (tnrh_cache_table[hash] == NULL) {
                tnrh_cache_table[hash] = tlbt;
        } else {
                /* Add in the beginning */
                tlbt->next = tnrh_cache_table[hash];
                tnrh_cache_table[hash] = tlbt;
        }
}

static tnrh_tlb_t *
lookup_cache_table(in_addr_t addr)
{
        tnrh_tlb_t *tlbt = NULL;
        unsigned char hash;

        hash = (unsigned char) get_hashvalue(addr);
        tlbt = tnrh_cache_table[hash];
        while (tlbt != NULL) {
                if (addr == tlbt->addr)
                        break;
                tlbt = tlbt->next;
        }
        return (tlbt);
}

static void
add_cache_entry_v6(in6_addr_t addr, char *name, int indx,
                                tnd_tnrhdb_t *src)
{
        unsigned char hash;
        tnrh_tlb_ipv6_t *tlbt;

        hash = (unsigned char) TNRH_ADDR_HASH_V6(addr);

        /* Look if some other thread already added this entry */
        if (lookup_cache_table_v6(addr) != NULL)
                return;

        if ((tlbt = (tnrh_tlb_ipv6_t *)calloc(1,
            sizeof (tnrh_tlb_ipv6_t))) == NULL)
                return;
        (void) memcpy(&tlbt->addr, &addr, sizeof (in6_addr_t));
        (void) strncpy(tlbt->template_name, name, TNTNAMSIZ-1);
        tlbt->masklen_used = indx;
        tlbt->reload = TNDB_LOAD;
        tlbt->src = src;

        /* Add to the chain */
        if (tnrh_cache_table_v6[hash] == NULL) {
                tnrh_cache_table_v6[hash] = tlbt;
        } else {
                /* Add in the beginning */
                tlbt->next = tnrh_cache_table_v6[hash];
                tnrh_cache_table_v6[hash] = tlbt;
        }
}

static tnrh_tlb_ipv6_t *
lookup_cache_table_v6(in6_addr_t addr)
{
        tnrh_tlb_ipv6_t *tlbt = NULL;
        unsigned char hash;

        hash = (unsigned char) TNRH_ADDR_HASH_V6(addr);
        tlbt = tnrh_cache_table_v6[hash];
        while (tlbt != NULL) {
                if (IN6_ARE_ADDR_EQUAL(&addr, &tlbt->addr))
                        break;
                tlbt = tlbt->next;
        }
        return (tlbt);
}


/*
 * Walk the cache table and check if this IP address/address prefix
 * will be a better match for an existing entry in the cache.
 * will add cache if not already exists
 */
static int
update_cache_table(tsol_rhent_t *ent, tnd_tnrhdb_t *src)
{
        int i;
        char result[TNTNAMSIZ];
        in_addr_t tmpmask;
        in_addr_t addr;
        struct sockaddr_in *saddrp;
        tnrh_tlb_t *tlbt;
        struct tnd_tnrhdb_c     *rhp;
        int rflag = 0;

        saddrp = (struct sockaddr_in *)&ent->rh_address.ip_addr_v4;
        addr = saddrp->sin_addr.s_addr;

        (void) rw_rdlock(&cache_rwlp);
        tlbt = lookup_cache_table(addr);
        (void) rw_unlock(&cache_rwlp);

        if (tlbt == NULL) {
                (void) rw_rdlock(&entire_rwlp);
                for (i = (IP_MASK_TABLE_SIZE - 1); i >= 0; i--) {
#ifdef DEBUG
                        (void) fprintf(logf, "update_cache_table i = %d\n", i);
#endif
                        if (tnrh_entire_table[i] == NULL)
                                continue;

                        tmpmask = rh_index_to_mask(i);
                        saddrp->sin_addr.s_addr &= tmpmask;
#ifdef DEBUG
                        (void) fprintf(logf,
                            "update_cache_table found i = %d\n", i);
                        (void) fprintf(logf, "\ti = %d, tmpmask = 0x%4x\n",
                            i, tmpmask);
#endif
                        rhp = (struct tnd_tnrhdb_c *)rhtable_lookup(saddrp, i);
                        if (rhp != NULL) {
                                (void) strcpy(result, rhp->rh_ent.rh_template);
                                /* Add this result to the cache also */
                                (void) rw_wrlock(&cache_rwlp);
                                add_cache_entry(addr, result, i, rhp);
                                rflag++;
                                (void) rw_unlock(&cache_rwlp);
                                break;
                        } else {
#ifdef DEBUG
                                (void) fprintf(logf,
                                    "rhtable_lookup return null !!");
#endif
                        }
                }
                (void) rw_unlock(&entire_rwlp);
        }

        rflag += walk_cache_table(addr, ent->rh_template, ent->rh_prefix, src);
        return (rflag);
}

/*
 * Walk the cache table and check if this IP address/address prefix
 * will be a better match for an existing entry in the cache.
 */
static int
update_cache_table_v6(tsol_rhent_t *ent, tnd_tnrhdb_t *src)
{
        int i;
        char result[TNTNAMSIZ];
        in6_addr_t addr;
        struct sockaddr_in6 *saddrp;
        tnrh_tlb_ipv6_t *tlbt;
        struct tnd_tnrhdb_c     *rhp;
        in6_addr_t tmpmask6;
        int rflag = 0;

        saddrp = (struct sockaddr_in6 *)&ent->rh_address.ip_addr_v6;
        (void) memcpy(&addr, &saddrp->sin6_addr, sizeof (in6_addr_t));

        /* Look in the cache first */
        (void) rw_rdlock(&cache_rwlp);
        tlbt = lookup_cache_table_v6(addr);
        (void) rw_unlock(&cache_rwlp);


        if (tlbt == NULL) {
                (void) rw_rdlock(&entire_rwlp_v6);
                for (i = (IPV6_MASK_TABLE_SIZE - 1); i >= 0; i--) {
                        if (tnrh_entire_table_v6[i] == NULL)
                                continue;
                        (void) rh_index_to_mask_v6(i, &tmpmask6);
                        rhp = (struct tnd_tnrhdb_c *)
                            rhtable_lookup_v6(saddrp, tmpmask6, i);
                        if (rhp != NULL) {
                                (void) strcpy(result, rhp->rh_ent.rh_template);
                                /* Add this result to the cache also */
                                (void) rw_wrlock(&cache_rwlp_v6);
                                add_cache_entry_v6(addr, result, i, rhp);
                                rflag++;
                                (void) rw_unlock(&cache_rwlp_v6);
                                break;
                        }
                }
                (void) rw_unlock(&entire_rwlp_v6);
        }

        rflag += walk_cache_table_v6(addr, ent->rh_template,
            ent->rh_prefix, src);
        return (rflag);
}


/*
 * Check if this prefix addr will be a better match
 * for an existing entry.
 */
static int
is_better_match(in_addr_t newaddr, int indx, tnrh_tlb_t *tlbt)
{
        if (tlbt->masklen_used <= indx) {
                in_addr_t tmpmask = rh_index_to_mask(indx);

                if ((newaddr) == (tlbt->addr & tmpmask))
                        return (1);
        }

        return (0);
}

/*
 * Walk the cache table and update entries if needed.
 * Mark entries for reload to kernel, if somehow their
 * template changed.
 * why is_better_match() is called???
 */
static int
walk_cache_table(in_addr_t newaddr, char *name, int indx, tnd_tnrhdb_t *src)
{
        int i;
        tnrh_tlb_t *tlbt;
        int rflag = 0;

        for (i = 0; i < TNRH_TABLE_HASH_SIZE; i++) {
                tlbt = tnrh_cache_table[i];

                while (tlbt != NULL) {
                        if (is_better_match(newaddr, indx, tlbt)) {
                                tlbt->masklen_used = indx;
                                tlbt->src = src;
                                /*
                                 * Reload to the kernel only if the
                                 * host type changed. There is no need
                                 * to load, if only the mask used has changed,
                                 * since the kernel does not need that
                                 * information.
                                 */
                                if (strcmp(name, tlbt->template_name) != 0) {
                                        (void) strncpy(tlbt->template_name,
                                            name, TNTNAMSIZ-1);
                                        tlbt->reload = TNDB_LOAD;
                                        rflag ++;
                                }
                        }

                        tlbt = tlbt->next;
                }
        }
#ifdef DEBUG
        (void) fprintf(logf, gettext("walk_cache_table rflag=%d\n"), rflag);
#endif
        return (rflag);
}

/*
 * Check if this prefix addr will be a better match
 * for an existing entry.
 */
static int
is_better_match_v6(in6_addr_t newaddr, int indx, tnrh_tlb_ipv6_t *tlbt)
{
        in6_addr_t tmpmask;

        if (tlbt->masklen_used <= indx) {
                (void) rh_index_to_mask_v6(indx, &tmpmask);

                if (V6_MASK_EQ(newaddr, tmpmask, tlbt->addr))
                        return (1);
        }

        return (0);
}


/*
 * Walk the cache table and update entries if needed.
 * Mark entries for reload to kernel, if somehow their
 * template changed.
 */
static int
walk_cache_table_v6(in6_addr_t newaddr, char *name, int indx, tnd_tnrhdb_t *src)
{
        int i;
        tnrh_tlb_ipv6_t *tlbt;
        int rflag = 0;

        for (i = 0; i < TNRH_TABLE_HASH_SIZE; i++) {
                tlbt = tnrh_cache_table_v6[i];

                while (tlbt != NULL) {
                        if (is_better_match_v6(newaddr, indx, tlbt)) {
                                tlbt->masklen_used = indx;
                                tlbt->src = src;
                                /*
                                 * Reload to the kernel only if the
                                 * host type changed. There is no need
                                 * to load, if only the mask used has changed,
                                 * since the kernel does not need that
                                 * information.
                                 */
                                if (strcmp(name, tlbt->template_name) != 0) {
                                        (void) strncpy(tlbt->template_name,
                                            name, TNTNAMSIZ-1);
                                        tlbt->reload = TNDB_LOAD;
                                        rflag ++;
                                }
                        }

                        tlbt = tlbt->next;
                }
        }

        return (rflag);
}

/*
 * load/delete marked rh ents into kernel
 * depending on the reload flag by invoking tnrh().
 * It will mark other entries as TNDB_NOOP
 */
static void
load_rh_marked()
{
        int i;
        tnrh_tlb_t *tlbt, *prev;
        struct tsol_rhent rhentp;

        (void) memset((char *)&rhentp, '\0', sizeof (rhentp));

        for (i = 0; i < TNRH_TABLE_HASH_SIZE; i++) {

                prev = tlbt = tnrh_cache_table[i];

                while (tlbt != NULL) {
                        if ((tlbt->reload == TNDB_LOAD) ||
                            (tlbt->reload == TNDB_DELETE)) {
                        /*
                         * We have to call tnrh() with tsol_rhent argument.
                         * Construct such a struct from the tlbt struct we have.
                         */
                                rhentp.rh_address.ip_addr_v4.sin_addr.s_addr =
                                    tlbt->addr;
                                rhentp.rh_address.ip_addr_v4.sin_family =
                                    AF_INET;
                                rhentp.rh_prefix = tlbt->masklen_used;
                                (void) strcpy(rhentp.rh_template,
                                    tlbt->template_name);

#ifdef DEBUG
                                (void) fprintf(logf, "load op =%d\n",
                                    tlbt->reload);
                                print_tlbt(tlbt);
#endif
                                update_rh_entry(tlbt->reload, &rhentp);

                                if (tlbt->reload == TNDB_DELETE) {
                                        if (tlbt == tnrh_cache_table[i]) {
                                                tnrh_cache_table[i] =
                                                    tlbt->next;
                                                prev = tnrh_cache_table[i];
                                        } else {
                                                prev->next = tlbt->next;
                                                prev = prev->next;
                                        }

                                        free(tlbt);
                                        if (prev == NULL)
                                                break;
                                        else {
                                                tlbt = prev;
                                                continue;
                                        }
                                }
                                tlbt->reload = TNDB_NOOP;
                        }

                        prev = tlbt;
                        tlbt = tlbt->next;
                }
        }

}

/* load marked rh ents into kernel */
static void
load_rh_marked_v6()
{
        int i;
        tnrh_tlb_ipv6_t *tlbt, *prev;
        struct tsol_rhent rhentp;

        (void) memset((char *)&rhentp, '\0', sizeof (rhentp));

        for (i = 0; i < TNRH_TABLE_HASH_SIZE; i++) {
                prev = tlbt = tnrh_cache_table_v6[i];

                while (tlbt != NULL) {
                if ((tlbt->reload == TNDB_LOAD) ||
                    (tlbt->reload == TNDB_DELETE)) {
                        /*
                         * We have to call tnrh() with tsol_rhent argument.
                         * Construct such a struct from the tlbt struct we have.
                         */
                        (void) memcpy(&rhentp.rh_address.ip_addr_v6.sin6_addr,
                            &tlbt->addr, sizeof (in6_addr_t));
                        rhentp.rh_address.ip_addr_v6.sin6_family = AF_INET6;
                        rhentp.rh_prefix = tlbt->masklen_used;
                        (void) strcpy(rhentp.rh_template, tlbt->template_name);

                        update_rh_entry(tlbt->reload, &rhentp);

                        if (tlbt->reload == TNDB_DELETE) {
                                if (tlbt == tnrh_cache_table_v6[i]) {
                                        tnrh_cache_table_v6[i] =
                                            tlbt->next;
                                        prev = tnrh_cache_table_v6[i];
                                } else {
                                        prev->next = tlbt->next;
                                        prev = prev->next;
                                }

                                free(tlbt);
                                if (prev == NULL)
                                        break;
                                else {
                                        tlbt = prev;
                                        continue;
                                }
                        }
                        tlbt->reload = TNDB_NOOP;
                }

                prev = tlbt;
                tlbt = tlbt->next;
        }
        }

}

/*
 * Does the real load/delete for the entry depending on op code.
 */

static void
update_rh_entry(int op, struct tsol_rhent *rhentp)
{
#ifdef DEBUG
        (void) fprintf(logf, gettext("\t###update_rh_entry op = %d\n"), op);
        print_entry(rhentp, AF_INET);
#endif
        if (tnrh(op, rhentp) != 0) {
                if (debugl && (logf != NULL)) {
                        (void) fprintf(logf, "%s : ", gettime());
                        (void) fprintf(logf, gettext("tnrh() failed: %s\n"),
                            strerror(errno));
                        if (op == TNDB_LOAD)
                        (void) fprintf(logf,
                            gettext("load of remote host database "
                            "%s into kernel cache failed\n"),
                            rhentp->rh_template);
                        if (op == TNDB_DELETE)
                        (void) fprintf(logf,
                            gettext("delete of remote host database "
                            "%s from kernel cache failed\n"),
                            rhentp->rh_template);
                        (void) fflush(logf);
                }
                cprint("tnrh() failed..: %s\n", strerror(errno));
        }
}

static void
timer()
{
        poll_now();
        (void) alarm(poll_interval);
}

#define max(a, b)       ((a) > (b) ? (a) : (b))

static void
poll_now()
{

        (void) fprintf(logf, "enter poll_now at %s \n", gettime());
        (void) fflush(logf);

        if (nss_get_tp() > 0) {
                load_tp();
                tp_flush_cache();
        }

#ifdef DEBUG
        (void) fprintf(logf, "now search for tnrhdb update %s \n", gettime());
#endif

        if (nss_get_rh() > 0) {
                if (logf != NULL) {
                        (void) fprintf(logf, "tnrhdb needs update %s \n",
                            gettime());
                }

                (void) rw_wrlock(&cache_rwlp);
                /* This function will cleanup cache table */
                load_rh_marked();
                (void) rw_unlock(&cache_rwlp);

                (void) rw_wrlock(&cache_rwlp_v6);
                /* This function will cleanup cache table */
                load_rh_marked_v6();
                (void) rw_unlock(&cache_rwlp_v6);
        }

#ifdef DEBUG
        if (logf != NULL) {
                cachetable_print();
                cachetable_print_v6();

                (void) fprintf(logf, "rh table begin\n");
                rhtable_print();
                rhtable_print_v6();
                (void) fprintf(logf, "rh table end \n");
                (void) fprintf(logf, "-------------------------\n\n");
                (void) fflush(logf);
        }
#endif
}

static void
tnd_serve()
{
        for (;;) {
                (void) pause();
        }
}

static void
terminate()
{
        if (debugl && (logf != NULL)) {
                (void) fprintf(logf, "%s : ", gettime());
                (void) fprintf(logf, gettext("tnd terminating on signal.\n"));
                (void) fflush(logf);
        }
        exit(1);
}

static void
noop()
{
}

static char *
gettime()
{
        time_t now;
        struct tm *tp, tm;
        char *fmt;

        (void) time(&now);
        tp = localtime(&now);
        (void) memcpy(&tm, tp, sizeof (struct tm));
        fmt = nl_langinfo(_DATE_FMT);

        (void) strftime(time_buf, _SZ_TIME_BUF, fmt, &tm);

        return (time_buf);
}
/*
 * debugging routines
 */


#ifdef DEBUG
static void
print_cache_entry(tnrh_tlb_t *tlbt)
{
        struct in_addr addr;

        addr.s_addr = tlbt->addr;
        (void) fprintf(logf, "\tIP address: %s", inet_ntoa(addr));
        (void) fprintf(logf, "\tTemplate name: %s", tlbt->template_name);
        (void) fprintf(logf, "\tMask length used: %d\n", tlbt->masklen_used);
}

static void
print_cache_entry_v6(tnrh_tlb_ipv6_t *tlbt)
{
        char abuf[INET6_ADDRSTRLEN];

        (void) fprintf(logf, "\tIP address: %s",
            inet_ntop(AF_INET6, &tlbt->addr, abuf, sizeof (abuf)));
        (void) fprintf(logf, "\tTemplate name: %s", tlbt->template_name);
        (void) fprintf(logf, "\tMask length used: %d\n", tlbt->masklen_used);
}

static void
cachetable_print()
{
        int i;
        tnrh_tlb_t *tlbt;

        (void) fprintf(logf, "-------------------------\n");
        (void) fprintf(logf, "Cache table begin\n");

        for (i = 0; i < TNRH_TABLE_HASH_SIZE; i++) {
                if ((tlbt = tnrh_cache_table[i]) != NULL)
                        print_cache_entry(tlbt);
        }

        (void) fprintf(logf, "Cache table end \n");
        (void) fprintf(logf, "-------------------------\n\n");
}

static void
cachetable_print_v6()
{
        int i;
        tnrh_tlb_ipv6_t *tlbt;

        (void) fprintf(logf, "-------------------------\n");
        (void) fprintf(logf, "Cache table begin\n");

        for (i = 0; i < TNRH_TABLE_HASH_SIZE; i++) {
                if ((tlbt = tnrh_cache_table_v6[i]) != NULL)
                        print_cache_entry_v6(tlbt);
        }

        (void) fprintf(logf, "Cache table end \n");
        (void) fprintf(logf, "-------------------------\n\n");
}


static void
print_entry(tsol_rhent_t *ent, int af)
{
        struct sockaddr_in *saddrp;
        struct sockaddr_in6 *saddrp6;
        char abuf[INET6_ADDRSTRLEN];

        if (af == AF_INET) {
                saddrp = (struct sockaddr_in *)&ent->rh_address.ip_addr_v4;
                (void) fprintf(logf, gettext("\tIP address: %s"),
                    inet_ntoa(saddrp->sin_addr));
        } else if (af == AF_INET6) {
                saddrp6 = (struct sockaddr_in6 *)&ent->rh_address.ip_addr_v6;
                (void) fprintf(logf, gettext("\tIP address: %s"),
                    inet_ntop(AF_INET6, &saddrp6->sin6_addr, abuf,
                    sizeof (abuf)));
        }

        (void) fprintf(logf,
            gettext("\tTemplate name: %s"), ent->rh_template);
        (void) fprintf(logf, gettext("\tprefix_len: %d\n"), ent->rh_prefix);
        (void) fflush(logf);
}

static void
print_tlbt(tnrh_tlb_t *tlbt)
{
        (void) fprintf(logf, "tlbt addr = 0x%4x name = %s \
            mask = %u, reload = %d\n", tlbt->addr, tlbt->template_name,
            tlbt->masklen_used, tlbt->reload);
}

static void
rhtable_print()
{
        rhtable_walk(print_entry);
        (void) fprintf(logf, "-----------------------------\n\n");
}

static void
rhtable_print_v6()
{
        rhtable_walk_v6(print_entry);
        (void) fprintf(logf, "-----------------------------\n\n");
}

/*
 * Walk through all the entries in tnrh_entire_table[][]
 * and execute the function passing the entry as argument.
 */
static void
rhtable_walk(void (*action)())
{
        int i, j;
        tnd_tnrhdb_t *rhent;

        for (i = 0; i <= IP_ABITS; i++) {
                if (tnrh_entire_table[i] == NULL)
                        continue;

                for (j = 0; j < TNRH_TABLE_HASH_SIZE; j++) {
                        rhent = tnrh_entire_table[i][j];

                        while (rhent != NULL) {
                                action(&rhent->rh_ent, AF_INET);
                                rhent = rhent->rh_next;
                        }
                }
        }
}

/*
 * Walk through all the entries in tnrh_entire_table_v6[][]
 * and execute the function passing the entry as argument.
 */
static void
rhtable_walk_v6(void (*action)())
{
        int i, j;
        tnd_tnrhdb_t *rhent;

        for (i = 0; i <= IPV6_ABITS; i++) {
                if (tnrh_entire_table_v6[i] == NULL)
                        continue;

                for (j = 0; j < TNRH_TABLE_HASH_SIZE; j++) {
                        rhent = tnrh_entire_table_v6[i][j];

                        while (rhent != NULL) {
                                action(&rhent->rh_ent, AF_INET6);
                                rhent = rhent->rh_next;
                        }
                }
        }
}
#endif /* DEBUG */