/**
 * @brief winExtDLL Net-SNMP agent extension module.
 *
 * Copyright (c) 2006-2009 Alex Burger.
 * Copyright (c) 2009-2010 Bart Van Assche <bart.vanassche@gmail.com>.
 *
 * This Net-SNMP agent extension module loads Windows SNMP Extension Agent
 * DLLs in the Net-SNMP agent. Not only extension DLLs provided with Windows
 * (e.g. hostmib.dll) but also third-party extension DLLs are supported. This
 * allows Net-SNMP to be a replacement for the Windows SNMP service, and makes
 * it possible to use the SNMPv3 protocol.
 *
 * @see See also <a href="http://msdn.microsoft.com/en-us/library/aa378988(VS.85).aspx">SNMP Functions</a>
 *   for more information about Microsoft's SNMP Extension Agent API.
 *
 * @note In order to use this agent extension module, the Windows SNMP service
 *   must be installed first and must be disabled. Installing the Windows SNMP
 *   service is the only way to install the Windows Extension DLLs and to make
 *   sure that information about these DLLs is present in the registry.
 *
 * @note All Windows extension DLLs are loaded during startup of the Net-SNMP
 *   service. The Net-SNMP service must be restarted to load new modules. This
 *   extension is NOT for dynamically loading Net-SNMP extensions.
 *
 *
 * History:
 * - 2010/03/19:
 *    * Multi-varbind set request PDUs are now handled correctly.
 *    * If loading an extension DLL fails, the reason why this failed is now
 *      logged.
 *    * Fixed a memory leak that occurred when SnmpExtensionQuery() or
 *      SnmpExtensionQueryEx() failed while processing an SNMP PDU. Note:
 *      occurrence of an SNMP error does not make these functions fail, and
 *      it is not yet known whether or not it was possible to trigger this
 *      memory leak.
 * - 2010/03/17: Fixed bug 2971257. Multi-varbind getNext requests with OIDs
 *     in reverse lexicographical order are again processed correctly.
 * - 2010/01/22: Compiles now with MinGW too.
 * - 2009/12/11:
 *   * The value of sysUpTime.0 reported by inetmib1.dll is now correct.
 *   * A linkUp or linkDown trap is now sent after the status of a network
 *     interface has changed.
 * - 2009/03/26: 
 *   * Removed several artificial limits. Result: more than 100 SNMP extension
 *     DLLs can now be loaded simultaneously and more than 100 OID ranges can
 *     now be registered. Loading e.g. the Dell OpenManage SNMP extension DLL
 *     does no longer crash Net-SNMP. 
 *   * Number of OID ranges registered during startup is now logged.
 *   * It is no longer attempted to free the Broadcom SNMP extension DLLs
 *     bcmif.dll and baspmgnt.dll since doing so triggers a deadlock.
 *   * Added support for reregistration of an OID prefix. As an example, both
 *     both Microsoft's inetmib1.dll and the Eicon Diva divasnmpx.dll register
 *     the OID prefix iso.org.dod.internet.mgmt.mib-2.interfaces
 *     (.1.3.6.1.2.1.2). WinExtDLL will process OIDs with this prefix by using
 *     the handler that was registered last for the OID prefix. A message will
 *     be logged indicating that a handler has been replaced.
 * - 2009/03/10:
 *   * Fixed several bugs in var_winExtDLL(): looking up extension DLL info
 *     based on the OID in a varbind is wrong. It does happen during GetNext
 *     processing that Net-SNMP passes intentionally varbinds to a handler
 *     with OIDs that are outside the range registered by the handler. Fixed
 *     this by filling in a pointer to the extension DLL info in
 *     netsnmp_mib_handler::myvoid and by using that information in the
 *     var_winExtDLL() handler function.
 *   * SetRequest PDUs are now passed once to an extension DLL instead of
 *     four times.
 *   * The error status and error index of a multi-varbind set request is now
 *     filled in correctly.
 *   * Added support for the SNMP extension DLL three-phase SNMP set.
 *   * Made traps SNMPv2 compliant by adding the sysUpTime.0 varbind.
 *   * The varbind list generated by extension DLLs for e.g. linkUp and
 *     linkDown traps is now passed to Net-SNMP. Previously this varbind list
 *     was discarded for generic traps.
 *   * Fixed memory leaks triggered by Get and GetNext PDU processing.
 *   * Added missing RegCloseKey() calls.
 *   * Added shutdown function shutdown_winExtDLL().
 *   * Replaced #include <cstdio> by #include <stdio.h> such that this source
 *     file compiles with Visual Studio 2005.
 *   * Removed many unused local variables.
 *   * Fixed several other compiler warnings.
 * - 2006/09/09: creation of this file.
 */

#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-features.h>
#include <net-snmp/agent/mib_module_config.h>

#ifdef USING_WINEXTDLL_MODULE

#include <net-snmp/types.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <windows.h>
#include <winerror.h>
#include "../../win32/Snmp-winExtDLL.h"

#include <net-snmp/net-snmp-includes.h>
#include <net-snmp/library/snmp_assert.h>
#include <net-snmp/agent/net-snmp-agent-includes.h>
#include "util_funcs.h"
#include "winExtDLL.h"

netsnmp_feature_require(oid_is_subtree);


#define MAX_VALUE_NAME          16383
#define MS_ASN_UINTEGER32 MS_ASN_UNSIGNED32


typedef         BOOL(WINAPI *
                     PFNSNMPEXTENSIONINIT) (DWORD dwUpTimeReference,
                                            HANDLE * phSubagentTrapEvent,
                                            AsnObjectIdentifier *
                                            pFirstSupportedRegion);

typedef         BOOL(WINAPI *
                     PFNSNMPEXTENSIONINITEX) (AsnObjectIdentifier *
                                              pNextSupportedRegion);

typedef         BOOL(WINAPI *
                     PFNSNMPEXTENSIONMONITOR) (LPVOID pAgentMgmtData);

typedef         BOOL(WINAPI * PFNSNMPEXTENSIONQUERY) (BYTE bPduType,
                                                      SnmpVarBindList *
                                                      pVarBindList,
                                                      AsnInteger32 *
                                                      pErrorStatus,
                                                      AsnInteger32 *
                                                      pErrorIndex);

typedef         BOOL(WINAPI * PFNSNMPEXTENSIONQUERYEX) (UINT nRequestType,
                                                        UINT
                                                        nTransactionId,
                                                        SnmpVarBindList *
                                                        pVarBindList,
                                                        AsnOctetString *
                                                        pContextInfo,
                                                        AsnInteger32 *
                                                        pErrorStatus,
                                                        AsnInteger32 *
                                                        pErrorIndex);

typedef         BOOL(WINAPI * PFNSNMPEXTENSIONTRAP) (AsnObjectIdentifier *
                                                     pEnterpriseOid,
                                                     AsnInteger32 *
                                                     pGenericTrapId,
                                                     AsnInteger32 *
                                                     pSpecificTrapId,
                                                     AsnTimeticks *
                                                     pTimeStamp,
                                                     SnmpVarBindList *
                                                     pVarBindList);

typedef         VOID(WINAPI * PFNSNMPEXTENSIONCLOSE) (void);

typedef BOOL (WINAPI *pfIsWow64Process)(HANDLE hProcess, BOOL *Wow64Process);


/**
 * Extensible array, a data structure similar to the C++ STL class
 * std::vector<>.
 */
typedef struct {
    /** Pointer to the memory allocated for the array. */
    void           *p;
    /** Number of bytes occupied by a single element.  */
    size_t          elem_size;
    /** Number of elements that have been allocated.   */
    int             reserved;
    /** Number of elements currently in use.           */
    int             size;
} xarray;

/**
 * Information managed by winExtDLL about Windows SNMP extension DLL's.
 */
typedef struct {
    char           *dll_name;                        /**< Dynamically allocated DLL name. */
    HANDLE          dll_handle;                      /**< DLL handle. */
    PFNSNMPEXTENSIONINIT pfSnmpExtensionInit;
    PFNSNMPEXTENSIONINITEX pfSnmpExtensionInitEx;
    PFNSNMPEXTENSIONCLOSE pfSnmpExtensionClose;
    PFNSNMPEXTENSIONQUERY pfSnmpExtensionQuery;
    PFNSNMPEXTENSIONQUERYEX pfSnmpExtensionQueryEx;
    PFNSNMPEXTENSIONTRAP pfSnmpExtensionTrap;
    HANDLE          subagentTrapEvent;
} winextdll;

/**
 * Information managed by winExtDLL about a single view of a Windows SNMP
 * extension DLL.
 */
typedef struct {
    winextdll      *winextdll_info;
    netsnmp_handler_registration *my_handler;
    oid             name[MAX_OID_LEN];                   /**< OID of this view. */
    size_t          name_length;
} winextdll_view;

/**
 * Per varbind SNMP extension DLL context information for SNMP set operations.
 */
typedef struct context_info_s {
    struct context_info_s *next;
    int             index;
    AsnOctetString  context_info;
} context_info;


/*
 * External function declarations. 
 */
void __declspec(dllimport) WINAPI SnmpSvcInitUptime(void);


/*
 * Local functions declarations. 
 */
