// snmptrap.c
//
// Original Microsoft code modified by ACE*COMM
// for use with WSNMP32.DLL and other trap receiving
// clients, per contract.
//
// Bob Natale, ACE*COMM (bnatale@acecomm.com)
// For NT v5 Beta, v970228
// Additional enhancements planned.
//
// This version of SNMPTRAP has no dependencies
// on either MGMTAPI.DLL or WSNMP32.DLL.
//
// WinSNMP clients use the SnmpRegister() function.
//
// Other clients will need to match the following
// values and structures:
//
//    SNMP_TRAP structure
//    SNMPTRAPPIPE name
//    TRAPBUFSIZE value
//
// Change log:
// ------------------------------------------------
// 4.0.1381.3  Apr 8, 1998 Bob Natale
//
// 1.  Re-worked the trap port monitoring thread into
//     two threads...one for IP and one for IPX, to
//     comply with WinSock v2's restrictions against
//     multi-protocol select().
//
// 2.  General clean-up/streamlining wrt "legacy" code
//     from original MS version...more to do here, esp.
//     wrt error handling code that does not do anything.
// ------------------------------------------------
// 4.0.1381.4  Apr. 10, 1998 Bob Natale
//
// 1.  Replaced mutex calls with critical_sectin calls.
//
// 2.  Cleaned out some dead code (removed commented out code)
// ------------------------------------------------
// Jan. 2, 2001 Frank Li
// 1. remove TerminateThread
// 2. add debug build loggings
// ------------------------------------------------
#include <windows.h>
#include <winsock.h>
#include <wsipx.h>
#include <process.h>

#ifdef DBG // include files for debug trace only
#include <stdio.h>
#include <time.h>
#endif

//--------------------------- PRIVATE VARIABLES -----------------------------
#define SNMPMGRTRAPPIPE "\\\\.\\PIPE\\MGMTAPI"
#define MAX_OUT_BUFS    16
#define TRAPBUFSIZE     4096
#define IP_TRAP_PORT    162
#define IPX_TRAP_PORT   36880

//
// constants added to allocate trap buffer for fixing trap data of length 
// > 8192 bytes. Here is the buffer allocation scheme based on the common
// cases that trap data sizes are less than 4-KBytes:
// 1. LargeTrap 
//    if (trap data size >= 8192 bytes), allocate MAX_UDP_SIZE sized buffer
// 2. MediumTrap
//    if (trap data size <= 4096 bytes), allocate FOUR_K_BUF_SIZE sized buffer
// 3. SmallTrap
//    if (4096 < trap data size < 8192), allocate just enough buffer size.
// Note:
// - when LargeTrap is received, the allocated buffer will stay for a time of
//   MAXUDPLEN_BUFFER_TIME from the last LargeTrap received.
// - Once MediumTrap is received, subsequent SmallTrap will reuse the
//   last MediumTrap allocated buffer. 
//
#define MAX_UDP_SIZE    (65535-8)  // max udp len - 8bytes udp header
#define MAX_FIONREAD_UDP_SIZE 8192 // max winsock FIONREAD reported size (8kB)
#define FOUR_K_BUF_SIZE   4096       // buffer of 4-KBytes in size
#define MAXUDPLEN_BUFFER_TIME (2*60*1000)  // max. 2 mins to keep the
                                           // last allocated large buffer.   
// ******** INITIALIZE A LIST HEAD ********
#define ll_init(head) (head)->next = (head)->prev = (head);
// ******** TEST A LIST FOR EMPTY ********
#define ll_empt(head) ( ((head)->next) == (head) )
// ******** Get ptr to next entry ********
#define ll_next(item,head)\
( (ll_node *)(item)->next == (head) ? 0 : \
(ll_node *)(item)->next )
// ******** Get ptr to prev entry ********
#define ll_prev(item)\
( (ll_node *)(item)->prev )
// ******** ADD AN ITEM TO THE END OF A LIST ********
#define ll_adde(item,head)\
   {\
   ll_node *pred = (head)->prev;\
   ((ll_node *)(item))->next = (head);\
   ((ll_node *)(item))->prev = pred;\
   (pred)->next = ((ll_node *)(item));\
   (head)->prev = ((ll_node *)(item));\
   }
// ******** REMOVE AN ITEM FROM A LIST ********
#define ll_rmv(item)\
   {\
   ll_node *pred = ((ll_node *)(item))->prev;\
   ll_node *succ = ((ll_node *)(item))->next;\
   pred->next = succ;\
   succ->prev = pred;\
   }
// ******** List head/node ********
typedef struct ll_s
   { // linked list structure
   struct  ll_s *next;  // next node
   struct  ll_s *prev;  // prev. node
   } ll_node;           // linked list node
typedef struct
   {// shared by server trap thread and pipe thread
   ll_node  links;
   HANDLE   hPipe;
   } svrPipeListEntry;
