/*++

Copyright (c) 1997  Microsoft Corporation

Module Name:

    mgmtapi.c

Abstract:

    SNMP Management API (wrapped around WinSNMP API).

Environment:

    User Mode - Win32

Revision History:

    05-Feb-1997 DonRyan
        Rewrote functions to be wrappers around WinSNMP.

--*/

///////////////////////////////////////////////////////////////////////////////
//                                                                           //
// Include Files                                                             //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////

#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <windows.h>
#include <wsipx.h>
#include <winsnmp.h>
#include <mgmtapi.h>
#include <oidconv.h>
#include <snmputil.h>


///////////////////////////////////////////////////////////////////////////////
//                                                                           //
// Private Definitions                                                       //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////

typedef struct _SNMP_MGR_SESSION {

    SOCKET            UnusedSocket;     // WARNING: Previous versions of the
    struct sockaddr   UnusedDestAddr;   // MGMTAPI.H header file exposed the
    LPSTR             UnusedCommunity;  // SNMP_MGR_SESSION structure which
    INT               UnusedTimeout;    // unfortunately encouraged people to
    INT               UnusedNumRetries; // muck with it.  Since this structure
    AsnInteger        UnusedRequestId;  // has now changed we must protect it.

    CRITICAL_SECTION  SessionLock;      // multiple threads may share session

    HSNMP_SESSION     hSnmpSession;     // handle to winsnmp session
    HSNMP_ENTITY      hAgentEntity;     // handle to agent entity
    HSNMP_ENTITY      hManagerEntity;   // handle to manager entity
    HSNMP_CONTEXT     hViewContext;     // handle to view context
    HSNMP_PDU         hPdu;             // handle to snmp pdu
    HSNMP_VBL         hVbl;             // handle to snmp pdu
    HWND              hWnd;             // handle to window

    smiINT32          nPduType;         // current pdu type
    smiINT32          nRequestId;       // current request id
    smiINT32          nErrorIndex;      // error index from pdu
    smiINT32          nErrorStatus;     // error status from pdu
    smiINT32          nLastError;       // last system error
    SnmpVarBindList * pVarBindList;     // pointer to varbind list

} SNMP_MGR_SESSION, *PSNMP_MGR_SESSION;

typedef struct _TRAP_LIST_ENTRY {

    LIST_ENTRY          Link;           // linked-list link
    AsnObjectIdentifier EnterpriseOID;  // generating enterprise
    AsnNetworkAddress   AgentAddress;   // generating agent addr
    AsnNetworkAddress   SourceAddress;  // generating network addr
    AsnInteger          nGenericTrap;   // generic trap type
    AsnInteger          nSpecificTrap;  // enterprise specific type
    AsnOctetString      Community;      // generating community
    AsnTimeticks        TimeStamp;      // time stamp
    SnmpVarBindList     VarBindList;    // variable bindings

} TRAP_LIST_ENTRY, * PTRAP_LIST_ENTRY;

#define IPADDRLEN           4
#define IPXADDRLEN          10

#define MAXENTITYSTRLEN     128

#define MINVARBINDLEN       2
#define SYSUPTIMEINDEX      0
#define SNMPTRAPOIDINDEX    1

#define DEFAULT_ADDRESS_IP  "127.0.0.1"
#define DEFAULT_ADDRESS_IPX "00000000.000000000000"

#define NOTIFICATION_CLASS  "MGMTAPI Notification Class"
#define WM_WSNMP_INCOMING   (WM_USER + 1)
#define WM_WSNMP_DONE       (WM_USER + 2)

#define WSNMP_FAILED(s)     ((s) == SNMPAPI_FAILURE)
#define WSNMP_SUCCEEDED(s)  ((s) != SNMPAPI_FAILURE)

#define WSNMP_ASSERT(s)     ASSERT((s))


///////////////////////////////////////////////////////////////////////////////
//                                                                           //
// Global Variables                                                          //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////

HINSTANCE         g_hDll;                       // module handle
HANDLE            g_hTrapEvent = NULL;          // trap event handle
HANDLE            g_hTrapThread = NULL;         // trap thread handle
HANDLE            g_hTrapRegisterdEvent = NULL; // event to sync. SnmpMgrTrapListen
BOOL              g_fIsSnmpStarted = FALSE;     // indicates winsnmp inited
BOOL              g_fIsSnmpListening = FALSE;   // indicates trap thread on
BOOL              g_fIsTrapRegistered = FALSE;  // indicates trap registered
DWORD             g_dwRequestId = 1;            // unique pdu request id
LIST_ENTRY        g_IncomingTraps;              // incoming trap queue
CRITICAL_SECTION  g_GlobalLock;                 // process resource lock
SNMP_MGR_SESSION  g_TrapSMS;                    // process trap session


///////////////////////////////////////////////////////////////////////////////
//                                                                           //
// Private Procedures                                                        //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////

DWORD
GetRequestId(
    )

/*++

Routine Description:

    Retrieve next global request id.

Arguments:

    None.

Return Values:

    Returns request id.

--*/

{
    DWORD dwRequestId;

    // obtain exclusive access to request id
    EnterCriticalSection(&g_GlobalLock);

    // obtain copy of request id
    dwRequestId = g_dwRequestId++;

    // obtain exclusive access to request id
    LeaveCriticalSection(&g_GlobalLock);

    return dwRequestId;
}


BOOL
TransferVb(
    PSNMP_MGR_SESSION pSMS,
    SnmpVarBind *     pVarBind
    )

/*++

Routine Description:

    Transfer VarBind structure to WinSNMP structure.

Arguments:

    pSMS - pointer to mgmtapi session structure.

    pVarBind - pointer to varbind to transfer.

Return Values:

    Returns true if successful.

--*/

{
    BOOL fOk = FALSE;
    SNMPAPI_STATUS status;
    smiVALUE tmpValue;
    smiOID tmpOID;

    // validate session ptr
    WSNMP_ASSERT(pSMS != NULL);

    // validate pointers
    if ((pVarBind != NULL) &&
        (pVarBind->name.ids != NULL) &&
        (pVarBind->name.idLength != 0)) {

        // re-init
        fOk = TRUE;

        // transfer oid information
        tmpOID.len = pVarBind->name.idLength;
        tmpOID.ptr = pVarBind->name.ids;

        // only initialize value if set
        if (pSMS->nPduType == SNMP_PDU_SET) {

            // syntax values are equivalent
            tmpValue.syntax = (smiINT32)(BYTE)pVarBind->value.asnType;

            // determine type
            switch (pVarBind->value.asnType) {

            case ASN_INTEGER32:

                // transfer signed int
                tmpValue.value.sNumber = pVarBind->value.asnValue.number;
                break;

            case ASN_UNSIGNED32:
            case ASN_COUNTER32:
            case ASN_GAUGE32:
            case ASN_TIMETICKS:

                // transfer unsigned int
                tmpValue.value.uNumber = pVarBind->value.asnValue.unsigned32;
                break;

            case ASN_COUNTER64:

                // transfer 64-bit counter
                tmpValue.value.hNumber.lopart =
                    pVarBind->value.asnValue.counter64.LowPart;
                tmpValue.value.hNumber.hipart =
                    pVarBind->value.asnValue.counter64.HighPart;
                break;

            case ASN_OPAQUE:
            case ASN_IPADDRESS:
            case ASN_OCTETSTRING:
            case ASN_BITS:

                // transfer octet string
                tmpValue.value.string.len =
                    pVarBind->value.asnValue.string.length;
                tmpValue.value.string.ptr =
                    pVarBind->value.asnValue.string.stream;
                break;

            case ASN_OBJECTIDENTIFIER:

                // transfer object id
                tmpValue.value.oid.len =
                    pVarBind->value.asnValue.object.idLength;
                tmpValue.value.oid.ptr =
                    pVarBind->value.asnValue.object.ids;
                break;

            case ASN_NULL:
            case SNMP_EXCEPTION_NOSUCHOBJECT:
            case SNMP_EXCEPTION_NOSUCHINSTANCE:
            case SNMP_EXCEPTION_ENDOFMIBVIEW:

                // initialize empty byte
                tmpValue.value.empty = 0;
                break;

            default:

                // failure
                fOk = FALSE;
                break;
            }
        }

        if (fOk) {

            // register varbind
            status = SnmpSetVb(
                        pSMS->hVbl,
                        0, // index
                        &tmpOID,
                        (pSMS->nPduType == SNMP_PDU_SET)
                            ? &tmpValue
                            : NULL
                        );

            // validate return code
            if (WSNMP_FAILED(status)) {

                SNMPDBG((
                    SNMP_LOG_ERROR,
                    "MGMTAPI: SnmpSetVb returned %d.\n",
                    SnmpGetLastError(pSMS->hSnmpSession)
                    ));

                // failure
                fOk = FALSE;
            }
        }
    }

    return fOk;
}


BOOL
AllocateVbl(
    PSNMP_MGR_SESSION pSMS
    )

/*++

Routine Description:

    Transfer VarBindList structure to WinSNMP structure.

Arguments:

    pSMS - pointer to mgmtapi session structure.

Return Values:

    Returns true if successful.

--*/

{
    BOOL fOk = FALSE;
    SNMPAPI_STATUS status;
    SnmpVarBind * pVarBind;
    DWORD cVarBind;

    // validate session ptr
    WSNMP_ASSERT(pSMS != NULL);

    // validate parameters
    WSNMP_ASSERT(pSMS->pVarBindList != NULL);
    WSNMP_ASSERT(pSMS->pVarBindList->len != 0);
    WSNMP_ASSERT(pSMS->pVarBindList->list != NULL);

    // allocate resources for variable bindings list
    pSMS->hVbl = SnmpCreateVbl(pSMS->hSnmpSession, NULL, NULL);

    // validate varbind handle
    if (WSNMP_SUCCEEDED(pSMS->hVbl)) {

        // re-init
        fOk = TRUE;

        // initialize varbind pointer
        pVarBind = pSMS->pVarBindList->list;

        // initialize varbind count
        cVarBind = pSMS->pVarBindList->len;

        // process each varbind
        while (fOk && cVarBind--) {

            // transfer variable binding
            fOk = TransferVb(pSMS, pVarBind++);
        }

        if (!fOk) {

            // release varbind list handle
            status = SnmpFreeVbl(pSMS->hVbl);

            // validate return code
            if (WSNMP_FAILED(status)) {

                SNMPDBG((
                    SNMP_LOG_ERROR,
                    "MGMTAPI: SnmpFreeVbl returned %d.\n",
                    SnmpGetLastError(pSMS->hSnmpSession)
                    ));
            }

            // re-initialize
            pSMS->hVbl = (HSNMP_VBL)NULL;
        }

    } else {

        SNMPDBG((
            SNMP_LOG_ERROR,
            "MGMTAPI: SnmpCreateVbl returned %d.\n",
            SnmpGetLastError(pSMS->hSnmpSession)
            ));
    }

    return fOk;
}