static int      basename_equals(const char *path, const char *basename);
static int      register_netsnmp_handler(winextdll_view *
                                         const ext_dll_view_info);
static void     read_extension_dlls_from_registry(void);
static void     read_extension_dlls_from_registry_at(const char *const subkey);
static char    *read_extension_dll_path_from_registry(const TCHAR *);
static void     subagentTrapCheck(unsigned int clientreg, void *clientarg);
static int      var_winExtDLL(netsnmp_mib_handler *handler,
                              netsnmp_handler_registration *reginfo,
                              netsnmp_agent_request_info *reqinfo,
                              netsnmp_request_info *requests);
static int      append_windows_varbind_list(netsnmp_variable_list **
                                            const net_snmp_varbinds,
                                            const SnmpVarBindList *
                                            const win_varbinds);
static int      append_windows_varbind(netsnmp_variable_list **
                                       const net_snmp_varbinds,
                                       const SnmpVarBind *
                                       const win_varbind);
static int      convert_to_windows_varbind_list(SnmpVarBindList *
                                                pVarBindList,
                                                netsnmp_variable_list *
                                                netsnmp_varbinds);
static int      convert_win_snmp_err(const int win_snmp_err);
static winextdll_view *lookup_view_by_oid(oid * const name,
                                          const size_t name_len);
static int      snmp_oid_compare_n_w(const oid * name1, size_t len1,
                                     const UINT * name2, UINT len2);
static int      snmp_oid_compare_w_n(const UINT * name1, UINT len1,
                                     const oid * name2, size_t len2);
static int      netsnmp_oid_is_subtree_n_w(const oid * name1, size_t len1,
                                           const UINT * name2, UINT len2);
static void     copy_oid(oid * const to_name, size_t * const to_name_len,
                         const oid * const from_name,
                         const size_t from_name_len);
static void     copy_oid_n_w(oid * const to_name, size_t * const to_name_len,
                             const UINT * const from_name,
                             const UINT from_name_len);
static UINT    *copy_oid_to_new_windows_oid(AsnObjectIdentifier *
                                            const windows_oid,
                                            const oid * const name,
                                            const size_t name_len);
static int      snmp_set_var_objid_w(netsnmp_variable_list * var,
                                     const UINT * name, UINT name_length);
static netsnmp_variable_list *
snmp_varlist_add_variable_w(netsnmp_variable_list ** varlist,
                            const UINT * name, UINT name_length,
                            u_char type, const void * value, size_t len);
static void     send_trap(const AsnObjectIdentifier * const,
                          const AsnInteger, const AsnInteger,
                          const AsnTimeticks,
                          const SnmpVarBindList * const);
static u_char  *winsnmp_memdup(const void *src, const size_t len);
#if 0
static void     xarray_init(xarray * a, size_t elem_size);
#endif
static void     xarray_destroy(xarray * a);
static void    *xarray_push_back(xarray * a, const void *elem);
#if 0
static void     xarray_erase(xarray * a, void *const elem);
#endif
static void    *xarray_reserve(xarray * a, int reserved);


/*
 * Local variable definitions. 
 */
#define WINEXTDLL(i)            ((winextdll*)s_winextdll.p)[i]
#define WINEXTDLL_VIEW(i)       ((winextdll_view*)s_winextdll_view.p)[i]
#define TRAPEVENT(i)            ((HANDLE*)s_trapevent.p)[i]
#define TRAPEVENT_TO_DLLINFO(i) ((winextdll**)s_trapevent_to_dllinfo.p)[i]
static const oid mibii_system_mib[] = { 1, 3, 6, 1, 2, 1, 1 };
static OSVERSIONINFO s_versioninfo = { sizeof(s_versioninfo) };
static xarray   s_winextdll = { 0, sizeof(winextdll) };
static xarray   s_winextdll_view = { 0, sizeof(winextdll_view) };
static xarray   s_trapevent = { 0, sizeof(HANDLE) };
static xarray   s_trapevent_to_dllinfo = { 0, sizeof(winextdll *) };
static context_info *context_info_head;


/*
 * Function definitions. 
 */

/** Initialize the winExtDLL extension agent. */
void
init_winExtDLL(void)
{
    BOOL            result, is_wow64_process = FALSE;
    int             i;
    uint32_t        uptime_reference;
    pfIsWow64Process IsWow64Process;

    DEBUGMSG(("winExtDLL", "init_winExtDLL started.\n"));

    GetVersionEx(&s_versioninfo);

    IsWow64Process = (pfIsWow64Process)(uintptr_t)
	GetProcAddress(GetModuleHandle("kernel32"), "IsWow64Process");
    if (IsWow64Process)
        (*IsWow64Process)(GetCurrentProcess(), &is_wow64_process);

    SnmpSvcInitUptime();

    read_extension_dlls_from_registry();

    DEBUGMSG(("winExtDLL",
              "init_winExtDLL: found %d extension DLLs in the registry.\n",
              s_winextdll.size));

    xarray_reserve(&s_winextdll, 128);

    /*
     * Load all the DLLs 
     */
    for (i = 0; i < s_winextdll.size; i++) {
        winextdll      *const ext_dll_info = &WINEXTDLL(i);
        AsnObjectIdentifier view;
        winextdll_view  ext_dll_view_info;

        netsnmp_assert(ext_dll_info);
        if (!ext_dll_info->dll_name)
            continue;

        DEBUGMSG(("winExtDLL", "loading DLL %s.\n",
                  ext_dll_info->dll_name));
        ext_dll_info->dll_handle = LoadLibrary(ext_dll_info->dll_name);

        if (ext_dll_info->dll_handle == NULL) {
            const DWORD     dwErrorcode = GetLastError();
            LPTSTR          lpMsgBuf;

            FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
                          FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwErrorcode,
                          MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                          (LPTSTR) & lpMsgBuf, 0, NULL);
            if (lpMsgBuf) {
                LPTSTR          p;

                /*
                 * Remove trailing "\r\n".
                 */
                p = strchr(lpMsgBuf, '\r');
                if (p)
                    *p = '\0';
            }
            snmp_log(LOG_ERR,
                     "init_winExtDLL: could not load SNMP extension"
                     " DLL %s: %s\n",
                     ext_dll_info->dll_name, lpMsgBuf ? lpMsgBuf : "(?)");
            if (lpMsgBuf)
                LocalFree(lpMsgBuf);
            continue;
        }

        /*
         * Store DLL name and functions in s_extension_dll_info array. 
         */
        ext_dll_info->pfSnmpExtensionInit = (PFNSNMPEXTENSIONINIT)(uintptr_t)
            GetProcAddress(ext_dll_info->dll_handle, "SnmpExtensionInit");
        ext_dll_info->pfSnmpExtensionInitEx =
	    (PFNSNMPEXTENSIONINITEX)(uintptr_t)
            GetProcAddress(ext_dll_info->dll_handle,
                           "SnmpExtensionInitEx");
        ext_dll_info->pfSnmpExtensionClose = (PFNSNMPEXTENSIONCLOSE)
            GetProcAddress(ext_dll_info->dll_handle, "SnmpExtensionClose");
        ext_dll_info->pfSnmpExtensionQuery = (PFNSNMPEXTENSIONQUERY)(uintptr_t)
            GetProcAddress(ext_dll_info->dll_handle, "SnmpExtensionQuery");
        ext_dll_info->pfSnmpExtensionQueryEx =
	    (PFNSNMPEXTENSIONQUERYEX)(uintptr_t)
            GetProcAddress(ext_dll_info->dll_handle,
                           "SnmpExtensionQueryEx");
        ext_dll_info->pfSnmpExtensionTrap = (PFNSNMPEXTENSIONTRAP)(uintptr_t)
            GetProcAddress(ext_dll_info->dll_handle, "SnmpExtensionTrap");


        if (ext_dll_info->pfSnmpExtensionQuery == NULL
            && ext_dll_info->pfSnmpExtensionQueryEx == NULL) {
            snmp_log(LOG_ERR,
                     "error in extension DLL %s: SNMP query function missing.\n",
                     ext_dll_info->dll_name);
        }

        /*
         * At least on a 64-bit Windows 7 system invoking SnmpExtensionInit()
         * in the 32-bit version of evntagnt.dll hangs. Also, all queries in
         * lmmib2.dll fail with "generic error" on a 64-bit Windows 7 system.
         * So skip these two DLLs.
         */
        if (s_versioninfo.dwMajorVersion >= 6
            && ((is_wow64_process
                 && basename_equals(ext_dll_info->dll_name, "evntagnt.dll"))
                || basename_equals(ext_dll_info->dll_name, "lmmib2.dll"))) {
            DEBUGMSG(("winExtDLL", "init_winExtDLL: skipped DLL %s.\n",
                      ext_dll_info->dll_name));
            continue;
        }

        /*
         * Init and get first supported view from Windows SNMP extension DLL.
         * Note: although according to the documentation of SnmpExtensionInit()
         * the first argument of this function should be ignored by extension
         * DLLs, passing a correct value for this first argument is necessary
         * to make inetmib1.dll work correctly. Passing zero as the first
         * argument causes inetmib1.dll to report an incorrect value for
         * sysUpTime.0 and also causes the same DLL not to send linkUp or
         * linkDown traps.
         */
        ext_dll_info->subagentTrapEvent = NULL;
        view.idLength = 0;
        view.ids = NULL;
        if (!is_wow64_process && s_versioninfo.dwMajorVersion >= 6)
            uptime_reference = GetTickCount() - 10 * SnmpSvcGetUptime();
        else
            uptime_reference = GetTickCount() / 10;
        result =
            ext_dll_info->pfSnmpExtensionInit(uptime_reference,
                                              &ext_dll_info->
                                              subagentTrapEvent, &view);
        DEBUGMSG(("winExtDLL",
                  "init_winExtDLL: DLL %s initialization result %d\n",
                  ext_dll_info->dll_name, result));

        if (ext_dll_info->subagentTrapEvent != NULL) {
            xarray_push_back(&s_trapevent,
                             &ext_dll_info->subagentTrapEvent);
            xarray_push_back(&s_trapevent_to_dllinfo, &ext_dll_info);
        }

        memset(&ext_dll_view_info, 0, sizeof(ext_dll_view_info));
        ext_dll_view_info.winextdll_info = ext_dll_info;
        DEBUGMSG(("winExtDLL",
                  "init_winExtDLL: DLL %s view length %d\n",
                  ext_dll_info->dll_name, view.idLength));
        if (view.idLength) {
            /*
             * Skip the mib-2 system section on Windows Vista and later because
             * at least on a 64-bit Windows 7 system all queries in that section
             * fail with status "generic error".
             */
            if (s_versioninfo.dwMajorVersion >= 6
                && snmp_oid_compare_w_n(view.ids, view.idLength,
                                        mibii_system_mib,
                                        sizeof(mibii_system_mib) /
                                        sizeof(mibii_system_mib[0])) == 0) {
                DEBUGMSG(("winExtDLL",
                          "init_winExtDLL: skipping system section of DLL %s.\n",
                          ext_dll_info->dll_name));
            } else {
                copy_oid_n_w(ext_dll_view_info.name,
                             &ext_dll_view_info.name_length,
                             view.ids, view.idLength);
                xarray_push_back(&s_winextdll_view, &ext_dll_view_info);
            }
        }

        /*
         * Loop looking for more supported views. 
         */
        while (ext_dll_info->pfSnmpExtensionInitEx
               && ext_dll_info->pfSnmpExtensionInitEx(&view)) {
            DEBUGMSG(("winExtDLL",
                      "init_winExtDLL: DLL %s view length %d\n",
                      ext_dll_info->dll_name, view.idLength));
            memset(&ext_dll_view_info, 0, sizeof(ext_dll_view_info));
            ext_dll_view_info.winextdll_info = ext_dll_info;
            copy_oid_n_w(ext_dll_view_info.name,
                         &ext_dll_view_info.name_length, view.ids,
                         view.idLength);
            xarray_push_back(&s_winextdll_view, &ext_dll_view_info);
        }
    }

    /*
     * Note: since register_netsnmp_handler() writes a pointer to the
     * winextdll_view in one of the Net-SNMP data structures, it is not
     * allowed to move winextdll_view data structures in memory after
     * registration with Net-SNMP. Or: register_snmp_handler() must be called
     * only once it is sure that the size of array s_winextdll_view won't change
     * anymore.
     */
    for (i = 0; i < s_winextdll_view.size; i++)
        register_netsnmp_handler(&WINEXTDLL_VIEW(i));

    DEBUGMSG(("winExtDLL",
              "init_winExtDLL: registered %d OID ranges.\n",
              s_winextdll_view.size));

    /*
     * Let Net-SNMP call subagentTrapCheck() once per second. 
     */
    if (s_trapevent.size)
        snmp_alarm_register(1, SA_REPEAT, subagentTrapCheck, NULL);

    DEBUGMSG(("winExtDLL", "init_winExtDLL finished.\n"));
}

