/*++

Copyright (C) Microsoft Corporation, 1997 - 1999

Module Name:

    senslogn.cxx

Abstract:

    This file contains the implementation of a Stub DLL to notify SENS of
    events generated by Winlogon.

Author:

    Gopal Parupudi    <GopalP>

Notes:

    a. This DLL notifies the following components:
        o EventSystem (contact DMcCrady)
        o IR service (contact JRoberts)
        o SENS service (contact GopalP)

    b. This DLL also maintains tokens of the currently logged on user. This
       is used by COM for activation.

Revision History:

    GopalP          12/7/1997         Start.

--*/



#include <common.hxx>
#include <stdio.h>
#include <windows.h>
#include <malloc.h>
#include <winwlx.h>
#include <sensapip.h>
#include <rpc.h>
#include "mutex.hxx"
#include "irnotify.h"
#include "usertok.h"
#include "senslogn.hxx"
#include "onestop.cxx"


//
// Constants
//
#define NOTIFY_LCE_STARTSHELL   0x00000003
#define NOTIFY_LCE_LOGOFF       0x00000004

#define SENS_START_WAIT_TIMEOUT 180*1000    // 3 minutes
#define NOTIFY_LCE_LOGONUSER    "NotifyLogonUser"
#define NOTIFY_LCE_LOGOFFUSER   "NotifyLogoffUser"
#define NOTIFY_IR_LOGONUSER     "OnUserLogon"
#define NOTIFY_IR_LOGOFFUSER    "OnUserLogoff"
#define NOTIFY_IR_INIT          "InitializeDll"
#define SENS_SERVICE            SENS_STRING("SENS")
#define EVENTSYSTEM_DLL         SENS_STRING("ES.DLL")
#define IR_DLL                  SENS_STRING("IRNOTIFY.DLL")
#define SENS_STARTED_EVENT      SENS_STRING("SENS Started Event")


//
// Globals
//

PWLX_NOTIFICATION_INFO gpStartShellInfo;
HANDLE ghSensStartedEvent;
MUTEX * SetupMutex;
BOOL gbIsTokenCodeInitialized;

// For GetCurrentUserToken
PSID LocalSystemSid;
USER_LOGON_TABLE * ActiveUserList;

// For IR notification of logon/logoff
RPC_BINDING_HANDLE g_hIrxfer;


//
// Some useful Macros
//

#ifdef DETAIL_DEBUG


#define DUMP_INFO(_EventType_)                                                      \
                                                                                    \
    char buf[512];                                                                  \
    PWLX_NOTIFICATION_INFO pInfo = (PWLX_NOTIFICATION_INFO) lpvParam;               \
                                                                                    \
    LogMessage(("------------------------------------------------------\n"));       \
    LogMessage((SENSLOGN " Received a %s Event.\n", _EventType_));                  \
    LogMessage(("          Size        - %d\n", pInfo->Size));                      \
    LogMessage(("          Flags       - 0x%x\n", pInfo->Flags));                   \
    LogMessage(("          UserName    - %s\n", UnicodeToAnsi(pInfo->UserName, buf)));      \
    LogMessage(("          Domain      - %s\n", UnicodeToAnsi(pInfo->Domain, buf)));        \
    LogMessage(("          WinStation  - %s\n", UnicodeToAnsi(pInfo->WindowStation, buf))); \
    LogMessage(("          hToken      - 0x%x\n", pInfo->hToken));                  \
    LogMessage(("          hDesktop    - 0x%x\n", pInfo->hDesktop));                \
    LogMessage(("          pCallback   - 0x%x\n", pInfo->pStatusCallback));         \
    LogMessage(("          dwSessionId - 0x%x\n", NtCurrentPeb()->SessionId));      \
    LogMessage(("------------------------------------------------------\n"));


//
// Functions
//

PCHAR
UnicodeToAnsi(
    PWSTR in,
    PCHAR out
    )
{
    PCHAR pSave = out;

    if (in == NULL)
        {
        return "<NULL>";
        }

    if (*in == (WCHAR)'\0')
        {
        return "<Null String>";
        }

    while( *out++ = (CHAR)*in++)
        ;

    return pSave;
}


#else // ! DETAIL_DEBUG

#define DUMP_INFO(_EventType_)

#endif // DETAIL_DEBUG



