/* 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.
 *
 * Portions of this file are copyrighted by:
 * Copyright (c) 2016 VMware, Inc. All rights reserved.
 * Use is subject to license terms specified in the COPYING file
 * distributed with the Net-SNMP package.
 */
#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-features.h>

#include <sys/types.h>

#if HAVE_STRING_H
#include <string.h>
#endif

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

#include <net-snmp/agent/bulk_to_next.h>

netsnmp_feature_child_of(agent_handler, libnetsnmpagent);

netsnmp_feature_child_of(handler_mark_requests_as_delegated, agent_handler);

static netsnmp_mib_handler *_clone_handler(netsnmp_mib_handler *it);

/***********************************************************************/
/*
 * New Handler based API 
 */
/***********************************************************************/
/** @defgroup handler Net-SNMP Agent handler and extensibility API
 *  @ingroup agent
 *
 *  The basic theory goes something like this: In the past, with the
 *  original mib module api (which derived from the original CMU SNMP
 *  code) the underlying mib modules were passed very little
 *  information (only the truly most basic information about a
 *  request).  This worked well at the time but in todays world of
 *  subagents, device instrumentation, low resource consumption, etc,
 *  it just isn't flexible enough.  "handlers" are here to fix all that.
 *
 *  With the rewrite of the agent internals for the net-snmp 5.0
 *  release, we introduce a modular calling scheme that allows agent
 *  modules to be written in a very flexible manner, and more
 *  importantly allows reuse of code in a decent way (and without the
 *  memory and speed overheads of OO languages like C++).
 *
 *  Functionally, the notion of what a handler does is the same as the
 *  older api: A handler is @link netsnmp_create_handler() created@endlink and
 *  then @link netsnmp_register_handler() registered@endlink with the main
 *  agent at a given OID in the OID tree and gets called any time a
 *  request is made that it should respond to.  You probably should
 *  use one of the convenience helpers instead of doing anything else
 *  yourself though:
 *
 *  Most importantly, though, is that the handlers are built on the
 *  notion of modularity and reuse.  Specifically, rather than do all
 *  the really hard work (like parsing table indexes out of an
 *  incoming oid request) in each module, the API is designed to make
 *  it easy to write "helper" handlers that merely process some aspect
 *  of the request before passing it along to the final handler that
 *  returns the real answer.  Most people will want to make use of the
 *  @link instance instance@endlink, @link table table@endlink, @link
 *  table_iterator table_iterator@endlink, @link table_data
 *  table_data@endlink, or @link table_dataset table_dataset@endlink
 *  helpers to make their life easier.  These "helpers" interpert
 *  important aspects of the request and pass them on to you.
 *
 *  For instance, the @link table table@endlink helper is designed to
 *  hand you a list of extracted index values from an incoming
 *  request.  THe @link table_iterator table_iterator@endlink helper
 *  is built on top of the table helper, and is designed to help you
 *  iterate through data stored elsewhere (like in a kernel) that is
 *  not in OID lexographical order (ie, don't write your own index/oid
 *  sorting routine, use this helper instead).  The beauty of the
 *  @link table_iterator table_iterator helper@endlink, as well as the @link
 *  instance instance@endlink helper is that they take care of the complex
 *  GETNEXT processing entirely for you and hand you everything you
 *  need to merely return the data as if it was a GET request.  Much
 *  less code and hair pulling.  I've pulled all my hair out to help
 *  you so that only one of us has to be bald.
 *
 * @{
 */

/** Creates a MIB handler structure.
 *  The new structure is allocated and filled using the given name
 *  and access method.
 *  The returned handler should then be @link netsnmp_register_handler()
 *  registered @endlink.
 *
 *  @param name is the handler name and is copied then assigned to
 *              netsnmp_mib_handler->handler_name
 *
 *  @param handler_access_method is a function pointer used as the access
 *	   method for this handler registration instance for whatever required
 *         needs.
 *
 *  @return a pointer to a populated netsnmp_mib_handler struct to be
 *          registered
 *
 *  @see netsnmp_create_handler_registration()
 *  @see netsnmp_register_handler()
 */
netsnmp_mib_handler *
netsnmp_create_handler(const char *name,
                       Netsnmp_Node_Handler * handler_access_method)
{
    netsnmp_mib_handler *ret = SNMP_MALLOC_TYPEDEF(netsnmp_mib_handler);
    if (ret) {
        ret->access_method = handler_access_method;
        if (NULL != name) {
            ret->handler_name = strdup(name);
            if (NULL == ret->handler_name)
                SNMP_FREE(ret);
        }
    }
    return ret;
}

/** Creates a MIB handler structure.
 *  The new structure is allocated and filled using the given name,
 *  access function, registration location OID and list of modes that
 *  the handler supports. If modes == 0, then modes will automatically
 *  be set to the default value of only HANDLER_CAN_DEFAULT, which is
 *  by default read-only GET and GETNEXT requests. A hander which supports
 *  sets but not row creation should set us a mode of HANDLER_CAN_SET_ONLY.
 *  @note This ends up calling netsnmp_create_handler(name, handler_access_method)
 *  @param name is the handler name and is copied then assigned to
 *              netsnmp_handler_registration->handlerName.
 *
 *  @param handler is a function pointer used as the access
 *	method for this handler registration instance for whatever required
 *	needs.
 *
 *  @param reg_oid is the registration location oid.
 *
 *  @param reg_oid_len is the length of reg_oid; can use the macro,
 *         OID_LENGTH
 *
 *  @param modes is used to configure read/write access.  If modes == 0, 
 *	then modes will automatically be set to the default 
 *	value of only HANDLER_CAN_DEFAULT, which is by default read-only GET 
 *	and GETNEXT requests.  The other two mode options are read only, 
 *	HANDLER_CAN_RONLY, and read/write, HANDLER_CAN_RWRITE.
 *
 *		- HANDLER_CAN_GETANDGETNEXT
 *		- HANDLER_CAN_SET
 *		- HANDLER_CAN_GETBULK      
 *
 *		- HANDLER_CAN_RONLY   (HANDLER_CAN_GETANDGETNEXT)
 *		- HANDLER_CAN_RWRITE  (HANDLER_CAN_GETANDGETNEXT | 
 *			HANDLER_CAN_SET)
 *		- HANDLER_CAN_DEFAULT HANDLER_CAN_RONLY
 *
 *  @return Returns a pointer to a netsnmp_handler_registration struct.
 *          NULL is returned only when memory could not be allocated for the 
 *          netsnmp_handler_registration struct.
 *
 *
 *  @see netsnmp_create_handler()
 *  @see netsnmp_register_handler()
 */
