/*
 * agent_registry.c
 */
/* Portions of this file are subject to the following copyright(s).  See
 * the Net-SNMP's COPYING file for more details and other copyrights
 * that may apply:
 */
/*
 * Portions of this file are copyrighted by:
 * Copyright © 2003 Sun Microsystems, Inc. All rights reserved.
 * Use is subject to license terms specified in the COPYING file
 * distributed with the Net-SNMP package.
 */
/** @defgroup agent_registry Maintain a registry of MIB subtrees, together with related information regarding mibmodule, sessions, etc
 *   @ingroup agent
 *
 * @{
 */

#define IN_SNMP_VARS_C

#include <net-snmp/net-snmp-config.h>
#include <signal.h>
#if HAVE_STRING_H
#include <string.h>
#endif
#if HAVE_STDLIB_H
#include <stdlib.h>
#endif
#include <sys/types.h>
#include <stdio.h>
#include <fcntl.h>
#if HAVE_WINSOCK_H
#include <winsock.h>
#endif
#if TIME_WITH_SYS_TIME
# ifdef WIN32
#  include <sys/timeb.h>
# else
#  include <sys/time.h>
# endif
# include <time.h>
#else
# if HAVE_SYS_TIME_H
#  include <sys/time.h>
# else
#  include <time.h>
# endif
#endif
#if HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif

#include <net-snmp/net-snmp-includes.h>
#include <net-snmp/agent/net-snmp-agent-includes.h>
#include <net-snmp/agent/agent_callbacks.h>

#include "snmpd.h"
#include "mibgroup/struct.h"
#include <net-snmp/agent/old_api.h>
#include <net-snmp/agent/null.h>
#include <net-snmp/agent/table.h>
#include <net-snmp/agent/table_iterator.h>
#include <net-snmp/agent/agent_registry.h>
#include "mib_module_includes.h"

#ifdef USING_AGENTX_SUBAGENT_MODULE
#include "agentx/subagent.h"
#include "agentx/client.h"
#endif

/* Lookup cache code */

#define SUBTREE_DEFAULT_CACHE_SIZE 8
#define SUBTREE_MAX_CACHE_SIZE     32
int lookup_cache_size = 0; /*enabled later after registrations are loaded */

typedef struct lookup_cache_s {
   netsnmp_subtree *next;
   netsnmp_subtree *previous;
} lookup_cache;

typedef struct lookup_cache_context_s {
   char *context;
   struct lookup_cache_context_s *next;
   int thecachecount;
   int currentpos;
   lookup_cache cache[SUBTREE_MAX_CACHE_SIZE];
} lookup_cache_context;

static lookup_cache_context *thecontextcache = NULL;

/** set the lookup cache size for optimized agent registration performance.
 * @param newsize set to the maximum size of a cache for a given
 * context.  Set to 0 to completely disable caching, or to -1 to set
 * to the default cache size (8), or to a number of your chosing.  The
 * rough guide is that it should be equal to the maximum number of
 * simultanious managers you expect to talk to the agent (M) times 80%
 * (or so, he says randomly) the average number (N) of varbinds you
 * expect to receive in a given request for a manager.  ie, M times N.
 * Bigger does NOT necessarily mean better.  Certainly 16 should be an
 * upper limit.  32 is the hard coded limit.
 */
void
netsnmp_set_lookup_cache_size(int newsize) {
    if (newsize < 0)
        lookup_cache_size = SUBTREE_DEFAULT_CACHE_SIZE;
    else if (newsize < SUBTREE_MAX_CACHE_SIZE)
        lookup_cache_size = newsize;
    else
        lookup_cache_size = SUBTREE_MAX_CACHE_SIZE;
}

/** retrieves the current value of the lookup cache size
 *  @return the current lookup cache size
 */
int
netsnmp_get_lookup_cache_size(void) {
    return lookup_cache_size;
}

NETSNMP_STATIC_INLINE lookup_cache_context *
get_context_lookup_cache(const char *context) {
    lookup_cache_context *ptr;
    if (!context)
        context = "";

    for(ptr = thecontextcache; ptr; ptr = ptr->next) {
        if (strcmp(ptr->context, context) == 0)
            break;
    }
    if (!ptr) {
        if (netsnmp_subtree_find_first(context)) {
            ptr = SNMP_MALLOC_TYPEDEF(lookup_cache_context);
            ptr->next = thecontextcache;
            ptr->context = strdup(context);
            thecontextcache = ptr;
        } else {
            return NULL;
        }
    }
    return ptr;
}

NETSNMP_STATIC_INLINE void
lookup_cache_add(const char *context,
                 netsnmp_subtree *next, netsnmp_subtree *previous) {
    lookup_cache_context *cptr;

    if ((cptr = get_context_lookup_cache(context)) == NULL)
        return;

    if (cptr->thecachecount < lookup_cache_size)
        cptr->thecachecount++;

    cptr->cache[cptr->currentpos].next = next;
    cptr->cache[cptr->currentpos].previous = previous;

    if (++cptr->currentpos >= lookup_cache_size)
        cptr->currentpos = 0;
}

NETSNMP_STATIC_INLINE void
lookup_cache_replace(lookup_cache *ptr,
                     netsnmp_subtree *next, netsnmp_subtree *previous) {

    ptr->next = next;
    ptr->previous = previous;
}

NETSNMP_STATIC_INLINE lookup_cache *
lookup_cache_find(const char *context, oid *name, size_t name_len,
                  int *retcmp) {
    lookup_cache_context *cptr;
    lookup_cache *ret = NULL;
    int cmp;
    int i;

    if ((cptr = get_context_lookup_cache(context)) == NULL)
        return NULL;

    for(i = 0; i < cptr->thecachecount && i < lookup_cache_size; i++) {
        if (cptr->cache[i].previous->start_a)
            cmp = snmp_oid_compare(name, name_len,
                                   cptr->cache[i].previous->start_a,
                                   cptr->cache[i].previous->start_len);
        else
            cmp = 1;
        if (cmp >= 0) {
            *retcmp = cmp;
            ret = &(cptr->cache[i]);
        }
    }
    return ret;
}

NETSNMP_STATIC_INLINE void
invalidate_lookup_cache(const char *context) {
    lookup_cache_context *cptr;
    if ((cptr = get_context_lookup_cache(context)) != NULL) {
        cptr->thecachecount = 0;
        cptr->currentpos = 0;
    }
}

/* End of Lookup cache code */

static void register_mib_detach_node(netsnmp_subtree *s);
subtree_context_cache *context_subtrees = NULL;

void
netsnmp_subtree_free(netsnmp_subtree *a)
{
  if (a != NULL) {
    if (a->variables != NULL && netsnmp_oid_equals(a->name_a, a->namelen, 
					     a->start_a, a->start_len) == 0) {
      SNMP_FREE(a->variables);
    }
    SNMP_FREE(a->name_a);
    a->namelen = 0;
    SNMP_FREE(a->start_a);
    a->start_len = 0;
    SNMP_FREE(a->end_a);
    a->end_len = 0;
    SNMP_FREE(a->label_a);
    netsnmp_handler_registration_free(a->reginfo);
    a->reginfo = NULL;
    SNMP_FREE(a);
  }
}