#define FIRE_EVENT(_EventType_)                                             \
    {                                                                       \
                                                                            \
    SENS_NOTIFY_WINLOGON Data;                                              \
                                                                            \
    Data.eType = _EventType_;                                               \
    Data.Info.Size = sizeof(SENS_NOTIFY_WINLOGON);                          \
    Data.Info.Flags = ((PWLX_NOTIFICATION_INFO)lpvParam)->Flags;            \
    Data.Info.UserName = ((PWLX_NOTIFICATION_INFO)lpvParam)->UserName;      \
    Data.Info.Domain = ((PWLX_NOTIFICATION_INFO)lpvParam)->Domain;          \
    Data.Info.WindowStation = ((PWLX_NOTIFICATION_INFO)lpvParam)->WindowStation;    \
    Data.Info.hToken = HandleToUlong(((PWLX_NOTIFICATION_INFO)lpvParam)->hToken);   \
    Data.Info.hDesktop = HandleToUlong(((PWLX_NOTIFICATION_INFO)lpvParam)->hDesktop); \
    Data.Info.dwSessionId = NtCurrentPeb()->SessionId;                      \
                                                                            \
    status = SensNotifyWinlogonEvent(&Data);                                \
                                                                            \
    if (status) {SensPrintToDebugger(SENS_DBG, (SENSLOGN "SensNotifyWinlogonEvent(0x%x) returned %d\n", _EventType_, status));}    \
    }


/*****************************************************************************
 *
 *  IsRemoteSession
 *
 *  On a Terminal Server: returns TRUE if current Session is the not the physical
 *  console , FALSE if it is the console session (SessionId == 0)
 *  On non-Hydra NT: always returns FALSE
 *
 * ENTRY:
 *   nothing
 *
 * EXIT:
 *   nothing
 *
 ****************************************************************************/

typedef BOOL (__stdcall * PFNPROCESSIDTOSESSIONID)(DWORD, PDWORD);
#define PFN_FIRSTTIME   (PFNPROCESSIDTOSESSIONID(-1))

BOOL
IsRemoteSession(VOID)
{
    static PFNPROCESSIDTOSESSIONID s_pfn = PFN_FIRSTTIME;
    static BOOL bCachedIsRemoteSession = FALSE;

    DWORD dwSessionId;

    if (PFN_FIRSTTIME == s_pfn)
        {
        HINSTANCE hinst = GetModuleHandle(L"KERNEL32.DLL");

        if (hinst)
            {
            s_pfn = (PFNPROCESSIDTOSESSIONID)GetProcAddress(hinst, "ProcessIdToSessionId");
            }
        else
            {
            s_pfn = NULL;
            }
    }

    if (s_pfn && s_pfn(GetCurrentProcessId(), &dwSessionId))
        {
        bCachedIsRemoteSession = (dwSessionId != 0);
        }

    // we set s_pfn = NULL to guarntee we only ever call ProcessIdToSessionId
    // once, and after that we just use the bCachedIsRemoteSession value
    s_pfn = NULL;

    return bCachedIsRemoteSession;
}




DWORD WINAPI
SensLogonEvent(
    LPVOID lpvParam
    )
/*++

Routine Description:

    Hook to trap the Logon Event. In addition, we do the following:

    a. Populate our token table with the current token.
    b. Initialize The token RPC interface, if necessary.

Arguments:

    lpvParam - Winlogon notification info.

Return Value:

    RPC status from SensNotifyWinlogonEvent()

--*/
{
    DWORD status;
    PWLX_NOTIFICATION_INFO pTempInfo = (PWLX_NOTIFICATION_INFO) lpvParam;

    DUMP_INFO("Logon");

    ActiveUserList->Add( pTempInfo );

    FIRE_EVENT(SENS_NOTIFY_WINLOGON_LOGON);

    return status;
}




DWORD WINAPI
SensLogoffEvent(
    LPVOID lpvParam
    )