netsnmp_handler_registration *
netsnmp_handler_registration_create(const char *name,
                                    netsnmp_mib_handler *handler,
                                    const oid * reg_oid, size_t reg_oid_len,
                                    int modes)
{
    netsnmp_handler_registration *the_reg;
    the_reg = SNMP_MALLOC_TYPEDEF(netsnmp_handler_registration);
    if (!the_reg)
        return NULL;

    if (modes)
        the_reg->modes = modes;
    else
        the_reg->modes = HANDLER_CAN_DEFAULT;

    the_reg->handler = handler;
    the_reg->priority = DEFAULT_MIB_PRIORITY;
    if (name)
        the_reg->handlerName = strdup(name);
    the_reg->rootoid = snmp_duplicate_objid(reg_oid, reg_oid_len);
    the_reg->rootoid_len = reg_oid_len;
    return the_reg;
}

/** Creates a handler registration structure with a new MIB handler.
 *  This function first @link netsnmp_create_handler() creates @endlink
 *  a MIB handler, then @link netsnmp_handler_registration_create()
 *  makes registation structure @endlink for it.
 *
 *  @param name is the handler name for netsnmp_create_handler()
 *
 *  @param handler_access_method is a function pointer used as the access
 *     method for netsnmp_create_handler()
 *
 *  @param reg_oid is the registration location oid.
 *
 *  @param reg_oid_len is the length of reg_oid; can use the macro,
 *         OID_LENGTH
 *
 *  @param modes is used to configure read/write access, as in
 *         netsnmp_handler_registration_create()
 *
 *  @return Returns a pointer to a netsnmp_handler_registration struct.
 *          If the structures creation failed, NULL is returned.
 *
 *  @see netsnmp_create_handler()
 *  @see netsnmp_handler_registration_create()
 */
netsnmp_handler_registration *
netsnmp_create_handler_registration(const char *name,
                                    Netsnmp_Node_Handler *
                                    handler_access_method, const oid * reg_oid,
                                    size_t reg_oid_len, int modes)
{
    netsnmp_handler_registration *rv = NULL;
    netsnmp_mib_handler *handler =
        netsnmp_create_handler(name, handler_access_method);
    if (handler) {
        rv = netsnmp_handler_registration_create(
            name, handler, reg_oid, reg_oid_len, modes);
        if (!rv)
            netsnmp_handler_free(handler);
    }
    return rv;
}

/** Registers a MIB handler inside the registration structure.
 *  Checks given registation handler for sanity, then
 *  @link netsnmp_register_mib() performs registration @endlink
 *  in the MIB tree, as defined by the netsnmp_handler_registration
 *  pointer. On success, SNMP_CALLBACK_APPLICATION is called.
 *  The registration struct may be created by call of
 *  netsnmp_create_handler_registration().
 *
 *  @param reginfo Pointer to a netsnmp_handler_registration struct.
 *
 *  @return Returns SNMPERR_SUCCESS or SNMP_ERR_* error code.
 *
 *  @see netsnmp_create_handler_registration()
 *  @see netsnmp_register_mib()
 */
int
netsnmp_register_handler(netsnmp_handler_registration *reginfo)
{
    netsnmp_mib_handler *handler;
    int flags = 0;

    if (reginfo == NULL) {
        snmp_log(LOG_ERR, "netsnmp_register_handler() called illegally\n");
        netsnmp_assert(reginfo != NULL);
        return SNMP_ERR_GENERR;
    }

    DEBUGIF("handler::register") {
        DEBUGMSGTL(("handler::register", "Registering %s (", reginfo->handlerName));
        for (handler = reginfo->handler; handler; handler = handler->next) {
            DEBUGMSG(("handler::register", "::%s", handler->handler_name));
        }

        DEBUGMSG(("handler::register", ") at "));
        if (reginfo->rootoid && reginfo->range_subid) {
            DEBUGMSGOIDRANGE(("handler::register", reginfo->rootoid,
                              reginfo->rootoid_len, reginfo->range_subid,
                              reginfo->range_ubound));
        } else if (reginfo->rootoid) {
            DEBUGMSGOID(("handler::register", reginfo->rootoid,
                         reginfo->rootoid_len));
        } else {
            DEBUGMSG(("handler::register", "[null]"));
        }
        DEBUGMSG(("handler::register", "\n"));
    }

    /*
     * don't let them register for absolutely nothing.  Probably a mistake 
     */
    if (0 == reginfo->modes) {
        reginfo->modes = HANDLER_CAN_DEFAULT;
        snmp_log(LOG_WARNING, "no registration modes specified for %s. "
                 "Defaulting to 0x%x\n", reginfo->handlerName, reginfo->modes);
    }

    /*
     * for handlers that can't GETBULK, force a conversion handler on them 
     */
    if (!(reginfo->modes & HANDLER_CAN_GETBULK)) {
        handler = netsnmp_get_bulk_to_next_handler();
        if (!handler ||
            (netsnmp_inject_handler(reginfo, handler) != SNMPERR_SUCCESS)) {
            snmp_log(LOG_WARNING, "could not inject bulk to next handler\n");
            if (handler)
                netsnmp_handler_free(handler);
            /** should this be a critical error? */
            netsnmp_handler_registration_free(reginfo);
            return SNMP_ERR_GENERR;
        }
    }

    for (handler = reginfo->handler; handler; handler = handler->next) {
        if (handler->flags & MIB_HANDLER_INSTANCE)
            flags = FULLY_QUALIFIED_INSTANCE;
    }

    return netsnmp_register_mib(reginfo->handlerName,
                                NULL, 0, 0,
                                reginfo->rootoid, reginfo->rootoid_len,
                                reginfo->priority,
                                reginfo->range_subid,
                                reginfo->range_ubound, NULL,
                                reginfo->contextName, reginfo->timeout, flags,
                                reginfo, 1);
}