typedef struct
   {
   SOCKADDR Addr;              
   int      AddrLen;           
   UINT     TrapBufSz;
   char     TrapBuf[TRAPBUFSIZE];   // the size of this array should match the size of the structure
                                    // defined in wsnmp_no.c!!!
   }        SNMP_TRAP, *PSNMP_TRAP;
typedef struct
{
    SOCKET s;
    OVERLAPPED ol;
} TRAP_THRD_CONTEXT, *PTRAP_THRD_CONTEXT;

HANDLE hExitEvent = NULL;
LPCTSTR svcName = "SNMPTRAP";
SERVICE_STATUS_HANDLE hService = 0;
SERVICE_STATUS status =
  {SERVICE_WIN32, SERVICE_STOPPED, SERVICE_ACCEPT_STOP, NO_ERROR, 0, 0, 0};
SOCKET ipSock = INVALID_SOCKET;
SOCKET ipxSock = INVALID_SOCKET;
HANDLE ipThread = NULL;
HANDLE ipxThread = NULL;
CRITICAL_SECTION cs_PIPELIST;
ll_node *pSvrPipeListHead = NULL;

// global variables added to remove the TerminateThread call
OVERLAPPED g_ol; // overlapped struct for svrPipeThread
TRAP_THRD_CONTEXT g_ipThreadContext;  // context for ip svrTrapThread
TRAP_THRD_CONTEXT g_ipxThreadContext; // context for ipx svrTrapThread

///////////////////////////////////////////////////////////////////////////////
//                                                                           //
// SNMPTRAP Debugging Prototypes                                             //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////
#if DBG
VOID
WINAPI
SnmpTrapDbgPrint(
    IN LPSTR szFormat,
    IN ...
    );
#define SNMPTRAPDBG(_x_)                    SnmpTrapDbgPrint _x_
#else
#define SNMPTRAPDBG(_x_)
#endif

//--------------------------- PRIVATE PROTOTYPES ----------------------------
DWORD WINAPI svrTrapThread (IN OUT LPVOID threadParam);
DWORD WINAPI svrPipeThread (IN LPVOID threadParam);
VOID WINAPI svcHandlerFunction (IN DWORD dwControl);
VOID WINAPI svcMainFunction (IN DWORD dwNumServicesArgs,
                             IN LPSTR *lpServiceArgVectors);
void FreeSvrPipeEntryList(IN ll_node* head);

//--------------------------- PRIVATE PROCEDURES ----------------------------
VOID WINAPI svcHandlerFunction (IN DWORD dwControl)
{
    if (dwControl == SERVICE_CONTROL_STOP)
    {
        status.dwCurrentState = SERVICE_STOP_PENDING;
        status.dwCheckPoint++;
        status.dwWaitHint = 45000;
        if (!SetServiceStatus(hService, &status))
            exit(1);
        // set event causing trap thread to terminate
        if (!SetEvent(hExitEvent))
        {
            status.dwCurrentState = SERVICE_STOPPED;
            status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
            status.dwServiceSpecificExitCode = 1; // OPENISSUE - svc err code
            status.dwCheckPoint = 0;
            status.dwWaitHint = 0;
            // We are exiting in any case, so ignore any error...
            SetServiceStatus (hService, &status);
            exit(1);
        }
    }
    else
    //   dwControl == SERVICE_CONTROL_INTERROGATE
    //   dwControl == SERVICE_CONTROL_PAUSE
    //   dwControl == SERVICE_CONTROL_CONTINUE
    //   dwControl == <anything else>
    {
        if (status.dwCurrentState == SERVICE_STOP_PENDING ||
            status.dwCurrentState == SERVICE_START_PENDING)
            status.dwCheckPoint++;
        if (!SetServiceStatus (hService, &status))
            exit(1);
    }
} // end_svcHandlerFunction()