/*++

Routine Description:

    Hook to trap the Logoff Event. We notify various components
    (like IR, OneStop, EventSystem, SENS) of the Logoff event.

Arguments:

    lpvParam - Winlogon notification info.

Notes:

    a. The system logoff will block till this call returns. Be very
       careful in adding code here.

Return Value:

    RPC status from SensNotifyWinlogonEvent()

--*/
{
    DWORD status;
    DWORD dwError;
    HRESULT hr;
    PWLX_NOTIFICATION_INFO pTempInfo;

    hr = S_OK;
    dwError = 0x0;
    pTempInfo = (PWLX_NOTIFICATION_INFO) lpvParam;

    LogMessage((SENSLOGN "[%d] Entered Logoff.\n", GetTickCount()));

    DUMP_INFO("Logoff");

    // Try to fire SENS Event
    LogMessage((SENSLOGN "[%d] Notifying SENS...\n", GetTickCount()));
    FIRE_EVENT(SENS_NOTIFY_WINLOGON_LOGOFF);
    LogMessage((SENSLOGN "[%d] SensNotifyWinlogonEvent(LOGOFF) succeeded.\n", GetTickCount()));

    // Notify IR
    LogMessage((SENSLOGN "[%d] Notifying IR...\n", GetTickCount()));
    OnUserLogoff( pTempInfo );
    LogMessage((SENSLOGN "[%d] OnUserLogoff() succeeded, notified IR.\n", GetTickCount()));

    // Start OneStop if necessary, except on Restart.
    if (   ((pTempInfo->Flags & EWX_REBOOT) == 0)
        && (IsAutoSyncEnabled(pTempInfo->hToken, AUTOSYNC_ON_LOGOFF)))
        {
        //
        // NOTE: If SENS ever becomes demandstarted on NT5, there are a couple
        //       of things that can be done:
        //          o Call StartSensIfNecessary() here. (OR)
        //          o Make Sens APIs call StartSensIfNecessary().
        //
        LogMessage((SENSLOGN "[%d] Notifying OneStop...\n", GetTickCount()));
        hr = SensNotifyOneStop(pTempInfo->hToken, SYNC_MANAGER_LOGOFF, TRUE);
        LogMessage((SENSLOGN "[%d] SensNotifyOneStop() returned 0x%x\n", GetTickCount(), hr));
        }

    // Notify EventSystem
    LogMessage((SENSLOGN "[%d] Notifying EventSystem...\n", GetTickCount()));
    hr = SensNotifyEventSystem(NOTIFY_LCE_LOGOFF, lpvParam);
    LogMessage((SENSLOGN "[%d] SensNotifyEventSystem() returned 0x%x\n", GetTickCount(), hr));

    //
    // Remove token handle from the list at the end so that COM activation
    // works till SENS gets done with event firings.
    //
    ActiveUserList->Remove( pTempInfo );
    LogMessage((SENSLOGN "Removed current user's token!\n"));

    return status;
}




DWORD WINAPI
SensStartupEvent(
    LPVOID lpvParam
    )
/*++

Routine Description:

    Hook to trap the Startup Event. Do some token management related
    initialization.

Arguments:

    lpvParam - Winlogon notification info.

Notes:

    a. This occurs very early in the bootup sequence. At this time,
       SENS service hasn't yet started up.

Return Value:

    RPC status from SensNotifyWinlogonEvent()

--*/
{
    DWORD status;
    DWORD dwLastError;

    status = 0x0;
    gbIsTokenCodeInitialized = FALSE;

    //
    // Create a SID representing the Local System account.
    //
    SID_IDENTIFIER_AUTHORITY Authority = SECURITY_NT_AUTHORITY;

    status = RtlAllocateAndInitializeSid(
                 &Authority,
                 1,
                 SECURITY_LOCAL_SYSTEM_RID,
                 0,
                 0,
                 0,
                 0,
                 0,
                 0,
                 0,
                 &LocalSystemSid
                 );
    if (!NT_SUCCESS(status))
        {
        return RtlNtStatusToDosError(status);
        }

    //
    // Create the table of logged-in users.
    //
    ActiveUserList = new USER_LOGON_TABLE( &status );
    if (!ActiveUserList || status)
        {
        delete ActiveUserList;
        return ERROR_NOT_ENOUGH_MEMORY;
        }

    SetupMutex = new MUTEX( &status );
    if (!SetupMutex || status)
        {
        delete SetupMutex;
        return ERROR_NOT_ENOUGH_MEMORY;
        }

    InitializeNotifyInterface();

    //
    // We are ready to use Token Code.
    //
    gbIsTokenCodeInitialized = TRUE;
    LogMessage((SENSLOGN "**** Token code initialized successfully ****\n"));

    //
    // Create an Event to indicate the starting of SENS.
    //
    ghSensStartedEvent = CreateEvent(
                             NULL,      // Specific Security Attributes
                             TRUE,      // Event is ManualReset
                             FALSE,     // Initial state is not Signalled
                             SENS_STARTED_EVENT
                             );
    if (ghSensStartedEvent == NULL)
        {
        dwLastError = GetLastError();
        LogMessage((SENSLOGN "SensStartupEvent() - CreateEvent() failed - %x.\n",
                    dwLastError));
        }
    else
        {
        LogMessage((SENSLOGN "SensStartedEvent created successfully\n"));
        }

    DUMP_INFO("Startup");

    return status;
}




BOOLEAN
InitializeNotifyInterface(
    void
    )