/** Unregisters a MIB handler described inside the registration structure.
 *  Removes a registration, performed earlier by
 *  netsnmp_register_handler(), from the MIB tree.
 *  Uses unregister_mib_context() to do the task.
 *
 *  @param reginfo Pointer to a netsnmp_handler_registration struct.
 *
 *  @return Returns SNMPERR_SUCCESS or SNMP_ERR_* error code.
 *
 *  @see netsnmp_register_handler()
 *  @see unregister_mib_context()
 */
int
netsnmp_unregister_handler(netsnmp_handler_registration *reginfo)
{
    if (!reginfo)
        return SNMPERR_SUCCESS;
    return unregister_mib_context(reginfo->rootoid, reginfo->rootoid_len,
                                  reginfo->priority,
                                  reginfo->range_subid, reginfo->range_ubound,
                                  reginfo->contextName);
}

/** Registers a MIB handler inside the registration structure.
 *  Checks given registation handler for sanity, then
 *  @link netsnmp_register_mib() performs registration @endlink
 *  in the MIB tree, as defined by the netsnmp_handler_registration
 *  pointer. Never calls SNMP_CALLBACK_APPLICATION.
 *  The registration struct may be created by call of
 *  netsnmp_create_handler_registration().
 *
 *  @param reginfo Pointer to a netsnmp_handler_registration struct.
 *
 *  @return Returns SNMPERR_SUCCESS or SNMP_ERR_* error code.
 *
 *  @see netsnmp_create_handler_registration()
 *  @see netsnmp_register_mib()
 */
int
netsnmp_register_handler_nocallback(netsnmp_handler_registration *reginfo)
{
    netsnmp_mib_handler *handler;
    if (reginfo == NULL) {
        snmp_log(LOG_ERR, "netsnmp_register_handler_nocallback() called illegally\n");
        netsnmp_assert(reginfo != NULL);
        return SNMP_ERR_GENERR;
    }
    DEBUGIF("handler::register") {
        DEBUGMSGTL(("handler::register",
                    "Registering (with no callback) "));
        for (handler = reginfo->handler; handler; handler = handler->next) {
            DEBUGMSG(("handler::register", "::%s", handler->handler_name));
        }

        DEBUGMSG(("handler::register", " at "));
        if (reginfo->rootoid && reginfo->range_subid) {
            DEBUGMSGOIDRANGE(("handler::register", reginfo->rootoid,
                              reginfo->rootoid_len, reginfo->range_subid,
                              reginfo->range_ubound));
        } else if (reginfo->rootoid) {
            DEBUGMSGOID(("handler::register", reginfo->rootoid,
                         reginfo->rootoid_len));
        } else {
            DEBUGMSG(("handler::register", "[null]"));
        }
        DEBUGMSG(("handler::register", "\n"));
    }

    /*
     * don't let them register for absolutely nothing.  Probably a mistake 
     */
    if (0 == reginfo->modes) {
        reginfo->modes = HANDLER_CAN_DEFAULT;
    }

    return netsnmp_register_mib(reginfo->handler->handler_name,
                                NULL, 0, 0,
                                reginfo->rootoid, reginfo->rootoid_len,
                                reginfo->priority,
                                reginfo->range_subid,
                                reginfo->range_ubound, NULL,
                                reginfo->contextName, reginfo->timeout, 0,
                                reginfo, 0);
}

/** Injects handler into the calling chain of handlers.
 *  The given MIB handler is inserted after the handler named before_what.
 *  If before_what is NULL, the handler is put at the top of the list,
 *  and hence will be the handler to be called first.
 *
 *  @return Returns SNMPERR_SUCCESS or SNMP_ERR_* error code.
 *
 *  @see netsnmp_create_handler_registration()
 *  @see netsnmp_inject_handler()
 */
int
netsnmp_inject_handler_before(netsnmp_handler_registration *reginfo,
                              netsnmp_mib_handler *handler,
                              const char *before_what)
{
    netsnmp_mib_handler *handler2 = handler;

    if (handler == NULL || reginfo == NULL) {
        snmp_log(LOG_ERR, "netsnmp_inject_handler() called illegally\n");
        netsnmp_assert(reginfo != NULL);
        netsnmp_assert(handler != NULL);
        return SNMP_ERR_GENERR;
    }
    while (handler2->next) {
        handler2 = handler2->next;  /* Find the end of a handler sub-chain */
    }
    if (reginfo->handler == NULL) {
        DEBUGMSGTL(("handler:inject", "injecting %s\n", handler->handler_name));
    }
    else {
        DEBUGMSGTL(("handler:inject", "injecting %s before %s\n",
                    handler->handler_name, reginfo->handler->handler_name));
    }
    if (before_what) {
        netsnmp_mib_handler *nexth, *prevh = NULL;
        if (reginfo->handler == NULL) {
            snmp_log(LOG_ERR, "no handler to inject before\n");
            return SNMP_ERR_GENERR;
        }
        for(nexth = reginfo->handler; nexth;
            prevh = nexth, nexth = nexth->next) {
            if (strcmp(nexth->handler_name, before_what) == 0)
                break;
        }
        if (!nexth) {
	    snmp_log(LOG_ERR, "Cannot inject '%s' before '%s': not found\n", handler->handler_name, before_what);
	    snmp_log(LOG_ERR, "The handlers are:\n");
	    for (nexth = reginfo->handler; nexth; nexth = nexth->next)
		snmp_log(LOG_ERR, "  %s\n", nexth->handler_name);
            return SNMP_ERR_GENERR;
	}
        if (prevh) {
            /* after prevh and before nexth */
            prevh->next = handler;
            handler2->next = nexth;
            handler->prev = prevh;
            nexth->prev = handler2;
            return SNMPERR_SUCCESS;
        }
        /* else we're first, which is what we do next anyway so fall through */
    }
    handler2->next = reginfo->handler;
    if (reginfo->handler)
        reginfo->handler->prev = handler2;
    reginfo->handler = handler;
    return SNMPERR_SUCCESS;
}