VOID WINAPI svcMainFunction (IN DWORD dwNumServicesArgs,
                             IN LPSTR *lpServiceArgVectors)
{
    WSADATA WinSockData;
    HANDLE  hPipeThread = NULL;
    DWORD   dwThreadId;
    //---------------------------------------------------------------------
    hService = RegisterServiceCtrlHandler (svcName, svcHandlerFunction);
    if (hService == 0)
    {
        status.dwCurrentState = SERVICE_STOPPED;
        status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
        status.dwServiceSpecificExitCode = 2; // OPENISSUE - svc err code
        status.dwCheckPoint = 0;
        status.dwWaitHint = 0;
        // We are exiting in any case, so ignore any error...
        SetServiceStatus (hService, &status);
        exit(1);
    }
    status.dwCurrentState = SERVICE_START_PENDING;
    status.dwWaitHint = 20000;

    if (!SetServiceStatus(hService, &status))
        exit(1);

    __try
    {
        InitializeCriticalSection (&cs_PIPELIST);
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        exit(1);
    }

    memset(&g_ipThreadContext.ol, 0, sizeof(g_ipThreadContext.ol));
    memset(&g_ipxThreadContext.ol, 0, sizeof(g_ipxThreadContext.ol));

    if (WSAStartup ((WORD)0x0101, &WinSockData))
        goto CLOSE_OUT; // WinSock startup failure


    // allocate linked-list header for client received traps
    if ((pSvrPipeListHead = (ll_node *)GlobalAlloc (GPTR, sizeof(ll_node))) == NULL)
        goto CLOSE_OUT;
    ll_init(pSvrPipeListHead);
    if ((hPipeThread = (HANDLE)_beginthreadex
                    (NULL, 0, svrPipeThread, NULL, 0, &dwThreadId)) == 0)
        goto CLOSE_OUT;
    
    //-----------------------------------------------------------------------------------
    //CHECK_IP:
    ipSock = socket (AF_INET, SOCK_DGRAM, 0);
    if (ipSock != INVALID_SOCKET)
    {
        struct sockaddr_in localAddress_in;
        struct servent *serv;
        ZeroMemory (&localAddress_in, sizeof(localAddress_in));
        localAddress_in.sin_family = AF_INET;
        if ((serv = getservbyname ("snmp-trap", "udp")) == NULL)
            localAddress_in.sin_port = htons (IP_TRAP_PORT);
        else
            localAddress_in.sin_port = (SHORT)serv->s_port;
        localAddress_in.sin_addr.s_addr = htonl (INADDR_ANY);
        if (bind (ipSock, (LPSOCKADDR)&localAddress_in, sizeof(localAddress_in)) != SOCKET_ERROR)
        {
            g_ipThreadContext.s = ipSock;
            // init the overlapped struct with manual reset non-signaled event
            g_ipThreadContext.ol.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
            if (NULL == g_ipThreadContext.ol.hEvent)    
                goto CLOSE_OUT;    
            
            ipThread = (HANDLE)_beginthreadex
                    (NULL, 0, svrTrapThread, (LPVOID)&g_ipThreadContext, 0, &dwThreadId);
        }
    }
    //-----------------------------------------------------------------------------------
    //CHECK_IPX:
    ipxSock = socket (AF_IPX, SOCK_DGRAM, NSPROTO_IPX);
    if (ipxSock != INVALID_SOCKET)
    {
        struct sockaddr_ipx localAddress_ipx;
        ZeroMemory (&localAddress_ipx, sizeof(localAddress_ipx));
        localAddress_ipx.sa_family = AF_IPX;
        localAddress_ipx.sa_socket = htons (IPX_TRAP_PORT);
        if (bind (ipxSock, (LPSOCKADDR)&localAddress_ipx, sizeof(localAddress_ipx)) != SOCKET_ERROR)
        {
            g_ipxThreadContext.s = ipxSock;
            // init the overlapped struct with manual reset non-signaled event
            g_ipxThreadContext.ol.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
            if (NULL == g_ipxThreadContext.ol.hEvent)
                goto CLOSE_OUT;    
             
            ipxThread = (HANDLE)_beginthreadex
                    (NULL, 0, svrTrapThread, (LPVOID)&g_ipxThreadContext, 0, &dwThreadId);
        }
    }
    //-----------------------------------------------------------------------------------
    // We are ready to listen for traps...
    status.dwCurrentState = SERVICE_RUNNING;
    status.dwCheckPoint   = 0;
    status.dwWaitHint     = 0;
    if (!SetServiceStatus(hService, &status))
        goto CLOSE_OUT;
    WaitForSingleObject (hExitEvent, INFINITE);
    //-----------------------------------------------------------------------------------
CLOSE_OUT:
    // make sure we can bail out if we are here because of goto statements above
    SetEvent(hExitEvent); 
    
    if (hPipeThread != NULL)
    {
        SNMPTRAPDBG(("svcMainFunction: enter SetEvent g_ol.hEvent.\n"));
        SetEvent(g_ol.hEvent); // signal to terminate the svrPipeThread thread
        WaitForSingleObject (hPipeThread, INFINITE);
        SNMPTRAPDBG(("svcMainFunction: WaitForSingleObject hPipeThread INFINITE done.\n"));
        CloseHandle (hPipeThread);
    }
    if (ipSock != INVALID_SOCKET)
        closesocket (ipSock); // unblock any socket call
    if (ipThread != NULL)
    {
        SNMPTRAPDBG(("svcMainFunction: enter SetEvent g_ipThreadContext.ol.hEvent.\n"));
        SetEvent(g_ipThreadContext.ol.hEvent); // signal to terminate thread
        WaitForSingleObject (ipThread, INFINITE);
        CloseHandle (ipThread);
    }
    if (g_ipThreadContext.ol.hEvent)
        CloseHandle(g_ipThreadContext.ol.hEvent);

    if (ipxSock != INVALID_SOCKET)
        closesocket (ipxSock); // unblock any socket call
    if (ipxThread != NULL)
    {
        SNMPTRAPDBG(("svcMainFunction: enter SetEvent g_ipxThreadContext.ol.hEvent.\n"));
        SetEvent(g_ipxThreadContext.ol.hEvent); // signal to terminate thread
        WaitForSingleObject (ipxThread, INFINITE);
        CloseHandle (ipxThread);
    }
    if (g_ipxThreadContext.ol.hEvent)
        CloseHandle(g_ipxThreadContext.ol.hEvent);

    EnterCriticalSection (&cs_PIPELIST);
    if (pSvrPipeListHead != NULL)
    {
        FreeSvrPipeEntryList(pSvrPipeListHead);
        pSvrPipeListHead = NULL;
    }
    LeaveCriticalSection (&cs_PIPELIST);
    
    DeleteCriticalSection (&cs_PIPELIST);
    WSACleanup();
    
    status.dwCurrentState = SERVICE_STOPPED;
    status.dwCheckPoint = 0;
    status.dwWaitHint = 0;
    if (!SetServiceStatus(hService, &status))
        exit(1);
} // end_svcMainFunction()