/*++

Routine Description:

    Initialize RPC interface for accepting calls to GetCurrentUserToken() API.

Arguments:

    None.

Return Value:

    TRUE, if successful

    FALSE, otherwise.

--*/
{
    unsigned i;
    DWORD status;

    SetupMutex->Enter();

    //
    // Register the interface for GetCurrentUserToken().
    //
    status = RpcServerRegisterAuthInfo(
                 NULL,
                 RPC_C_AUTHN_WINNT,
                 NULL,
                 NULL
                 );
    if (status)
        {
        SetupMutex->Leave();
        return FALSE;
        }

    status = RpcServerUseAllProtseqsIf( RPC_C_PROTSEQ_MAX_REQS_DEFAULT,
                                        _GetUserToken_ServerIfHandle,
                                        NULL
                                        );
    if (status)
        {
        SetupMutex->Leave();
        return FALSE;
        }

    //
    // We expect another part of the process has already called RpcServerListen.
    //
    status = RpcServerRegisterIfEx(
                 _GetUserToken_ServerIfHandle,
                 NULL,
                 NULL,
                 0,
                 RPC_C_LISTEN_MAX_CALLS_DEFAULT,
                 AllowLocalSystem
                 );
    if (status)
        {
        SetupMutex->Leave();
        return FALSE;
        }

    if (!IsRemoteSession())
        {
        //
        // Make a handle to the Infrared Monitor app.
        //
        RPC_BINDING_HANDLE hServer;
        status = RpcBindingFromStringBinding(L"ncalrpc:[,security=impersonation dynamic false]", &hServer);
        if (status)
            {
            SetupMutex->Leave();
            return FALSE;
            }

        RPC_SECURITY_QOS RpcSecQos;

        RpcSecQos.Version= RPC_C_SECURITY_QOS_VERSION_1;
        RpcSecQos.ImpersonationType= RPC_C_IMP_LEVEL_IMPERSONATE;
        RpcSecQos.IdentityTracking= RPC_C_QOS_IDENTITY_DYNAMIC;
        RpcSecQos.Capabilities= RPC_C_QOS_CAPABILITIES_MUTUAL_AUTH;

        status= RpcBindingSetAuthInfoEx(hServer,
                                        L"NT Authority\\System",
                                        RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
                                        RPC_C_AUTHN_WINNT,
                                        NULL,
                                        RPC_C_AUTHZ_NONE,
                                        (RPC_SECURITY_QOS *)&RpcSecQos);
        if (RPC_S_OK != status)
            {
            RpcBindingFree(&hServer);
            SetupMutex->Leave();
            return FALSE;
            }

        g_hIrxfer = hServer;
        }

    SetupMutex->Leave();
    return TRUE;
}




DWORD WINAPI
SensStartShellEvent(
    LPVOID lpvParam
    )
/*++

Routine Description:

    Hook to trap the StartShell Event. We treat this as Logon and notify
    various components (IR, OneStop, SENS, EventSystem) of Logon.

Arguments:

    lpvParam - Winlogon notification info.

Notes:

    a. When this function is called by Winlogon, the shell has begun starting.
       There is no guarantee that shell has started completely and is up and
       running.

    b. We create a thread to do bulk of the work and allow the function
       to return.

Return Value:

    RPC status from SensNotifyWinlogonEvent()

--*/
{
    BOOL bRetVal;
    DWORD status;
    DWORD dwLastError;
    PWLX_NOTIFICATION_INFO pTempInfo;

    dwLastError = 0;
    bRetVal = TRUE;

    DUMP_INFO("StartShell");

    //
    // Allocate space for the parameters
    //
    pTempInfo = (PWLX_NOTIFICATION_INFO) lpvParam;
    gpStartShellInfo = (PWLX_NOTIFICATION_INFO) new char[(sizeof(WLX_NOTIFICATION_INFO))];
    if (gpStartShellInfo == NULL)
        {
        dwLastError = GetLastError();
        LogMessage((SENSLOGN "SensStartShellEvent(): new() failed - %x.\n", dwLastError));
        return dwLastError;
        }
    memcpy(gpStartShellInfo, pTempInfo, sizeof(WLX_NOTIFICATION_INFO));

    // String in WLX_NOTIFICATION_INFO are Unicode
    gpStartShellInfo->UserName = (PWSTR) new WCHAR[(wcslen(pTempInfo->UserName) + 1)];
    gpStartShellInfo->Domain = (PWSTR) new WCHAR[(wcslen(pTempInfo->Domain) + 1)];
    gpStartShellInfo->WindowStation = (PWSTR) new WCHAR[(wcslen(pTempInfo->WindowStation) + 1)];
    if (   (gpStartShellInfo->UserName == NULL)
        || (gpStartShellInfo->Domain == NULL)
        || (gpStartShellInfo->WindowStation == NULL))
        {
        dwLastError = GetLastError();
        LogMessage((SENSLOGN "SensStartShellEvent(): new() of strings failed - %x.\n", dwLastError));
        delete gpStartShellInfo->UserName;
        delete gpStartShellInfo->Domain;
        delete gpStartShellInfo->WindowStation;
        delete gpStartShellInfo;
        gpStartShellInfo = NULL;
        return dwLastError;
        }

    //
    // Copy the parameters
    //
    wcscpy(gpStartShellInfo->UserName, pTempInfo->UserName);
    wcscpy(gpStartShellInfo->Domain, pTempInfo->Domain);
    wcscpy(gpStartShellInfo->WindowStation, pTempInfo->WindowStation);

    //
    // Create a thread to wait on the StartShell event
    //
    bRetVal = SensQueueUserWorkItem(
                  (LPTHREAD_START_ROUTINE) SensWaitToStartRoutine,
                  gpStartShellInfo, // Winlogon event info
                  SENS_LONG_ITEM    // Flags
                  );
    if (FALSE == bRetVal)
        {
        dwLastError = GetLastError();
        LogMessage((SENSLOGN "SensStartShellEvent(): SensQueueUserWorkItem() failed with %x.\n",
                    dwLastError));

        // Cleanup
        delete gpStartShellInfo->UserName;
        delete gpStartShellInfo->Domain;
        delete gpStartShellInfo->WindowStation;
        delete gpStartShellInfo;
        gpStartShellInfo = NULL;
        }

    return dwLastError;
}