netsnmp_subtree *
netsnmp_subtree_deepcopy(netsnmp_subtree *a)
{
  netsnmp_subtree *b = (netsnmp_subtree *)calloc(1, sizeof(netsnmp_subtree));

  if (b != NULL) {
    memcpy(b, a, sizeof(netsnmp_subtree));
    b->name_a  = snmp_duplicate_objid(a->name_a,  a->namelen);
    b->start_a = snmp_duplicate_objid(a->start_a, a->start_len);
    b->end_a   = snmp_duplicate_objid(a->end_a,   a->end_len);
    b->label_a = strdup(a->label_a);
    
    if (b->name_a == NULL || b->start_a == NULL || 
	b->end_a  == NULL || b->label_a == NULL) {
      netsnmp_subtree_free(b);
      return NULL;
    }

    if (a->variables != NULL) {
      b->variables = (struct variable *)malloc(a->variables_len * 
					       a->variables_width);
      if (b->variables != NULL) {
	memcpy(b->variables, a->variables,a->variables_len*a->variables_width);
      } else {
	netsnmp_subtree_free(b);
	return NULL;
      }
    }

    if (a->reginfo != NULL) {
      b->reginfo = netsnmp_handler_registration_dup(a->reginfo);
      if (b->reginfo == NULL) {
	netsnmp_subtree_free(b);
	return NULL;
      }
    }
  }
  return b;
}

subtree_context_cache *
get_top_context_cache(void)
{
    return context_subtrees;
}

netsnmp_subtree *
netsnmp_subtree_find_first(const char *context_name)
{
    subtree_context_cache *ptr;

    if (!context_name) {
        context_name = "";
    }

    DEBUGMSGTL(("subtree", "looking for subtree for context: \"%s\"\n", 
		context_name));
    for (ptr = context_subtrees; ptr != NULL; ptr = ptr->next) {
        if (ptr->context_name != NULL && 
	    strcmp(ptr->context_name, context_name) == 0) {
            DEBUGMSGTL(("subtree", "found one for: \"%s\"\n", context_name));
            return ptr->first_subtree;
        }
    }
    DEBUGMSGTL(("subtree", "didn't find a subtree for context: \"%s\"\n", 
		context_name));
    return NULL;
}

netsnmp_subtree *
add_subtree(netsnmp_subtree *new_tree, const char *context_name)
{
    subtree_context_cache *ptr = SNMP_MALLOC_TYPEDEF(subtree_context_cache);
    
    if (!context_name) {
        context_name = "";
    }

    if (!ptr) {
        return NULL;
    }
    
    DEBUGMSGTL(("subtree", "adding subtree for context: \"%s\"\n",	
		context_name));

    ptr->next = context_subtrees;
    ptr->first_subtree = new_tree;
    ptr->context_name = strdup(context_name);
    context_subtrees = ptr;

    return ptr->first_subtree;
}

netsnmp_subtree *
netsnmp_subtree_replace_first(netsnmp_subtree *new_tree, 
			      const char *context_name)
{
    subtree_context_cache *ptr;
    if (!context_name) {
        context_name = "";
    }
    for (ptr = context_subtrees; ptr != NULL; ptr = ptr->next) {
        if (ptr->context_name != NULL &&
	    strcmp(ptr->context_name, context_name) == 0) {
            ptr->first_subtree = new_tree;
            return ptr->first_subtree;
        }
    }
    return add_subtree(new_tree, context_name);
}

NETSNMP_INLINE void
netsnmp_subtree_change_next(netsnmp_subtree *ptr, netsnmp_subtree *thenext)
{
    ptr->next = thenext;
    if (thenext)
        netsnmp_oid_compare_ll(ptr->start_a,
                               ptr->start_len,
                               thenext->start_a,
                               thenext->start_len,
                               &thenext->oid_off);
}

NETSNMP_INLINE void
netsnmp_subtree_change_prev(netsnmp_subtree *ptr, netsnmp_subtree *theprev)
{
    ptr->prev = theprev;
    if (theprev)
        netsnmp_oid_compare_ll(theprev->start_a,
                               theprev->start_len,
                               ptr->start_a,
                               ptr->start_len,
                               &ptr->oid_off);
}

int
netsnmp_subtree_compare(const netsnmp_subtree *ap, const netsnmp_subtree *bp)
{
    return snmp_oid_compare(ap->name_a, ap->namelen, bp->name_a, bp->namelen);
}

void
netsnmp_subtree_join(netsnmp_subtree *root)
{
    netsnmp_subtree *s, *tmp, *c, *d;

    while (root != NULL) {
        s = root->next;
        while (s != NULL && root->reginfo == s->reginfo) {
            tmp = s->next;
            DEBUGMSGTL(("subtree", "root start "));
            DEBUGMSGOID(("subtree", root->start_a, root->start_len));
            DEBUGMSG(("subtree", " (original end "));
            DEBUGMSGOID(("subtree", root->end_a, root->end_len));
            DEBUGMSG(("subtree", ")\n"));
            DEBUGMSGTL(("subtree", "  JOINING to "));
            DEBUGMSGOID(("subtree", s->start_a, s->start_len));

	    SNMP_FREE(root->end_a);
	    root->end_a   = s->end_a;
            root->end_len = s->end_len;
	    s->end_a      = NULL;

            for (c = root; c != NULL; c = c->children) {
                netsnmp_subtree_change_next(c, s->next);
            }
            for (c = s; c != NULL; c = c->children) {
                netsnmp_subtree_change_prev(c, root);
            }
            DEBUGMSG(("subtree", " so new end "));
            DEBUGMSGOID(("subtree", root->end_a, root->end_len));
            DEBUGMSG(("subtree", "\n"));
            /*
             * Probably need to free children too?  
             */
            for (c = s->children; c != NULL; c = d) {
                d = c->children;
                netsnmp_subtree_free(c);
            }
            netsnmp_subtree_free(s);
            s = tmp;
        }
        root = root->next;
    }
}


        /*
         *  Split the subtree into two at the specified point,
         *    returning the new (second) subtree
         */
netsnmp_subtree *
netsnmp_subtree_split(netsnmp_subtree *current, oid name[], int name_len)
{
    struct variable *vp = NULL;
    netsnmp_subtree *new_sub, *ptr;
    int i = 0, rc = 0, rc2 = 0;
    size_t common_len = 0;
    char *cp;
    oid *tmp_a, *tmp_b;

    if (snmp_oid_compare(name, name_len, current->end_a, current->end_len)>0) {
	/* Split comes after the end of this subtree */
        return NULL;
    }

    new_sub = netsnmp_subtree_deepcopy(current);
    if (new_sub == NULL) {
        return NULL;
    }

    /*  Set up the point of division.  */
    tmp_a = snmp_duplicate_objid(name, name_len);
    if (tmp_a == NULL) {
	netsnmp_subtree_free(new_sub);
	return NULL;
    }
    tmp_b = snmp_duplicate_objid(name, name_len);
    if (tmp_b == NULL) {
	netsnmp_subtree_free(new_sub);
	SNMP_FREE(tmp_a);
	return NULL;
    }

    SNMP_FREE(current->end_a);
    current->end_a = tmp_a;
    current->end_len = name_len;
    if (new_sub->start_a != NULL) {
	SNMP_FREE(new_sub->start_a);
    }
    new_sub->start_a = tmp_b;
    new_sub->start_len = name_len;

    /*  Split the variables between the two new subtrees.  */
    i = current->variables_len;
    current->variables_len = 0;

    for (vp = current->variables; i > 0; i--) {
	/*  Note that the variable "name" field omits the prefix common to the
	    whole registration, hence the strange comparison here.  */

	rc = snmp_oid_compare(vp->name, vp->namelen,
			      name     + current->namelen, 
			      name_len - current->namelen);

        if (name_len - current->namelen > vp->namelen) {
            common_len = vp->namelen;
        } else {
            common_len = name_len - current->namelen;
        }

        rc2 = snmp_oid_compare(vp->name, common_len,
                               name + current->namelen, common_len);

        if (rc >= 0) {
            break;  /* All following variables belong to the second subtree */
	}

        current->variables_len++;
        if (rc2 < 0) {
            new_sub->variables_len--;
            cp = (char *) new_sub->variables;
            new_sub->variables = (struct variable *)(cp + 
						     new_sub->variables_width);
        }
        vp = (struct variable *) ((char *) vp + current->variables_width);
    }

    /* Delegated trees should retain their variables regardless */
    if (current->variables_len > 0 &&
        IS_DELEGATED((u_char) current->variables[0].type)) {
        new_sub->variables_len = 1;
        new_sub->variables = current->variables;
    }

    /* Propogate this split down through any children */
    if (current->children) {
        new_sub->children = netsnmp_subtree_split(current->children, 
						  name, name_len);
    }

    /* Retain the correct linking of the list */
    for (ptr = current; ptr != NULL; ptr = ptr->children) {
        netsnmp_subtree_change_next(ptr, new_sub);
    }
    for (ptr = new_sub; ptr != NULL; ptr = ptr->children) {
        netsnmp_subtree_change_prev(ptr, current);
    }
    for (ptr = new_sub->next; ptr != NULL; ptr=ptr->children) {
        netsnmp_subtree_change_prev(ptr, new_sub);
    }

    return new_sub;
}