BOOL
FreeVbl(
    PSNMP_MGR_SESSION pSMS
    )

/*++

Routine Description:

    Cleanup VarBind resources from WinSNMP structure.

Arguments:

    pSMS - pointer to mgmtapi session structure.

Return Values:

    Returns true if successful.

--*/

{
    BOOL fOk = TRUE;
    SNMPAPI_STATUS status;

    // validate session ptr
    WSNMP_ASSERT(pSMS != NULL);

    // validate handle
    if (pSMS->hVbl != (HSNMP_VBL)NULL) {

        // actually release vbl handle
        status = SnmpFreeVbl(pSMS->hVbl);

        // validate return code
        if (WSNMP_FAILED(status)) {

            SNMPDBG((
                SNMP_LOG_ERROR,
                "MGMTAPI: SnmpFreeVbl returned %d.\n",
                SnmpGetLastError(pSMS->hSnmpSession)
                ));

            // failure
            fOk = FALSE;
        }

        // re-initialize handle
        pSMS->hVbl = (HSNMP_VBL)NULL;
    }

    return fOk;
}


BOOL
AllocatePdu(
    PSNMP_MGR_SESSION pSMS
    )

/*++

Routine Description:

    Initialize session structure for sending request.

Arguments:

    pSMS - pointer to mgmtapi session structure.

Return Values:

    Returns true if successful.

--*/

{
    BOOL fOk = FALSE;

    // validate session ptr
    WSNMP_ASSERT(pSMS != NULL);

    // transfer varbinds
    if (AllocateVbl(pSMS)) {

        // grab next shared request id
        pSMS->nRequestId = GetRequestId();

        // create request pdu
        pSMS->hPdu = SnmpCreatePdu(
                        pSMS->hSnmpSession,
                        pSMS->nPduType,
                        pSMS->nRequestId,
                        0, // errorStatus
                        0, // errorIndex
                        pSMS->hVbl
                        );

        // validate return status
        if (WSNMP_SUCCEEDED(pSMS->hPdu)) {

            // success
            fOk = TRUE;

        } else {

            SNMPDBG((
                SNMP_LOG_ERROR,
                "MGMTAPI: SnmpCreatePdu returned %d.\n",
                SnmpGetLastError(pSMS->hSnmpSession)
                ));

            // free resources
            FreeVbl(pSMS);
        }
    }

    return fOk;
}


BOOL
FreePdu(
    PSNMP_MGR_SESSION pSMS
    )

/*++

Routine Description:

    Cleanup session structure after processing response.

Arguments:

    pSMS - pointer to mgmtapi session structure.

Return Values:

    Returns true if successful.

--*/

{
    BOOL fOk = TRUE;
    SNMPAPI_STATUS status;

    // validate session ptr
    WSNMP_ASSERT(pSMS != NULL);

    // validate handle
    if (pSMS->hPdu != (HSNMP_PDU)NULL) {

        // free vbl
        FreeVbl(pSMS);

        // actually release pdu handle
        status = SnmpFreePdu(pSMS->hPdu);

        // validate return code
        if (WSNMP_FAILED(status)) {

            SNMPDBG((
                SNMP_LOG_ERROR,
                "MGMTAPI: SnmpFreePdu returned %d.\n",
                SnmpGetLastError(pSMS->hSnmpSession)
                ));

            // failure
            fOk = FALSE;
        }

        // re-initialize handle
        pSMS->hPdu = (HSNMP_PDU)NULL;
    }

    return fOk;
}


BOOL
CopyOid(
    AsnObjectIdentifier * pDstOID,
    smiLPOID              pSrcOID
    )

/*++

Routine Description:

    Copies object identifier from WinSNMP format to MGMTAPI format.

Arguments:

    pDstOID - points to MGMTAPI structure to receive OID.

    pSrcOID - points to WinSNMP structure to copy.

Return Values:

    Returns true if successful.

--*/

{
    BOOL fOk = FALSE;

    // validate pointers
    WSNMP_ASSERT(pDstOID != NULL);
    WSNMP_ASSERT(pSrcOID != NULL);
    WSNMP_ASSERT(pSrcOID->len != 0);
    WSNMP_ASSERT(pSrcOID->ptr != NULL);

    // store the number of subids
    pDstOID->idLength = pSrcOID->len;

    // allocate memory for subidentifiers
    pDstOID->ids = SnmpUtilMemAlloc(pDstOID->idLength * sizeof(DWORD));

    // validate pointer
    if (pDstOID->ids != NULL) {

        // transfer memory
        memcpy(pDstOID->ids,
               pSrcOID->ptr,
               pDstOID->idLength * sizeof(DWORD)
               );

        // success
        fOk = TRUE;
    }

    // now release memory for original oid
    SnmpFreeDescriptor(SNMP_SYNTAX_OID, (smiLPOPAQUE)pSrcOID);

    return fOk;
}


BOOL
CopyOctets(
    AsnOctetString * pDstOctets,
    smiLPOCTETS      pSrcOctets
    )

/*++

Routine Description:

    Copies octet string from WinSNMP format to MGMTAPI format.

Arguments:

    pDstOctets - points to MGMTAPI structure to receive octets.

    pSrcOctets - points to WinSNMP structure to copy.

Return Values:

    Returns true if successful.

--*/

{
    BOOL fOk = FALSE;
    SNMPAPI_STATUS status;

    // validate pointers
    WSNMP_ASSERT(pDstOctets != NULL);
    WSNMP_ASSERT(pSrcOctets != NULL);

    // it is legitimate that 
    // 1. pSrcOctets->len == 0
    // 2. pSrcOctets->ptr == NULL

    if (pSrcOctets->len == 0 || pSrcOctets->ptr == NULL)
    {
        pDstOctets->dynamic = FALSE;
        pDstOctets->length = 0;
        pDstOctets->stream = NULL;
        fOk = TRUE;
    }
    else
    {
        // allocate memory for octet string
        pDstOctets->stream = SnmpUtilMemAlloc(pSrcOctets->len);

        // validate pointer
        if (pDstOctets->stream != NULL) {

            // octet string allocated
            pDstOctets->dynamic = TRUE;

            // store the number of bytes
            pDstOctets->length = pSrcOctets->len;
       
            // transfer memory
            memcpy(pDstOctets->stream,
                   pSrcOctets->ptr,
                   pDstOctets->length
                   );

            // success
            fOk = TRUE;
        }
    }

    // now release memory for original string
    SnmpFreeDescriptor(SNMP_SYNTAX_OCTETS, (smiLPOPAQUE)pSrcOctets);

    return fOk;
}


CopyVb(
    PSNMP_MGR_SESSION pSMS,
    DWORD             iVarBind,
    SnmpVarBind *     pVarBind
    )

/*++

Routine Description:

    Copy variable binding from WinSNMP structure to MGMTAPI structure.

Arguments:

    pSMS - pointer to mgmtapi session structure.

    iVarBind - index of varbind structure to copy.

    pVarBind - pointer to varbind structure.

Return Values:

    Returns true if successful.

--*/

{
    BOOL fOk = FALSE;
    SNMPAPI_STATUS status;
    smiOID tmpOID;
    smiVALUE tmpValue;

    // validate session ptr
    WSNMP_ASSERT(pSMS != NULL);
    WSNMP_ASSERT(pVarBind != NULL);

    // attempt to retrieve varbind data from winsnmp structure
    status = SnmpGetVb(pSMS->hVbl, iVarBind, &tmpOID, &tmpValue);

    // validate return code
    if (WSNMP_SUCCEEDED(status)) {

        // transfer object identifier value
        fOk = CopyOid(&pVarBind->name, &tmpOID);

        // syntax values are equivalent
        pVarBind->value.asnType = (BYTE)(smiINT32)tmpValue.syntax;

        // determine syntax
        switch (tmpValue.syntax) {

        case SNMP_SYNTAX_INT32:

            // transfer signed int
            pVarBind->value.asnValue.number = tmpValue.value.sNumber;
            break;

        case SNMP_SYNTAX_UINT32:
        case SNMP_SYNTAX_CNTR32:
        case SNMP_SYNTAX_GAUGE32:
        case SNMP_SYNTAX_TIMETICKS:

            // transfer unsigned int
            pVarBind->value.asnValue.unsigned32 = tmpValue.value.uNumber;
            break;

        case SNMP_SYNTAX_CNTR64:

            // transfer 64-bit counter
            pVarBind->value.asnValue.counter64.LowPart =
                tmpValue.value.hNumber.lopart;
            pVarBind->value.asnValue.counter64.HighPart =
                tmpValue.value.hNumber.hipart;
            break;

        case SNMP_SYNTAX_OPAQUE:
        case SNMP_SYNTAX_IPADDR:
        case SNMP_SYNTAX_OCTETS:
        case SNMP_SYNTAX_BITS:

            // transfer octet string
            if (!CopyOctets(&pVarBind->value.asnValue.string,
                            &tmpValue.value.string)) {
              
                // re-initialize
                pVarBind->value.asnType = ASN_NULL;

                // failure
                fOk = FALSE;
            }

            break;

        case SNMP_SYNTAX_OID:

            // transfer object identifier
            if (!CopyOid(&pVarBind->value.asnValue.object,
                         &tmpValue.value.oid)) {

                // re-initialize
                pVarBind->value.asnType = ASN_NULL;

                // failure
                fOk = FALSE;
            }

            break;

        case SNMP_SYNTAX_NULL:
        case SNMP_SYNTAX_NOSUCHOBJECT:
        case SNMP_SYNTAX_NOSUCHINSTANCE:
        case SNMP_SYNTAX_ENDOFMIBVIEW:

            break; // do nothing...

        default:

            SNMPDBG((
                SNMP_LOG_ERROR,
                "MGMTAPI: SnmpGetVb returned invalid type.\n"
                ));

            // re-initialize
            pVarBind->value.asnType = ASN_NULL;

           // failure
            fOk = FALSE;

            break;
        }

    } else {

        SNMPDBG((
            SNMP_LOG_ERROR,
            "MGMTAPI: SnmpGetVb returned %d.\n",
            SnmpGetLastError(pSMS->hSnmpSession)
            ));
    }

    return fOk;
}


