/*++

Copyright (c) 1992-1997  Microsoft Corporation

Module Name:

    trapthrd.c

Abstract:

    Contains routines for trap processing thread.

Environment:

    User Mode - Win32

Revision History:

    10-Feb-1997 DonRyan
        Rewrote to implement SNMPv2 support.

--*/
 
///////////////////////////////////////////////////////////////////////////////
//                                                                           //
// Include files                                                             //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////

#include "globals.h"
#include "trapthrd.h"
#include "subagnts.h"
#include "snmppdus.h"
#include "trapmgrs.h"
#include "snmpmgrs.h"
#include "network.h"
#include "snmpmgmt.h"


///////////////////////////////////////////////////////////////////////////////
//                                                                           //
// Global variables                                                          //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////

static SnmpVarBindList g_NullVbl = { NULL, 0 };


///////////////////////////////////////////////////////////////////////////////
//                                                                           //
// Private procedures                                                        //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////

BOOL
LoadWaitObjects(
    DWORD * pnWaitObjects,
    PHANDLE * ppWaitObjects,
    PSUBAGENT_LIST_ENTRY ** pppNLEs
    )

/*++

Routine Description:

    Loads arrays with necessary wait object information.

Arguments:

    pnWaitObjects - pointer to receive count of wait objects.

    ppWaitObjects - pointer to receive wait object handles.

    pppNLEs - pointer to receive array of associated subagents pointers.

Return Values:

    Returns true if successful.

--*/

{
    PLIST_ENTRY pLE;
    PSUBAGENT_LIST_ENTRY pNLE;
    PSUBAGENT_LIST_ENTRY * ppNLEs = NULL;
    PHANDLE pWaitObjects = NULL;
    DWORD nWaitObjects = 2;
    BOOL fOk = FALSE;

    EnterCriticalSection(&g_RegCriticalSectionB);
    
    // point to first subagent
    pLE = g_Subagents.Flink;

    // process each subagent
    while (pLE != &g_Subagents) {

        // retreive pointer to subagent list entry from link
        pNLE = CONTAINING_RECORD(pLE, SUBAGENT_LIST_ENTRY, Link);

        // check for subagent trap event
        if (pNLE->hSubagentTrapEvent != NULL) {
        
            // increment        
            nWaitObjects++;
        }            

        // next entry
        pLE = pLE->Flink;
    }
    
    // attempt to allocate array of subagent pointers
    ppNLEs = AgentMemAlloc(nWaitObjects * sizeof(PSUBAGENT_LIST_ENTRY));
        
    // validate pointers
    if (ppNLEs != NULL) {

        // attempt to allocate array of event handles
        pWaitObjects = AgentMemAlloc(nWaitObjects * sizeof(HANDLE));

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

            // success
            fOk = TRUE;

        } else {
                
            SNMPDBG((
                SNMP_LOG_ERROR,
                "SNMP: SVC: could not allocate handle array.\n"
                ));

            // release array
            AgentMemFree(ppNLEs);
    
            // re-init
            ppNLEs = NULL;
        }

    } else {
        
        SNMPDBG((
            SNMP_LOG_ERROR,
            "SNMP: SVC: could not allocate subagent pointers.\n"
            ));
    }
    
    if (fOk) {
    
        // initialize
        DWORD dwIndex = 0;

        // point to first subagent
        pLE = g_Subagents.Flink;

        // process each subagent and check for overflow
        while ((pLE != &g_Subagents) && (dwIndex < nWaitObjects - 1)) {

            // retreive pointer to subagent list entry from link
            pNLE = CONTAINING_RECORD(pLE, SUBAGENT_LIST_ENTRY, Link);

            // check for subagent trap event
            if (pNLE->hSubagentTrapEvent != NULL) {
            
                // copy subagent trap event handle
                pWaitObjects[dwIndex] = pNLE->hSubagentTrapEvent;

                // copy subagent pointer
                ppNLEs[dwIndex] = pNLE;

                // next
                dwIndex++;
            }            

            // next entry
            pLE = pLE->Flink;
        }

        // copy registry update event into second last entry
        pWaitObjects[dwIndex++] = g_hRegistryEvent;

        // copy termination event into last entry
        pWaitObjects[dwIndex++] = g_hTerminationEvent;

        // validate number of items
        if (dwIndex != nWaitObjects) {
            
            SNMPDBG((
                SNMP_LOG_WARNING,
                "SNMP: SVC: updating number of events from %d to %d.\n",
                nWaitObjects,
                dwIndex
                ));

            // use latest number
            nWaitObjects = dwIndex;
        }
    }

    // transfer wait object information 
    *pnWaitObjects = fOk ? nWaitObjects : 0;
    *ppWaitObjects = pWaitObjects;
    *pppNLEs = ppNLEs;

    LeaveCriticalSection(&g_RegCriticalSectionB);

    return fOk;
}