int
netsnmp_subtree_load(netsnmp_subtree *new_sub, const char *context_name)
{
    netsnmp_subtree *tree1, *tree2, *new2;
    netsnmp_subtree *prev, *next;
    int             res, rc = 0;

    if (new_sub == NULL) {
        return MIB_REGISTERED_OK;       /* Degenerate case */
    }

    if (!netsnmp_subtree_find_first(context_name)) {
        static int inloop = 0;
        if (!inloop) {
            oid ccitt[1]           = { 0 };
            oid iso[1]             = { 1 };
            oid joint_ccitt_iso[1] = { 2 };
            inloop = 1;
            netsnmp_register_null_context(snmp_duplicate_objid(ccitt, 1), 1,
                                          context_name);
            netsnmp_register_null_context(snmp_duplicate_objid(iso, 1), 1,
                                          context_name);
            netsnmp_register_null_context(snmp_duplicate_objid(joint_ccitt_iso, 1),
                                          1, context_name);
            inloop = 0;
        }
    }

    /*  Find the subtree that contains the start of the new subtree (if
	any)...*/

    tree1 = netsnmp_subtree_find(new_sub->start_a, new_sub->start_len, 
				 NULL, context_name);

    /*  ... and the subtree that follows the new one (NULL implies this is the
	final region covered).  */

    if (tree1 == NULL) {
	tree2 = netsnmp_subtree_find_next(new_sub->start_a, new_sub->start_len,
					  NULL, context_name);
    } else {
	tree2 = tree1->next;
    }

    /*  Handle new subtrees that start in virgin territory.  */

    if (tree1 == NULL) {
	new2 = NULL;
	/*  Is there any overlap with later subtrees?  */
	if (tree2 && snmp_oid_compare(new_sub->end_a, new_sub->end_len,
				      tree2->start_a, tree2->start_len) > 0) {
	    new2 = netsnmp_subtree_split(new_sub, 
					 tree2->start_a, tree2->start_len);
	}

	/*  Link the new subtree (less any overlapping region) with the list of
	    existing registrations.  */

	if (tree2) {
            netsnmp_subtree_change_prev(new_sub, tree2->prev);
            netsnmp_subtree_change_prev(tree2, new_sub);
	} else {
            netsnmp_subtree_change_prev(new_sub,
                                        netsnmp_subtree_find_prev(new_sub->start_a,
                                                                  new_sub->start_len, NULL, context_name));

	    if (new_sub->prev) {
                netsnmp_subtree_change_next(new_sub->prev, new_sub);
	    } else {
		netsnmp_subtree_replace_first(new_sub, context_name);
	    }

            netsnmp_subtree_change_next(new_sub, tree2);

	    /* If there was any overlap, recurse to merge in the overlapping
	       region (including anything that may follow the overlap).  */
	    if (new2) {
		return netsnmp_subtree_load(new2, context_name);
	    }
	}
    } else {
	/*  If the new subtree starts *within* an existing registration
	    (rather than at the same point as it), then split the existing
	    subtree at this point.  */

	if (netsnmp_oid_equals(new_sub->start_a, new_sub->start_len, 
			     tree1->start_a,   tree1->start_len) != 0) {
	    tree1 = netsnmp_subtree_split(tree1, new_sub->start_a, 
					  new_sub->start_len);
	}

        if (tree1 == NULL) {
            return MIB_REGISTRATION_FAILED;
	}

	/*  Now consider the end of this existing subtree:
	    
	    If it matches the new subtree precisely,
	            simply merge the new one into the list of children

	    If it includes the whole of the new subtree,
		    split it at the appropriate point, and merge again
     
	    If the new subtree extends beyond this existing region,
	            split it, and recurse to merge the two parts.  */

	rc = snmp_oid_compare(new_sub->end_a, new_sub->end_len, 
			      tree1->end_a, tree1->end_len);

        switch (rc) {

	case -1:
	    /*  Existing subtree contains new one.  */
	    netsnmp_subtree_split(tree1, new_sub->end_a, new_sub->end_len);
	    /* Fall Through */

	case  0:
	    /*  The two trees match precisely.  */

	    /*  Note: This is the only point where the original registration
	        OID ("name") is used.  */

	    prev = NULL;
	    next = tree1;
	
	    while (next && next->namelen > new_sub->namelen) {
		prev = next;
		next = next->children;
	    }

	    while (next && next->namelen == new_sub->namelen &&
		   next->priority < new_sub->priority ) {
		prev = next;
		next = next->children;
	    }
	
	    if (next && (next->namelen  == new_sub->namelen) &&
		(next->priority == new_sub->priority)) {
                if (new_sub->namelen != 1) /* ignore root OID dups */
                    snmp_log(LOG_ERR, "duplicate registration (%s, %s)", next->label_a, new_sub->label_a);
		return MIB_DUPLICATE_REGISTRATION;
	    }

	    if (prev) {
		prev->children    = new_sub;
		new_sub->children = next;
                netsnmp_subtree_change_prev(new_sub, prev->prev);
                netsnmp_subtree_change_next(new_sub, prev->next);
	    } else {
		new_sub->children = next;
                netsnmp_subtree_change_prev(new_sub, next->prev);
                netsnmp_subtree_change_next(new_sub, next->next);
	
		for (next = new_sub->next; next != NULL;next = next->children){
                    netsnmp_subtree_change_prev(next, new_sub);
		}

		for (prev = new_sub->prev; prev != NULL;prev = prev->children){
                    netsnmp_subtree_change_next(prev, new_sub);
		}
	    }
	    break;

	case  1:
	    /*  New subtree contains the existing one.  */
	    new2 = netsnmp_subtree_split(new_sub, tree1->end_a,tree1->end_len);
	    res = netsnmp_subtree_load(new_sub, context_name);
	    if (res != MIB_REGISTERED_OK) {
		netsnmp_subtree_free(new2);
		return res;
	    }
	    return netsnmp_subtree_load(new2, context_name);
	}
    }
    return 0;
}

/*
 * Note: reginfo will be freed on failures
 */