void
shutdown_winExtDLL(void)
{
    int             i;

    DEBUGMSG(("winExtDLL", "shutdown_winExtDLL() started.\n"));

    for (i = s_winextdll_view.size - 1; i >= 0; i--) {
        winextdll_view *const v = &WINEXTDLL_VIEW(i);
        if (v && v->my_handler) {
            DEBUGIF("winExtDLL") {
                DEBUGMSG(("winExtDLL",
                          "unregistering handler for DLL %s and OID prefix ",
                          v->winextdll_info->dll_name));
                DEBUGMSGOID(("winExtDLL", v->name, v->name_length));
                DEBUGMSG(("winExtDLL", " ("));
                DEBUGMSGSUBOID(("winExtDLL", v->name, v->name_length));
                DEBUGMSG(("winExtDLL", ").\n"));
            }
            netsnmp_unregister_handler(v->my_handler);
        }
    }
    xarray_destroy(&s_winextdll_view);

    for (i = s_winextdll.size - 1; i >= 0; i--) {
        winextdll      *const ext_dll_info = &WINEXTDLL(i);
        if (ext_dll_info->dll_handle) {
            if (ext_dll_info->pfSnmpExtensionClose) {
                DEBUGMSG(("winExtDLL", "closing %s.\n",
                          ext_dll_info->dll_name));
                ext_dll_info->pfSnmpExtensionClose();
            }
            /*
             * Freeing the Broadcom SNMP extension libraries triggers
             * a deadlock, so skip bcmif.dll and baspmgnt.dll.
             */
            if (!basename_equals(ext_dll_info->dll_name, "bcmif.dll")
                && !basename_equals(ext_dll_info->dll_name, "baspmgnt.dll")) {
                DEBUGMSG(("winExtDLL", "unloading %s.\n",
                          ext_dll_info->dll_name));
                FreeLibrary(ext_dll_info->dll_handle);
            }
        }
        free(ext_dll_info->dll_name);
    }
    xarray_destroy(&s_winextdll);

    xarray_destroy(&s_trapevent_to_dllinfo);

    xarray_destroy(&s_trapevent);

    DEBUGMSG(("winExtDLL", "shutdown_winExtDLL() finished.\n"));
}

/**
 * Compare the basename of a path with a given string.
 *
 * @return 1 if the basename matches, 0 if not.
 */
static int
basename_equals(const char *path, const char *basename)
{
    const size_t    path_len = strlen(path);
    const size_t    basename_len = strlen(basename);

    netsnmp_assert(strchr(path, '/') == 0);
    netsnmp_assert(strchr(basename, '/') == 0);
    netsnmp_assert(strchr(basename, '\\') == 0);

    return path_len >= basename_len + 1
        && path[path_len - basename_len - 1] == '\\'
        && strcasecmp(path + path_len - basename_len, basename) == 0;
}

/**
 * Register a single OID subtree with Net-SNMP.
 *
 * @return 1 if successful, 0 if not.
 */
int
register_netsnmp_handler(winextdll_view * const ext_dll_view_info)
{
    winextdll      *ext_dll_info;
    winextdll_view *previously_registered_view;

    ext_dll_info = ext_dll_view_info->winextdll_info;

    previously_registered_view
        = lookup_view_by_oid(ext_dll_view_info->name,
                             ext_dll_view_info->name_length);

    if (previously_registered_view) {
        size_t          oid_namelen, outlen;
        char           *oid_name;
        int             buffer_large_enough;

        oid_namelen = 0;
        outlen = 0;
        oid_name = NULL;
        buffer_large_enough =
            sprint_realloc_objid((u_char **) & oid_name, &oid_namelen,
                                 &outlen, 1, ext_dll_view_info->name,
                                 ext_dll_view_info->name_length);
        snmp_log(LOG_INFO, "OID range %s%s: replacing handler %s by %s.\n",
                 oid_name ? oid_name : "",
                 buffer_large_enough ? "" : " [TRUNCATED]",
                 previously_registered_view->winextdll_info->dll_name,
                 ext_dll_view_info->winextdll_info->dll_name);
        if (oid_name)
            free(oid_name);

        previously_registered_view->winextdll_info = ext_dll_info;
        memset(ext_dll_view_info, 0, sizeof(*ext_dll_view_info));
        return 1;
    } else {
        // Create handler registration
        ext_dll_view_info->my_handler
            = netsnmp_create_handler_registration(ext_dll_info->dll_name,
                                                  var_winExtDLL,
                                                  ext_dll_view_info->name,
                                                  ext_dll_view_info->
                                                  name_length,
                                                  HANDLER_CAN_RWRITE);

        if (ext_dll_view_info->my_handler) {
            ext_dll_view_info->my_handler->handler->myvoid =
                ext_dll_view_info;
            if (netsnmp_register_handler(ext_dll_view_info->my_handler)
                == MIB_REGISTERED_OK) {
                DEBUGIF("winExtDLL") {
                    DEBUGMSG(("winExtDLL",
                              "registering handler for DLL %s and OID prefix ",
                              ext_dll_info->dll_name));
                    DEBUGMSGOID(("winExtDLL", ext_dll_view_info->name,
                                 ext_dll_view_info->name_length));
                    DEBUGMSG(("winExtDLL", " ("));
                    DEBUGMSGSUBOID(("winExtDLL", ext_dll_view_info->name,
                                    ext_dll_view_info->name_length));
                    DEBUGMSG(("winExtDLL", ").\n"));
                }
                return 1;
            } else {
                snmp_log(LOG_ERR, "handler registration failed.\n");
                ext_dll_view_info->my_handler = 0;
            }
        } else {
            snmp_log(LOG_ERR, "handler creation failed.\n");
        }
    }

    return 0;
}