BOOL
UnloadWaitObjects(
    PHANDLE pWaitObjects,
    PSUBAGENT_LIST_ENTRY * ppNLEs
    )

/*++

Routine Description:

    Loads arrays with necessary wait object information.

Arguments:

    pWaitObjects - pointer to wait object handles.

    ppNLEs - pointer to array of associated subagents pointers.

Return Values:

    Returns true if successful.

--*/

{
    // release array
    AgentMemFree(pWaitObjects);

    // release array
    AgentMemFree(ppNLEs);
    
    return TRUE;
}


BOOL
GenerateExtensionTrap(
    AsnObjectIdentifier * pEnterpriseOid,
    AsnInteger32          nGenericTrap,
    AsnInteger32          nSpecificTrap,
    AsnTimeticks          nTimeStamp,
    SnmpVarBindList *     pVbl
    )

/*

Routine Description:

    Generates trap for subagent.

Arguments:

    pEnterpriseOid - pointer to EnterpriseOid OID.

    nGenericTrap - generic trap identifier.

    nSpecificTrap - EnterpriseOid specific trap identifier.

    nTimeStamp - timestamp to include in trap.

    pVbl - pointer to optional variables.

Return Values:

    Returns true if successful.

*/

{
    SNMP_PDU Pdu;
    BOOL fOk = FALSE;

    // note this is in older format
    Pdu.nType = SNMP_PDU_V1TRAP;

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

        // copy varbinds 
        Pdu.Vbl = *pVbl;

    } else {

        // initialize
        Pdu.Vbl.len = 0;
        Pdu.Vbl.list = NULL;
    }

    // validate enterprise oid
    if ((pEnterpriseOid != NULL) &&
        (pEnterpriseOid->ids != NULL) &&
        (pEnterpriseOid->idLength != 0)) {

        // transfer specified enterprise oid
        Pdu.Pdu.TrapPdu.EnterpriseOid = *pEnterpriseOid;

    } else {

        // transfer microsoft enterprise oid 
        // Note: transfer the AsnObjectIdentifier structure as a whole, but no new memory is allocated
        // for the 'ids' buffer. Hence, Pdu....EnterpriseOid should not be 'SnmpUtilFreeOid'!!
        Pdu.Pdu.TrapPdu.EnterpriseOid = snmpMgmtBase.AsnObjectIDs[OsnmpSysObjectID].asnValue.object;
    }    

    // make sure that the system uptime is consistent by overriding
    Pdu.Pdu.TrapPdu.nTimeticks = nTimeStamp ? SnmpSvcGetUptime() : 0;

    // transfer the remaining parameters 
    Pdu.Pdu.TrapPdu.nGenericTrap  = nGenericTrap;
    Pdu.Pdu.TrapPdu.nSpecificTrap = nSpecificTrap;

    // initialize agent address structure
    Pdu.Pdu.TrapPdu.AgentAddr.dynamic = FALSE;
    Pdu.Pdu.TrapPdu.AgentAddr.stream  = NULL;
    Pdu.Pdu.TrapPdu.AgentAddr.length  = 0;

    // send trap to managers
    return GenerateTrap(&Pdu);
}


///////////////////////////////////////////////////////////////////////////////
//                                                                           //
// Public procedures                                                         //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////

BOOL
ProcessSubagentEvents(
    )

/*++

Routine Description:

    Processes subagent trap events.

Arguments:

    None.

Return Values:

    Returns true if successful.

--*/

