//  --------------------------------------------------------------------------
//  Module Name: WaitInteractiveReady.cpp
//
//  Copyright (c) 2001, Microsoft Corporation
//
//  Class to handle waiting on the shell signal the desktop switch.
//
//  History:    2001-01-15  vtan        created
//  --------------------------------------------------------------------------

#include "StandardHeader.h"
#include "WaitInteractiveReady.h"

#include <ginaipc.h>
#include <msginaexports.h>

#include "Impersonation.h"
#include "StatusCode.h"

//  --------------------------------------------------------------------------
//  CWaitInteractiveReady::s_pWlxContext
//  CWaitInteractiveReady::s_hWait
//  CWaitInteractiveReady::s_hEvent
//  CWaitInteractiveReady::s_hEventShellReady
//  CWaitInteractiveReady::s_szEventName
//
//  Purpose:    Static member variables.
//
//  History:    2001-01-15  vtan        created
//  --------------------------------------------------------------------------

HANDLE                  CWaitInteractiveReady::s_hWait                  =   NULL;
CWaitInteractiveReady*  CWaitInteractiveReady::s_pWaitInteractiveReady  =   NULL;
HANDLE                  CWaitInteractiveReady::s_hEventShellReady       =   NULL;
const TCHAR             CWaitInteractiveReady::s_szEventName[]          =   TEXT("msgina: ShellReadyEvent");

//  --------------------------------------------------------------------------
//  CWaitInteractiveReady::CWaitInteractiveReady
//
//  Arguments:  pWlxContext     =   PGLOBALS struct for msgina.
//
//  Returns:    <none>
//
//  Purpose:    Private constructor for this class. Create a synchronization
//              event for callback state determination.
//
//  History:    2001-07-17  vtan        created
//  --------------------------------------------------------------------------

CWaitInteractiveReady::CWaitInteractiveReady (void *pWlxContext) :
    _pWlxContext(pWlxContext),
    _hEvent(CreateEvent(NULL, TRUE, FALSE, NULL))

{
}

//  --------------------------------------------------------------------------
//  CWaitInteractiveReady::~CWaitInteractiveReady
//
//  Arguments:  <none>
//
//  Returns:    <none>
//
//  Purpose:    Destructor. Clears member variables.
//
//  History:    2001-07-17  vtan        created
//  --------------------------------------------------------------------------

CWaitInteractiveReady::~CWaitInteractiveReady (void)

{
    ReleaseHandle(_hEvent);
    _pWlxContext = NULL;
}

//  --------------------------------------------------------------------------
//  CWaitInteractiveReady::Create
//
//  Arguments:  pWlxContext     =   PGLOBALS struct for msgina.
//
//  Returns:    NTSTATUS
//
//  Purpose:    Creates resources required to manage switching desktops when
//              the shell signals the interactive ready event. This allows
//              the shell to be brought up in an interactive state.
//
//  History:    2001-01-15  vtan        created
//  --------------------------------------------------------------------------

NTSTATUS    CWaitInteractiveReady::Create (void *pWlxContext)

{
    NTSTATUS    status;
    HANDLE      hToken;

    ASSERTMSG(s_hWait == NULL, "Wait already registered in CWaitInteractiveReady::Start");
    ASSERTMSG(s_hEventShellReady == NULL, "Named event already exists in CWaitInteractiveReady::Start");
    hToken = _Gina_GetUserToken(pWlxContext);
    if (hToken != NULL)
    {
        CImpersonation  impersonation(hToken);

        if (impersonation.IsImpersonating())
        {
            s_hEventShellReady = CreateEvent(NULL, TRUE, FALSE, s_szEventName);
            if (s_hEventShellReady != NULL)
            {
                status = STATUS_SUCCESS;
            }
            else
            {
                status = CStatusCode::StatusCodeOfLastError();
                TSTATUS(ReleaseEvent());
            }
        }
        else
        {
            status = STATUS_BAD_IMPERSONATION_LEVEL;
        }
    }
    else
    {
        status = STATUS_NO_TOKEN;
    }
    return(status);
}