/** Injects handler into the calling chain of handlers.
 *  The given MIB handler is put at the top of the list,
 *  and hence will be the handler to be called first.
 *
 *  @return Returns SNMPERR_SUCCESS or SNMP_ERR_* error code.
 *
 *  @see netsnmp_create_handler_registration()
 *  @see netsnmp_inject_handler_before()
 */
int
netsnmp_inject_handler(netsnmp_handler_registration *reginfo,
                       netsnmp_mib_handler *handler)
{
    return netsnmp_inject_handler_before(reginfo, handler, NULL);
}

/** Calls a MIB handlers chain, starting with specific handler.
 *  The given arguments and MIB handler are checked
 *  for sanity, then the handlers are called, one by one,
 *  until next handler is NULL.
 *
 *  @return Returns SNMPERR_SUCCESS or SNMP_ERR_* error code.
 */
NETSNMP_INLINE int
netsnmp_call_handler(netsnmp_mib_handler *next_handler,
                     netsnmp_handler_registration *reginfo,
                     netsnmp_agent_request_info *reqinfo,
                     netsnmp_request_info *requests)
{
    Netsnmp_Node_Handler *nh;
    int             ret;

    if (next_handler == NULL || reginfo == NULL || reqinfo == NULL ||
        requests == NULL) {
        snmp_log(LOG_ERR, "netsnmp_call_handler() called illegally\n");
        netsnmp_assert(next_handler != NULL);
        netsnmp_assert(reqinfo != NULL);
        netsnmp_assert(reginfo != NULL);
        netsnmp_assert(requests != NULL);
        return SNMP_ERR_GENERR;
    }

    do {
        nh = next_handler->access_method;
        if (!nh) {
            if (next_handler->next) {
                snmp_log(LOG_ERR, "no access method specified in handler %s.",
                         next_handler->handler_name);
                return SNMP_ERR_GENERR;
            }
            /*
             * The final handler registration in the chain may well not need
             * to include a handler routine, if the processing of this object
             * is handled completely by the agent toolkit helpers.
             */
            return SNMP_ERR_NOERROR;
        }

        DEBUGMSGTL(("handler:calling", "calling handler %s for mode %s\n",
                    next_handler->handler_name,
                    se_find_label_in_slist("agent_mode", reqinfo->mode)));

        /*
         * XXX: define acceptable return statuses
         */
        ret = (*nh) (next_handler, reginfo, reqinfo, requests);

        DEBUGMSGTL(("handler:returned", "handler %s returned %d\n",
                    next_handler->handler_name, ret));

        if (! (next_handler->flags & MIB_HANDLER_AUTO_NEXT))
            break;

        /*
         * did handler signal that it didn't want auto next this time around?
         */
        if(next_handler->flags & MIB_HANDLER_AUTO_NEXT_OVERRIDE_ONCE) {
            next_handler->flags &= ~MIB_HANDLER_AUTO_NEXT_OVERRIDE_ONCE;
            break;
        }

        next_handler = next_handler->next;

    } while(next_handler);

    return ret;
}

/** @private
 *  Calls all the MIB Handlers in registration struct for a given mode.
 *
 *  @return Returns SNMPERR_SUCCESS or SNMP_ERR_* error code.
 */
int
netsnmp_call_handlers(netsnmp_handler_registration *reginfo,
                      netsnmp_agent_request_info *reqinfo,
                      netsnmp_request_info *requests)
{
    netsnmp_request_info *request;
    int             status;

    if (reginfo == NULL || reqinfo == NULL || requests == NULL) {
        snmp_log(LOG_ERR, "netsnmp_call_handlers() called illegally\n");
        netsnmp_assert(reqinfo != NULL);
        netsnmp_assert(reginfo != NULL);
        netsnmp_assert(requests != NULL);
        return SNMP_ERR_GENERR;
    }

    if (reginfo->handler == NULL) {
        snmp_log(LOG_ERR, "no handler specified.");
        return SNMP_ERR_GENERR;
    }

    switch (reqinfo->mode) {
    case MODE_GETBULK:
    case MODE_GET:
    case MODE_GETNEXT:
        if (!(reginfo->modes & HANDLER_CAN_GETANDGETNEXT))
            return SNMP_ERR_NOERROR;    /* legal */
        break;

#ifndef NETSNMP_NO_WRITE_SUPPORT
    case MODE_SET_RESERVE1:
    case MODE_SET_RESERVE2:
    case MODE_SET_ACTION:
    case MODE_SET_COMMIT:
    case MODE_SET_FREE:
    case MODE_SET_UNDO:
        if (!(reginfo->modes & HANDLER_CAN_SET)) {
            for (; requests; requests = requests->next) {
                netsnmp_set_request_error(reqinfo, requests,
                                          SNMP_ERR_NOTWRITABLE);
            }
            return SNMP_ERR_NOERROR;
        }
        break;
#endif /* NETSNMP_NO_WRITE_SUPPORT */

    default:
        snmp_log(LOG_ERR, "unknown mode in netsnmp_call_handlers! bug!\n");
        return SNMP_ERR_GENERR;
    }
    DEBUGMSGTL(("handler:calling", "main handler %s\n",
                reginfo->handler->handler_name));

    for (request = requests ; request; request = request->next) {
        request->processed = 0;
    }

    status = netsnmp_call_handler(reginfo->handler, reginfo, reqinfo, requests);

    return status;
}

/** @private
 *  Calls the next MIB handler in the chain, after the current one.
 *  The given arguments and MIB handler are checked
 *  for sanity, then the next handler is called.
 *
 *  @return Returns SNMPERR_SUCCESS or SNMP_ERR_* error code.
 */