/**
 * Allocate SNMP extension DLL context information. Such context information
 * is necessary to allow an extension DLL to process a set request.
 *
 * @param[in] index Varbind index in original PDU.
 *
 * @return NULL if context information for the specified index was already
 *   allocated, and otherwise a pointer to the newly allocated context
 *   information.
 */
static context_info *
alloc_context_info(const int index)
{
    context_info   *p;

    DEBUGMSG(("winExtDLL:context_info", "alloc_context_info(%d)\n",
              index));

    for (p = context_info_head; p; p = p->next) {
        if (p->index == index) {
            netsnmp_assert(FALSE);
            return NULL;
        }
    }

    p = calloc(1, sizeof(context_info));
    p->next = context_info_head;
    context_info_head = p;
    p->index = index;

    return p;
}

/**
 * Deallocate SNMP extension DLL context information.
 *
 * @param[in] index Varbind index in original PDU.
 */
static void
free_context_info(const int index)
{
    context_info  **pprev = &context_info_head;
    context_info   *p;

    DEBUGMSG(("winExtDLL:context_info", "free_context_info(%d)\n", index));

    for (p = context_info_head; p; p = p->next) {
        if (p->index == index) {
            *pprev = p->next;
            free(p);
            break;
        }
        pprev = &p->next;
    }
}

/**
 * Look up SNMP extension DLL context information.
 *
 * @param[in] index Varbind index in original PDU.
 */
static AsnOctetString *
get_context_info(const int index)
{
    context_info   *p;

    DEBUGMSG(("winExtDLL:context_info", "get_context_info(%d)\n", index));

    for (p = context_info_head; p; p = p->next)
        if (p->index == index)
            return &p->context_info;

    netsnmp_assert(FALSE);
    return NULL;
}

/*
 * Translate Net-SNMP request mode into an SnmpExtensionQuery() PDU type
 * or into an SnmpExtensionQueryEx() request type.
 */
static int
get_request_type(int mode, int request_type, UINT *nRequestType)
{
    switch (request_type) {
    case 0:
        /* SnmpExtensionQuery() PDU type */
        switch (mode) {
        case MODE_GET:
            *nRequestType = SNMP_PDU_GET;
            return 1;
        case MODE_GETNEXT:
            *nRequestType = SNMP_PDU_GETNEXT;
            return 1;
        case MODE_SET_RESERVE1:
            return 0;
        case MODE_SET_RESERVE2:
            return 0;
        case MODE_SET_ACTION:
            return 0;
        case MODE_SET_UNDO:
            return 0;
        case MODE_SET_COMMIT:
            *nRequestType = SNMP_PDU_SET;
            return 1;
        case MODE_SET_FREE:
            return 0;
        default:
            DEBUGMSG(("winExtDLL", "internal error: invalid mode %d.\n", mode));
            netsnmp_assert(0);
            return 0;
        }
    case 1:
        /* SnmpExtensionQueryEx() request type */
        switch (mode) {
        case MODE_GET:
            *nRequestType = SNMP_EXTENSION_GET;
            return 1;
        case MODE_GETNEXT:
            *nRequestType = SNMP_EXTENSION_GET_NEXT;
            return 1;
        case MODE_SET_RESERVE1:
            *nRequestType = SNMP_EXTENSION_SET_TEST;
            return 1;
        case MODE_SET_RESERVE2:
            return 0;
        case MODE_SET_ACTION:
            return 0;
        case MODE_SET_UNDO:
            *nRequestType = SNMP_EXTENSION_SET_UNDO;
            return 1;
        case MODE_SET_COMMIT:
            *nRequestType = SNMP_EXTENSION_SET_COMMIT;
            return 1;
        case MODE_SET_FREE:
            *nRequestType = SNMP_EXTENSION_SET_CLEANUP;
            return 1;
        default:
            DEBUGMSG(("winExtDLL", "internal error: invalid mode %d.\n", mode));
            netsnmp_assert(0);
            return 0;
        }
    default:
        DEBUGMSG(("winExtDLL", "internal error: invalid argument %d.\n",
                  request_type));
        netsnmp_assert(0);
        return 0;
    }
}

static int
var_winExtDLL(netsnmp_mib_handler *handler,
              netsnmp_handler_registration *reginfo,
              netsnmp_agent_request_info *reqinfo,
              netsnmp_request_info *requests)
{
    winextdll_view *const ext_dll_view_info = handler->myvoid;
    winextdll      *ext_dll_info;
    netsnmp_request_info *request;
    UINT            nRequestType;
    int             rc;

    netsnmp_assert(ext_dll_view_info);
    ext_dll_info = ext_dll_view_info->winextdll_info;
#if ! defined(NDEBUG)
    netsnmp_assert(ext_dll_view_info ==
           lookup_view_by_oid(reginfo->rootoid, reginfo->rootoid_len));
#endif

    if (ext_dll_info == 0) {
        DEBUGMSG(("winExtDLL",
                  "internal error: no matching extension DLL found.\n"));
        netsnmp_assert(0);
        return SNMP_ERR_GENERR;
    }

    if (!get_request_type(reqinfo->mode, !!ext_dll_info->pfSnmpExtensionQueryEx,
                          &nRequestType)) {
        return SNMP_ERR_NOERROR;
    }

    rc = SNMP_ERR_NOERROR;

    for (request = requests; request; request = request->next) {
        netsnmp_variable_list *varbind;
        SnmpVarBindList win_varbinds;
        AsnInteger32    ErrorStatus;
        AsnInteger32    ErrorIndex;
        BOOL            result;
        BOOL            copy_value;

        memset(&win_varbinds, 0, sizeof(win_varbinds));

        if (request->processed || rc != SNMP_ERR_NOERROR)
            goto free_win_varbinds;

        if (reqinfo->mode == MODE_SET_RESERVE1)
            alloc_context_info(request->index);

        varbind = request->requestvb;
        netsnmp_assert(varbind);

        /*
         * Convert the Net-SNMP varbind to a Windows SNMP varbind list.
         */
        rc = convert_to_windows_varbind_list(&win_varbinds, varbind);
        if (rc != SNMP_ERR_NOERROR) {
            DEBUGMSG(("winExtDLL",
                      "converting varbind list to Windows format failed with"
                      " error code %d.\n", request->status));
            netsnmp_request_set_error(requests, rc);
            goto free_win_varbinds;
        }

        netsnmp_assert(win_varbinds.len == 1);

        /*
         * For a GetNext PDU, if the varbind OID comes lexicographically
         * before the root OID of this handler, replace it by the root OID.
         */
        if (reqinfo->mode == MODE_GETNEXT
            && snmp_oid_compare_w_n(win_varbinds.list[0].name.ids,
                                    win_varbinds.list[0].name.idLength,
                                    reginfo->rootoid,
                                    reginfo->rootoid_len) < 0) {
            DEBUGIF("winExtDLL") {
                size_t          oid1_namelen = 0, oid2_namelen = 0, outlen1 = 0,
                                outlen2 = 0;
                char           *oid1_name = NULL, *oid2_name = NULL;
                int             overflow1 = 0, overflow2 = 0;

                netsnmp_static_assert(sizeof(oid) == sizeof(UINT));
                netsnmp_sprint_realloc_objid((u_char **) & oid1_name,
                                             &oid1_namelen, &outlen1, 1,
                                             &overflow1, (const oid *)
                                             win_varbinds.list[0].name.ids,
                                             win_varbinds.list[0].name.idLength);
                netsnmp_sprint_realloc_objid((u_char **) & oid2_name,
                                             &oid2_namelen, &outlen2, 1,
                                             &overflow2, reginfo->rootoid,
                                             reginfo->rootoid_len);
                DEBUGMSG(("winExtDLL",
                          "extension DLL %s: replacing OID %s%s by OID %s%s.\n",
                          ext_dll_info->dll_name,
                          oid1_name, overflow1 ? " [TRUNCATED]" : "",
                          oid2_name, overflow2 ? " [TRUNCATED]" : ""));
                free(oid2_name);
                free(oid1_name);
            }

            SnmpUtilOidFree(&win_varbinds.list[0].name);
            memset(&win_varbinds.list[0].name, 0,
                   sizeof(win_varbinds.list[0].name));
            copy_oid_to_new_windows_oid(&win_varbinds.list[0].name,
                                        reginfo->rootoid,
                                        reginfo->rootoid_len);
        }

        if (ext_dll_info->pfSnmpExtensionQueryEx) {
            result = ext_dll_info->pfSnmpExtensionQueryEx(nRequestType,
                                                          1,
                                                          &win_varbinds,
                                                          get_context_info(request->index),
                                                          &ErrorStatus,
                                                          &ErrorIndex);
        } else if (ext_dll_info->pfSnmpExtensionQuery) {
            result =
                ext_dll_info->pfSnmpExtensionQuery((BYTE) nRequestType,
                                                   &win_varbinds,
                                                   &ErrorStatus,
                                                   &ErrorIndex);
        } else {
            snmp_log(LOG_ERR,
                     "error in extension DLL %s: SNMP query function missing.\n",
                     ext_dll_info->dll_name);
            result = FALSE;
        }

        if (!result) {
            snmp_log(LOG_ERR,
                     "extension DLL %s: SNMP query function failed.\n",
                     ext_dll_info->dll_name);
            rc = SNMP_ERR_GENERR;
            goto free_win_varbinds;
        }

        rc = convert_win_snmp_err(ErrorStatus);
        if (rc != SNMP_ERR_NOERROR) {
            DEBUGIF("winExtDLL") {
                size_t          oid_namelen = 0, outlen = 0;
                char           *oid_name = NULL;
                int             overflow = 0;

                netsnmp_sprint_realloc_objid((u_char **) & oid_name,
                                             &oid_namelen,
                                             &outlen, 1, &overflow,
                                             ext_dll_view_info->name,
                                             ext_dll_view_info->name_length);
                DEBUGMSG(("winExtDLL", "extension DLL %s: SNMP query function"
                          " returned error code %lu (Windows) / %d (Net-SNMP)"
                          " for request type %d, OID %s%s, ASN type %d and"
                          " value %ld.\n",
                          ext_dll_info->dll_name, ErrorStatus, rc, nRequestType,
                          oid_name, overflow ? " [TRUNCATED]" : "",
                          win_varbinds.list[0].value.asnType,
                          win_varbinds.list[0].value.asnValue.number));
                free(oid_name);
            }
            netsnmp_assert(ErrorIndex == 1);
            netsnmp_request_set_error(requests, rc);
            if (rc == SNMP_NOSUCHOBJECT || rc == SNMP_NOSUCHINSTANCE
                || rc == SNMP_ERR_NOSUCHNAME)
                rc = SNMP_ERR_NOERROR;
            goto free_win_varbinds;
        }

        copy_value = FALSE;
        if (reqinfo->mode == MODE_GET)
            copy_value = TRUE;
        else if (reqinfo->mode == MODE_GETNEXT) {
            const SnmpVarBind *win_varbind;

            win_varbind = &win_varbinds.list[0];

            /*
             * Verify whether the OID returned by the extension DLL fits
             * inside the OID range this handler has been registered
             * with. Also compare the OID passed to the extension DLL with
             * the OID returned by the same DLL. If the DLL returned a
             * lexicographically earlier OID, this means that there is no
             * next OID in the MIB implemented by the DLL.
             *
             * Note: for some GetNext requests BoundsChecker will report
             * that the code below accesses a dangling pointer. This is
             * a limitation of BoundsChecker: apparently BoundsChecker is
             * not able to cope with reallocation of memory for
             * win_varbind by an SNMP extension DLL that has not been
             * instrumented by BoundsChecker.
             */
            if (netsnmp_oid_is_subtree_n_w(ext_dll_view_info->name,
                                           ext_dll_view_info->name_length,
                                           win_varbind->name.ids,
                                           win_varbind->name.idLength) == 0
                && snmp_oid_compare_n_w(varbind->name, varbind->name_length,
                                        win_varbind->name.ids,
                                        win_varbind->name.idLength) < 0) {
                /*
                 * Copy the OID returned by the extension DLL to the
                 * Net-SNMP varbind.
                 */
                snmp_set_var_objid_w(varbind,
                                     win_varbind->name.ids,
                                     win_varbind->name.idLength);
                copy_value = TRUE;
            }
        }
        if (copy_value) {
            netsnmp_variable_list *result_vb;

            /*
             * Copy the value returned by the extension DLL to the Net-SNMP
             * varbind.
             */
            result_vb = NULL;
            rc = append_windows_varbind(&result_vb, &win_varbinds.list[0]);
            netsnmp_assert(result_vb || rc != SNMP_ERR_NOERROR);
            if (result_vb) {
                snmp_set_var_typed_value(varbind,
                                         result_vb->type,
                                         result_vb->val.string,
                                         result_vb->val_len);
                snmp_free_varbind(result_vb);
            } else {
                netsnmp_request_set_error(requests, rc);
                goto free_win_varbinds;
            }
        }

free_win_varbinds:
        if (reqinfo->mode == MODE_SET_COMMIT
            || reqinfo->mode == MODE_SET_UNDO
            || reqinfo->mode == MODE_SET_FREE)
            free_context_info(request->index);
        if (win_varbinds.list)
            SnmpUtilVarBindListFree(&win_varbinds);
    }

    return rc;
}