{
    BOOL fOk = FALSE;
    PSUBAGENT_LIST_ENTRY * ppNLEs = NULL;
    PHANDLE pWaitObjects = NULL;
    DWORD nWaitObjects = 0;
    DWORD dwIndex;

    // attempt to load waitable objects into array
    if (LoadWaitObjects(&nWaitObjects, &pWaitObjects, &ppNLEs)) {

        // loop
        for (;;) {

            // subagent event or termination 
            dwIndex = WaitForMultipleObjects(
                            nWaitObjects,
                            pWaitObjects,
                            FALSE,
                            INFINITE
                            );

            // check for process termination event first 
            // note: g_hTerminationEvent is a manual reset event
            if (WAIT_OBJECT_0 == WaitForSingleObject(g_hTerminationEvent, 0)) {
                
                SNMPDBG((
                    SNMP_LOG_TRACE,
                    "SNMP: SVC: shutting down trap thread.\n"
                    ));
                
                break; // bail...

            // check for registry update event next
            } else if (dwIndex == (WAIT_OBJECT_0 + nWaitObjects - 2)) {
                
                SNMPDBG((
                    SNMP_LOG_TRACE,
                    "SNMP: SVC: recalling LoadWaitObjects.\n"
                    ));
                
                if (!LoadWaitObjects(&nWaitObjects, &pWaitObjects, &ppNLEs))
                    break;

            // check for subagent trap notification event
            } else if (dwIndex < (WAIT_OBJECT_0 + nWaitObjects - 1)) {

                AsnObjectIdentifier EnterpriseOid;
                AsnInteger          nGenericTrap;
                AsnInteger          nSpecificTrap;
                AsnInteger          nTimeStamp;
                SnmpVarBindList     Vbl;

                PFNSNMPEXTENSIONTRAP pfnSnmpExtensionTrap;

                // retrieve pointer to subagent trap entry point
                pfnSnmpExtensionTrap = ppNLEs[dwIndex]->pfnSnmpExtensionTrap;
                                
                // validate function pointer                    
                if (pfnSnmpExtensionTrap != NULL) {            

                    __try {

                        // loop until false is returned
                        while ((*pfnSnmpExtensionTrap)(
                                    &EnterpriseOid,
                                    &nGenericTrap,
                                    &nSpecificTrap,
                                    &nTimeStamp,
                                    &Vbl)) {
                                    
                            // send extension trap                                
                            GenerateExtensionTrap(
                                &EnterpriseOid,
                                nGenericTrap,
                                nSpecificTrap,
                                nTimeStamp,
                                &Vbl
                                );

                            SnmpUtilVarBindListFree(&Vbl);
                            
                            // check for process termination event while we are in this while loop 
                            if (WAIT_OBJECT_0 == WaitForSingleObject(g_hTerminationEvent, 0)) 
                            {
                
                                SNMPDBG((
                                    SNMP_LOG_TRACE,
                                    "SNMP: SVC: shutting down trap thread in \"while((*pfnSnmpExtensionTrap)\" loop.\n"
                                    ));
                
                                break; // bail...
                            }
                        }
                    
                    } __except (EXCEPTION_EXECUTE_HANDLER) {
                        
                        SNMPDBG((
                            SNMP_LOG_ERROR,
                            "SNMP: SVC: exception 0x%08lx polling %s.\n",
                            GetExceptionCode(),
                            ppNLEs[dwIndex]->pPathname
                            ));
                    }
                }
            }            
        }
    
        // release memory for wait objects
        UnloadWaitObjects(pWaitObjects, ppNLEs);
    }

    return fOk;
}


BOOL
GenerateTrap(
    PSNMP_PDU pPdu
    )

/*

Routine Description:

    Generates trap for agent.

Arguments:

    pPdu - pointer to initialized TRAP or TRAPv1 PDU.

Return Values:

    Returns true if successful.

*/