int
netsnmp_register_mib(const char *moduleName,
                     struct variable *var,
                     size_t varsize,
                     size_t numvars,
                     oid * mibloc,
                     size_t mibloclen,
                     int priority,
                     int range_subid,
                     oid range_ubound,
                     netsnmp_session * ss,
                     const char *context,
                     int timeout,
                     int flags,
                     netsnmp_handler_registration *reginfo,
                     int perform_callback)
{
    netsnmp_subtree *subtree, *sub2;
    int             res, i;
    struct register_parameters reg_parms;
    int old_lookup_cache_val = netsnmp_get_lookup_cache_size();

    if (moduleName == NULL ||
        mibloc     == NULL) {
        /* Shouldn't happen ??? */
        netsnmp_handler_registration_free(reginfo);
        return MIB_REGISTRATION_FAILED;
    }
    subtree = (netsnmp_subtree *)calloc(1, sizeof(netsnmp_subtree));
    if (subtree == NULL) {
        netsnmp_handler_registration_free(reginfo);
        return MIB_REGISTRATION_FAILED;
    }

    DEBUGMSGTL(("register_mib", "registering \"%s\" at ", moduleName));
    DEBUGMSGOIDRANGE(("register_mib", mibloc, mibloclen, range_subid,
                      range_ubound));
    DEBUGMSG(("register_mib", " with context \"%s\"\n",
              SNMP_STRORNULL(context)));

    /*
     * verify that the passed context is equal to the context
     * in the reginfo.
     * (which begs the question, why do we have both? It appears that the
     *  reginfo item didn't appear til 5.2)
     */
    if( ((NULL == context) && (NULL != reginfo->contextName)) ||
        ((NULL != context) && (NULL == reginfo->contextName)) ||
        ( ((NULL != context) && (NULL != reginfo->contextName)) &&
          (0 != strcmp(context, reginfo->contextName))) ) {
        snmp_log(LOG_WARNING,"context passed during registration does not "
                 "equal the reginfo contextName! ('%s' != '%s')\n",
                 context, reginfo->contextName);
        netsnmp_assert(!"register context == reginfo->contextName"); /* always false */
    }

    /*  Create the new subtree node being registered.  */

    subtree->reginfo = reginfo;
    subtree->name_a  = snmp_duplicate_objid(mibloc, mibloclen);
    subtree->start_a = snmp_duplicate_objid(mibloc, mibloclen);
    subtree->end_a   = snmp_duplicate_objid(mibloc, mibloclen);
    subtree->label_a = strdup(moduleName);
    if (subtree->name_a == NULL || subtree->start_a == NULL || 
	subtree->end_a  == NULL || subtree->label_a == NULL) {
	netsnmp_subtree_free(subtree); /* also frees reginfo */
	return MIB_REGISTRATION_FAILED;
    }
    subtree->namelen   = (u_char)mibloclen;
    subtree->start_len = (u_char)mibloclen;
    subtree->end_len   = (u_char)mibloclen;
    subtree->end_a[mibloclen - 1]++;

    if (var != NULL) {
	subtree->variables = (struct variable *)malloc(varsize*numvars);
	if (subtree->variables == NULL) {
	    netsnmp_subtree_free(subtree); /* also frees reginfo */
	    return MIB_REGISTRATION_FAILED;
	}
	memcpy(subtree->variables, var, numvars*varsize);
	subtree->variables_len = numvars;
	subtree->variables_width = varsize;
    }
    subtree->priority = priority;
    subtree->timeout = timeout;
    subtree->range_subid = range_subid;
    subtree->range_ubound = range_ubound;
    subtree->session = ss;
    subtree->flags = (u_char)flags;    /*  used to identify instance oids  */
    subtree->flags |= SUBTREE_ATTACHED;
    subtree->global_cacheid = reginfo->global_cacheid;

    netsnmp_set_lookup_cache_size(0);
    res = netsnmp_subtree_load(subtree, context);

    /*  If registering a range, use the first subtree as a template for the
	rest of the range.  */

    if (res == MIB_REGISTERED_OK && range_subid != 0) {
	for (i = mibloc[range_subid - 1] + 1; i <= (int)range_ubound; i++) {
	    sub2 = netsnmp_subtree_deepcopy(subtree);

	    if (sub2 == NULL) {
                unregister_mib_context(mibloc, mibloclen, priority,
                                       range_subid, range_ubound, context);
                netsnmp_set_lookup_cache_size(old_lookup_cache_val);
                invalidate_lookup_cache(context);
                return MIB_REGISTRATION_FAILED;
            }

            sub2->name_a[range_subid - 1]  = i;
            sub2->start_a[range_subid - 1] = i;
            sub2->end_a[range_subid - 1]   = i;     /* XXX - ???? */
            if (range_subid == (int)mibloclen) {
                ++sub2->end_a[range_subid - 1];
            }
            sub2->flags |= SUBTREE_ATTACHED;
            sub2->global_cacheid = reginfo->global_cacheid;
            /* FRQ This is essential for requests to succeed! */
            sub2->reginfo->rootoid[range_subid - 1]  = i;

            res = netsnmp_subtree_load(sub2, context);
            if (res != MIB_REGISTERED_OK) {
                unregister_mib_context(mibloc, mibloclen, priority,
                                       range_subid, range_ubound, context);
		netsnmp_subtree_free(sub2);
                netsnmp_set_lookup_cache_size(old_lookup_cache_val);
                invalidate_lookup_cache(context);
                return res;
            }
        }
    } else if (res == MIB_DUPLICATE_REGISTRATION ||
               res == MIB_REGISTRATION_FAILED) {
        netsnmp_set_lookup_cache_size(old_lookup_cache_val);
        invalidate_lookup_cache(context);
        netsnmp_subtree_free(subtree);
        return res;
    }

    /*
     * mark the MIB as detached, if there's no master agent present as of now 
     */
    if (netsnmp_ds_get_boolean(NETSNMP_DS_APPLICATION_ID, 
			       NETSNMP_DS_AGENT_ROLE) != MASTER_AGENT) {
        extern struct snmp_session *main_session;
        if (main_session == NULL) {
            register_mib_detach_node(subtree);
	}
    }

    if (res == MIB_REGISTERED_OK && perform_callback) {
        memset(&reg_parms, 0x0, sizeof(reg_parms));
        reg_parms.name = mibloc;
        reg_parms.namelen = mibloclen;
        reg_parms.priority = priority;
        reg_parms.range_subid = range_subid;
        reg_parms.range_ubound = range_ubound;
        reg_parms.timeout = timeout;
        reg_parms.flags = (u_char) flags;
        reg_parms.contextName = context;
        reg_parms.session = ss;
        reg_parms.reginfo = reginfo;
        reg_parms.contextName = context;
        snmp_call_callbacks(SNMP_CALLBACK_APPLICATION,
                            SNMPD_CALLBACK_REGISTER_OID, &reg_parms);
    }

    netsnmp_set_lookup_cache_size(old_lookup_cache_val);
    invalidate_lookup_cache(context);
    return res;
}

/*
 * Reattach a particular node.  
 */

static void
register_mib_reattach_node(netsnmp_subtree *s)
{
    if ((s != NULL) && (s->namelen > 1) && !(s->flags & SUBTREE_ATTACHED)) {
        struct register_parameters reg_parms;
        /*
         * only do registrations that are not the top level nodes 
         */
        memset(&reg_parms, 0x0, sizeof(reg_parms));

        /*
         * XXX: do this better 
         */
        reg_parms.name = s->name_a;
        reg_parms.namelen = s->namelen;
        reg_parms.priority = s->priority;
        reg_parms.range_subid = s->range_subid;
        reg_parms.range_ubound = s->range_ubound;
        reg_parms.timeout = s->timeout;
        reg_parms.flags = s->flags;
        reg_parms.session = s->session;
        reg_parms.reginfo = s->reginfo;
        /* XXX: missing in subtree: reg_parms.contextName = s->context; */
        if ((NULL != s->reginfo) && (NULL != s->reginfo->contextName))
            reg_parms.contextName = s->reginfo->contextName;
        snmp_call_callbacks(SNMP_CALLBACK_APPLICATION,
                            SNMPD_CALLBACK_REGISTER_OID, &reg_parms);
        s->flags |= SUBTREE_ATTACHED;
    }
}

/*
 * Call callbacks to reattach all our nodes.  
 */