/**
 * Iterate over the SNMP extension DLL information in the registry and store
 * the retrieved information in s_winextdll[].
 *
 * At the time an SNMP extension DLL is installed, some information about the
 * DLL is written to the registry at one of the two following locations:
 * HKLM\SYSTEM\CurrentControlSet\Control\SNMP\Parameters\ExtensionAgents for
 * Windows Vista, Windows 7 and Windows 2008 or
 * HKLM\SYSTEM\CurrentControlSet\Services\SNMP\Parameters\ExtensionAgents for
 * earlier Windows versions. Under this key zero or more REG_SZ values are
 * stored with the names of registry keys containing the DLL path.
 */
void
read_extension_dlls_from_registry()
{
    DEBUGMSGTL(("winExtDLL",
                "read_extension_dlls_from_registry called\n"));

    read_extension_dlls_from_registry_at
        ("SYSTEM\\CurrentControlSet\\Services\\SNMP\\Parameters\\ExtensionAgents");
    read_extension_dlls_from_registry_at
        ("SYSTEM\\CurrentControlSet\\Control\\SNMP\\Parameters\\ExtensionAgents");
}

void
read_extension_dlls_from_registry_at(const char *const subkey)
{
    DWORD           retCode;
    HKEY            hKey;
    int             i;
    DWORD           valueSize;
    TCHAR           valueName[MAX_VALUE_NAME];
    DWORD           dataType;
    TCHAR           data[MAX_VALUE_NAME];
    DWORD           dataSize;

    retCode = RegOpenKeyExA(HKEY_LOCAL_MACHINE, subkey,
                            0, KEY_QUERY_VALUE, &hKey);

    if (retCode == ERROR_SUCCESS) {
        for (i = 0; ; i++) {
            valueSize = sizeof(valueName);
            dataSize = sizeof(data);
            retCode = RegEnumValue(hKey, i, valueName, &valueSize, NULL,
                                   &dataType, (BYTE *) data, &dataSize);

            if (retCode != ERROR_SUCCESS)
                break;
            if (dataType == REG_SZ) {
                winextdll       ext_dll_info;

                memset(&ext_dll_info, 0, sizeof(ext_dll_info));
                ext_dll_info.dll_name =
                    read_extension_dll_path_from_registry(data);
                if (ext_dll_info.dll_name) {
                    xarray_push_back(&s_winextdll, &ext_dll_info);
                    DEBUGMSG(("winExtDLL", "registry key %s: DLL %s.\n",
                              data, ext_dll_info.dll_name));
                }
            }
        }
        RegCloseKey(hKey);
    }
}

/** Store the DLL path in dynamically allocated memory. */
char           *
read_extension_dll_path_from_registry(const TCHAR * keyName)
{
    HKEY            hKey;
    DWORD           key_value_type = 0;
    TCHAR           valueName[MAX_VALUE_NAME];
    DWORD           key_value_size = MAX_VALUE_NAME;
    TCHAR           valueNameExpanded[MAX_VALUE_NAME];
    DWORD           retCode;
    char           *result = 0;

    retCode = RegOpenKeyExA(HKEY_LOCAL_MACHINE,
                            keyName, 0, KEY_QUERY_VALUE, &hKey);

    if (retCode != ERROR_SUCCESS)
        return 0;

    retCode = RegQueryValueExA(hKey,
                               "Pathname",
                               NULL,
                               &key_value_type,
                               (BYTE *) valueName, &key_value_size);

    if (retCode != ERROR_SUCCESS) {
        RegCloseKey(hKey);
        return 0;
    }

    if (key_value_type == REG_EXPAND_SZ) {
        if (ExpandEnvironmentStrings
            (valueName, valueNameExpanded, MAX_VALUE_NAME))
            result = strdup(valueNameExpanded);
    } else if (key_value_type == REG_SZ)
        result = strdup(valueName);

    RegCloseKey(hKey);
    return result;
}

/**
 * Callback function called by the Net-SNMP agent to check for traps waiting
 * to be processed.
 */