DWORD WINAPI
SensPostShellEvent(
    LPVOID lpvParam
    )
/*++

Routine Description:

    Hook to trap the Post Shell Event.

Arguments:

    lpvParam - Winlogon notification info.

Return Value:

    RPC status from SensNotifyWinlogonEvent()

--*/
{
    DWORD status;

    DUMP_INFO("Post Shell");

    FIRE_EVENT(SENS_NOTIFY_WINLOGON_POSTSHELL);

    return status;
}




DWORD WINAPI
SensDisconnectEvent(
    LPVOID lpvParam
    )
/*++

Routine Description:

    Hook to trap the Session Disconnect Event.

Arguments:

    lpvParam - Winlogon notification info.

Return Value:

    RPC status from SensNotifyWinlogonEvent()

--*/
{
    DWORD status;

    DUMP_INFO("Session Disconnect");

    FIRE_EVENT(SENS_NOTIFY_WINLOGON_SESSION_DISCONNECT);

    return status;
}




DWORD WINAPI
SensReconnectEvent(
    LPVOID lpvParam
    )
/*++

Routine Description:

    Hook to trap the Session Reconnect Event.

Arguments:

    lpvParam - Winlogon notification info.

Return Value:

    RPC status from SensNotifyWinlogonEvent()

--*/
{
    DWORD status;

    DUMP_INFO("Session Reconnect");

    FIRE_EVENT(SENS_NOTIFY_WINLOGON_SESSION_RECONNECT);

    return status;
}




DWORD WINAPI
SensShutdownEvent(
    LPVOID lpvParam
    )
/*++

Routine Description:

    Hook to trap the Shutdown Event. Do some cleanup.

Arguments:

    lpvParam - Winlogon notification info.

Notes:

    a. It is guaranteed that COM activation will not work when this event
       is received.

Return Value:

    RPC status from SensNotifyWinlogonEvent()

--*/
{
    DWORD status;

    status = ERROR_SUCCESS;

    DUMP_INFO("Shutdown");

    return status;
}




DWORD WINAPI
SensLockEvent(
    LPVOID lpvParam
    )
/*++

Routine Description:

    Hook to trap the Display Lock Event.

Arguments:

    lpvParam - Winlogon notification info.

Return Value:

    RPC status from SensNotifyWinlogonEvent()

--*/
{
    DWORD status;

    DUMP_INFO("Display Lock");

    FIRE_EVENT(SENS_NOTIFY_WINLOGON_LOCK);

    return status;
}




DWORD WINAPI
SensUnlockEvent(
    LPVOID lpvParam
    )
/*++

Routine Description:

    Hook to trap the Display unlock Event.

Arguments:

    lpvParam - Winlogon notification info.

Return Value:

    RPC status from SensNotifyWinlogonEvent()

--*/
{
    DWORD status;

    DUMP_INFO("Display Unlock");

    FIRE_EVENT(SENS_NOTIFY_WINLOGON_UNLOCK);

    return status;
}




DWORD WINAPI
SensStartScreenSaverEvent(
    LPVOID lpvParam
    )