void
register_mib_reattach(void)
{
    netsnmp_subtree *s, *t;
    subtree_context_cache *ptr;

    for (ptr = context_subtrees; ptr; ptr = ptr->next) {
        for (s = ptr->first_subtree; s != NULL; s = s->next) {
            register_mib_reattach_node(s);
            for (t = s->children; t != NULL; t = t->children) {
                register_mib_reattach_node(t);
            }
        }
    }
}

/*
 * Mark a node as detached.  
 */

static void
register_mib_detach_node(netsnmp_subtree *s)
{
    if (s != NULL) {
        s->flags = s->flags & ~SUBTREE_ATTACHED;
    }
}

/*
 * Mark all our registered OIDs as detached.  This is only really
 * useful for subagent protocols, when a connection is lost or
 * something.  
 */

void
register_mib_detach(void)
{
    netsnmp_subtree *s, *t;
    subtree_context_cache *ptr;
    for (ptr = context_subtrees; ptr; ptr = ptr->next) {
        for (s = ptr->first_subtree; s != NULL; s = s->next) {
            register_mib_detach_node(s);
            for (t = s->children; t != NULL; t = t->children) {
                register_mib_detach_node(t);
            }
        }
    }
}

int
register_mib_context(const char *moduleName,
                     struct variable *var,
                     size_t varsize,
                     size_t numvars,
                     oid * mibloc,
                     size_t mibloclen,
                     int priority,
                     int range_subid,
                     oid range_ubound,
                     netsnmp_session * ss,
                     const char *context, int timeout, int flags)
{
    return netsnmp_register_old_api(moduleName, var, varsize, numvars,
                                    mibloc, mibloclen, priority,
                                    range_subid, range_ubound, ss, context,
                                    timeout, flags);
}

int
register_mib_range(const char *moduleName,
                   struct variable *var,
                   size_t varsize,
                   size_t numvars,
                   oid * mibloc,
                   size_t mibloclen,
                   int priority,
                   int range_subid, oid range_ubound, netsnmp_session * ss)
{
    return register_mib_context(moduleName, var, varsize, numvars,
                                mibloc, mibloclen, priority,
                                range_subid, range_ubound, ss, "", -1, 0);
}

int
register_mib_priority(const char *moduleName,
                      struct variable *var,
                      size_t varsize,
                      size_t numvars,
                      oid * mibloc, size_t mibloclen, int priority)
{
    return register_mib_range(moduleName, var, varsize, numvars,
                              mibloc, mibloclen, priority, 0, 0, NULL);
}

int
register_mib(const char *moduleName,
             struct variable *var,
             size_t varsize,
             size_t numvars, oid * mibloc, size_t mibloclen)
{
    return register_mib_priority(moduleName, var, varsize, numvars,
                                 mibloc, mibloclen, DEFAULT_MIB_PRIORITY);
}

void
netsnmp_subtree_unload(netsnmp_subtree *sub, netsnmp_subtree *prev, const char *context)
{
    netsnmp_subtree *ptr;

    DEBUGMSGTL(("register_mib", "unload("));
    if (sub != NULL) {
        DEBUGMSGOID(("register_mib", sub->start_a, sub->start_len));
    } else {
        DEBUGMSG(("register_mib", "[NIL]"));
    }
    DEBUGMSG(("register_mib", ", "));
    if (prev != NULL) {
        DEBUGMSGOID(("register_mib", prev->start_a, prev->start_len));
    } else {
        DEBUGMSG(("register_mib", "[NIL]"));
    }
    DEBUGMSG(("register_mib", ")\n"));

    if (prev != NULL) {         /* non-leading entries are easy */
        prev->children = sub->children;
        invalidate_lookup_cache(context);
        return;
    }
    /*
     * otherwise, we need to amend our neighbours as well 
     */

    if (sub->children == NULL) {        /* just remove this node completely */
        for (ptr = sub->prev; ptr; ptr = ptr->children) {
            netsnmp_subtree_change_next(ptr, sub->next);
        }
        for (ptr = sub->next; ptr; ptr = ptr->children) {
            netsnmp_subtree_change_prev(ptr, sub->prev);
        }

	if (sub->prev == NULL) {
	    netsnmp_subtree_replace_first(sub->next, context);
	}

    } else {
        for (ptr = sub->prev; ptr; ptr = ptr->children)
            netsnmp_subtree_change_next(ptr, sub->children);
        for (ptr = sub->next; ptr; ptr = ptr->children)
            netsnmp_subtree_change_prev(ptr, sub->children);

	if (sub->prev == NULL) {
	    netsnmp_subtree_replace_first(sub->children, context);
	}
    }
    invalidate_lookup_cache(context);
}

/**
 * Unregisters an OID that has an associated context name value. 
 * Typically used when a module has multiple contexts defined.  The parameters
 * priority, range_subid, and range_ubound should be used in conjunction with
 * agentx, see RFC 2741, otherwise these values should always be 0.
 *
 * @param name  the specific OID to unregister if it conatins the associated
 *              context.
 *
 * @param len   the length of the OID, use  OID_LENGTH macro.
 *
 * @param priority  a value between 1 and 255, used to achieve a desired
 *                  configuration when different sessions register identical or
 *                  overlapping regions.  Subagents with no particular
 *                  knowledge of priority should register with the default
 *                  value of 127.
 *
 * @param range_subid  permits specifying a range in place of one of a subtree
 *                     sub-identifiers.  When this value is zero, no range is
 *                     being specified.
 *
 * @param range_ubound  the upper bound of a sub-identifier's range.
 *                      This field is present only if range_subid is not 0.
 *
 * @param context  a context name that has been created
 *
 * @return 
 * 
 */
int
unregister_mib_context(oid * name, size_t len, int priority,
                       int range_subid, oid range_ubound,
                       const char *context)
{
    netsnmp_subtree *list, *myptr;
    netsnmp_subtree *prev, *child, *next; /* loop through children */
    struct register_parameters reg_parms;
    int old_lookup_cache_val = netsnmp_get_lookup_cache_size();
    int unregistering = 1;
    int orig_subid_val = -1;

    netsnmp_set_lookup_cache_size(0);

    if ((range_subid != 0) &&  (range_subid <= len))
        orig_subid_val = name[range_subid-1];

    while(unregistering){
        DEBUGMSGTL(("register_mib", "unregistering "));
        DEBUGMSGOIDRANGE(("register_mib", name, len, range_subid, range_ubound));
        DEBUGMSG(("register_mib", "\n"));

        list = netsnmp_subtree_find(name, len, netsnmp_subtree_find_first(context),
                    context);
        if (list == NULL) {
            return MIB_NO_SUCH_REGISTRATION;
        }

        for (child = list, prev = NULL; child != NULL;
            prev = child, child = child->children) {
            if (netsnmp_oid_equals(child->name_a, child->namelen, name, len) == 0 &&
                child->priority == priority) {
                break;              /* found it */
             }
        }

        if (child == NULL) {
            return MIB_NO_SUCH_REGISTRATION;
        }

        netsnmp_subtree_unload(child, prev, context);
        myptr = child;              /* remember this for later */

        /*
        *  Now handle any occurances in the following subtrees,
        *      as a result of splitting this range.  Due to the
        *      nature of the way such splits work, the first
        *      subtree 'slice' that doesn't refer to the given
        *      name marks the end of the original region.
        *
        *  This should also serve to register ranges.
        */

        for (list = myptr->next; list != NULL; list = next) {
            next = list->next; /* list gets freed sometimes; cache next */
            for (child = list, prev = NULL; child != NULL;
                prev = child, child = child->children) {
                if ((netsnmp_oid_equals(child->name_a, child->namelen,
                    name, len) == 0) &&
            (child->priority == priority)) {
                    netsnmp_subtree_unload(child, prev, context);
                    netsnmp_subtree_free(child);
                    break;
                }
            }
            if (child == NULL)      /* Didn't find the given name */
                break;
        }

        /* Maybe we are in a range... */
        if (orig_subid_val != -1){
            if (++name[range_subid-1] >= orig_subid_val+range_ubound)
                {
                unregistering=0;
                name[range_subid-1] = orig_subid_val;
                }
        }
        else {
            unregistering=0;
        }
    }

    memset(&reg_parms, 0x0, sizeof(reg_parms));
    reg_parms.name = name;
    reg_parms.namelen = len;
    reg_parms.priority = priority;
    reg_parms.range_subid = range_subid;
    reg_parms.range_ubound = range_ubound;
    reg_parms.flags = 0x00;     /*  this is okay I think  */
    reg_parms.contextName = context;
    snmp_call_callbacks(SNMP_CALLBACK_APPLICATION,
                        SNMPD_CALLBACK_UNREGISTER_OID, &reg_parms);

    netsnmp_subtree_free(myptr);
    netsnmp_set_lookup_cache_size(old_lookup_cache_val);
    invalidate_lookup_cache(context);
    return MIB_UNREGISTERED_OK;
}