void
subagentTrapCheck(unsigned int clientreg, void *clientarg)
{
    while (1) {
        DWORD           dwWaitResult;
        BOOL            bResult;
        int             i;
        int             j;
        const winextdll *ext_dll_info;

        if (s_trapevent.size == 0)
            return;

        dwWaitResult = WaitForMultipleObjects(s_trapevent.size,
                                              &TRAPEVENT(0), FALSE, 0);

        i = dwWaitResult - WAIT_OBJECT_0;
        if (i < 0 || i >= s_trapevent.size) {
            netsnmp_assert(dwWaitResult == WAIT_TIMEOUT);
            return;
        }

        netsnmp_assert(s_trapevent.size == s_trapevent_to_dllinfo.size);
        ext_dll_info = TRAPEVENT_TO_DLLINFO(i);
        netsnmp_assert(ext_dll_info->subagentTrapEvent == TRAPEVENT(i));

        /*
         * Reset the signalled event just in case the extension DLL erroneously
         * allocated a manual-reset event instead of an auto-reset event. It is
         * important to reset the event BEFORE traps are processed, otherwise a
         * race condition is triggered between the extension DLL setting the
         * event and this code resetting the event.
         */
        ResetEvent(TRAPEVENT(i));

        if (!ext_dll_info->pfSnmpExtensionTrap) {
            snmp_log(LOG_ERR,
                     "internal error in SNMP extension DLL %s: a trap is ready"
                     " but the function SnmpExtensionTrap() is missing.\n",
                     ext_dll_info->dll_name);
            return;
        }

        /*
         * Process at most hundred traps per extension DLL. If the extension DLL
         * has more traps waiting, that's probably a bug in the extension DLL.
         */
        for (j = 0; j < 100; j++) {
            AsnObjectIdentifier Enterprise = { 0, NULL };
            AsnInteger      GenericTrap = 0;
            AsnInteger      SpecificTrap = 0;
            AsnTimeticks    TimeStamp = 0;
            SnmpVarBindList TrapVarbinds = { NULL, 0 };

            bResult = ext_dll_info->pfSnmpExtensionTrap(&Enterprise,
                                                        &GenericTrap,
                                                        &SpecificTrap,
                                                        &TimeStamp,
                                                        &TrapVarbinds);

            if (!bResult)
                break;

            send_trap(&Enterprise, GenericTrap, SpecificTrap, TimeStamp,
                      &TrapVarbinds);

            SnmpUtilVarBindListFree(&TrapVarbinds);
        }
    }
}

void
send_trap(const AsnObjectIdentifier * const pEnterprise,
          const AsnInteger GenericTrap,
          const AsnInteger SpecificTrap,
          const AsnTimeticks TimeStamp,
          const SnmpVarBindList * const pTrapVarbinds)
{
    /*
     * A quote from the paragraph in RFC 1908 about SNMPv1 to SNMPv2c
     * trap translation (http://www.ietf.org/rfc/rfc1908.txt):
     * <quote>
     * If a Trap-PDU is received, then it is mapped into a SNMPv2-Trap-
     * PDU.  This is done by prepending onto the variable-bindings field
     * two new bindings:  sysUpTime.0 [6], which takes its value from the
     * timestamp field of the Trap-PDU; and, snmpTrapOID.0 [6], which is
     * calculated thusly:  if the value of generic-trap field is
     * `enterpriseSpecific', then the value used is the concatenation of
     * the enterprise field from the Trap-PDU with two additional sub-
     * identifiers, `0', and the value of the specific-trap field;
     * otherwise, the value of the corresponding trap defined in [6] is
     * used.
     * </quote>
     *
     * Reference [6] refers to RFC 1907 (http://www.ietf.org/rfc/rfc1907.txt),
     * where the generic trap OIDs have been defined as follows:
     * coldStart             ::= { snmpTraps 1 }
     * warmStart             ::= { snmpTraps 2 }
     * linkDown              ::= { snmpTraps 3 }
     * linkUp                ::= { snmpTraps 4 }
     * authenticationFailure ::= { snmpTraps 5 }
     * egpNeighborLoss       ::= { snmpTraps 6 }
     */
    static const oid sysuptime_oid[] = { 1, 3, 6, 1, 2, 1, 1, 3, 0 };
    static const size_t sysuptime_oid_len = OID_LENGTH(sysuptime_oid);

    static const oid snmptrap_oid[] = { 1, 3, 6, 1, 6, 3, 1, 1, 4, 1, 0 };
    static const size_t snmptrap_oid_len = OID_LENGTH(snmptrap_oid);

    static const oid snmptraps_oid[] = { 1, 3, 6, 1, 6, 3, 1, 1, 5 };
    static const size_t snmptraps_oid_len = OID_LENGTH(snmptraps_oid);

    oid             vb2_oid[MAX_OID_LEN];
    size_t          vb2_oid_len;

    netsnmp_variable_list *notification_vars = NULL;


    /*
     * Append the varbind (sysUpTime.0, TimeStamp). 
     */
    snmp_varlist_add_variable(&notification_vars,
                              sysuptime_oid, sysuptime_oid_len,
                              ASN_TIMETICKS,
                              (const u_char *) &TimeStamp,
                              sizeof(TimeStamp));

    if (GenericTrap == SNMP_GENERICTRAP_ENTERSPECIFIC) {
        /*
         * Enterprise specific trap: compute the OID
         * *pEnterprise + ".0." + SpecificTrap.
         */
        copy_oid_n_w(vb2_oid, &vb2_oid_len,
                     pEnterprise->ids, pEnterprise->idLength);
        vb2_oid[vb2_oid_len++] = 0;
        vb2_oid[vb2_oid_len++] = SpecificTrap;
    } else {
        /*
         * Generic trap: compute the OID snmpTraps + "." + GenericTrap.
         * Since the GenericTrap values are those defined in SNMPv1, since
         * these values start at zero, and since the corresponding values in
         * SNMPv2 start at one, translate the GenericTrap value accordingly.
         * See also http://www.ietf.org/rfc/rfc1214.txt and
         * http://www.ietf.org/rfc/rfc3418.txt.
         */
        copy_oid(vb2_oid, &vb2_oid_len, snmptraps_oid, snmptraps_oid_len);
        vb2_oid[vb2_oid_len++] = GenericTrap + 1;
    }

    /*
     * Append the varbind (snmpTrap, vb2_oid). 
     */
    snmp_varlist_add_variable(&notification_vars,
                              snmptrap_oid, snmptrap_oid_len,
                              ASN_OBJECT_ID,
                              (u_char *) vb2_oid,
                              vb2_oid_len * sizeof(vb2_oid[0]));

    /*
     * Append all the varbinds in pTrapVarbinds. 
     */
    append_windows_varbind_list(&notification_vars, pTrapVarbinds);

    /*
     * Send trap. 
     */
    send_v2trap(notification_vars);

    /*
     * Free the memory allocated for notification_vars. 
     */
    snmp_free_varbind(notification_vars);
}

/**
 * Convert a Windows varbind to a Net-SNMP varbind and add it to the list of
 * varbinds 'net_snmp_varbinds'.
 *
 * @note The memory allocated inside this function must be freed by the caller
 *   as follows: snmp_free_varbind(*net_snmp_varbinds).
 */
static int
append_windows_varbind_list(netsnmp_variable_list **
                            const net_snmp_varbinds,
                            const SnmpVarBindList * const win_varbinds)
{
    int             i, status = SNMP_ERR_NOERROR;

    for (i = 0; i < win_varbinds->len; i++) {
        status =
            append_windows_varbind(net_snmp_varbinds,
                                   &win_varbinds->list[i]);
        if (status != SNMP_ERR_NOERROR)
            break;
    }
    return status;
}