/*++

Routine Description:

    Hook to trap the ScreenSaver Start Event.

Arguments:

    lpvParam - Winlogon notification info.

Return Value:

    RPC status from SensNotifyWinlogonEvent()

--*/
{
    DWORD status;

    DUMP_INFO("StartScreenSaver");

    FIRE_EVENT(SENS_NOTIFY_WINLOGON_STARTSCREENSAVER);

    return status;
}





DWORD WINAPI
SensStopScreenSaverEvent(
    LPVOID lpvParam
    )
/*++

Routine Description:

    Hook to trap the Screen Saver Stop Event.

Arguments:

    lpvParam - Winlogon notification info.

Return Value:

    RPC status from SensNotifyWinlogonEvent()

--*/
{
    DWORD status;

    DUMP_INFO("StopScreenSaver");

    FIRE_EVENT(SENS_NOTIFY_WINLOGON_STOPSCREENSAVER);

    return status;
}




DWORD WINAPI
SensWaitToStartRoutine(
    LPVOID lpvParam
    )
/*++

Routine Description:

    This routine implements the work item that is queued when the StartShell
    event is received.

Arguments:

    lpvParam - Winlogon notification info.

Return Value:

    ERROR_SUCCESS, always

--*/
{
    DWORD dwError;
    DWORD dwWaitStatus;
    DWORD status;
    HRESULT hr;
    PWLX_NOTIFICATION_INFO pTempInfo;

    dwError = ERROR_SUCCESS;
    dwWaitStatus = 0x0;
    pTempInfo = (PWLX_NOTIFICATION_INFO) lpvParam;

    // Give SENS a chance to start if it has not already stated.
    // Give up after a short time in case sens is has been set to manual start
    // We could check the service configuration but the thread as already been
    // created so we save won't very much.

    ASSERT(ghSensStartedEvent);

    dwError = WaitForSingleObject(ghSensStartedEvent, 20*1000);
    if (dwError != STATUS_WAIT_0)
        {
        LogMessage((SENSLOGN "[%d] Wait for sens start event timed out...\n", GetTickCount()));
        }

    // Notify EventSystem
    LogMessage((SENSLOGN "[%d] Notifying EventSystem...\n", GetTickCount()));
    hr = SensNotifyEventSystem(NOTIFY_LCE_STARTSHELL, lpvParam);
    LogMessage((SENSLOGN "[%d] SensNotifyEventSystem() returned 0x%x\n", GetTickCount(), hr));

    // Notify IR
    LogMessage((SENSLOGN "[%d] Notifying IR...\n", GetTickCount()));
    OnUserLogon( pTempInfo );
    LogMessage((SENSLOGN "[%d] OnUserLogon succeeded.\n", GetTickCount()));

    // Try to fire SENS Event
    LogMessage((SENSLOGN "[%d] Notifying SENS...\n", GetTickCount()));
    FIRE_EVENT(SENS_NOTIFY_WINLOGON_STARTSHELL);
    LogMessage((SENSLOGN "[%d] SensNotifyWinlogonEvent(STARTSHELL) succeeded.\n", GetTickCount()));

    // Cleanup
    delete pTempInfo->UserName;
    delete pTempInfo->Domain;
    delete pTempInfo->WindowStation;
    delete pTempInfo;
    gpStartShellInfo = NULL;

    return ERROR_SUCCESS;
}




HRESULT
SensNotifyEventSystem(
    DWORD dwEvent,
    LPVOID lpvParam
    )
/*++

Routine Description:

    This routine notifies EventSystem of the Logon/Logoff events.

Arguments:

    dwEvent - Tells if the event is Logon or Logoff.

    lpvParam - Winlogon notification info.

Notes:

    a. EventSystem's notify routine has been observed sometimes to take
       a long time causing Logoff to take longer time to complete.

Return Value:

    HRESULT from EventSystem's Notify routine.

--*/
{
    HRESULT hr;

    hr = S_OK;

    PWLX_NOTIFICATION_INFO pTempInfo = (PWLX_NOTIFICATION_INFO)lpvParam;

    //
    // Notify the COM+ Event System that a user has just logged on.
    // Per-user subscriptions will be activated.
    //

    typedef HRESULT (__stdcall *LPFN_NOTIFICATION)(HANDLE);

    HMODULE hDLL;
    LPFN_NOTIFICATION lpfnNotify = NULL;

    hDLL = (HMODULE) LoadLibrary(EVENTSYSTEM_DLL);
    if (hDLL == NULL)
        {
        hr = HRESULT_FROM_WIN32(GetLastError());
        }
    else
        {
        if (dwEvent == NOTIFY_LCE_STARTSHELL)
            {
            lpfnNotify = (LPFN_NOTIFICATION) GetProcAddress(hDLL, NOTIFY_LCE_LOGONUSER);
            }
        else
        if (dwEvent == NOTIFY_LCE_LOGOFF)
            {
            lpfnNotify = (LPFN_NOTIFICATION) GetProcAddress(hDLL, NOTIFY_LCE_LOGOFFUSER);
            }

        hr = (lpfnNotify == NULL) ? HRESULT_FROM_WIN32(GetLastError()) : (*lpfnNotify)(pTempInfo->hToken);

        FreeLibrary(hDLL);
        }

    return hr;
}