//--------------------------- PUBLIC PROCEDURES -----------------------------
int __cdecl main ()
{
    BOOL fOk;
    OSVERSIONINFO osInfo;
    SERVICE_TABLE_ENTRY svcStartTable[2] =
    {
        {(LPTSTR)svcName, svcMainFunction},
        {NULL, NULL}
    };
    osInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
    fOk = GetVersionEx (&osInfo);
    if (fOk && (osInfo.dwPlatformId == VER_PLATFORM_WIN32_NT))
    {   // create event to synchronize trap server shutdown
        hExitEvent = CreateEvent (NULL, TRUE, FALSE, NULL);
        if (NULL == hExitEvent)
        {
            exit(1);
        }
        // init the overlapped struct used by svrTrapThread
        // with manual reset non-signaled event
        memset(&g_ol, 0, sizeof(g_ol));
        g_ol.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
        if (NULL == g_ol.hEvent)
        {
            CloseHandle(hExitEvent);
            exit(1);
        }
        // this call will not return until service stopped
        fOk = StartServiceCtrlDispatcher (svcStartTable);
        CloseHandle (hExitEvent);
        CloseHandle(g_ol.hEvent);
    }
    return fOk; 
} // end_main()

//
DWORD WINAPI svrTrapThread (LPVOID threadParam)
// This thread takes a SOCKET from the TRAP_THRD_CONTEXT parameter, 
// loops on select()
// for data in-coming over that socket, writing it back
// out to clients over all pipes currently on the list of
// trap notification pipes shared by this thread and the
// pipe thread
{
    PSNMP_TRAP pRecvTrap = NULL;
    struct fd_set readfds;
    PTRAP_THRD_CONTEXT pThreadContext = (PTRAP_THRD_CONTEXT) threadParam;
    SOCKET fd = INVALID_SOCKET;
    int len;
    DWORD dwLastAllocatedUdpDataLen = 0;  // the last allocated UDP data buffer size
    DWORD dwLastBigBufferRequestTime = 0; // the tick count that the last  
                                          // LargeTrap received
    BOOL fTimeoutForMaxUdpLenBuffer = FALSE; // need to deallocate the big buffer
    //
    if (NULL == pThreadContext)
        return 0;
    fd = pThreadContext->s;
    dwLastBigBufferRequestTime = GetTickCount();
    while (TRUE)
    {

        ULONG ulTrapSize = 0;
        DWORD dwError = 0;

        if (WAIT_OBJECT_0 == WaitForSingleObject (hExitEvent, 0))
        {
            SNMPTRAPDBG(("svrTrapThread: exit 0.\n"));
            break;
        }

        // construct readfds which gets destroyed by select()
        FD_ZERO(&readfds);
        FD_SET(fd, &readfds);
        if (select (0, &readfds, NULL, NULL, NULL) == SOCKET_ERROR)
        {
            SNMPTRAPDBG(("svrTrapThread: select failed %d.\n", WSAGetLastError()));
            break; // terminate thread
        }
        if (!(FD_ISSET(fd, &readfds)))
            continue;

        if (ioctlsocket(
                    fd,              // socket to query
                    FIONREAD,        // query for the size of the incoming datagram
                    &ulTrapSize      // unsigned long to store the size of the datagram
                    ) != 0)
        {
            dwError = WSAGetLastError();
            SNMPTRAPDBG((
                "ioctlsocket FIONREAD failed: lasterror: 0x%08lx\n", 
                dwError));

            continue;              // continue if we could not determine the size of the
                                   // incoming datagram
        }
        
        if (ulTrapSize >= MAX_FIONREAD_UDP_SIZE)
        {
            dwLastBigBufferRequestTime = GetTickCount(); // update tickcount
            
            // the ulTrapSize is not accurat on reporting the size of the 
            // next UDP datagram message. KB Q192599 and KB Q140263
            if ( NULL == pRecvTrap ||
                 dwLastAllocatedUdpDataLen < MAX_UDP_SIZE )
            {
                if (pRecvTrap)
                {
                    GlobalFree(pRecvTrap);
                    pRecvTrap = NULL;
                    dwLastAllocatedUdpDataLen = 0;
                }
                SNMPTRAPDBG((
                    "allocate LargeTrap of size : %d\n", 
                    sizeof(SNMP_TRAP) - TRAPBUFSIZE + MAX_UDP_SIZE));
                // allocate for the trap header + max udp size
                pRecvTrap = (PSNMP_TRAP)GlobalAlloc(GPTR, (sizeof(SNMP_TRAP) - 
                                                TRAPBUFSIZE + MAX_UDP_SIZE));
                if (NULL == pRecvTrap)
                {
                    SNMPTRAPDBG(("svrTrapThread: GlobalAlloc failed.\n"));
                    dwLastAllocatedUdpDataLen = 0;
                    break;       
                }      
                dwLastAllocatedUdpDataLen = MAX_UDP_SIZE;
            }
        }
        else
        {
            // winsock has reported the exact amount of UDP datagram 
            // size to be recieved as long as the next datagram is less than
            // 8-kbyte

            //
            // if we've allocated a big buffer before, check to see if we need
            // to deallocate it to save the usage of resource.
            //
            fTimeoutForMaxUdpLenBuffer = FALSE; // reset timeout flag
            if (MAX_UDP_SIZE == dwLastAllocatedUdpDataLen)
            {
                // we've allocated a big buffer before
                DWORD dwCurrTime = GetTickCount();
                if (dwCurrTime < dwLastBigBufferRequestTime)
                {
                    // wrap around occured. we just simply assume it is time to 
                    // release the big buffer.
                    fTimeoutForMaxUdpLenBuffer = TRUE;
                    SNMPTRAPDBG((
                        "Timeout to free LargeTrap buffer of size %d bytes.\n",
                                dwLastAllocatedUdpDataLen));
                }
                else
                {
                    if ( (dwCurrTime-dwLastBigBufferRequestTime) > 
                         MAXUDPLEN_BUFFER_TIME )
                    {
                        // after quite a long time, we don't have a large UDP
                        // datagram received.
                        fTimeoutForMaxUdpLenBuffer = TRUE;
                        SNMPTRAPDBG((
                            "Timeout to free LargeTrap buffer size of %d bytes.\n",
                            dwLastAllocatedUdpDataLen));
                    }
                }
            }

            if (pRecvTrap == NULL ||
                fTimeoutForMaxUdpLenBuffer ||
                dwLastAllocatedUdpDataLen < ulTrapSize)
            {
                // allocate/reallocate buffer
                if (pRecvTrap != NULL)
                {
                    GlobalFree(pRecvTrap);
                    pRecvTrap = NULL;
                    dwLastAllocatedUdpDataLen = 0;
                }
                
                if (FOUR_K_BUF_SIZE >= ulTrapSize)
                {
                    // allocate at least 4 KBytes buffer to avoid 
                    // re-allocations on different sizes of small trap received
                    pRecvTrap = (PSNMP_TRAP)GlobalAlloc(GPTR, (sizeof(SNMP_TRAP) - 
                                                TRAPBUFSIZE + FOUR_K_BUF_SIZE));
                    dwLastAllocatedUdpDataLen = FOUR_K_BUF_SIZE; 
                    SNMPTRAPDBG((
                        "allocate SmallTrap of size : %d\n", 
                        sizeof(SNMP_TRAP) - TRAPBUFSIZE + FOUR_K_BUF_SIZE));
                }
                else
                {
                    // allocate what is necessary
                    pRecvTrap = (PSNMP_TRAP)GlobalAlloc(GPTR, (sizeof(SNMP_TRAP) - 
                                                TRAPBUFSIZE + ulTrapSize));
                    dwLastAllocatedUdpDataLen = ulTrapSize;
                    SNMPTRAPDBG((
                        "allocate MediumTrap of size : %d\n", 
                        sizeof(SNMP_TRAP) - TRAPBUFSIZE + ulTrapSize));
                }
                if (NULL == pRecvTrap) // if there is so few memory that we can't allocate a bit ..
                {                      // bail out and stop the SNMPTRAP service (bug? - other option => 100% CPU which is worst)
                    SNMPTRAPDBG(("svrTrapThread: GlobalAlloc failed.\n"));
                    dwLastAllocatedUdpDataLen = 0;
                    break;       
                }       
            }
        }

        pRecvTrap->TrapBufSz = dwLastAllocatedUdpDataLen; // actual buffer size
        pRecvTrap->AddrLen = sizeof(pRecvTrap->Addr);

        len = recvfrom (
                fd,
                pRecvTrap->TrapBuf,
                pRecvTrap->TrapBufSz,
                0, 
                &(pRecvTrap->Addr),
                &(pRecvTrap->AddrLen));
        
        if (len == SOCKET_ERROR)
        {
            dwError = WSAGetLastError();
            SNMPTRAPDBG((
                "recvfrom failed: ulTrapSize: %d bytes, TrapBufSz: %d bytes, lasterror: 0x%08lx\n", 
                ulTrapSize, pRecvTrap->TrapBufSz, dwError));
            continue;
        }

        EnterCriticalSection (&cs_PIPELIST);
        pRecvTrap->TrapBufSz = len; // the acutal trap data len received
        // add header to length
        len += sizeof(SNMP_TRAP) - sizeof(pRecvTrap->TrapBuf); // - TRAPBUFSIZE
        if (!ll_empt(pSvrPipeListHead))
        {
            DWORD written;
            ll_node *item = pSvrPipeListHead;
            while (item = ll_next(item, pSvrPipeListHead))
            {
                if (WAIT_OBJECT_0 == WaitForSingleObject (hExitEvent, 0))
                {
                    SNMPTRAPDBG(("svrTrapThread: exit 1.\n"));
                    LeaveCriticalSection (&cs_PIPELIST);
                    break;
                }
                if (!WriteFile(
                        ((svrPipeListEntry *)item)->hPipe,
                        (LPBYTE)pRecvTrap,
                        len,
                        &written,
                        &pThreadContext->ol))
                {
                    if (ERROR_IO_PENDING == GetLastError())
                    {
                        SNMPTRAPDBG(("svrTrapThread: before GetOverlappedResult.\n"));
                        GetOverlappedResult(
                            ((svrPipeListEntry *)item)->hPipe,
                            &pThreadContext->ol,
                            &written,
                            TRUE // Block
                            );
                        SNMPTRAPDBG(("svrTrapThread: after GetOverlappedResult.\n"));
                        if (WAIT_OBJECT_0 == WaitForSingleObject (hExitEvent, 0))
                        {
                            SNMPTRAPDBG(("svrTrapThread: exit 2.\n"));
                            LeaveCriticalSection (&cs_PIPELIST);
                            break;
                        }
                        // reset event to non-signaled state for next I/O
                        ResetEvent(pThreadContext->ol.hEvent);
                    }
                    else
                    {
                        ll_node *hold;

                        if (!DisconnectNamedPipe(((svrPipeListEntry *)item)->hPipe))
                        {
                            ; // Placeholder for error handling
                        }
                        if (!CloseHandle(((svrPipeListEntry *)item)->hPipe))
                        {
                            ; // Placeholder for error handling
                        }
                        hold = ll_prev(item);
                        ll_rmv(item);
                        GlobalFree(item); // check for errors?
                        item = hold;
                    }
                } // end_if !WriteFile
                else if (written != (DWORD)len)
                {
                    SNMPTRAPDBG(("svrTrapThread: written != len\n"));
                    ; // Placeholder for error handling
                }
            } // end_while item = ll_next
        } // end_if !ll_empt
        LeaveCriticalSection (&cs_PIPELIST);
    } // end while TRUE

   if (pRecvTrap != NULL)
       GlobalFree(pRecvTrap);

   return 0;
} // end svrTrapThread()