NETSNMP_INLINE int
netsnmp_call_next_handler(netsnmp_mib_handler *current,
                          netsnmp_handler_registration *reginfo,
                          netsnmp_agent_request_info *reqinfo,
                          netsnmp_request_info *requests)
{

    if (current == NULL || reginfo == NULL || reqinfo == NULL ||
        requests == NULL) {
        snmp_log(LOG_ERR, "netsnmp_call_next_handler() called illegally\n");
        netsnmp_assert(current != NULL);
        netsnmp_assert(reginfo != NULL);
        netsnmp_assert(reqinfo != NULL);
        netsnmp_assert(requests != NULL);
        return SNMP_ERR_GENERR;
    }

    return netsnmp_call_handler(current->next, reginfo, reqinfo, requests);
}

/** @private
 *  Calls the next MIB handler in the chain, after the current one.
 *  The given arguments and MIB handler are not validated before
 *  the call, only request is checked.
 *
 *  @return Returns SNMPERR_SUCCESS or SNMP_ERR_* error code.
 */
netsnmp_feature_child_of(netsnmp_call_next_handler_one_request,netsnmp_unused);
#ifndef NETSNMP_FEATURE_REMOVE_NETSNMP_CALL_NEXT_HANDLER_ONE_REQUEST
NETSNMP_INLINE int
netsnmp_call_next_handler_one_request(netsnmp_mib_handler *current,
                                      netsnmp_handler_registration *reginfo,
                                      netsnmp_agent_request_info *reqinfo,
                                      netsnmp_request_info *requests)
{
    netsnmp_request_info *request;
    int ret;
    
    if (!requests) {
        snmp_log(LOG_ERR, "netsnmp_call_next_handler_ONE_REQUEST() called illegally\n");
        netsnmp_assert(requests != NULL);
        return SNMP_ERR_GENERR;
    }

    request = requests->next;
    requests->next = NULL;
    ret = netsnmp_call_handler(current->next, reginfo, reqinfo, requests);
    requests->next = request;
    return ret;
}
#endif /* NETSNMP_FEATURE_REMOVE_NETSNMP_CALL_NEXT_HANDLER_ONE_REQUEST */

/** Deallocates resources associated with a given handler.
 *  The handler is removed from chain and then freed.
 *  After calling this function, the handler pointer is invalid
 *  and should be set to NULL.
 *
 *  @param handler is the MIB Handler to be freed
 */
void
netsnmp_handler_free(netsnmp_mib_handler *handler)
{
    if (handler != NULL) {
        if (handler->next != NULL) {
            /** make sure we aren't pointing to ourselves.  */
            netsnmp_assert(handler != handler->next); /* bugs caught: 1 */
            netsnmp_handler_free(handler->next);
            handler->next = NULL;
        }
        if ((handler->myvoid != NULL) && (handler->data_free != NULL))
        {
            handler->data_free(handler->myvoid);
        }
        SNMP_FREE(handler->handler_name);
        SNMP_FREE(handler);
    }
}

/** Duplicates a MIB handler and all subsequent handlers.
 *  Creates a copy of all data in given handlers chain.
 *
 *  @param handler is the MIB Handler to be duplicated
 *
 *  @return Returns a pointer to the complete copy,
 *         or NULL if any problem occured.
 *
 * @see _clone_handler()
 */
netsnmp_mib_handler *
netsnmp_handler_dup(netsnmp_mib_handler *handler)
{
    netsnmp_mib_handler *h = NULL;

    if (!handler)
        goto err;

    h = _clone_handler(handler);
    if (!h)
        goto err;

    /*
     * Providing a clone function without a free function is asking for
     * memory leaks, and providing a free function without clone function
     * is asking for memory corruption. Hence the log statement below.
     */
    if (!!handler->data_clone != !!handler->data_free)
        snmp_log(LOG_ERR, "data_clone / data_free inconsistent (%s)\n",
                 handler->handler_name);
    if (handler->myvoid && handler->data_clone) {
        h->myvoid = handler->data_clone(handler->myvoid);
        if (!h->myvoid)
            goto err;
    } else
        h->myvoid = handler->myvoid;
    h->data_clone = handler->data_clone;
    h->data_free = handler->data_free;

    if (handler->next != NULL) {
        h->next = netsnmp_handler_dup(handler->next);
        if (!h->next)
            goto err;
        h->next->prev = h;
    }
    h->prev = NULL;
    return h;

err:
    netsnmp_handler_free(h);
    return NULL;
}

/** Free resources associated with a handler registration object.
 *  The registration object and all MIB Handlers in the chain are
 *  freed. When the function ends, given pointer is no longer valid
 *  and should be set to NULL.
 *
 *  @param reginfo is the handler registration object to be freed
 */
void
netsnmp_handler_registration_free(netsnmp_handler_registration *reginfo)
{
    if (reginfo != NULL) {
        netsnmp_handler_free(reginfo->handler);
        SNMP_FREE(reginfo->handlerName);
        SNMP_FREE(reginfo->contextName);
        SNMP_FREE(reginfo->rootoid);
        reginfo->rootoid_len = 0;
        SNMP_FREE(reginfo);
    }
}

/** Duplicates handler registration object and all subsequent handlers.
 *  Creates a copy of the handler registration object and all its data.
 *
 *  @param reginfo is the handler registration object to be duplicated
 *
 *  @return Returns a pointer to the complete copy,
 *         or NULL if any problem occured.
 *
 * @see netsnmp_handler_dup()
 */
netsnmp_handler_registration *
netsnmp_handler_registration_dup(netsnmp_handler_registration *reginfo)
{
    netsnmp_handler_registration *r = NULL;

    if (reginfo == NULL)
        return NULL;

    r = calloc(1, sizeof(netsnmp_handler_registration));
    if (!r)
        return r;
    r->modes = reginfo->modes;
    r->priority = reginfo->priority;
    r->range_subid = reginfo->range_subid;
    r->timeout = reginfo->timeout;
    r->range_ubound = reginfo->range_ubound;
    r->rootoid_len = reginfo->rootoid_len;

    if (reginfo->handlerName != NULL) {
        r->handlerName = strdup(reginfo->handlerName);
        if (r->handlerName == NULL)
            goto err;
    }

    if (reginfo->contextName != NULL) {
        r->contextName = strdup(reginfo->contextName);
        if (r->contextName == NULL)
            goto err;
    }

    if (reginfo->rootoid != NULL) {
        /*
         * + 1 to make the following code safe:
         * reginfo->rootoid[reginfo->rootoid_len++] = 0;
         * See also netsnmp_scalar_helper_handler().
         */
        r->rootoid = malloc((reginfo->rootoid_len + 1) * sizeof(oid));
        if (r->rootoid == NULL)
            goto err;
        memcpy(r->rootoid, reginfo->rootoid,
               reginfo->rootoid_len * sizeof(oid));
    }

    r->handler = netsnmp_handler_dup(reginfo->handler);
    if (r->handler == NULL)
        goto err;
    return r;

err:
    netsnmp_handler_registration_free(r);
    return NULL;
}