void
OnUserLogon(
    WLX_NOTIFICATION_INFO * User
    )
{
    DWORD status = 0;

    if (FALSE == gbIsTokenCodeInitialized)
        {
        return;
        }

    if (IsRemoteSession())
        {
        return;
        }

    if (!ImpersonateLoggedOnUser( User->hToken ))
        {
        return;
        }

    UserLoggedOn( g_hIrxfer, &status );

    RevertToSelf();
}




void
OnUserLogoff(
    WLX_NOTIFICATION_INFO * User
    )
{
    DWORD status;

    if (FALSE == gbIsTokenCodeInitialized)
        {
        return;
        }

    if (IsRemoteSession())
        {
        return;
        }

    if (!ImpersonateLoggedOnUser( User->hToken ))
        {
        return;
        }

    UserLoggedOff( g_hIrxfer, &status );

    RevertToSelf();
}

#define MAX_WINDOWSTATION_NAME_LENGTH 1000


DWORD
_SecpGetCurrentUserToken(
    handle_t      Binding,
    wchar_t       WindowStation[],
    unsigned long ProcessId,
    unsigned long * pToken,
    unsigned long DesiredAccess
    )
{
    HANDLE LocalToken;
    HANDLE RemoteToken;
    HANDLE RemoteProcess;

    if (FALSE == gbIsTokenCodeInitialized)
        {
        return ERROR_OUTOFMEMORY;
        }

    //
    // Validate arguments.  The only one that can cause us harm is the window station.
    //
    if (IsBadStringPtr( WindowStation, MAX_WINDOWSTATION_NAME_LENGTH))
        {
        return ERROR_ACCESS_DENIED;
        }

    //
    // Clone the token into the requested process.
    //
    LocalToken = ActiveUserList->CurrentUserTokenFromWindowStation( WindowStation );
    if (!LocalToken)
        {
        LogMessage((SENSLOGN "GetCurrentUserToken(): User not logged on!\n"));
        return ERROR_NOT_LOGGED_ON;
        }

    RemoteProcess = OpenProcess(
                        PROCESS_DUP_HANDLE,
                        FALSE,         // not inheritable
                        ProcessId
                        );
    if (!RemoteProcess)
        {
        LogMessage((SENSLOGN "GetCurrentUserToken(): OpenProcess() failed!\n"));
        return GetLastError();
        }

    if (!DuplicateHandle(
             GetCurrentProcess(),
             LocalToken,
             RemoteProcess,
             &RemoteToken,
             DesiredAccess,
             FALSE,                // not inheritable
             0                     // no funny options
             ))
        {
        LogMessage((SENSLOGN "GetCurrentUserToken(): DuplicateHandle() failed!\n"));
        CloseHandle( RemoteProcess );
        return GetLastError();
        }

    CloseHandle( RemoteProcess );
    *pToken = HandleToUlong(RemoteToken);

    LogMessage((SENSLOGN "GetCurrentUserToken(): Succeeded. Returning 0x%x.\n", *pToken));

    return ERROR_SUCCESS;
}




BOOL
USER_LOGON_TABLE::Add(
    WLX_NOTIFICATION_INFO * User
    )
{
    USER_INFO_NODE * Entry;

    if (FALSE == gbIsTokenCodeInitialized)
        {
        return FALSE;
        }

    CLAIM_MUTEX Lock( Mutex );

    Entry = FindInactiveEntry();
    if (!Entry)
        {
        Entry = new USER_INFO_NODE;
        if (!Entry)
            {
            return FALSE;
            }

        Entry->fActive = FALSE;

        InsertTailList( &List, &Entry->Links );
        }

    Entry->Info.Size   = sizeof(WLX_NOTIFICATION_INFO);
    Entry->Info.Flags  = User->Flags;
    Entry->Info.hToken = User->hToken;

    WCHAR * Buffer = new WCHAR[ 1+wcslen(User->UserName) +
                                1+wcslen(User->Domain)   +
                                1+wcslen(User->WindowStation) ];
    if (!Buffer)
        {
        Entry->Info.UserName      = 0;
        Entry->Info.Domain        = 0;
        Entry->Info.WindowStation = 0;

        return FALSE;
        }

    //
    // This must match the code in Remove().
    //
    Entry->Info.UserName      = Buffer;
    Entry->Info.Domain        = Entry->Info.UserName+1+wcslen(User->UserName);
    Entry->Info.WindowStation = Entry->Info.Domain  +1+wcslen(User->Domain);

    wcscpy(Entry->Info.UserName,      User->UserName);
    wcscpy(Entry->Info.Domain,        User->Domain);
    wcscpy(Entry->Info.WindowStation, User->WindowStation);

    Entry->fActive = TRUE;

    return TRUE;
}