BOOL
CopyVbl(
    PSNMP_MGR_SESSION pSMS,
    SnmpVarBindList * pVarBindList
    )

/*++

Routine Description:

    Copy variable bindings from WinSNMP structure to MGMTAPI structure.

Arguments:

    pSMS - pointer to mgmtapi session structure.

    pVarBindList - pointer to varbind list structure.

Return Values:

    Returns true if successful.

--*/

{
    BOOL fOk = TRUE;

    // validate session ptr
    WSNMP_ASSERT(pSMS != NULL);
    WSNMP_ASSERT(pVarBindList != NULL);

    // initialize
    pVarBindList->len  = 0;
    pVarBindList->list = NULL;

    // validate varbind list handle
    if (pSMS->hVbl != (HSNMP_VBL)NULL) {

        // determine number of varbinds
        pVarBindList->len = SnmpCountVbl(pSMS->hVbl);

        // validate number of varbinds
        if (WSNMP_SUCCEEDED(pVarBindList->len)) {

            // allocate memory for varbinds
            pVarBindList->list = SnmpUtilMemAlloc(
                                    pVarBindList->len *
                                    sizeof(SnmpVarBind)
                                    );

            // validate pointer
            if (pVarBindList->list != NULL) {

                DWORD cVarBind = 1;
                SnmpVarBind * pVarBind;

                // save pointer to varbinds
                pVarBind = pVarBindList->list;

                // process varbinds in the list
                while (fOk && (cVarBind <= pVarBindList->len)) {

                    // copy varbind from winsnmp to mgmtapi
                    fOk = CopyVb(pSMS, cVarBind++, pVarBind++);
                }

            } else {

                SNMPDBG((
                    SNMP_LOG_ERROR,
                    "MGMTAPI: Could not allocate VBL.\n"
                    ));

                // re-initialize
                pVarBindList->len = 0;

                // failure
                fOk = FALSE;
            }

        } else if (pVarBindList->len != SNMPAPI_NOOP) {

            SNMPDBG((
                SNMP_LOG_ERROR,
                "MGMTAPI: SnmpCountVbl returned %s.\n",
                SnmpGetLastError(pSMS->hSnmpSession)
                ));

            // re-initialize
            pVarBindList->len = 0;

            // failure
            fOk = FALSE;
        }
    }

    if (!fOk) {

        // cleanup any varbinds allocated
        SnmpUtilVarBindListFree(pVarBindList);
    }

    return fOk;
}


BOOL
ParseVbl(
    PSNMP_MGR_SESSION pSMS,
    PTRAP_LIST_ENTRY  pTLE
    )

/*++

Routine Description:

    Parse varbind list for trap-related varbinds.

Arguments:

    pSMS - pointer to MGMTAPI session structure.

    pTLE - pointer to trap list entry.

Return Values:

    Returns true if successful.

--*/

{
    BOOL fOk = FALSE;
    SnmpVarBind * pVarBind;
    AsnObjectIdentifier * pOID;
    AsnNetworkAddress   * pAgentAddress = NULL;
    AsnObjectIdentifier * pEnterpriseOID = NULL;

    // object identifiers to convert snmpv2 trap format
    static UINT _sysUpTime[]             = { 1, 3, 6, 1, 2, 1, 1, 3       };
    static UINT _snmpTrapOID[]           = { 1, 3, 6, 1, 6, 3, 1, 1, 4, 1 };
    static UINT _snmpAddress[]           = { 1, 3, 6, 1, 3, 1057, 1       };
    static UINT _snmpTrapEnterprise[]    = { 1, 3, 6, 1, 6, 3, 1, 1, 4, 3 };
    static UINT _snmpTraps[]             = { 1, 3, 6, 1, 6, 3, 1, 1, 5    };

    static AsnObjectIdentifier sysUpTime          = DEFINE_OID(_sysUpTime);
    static AsnObjectIdentifier snmpTrapOID        = DEFINE_OID(_snmpTrapOID);
    static AsnObjectIdentifier snmpAddress        = DEFINE_OID(_snmpAddress);
    static AsnObjectIdentifier snmpTrapEnterprise = DEFINE_OID(_snmpTrapEnterprise);
    static AsnObjectIdentifier snmpTraps          = DEFINE_OID(_snmpTraps);

    // validate pointers
    WSNMP_ASSERT(pSMS != NULL);
    WSNMP_ASSERT(pTLE != NULL);

    // validate vbl have minimum entries
    if (pTLE->VarBindList.len >= MINVARBINDLEN) {

        // point to sysUpTime varbind structure
        pVarBind = &pTLE->VarBindList.list[SYSUPTIMEINDEX];

        // verify variable is sysUpTime
        if ((pVarBind->value.asnType == ASN_TIMETICKS) &&
            !SnmpUtilOidNCmp(&pVarBind->name,
                             &sysUpTime,
                             sysUpTime.idLength)) {

            // transfer sysUpTime value to trap entry
            pTLE->TimeStamp = pVarBind->value.asnValue.ticks;

        } else {

            SNMPDBG((
                SNMP_LOG_ERROR,
                "MGMTAPI: Could not find sysUpTime.\n"
                ));

            goto cleanup; // bail...
        }

        // see if any additional varbinds present
        if (pTLE->VarBindList.len > MINVARBINDLEN) {

            // point to snmpTrapEnterprise varbind structure (maybe)
            pVarBind = &pTLE->VarBindList.list[pTLE->VarBindList.len - 1];

            // verify variable is snmpTrapEnterprise
            if ((pVarBind->value.asnType == ASN_OBJECTIDENTIFIER) &&
                !SnmpUtilOidNCmp(&pVarBind->name,
                                 &snmpTrapEnterprise,
                                 snmpTrapEnterprise.idLength))  {

                // transfer enterprise oid to list entry
                pTLE->EnterpriseOID = pVarBind->value.asnValue.object;

                // store enterprise oid for later
                pEnterpriseOID = &pTLE->EnterpriseOID;

                // modify type to avoid deallocation
                pVarBind->value.asnType = ASN_NULL;

            } else {

                SNMPDBG((
                    SNMP_LOG_TRACE,
                    "MGMTAPI: Could not find snmpTrapEnterprise.\n"
                    ));
            }
        }

        // see if the agent address is present
        if (pTLE->VarBindList.len > MINVARBINDLEN+1) {
            
            // point to snmpAddress varbind structure (maybe)
            pVarBind = &pTLE->VarBindList.list[pTLE->VarBindList.len - 2];

            // verify variable is snmpAddress
            if ((pVarBind->value.asnType == SNMP_SYNTAX_IPADDR) &&
                !SnmpUtilOidNCmp(&pVarBind->name,
                                 &snmpAddress,
                                 snmpAddress.idLength))  {

                // transfer agent address oid to list entry
                pTLE->AgentAddress = pVarBind->value.asnValue.address;

                // store agent address for later
                pAgentAddress = &pTLE->AgentAddress;

                // modify type to avoid deallocation
                pVarBind->value.asnType = ASN_NULL;

            } else {

                SNMPDBG((
                    SNMP_LOG_TRACE,
                    "MGMTAPI: Could not find snmpAddress.\n"
                    ));
            }
        }

        // point to snmpTrapOID varbind structure
        pVarBind = &pTLE->VarBindList.list[SNMPTRAPOIDINDEX];

        // verify variable is snmpTrapOID
        if ((pVarBind->value.asnType == ASN_OBJECTIDENTIFIER) &&
            !SnmpUtilOidNCmp(&pVarBind->name,
                             &snmpTrapOID,
                             snmpTrapOID.idLength))  {

            // retrieve pointer to oid
            pOID = &pVarBind->value.asnValue.object;

            // check for generic trap
            if (!SnmpUtilOidNCmp(pOID,
                                 &snmpTraps,
                                 snmpTraps.idLength)) {

                // validate size is one greater than root
                if (pOID->idLength == (snmpTraps.idLength + 1)) {

                    // retrieve trap id
                    // --ft:10/01/98 (bug #231344): WINSNMP gives up the V2 syntax => pOID->ids[snmpTraps.idLength] = [1..6]
                    // --ft:10/01/98 (bug #231344): as MGMTAPI turns back to V1, we need to decrement this value.
                    pTLE->nGenericTrap = (pOID->ids[snmpTraps.idLength])-1;

                    // re-initialize
                    pTLE->nSpecificTrap = 0;

                } else {

                    SNMPDBG((
                        SNMP_LOG_ERROR,
                        "MGMTAPI: Invalid snmpTrapOID.\n"
                        ));

                    goto cleanup; // bail...
                }

            // check for specific trap
            } else if ((pEnterpriseOID != NULL) &&
                       !SnmpUtilOidNCmp(pOID,
                                        pEnterpriseOID,
                                        pEnterpriseOID->idLength)) {

                // validate size is two greater than root
                if (pOID->idLength == (pEnterpriseOID->idLength + 2)) {

                    // validate separator sub-identifier
                    WSNMP_ASSERT(pOID->ids[pEnterpriseOID->idLength] == 0);

                    // retrieve trap id
                    pTLE->nSpecificTrap = pOID->ids[pEnterpriseOID->idLength + 1];

                    // re-initialize
                    pTLE->nGenericTrap = SNMP_GENERICTRAP_ENTERSPECIFIC;

                } else {

                    SNMPDBG((
                        SNMP_LOG_ERROR,
                        "MGMTAPI: Invalid snmpTrapOID.\n"
                        ));

                    goto cleanup; // bail...
                }

            } else {

                SNMPDBG((
                    SNMP_LOG_ERROR,
                    "MGMTAPI: Could not identify snmpTrapOID.\n"
                    ));

               goto cleanup; // bail...
            }

        } else {

            SNMPDBG((
                SNMP_LOG_ERROR,
                "MGMTAPI: Could not find snmpTrapOID.\n"
                ));

            goto cleanup; // bail...
        }

        // check for enterprise oid
        if (pEnterpriseOID != NULL) {

            // release snmpTrapEnterprise varbind structure
            SnmpUtilVarBindFree(&pTLE->VarBindList.list[pTLE->VarBindList.len - 1]);

            // decrement the list length as the last varbind was freed
            pTLE->VarBindList.len--;
        }

        // check for agent address
        if (pAgentAddress != NULL) {

            // release snmpAgentAddress varbind structure
            SnmpUtilVarBindFree(&pTLE->VarBindList.list[pTLE->VarBindList.len - 1]);

            // decrement the list length as the last varbind was again freed
            pTLE->VarBindList.len--;
        }

        // release sysUpTime varbind structure
        SnmpUtilVarBindFree(&pTLE->VarBindList.list[SYSUPTIMEINDEX]);

        // release snmpTrapOID varbind structure
        SnmpUtilVarBindFree(&pTLE->VarBindList.list[SNMPTRAPOIDINDEX]);

        // subtract released varbinds
        pTLE->VarBindList.len -= MINVARBINDLEN;

        // check if all varbinds freed
        if (pTLE->VarBindList.len == 0) {

            // release memory for list
            SnmpUtilMemFree(pTLE->VarBindList.list);

            // re-initialize
            pTLE->VarBindList.list = NULL;

        } else {

            // shift varbind list up two spaces
            memmove((LPBYTE)(pTLE->VarBindList.list),
                    (LPBYTE)(pTLE->VarBindList.list + MINVARBINDLEN),
                    (pTLE->VarBindList.len * sizeof(SnmpVarBind))
                    );
        }

    } else {

        SNMPDBG((
            SNMP_LOG_ERROR,
            "MGMTAPI: Too few subidentifiers.\n"
            ));
    }

    // success
    return TRUE;

cleanup:

    // failure
    return FALSE;
}