static int
append_windows_varbind(netsnmp_variable_list ** const net_snmp_varbinds,
                       const SnmpVarBind * const win_varbind)
{
    switch (win_varbind->value.asnType) {
    case MS_ASN_INTEGER:
        snmp_varlist_add_variable_w(net_snmp_varbinds, win_varbind->name.ids,
                                    win_varbind->name.idLength,
                                    ASN_INTEGER,
                                    &win_varbind->value.asnValue.number,
                                    sizeof(win_varbind->value.asnValue.
                                           number));
        break;
    case MS_ASN_BITS:
        snmp_varlist_add_variable_w(net_snmp_varbinds, win_varbind->name.ids,
                                    win_varbind->name.idLength,
                                    ASN_BIT_STR,
                                    win_varbind->value.asnValue.bits.stream,
                                    win_varbind->value.asnValue.bits.length);
        break;
    case MS_ASN_OCTETSTRING:
        snmp_varlist_add_variable_w(net_snmp_varbinds, win_varbind->name.ids,
                                    win_varbind->name.idLength,
                                    ASN_OCTET_STR,
                                    win_varbind->value.asnValue.string.
                                    stream,
                                    win_varbind->value.asnValue.string.
                                    length);
        break;
    case MS_ASN_NULL:
        snmp_varlist_add_variable_w(net_snmp_varbinds, win_varbind->name.ids,
                                    win_varbind->name.idLength,
                                    ASN_NULL, 0, 0);
        break;
    case MS_ASN_OBJECTIDENTIFIER:
        snmp_varlist_add_variable_w(net_snmp_varbinds, win_varbind->name.ids,
                                    win_varbind->name.idLength,
                                    ASN_OBJECT_ID,
                                    win_varbind->value.asnValue.
                                    object.ids,
                                    win_varbind->value.asnValue.object.
                                    idLength * sizeof(oid));
        break;

        /*
         * MS_ASN_INTEGER32: synonym for MS_ASN_INTEGER. 
         */

    case MS_ASN_SEQUENCE:
        snmp_varlist_add_variable_w(net_snmp_varbinds, win_varbind->name.ids,
                                    win_varbind->name.idLength,
                                    ASN_SEQUENCE,
                                    win_varbind->value.asnValue.sequence.
                                    stream,
                                    win_varbind->value.asnValue.sequence.
                                    length);
        break;
    case MS_ASN_IPADDRESS:
        snmp_varlist_add_variable_w(net_snmp_varbinds, win_varbind->name.ids,
                                    win_varbind->name.idLength,
                                    ASN_IPADDRESS,
                                    win_varbind->value.asnValue.address.
                                    stream,
                                    win_varbind->value.asnValue.address.
                                    length);
        break;
    case MS_ASN_COUNTER32:
        snmp_varlist_add_variable_w(net_snmp_varbinds, win_varbind->name.ids,
                                    win_varbind->name.idLength,
                                    ASN_COUNTER,
                                    &win_varbind->value.asnValue.counter,
                                    sizeof(win_varbind->value.asnValue.
                                           counter));
        break;
    case MS_ASN_GAUGE32:
        snmp_varlist_add_variable_w(net_snmp_varbinds, win_varbind->name.ids,
                                    win_varbind->name.idLength,
                                    ASN_GAUGE,
                                    &win_varbind->value.asnValue.gauge,
                                    sizeof(win_varbind->value.asnValue.
                                           gauge));
        break;
    case MS_ASN_TIMETICKS:
        snmp_varlist_add_variable_w(net_snmp_varbinds, win_varbind->name.ids,
                                    win_varbind->name.idLength,
                                    ASN_TIMETICKS,
                                    &win_varbind->value.asnValue.ticks,
                                    sizeof(win_varbind->value.asnValue.
                                           ticks));
        break;
    case MS_ASN_OPAQUE:        // AsnOctetString
        snmp_varlist_add_variable_w(net_snmp_varbinds, win_varbind->name.ids,
                                    win_varbind->name.idLength,
                                    ASN_OPAQUE,
                                    win_varbind->value.asnValue.arbitrary.
                                    stream,
                                    win_varbind->value.asnValue.arbitrary.
                                    length);
        break;
    case MS_ASN_COUNTER64:
        snmp_varlist_add_variable_w(net_snmp_varbinds, win_varbind->name.ids,
                                    win_varbind->name.idLength,
                                    ASN_COUNTER64,
                                    &win_varbind->value.asnValue.counter64,
                                    sizeof(win_varbind->value.asnValue.
                                           counter64));
        break;
    case MS_ASN_UINTEGER32:
        snmp_varlist_add_variable_w(net_snmp_varbinds, win_varbind->name.ids,
                                    win_varbind->name.idLength,
                                    ASN_UNSIGNED,
                                    &win_varbind->value.asnValue.unsigned32,
                                    sizeof(win_varbind->value.asnValue.
                                           unsigned32));
        break;
    default:
        return SNMP_ERR_GENERR;
    }

    return SNMP_ERR_NOERROR;
}

static int
snmp_set_var_objid_w(netsnmp_variable_list * var, const UINT * name,
                     UINT name_length)
{
    netsnmp_static_assert(sizeof(oid) == sizeof(UINT));
    return snmp_set_var_objid(var, (const oid *) name, name_length);
}

static netsnmp_variable_list *
snmp_varlist_add_variable_w(netsnmp_variable_list ** varlist, const UINT * name,
                            UINT name_length, u_char type, const void * value,
                            size_t len)
{
    netsnmp_static_assert(sizeof(oid) == sizeof(UINT));
    return snmp_varlist_add_variable(varlist, (const oid *) name, name_length, type,
                                     value, len);
}

/**
 * Convert a Net-SNMP varbind to a WinSNMP varbind list.
 *
 * @param[out] pVarBindList WinSNMP varbind list, initialized by this
 *               function.
 * @param[in]  varbind Net-SNMP varbind.
 */
int
convert_to_windows_varbind_list(SnmpVarBindList * pVarBindList,
                                netsnmp_variable_list * varbind)
{
    SnmpVarBind    *win_varbind;

    netsnmp_assert(pVarBindList);
    netsnmp_assert(varbind);

    pVarBindList->len = 1;
    pVarBindList->list
        = (SnmpVarBind *) SnmpUtilMemAlloc(pVarBindList->len
                                           *
                                           sizeof(pVarBindList->list[0]));
    if (pVarBindList->list == 0)
        goto generr;

    memset(&pVarBindList->list[0], 0, sizeof(pVarBindList->list[0]));

    win_varbind = &pVarBindList->list[0];

    if (varbind->name
        && !copy_oid_to_new_windows_oid(&win_varbind->name,
                                        varbind->name,
                                        varbind->name_length))
        goto generr;

    switch (varbind->type) {
    case ASN_BOOLEAN:
        // There is no equivalent type in Microsoft's <snmp.h>.
        netsnmp_assert(0);
        win_varbind->value.asnType = MS_ASN_INTEGER;
        win_varbind->value.asnValue.number = *(varbind->val.integer);
        break;
    case ASN_INTEGER:
        win_varbind->value.asnType = MS_ASN_INTEGER;
        win_varbind->value.asnValue.number = *(varbind->val.integer);
        break;
    case ASN_BIT_STR:
        win_varbind->value.asnType = MS_ASN_BITS;
        win_varbind->value.asnValue.string.stream
            = winsnmp_memdup(varbind->val.string, varbind->val_len);
        win_varbind->value.asnValue.string.length =
            (UINT) (varbind->val_len);
        win_varbind->value.asnValue.string.dynamic = TRUE;
        break;
    case ASN_OCTET_STR:
        win_varbind->value.asnType = MS_ASN_OCTETSTRING;
        win_varbind->value.asnValue.string.stream
            = winsnmp_memdup(varbind->val.string, varbind->val_len);
        win_varbind->value.asnValue.string.length =
            (UINT) (varbind->val_len);
        win_varbind->value.asnValue.string.dynamic = TRUE;
        break;
    case ASN_NULL:
        win_varbind->value.asnType = MS_ASN_NULL;
        memset(&win_varbind->value, 0, sizeof(win_varbind->value));
        break;
    case ASN_OBJECT_ID:
        win_varbind->value.asnType = MS_ASN_OBJECTIDENTIFIER;
        if (!copy_oid_to_new_windows_oid
            (&win_varbind->value.asnValue.object, varbind->val.objid,
             varbind->val_len / sizeof(varbind->val.objid[0])))
            return SNMP_ERR_GENERR;
        break;
    case ASN_SEQUENCE:
        win_varbind->value.asnType = MS_ASN_SEQUENCE;
        win_varbind->value.asnValue.string.stream
            = winsnmp_memdup(varbind->val.string, varbind->val_len);
        win_varbind->value.asnValue.string.length =
            (UINT) (varbind->val_len);
        win_varbind->value.asnValue.string.dynamic = TRUE;
        break;
    case ASN_SET:
        // There is no equivalent type in Microsoft's <snmp.h>.
        netsnmp_assert(0);
        win_varbind->value.asnType = MS_ASN_INTEGER;
        win_varbind->value.asnValue.number = *(varbind->val.integer);
        break;
    case ASN_IPADDRESS:
        win_varbind->value.asnType = MS_ASN_IPADDRESS;
        win_varbind->value.asnValue.string.stream
            = winsnmp_memdup(varbind->val.string, varbind->val_len);
        win_varbind->value.asnValue.string.length =
            (UINT) (varbind->val_len);
        win_varbind->value.asnValue.string.dynamic = TRUE;
        break;
    case ASN_COUNTER:
        win_varbind->value.asnType = MS_ASN_COUNTER32;
        win_varbind->value.asnValue.counter = *(varbind->val.integer);
        break;
        /*
         * ASN_GAUGE == ASN_UNSIGNED 
         */
    case ASN_UNSIGNED:
        win_varbind->value.asnType = MS_ASN_UNSIGNED32;
        win_varbind->value.asnValue.unsigned32 = *(varbind->val.integer);
        break;
    case ASN_TIMETICKS:
        win_varbind->value.asnType = MS_ASN_TIMETICKS;
        win_varbind->value.asnValue.ticks = *(varbind->val.integer);
        break;
    case ASN_OPAQUE:
        win_varbind->value.asnType = MS_ASN_OPAQUE;
        win_varbind->value.asnValue.string.stream
            = winsnmp_memdup(varbind->val.string, varbind->val_len);
        win_varbind->value.asnValue.string.length =
            (UINT) (varbind->val_len);
        win_varbind->value.asnValue.string.dynamic = TRUE;
        break;
    case ASN_COUNTER64:
        win_varbind->value.asnType = MS_ASN_COUNTER64;
        win_varbind->value.asnValue.counter64.HighPart
            = varbind->val.counter64->high;
        win_varbind->value.asnValue.counter64.LowPart
            = varbind->val.counter64->low;
        break;
    default:
        netsnmp_assert(0);
        goto generr;
    }

    return SNMP_ERR_NOERROR;

  generr:
    SnmpUtilVarBindListFree(pVarBindList);
    memset(pVarBindList, 0, sizeof(*pVarBindList));
    return SNMP_ERR_GENERR;
}