//  --------------------------------------------------------------------------
//  CWaitInteractiveReady::Register
//
//  Arguments:  <none>
//
//  Returns:    NTSTATUS
//
//  Purpose:    Checks the state of the event being waited on. It's possible
//              that explorer may have already signaled this event before this
//              code is executed. If the event is signaled then CB_ShellReady
//              has already been called.
//              
//  History:    2001-07-16  vtan        created
//  --------------------------------------------------------------------------

NTSTATUS    CWaitInteractiveReady::Register (void *pWlxContext)

{
    NTSTATUS    status;

    ASSERTMSG(s_hWait == NULL, "Wait already registered in CWaitInteractiveReady::Check");

    //  Check and Stop should not be called from any thread other than
    //  the main thread of winlogon. It's called in only a few places.

    //  Firstly check the named event (msgina: ShellReadyEvent).

    if (s_hEventShellReady != NULL)
    {

        //  If it exists then check to see if it's signaled.

        if (WaitForSingleObject(s_hEventShellReady, 0) == WAIT_OBJECT_0)
        {

            //  If it's signaled then release the resources and return
            //  a failure code (force it down the classic UI path).

            TSTATUS(ReleaseEvent());
            status = STATUS_UNSUCCESSFUL;
        }
        else
        {
            CWaitInteractiveReady   *pWaitInteractiveReady;

            pWaitInteractiveReady = new CWaitInteractiveReady(pWlxContext);
            if (pWaitInteractiveReady != NULL)
            {
                if (pWaitInteractiveReady->IsCreated())
                {

                    //  Otherwise if it's not signaled then register a wait on
                    //  the named object for 30 seconds.

                    if (RegisterWaitForSingleObject(&s_hWait,
                                                    s_hEventShellReady,
                                                    CB_ShellReady,
                                                    pWaitInteractiveReady,
                                                    30000,
                                                    WT_EXECUTEDEFAULT | WT_EXECUTEONLYONCE) == FALSE)
                    {
                        status = CStatusCode::StatusCodeOfLastError();
                        delete pWaitInteractiveReady;
                        TSTATUS(ReleaseEvent());
                    }
                    else
                    {
                        s_pWaitInteractiveReady = pWaitInteractiveReady;
                        status = STATUS_SUCCESS;
                    }
                }
                else
                {
                    delete pWaitInteractiveReady;
                    TSTATUS(ReleaseEvent());
                    status = STATUS_NO_MEMORY;
                }
            }
            else
            {
                TSTATUS(ReleaseEvent());
                status = STATUS_NO_MEMORY;
            }
        }
    }
    else
    {
        status = STATUS_UNSUCCESSFUL;
    }
    return(status);
}

//  --------------------------------------------------------------------------
//  CWaitInteractiveReady::Cancel
//
//  Arguments:  <none>
//
//  Returns:    NTSTATUS
//
//  Purpose:    Removes the wait on the interactive ready object. This is
//              done when a user causes a return to welcome. This is
//              necessary because if the callback fires AFTER the return to
//              welcome we will switch to the user's desktop which violates
//              security.
//              
//  History:    2001-01-15  vtan        created
//  --------------------------------------------------------------------------

NTSTATUS    CWaitInteractiveReady::Cancel (void)

{
    HANDLE  hWait;

    //  Grab the global hWait. If somebody beat us to this or it
    //  didn't exist then there's nothing to do.

    hWait = InterlockedExchangePointer(&s_hWait, NULL);
    if (hWait != NULL)
    {
        CWaitInteractiveReady   *pThis;

        //  Grab the s_pWaitInteractiveReady. This is a pointer to the callback
        //  memory. It will be valid unless the callback has already interlocked
        //  the variable itself which means the callback has reached the determined
        //  p0int already anyway and no wait is necessary.

        pThis = static_cast<CWaitInteractiveReady*>(InterlockedExchangePointer(reinterpret_cast<void**>(&s_pWaitInteractiveReady), NULL));

        //  Try to unregister the wait. If this fails then the callback
        //  is being executed. Wait until the callback reaches a determined
        //  point (it will signal the internal event). Wait TWO minutes for
        //  this. We cannot block the main thread of winlogon. If everything
        //  is working nicely then this will be a no-brainer wait.

        if (UnregisterWait(hWait) == FALSE)
        {

            //  If the unregister fails then wait if there's a valid event
            //  to wait on - reasons explained above.

            if (pThis != NULL)
            {
                (DWORD)WaitForSingleObject(pThis->_hEvent, 120000);
            }
        }
        else
        {

            //  Otherwise the wait was successfully unregistered indicating the
            //  callback is not executing. Release the memory that was allocated
            //  for it because it's not going to execute now.

            if (pThis != NULL)
            {
                delete pThis;
            }
        }
    }

    //  Always release the wait handle. This is valid because if the callback
    //  is executing and it grabbed the s_hWait then it will be NULL and it will
    //  also try to release the event handle. If there was no s_hWait then we
    //  just release the event handle anyway. Otherwise we grabbed the s_hWait
    //  above and can release the event handle as well.

    TSTATUS(ReleaseEvent());
    return(STATUS_SUCCESS);
}