BOOL
FreeTle(
    PTRAP_LIST_ENTRY pTLE
    )

/*++

Routine Description:

    Release memory used for trap entry.

Arguments:

    pTLE - pointer to trap list entry.

Return Values:

    Returns true if successful.

--*/

{
    // validate pointer
    WSNMP_ASSERT(pTLE != NULL);

    // release memory for enterprise oid
    SnmpUtilOidFree(&pTLE->EnterpriseOID);

    // release memory for community string
    SnmpUtilMemFree(pTLE->Community.stream);

    // release memory used in varbind list
    SnmpUtilVarBindListFree(&pTLE->VarBindList);

    // release list entry
    SnmpUtilMemFree(pTLE);

    return TRUE;
}


BOOL
AllocateTle(
    PSNMP_MGR_SESSION  pSMS,
    PTRAP_LIST_ENTRY * ppTLE,
    HSNMP_ENTITY       hAgentEntity,
    HSNMP_CONTEXT      hViewContext
    )

/*++

Routine Description:

    Allocate memory for trap entry.

Arguments:

    pSMS - pointer to MGMTAPI session structure.

    ppTLE - pointer to pointer to trap list entry.

    hAgentEntity - handle to agent sending trap.

    hViewContext - handle to view context of trap.

Return Values:

    Returns true if successful.

--*/

{
    BOOL fOk = FALSE;
    PTRAP_LIST_ENTRY pTLE;
    SNMPAPI_STATUS status;
    smiOCTETS CommunityStr;
    CHAR SourceStrAddr[MAXENTITYSTRLEN+1];
    struct sockaddr SourceSockAddr;

    // validate pointers
    WSNMP_ASSERT(pSMS != NULL);
    WSNMP_ASSERT(ppTLE != NULL);

    // allocate memory from list entry
    pTLE = SnmpUtilMemAlloc(sizeof(TRAP_LIST_ENTRY));

    // validate pointer
    if (pTLE == NULL) {

        SNMPDBG((
            SNMP_LOG_ERROR,
            "MGMTAPI: Could not allocate trap entry.\n"
            ));

        return FALSE; // bail...
    }

    // initialize
    *ppTLE = NULL;

    // copy varbinds to trap list entry
    if (!CopyVbl(pSMS, &pTLE->VarBindList)) {
        goto cleanup; // bail...
    }

    // parse trap-related varbinds
    if (!ParseVbl(pSMS, pTLE)) {
        goto cleanup; // bail...
    }

    // check if source address is specified
    if (hAgentEntity != (HSNMP_ENTITY)NULL) {

        // convert addr to string
        status = SnmpEntityToStr(
                    hAgentEntity,
                    sizeof(SourceStrAddr),
                    SourceStrAddr
                    );

        // validate error code
        if (WSNMP_SUCCEEDED(status)) {

            DWORD  AddrLen = 0;
            LPBYTE AddrPtr = NULL;

            // convert string to socket address structure
            SnmpSvcAddrToSocket(SourceStrAddr, &SourceSockAddr);

            // validate address family
            if (SourceSockAddr.sa_family == AF_INET) {

                // assign ip values
                AddrLen = IPADDRLEN;
                AddrPtr = (LPBYTE)&(((struct sockaddr_in *)
                            (&SourceSockAddr))->sin_addr);

            } else if (SourceSockAddr.sa_family == AF_IPX) {

                // assign ipx values
                AddrLen = IPXADDRLEN;
                AddrPtr = (LPBYTE)&(((struct sockaddr_ipx *)
                            (&SourceSockAddr))->sa_netnum);

            } else {

                SNMPDBG((
                    SNMP_LOG_ERROR,
                    "MGMTAPI: Ignoring invalid address.\n"
                    ));

                goto cleanup; // bail...
            }

            // allocate address to return (if specified)
            pTLE->SourceAddress.stream = SnmpUtilMemAlloc(AddrLen);

            // validate pointer
            if (pTLE->SourceAddress.stream != NULL) {

                // initialize length values
                pTLE->SourceAddress.length  = AddrLen;
                pTLE->SourceAddress.dynamic = TRUE;

                // transfer agent address information
                memcpy(pTLE->SourceAddress.stream, AddrPtr, AddrLen);
            }

        } else {

            SNMPDBG((
                SNMP_LOG_ERROR,
                "MGMTAPI: SnmpEntityToStr returned %d.\n",
                SnmpGetLastError((HSNMP_SESSION)NULL)
                ));

            goto cleanup; // bail...
        }
    }

    // check if community specified
    if (hViewContext != (HSNMP_CONTEXT)NULL) {

        // convert agent entity to string
        status = SnmpContextToStr(hViewContext, &CommunityStr);

        // validate error code
        if (WSNMP_SUCCEEDED(status)) {

            // copy octet string, memory allocated in CommunityStr is also freed
            CopyOctets(&pTLE->Community, &CommunityStr);

        } else {

            SNMPDBG((
                SNMP_LOG_ERROR,
                "MGMTAPI: SnmpContextToStr returned %d.\n",
                SnmpGetLastError((HSNMP_SESSION)NULL)
                ));

            goto cleanup; // bail...
        }
    }

    // transfer
    *ppTLE = pTLE;

    // success
    return TRUE;

cleanup:

    // release
    FreeTle(pTLE);

    // failure
    return FALSE;
}


BOOL
NotificationCallback(
    PSNMP_MGR_SESSION pSMS
    )

/*++

Routine Description:

    Callback for processing notification messages.

Arguments:

    pSMS - pointer to mgmtapi session structure.

Return Values:

    Returns true if processing finished.

--*/

{
    BOOL fDone = TRUE;
    SNMPAPI_STATUS status;
    HSNMP_ENTITY   hAgentEntity   = (HSNMP_ENTITY)NULL;
    HSNMP_ENTITY   hManagerEntity = (HSNMP_ENTITY)NULL;
    HSNMP_CONTEXT  hViewContext   = (HSNMP_CONTEXT)NULL;
    smiINT32       nPduType;
    smiINT32       nRequestId;

    // validate pointer
    WSNMP_ASSERT(pSMS != NULL);

    // retrieve message
    status = SnmpRecvMsg(
                pSMS->hSnmpSession,
                &hAgentEntity,
                &hManagerEntity,
                &hViewContext,
                &pSMS->hPdu
                );

    // validate return code
    if (WSNMP_SUCCEEDED(status)) {

        // retrieve pdu data
        status = SnmpGetPduData(
                    pSMS->hPdu,
                    &nPduType,
                    &nRequestId,
                    &pSMS->nErrorStatus,
                    &pSMS->nErrorIndex,
                    &pSMS->hVbl
                    );

        // validate return code
        if (WSNMP_SUCCEEDED(status)) {

            // process reponse to request
            if (nPduType == SNMP_PDU_RESPONSE) {

                // validate context information
                if ((pSMS->nRequestId == nRequestId) &&
                    (pSMS->hViewContext == hViewContext) &&
                    (pSMS->hAgentEntity == hAgentEntity) &&
                    (pSMS->hManagerEntity == hManagerEntity)) {

                    // validate returned error status
                    if (pSMS->nErrorStatus == SNMP_ERROR_NOERROR) {

                        SnmpVarBindList VarBindList;

                        // copy variable binding list
                        if (CopyVbl(pSMS, &VarBindList)) {

                            // release existing varbind list
                            SnmpUtilVarBindListFree(pSMS->pVarBindList);

                            // manually copy new varbind list
                            *pSMS->pVarBindList = VarBindList;

                        } else {

                            // modify last error status
                            pSMS->nLastError = SNMPAPI_ALLOC_ERROR;
                        }
                    }

                } else {

                    SNMPDBG((
                        SNMP_LOG_TRACE,
                        "MGMTAPI: Ignoring invalid context.\n"
                        ));

                    // continue
                    fDone = FALSE;
                }

            } else if (nPduType == SNMP_PDU_TRAP) {

                PTRAP_LIST_ENTRY pTLE;

                // allocate trap list entry (transfers varbinds etc.)
                if (AllocateTle(pSMS, &pTLE, hAgentEntity, hViewContext)) {

                    // obtain exclusive access
                    EnterCriticalSection(&g_GlobalLock);

                    // insert new trap into the incoming queue
                    InsertTailList(&g_IncomingTraps, &pTLE->Link);

                    // alert user
                    SetEvent(g_hTrapEvent);

                    // release exclusive access
                    LeaveCriticalSection(&g_GlobalLock);
                }

            } else {

                SNMPDBG((
                    SNMP_LOG_ERROR,
                    "MGMTAPI: Ignoring invalid pdu type %d.\n",
                    nPduType
                    ));

                // continue
                fDone = FALSE;
            }

        } else {

            SNMPDBG((
                SNMP_LOG_ERROR,
                "MGMTAPI: SnmpGetPduData returned %d.\n",
                SnmpGetLastError(pSMS->hSnmpSession)
                ));

            // retrieve last error status from winsnmp
            pSMS->nLastError = SnmpGetLastError(pSMS->hSnmpSession);
        }

        // release temporary entity
        SnmpFreeEntity(hAgentEntity);

        // release temporary entity
        SnmpFreeEntity(hManagerEntity);

        // release temporary context
        SnmpFreeContext(hViewContext);

    } else {

        SNMPDBG((
            SNMP_LOG_ERROR,
            "MGMTAPI: SnmpRecvMsg returned %d.\n",
            SnmpGetLastError(pSMS->hSnmpSession)
            ));

        // retrieve last error status from winsnmp
        pSMS->nLastError = SnmpGetLastError(pSMS->hSnmpSession);
    }

    // release pdu
    FreePdu(pSMS);

    return fDone;
}