{
    BOOL fOk = TRUE;
    PLIST_ENTRY pLE1;
    PLIST_ENTRY pLE2;
    PLIST_ENTRY pLE3;
    PNETWORK_LIST_ENTRY pNLE;
    PMANAGER_LIST_ENTRY pMLE;
    PTRAP_DESTINATION_LIST_ENTRY pTLE;
    AsnOctetString CommunityOctets;
    DWORD dwStatus;
    DWORD dwIPAddr;

    EnterCriticalSection(&g_RegCriticalSectionC);
    // obtain first trap destination
    pLE1 = g_TrapDestinations.Flink;

    // process each trap destination
    while (pLE1 != &g_TrapDestinations) {

        // retrieve pointer to outgoing transport structure
        pTLE = CONTAINING_RECORD(pLE1, TRAP_DESTINATION_LIST_ENTRY, Link);

        // copy community string into octet structure
        CommunityOctets.length  = strlen(pTLE->pCommunity);
        CommunityOctets.stream  = pTLE->pCommunity;
        CommunityOctets.dynamic = FALSE;

        // obtain first manager
        pLE2 = pTLE->Managers.Flink;

        // process each receiving manager
        while (pLE2 != &pTLE->Managers) {

            // retrieve pointer to next manager
            pMLE = CONTAINING_RECORD(pLE2, MANAGER_LIST_ENTRY, Link);
            
            // refresh addr 
            UpdateMLE(pMLE);

            // don't send traps to addresses that are DEAD or NULL
            if (pMLE->dwAge == MGRADDR_DEAD || 
                !IsValidSockAddr(&pMLE->SockAddr))
            {
                pLE2 = pLE2->Flink;
                continue;
            }
        
            // obtain first outgoing transport
            pLE3 = g_OutgoingTransports.Flink;

            // process each outgoing transport
            while (pLE3 != &g_OutgoingTransports) {

                // retrieve pointer to outgoing transport structure
                pNLE = CONTAINING_RECORD(pLE3, NETWORK_LIST_ENTRY, Link);

                // initialize buffer length
                pNLE->Buffer.len = NLEBUFLEN;

                // can only send on same protocol
                if (pNLE->SockAddr.sa_family != pMLE->SockAddr.sa_family)
                {
                    pLE3 = pLE3->Flink;
                    continue;
                }

                // modify agent address
                if (pNLE->SockAddr.sa_family == AF_INET) 
                {

                    struct sockaddr_in * pSockAddrIn;
                    DWORD                szSockToBind;

                    // see if the trap destination address is valid and if the
                    // card to use for sending the trap could be determined
                    if (WSAIoctl(pNLE->Socket,
                             SIO_ROUTING_INTERFACE_QUERY,
                             &pMLE->SockAddr,
                             sizeof(pMLE->SockAddr),
                             &pNLE->SockAddr,
                             sizeof(pNLE->SockAddr),
                             &szSockToBind,
                             NULL,
                             NULL) == SOCKET_ERROR)
                    {
                        SNMPDBG((
                            SNMP_LOG_ERROR,
                            "SNMP: SVC: cannot determine interface to use for trap destination %s [err=%d].\n",
                            inet_ntoa(((struct sockaddr_in *)&pMLE->SockAddr)->sin_addr),
                            WSAGetLastError()
                            ));
                        // if we can't determine on what interface the trap will be sent from, just bail.
                        pLE3 = pLE3->Flink;
                        continue;
                    }
                    
                    // obtain pointer to protocol specific structure
                    pSockAddrIn = (struct sockaddr_in * )&pNLE->SockAddr;

                    // copy agent address into temp buffer
                    dwIPAddr = pSockAddrIn->sin_addr.s_addr;

                    // initialize agent address structure
                    pPdu->Pdu.TrapPdu.AgentAddr.dynamic = FALSE;
                    pPdu->Pdu.TrapPdu.AgentAddr.stream  = (LPBYTE)&dwIPAddr;
                    pPdu->Pdu.TrapPdu.AgentAddr.length  = sizeof(dwIPAddr);

                } else {

                    // re-initialize agent address structure
                    pPdu->Pdu.TrapPdu.AgentAddr.dynamic = FALSE;
                    pPdu->Pdu.TrapPdu.AgentAddr.stream  = NULL;
                    pPdu->Pdu.TrapPdu.AgentAddr.length  = 0;
                }

                // build message
                if (BuildMessage(
                        SNMP_VERSION_1,
                        &CommunityOctets,
                        pPdu,
                        pNLE->Buffer.buf,
                        &pNLE->Buffer.len
                        )) {
                                
                    // synchronous send
                    dwStatus = WSASendTo(
                                  pNLE->Socket,
                                  &pNLE->Buffer,
                                  1,
                                  &pNLE->dwBytesTransferred,
                                  pNLE->dwFlags,
                                  &pMLE->SockAddr,
                                  pMLE->SockAddrLen,
                                  NULL,
                                  NULL
                                  );
                            
                    // register outgoing packet into the management structure
                    mgmtCTick(CsnmpOutPkts);
                    // retister outgoing trap into the management structure
                    mgmtCTick(CsnmpOutTraps);

                    // validate return code
                    if (dwStatus == SOCKET_ERROR) {
                        
                        SNMPDBG((
                            SNMP_LOG_ERROR,
                            "SNMP: SVC: error code %d on sending trap to %s.\n",
                            WSAGetLastError(),
                            pTLE->pCommunity
                            ));
                    }
                }    

                // next entry
                pLE3 = pLE3->Flink;
            }

            // next entry
            pLE2 = pLE2->Flink;
        }

        // next entry
        pLE1 = pLE1->Flink;                
    }
    LeaveCriticalSection(&g_RegCriticalSectionC);

    return fOk;
}


BOOL
GenerateColdStartTrap(
    )

/*

Routine Description:

    Generates cold start trap.

Arguments:

    None.

Return Values:

    Returns true if successful.

*/

{
    // generate cold start
    return GenerateExtensionTrap(
                NULL,   // pEnterpriseOid
                SNMP_GENERICTRAP_COLDSTART,
                0,      // nSpecificTrapId
                0,      // nTimeStamp
                &g_NullVbl
                );    
}


BOOL
GenerateAuthenticationTrap(
    )

/*

Routine Description:

    Generates authentication trap.

Arguments:

    None.

Return Values:

    Returns true if successful.

*/

{
    // generate cold start
    return GenerateExtensionTrap(
                NULL,   // pEnterpriseOid
                SNMP_GENERICTRAP_AUTHFAILURE,
                0,      // nSpecificTrapId
                SnmpSvcGetUptime(),
                &g_NullVbl
                );    
}