/** Convert a Windows SNMP error code to the equivalent Net-SNMP error code. */
int
convert_win_snmp_err(const int win_snmp_err)
{
    switch (win_snmp_err) {
    case SNMP_ERRORSTATUS_NOERROR:
        return SNMP_ERR_NOERROR;
    case SNMP_ERRORSTATUS_TOOBIG:
        return SNMP_ERR_TOOBIG;
    case SNMP_ERRORSTATUS_NOSUCHNAME:
        /*
         * Note: SNMP extension DLLs return SNMP_ERRORSTATUS_NOSUCHNAME
         * when either noSuchObject or noSuchInstance should be returned to
         * the SNMP manager (assuming SNMPv2c or SNMPv3). Unfortunately it
         * is not possible without consulting the MIB to find out whether
         * either SNMP_NOSUCHINSTANCE or SNMP_NOSUCHOBJECT should be returned.
         * See also RFC 1448.
         */
        return SNMP_NOSUCHINSTANCE;
    case SNMP_ERRORSTATUS_BADVALUE:
        return SNMP_ERR_BADVALUE;
    case SNMP_ERRORSTATUS_READONLY:
        return SNMP_ERR_READONLY;
    case SNMP_ERRORSTATUS_GENERR:
        return SNMP_ERR_GENERR;
    case SNMP_ERRORSTATUS_NOACCESS:
        return SNMP_ERR_NOACCESS;
    case SNMP_ERRORSTATUS_WRONGTYPE:
        return SNMP_ERR_WRONGTYPE;
    case SNMP_ERRORSTATUS_WRONGLENGTH:
        return SNMP_ERR_WRONGLENGTH;
    case SNMP_ERRORSTATUS_WRONGENCODING:
        return SNMP_ERR_WRONGENCODING;
    case SNMP_ERRORSTATUS_WRONGVALUE:
        return SNMP_ERR_WRONGVALUE;
    case SNMP_ERRORSTATUS_NOCREATION:
        return SNMP_ERR_NOCREATION;
    case SNMP_ERRORSTATUS_INCONSISTENTVALUE:
        return SNMP_ERR_INCONSISTENTVALUE;
    case SNMP_ERRORSTATUS_RESOURCEUNAVAILABLE:
        return SNMP_ERR_RESOURCEUNAVAILABLE;
    case SNMP_ERRORSTATUS_COMMITFAILED:
        return SNMP_ERR_COMMITFAILED;
    case SNMP_ERRORSTATUS_UNDOFAILED:
        return SNMP_ERR_UNDOFAILED;
    case SNMP_ERRORSTATUS_AUTHORIZATIONERROR:
        return SNMP_ERR_AUTHORIZATIONERROR;
    case SNMP_ERRORSTATUS_NOTWRITABLE:
        return SNMP_ERR_NOTWRITABLE;
    case SNMP_ERRORSTATUS_INCONSISTENTNAME:
        return SNMP_ERR_INCONSISTENTNAME;
    }
    netsnmp_assert(0);
    return SNMP_ERR_GENERR;
}

/**
 * Look up the extension DLL view that was registered with the given OID.
 */
static winextdll_view *
lookup_view_by_oid(oid * const name, const size_t name_len)
{
    int             i;

    for (i = 0; i < s_winextdll_view.size; i++) {
        if (netsnmp_oid_equals(WINEXTDLL_VIEW(i).name,
                               WINEXTDLL_VIEW(i).name_length,
                               name, name_len) == 0
            && WINEXTDLL_VIEW(i).my_handler) {
            return &WINEXTDLL_VIEW(i);
        }
    }

    return NULL;
}

static int
snmp_oid_compare_n_w(const oid * name1, size_t len1, const UINT * name2,
                     UINT len2)
{
    netsnmp_static_assert(sizeof(oid) == sizeof(UINT));
    return snmp_oid_compare(name1, len1, (const oid *) name2, len2);
}

static int
snmp_oid_compare_w_n(const UINT * name1, UINT len1, const oid * name2,
                     size_t len2)
{
    netsnmp_static_assert(sizeof(oid) == sizeof(UINT));
    return snmp_oid_compare((const oid *) name1, len1, name2, len2);
}

static int
netsnmp_oid_is_subtree_n_w(const oid * name1, size_t len1, const UINT * name2,
                           UINT len2)
{
    netsnmp_static_assert(sizeof(oid) == sizeof(UINT));
    return netsnmp_oid_is_subtree(name1, len1, (const oid *) name2, len2);
}

/**
 * Copy an OID.
 *
 * @param[out] to_name       Number of elements written to destination OID.
 * @param[out] to_name_len   Length of destination OID. Must have at least
 *                           min(from_name_len, MAX_OID_LEN) elements.
 * @param[in]  from_name     Original OID.
 * @param[in]  from_name_len Length of original OID.
 */
static void
copy_oid(oid * const to_name, size_t * const to_name_len,
         const oid * const from_name, const size_t from_name_len)
{
    int             j;

    netsnmp_assert(to_name);
    netsnmp_assert(to_name_len);
    netsnmp_assert(from_name);

    for (j = 0; j < from_name_len && j < MAX_OID_LEN; j++)
        to_name[j] = from_name[j];

    *to_name_len = j;
}

/**
 * Copy an OID.
 *
 * @param[out] to_name       Number of elements written to destination OID.
 * @param[out] to_name_len   Length of destination OID. Must have at least
 *                           min(from_name_len, MAX_OID_LEN) elements.
 * @param[in]  from_name     Original OID.
 * @param[in]  from_name_len Length of original OID.
 */
static void
copy_oid_n_w(oid * const to_name, size_t * const to_name_len,
             const UINT * const from_name, const UINT from_name_len)
{
    netsnmp_static_assert(sizeof(oid) == sizeof(UINT));
    copy_oid(to_name, to_name_len, (const oid *) from_name, from_name_len);
}

/**
 * Convert a Net-SNMP OID into a Windows OID and allocate memory for the
 * Windows OID.
 *
 * @param[out] windows_oid   Pointer to a AsnObjectIdentifier.
 * @param[in]  name   Pointer to an array with elements of type oid
 *           and length name_len.
 * @param[in]  name_len Number of elements of input and output OID.
 */
static UINT    *
copy_oid_to_new_windows_oid(AsnObjectIdentifier * const windows_oid,
                            const oid * const name, const size_t name_len)
{
    netsnmp_assert(windows_oid);
    netsnmp_assert(windows_oid->ids == 0);
    netsnmp_assert(windows_oid->idLength == 0);
    netsnmp_assert(name);

    windows_oid->ids
        =
        (UINT *) winsnmp_memdup(name,
                                sizeof(windows_oid->ids[0]) * name_len);
    windows_oid->idLength = (UINT) name_len;
    return windows_oid->ids;
}

static u_char  *
winsnmp_memdup(const void *src, const size_t len)
{
    u_char         *p;

    netsnmp_assert(len == (UINT) len);

    p = SnmpUtilMemAlloc((UINT) len);
    if (p)
        memcpy(p, src, len);
    return p;
}

#if 0
/** Initialize array 'a'. */
static void
xarray_init(xarray * a, size_t elem_size)
{
    netsnmp_assert(a);

    memset(a, 0, sizeof(*a));
    a->elem_size = elem_size;
}
#endif

/** Deallocate any memory that was dynamically allocated for 'a'. */
static void
xarray_destroy(xarray * a)
{
    netsnmp_assert(a);

    xarray_reserve(a, 0);
}

/**
 * Append the contents of the address range [ elem, elem + a->elem_size [ to a.
 *
 * Resize a if necessary.
 *
 * @return A pointer to the address where the data has been copied upon success,
 *         or NULL upon failure.
 */
static void    *
xarray_push_back(xarray * a, const void *elem)
{
    netsnmp_assert(a);
    netsnmp_assert(elem);
    netsnmp_assert(a->size <= a->reserved);

    if (a->size == a->reserved)
        xarray_reserve(a, a->reserved == 0 ? 16 : 2 * a->reserved);
    if (a->size < a->reserved) {
        netsnmp_assert(a->size < a->reserved);
        return memcpy((char *) (a->p) + a->elem_size * a->size++, elem,
                      a->elem_size);
    }
    return NULL;
}

#if 0
/** Erase [ elem, elem + a->elem_size [ from a. */
static void
xarray_erase(xarray * a, void *const elem)
{
    netsnmp_assert(a);
    netsnmp_assert(a->size >= 1);
    netsnmp_assert(a->p <= elem);
    netsnmp_assert((const char *) elem + a->elem_size <=
           (char *) a->p + a->size * a->elem_size);
    netsnmp_assert(((const char *) elem - (char *) a->p) % a->elem_size == 0);

    a->size--;
    memmove((char *) elem, (char *) elem + a->elem_size,
            a->size - ((const char *) elem -
                       (char *) a->p) / a->elem_size);
}
#endif

/**
 * Change the number of allocated elements to 'reserved'.
 *
 * Can be used either for enlarging or for shrinking the memory allocated for
 * 'a'. Does not modify 'a' if memory allocation fails. Newly allocted memory
 * is not initialized.
 *
 * @return != NULL upon success, NULL upon failure.
 */
static void    *
xarray_reserve(xarray * a, int reserved)
{
    netsnmp_assert(a);
    netsnmp_assert(a->size <= a->reserved);

    if ((a->p = realloc(a->p, a->elem_size * reserved)))
        a->reserved = reserved;
    else
        a->reserved = 0;
    return a->p;
}

#endif                          /* USING_WINEXTDLL_MODULE */