LRESULT
CALLBACK
NotificationWndProc(
    HWND   hWnd,
    UINT   uMsg,
    WPARAM wParam,
    LPARAM lParam
    )

/*++

Routine Description:

    Callback that processes WinSNMP notifications.

Arguments:

    hWnd - window handle.

    uMsg - message identifier.

    wParam - first message parameter.

    lParam - second message parameter.

Return Values:

    The return value is the result of the message processing and
    depends on the message sent.

--*/

{
    // check for winsnmp notification and transport timeout
    if (uMsg == WM_WSNMP_INCOMING && wParam == SNMPAPI_TL_TIMEOUT) {
        
        PSNMP_MGR_SESSION pSMS;

        // retrieve mgmtapi session pointer from window
        pSMS = (PSNMP_MGR_SESSION)GetWindowLongPtr(hWnd, 0);

        // validate session ptr
        WSNMP_ASSERT(pSMS != NULL);

        // translate winsnmp error to mgmtapi error
        pSMS->nLastError = SNMP_MGMTAPI_TIMEOUT;

        // post message to break out of message pump
        PostMessage(pSMS->hWnd, WM_WSNMP_DONE, (WPARAM)0, (LPARAM)0);
        
        return (LRESULT)0;
    }
    // check for winsnmp notification
    else if (uMsg == WM_WSNMP_INCOMING) {

        PSNMP_MGR_SESSION pSMS;

        // retrieve mgmtapi session pointer from window
        pSMS = (PSNMP_MGR_SESSION)GetWindowLongPtr(hWnd, 0);

        // validate session ptr
        WSNMP_ASSERT(pSMS != NULL);

        // process notification message
        if (NotificationCallback(pSMS)) {

            // post message to break out of message pump
            PostMessage(pSMS->hWnd, WM_WSNMP_DONE, (WPARAM)0, (LPARAM)0);
        }

        return (LRESULT)0;

    } else {

        // forward all other messages to windows
        return DefWindowProc(hWnd, uMsg, wParam, lParam);
    }
}


BOOL
RegisterNotificationClass(
    )

/*++

Routine Description:

    Register notification class for sessions.

Arguments:

    None.

Return Values:

    Returns true if successful.

--*/

{
    BOOL fOk;
    WNDCLASS wc;

    // initialize notification window class
    wc.lpfnWndProc   = (WNDPROC)NotificationWndProc;
    wc.lpszClassName = NOTIFICATION_CLASS;
    wc.lpszMenuName  = NULL;
    wc.hInstance     = g_hDll;
    wc.hIcon         = NULL;
    wc.hCursor       = NULL;
    wc.hbrBackground = NULL;
    wc.cbWndExtra    = sizeof(PSNMP_MGR_SESSION);
    wc.cbClsExtra    = 0;
    wc.style         = 0;

    // register class
    fOk = RegisterClass(&wc);

    if (!fOk) {

        SNMPDBG((
            SNMP_LOG_ERROR,
            "MGMTAPI: RegisterClass returned %d.\n",
            GetLastError()
            ));
    }

    return fOk;
}


BOOL
UnregisterNotificationClass(
    )

/*++

Routine Description:

    Unregister notification class.

Arguments:

    None.

Return Values:

    Returns true if successful.

--*/

{
    BOOL fOk;

    // unergister notification window class
    fOk = UnregisterClass(NOTIFICATION_CLASS, g_hDll);

    if (!fOk) {

        SNMPDBG((
            SNMP_LOG_ERROR,
            "MGMTAPI: UnregisterClass returned %d.\n",
            GetLastError()
            ));
    }

    return fOk;
}


BOOL
StartSnmpIfNecessary(
    )

/*++

Routine Description:

    Initialize WinSNMP DLL if necessary.

Arguments:

    None.

Return Values:

    Returns true if successful.

--*/

{
    BOOL fOk = TRUE;

    // serialize access to startup code
    EnterCriticalSection(&g_GlobalLock);

    // see if already started
    if (g_fIsSnmpStarted != TRUE) {

        SNMPAPI_STATUS status;

        // initialize start params
        smiUINT32 nMajorVersion   = 0;
        smiUINT32 nMinorVersion   = 0;
        smiUINT32 nLevel          = 0;
        smiUINT32 nTranslateMode  = 0;
        smiUINT32 nRetransmitMode = 0;

        // start winsnmp
        status = SnmpStartup(
                    &nMajorVersion,
                    &nMinorVersion,
                    &nLevel,
                    &nTranslateMode,
                    &nRetransmitMode
                    );

        // validate return code
        if (WSNMP_SUCCEEDED(status)) {

            SNMPDBG((
                SNMP_LOG_TRACE,
                "MGMTAPI: SnmpStartup succeeded:\n"
                "MGMTAPI:\tnMajorVersion   = %d\n"
                "MGMTAPI:\tnMinorVersion   = %d\n"
                "MGMTAPI:\tnLevel          = %d\n"
                "MGMTAPI:\tnTranslateMode  = %d\n"
                "MGMTAPI:\tnRetransmitMode = %d\n",
                nMajorVersion,
                nMinorVersion,
                nLevel,
                nTranslateMode,
                nRetransmitMode
                ));

            // allocate global trap available event
            g_hTrapEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

            // allocate global event to sync. SnmpMgrTrapListen
            g_hTrapRegisterdEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

            // make sure translate mode is snmp v1
            SnmpSetTranslateMode(SNMPAPI_UNTRANSLATED_V1);

            // make sure retransmit mode is on
            SnmpSetRetransmitMode(SNMPAPI_ON);

            // register notification class
            RegisterNotificationClass();

            // save new status
            g_fIsSnmpStarted = TRUE;

            // success
            fOk = TRUE;

        } else {

            SNMPDBG((
                SNMP_LOG_ERROR,
                "MGMTAPI: SnmpStartup returned %d.\n",
                SnmpGetLastError((HSNMP_SESSION)NULL)
                ));

            // failure
            fOk = FALSE;
        }
    }

    // serialize access to startup code
    LeaveCriticalSection(&g_GlobalLock);

    return fOk;
}


BOOL
CleanupIfNecessary(
    )

/*++

Routine Description:

    Cleanup WinSNMP DLL if necessary.

Arguments:

    None.

Return Values:

    Returns true if successful.

--*/

{
    BOOL fOk = TRUE;

    // serialize access to startup code
    EnterCriticalSection(&g_GlobalLock);

    // see if already started
    if (g_fIsSnmpStarted == TRUE) {

        SNMPAPI_STATUS status;

        // shutdown winsnmp
        status = SnmpCleanup();

        // validate return code
        if (WSNMP_FAILED(status)) {

            SNMPDBG((
                SNMP_LOG_ERROR,
                "MGMTAPI: SnmpCleanup returned %d.\n",
                SnmpGetLastError((HSNMP_SESSION)NULL)
                ));

            // failure
            fOk = FALSE;
        }

        // unregister notification class
        UnregisterNotificationClass();

        // save new status
        g_fIsSnmpStarted = FALSE;
    }

    // check trap handle
    if (g_hTrapEvent != NULL) {

        // close trap handle
        CloseHandle(g_hTrapEvent);

        // re-initialize
        g_hTrapEvent = NULL;
    }

    // serialize access to startup code
    LeaveCriticalSection(&g_GlobalLock);

    return fOk;
}


BOOL
CreateNotificationWindow(
    PSNMP_MGR_SESSION pSMS
    )

/*++

Routine Description:

    Create notification window for session.

Arguments:

    pSMS - pointer to MGMTAPI session structure.

Return Values:

    Returns true if successful.

--*/

{
    BOOL fOk;

    // validate session ptr
    WSNMP_ASSERT(pSMS != NULL);

    // create notification window
    pSMS->hWnd = CreateWindow(
                    NOTIFICATION_CLASS,
                    NULL,       // pointer to window name
                    0,          // window style
                    0,          // horizontal position of window
                    0,          // vertical position of window
                    0,          // window width
                    0,          // window height
                    NULL,       // handle to parent or owner window
                    NULL,       // handle to menu or child-window identifier
                    g_hDll,     // handle to application instance
                    NULL        // pointer to window-creation data
                    );

    // validate window handle
    if (pSMS->hWnd != NULL) {

        // store pointer to session in window
        SetWindowLongPtr(pSMS->hWnd, 0, (LONG_PTR)pSMS);

        // success
        fOk = TRUE;

    } else {

        SNMPDBG((
            SNMP_LOG_ERROR,
            "MGMTAPI: CreateWindow returned %d.\n",
            GetLastError()
            ));

        // failure
        fOk = FALSE;
    }

    return fOk;
}


BOOL
DestroyNotificationWindow(
    HWND hWnd
    )

/*++

Routine Description:

    Destroy notification window for session.

Arguments:

    hWnd - window handle for session.

Return Values:

    Returns true if successful.

--*/

{
    BOOL fOk;

    // destroy notification window
    fOk = DestroyWindow(hWnd);

    if (!fOk) {

        SNMPDBG((
            SNMP_LOG_ERROR,
            "MGMTAPI: DestroyWindow returned %d.\n",
            GetLastError()
            ));
    }

    return fOk;
}


BOOL
CloseSession(
    PSNMP_MGR_SESSION pSMS
    )

/*++

Routine Description:

    Close WinSNMP session associated with MGMTAPI session.

Arguments:

    pSMS - pointer to MGMTAPI session structure.

Return Values:

    Returns true if successful.

--*/