/** Creates a cache of information which can be saved for future reference.
 *  The cache is filled with pointers from parameters. Note that
 *  the input structures are not duplicated, but put directly into
 *  the new cache struct.
 *  Use netsnmp_handler_check_cache() later to make sure it's still
 *  valid before referencing it in the future.
 *
 * @see netsnmp_handler_check_cache()
 * @see netsnmp_free_delegated_cache()
 */
NETSNMP_INLINE netsnmp_delegated_cache *
netsnmp_create_delegated_cache(netsnmp_mib_handler *handler,
                               netsnmp_handler_registration *reginfo,
                               netsnmp_agent_request_info *reqinfo,
                               netsnmp_request_info *requests,
                               void *localinfo)
{
    netsnmp_delegated_cache *ret;

    ret = SNMP_MALLOC_TYPEDEF(netsnmp_delegated_cache);
    if (ret) {
        ret->transaction_id = reqinfo->asp->pdu->transid;
        ret->handler = handler;
        ret->reginfo = reginfo;
        ret->reqinfo = reqinfo;
        ret->requests = requests;
        ret->localinfo = localinfo;
    }
    return ret;
}

/** Check if a given delegated cache is still valid.
 *  The cache is valid if it's a part of transaction
 *  (ie, the agent still considers it to be an outstanding request).
 *
 *  @param dcache is the delegated cache to be checked.
 *
 *  @return Returns the cache pointer if it is still valid, NULL otherwise.
 *
 * @see netsnmp_create_delegated_cache()
 * @see netsnmp_free_delegated_cache()
 */
NETSNMP_INLINE netsnmp_delegated_cache *
netsnmp_handler_check_cache(netsnmp_delegated_cache *dcache)
{
    if (!dcache)
        return dcache;

    if (netsnmp_check_transaction_id(dcache->transaction_id) ==
        SNMPERR_SUCCESS)
        return dcache;

    return NULL;
}

/** Free a cache once it's no longer used.
 *  Deletes the data allocated by netsnmp_create_delegated_cache().
 *  Structures which were not allocated by netsnmp_create_delegated_cache()
 *  are not freed (pointers are dropped).
 *
 *  @param dcache is the delegated cache to be freed.
 *
 * @see netsnmp_create_delegated_cache()
 * @see netsnmp_handler_check_cache()
 */
NETSNMP_INLINE void
netsnmp_free_delegated_cache(netsnmp_delegated_cache *dcache)
{
    /*
     * right now, no extra data is there that needs to be freed 
     */
    if (dcache)
        SNMP_FREE(dcache);

    return;
}


#ifndef NETSNMP_FEATURE_REMOVE_HANDLER_MARK_REQUESTS_AS_DELEGATED
/** Sets a list of requests as delegated or not delegated.
 *  Sweeps through given chain of requests and sets 'delegated'
 *  flag accordingly to the isdelegaded parameter.
 *
 *  @param requests Request list.
 *  @param isdelegated New value of the 'delegated' flag.
 */
void
netsnmp_handler_mark_requests_as_delegated(netsnmp_request_info *requests,
                                           int isdelegated)
{
    while (requests) {
        requests->delegated = isdelegated;
        requests = requests->next;
    }
}
#endif /* NETSNMP_FEATURE_REMOVE_HANDLER_MARK_REQUESTS_AS_DELEGATED */

/** Adds data from node list to the request information structure.
 *  Data in the request can be later extracted and used by submodules.
 *
 * @param request Destination request information structure.
 *
 * @param node The data to be added to the linked list
 *             request->parent_data.
 *
 * @see netsnmp_request_remove_list_data()
 * @see netsnmp_request_get_list_data()
 */
NETSNMP_INLINE void
netsnmp_request_add_list_data(netsnmp_request_info *request,
                              netsnmp_data_list *node)
{
    if (request) {
        if (request->parent_data)
            netsnmp_add_list_data(&request->parent_data, node);
        else
            request->parent_data = node;
    }
}

/** Removes all data from a request.
 *
 * @param request the netsnmp request info structure
 *
 * @param name this is the name of the previously added data
 *
 * @return 0 on successful find-and-delete, 1 otherwise.
 *
 * @see netsnmp_request_add_list_data()
 * @see netsnmp_request_get_list_data()
 */
NETSNMP_INLINE int
netsnmp_request_remove_list_data(netsnmp_request_info *request,
                                 const char *name)
{
    if ((NULL == request) || (NULL ==request->parent_data))
        return 1;

    return netsnmp_remove_list_node(&request->parent_data, name);
}

/** Extracts data from a request.
 *  Retrieves data that was previously added to the request,
 *  usually by a parent module.
 *
 * @param request Source request information structure.
 *
 * @param name Used to compare against the request->parent_data->name value;
 *             if a match is found, request->parent_data->data is returned.
 *
 * @return Gives a void pointer(request->parent_data->data); NULL is
 *         returned if source data is NULL or the object is not found.
 *
 * @see netsnmp_request_add_list_data()
 * @see netsnmp_request_remove_list_data()
 */
void    *
netsnmp_request_get_list_data(netsnmp_request_info *request,
                              const char *name)
{
    if (request)
        return netsnmp_get_list_data(request->parent_data, name);
    return NULL;
}

/** Free the extra data stored in a request.
 *  Deletes the data in given request only. Other chain items
 *  are unaffected.
 *
 * @param request Request information structure to be modified.
 *
 * @see netsnmp_request_add_list_data()
 * @see netsnmp_free_list_data()
 */