PACL AllocGenericACL()
{
    PACL                        pAcl;
    PSID                        pSidAdmins, pSidUsers, pSidLocalService;
    SID_IDENTIFIER_AUTHORITY    Authority = SECURITY_NT_AUTHORITY;
    DWORD                       dwAclLength;

    pSidAdmins = pSidUsers = pSidLocalService = NULL;

    // Bug# 179644 The SNMP trap service should not run in the LocalSystem account
    if ( !AllocateAndInitializeSid( &Authority,
                                    2,
                                    SECURITY_BUILTIN_DOMAIN_RID,
                                    DOMAIN_ALIAS_RID_ADMINS,
                                    0, 0, 0, 0, 0, 0,
                                    &pSidAdmins ) ||
         !AllocateAndInitializeSid( &Authority,
                                    2,
                                    SECURITY_BUILTIN_DOMAIN_RID,
                                    DOMAIN_ALIAS_RID_USERS,
                                    0, 0, 0, 0, 0, 0,
                                    &pSidUsers ) ||
         !AllocateAndInitializeSid( &Authority,
                                    1,
                                    SECURITY_LOCAL_SERVICE_RID,
                                    0,
                                    0, 0, 0, 0, 0, 0,
                                    &pSidLocalService ))
    {
        if (pSidAdmins)
        {
            FreeSid(pSidAdmins);
        }
        if (pSidUsers)
        {
            FreeSid(pSidUsers);
        }
        return NULL;
    }

    dwAclLength = sizeof(ACL) + 
                  sizeof(ACCESS_ALLOWED_ACE) -
                  sizeof(ULONG) +
                  GetLengthSid(pSidAdmins) +
                  sizeof(ACCESS_ALLOWED_ACE) - 
                  sizeof(ULONG) +
                  GetLengthSid(pSidUsers) +
                  sizeof(ACCESS_ALLOWED_ACE) - 
                  sizeof(ULONG) +
                  GetLengthSid(pSidLocalService);

    pAcl = GlobalAlloc (GPTR, dwAclLength);
    if (pAcl != NULL)
    {
        if (!InitializeAcl( pAcl, dwAclLength, ACL_REVISION) ||
            !AddAccessAllowedAce ( pAcl,
                                   ACL_REVISION,
                                   GENERIC_READ | GENERIC_WRITE,
                                   pSidLocalService ) || 
            !AddAccessAllowedAce ( pAcl,
                                   ACL_REVISION,
                                   GENERIC_READ | GENERIC_WRITE,
                                   pSidAdmins ) || 
            !AddAccessAllowedAce ( pAcl,
                                   ACL_REVISION,
                                   (GENERIC_READ | (FILE_GENERIC_WRITE & ~FILE_CREATE_PIPE_INSTANCE)),
                                   pSidUsers ))
        {
            GlobalFree(pAcl);
            pAcl = NULL;
        }
    }

    FreeSid(pSidAdmins);
    FreeSid(pSidUsers);
    FreeSid(pSidLocalService);

    return pAcl;
}