{
    BOOL fOk = TRUE;
    SNMPAPI_STATUS status;

    // validate session ptr
    WSNMP_ASSERT(pSMS != NULL);

    // check if window opened
    if (pSMS->hWnd != (HWND)NULL) {

        // destroy notification window
        fOk = DestroyNotificationWindow(pSMS->hWnd);
    }

    // check if agent entity allocated
    if (pSMS->hAgentEntity != (HSNMP_ENTITY)NULL) {

        // close the entity handle
        status = SnmpFreeEntity(pSMS->hAgentEntity);

        // validate status
        if (WSNMP_FAILED(status)) {

            SNMPDBG((
                SNMP_LOG_ERROR,
                "MGMTAPI: SnmpFreeEntity returned %d.\n",
                SnmpGetLastError((HSNMP_SESSION)NULL)
                ));

            // failure
            fOk = FALSE;
        }

        // re-initialize
        pSMS->hAgentEntity = (HSNMP_ENTITY)NULL;
    }

    // check if manager entity allocated
    if (pSMS->hManagerEntity != (HSNMP_ENTITY)NULL) {

        // close the entity handle
        status = SnmpFreeEntity(pSMS->hManagerEntity);

        // validate status
        if (WSNMP_FAILED(status)) {

            SNMPDBG((
                SNMP_LOG_ERROR,
                "MGMTAPI: SnmpFreeEntity returned %d.\n",
                SnmpGetLastError((HSNMP_SESSION)NULL)
                ));

            // failure
            fOk = FALSE;
        }

        // re-initialize
        pSMS->hManagerEntity = (HSNMP_ENTITY)NULL;
    }

    // check if session allocated
    if (pSMS->hSnmpSession != (HSNMP_SESSION)NULL) {

        // close the winsnmp session
        status = SnmpClose(pSMS->hSnmpSession);

        // validate status
        if (WSNMP_FAILED(status)) {

            SNMPDBG((
                SNMP_LOG_ERROR,
                "MGMTAPI: SnmpClose returned %d.\n",
                SnmpGetLastError((HSNMP_SESSION)NULL)
                ));

            // failure
            fOk = FALSE;
        }

        // re-initialize
        pSMS->hSnmpSession = (HSNMP_SESSION)NULL;
    }

    return fOk;
}

//SNMPAPI_STATUS SNMPAPI_CALL
//   SnmpConveyAgentAddress (SNMPAPI_STATUS mode);


BOOL
OpenSession(
    PSNMP_MGR_SESSION pSMS,
    LPSTR             pAgentAddress,
    LPSTR             pAgentCommunity,
    INT               nTimeOut,
    INT               nRetries
    )

/*++

Routine Description:

    Open WinSNMP session and associate with MGMTAPI session.

Arguments:

    pSMS - pointer to MGMTAPI session structure.

    pAgentAddress - points to a null-terminated string specifying either a
        dotted-decimal IP address or a host name that can be resolved to an
        IP address, an IPX address (in 8.12 notation), or an ethernet address.

    pAgentCommunity - points to a null-terminated string specifying the
        SNMP community name used when communicating with the agent specified
        in the lpAgentAddress parameter

    nTimeOut - specifies the communications time-out in milliseconds.

    nRetries - specifies the communications retry count.

Return Values:

    Returns true if successful.

--*/

{
    BOOL fOk;
    struct sockaddr AgentSockAddr;
    CHAR AgentStrAddr[MAXENTITYSTRLEN+1];
    smiOCTETS smiCommunity;

    // validate session ptr
    WSNMP_ASSERT(pSMS != NULL);

    // initialize notification window
    if (!CreateNotificationWindow(pSMS)) {
        return FALSE; // bail...
    }

    // open a winsnmp session which corresponds to mgmtapi session
    pSMS->hSnmpSession = SnmpOpen(pSMS->hWnd, WM_WSNMP_INCOMING);

    // --ft
    // we need to turn this on in order to have WINSNMP to pass back not
    // only the entity standing for the source Ip address but also the
    // agent address as it was sent into the V1 Trap Pdu. Without it,
    // SnmpMgrGetTrapEx() will return a NULL address for the pSourceAddress
    // paramter. However, SnmpMgrGetTrapEx() is not documented!!!
    //SnmpConveyAgentAddress(SNMPAPI_ON); // Move this into wsnmp_cf.c:SnmpStartup
    // to avoid missing entry point problem when wsnmp32.dll is from other vendors

    // validate session handle returned
    if (WSNMP_FAILED(pSMS->hSnmpSession)) {

        SNMPDBG((
            SNMP_LOG_ERROR,
            "MGMTAPI: SnmpOpen returned %d.\n",
            SnmpGetLastError((HSNMP_SESSION)NULL)
            ));

        // re-initialize
        pSMS->hSnmpSession = (HSNMP_SESSION)NULL;

        goto cleanup; // bail...
    }

    // validate pointer
    if (pAgentAddress != NULL) {

        // use snmpapi.dll to do convert to sockets structure
        if (!SnmpSvcAddrToSocket(pAgentAddress, &AgentSockAddr)) {
            goto cleanup; // bail...
        }

        // check address family of agent
        if (AgentSockAddr.sa_family == AF_INET) {

            LPSTR pAgentStrAddr;
            struct sockaddr_in * pAgentSockAddr;

            // cast generic socket address structure to inet
            pAgentSockAddr = (struct sockaddr_in *)&AgentSockAddr;

            // obtain exclusive access to api
            EnterCriticalSection(&g_GlobalLock);

            // attempt to convert address into string
            pAgentStrAddr = inet_ntoa(pAgentSockAddr->sin_addr);

            // copy to stack variable
            strcpy(AgentStrAddr, pAgentStrAddr);

            // release exclusive access to api
            LeaveCriticalSection(&g_GlobalLock);

        } else if (AgentSockAddr.sa_family == AF_IPX) {

            // simply copy original string
            strcpy(AgentStrAddr, pAgentAddress);

        } else {

            SNMPDBG((
                SNMP_LOG_ERROR,
                "MGMTAPI: Incorrect address family.\n"
                ));

            goto cleanup; // bail...
        }

        // create remote agent entity
        pSMS->hAgentEntity = SnmpStrToEntity(
                                    pSMS->hSnmpSession,
                                    AgentStrAddr
                                    );

        // validate agent entity returned
        if (WSNMP_FAILED(pSMS->hAgentEntity)) {

            SNMPDBG((
                SNMP_LOG_ERROR,
                "MGMTAPI: SnmpStrToEntity returned %d.\n",
                SnmpGetLastError(pSMS->hSnmpSession)
                ));

            // re-initialize
            pSMS->hAgentEntity = (HSNMP_ENTITY)NULL;

            goto cleanup; // bail...
        }

        // attach timeout specified with agent
        SnmpSetTimeout(pSMS->hAgentEntity, nTimeOut / 10);

        // attach retries specified with agent
        SnmpSetRetry(pSMS->hAgentEntity, nRetries);

        // create local manager entity
        pSMS->hManagerEntity = SnmpStrToEntity(
                                        pSMS->hSnmpSession,
                                        (AgentSockAddr.sa_family == AF_INET)
                                            ? DEFAULT_ADDRESS_IP
                                            : DEFAULT_ADDRESS_IPX
                                        );

        // validate manager entity returned
        if (WSNMP_FAILED(pSMS->hManagerEntity)) {

            SNMPDBG((
                SNMP_LOG_ERROR,
                "MGMTAPI: SnmpStrToEntity returned %d.\n",
                SnmpGetLastError(pSMS->hSnmpSession)
                ));

            // re-initialize
            pSMS->hManagerEntity = (HSNMP_ENTITY)NULL;

            goto cleanup; // bail...
        }

        // attach timeout specified with manager
        SnmpSetTimeout(pSMS->hManagerEntity, nTimeOut / 10);

        // attach retries specified with manager
        SnmpSetRetry(pSMS->hManagerEntity, nRetries);
    }

    // validate pointer
    if (pAgentCommunity != NULL) {

        // transfer community string
        smiCommunity.ptr = (smiLPBYTE)pAgentCommunity;
        smiCommunity.len = pAgentCommunity ? lstrlen(pAgentCommunity) : 0;

        // obtain context from community string
        pSMS->hViewContext = SnmpStrToContext(
                                pSMS->hSnmpSession,
                                &smiCommunity
                                );

        // validate context handle
        if (WSNMP_FAILED(pSMS->hViewContext)) {

            SNMPDBG((
                SNMP_LOG_ERROR,
                "MGMTAPI: SnmpStrToContext returned %d.\n",
                SnmpGetLastError(pSMS->hSnmpSession)
                ));

            // re-initialize
            pSMS->hViewContext = (HSNMP_CONTEXT)NULL;

            goto cleanup; // bail...
        }
    }

    // success
    return TRUE;

cleanup:

    // cleanup resources
    CloseSession(pSMS);

    // failure
    return FALSE;
}


BOOL
AllocateSession(
    PSNMP_MGR_SESSION * ppSMS
    )

/*++

Routine Description:

    Allocate mgmtapi session structure.

Arguments:

    ppSMS - pointer to session pointer to return.

Return Values:

    Returns true if successful.

--*/

{
    PSNMP_MGR_SESSION pSMS = NULL;

       __try
    {
        // allocate new session table entry
        pSMS = SnmpUtilMemAlloc(sizeof(SNMP_MGR_SESSION));

        // validate pointer
        if (pSMS != NULL) {

            // initialize session level lock
            InitializeCriticalSection(&pSMS->SessionLock);

        } else {

            SNMPDBG((
                SNMP_LOG_ERROR,
                "MGMTAPI: Could not allocate session.\n"
                ));

            // notify application of error
            SetLastError(SNMP_MEM_ALLOC_ERROR);
        }

        // transfer
        *ppSMS = pSMS;
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        if (pSMS != NULL)
        {
            SnmpUtilMemFree(pSMS);
            pSMS = NULL;
        }
    }

    // return status
    return (pSMS != NULL);
}


VOID
FreeSession(
    PSNMP_MGR_SESSION pSMS
    )

/*++

Routine Description:

    Frees mgmtapi session structure.

Arguments:

    pSMS - pointer to mgmtapi session structure.

Return Values:

    None.

--*/

{
    // is session valid?
    if (pSMS != NULL) {

        // destroy the session level lock
        DeleteCriticalSection(&pSMS->SessionLock);

        // free session object
        SnmpUtilMemFree(pSMS);
    }
}


BOOL
ProcessAgentResponse(
    PSNMP_MGR_SESSION pSMS
    )

/*++

Routine Description:

    Message pump for notification window.

Arguments:

    pSMS - pointer to MGMTAPI session structure.

Return Values:

    Returns true if agent responded.

--*/

{
    MSG msg;
    BOOL fOk = FALSE;
    BOOL fRet; 

    // validate session ptr
    WSNMP_ASSERT(pSMS != NULL);

    // get the next message for this session
    while ((fRet = GetMessage(&msg, pSMS->hWnd, 0, 0))) {
        
        if (fRet == -1) {
            // If there is an error, GetMessage returns -1 
           
            pSMS->nLastError = SNMPAPI_OTHER_ERROR;

            SNMPDBG((
                SNMP_LOG_ERROR,
                "MGMTAPI: ProcessAgentResponse: GetMessage returns -1.\n"
                ));
            
            break;
        }

        // check for private message
        if (msg.message != WM_WSNMP_DONE) {

            // translate message
            TranslateMessage(&msg);

            // dispatch message
            DispatchMessage(&msg);

        } else {

            // success
            fOk = TRUE;

            break;
        }
    }

    return fOk;
}