NETSNMP_INLINE void
netsnmp_free_request_data_set(netsnmp_request_info *request)
{
    if (request)
        netsnmp_free_list_data(request->parent_data);
}

/** Free the extra data stored in a bunch of requests.
 *  Deletes all data in the chain inside request.
 *
 * @param request Request information structure to be modified.
 *
 * @see netsnmp_request_add_list_data()
 * @see netsnmp_free_request_data_set()
 */
NETSNMP_INLINE void
netsnmp_free_request_data_sets(netsnmp_request_info *request)
{
    if (request && request->parent_data) {
        netsnmp_free_all_list_data(request->parent_data);
        request->parent_data = NULL;
    }
}

/** Returns a MIB handler from a chain based on the name.
 *
 * @param reginfo Handler registration struct which contains the chain.
 *
 * @param name Target MIB Handler name string. The name is case
 *        sensitive.
 *
 * @return The MIB Handler is returned, or NULL if not found.
 *
 * @see netsnmp_request_add_list_data()
 */
netsnmp_mib_handler *
netsnmp_find_handler_by_name(netsnmp_handler_registration *reginfo,
                             const char *name)
{
    netsnmp_mib_handler *it;
    if (reginfo == NULL || name == NULL )
        return NULL;
    for (it = reginfo->handler; it; it = it->next) {
        if (strcmp(it->handler_name, name) == 0) {
            return it;
        }
    }
    return NULL;
}

/** Returns a handler's void pointer from a chain based on the name.
 *
 * @warning The void pointer data may change as a handler evolves.
 *        Handlers should really advertise some function for you
 *        to use instead.
 *
 * @param reginfo Handler registration struct which contains the chain.
 *
 * @param name Target MIB Handler name string. The name is case
 *        sensitive.
 *
 * @return The MIB Handler's void * pointer is returned,
 *        or NULL if the handler is not found.
 *
 * @see netsnmp_find_handler_by_name()
 */
void           *
netsnmp_find_handler_data_by_name(netsnmp_handler_registration *reginfo,
                                  const char *name)
{
    netsnmp_mib_handler *it = netsnmp_find_handler_by_name(reginfo, name);
    if (it)
        return it->myvoid;
    return NULL;
}

/** @private
 *  Clones a MIB Handler with its basic properties.
 *  Creates a copy of the given MIB Handler. Copies name, flags and
 *  access methods only; not myvoid.
 *
 *  @see netsnmp_handler_dup()
 */
static netsnmp_mib_handler *
_clone_handler(netsnmp_mib_handler *it)
{
    netsnmp_mib_handler *dup;

    if(NULL == it)
        return NULL;

    dup = netsnmp_create_handler(it->handler_name, it->access_method);
    if(NULL != dup)
        dup->flags = it->flags;

    return dup;
}

static netsnmp_data_list *handler_reg = NULL;

void
handler_free_callback(void *handler)
{
    netsnmp_handler_free((netsnmp_mib_handler *)handler);
}

/** Registers a given handler by name, so that it can be found easily later.
 *  Pointer to the handler is put into a list where it can be easily located
 *  at any time.
 *
 * @param name Name string to be associated with the handler.
 *
 * @param handler Pointer the MIB Handler.
 *
 * @see netsnmp_clear_handler_list()
 */
void
netsnmp_register_handler_by_name(const char *name,
                                 netsnmp_mib_handler *handler)
{
    netsnmp_add_list_data(&handler_reg,
                          netsnmp_create_data_list(name, (void *) handler,
                                                   handler_free_callback));
    DEBUGMSGTL(("handler_registry", "registering helper %s\n", name));
}

/** Clears the entire MIB Handlers registration list.
 *  MIB Handlers registration list is used to access any MIB Handler by
 *  its name. The function frees the list memory and sets pointer to NULL.
 *  Instead of calling this function directly, use shutdown_agent().
 *
 *  @see shutdown_agent()
 *  @see netsnmp_register_handler_by_name()
 */
void
netsnmp_clear_handler_list(void)
{
    DEBUGMSGTL(("agent_handler", "netsnmp_clear_handler_list() called\n"));
    netsnmp_free_all_list_data(handler_reg);
    handler_reg = NULL;
}

/** @private
 *  Injects a handler into a subtree, peers and children when a given
 *  subtrees name matches a passed in name.
 */
void
netsnmp_inject_handler_into_subtree(netsnmp_subtree *tp, const char *name,
                                    netsnmp_mib_handler *handler,
                                    const char *before_what)
{
    netsnmp_subtree *tptr;
    netsnmp_mib_handler *mh;

    for (tptr = tp; tptr != NULL; tptr = tptr->next) {
        /*  if (tptr->children) { 
              netsnmp_inject_handler_into_subtree(tptr->children,name,handler);
	    }   */
        if (strcmp(tptr->label_a, name) == 0) {
            DEBUGMSGTL(("injectHandler", "injecting handler %s into %s\n",
                        handler->handler_name, tptr->label_a));
            netsnmp_inject_handler_before(tptr->reginfo, _clone_handler(handler),
                                          before_what);
        } else if (tptr->reginfo != NULL &&
		   tptr->reginfo->handlerName != NULL &&
                   strcmp(tptr->reginfo->handlerName, name) == 0) {
            DEBUGMSGTL(("injectHandler", "injecting handler into %s/%s\n",
                        tptr->label_a, tptr->reginfo->handlerName));
            netsnmp_inject_handler_before(tptr->reginfo, _clone_handler(handler),
                                          before_what);
        } else if (tptr->reginfo != NULL) {
            for (mh = tptr->reginfo->handler; mh != NULL; mh = mh->next) {
                if (mh->handler_name && strcmp(mh->handler_name, name) == 0) {
                    DEBUGMSGTL(("injectHandler", "injecting handler into %s\n",
                                tptr->label_a));
                    netsnmp_inject_handler_before(tptr->reginfo,
                                                  _clone_handler(handler),
                                                  before_what);
                    break;
                } else {
                    DEBUGMSGTL(("injectHandler",
                                "not injecting handler into %s\n",
                                mh->handler_name));
                }
            }
        }
    }
}