int
netsnmp_unregister_mib_table_row(oid * name, size_t len, int priority,
                                 int var_subid, oid range_ubound,
                                 const char *context)
{
    netsnmp_subtree *list, *myptr, *futureptr;
    netsnmp_subtree *prev, *child;       /* loop through children */
    struct register_parameters reg_parms;
    oid             range_lbound = name[var_subid - 1];

    DEBUGMSGTL(("register_mib", "unregistering "));
    DEBUGMSGOIDRANGE(("register_mib", name, len, var_subid, range_ubound));
    DEBUGMSG(("register_mib", "\n"));

    for (; name[var_subid - 1] <= range_ubound; name[var_subid - 1]++) {
        list = netsnmp_subtree_find(name, len, 
				netsnmp_subtree_find_first(context), context);

        if (list == NULL) {
            continue;
        }

        for (child = list, prev = NULL; child != NULL;
             prev = child, child = child->children) {

            if (netsnmp_oid_equals(child->name_a, child->namelen, 
				 name, len) == 0 && 
		(child->priority == priority)) {
                break;          /* found it */
            }
        }

        if (child == NULL) {
            continue;
        }

        netsnmp_subtree_unload(child, prev, context);
        myptr = child;          /* remember this for later */

        for (list = myptr->next; list != NULL; list = futureptr) {
            /* remember the next spot in the list in case we free this node */
            futureptr = list->next;

            /* check each child */
            for (child = list, prev = NULL; child != NULL;
                 prev = child, child = child->children) {

                if (netsnmp_oid_equals(child->name_a, child->namelen, 
				      name, len) == 0 &&
                    (child->priority == priority)) {
                    netsnmp_subtree_unload(child, prev, context);
                    netsnmp_subtree_free(child);
                    break;
                }
            }

            /* XXX: wjh: not sure why we're bailing here */
            if (child == NULL) {        /* Didn't find the given name */
                break;
            }
        }
        netsnmp_subtree_free(myptr);
    }

    name[var_subid - 1] = range_lbound;
    memset(&reg_parms, 0x0, sizeof(reg_parms));
    reg_parms.name = name;
    reg_parms.namelen = len;
    reg_parms.priority = priority;
    reg_parms.range_subid = var_subid;
    reg_parms.range_ubound = range_ubound;
    reg_parms.flags = 0x00;     /*  this is okay I think  */
    reg_parms.contextName = context;
    snmp_call_callbacks(SNMP_CALLBACK_APPLICATION,
                        SNMPD_CALLBACK_UNREGISTER_OID, &reg_parms);

    return 0;
}

int
unregister_mib_range(oid * name, size_t len, int priority,
                     int range_subid, oid range_ubound)
{
    return unregister_mib_context(name, len, priority, range_subid,
                                  range_ubound, "");
}

int
unregister_mib_priority(oid * name, size_t len, int priority)
{
    return unregister_mib_range(name, len, priority, 0, 0);
}

int
unregister_mib(oid * name, size_t len)
{
    return unregister_mib_priority(name, len, DEFAULT_MIB_PRIORITY);
}

void
unregister_mibs_by_session(netsnmp_session * ss)
{
    netsnmp_subtree *list, *list2;
    netsnmp_subtree *child, *prev, *next_child;
    struct register_parameters rp;
    subtree_context_cache *contextptr;

    DEBUGMSGTL(("register_mib", "unregister_mibs_by_session(%p) ctxt \"%s\"\n",
		ss, (ss && ss->contextName) ? ss->contextName : "[NIL]"));

    for (contextptr = get_top_context_cache(); contextptr != NULL;
         contextptr = contextptr->next) {
        for (list = contextptr->first_subtree; list != NULL; list = list2) {
            list2 = list->next;

            for (child = list, prev = NULL; child != NULL; child = next_child){
                next_child = child->children;

                if (((!ss || ss->flags & SNMP_FLAGS_SUBSESSION) &&
		     child->session == ss) ||
                    (!(!ss || ss->flags & SNMP_FLAGS_SUBSESSION) && child->session &&
                     child->session->subsession == ss)) {

                    memset(&rp,0x0,sizeof(rp));
                    rp.name = child->name_a;
		    child->name_a = NULL;
                    rp.namelen = child->namelen;
                    rp.priority = child->priority;
                    rp.range_subid = child->range_subid;
                    rp.range_ubound = child->range_ubound;
                    rp.timeout = child->timeout;
                    rp.flags = child->flags;
                    if ((NULL != child->reginfo) &&
                        (NULL != child->reginfo->contextName))
                        rp.contextName = child->reginfo->contextName;

                    if (child->reginfo != NULL) {
                        /*
                         * Don't let's free the session pointer just yet!  
                         */
                        child->reginfo->handler->myvoid = NULL;
                        netsnmp_handler_registration_free(child->reginfo);
			child->reginfo = NULL;
                    }

                    netsnmp_subtree_unload(child, prev, contextptr->context_name);
                    netsnmp_subtree_free(child);

                    snmp_call_callbacks(SNMP_CALLBACK_APPLICATION,
                                        SNMPD_CALLBACK_UNREGISTER_OID, &rp);
		    SNMP_FREE(rp.name);
                } else {
                    prev = child;
                }
            }
        }
        netsnmp_subtree_join(contextptr->first_subtree);
    }
}

/*
 * in_a_view: determines if a given snmp_pdu is allowed to see a
 * given name/namelen OID pointer
 * name         IN - name of var, OUT - name matched
 * nameLen      IN -number of sub-ids in name, OUT - subid-is in matched name
 * pi           IN - relevant auth info re PDU 
 * cvp          IN - relevant auth info re mib module
 */

int
in_a_view(oid *name, size_t *namelen, netsnmp_pdu *pdu, int type)
{
    struct view_parameters view_parms;

    if (pdu->flags & UCD_MSG_FLAG_ALWAYS_IN_VIEW) {
	/* Enable bypassing of view-based access control */
        return VACM_SUCCESS;
    }

    /*
     * check for v1 and counter64s, since snmpv1 doesn't support it 
     */
#ifndef NETSNMP_DISABLE_SNMPV1
    if (pdu->version == SNMP_VERSION_1 && type == ASN_COUNTER64) {
        return VACM_NOTINVIEW;
    }
#endif

    view_parms.pdu = pdu;
    view_parms.name = name;
    if (namelen != NULL) {
        view_parms.namelen = *namelen;
    } else {
        view_parms.namelen = 0;
    }
    view_parms.errorcode = 0;
    view_parms.check_subtree = 0;

    switch (pdu->version) {
#ifndef NETSNMP_DISABLE_SNMPV1
    case SNMP_VERSION_1:
#endif
#ifndef NETSNMP_DISABLE_SNMPV2C
    case SNMP_VERSION_2c:
#endif
    case SNMP_VERSION_3:
        snmp_call_callbacks(SNMP_CALLBACK_APPLICATION,
                            SNMPD_CALLBACK_ACM_CHECK, &view_parms);
        return view_parms.errorcode;
    }
    return VACM_NOSECNAME;
}