void FreeGenericACL( PACL pAcl)
{
    if (pAcl != NULL)
        GlobalFree(pAcl);
}

DWORD WINAPI svrPipeThread (LPVOID threadParam)
{
    // This thread creates a named pipe instance and
    // blocks waiting for a client connection.  When
    // client connects, the pipe handle is added to the
    // list of trap notification pipes.
    // It then waits for another connection.
    DWORD  nInBufLen = sizeof(SNMP_TRAP);
    DWORD  nOutBufLen = sizeof(SNMP_TRAP) * MAX_OUT_BUFS;
    SECURITY_ATTRIBUTES S_Attrib;
    SECURITY_DESCRIPTOR S_Desc;
    PACL   pAcl;
    DWORD dwRead;
    // construct security decsriptor
    InitializeSecurityDescriptor (&S_Desc, SECURITY_DESCRIPTOR_REVISION);

    if ((pAcl = AllocGenericACL()) == NULL ||
        !SetSecurityDescriptorDacl (&S_Desc, TRUE, pAcl, FALSE))
    {
        FreeGenericACL(pAcl);
        return (0);
    }

    S_Attrib.nLength = sizeof(SECURITY_ATTRIBUTES);
    S_Attrib.lpSecurityDescriptor = &S_Desc;
    S_Attrib.bInheritHandle = TRUE;

    while (TRUE)
    {
        HANDLE hPipe;
        svrPipeListEntry *item;
        BOOL bSuccess;

        // eliminate the TerminateThread call in CLOSE_OUT of svcMainFunction
        if (WAIT_OBJECT_0 == WaitForSingleObject (hExitEvent, 0))
        {
            SNMPTRAPDBG(("svrPipeThread: exit 0.\n"));
            break;
        }
        hPipe = CreateNamedPipe (SNMPMGRTRAPPIPE,
                    PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
                    (PIPE_WAIT | PIPE_READMODE_MESSAGE | PIPE_TYPE_MESSAGE),
                    PIPE_UNLIMITED_INSTANCES,
                    nOutBufLen, nInBufLen, 0, &S_Attrib);

        if (hPipe == INVALID_HANDLE_VALUE)
        {
            SNMPTRAPDBG(("svrPipeThread: CreateNamedPipe failed 0x%08lx.\n", GetLastError()));
            break;
        }
        else 
        {
            bSuccess = ConnectNamedPipe(hPipe, &g_ol);
            if (!bSuccess && GetLastError() == ERROR_IO_PENDING)  
            {
                // blocking wait until g_ol.hEvent signaled by system for a new client
                // connection request or by our own termination.
                SNMPTRAPDBG(("svrPipeThread: before GetOverlappedResult.\n"));
                bSuccess = GetOverlappedResult(hPipe, &g_ol, &dwRead, TRUE);
                SNMPTRAPDBG(("svrPipeThread: after GetOverlappedResult.\n"));
                if (WAIT_OBJECT_0 == WaitForSingleObject (hExitEvent, 0))
                {
                    SNMPTRAPDBG(("svrPipeThread: exit 1.\n"));
                    CloseHandle(hPipe);
                    break;
                }
                // reset event to non-signaled state for next I/O
                ResetEvent(g_ol.hEvent);
            }
            // check return from either ConnectNamedPipe or GetOverlappedResult.
            // If a client managed to connect between the CreateNamedPipe and
            // ConnectNamedPipe calls, ERROR_PIPE_CONNECTED will result
            if (!bSuccess && GetLastError() != ERROR_PIPE_CONNECTED)
            {
                // something went wrong, close instance and try again
                SNMPTRAPDBG(("svrPipeThread: ConnectNamedPipe 0x%08lx.\n", GetLastError()));
                CloseHandle(hPipe);
                continue;
            }
        }
        
        if (!(item = (svrPipeListEntry *)
                 GlobalAlloc (GPTR, sizeof(svrPipeListEntry))))
        {
            SNMPTRAPDBG(("svrPipeThread: E_OUTOFMEMORY\n"));
            DisconnectNamedPipe(hPipe);
            CloseHandle(hPipe);
            break;
        }
        else
        {
            ll_node *crt;
            item->hPipe = hPipe;

            SNMPTRAPDBG(("svrPipeThread: add connected client to pipe list\n"));

            EnterCriticalSection (&cs_PIPELIST);
            ll_adde(item, pSvrPipeListHead);
            crt = pSvrPipeListHead;

            // scan all the pipe instances to detect the ones that are disconnected
            while (crt = ll_next(crt, pSvrPipeListHead))
            {
                DWORD dwError;

                // subsequent ConnectNamePipe() on a handle already connected return:
                // - ERROR_PIPE_CONNECTED if the client is still there
                // - ERROR_NO_DATA if the client has disconnected
                ConnectNamedPipe(
                            ((svrPipeListEntry *)crt)->hPipe,
                            NULL);

                dwError = GetLastError();

                // For anything else but ERROR_PIPE_CONNECTED, conclude there has been
                // something wrong with the client/pipe so disconect and close the handle
                // and release the memory
                if (dwError != ERROR_PIPE_CONNECTED)
                {
                    ll_node *hold;

                    SNMPTRAPDBG(("svrPipeThread: disconnect client pipe handle 0x%08lx.\n", ((svrPipeListEntry *)crt)->hPipe));
                    if (!DisconnectNamedPipe(((svrPipeListEntry *)crt)->hPipe))
                    {
                        ; // Placeholder for error handling
                    }
                    if (!CloseHandle(((svrPipeListEntry *)crt)->hPipe))
                    {
                        ; // Placeholder for error handling
                    }

                    hold = ll_prev(crt);
                    ll_rmv(crt);
                    GlobalFree(crt); // check for errors?
                    crt = hold;
                } // end_if
            }

            LeaveCriticalSection (&cs_PIPELIST);
        } // end_else
   } // end_while TRUE

    FreeGenericACL(pAcl);
    return(0);

} // end_svrPipeThread()