BOOL
USER_LOGON_TABLE::Remove(
    WLX_NOTIFICATION_INFO * User
    )
{
    if (FALSE == gbIsTokenCodeInitialized)
        {
        return FALSE;
        }

    USER_INFO_NODE * Entry = FromWindowStation( User->WindowStation );

    if (Entry)
        {
        //
        // This must match the code in Add().
        //
        delete Entry->Info.UserName;

        Entry->fActive = FALSE;

        Mutex.Leave();
        return TRUE;
        }

    Mutex.Leave();
    return FALSE;
}




HANDLE
USER_LOGON_TABLE::CurrentUserTokenFromWindowStation(
    wchar_t WindowStation[]
    )
{
    HANDLE Token = NULL;
    USER_INFO_NODE * Node = FromWindowStation(WindowStation);

    if (Node)
        {
        Token = Node->Info.hToken;
        }

    Mutex.Leave();
    return Token;
}




USER_INFO_NODE *
USER_LOGON_TABLE::FromWindowStation(
    wchar_t WindowStation[]
    )
/*++

    Note that the mutex is held on exit, to avoid race conditions.

--*/
{
    USER_INFO_NODE * Node;
    LIST_ENTRY *     Link;

    Mutex.Enter();

    for (Link = List.Flink,  Node = CONTAINING_RECORD(Link, USER_INFO_NODE, Links);
         Link != &List;
         Link = Link->Flink, Node = CONTAINING_RECORD(Link, USER_INFO_NODE, Links))
        {
        if (Node->fActive && 0 == wcscmp(Node->Info.WindowStation, WindowStation))
            {
            return Node;
            }
        }

    return 0;
}




USER_INFO_NODE *
USER_LOGON_TABLE::FindInactiveEntry(
    void
    )
{
    USER_INFO_NODE * Node;
    LIST_ENTRY *     Link;

    for (Link = List.Flink,  Node = CONTAINING_RECORD(Link, USER_INFO_NODE, Links);
         Link != &List;
         Link = Link->Flink, Node = CONTAINING_RECORD(Link, USER_INFO_NODE, Links))
        {
        if (!Node->fActive)
            {
            return Node;
            }
        }

    return 0;
}




RPC_STATUS RPC_ENTRY
AllowLocalSystem (
    IN RPC_IF_HANDLE  InterfaceUuid,
    IN void *Context
    )
{
    if (RpcImpersonateClient(Context))
        {
        return ERROR_ACCESS_DENIED;
        }

    //
    // Clone the user's token so we can launch apps on the desktop.
    //
    HANDLE ImpersonationToken;

    if (!OpenThreadToken(
             GetCurrentThread(),
             TOKEN_QUERY,
             TRUE,                 // use process token for access check
             &ImpersonationToken
             ))
        {
        return GetLastError();
        }

    //
    // Get the SID from the token.
    //
    DWORD SizeNeeded = 0;
    TOKEN_USER * TokenData = NULL;

    // Get the size first.
    if (!GetTokenInformation(
             ImpersonationToken,
             TokenUser,
             0,
             0,
             &SizeNeeded
             ))
        {
        DWORD dwLastError = GetLastError();

        if (ERROR_INSUFFICIENT_BUFFER != dwLastError)
            {
            CloseHandle( ImpersonationToken );
            return dwLastError;
            }
        }

    __try
        {
        TokenData = (TOKEN_USER *) _alloca( SizeNeeded );
        }
    __except(EXCEPTION_EXECUTE_HANDLER)
        {
        // Nothing
        }

    if (!TokenData)
        {
        CloseHandle( ImpersonationToken );
        return ERROR_NOT_ENOUGH_MEMORY;
        }

    if (!GetTokenInformation(
             ImpersonationToken,
             TokenUser,
             TokenData,
             SizeNeeded,
             &SizeNeeded
             ))
        {
        CloseHandle( ImpersonationToken );
        return GetLastError();
        }

    CloseHandle(ImpersonationToken);

    if (!RtlEqualSid(
             TokenData->User.Sid,
             LocalSystemSid
             ))
        {
        return ERROR_ACCESS_DENIED;
        }

    return 0;
}