/*
 * check_acces: determines if a given snmp_pdu is ever going to be
 * allowed to do anynthing or if it's not going to ever be
 * authenticated.
 */
int
check_access(netsnmp_pdu *pdu)
{                               /* IN - pdu being checked */
    struct view_parameters view_parms;
    view_parms.pdu = pdu;
    view_parms.name = NULL;
    view_parms.namelen = 0;
    view_parms.errorcode = 0;
    view_parms.check_subtree = 0;

    if (pdu->flags & UCD_MSG_FLAG_ALWAYS_IN_VIEW) {
	/* Enable bypassing of view-based access control */
        return 0;
    }

    switch (pdu->version) {
#ifndef NETSNMP_DISABLE_SNMPV1
    case SNMP_VERSION_1:
#endif
#ifndef NETSNMP_DISABLE_SNMPV2C
    case SNMP_VERSION_2c:
#endif
    case SNMP_VERSION_3:
        snmp_call_callbacks(SNMP_CALLBACK_APPLICATION,
                            SNMPD_CALLBACK_ACM_CHECK_INITIAL, &view_parms);
        return view_parms.errorcode;
    }
    return 1;
}

/** checks to see if everything within a
 *  given subtree is either: in view, not in view, or possibly both.
 *  If the entire subtree is not-in-view we can use this information to
 *  skip calling the sub-handlers entirely.
 *  @returns 0 if entire subtree is accessible, 5 if not and 7 if
 *  portions are both.  1 on error (illegal pdu version).
 */
int
netsnmp_acm_check_subtree(netsnmp_pdu *pdu, oid *name, size_t namelen)
{                               /* IN - pdu being checked */
    struct view_parameters view_parms;
    view_parms.pdu = pdu;
    view_parms.name = name;
    view_parms.namelen = namelen;
    view_parms.errorcode = 0;
    view_parms.check_subtree = 1;

    if (pdu->flags & UCD_MSG_FLAG_ALWAYS_IN_VIEW) {
	/* Enable bypassing of view-based access control */
        return 0;
    }

    switch (pdu->version) {
#ifndef NETSNMP_DISABLE_SNMPV1
    case SNMP_VERSION_1:
#endif
#ifndef NETSNMP_DISABLE_SNMPV2C
    case SNMP_VERSION_2c:
#endif
    case SNMP_VERSION_3:
        snmp_call_callbacks(SNMP_CALLBACK_APPLICATION,
                            SNMPD_CALLBACK_ACM_CHECK_SUBTREE, &view_parms);
        return view_parms.errorcode;
    }
    return 1;
}