void FreeSvrPipeEntryList(ll_node* head)
{
    if (head)
    {
        ll_node* current;
        current = head;
        while (current = ll_next(current, head))
        {
            ll_node *hold;
            if (!DisconnectNamedPipe(((svrPipeListEntry *)current)->hPipe))
            {
                ; // Placeholder for error handling
            }
            if (!CloseHandle(((svrPipeListEntry *)current)->hPipe))
            {
                ; // Placeholder for error handling
            }

            hold = ll_prev(current);
            ll_rmv(current);
            GlobalFree(current); // check for errors?
            current = hold;
        }
        GlobalFree(head);
    }
}


#if DBG
// modified from snmp\common\dll\dbg.c
#define MAX_LOG_ENTRY_LEN 512
VOID 
WINAPI 
SnmpTrapDbgPrint(
    LPSTR szFormat, 
    ...
    )

/*++

Routine Description:

    Prints debug message.

Arguments:


    szFormat - formatting string (see printf).

Return Values:

    None. 

--*/

{
    va_list arglist;

    // 640 octets should be enough to encode oid's of 128 sub-ids.
    // (one subid can be encoded on at most 5 octets; there can be at
    // 128 sub-ids per oid. MAX_LOG_ENTRY_LEN = 512
    char szLogEntry[4*MAX_LOG_ENTRY_LEN];

    time_t now;

    // initialize variable args
    va_start(arglist, szFormat);

    time(&now);
    strftime(szLogEntry, MAX_LOG_ENTRY_LEN, "%H:%M:%S :", localtime(&now));

    // transfer variable args to buffer
    vsprintf(szLogEntry + strlen(szLogEntry), szFormat, arglist);

    // output entry to debugger
    OutputDebugStringA(szLogEntry);
}
#endif