DWORD
WINAPI
TrapThreadProc(
    LPVOID lpParam
    )

/*++

Routine Description:

    Trap processing procedure.

Arguments:

    lpParam - unused thread parameter.

Return Values:

    Returns NOERROR if successful.

--*/

{
    SNMPAPI_STATUS status;
    PSNMP_MGR_SESSION pSMS;

    SNMPDBG((
        SNMP_LOG_TRACE,
        "MGMTAPI: Trap thread starting...\n"
        ));

    // obtain pointer
    pSMS = &g_TrapSMS;

    // re-initialize
    pSMS->nLastError = 0;

    g_fIsTrapRegistered = FALSE; // init to failure. Note that there will
                                 // be only 1 instance of this thread


    // initialize winsnmp trap session
    if (OpenSession(pSMS, NULL, NULL, 0, 0)) 
    {

        // register
        status = SnmpRegister(
                    pSMS->hSnmpSession,
                    (HSNMP_ENTITY)NULL,     // hAgentEntity
                    (HSNMP_ENTITY)NULL,     // hManagerEntity
                    (HSNMP_CONTEXT)NULL,    // hViewContext
                    (smiLPCOID)NULL,        // notification
                    SNMPAPI_ON
                    );

        // validate return code
        if (WSNMP_SUCCEEDED(status)) 
        {
            // signal main thread that Trap has been registered with WinSNMP
            g_fIsTrapRegistered = TRUE;
            SetEvent(g_hTrapRegisterdEvent);

            // loop processing responses
            while (ProcessAgentResponse(pSMS)) 
            {

                //
                // processing done in window procedure...
                //
            }

        } 
        else 
        {

            SNMPDBG((
                SNMP_LOG_ERROR,
                "MGMTAPI: SnmpRegister returned %d.\n",
                SnmpGetLastError(pSMS->hSnmpSession)
                ));

            // transfer last error to global structure
            pSMS->nLastError = SnmpGetLastError(pSMS->hSnmpSession);

            // signal main thread that there is an error 
            // in registering Trap with WinSNMP
            
            SetEvent(g_hTrapRegisterdEvent);
        }

    } 
    else 
    {

        // transfer last error to global structure
        pSMS->nLastError = SnmpGetLastError((HSNMP_SESSION)NULL);
        
        // signal main thread that there is an error 
        // in registering Trap with WinSNMP
      
        SetEvent(g_hTrapRegisterdEvent);
    }

    // free session
    CloseSession(pSMS);

    // obtain exclusive access
    EnterCriticalSection(&g_GlobalLock);

    // signal successful start
    g_fIsSnmpListening = FALSE;

    // release exclusive access
    LeaveCriticalSection(&g_GlobalLock);

    SNMPDBG((
        SNMP_LOG_TRACE,
        "MGMTAPI: Trap thread exiting...\n"
        ));

    // success
    return NOERROR;
}


BOOL
StartTrapsIfNecessary(
    HANDLE * phTrapAvailable
    )

/*++

Routine Description:

    Initializes global structures for trap listening.

Arguments:

    phTrapAvailable - pointer to event for signalling traps.

Return Values:

    Returns true if successful (must be called only once).

--*/

{
    BOOL fOk = FALSE;
    DWORD dwTrapThreadId;
    DWORD dwWaitTrapRegisterd;

    // validate pointer
    if (phTrapAvailable != NULL) 
    {

        // obtain exclusive access
        EnterCriticalSection(&g_GlobalLock);

        // transfer trap event to app
        *phTrapAvailable = g_hTrapEvent;

        // only start listening once
        if (g_fIsSnmpListening == FALSE) 
        {

            // spawn client trap thread
            g_hTrapThread = CreateThread(
                                NULL,   // lpThreadAttributes
                                0,      // dwStackSize
                                TrapThreadProc,
                                NULL,   // lpParameter
                                0,      // dwCreationFlags
                                &dwTrapThreadId
                                );

            // signal successful start
            g_fIsSnmpListening = TRUE;

            // release exclusive access
            LeaveCriticalSection(&g_GlobalLock);

            // WinSE bug 6182
            // wait for TrapThreadProc to signal sucessful or failure
            dwWaitTrapRegisterd = WaitForSingleObject(g_hTrapRegisterdEvent, INFINITE);
            if (dwWaitTrapRegisterd == WAIT_OBJECT_0)
            {
                if (g_fIsTrapRegistered == TRUE)
                    fOk = TRUE;  // success
                else
                {
                    SetLastError(SNMP_MGMTAPI_TRAP_ERRORS);

                    SNMPDBG((
                        SNMP_LOG_ERROR,
                        "MGMTAPI: Traps are not accessible.\n"
                        ));

                }
            }
            else
            {
                SetLastError(SNMP_MGMTAPI_TRAP_ERRORS);

                SNMPDBG((
                    SNMP_LOG_ERROR,
                    "MGMTAPI: Traps are not accessible.\n"
                    ));
            }
        
        } 
        else 
        {

            // whine about having called this before
            SetLastError(SNMP_MGMTAPI_TRAP_DUPINIT);

            SNMPDBG((
                SNMP_LOG_ERROR,
                "MGMTAPI: Duplicate registration detected.\n"
                ));
            // release exclusive access
            LeaveCriticalSection(&g_GlobalLock);
        }

    }

    return fOk;
}


///////////////////////////////////////////////////////////////////////////////
//                                                                           //
// Dll Entry Point                                                           //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////

BOOL
DllMain(
    HANDLE hDll,
    DWORD  dwReason,
    LPVOID lpReserved
    )

/*++

Routine Description:

    Dll entry point.

Arguments:

    hDll - module handle.

    dwReason - reason DllMain is being called.

    lpReserved - unused.

Return Values:

    None.

--*/

{
    BOOL bOk = TRUE;

    __try
    {
        // determine reason for being called
        if (dwReason == DLL_PROCESS_ATTACH)
        {

            // initialize startup critical section
            InitializeCriticalSection(&g_GlobalLock);

            // initialize list of incoming traps
            InitializeListHead(&g_IncomingTraps);

            // optimize thread startup
            DisableThreadLibraryCalls(hDll);

            // save handle
            g_hDll = hDll;
        }
        else if (dwReason == DLL_PROCESS_DETACH)
        {

            // cleanup winsnmp
            CleanupIfNecessary();

            // nuke startup critical section
            DeleteCriticalSection(&g_GlobalLock);
        }

    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        bOk = FALSE;
    }

    return bOk;
}


///////////////////////////////////////////////////////////////////////////////
//                                                                           //
// Public Procedures                                                         //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////
LPSNMP_MGR_SESSION
SNMP_FUNC_TYPE
SnmpMgrOpen(
    LPSTR pAgentAddress,
    LPSTR pAgentCommunity,
    INT   nTimeOut,
    INT   nRetries
    )

/*++

Routine Description:

    Initializes resources necessary for communication with specified agent.

Arguments:

    pAgentAddress - points to a null-terminated string specifying either a
        dotted-decimal IP address or a host name that can be resolved to an
        IP address, an IPX address (in 8.12 notation), or an ethernet address.

    pAgentCommunity - points to a null-terminated string specifying the
        SNMP community name used when communicating with the agent specified
        in the lpAgentAddress parameter

    nTimeOut - specifies the communications time-out in milliseconds.

    nRetries - specifies the communications retry count.

Return Values:

    Returns session handle if successful.

--*/

{
    PSNMP_MGR_SESSION pSMS = NULL;

    // initialize winsnmp
    if (StartSnmpIfNecessary()) {

        // allocate mgmtapi session
        if (AllocateSession(&pSMS)) {

            // open session
            if (!OpenSession(
                    pSMS,
                    pAgentAddress,
                    pAgentCommunity,
                    nTimeOut,
                    nRetries)) {

                // free session
                FreeSession(pSMS);

                // reset
                pSMS = NULL;
            }
        }
    }

    // return opaque pointer
    return (LPSNMP_MGR_SESSION)pSMS;
}

BOOL
SNMP_FUNC_TYPE
SnmpMgrCtl(
    LPSNMP_MGR_SESSION session,             // pointer to the MGMTAPI session
    DWORD              dwCtlCode,           // control code for the command requested
    LPVOID             lpvInBuffer,         // buffer with the input parameters for the operation
    DWORD              cbInBuffer,          // size of lpvInBuffer in bytes
    LPVOID             lpvOUTBuffer,        // buffer for all the output parameters of the command
    DWORD              cbOUTBuffer,         // size of lpvOUTBuffer
    LPDWORD            lpcbBytesReturned    // space used from lpvOutBuffer
    )
/*++

Routine Description:

    Operates several control operations over the MGMTAPI session

Arguments:

    pSession - pointer to the session to 


Return Values:


--*/
{
    BOOL bOk = FALSE;
    PSNMP_MGR_SESSION pSMS = (PSNMP_MGR_SESSION)session;

    switch(dwCtlCode)
    {
    case MGMCTL_SETAGENTPORT:
        if (pSMS == NULL)
            SetLastError(SNMP_MGMTAPI_INVALID_SESSION);
        else if (lpvInBuffer == NULL || cbInBuffer < sizeof(UINT))
            SetLastError(SNMP_MGMTAPI_INVALID_BUFFER);
        else if (WSNMP_FAILED(SnmpSetPort(pSMS->hAgentEntity, *(UINT*)lpvInBuffer)))
            SetLastError(SnmpGetLastError(pSMS->hSnmpSession));
        else
            bOk = TRUE;
        break;

    default:
        SetLastError(SNMP_MGMTAPI_INVALID_CTL);
        break;
    }

    return bOk;
}


BOOL
SNMP_FUNC_TYPE
SnmpMgrClose(
    LPSNMP_MGR_SESSION session
    )

/*++

Routine Description:

    Cleanups resources needed for communication with specified agent.

Arguments:

    session - points to an internal structure that specifies
        which session to close.

Return Values:

    Returns true if successful.

--*/

{
    PSNMP_MGR_SESSION pSMS = (PSNMP_MGR_SESSION)session;

    // validate pointer
    if (pSMS != NULL) {

        // close session
        CloseSession(pSMS);

        // free session
        FreeSession(pSMS);
    }

    return TRUE;
}