netsnmp_subtree *
netsnmp_subtree_find_prev(oid *name, size_t len, netsnmp_subtree *subtree,
			  const char *context_name)
{
    lookup_cache *lookup_cache = NULL;
    netsnmp_subtree *myptr = NULL, *previous = NULL;
    int cmp = 1;
    size_t ll_off = 0;

    if (subtree) {
        myptr = subtree;
    } else {
	/* look through everything */
        if (lookup_cache_size) {
            lookup_cache = lookup_cache_find(context_name, name, len, &cmp);
            if (lookup_cache) {
                myptr = lookup_cache->next;
                previous = lookup_cache->previous;
            }
            if (!myptr)
                myptr = netsnmp_subtree_find_first(context_name);
        } else {
            myptr = netsnmp_subtree_find_first(context_name);
        }
    }

    /*
     * this optimization causes a segfault on sf cf alpha-linux1.
     * ifdef out until someone figures out why and fixes it. xxx-rks 20051117
     */
#ifndef __alpha
#define WTEST_OPTIMIZATION 1
#endif
#ifdef WTEST_OPTIMIZATION
    DEBUGMSGTL(("wtest","oid in: "));
    DEBUGMSGOID(("wtest", name, len));
    DEBUGMSG(("wtest","\n"));
#endif
    for (; myptr != NULL; previous = myptr, myptr = myptr->next) {
#ifdef WTEST_OPTIMIZATION
        /* Compare the incoming oid with the linked list.  If we have
           results of previous compares, its faster to make sure the
           length we differed in the last check is greater than the
           length between this pointer and the last then we don't need
           to actually perform a comparison */
        DEBUGMSGTL(("wtest","oid cmp: "));
        DEBUGMSGOID(("wtest", myptr->start_a, myptr->start_len));
        DEBUGMSG(("wtest","  --- off = %lu, in off = %lu test = %d\n",
                  (unsigned long)myptr->oid_off, (unsigned long)ll_off,
                  !(ll_off && myptr->oid_off &&
                    myptr->oid_off > ll_off)));
        if (!(ll_off && myptr->oid_off && myptr->oid_off > ll_off) &&
            netsnmp_oid_compare_ll(name, len,
                                   myptr->start_a, myptr->start_len,
                                   &ll_off) < 0) {
#else
        if (snmp_oid_compare(name, len, myptr->start_a, myptr->start_len) < 0) {
#endif
            if (lookup_cache_size && previous && cmp) {
                if (lookup_cache) {
                    lookup_cache_replace(lookup_cache, myptr, previous);
                } else {
                    lookup_cache_add(context_name, myptr, previous);
                }
            }
            return previous;
        }
    }
    return previous;
}

netsnmp_subtree *
netsnmp_subtree_find_next(oid *name, size_t len,
			  netsnmp_subtree *subtree, const char *context_name)
{
    netsnmp_subtree *myptr = NULL;

    myptr = netsnmp_subtree_find_prev(name, len, subtree, context_name);

    if (myptr != NULL) {
        myptr = myptr->next;
        while (myptr != NULL && (myptr->variables == NULL || 
				 myptr->variables_len == 0)) {
            myptr = myptr->next;
        }
        return myptr;
    } else if (subtree != NULL && snmp_oid_compare(name, len, 
				   subtree->start_a, subtree->start_len) < 0) {
        return subtree;
    } else {
        return NULL;
    }
}

netsnmp_subtree *
netsnmp_subtree_find(oid *name, size_t len, netsnmp_subtree *subtree, 
		     const char *context_name)
{
    netsnmp_subtree *myptr;

    myptr = netsnmp_subtree_find_prev(name, len, subtree, context_name);
    if (myptr && myptr->end_a &&
        snmp_oid_compare(name, len, myptr->end_a, myptr->end_len)<0) {
        return myptr;
    }

    return NULL;
}

netsnmp_session *
get_session_for_oid(oid *name, size_t len, const char *context_name)
{
    netsnmp_subtree *myptr;

    myptr = netsnmp_subtree_find_prev(name, len, 
				      netsnmp_subtree_find_first(context_name),
				      context_name);

    while (myptr && myptr->variables == NULL) {
        myptr = myptr->next;
    }

    if (myptr == NULL) {
        return NULL;
    } else {
        return myptr->session;
    }
}

void
setup_tree(void)
{
    oid ccitt[1]           = { 0 };
    oid iso[1]             = { 1 };
    oid joint_ccitt_iso[1] = { 2 };

#ifdef USING_AGENTX_SUBAGENT_MODULE
    int role =  netsnmp_ds_get_boolean(NETSNMP_DS_APPLICATION_ID, 
				       NETSNMP_DS_AGENT_ROLE);

    netsnmp_ds_set_boolean(NETSNMP_DS_APPLICATION_ID, NETSNMP_DS_AGENT_ROLE, 
			   MASTER_AGENT);
#endif

    /* 
     * we need to have the oid's in the heap, that we can *free* it for every case, 
     * thats the purpose of the duplicate_objid's
     */
    netsnmp_register_null(snmp_duplicate_objid(ccitt, 1), 1);
    netsnmp_register_null(snmp_duplicate_objid(iso, 1), 1);
    netsnmp_register_null(snmp_duplicate_objid(joint_ccitt_iso, 1), 1);

#ifdef USING_AGENTX_SUBAGENT_MODULE
    netsnmp_ds_set_boolean(NETSNMP_DS_APPLICATION_ID, NETSNMP_DS_AGENT_ROLE, 
			   role);
#endif
}

int 
remove_tree_entry (oid *name, size_t len) {

    netsnmp_subtree *sub = NULL;

    if ((sub = netsnmp_subtree_find(name, len, NULL, "")) == NULL) {
	return MIB_NO_SUCH_REGISTRATION;
    }

    return unregister_mib_context(name, len, sub->priority,
				  sub->range_subid, sub->range_ubound, "");

}


void
shutdown_tree(void) {
    oid ccitt[1]           = { 0 };
    oid iso[1]             = { 1 };
    oid joint_ccitt_iso[1] = { 2 };

    DEBUGMSGTL(("agent_registry", "shut down tree\n"));

    remove_tree_entry(joint_ccitt_iso, 1);
    remove_tree_entry(iso, 1);
    remove_tree_entry(ccitt, 1);

}

void
clear_subtree (netsnmp_subtree *sub) {

    netsnmp_subtree *nxt;
    
    if (sub == NULL)
	return;

    for(nxt = sub; nxt;) {
        if (nxt->children != NULL) {
            clear_subtree(nxt->children);
        }
        sub = nxt;
        nxt = nxt->next;
        netsnmp_subtree_free(sub);
    }

}

void
clear_lookup_cache(void) {

    lookup_cache_context *ptr = NULL, *next = NULL;

    ptr = thecontextcache;
    while (ptr) {
	next = ptr->next;
	SNMP_FREE(ptr->context);
	SNMP_FREE(ptr);
	ptr = next;
    }
    thecontextcache = NULL; /* !!! */
}

void
clear_context(void) {

    subtree_context_cache *ptr = NULL, *next = NULL;

    DEBUGMSGTL(("agent_registry", "clear context\n"));

    ptr = get_top_context_cache(); 
    while (ptr) {
	next = ptr->next;

	if (ptr->first_subtree) {
	    clear_subtree(ptr->first_subtree);
	}

	SNMP_FREE(ptr->context_name);
        SNMP_FREE(ptr);

	ptr = next;
    }
    context_subtrees = NULL; /* !!! */
    clear_lookup_cache();
}

extern void     dump_idx_registry(void);
void
dump_registry(void)
{
    struct variable *vp = NULL;
    netsnmp_subtree *myptr, *myptr2;
    u_char *s = NULL, *e = NULL, *v = NULL;
    size_t sl = 256, el = 256, vl = 256, sl_o = 0, el_o = 0, vl_o = 0;
    int i = 0;

    if ((s = (u_char *) calloc(sl, 1)) != NULL &&
        (e = (u_char *) calloc(sl, 1)) != NULL &&
        (v = (u_char *) calloc(sl, 1)) != NULL) {

        subtree_context_cache *ptr;
        for (ptr = context_subtrees; ptr; ptr = ptr->next) {
            printf("Subtrees for Context: %s\n", ptr->context_name);
            for (myptr = ptr->first_subtree; myptr != NULL;
                 myptr = myptr->next) {
                sl_o = el_o = vl_o = 0;

                if (!sprint_realloc_objid(&s, &sl, &sl_o, 1,
                                          myptr->start_a,
                                          myptr->start_len)) {
                    break;
                }
                if (!sprint_realloc_objid(&e, &el, &el_o, 1,
                                          myptr->end_a,
					  myptr->end_len)) {
                    break;
                }

                if (myptr->variables) {
                    printf("%02x ( %s - %s ) [", myptr->flags, s, e);
                    for (i = 0, vp = myptr->variables;
                         i < myptr->variables_len; i++) {
                        vl_o = 0;
                        if (!sprint_realloc_objid
                            (&v, &vl, &vl_o, 1, vp->name, vp->namelen)) {
                            break;
                        }
                        printf("%s, ", v);
                        vp = (struct variable *) ((char *) vp +
                                                  myptr->variables_width);
                    }
                    printf("]\n");
                } else {
                    printf("%02x   %s - %s  \n", myptr->flags, s, e);
                }
                for (myptr2 = myptr; myptr2 != NULL;
                     myptr2 = myptr2->children) {
                    if (myptr2->label_a && myptr2->label_a[0]) {
                        if (strcmp(myptr2->label_a, "old_api") == 0) {
                            struct variable *vp =
                                myptr2->reginfo->handler->myvoid;

                            if (!sprint_realloc_objid(&s, &sl, &sl_o, 1,
                                                 vp->name, vp->namelen)) {
                                continue;
                            }
                            printf("\t%s[%s] %p var %s\n", myptr2->label_a,
                                   myptr2->reginfo->handlerName ?
                                   myptr2->reginfo->handlerName : "no-name",
                                   myptr2->reginfo, s);
                        } else {
                            printf("\t%s %s %p\n", myptr2->label_a,
                                   myptr2->reginfo->handlerName ?
                                   myptr2->reginfo->handlerName : "no-handler-name",
                                   myptr2->reginfo);
                        }
                    }
                }
            }
        }
    }

    if (s != NULL) {
        SNMP_FREE(s);
    }
    if (e != NULL) {
        SNMP_FREE(e);
    }
    if (v != NULL) {
        SNMP_FREE(v);
    }

    dump_idx_registry();
}

int             external_signal_scheduled[NUM_EXTERNAL_SIGS];
void            (*external_signal_handler[NUM_EXTERNAL_SIGS]) (int);

#ifndef WIN32

/*
 * TODO: add agent_SIGXXX_handler functions and `case SIGXXX: ...' lines
 *       below for every single that might be handled by register_signal().
 */

RETSIGTYPE
agent_SIGCHLD_handler(int sig)
{
    external_signal_scheduled[SIGCHLD]++;
#ifndef HAVE_SIGACTION
    /*
     * signal() sucks. It *might* have SysV semantics, which means that
     * * a signal handler is reset once it gets called. Ensure that it
     * * remains active.
     */
    signal(SIGCHLD, agent_SIGCHLD_handler);
#endif
}

int
register_signal(int sig, void (*func) (int))
{

    switch (sig) {
#if defined(SIGCHLD)
    case SIGCHLD:
#ifdef HAVE_SIGACTION
        {
            static struct sigaction act;
            act.sa_handler = agent_SIGCHLD_handler;
            sigemptyset(&act.sa_mask);
            act.sa_flags = 0;
            sigaction(SIGCHLD, &act, NULL);
        }
#else
        signal(SIGCHLD, agent_SIGCHLD_handler);
#endif
        break;
#endif
    default:
        snmp_log(LOG_CRIT,
                 "register_signal: signal %d cannot be handled\n", sig);
        return SIG_REGISTRATION_FAILED;
    }

    external_signal_handler[sig] = func;
    external_signal_scheduled[sig] = 0;

    DEBUGMSGTL(("register_signal", "registered signal %d\n", sig));
    return SIG_REGISTERED_OK;
}

int
unregister_signal(int sig)
{
    signal(sig, SIG_DFL);
    DEBUGMSGTL(("unregister_signal", "unregistered signal %d\n", sig));
    return SIG_UNREGISTERED_OK;
}

#endif                          /* !WIN32 */

/**  @} */