static int      doneit = 0;
/** @private
 *  Parses the "injectHandler" token line.
 */
void
parse_injectHandler_conf(const char *token, char *cptr)
{
    char            handler_to_insert[256], reg_name[256];
    subtree_context_cache *stc;
    netsnmp_mib_handler *handler;

    /*
     * XXXWWW: ensure instead that handler isn't inserted twice 
     */
    if (doneit)                 /* we only do this once without restart the agent */
        return;

    cptr = copy_nword(cptr, handler_to_insert, sizeof(handler_to_insert));
    handler = (netsnmp_mib_handler*)netsnmp_get_list_data(handler_reg, handler_to_insert);
    if (!handler) {
	netsnmp_config_error("no \"%s\" handler registered.",
			     handler_to_insert);
        return;
    }

    if (!cptr) {
        config_perror("no INTONAME specified.  Can't do insertion.");
        return;
    }
    cptr = copy_nword(cptr, reg_name, sizeof(reg_name));

    for (stc = get_top_context_cache(); stc; stc = stc->next) {
        DEBUGMSGTL(("injectHandler", "Checking context tree %s (before=%s)\n",
                    stc->context_name, (cptr)?cptr:"null"));
        netsnmp_inject_handler_into_subtree(stc->first_subtree, reg_name,
                                            handler, cptr);
    }
}

/** @private
 *  Callback to ensure injectHandler parser doesn't do things twice.
 *  @todo replace this with a method to check the handler chain instead.
 */
static int
handler_mark_inject_handler_done(int majorID, int minorID,
                    void *serverarg, void *clientarg)
{
    doneit = 1;
    return 0;
}

/** @private
 *  Registers the injectHandle parser token.
 *  Used in init_agent_read_config().
 *
 *  @see init_agent_read_config()
 */
void
netsnmp_init_handler_conf(void)
{
    snmpd_register_config_handler("injectHandler",
                                  parse_injectHandler_conf,
                                  NULL, "injectHandler NAME INTONAME [BEFORE_OTHER_NAME]");
    snmp_register_callback(SNMP_CALLBACK_LIBRARY,
                           SNMP_CALLBACK_POST_READ_CONFIG,
                           handler_mark_inject_handler_done, NULL);

    se_add_pair_to_slist("agent_mode", strdup("GET"), MODE_GET);
    se_add_pair_to_slist("agent_mode", strdup("GETNEXT"), MODE_GETNEXT);
    se_add_pair_to_slist("agent_mode", strdup("GETBULK"), MODE_GETBULK);
#ifndef NETSNMP_NO_WRITE_SUPPORT
    se_add_pair_to_slist("agent_mode", strdup("SET_BEGIN"),
                         MODE_SET_BEGIN);
    se_add_pair_to_slist("agent_mode", strdup("SET_RESERVE1"),
                         MODE_SET_RESERVE1);
    se_add_pair_to_slist("agent_mode", strdup("SET_RESERVE2"),
                         MODE_SET_RESERVE2);
    se_add_pair_to_slist("agent_mode", strdup("SET_ACTION"),
                         MODE_SET_ACTION);
    se_add_pair_to_slist("agent_mode", strdup("SET_COMMIT"),
                         MODE_SET_COMMIT);
    se_add_pair_to_slist("agent_mode", strdup("SET_FREE"), MODE_SET_FREE);
    se_add_pair_to_slist("agent_mode", strdup("SET_UNDO"), MODE_SET_UNDO);

    se_add_pair_to_slist("babystep_mode", strdup("pre-request"),
                         MODE_BSTEP_PRE_REQUEST);
    se_add_pair_to_slist("babystep_mode", strdup("object_lookup"),
                         MODE_BSTEP_OBJECT_LOOKUP);
    se_add_pair_to_slist("babystep_mode", strdup("check_value"),
                         MODE_BSTEP_CHECK_VALUE);
    se_add_pair_to_slist("babystep_mode", strdup("row_create"),
                         MODE_BSTEP_ROW_CREATE);
    se_add_pair_to_slist("babystep_mode", strdup("undo_setup"),
                         MODE_BSTEP_UNDO_SETUP);
    se_add_pair_to_slist("babystep_mode", strdup("set_value"),
                         MODE_BSTEP_SET_VALUE);
    se_add_pair_to_slist("babystep_mode", strdup("check_consistency"),
                         MODE_BSTEP_CHECK_CONSISTENCY);
    se_add_pair_to_slist("babystep_mode", strdup("undo_set"),
                         MODE_BSTEP_UNDO_SET);
    se_add_pair_to_slist("babystep_mode", strdup("commit"),
                         MODE_BSTEP_COMMIT);
    se_add_pair_to_slist("babystep_mode", strdup("undo_commit"),
                         MODE_BSTEP_UNDO_COMMIT);
    se_add_pair_to_slist("babystep_mode", strdup("irreversible_commit"),
                         MODE_BSTEP_IRREVERSIBLE_COMMIT);
    se_add_pair_to_slist("babystep_mode", strdup("undo_cleanup"),
                         MODE_BSTEP_UNDO_CLEANUP);
    se_add_pair_to_slist("babystep_mode", strdup("post_request"),
                         MODE_BSTEP_POST_REQUEST);
    se_add_pair_to_slist("babystep_mode", strdup("original"), 0xffff);
#endif /* NETSNMP_NO_WRITE_SUPPORT */

    /*
     * xxx-rks: hmmm.. will this work for modes which are or'd together?
     *          I'm betting not...
     */
    se_add_pair_to_slist("handler_can_mode", strdup("GET/GETNEXT"),
                         HANDLER_CAN_GETANDGETNEXT);
    se_add_pair_to_slist("handler_can_mode", strdup("SET"),
                         HANDLER_CAN_SET);
    se_add_pair_to_slist("handler_can_mode", strdup("GETBULK"),
                         HANDLER_CAN_GETBULK);
    se_add_pair_to_slist("handler_can_mode", strdup("BABY_STEP"),
                         HANDLER_CAN_BABY_STEP);
}

/** @} */