SNMPAPI
SNMP_FUNC_TYPE
SnmpMgrRequest(
    LPSNMP_MGR_SESSION session,
    BYTE               requestType,
    SnmpVarBindList  * pVarBindList,
    AsnInteger       * pErrorStatus,
    AsnInteger       * pErrorIndex
    )

/*++

Routine Description:

    Requests the specified operation be performed with the specified agent.

Arguments:

    session - points to an internal structure that specifies the session
        that will perform the request.

    requestType - specifies the SNMP request type.

    pVarBindList - points to the variable bindings list

    pErrorStatus - points to a variable in which the error status result
        will be returned.

    pErrorIndex - points to a variable in which the error index result
        will be returned.

Return Values:

    Returns true if successful.

--*/

{
    BOOL fOk = FALSE;
    SNMPAPI_STATUS status;
    PSNMP_MGR_SESSION pSMS = (PSNMP_MGR_SESSION)session;

    // validate pointers
    if ((pSMS != NULL) &&
        (pErrorIndex != NULL) &&
        (pErrorStatus != NULL) &&
        (pVarBindList != NULL) &&
        (pVarBindList->len != 0) &&
        (pVarBindList->list != NULL)) {

        // obtain exclusive access to session
        EnterCriticalSection(&pSMS->SessionLock);

        // initialize session structure
        pSMS->pVarBindList = pVarBindList;
        pSMS->nPduType = (smiINT32)(BYTE)requestType;
        pSMS->hVbl = (HSNMP_VBL)NULL;
        pSMS->hPdu = (HSNMP_PDU)NULL;
        pSMS->nErrorStatus = 0;
        pSMS->nErrorIndex = 0;
        pSMS->nLastError = 0;

        // allocate resources
        if (AllocatePdu(pSMS)) {

            // actually send
            status = SnmpSendMsg(
                        pSMS->hSnmpSession,
                        pSMS->hManagerEntity,
                        pSMS->hAgentEntity,
                        pSMS->hViewContext,
                        pSMS->hPdu
                        );

            // release now
            FreePdu(pSMS);

            // validate return code
            if (WSNMP_SUCCEEDED(status)) {

                // process agent response
                if (ProcessAgentResponse(pSMS) &&
                   (pSMS->nLastError == SNMP_ERROR_NOERROR)) {

                    // update error status and index
                    *pErrorStatus = pSMS->nErrorStatus;
                    *pErrorIndex  = pSMS->nErrorIndex;

                    // success
                    fOk = TRUE;

                } else {

                    // set error to winsnmp error
                    SetLastError(pSMS->nLastError);

                    // failure
                    fOk = FALSE;
                }

            } else {

                SNMPDBG((
                    SNMP_LOG_ERROR,
                    "MGMTAPI: SnmpSendMsg returned %d.\n",
                    SnmpGetLastError(pSMS->hSnmpSession)
                    ));
            }
        }

        // release exclusive access to session
        LeaveCriticalSection(&pSMS->SessionLock);
    }

    return fOk;
}


BOOL
SNMP_FUNC_TYPE
SnmpMgrStrToOid(
    LPSTR                 pString,
    AsnObjectIdentifier * pOID
    )

/*++

Routine Description:

    Converts a string object identifier or object descriptor representation
    to an internal object identifier.

Arguments:

    pString - points to a null-terminated string to be converted.

    pOID - points to an object identifier variable that will receive the
        converted value.

Return Values:

    Returns true if successful.

--*/

{
    // validate pointer to oid and string
    if ((pOID != NULL) && (pString != NULL)) {

        // forward to mibcc code for now
        return SnmpMgrText2Oid(pString, pOID);
    }

    return FALSE;
}


BOOL
SNMP_FUNC_TYPE
SnmpMgrOidToStr(
    AsnObjectIdentifier * pOID,
    LPSTR               * ppString
    )

/*++

Routine Description:

    Converts an internal object identifier to a string object identifier or
    object descriptor representation.

Arguments:

    pOID - pointers to object identifier to be converted.

    ppString - points to string pointer to receive converted value.

Return Values:

    Returns true if successful.

--*/

{
    // validate pointer to oid and string
    if ((pOID != NULL) && (ppString != NULL)) {

        // forward to mibcc code for now
        return SnmpMgrOid2Text(pOID, ppString);
    }

    return FALSE;
}


BOOL
SNMP_FUNC_TYPE
SnmpMgrTrapListen(
    HANDLE * phTrapAvailable
    )

/*++

Routine Description:

    Registers the ability of a manager application to receive SNMP traps.

Arguments:

    phTrapAvailable - points to an event handle that will be used to indicate
        that there are traps available

Return Values:

    Returns true if successful.

--*/

{
    BOOL fOk = FALSE;

    // startup winsnmp
    if (StartSnmpIfNecessary()) {

        // spawn only one trap client thread
        if (StartTrapsIfNecessary(phTrapAvailable)) {

            // success
            fOk = TRUE;
        }
    }

    return fOk;
}


BOOL
SNMP_FUNC_TYPE
SnmpMgrGetTrap(
    AsnObjectIdentifier * pEnterpriseOID,
    AsnNetworkAddress   * pAgentAddress,
    AsnInteger          * pGenericTrap,
    AsnInteger          * pSpecificTrap,
    AsnTimeticks        * pTimeStamp,
    SnmpVarBindList     * pVarBindList
    )

/*++

Routine Description:

    Returns outstanding trap data that the caller has not received if
    trap reception is enabled.

Arguments:

    pEnterpriseOID - points to an object identifier that specifies the
        enterprise that generated the SNMP trap

    pAgentAddress - points to the address of the agent that generated the
        SNMP trap (retrieved from PDU).

    pGenericTrap - points to an indicator of the generic trap id.

    pSpecificTrap - points to an indicator of the specific trap id.

    pTimeStamp - points to a variable to receive the time stamp.

    pVarBindList - points to the associated variable bindings.

Return Values:

    Returns true if successful.

--*/

{
    // forward to new api
    return SnmpMgrGetTrapEx(
                pEnterpriseOID,
                pAgentAddress,
                NULL,
                pGenericTrap,
                pSpecificTrap,
                NULL,
                pTimeStamp,
                pVarBindList
                );
}


BOOL
SNMP_FUNC_TYPE
SnmpMgrGetTrapEx(
    AsnObjectIdentifier * pEnterpriseOID,
    AsnNetworkAddress   * pAgentAddress,
    AsnNetworkAddress   * pSourceAddress,
    AsnInteger          * pGenericTrap,
    AsnInteger          * pSpecificTrap,
    AsnOctetString      * pCommunity,
    AsnTimeticks        * pTimeStamp,
    SnmpVarBindList     * pVarBindList
    )

/*++

Routine Description:

    Returns outstanding trap data that the caller has not received if
    trap reception is enabled.

Arguments:

    pEnterpriseOID - points to an object identifier that specifies the
        enterprise that generated the SNMP trap

    pAgentAddress - points to the address of the agent that generated the
        SNMP trap (retrieved from PDU).

    pSourceAddress - points to the address of the agent that generated the
        SNMP trap (retrieved from network transport).

    pGenericTrap - points to an indicator of the generic trap id.

    pSpecificTrap - points to an indicator of the specific trap id.

    pCommunity - points to structure to receive community string.

    pTimeStamp - points to a variable to receive the time stamp.

    pVarBindList - points to the associated variable bindings.

Return Values:

    Returns true if successful.

--*/

{
    BOOL fOk = FALSE;
    PLIST_ENTRY pLE = NULL;
    PTRAP_LIST_ENTRY pTLE = NULL;
    smiINT32 nLastError;

    // obtain exclusive access
    EnterCriticalSection(&g_GlobalLock);

    // make sure list has entries
    if (!IsListEmpty(&g_IncomingTraps)) {

        // remove first item from list
        pLE = RemoveHeadList(&g_IncomingTraps);

    } else {

        // check for trap thread failure
        nLastError = g_TrapSMS.nLastError;
    }

    // release exclusive access
    LeaveCriticalSection(&g_GlobalLock);

    // validate pointer
    if (pLE != NULL) {

        // retrieve pointer to trap list entry
        pTLE = CONTAINING_RECORD(pLE, TRAP_LIST_ENTRY, Link);

        // validate pointer
        if (pEnterpriseOID != NULL) {

            // manually copy enterprise oid
            *pEnterpriseOID = pTLE->EnterpriseOID;

            // re-initialize list entry
            pTLE->EnterpriseOID.ids = NULL;
            pTLE->EnterpriseOID.idLength = 0;
        }

        // validate pointer
        if (pCommunity != NULL) {

            // transfer string info
            *pCommunity = pTLE->Community;

            // re-initialize list entry
            pTLE->Community.length  = 0;
            pTLE->Community.stream  = NULL;
            pTLE->Community.dynamic = FALSE;
        }

        // validate pointer
        if (pVarBindList != NULL) {

            // transfer varbindlist
            *pVarBindList = pTLE->VarBindList;

            // re-initialize list entry
            pTLE->VarBindList.len  = 0;
            pTLE->VarBindList.list = NULL;
        }

        // validate pointer
        if (pAgentAddress != NULL) {

            // copy structure
            memcpy(pAgentAddress,
                   &pTLE->AgentAddress,
                   sizeof(pTLE->AgentAddress)
                   );
        }

        // validate pointer
        if (pSourceAddress != NULL) {

            // copy structure
            memcpy(pSourceAddress,
                   &pTLE->SourceAddress,
                   sizeof(pTLE->SourceAddress)
                   );
        }

        // validate pointer
        if (pGenericTrap != NULL) {

            // transfer generic trap info
            *pGenericTrap = pTLE->nGenericTrap;
        }

        // validate pointer
        if (pSpecificTrap != NULL) {

            // transfer generic trap info
            *pSpecificTrap = pTLE->nSpecificTrap;
        }

        // validate pointer
        if (pTimeStamp != NULL) {

            // transfer time info
            *pTimeStamp = pTLE->TimeStamp;
        }

        // release
        FreeTle(pTLE);

        // success
        fOk = TRUE;

    } else if (nLastError != NOERROR) {

        // indicate there was an thread error
        SetLastError(SNMP_MGMTAPI_TRAP_ERRORS);

    } else {

        // indicate there are no traps
        SetLastError(SNMP_MGMTAPI_NOTRAPS);
    }

    return fOk;
}


VOID
serverTrapThread(
    LPVOID pUnused
    )

/*++

Routine Description:

    Old thread procedure used by the SNMP Trap Service.

Arguments:

    pUnused - unused parameter.

Return Values:

    None.

--*/

{
    //
    // do nothing here...
    //
}