//  --------------------------------------------------------------------------
//  CWaitInteractiveReady::IsCreated
//
//  Arguments:  <none>
//
//  Returns:    bool
//
//  Purpose:    Returns whether the object is successfully created.
//
//  History:    2001-07-17  vtan        created
//  --------------------------------------------------------------------------

bool    CWaitInteractiveReady::IsCreated (void)    const

{
    return(_hEvent != NULL);
}

//  --------------------------------------------------------------------------
//  CWaitInteractiveReady::ReleaseEvent
//
//  Arguments:  <none>
//
//  Returns:    NTSTATUS
//
//  Purpose:    Resets the static member variables to the uninitialized
//              state.
//
//  History:    2001-01-15  vtan        created
//  --------------------------------------------------------------------------

NTSTATUS    CWaitInteractiveReady::ReleaseEvent (void)

{
    HANDLE  h;

    h = InterlockedExchangePointer(&s_hEventShellReady, NULL);
    if (h != NULL)
    {
        TBOOL(CloseHandle(h));
    }
    return(STATUS_SUCCESS);
}

//  --------------------------------------------------------------------------
//  CWaitInteractiveReady::CB_ShellReady
//
//  Arguments:  pParameter          =   User callback parameter.
//              TimerOrWaitFired    =   Timer or wait fired.
//
//  Returns:    <none>
//
//  Purpose:    Invoked when the interactive ready event is signaled by the
//              shell. Switch the desktop to the user's desktop.
//
//  History:    2001-01-15  vtan        created
//  --------------------------------------------------------------------------

void    CALLBACK    CWaitInteractiveReady::CB_ShellReady (void *pParameter, BOOLEAN TimerOrWaitFired)

{
    UNREFERENCED_PARAMETER(TimerOrWaitFired);

    HANDLE                  hWait;
    CWaitInteractiveReady   *pThis;

    pThis = static_cast<CWaitInteractiveReady*>(pParameter);

    //  Wrap the desktop manipulation around a scope which saves and restores
    //  the desktop. _Gina_SwitchDesktopToUser will set the thread's desktop
    //  to \Default and will NOT restore it. This scoped object will restore it.

    if (pThis->_pWlxContext != NULL)
    {
        CDesktop    desktop;

        //  Hide the status host. Switch the desktops.

        _ShellStatusHostEnd(HOST_END_HIDE);
        (int)_Gina_SwitchDesktopToUser(pThis->_pWlxContext);
    }

    //  Signal the internal event.

    TBOOL(SetEvent(pThis->_hEvent));

    //  Grab the global hWait. If somebody beat us to it then they're trying
    //  to stop this from happening. They could beat us to it at any time from
    //  the invokation of the callback to here. That thread will wait for this
    //  one to signal the internal event. In that case there's no work for this
    //  thread. The owner of the hWait has to clean up. If this thread gets the
    //  hWait then unregister the wait and release the resources.

    hWait = InterlockedExchangePointer(&s_hWait, NULL);
    if (hWait != NULL)
    {
        (BOOL)UnregisterWait(hWait);
        TSTATUS(ReleaseEvent());
    }

    //  Interlock the s_pWaitInteractiveReady variable which is also an
    //  indicator of having reached the determined point in the callback.

    (CWaitInteractiveReady*)InterlockedExchangePointer(reinterpret_cast<void**>(&s_pWaitInteractiveReady), NULL);

    //  Delete our blob of data.

    delete pThis;
}