//  --------------------------------------------------------------------------
//  Module Name: GracefulTerminateApplication.cpp
//
//  Copyright (c) 2000, Microsoft Corporation
//
//  Class to manager terminating applications gracefully.
//
//  History:    2000-10-27  vtan        created
//              2000-11-04  vtan        split into separate file
//  --------------------------------------------------------------------------

#ifdef      _X86_

#include "StandardHeader.h"
#include "GracefulTerminateApplication.h"

#include "KernelResources.h"
#include "Thread.h"
#include "WarningDialog.h"

//  --------------------------------------------------------------------------
//  CProgressDialog
//
//  Purpose:    A class to manage displaying a progress dialog on a separate
//              thread if a certain period of time elapses. This is so that
//              in case the process doesn't terminate in a period of time
//              a dialog indicating wait is shown so the user is not left
//              staring at a blank screen.
//
//  History:    2000-11-04  vtan        created
//  --------------------------------------------------------------------------

class   CProgressDialog : public CThread
{
    private:
                                    CProgressDialog (void);
    public:
                                    CProgressDialog (CWarningDialog *pWarningDialog);
        virtual                     ~CProgressDialog (void);

                void                SignalTerminated (void);
    protected:
        virtual DWORD               Entry (void);
    private:
                CWarningDialog*     _pWarningDialog;
                CEvent              _event;
};

//  --------------------------------------------------------------------------
//  CProgressDialog::CProgressDialog
//
//  Arguments:  <none>
//
//  Returns:    <none>
//
//  Purpose:    Constructor for CProgressDialog. Keeps a reference to the
//              given CWarningDialog.
//
//  History:    2000-11-04  vtan        created
//  --------------------------------------------------------------------------

CProgressDialog::CProgressDialog (CWarningDialog *pWarningDialog) :
    _pWarningDialog(NULL),
    _event(NULL)

{
    if (IsCreated())
    {
        pWarningDialog->AddRef();
        _pWarningDialog = pWarningDialog;
        Resume();
    }
}

//  --------------------------------------------------------------------------
//  CProgressDialog::~CProgressDialog
//
//  Arguments:  <none>
//
//  Returns:    <none>
//
//  Purpose:    Releases the CWarningDialog reference.
//
//  History:    2000-11-04  vtan        created
//  --------------------------------------------------------------------------

CProgressDialog::~CProgressDialog (void)

{
    _pWarningDialog->Release();
    _pWarningDialog = NULL;
}

//  --------------------------------------------------------------------------
//  CProgressDialog::SignalTerminated
//
//  Arguments:  <none>
//
//  Returns:    <none>
//
//  Purpose:    Signals the internal event that the process being watched is
//              termination. This is necessary because there is no handle to
//              the actual process to watch as it's kept on the server side
//              and not given to us the client. However, the result of the
//              termination is. Signaling this object will release the waiting
//              thread and cause it to exit.
//
//  History:    2000-11-04  vtan        created
//  --------------------------------------------------------------------------

void    CProgressDialog::SignalTerminated (void)

{
    TSTATUS(_event.Set());
}

//  --------------------------------------------------------------------------
//  CProgressDialog::Entry
//
//  Arguments:  <none>
//
//  Returns:    DWORD
//
//  Purpose:    Thread which waits for the internal event to be signaled. If
//              the event is signaled it will "cancel" the 3 second wait and
//              the thread will exit. Otherwise the wait times out and the
//              progress dialog is shown - waiting for termination.
//
//  History:    2000-11-04  vtan        created
//  --------------------------------------------------------------------------

DWORD   CProgressDialog::Entry (void)

{
    DWORD   dwWaitResult;

    //  Wait for the event to be signaled or for it to timeout. If signaled
    //  then the process is terminated and no progress is required. Otherwise
    //  prepare to show progress while the process is being terminated.

    if (NT_SUCCESS(_event.Wait(2000, &dwWaitResult)) && (WAIT_TIMEOUT == dwWaitResult))
    {
        _pWarningDialog->ShowProgress(100, 7500);
    }
    return(0);
}

//  --------------------------------------------------------------------------
//  CGracefulTerminateApplication::CGracefulTerminateApplication
//
//  Arguments:  <none>
//
//  Returns:    <none>
//
//  Purpose:    Constructor for CGracefulTerminateApplication.
//
//  History:    2000-10-27  vtan        created
//  --------------------------------------------------------------------------

CGracefulTerminateApplication::CGracefulTerminateApplication (void) :
    _dwProcessID(0),
    _fFoundWindow(false)

{
}

//  --------------------------------------------------------------------------
//  CGracefulTerminateApplication::~CGracefulTerminateApplication
//
//  Arguments:  <none>
//
//  Returns:    <none>
//
//  Purpose:    Destructor for CGracefulTerminateApplication.
//
//  History:    2000-10-27  vtan        created
//  --------------------------------------------------------------------------

CGracefulTerminateApplication::~CGracefulTerminateApplication (void)

{
}

//  --------------------------------------------------------------------------
//  CGracefulTerminateApplication::Terminate
//
//  Arguments:  dwProcessID     =   Process ID of process to terminate.
//
//  Returns:    <none>
//
//  Purpose:    Walk the window list for top level windows that correspond
//              to this process ID and are visible. Close them. The callback
//              handles the work and this function returns the result in the
//              process exit code which the server examines.
//
//  History:    2000-10-27  vtan        created
//  --------------------------------------------------------------------------

void    CGracefulTerminateApplication::Terminate (DWORD dwProcessID)

{
    DWORD   dwExitCode;

    _dwProcessID = dwProcessID;
    TBOOL(EnumWindows(EnumWindowsProc, reinterpret_cast<LPARAM>(this)));
    if (_fFoundWindow)
    {
        dwExitCode = WAIT_WINDOWS_FOUND;
    }
    else
    {
        dwExitCode = NO_WINDOWS_FOUND;
    }
    ExitProcess(dwExitCode);
}

//  --------------------------------------------------------------------------
//  CGracefulTerminateApplication::Prompt
//
//  Arguments:  hInstance   =   HINSTANCE of this DLL.
//              hProcess    =   Inherited handle to parent process.
//
//  Returns:    <none>
//
//  Purpose:    Shows a prompt that handles termination of the parent of this
//              process. The parent is assumed to be a bad application type 1
//              that caused this stub to be executed via the
//              "rundll32 shsvcs.dll,FUSCompatibilityEntry prompt" command.
//              Because there can only be a single instance of the type 1
//              application running and the parent of this process hasn't
//              registered yet querying for this process by image name will
//              always find the correct process.
//
//  History:    2000-11-03  vtan        created
//  --------------------------------------------------------------------------

void    CGracefulTerminateApplication::Prompt (HINSTANCE hInstance, HANDLE hProcess)

{
    bool                        fTerminated;
    ULONG                       ulReturnLength;
    PROCESS_BASIC_INFORMATION   processBasicInformation;

    //  Read the parent's image name from the RTL_USER_PROCESS_PARAMETERS.

    fTerminated = false;
    if (hProcess != NULL)
    {
        if (NT_SUCCESS(NtQueryInformationProcess(hProcess,
                                                 ProcessBasicInformation,
                                                 &processBasicInformation,
                                                 sizeof(processBasicInformation),
                                                 &ulReturnLength)))
        {
            SIZE_T  dwNumberOfBytesRead;
            PEB     peb;

            if (ReadProcessMemory(hProcess,
                                  processBasicInformation.PebBaseAddress,
                                  &peb,
                                  sizeof(peb),
                                  &dwNumberOfBytesRead) != FALSE)
            {
                RTL_USER_PROCESS_PARAMETERS     processParameters;

                if (ReadProcessMemory(hProcess,
                                      peb.ProcessParameters,
                                      &processParameters,
                                      sizeof(processParameters),
                                      &dwNumberOfBytesRead) != FALSE)
                {
                    WCHAR   *pszImageName;

                    pszImageName = static_cast<WCHAR*>(LocalAlloc(LMEM_FIXED, processParameters.ImagePathName.Length + (sizeof('\0') * sizeof(WCHAR))));
                    if (pszImageName != NULL)
                    {
                        if (ReadProcessMemory(hProcess,
                                              processParameters.ImagePathName.Buffer,
                                              pszImageName,
                                              processParameters.ImagePathName.Length,
                                              &dwNumberOfBytesRead) != FALSE)
                        {
                            pszImageName[processParameters.ImagePathName.Length / sizeof(WCHAR)] = L'\0';

                            //  And show a prompt for this process.

                            fTerminated = ShowPrompt(hInstance, pszImageName);
                        }
                        (HLOCAL)LocalFree(pszImageName);
                    }
                }
            }
        }
        TBOOL(CloseHandle(hProcess));
    }
    ExitProcess(fTerminated);
}

//  --------------------------------------------------------------------------
//  CGracefulTerminateApplication::ShowPrompt
//
//  Arguments:  hInstance       =   HINSTANCE of this DLL.
//              pszImagename    =   Image name of process to terminate.
//
//  Returns:    bool
//
//  Purpose:    Shows the appropriate prompt for termination of the first
//              instance of a BAM type 1 process. If the current user does
//              not have administrator privileges then a "STOP" dialog is
//              shown that the user must get the other user to close the
//              program. Otherwise the "PROMPT" dialog is shown which gives
//              the user the option to terminate the process.
//
//              If termination is requested and the termatinion fails the
//              another warning dialog to that effect is shown.
//
//  History:    2000-11-03  vtan        created
//  --------------------------------------------------------------------------

bool    CGracefulTerminateApplication::ShowPrompt (HINSTANCE hInstance, const WCHAR *pszImageName)

{
    bool                            fTerminated;
    ULONG                           ulConnectionInfoLength;
    HANDLE                          hPort;
    UNICODE_STRING                  portName;
    SECURITY_QUALITY_OF_SERVICE     sqos;
    WCHAR                           szConnectionInfo[32];

    fTerminated = false;
    RtlInitUnicodeString(&portName, FUS_PORT_NAME);
    sqos.Length = sizeof(sqos);
    sqos.ImpersonationLevel = SecurityImpersonation;
    sqos.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING;
    sqos.EffectiveOnly = TRUE;
    lstrcpyW(szConnectionInfo, FUS_CONNECTION_REQUEST);
    ulConnectionInfoLength = sizeof(szConnectionInfo);
    if (NT_SUCCESS(NtConnectPort(&hPort,
                                 &portName,
                                 &sqos,
                                 NULL,
                                 NULL,
                                 NULL,
                                 szConnectionInfo,
                                 &ulConnectionInfoLength)))
    {
        bool            fCanTerminateFirstInstance;
        CWarningDialog  *pWarningDialog;
        WCHAR           szUser[256];

        //  Get the user's privilege level for this operation. This API also
        //  returns the current user for the BAM type 1 process.

        fCanTerminateFirstInstance = CanTerminateFirstInstance(hPort, pszImageName, szUser, ARRAYSIZE(szUser));
        pWarningDialog = new CWarningDialog(hInstance, NULL, pszImageName, szUser);
        if (pWarningDialog != NULL)
        {

            //  Show the appropriate dialog based on the privilege level.

            if (pWarningDialog->ShowPrompt(fCanTerminateFirstInstance) == IDOK)
            {
                CProgressDialog     *pProgressDialog;

                //  Create a progress dialog object in case of delayed termination.
                //  This will create the watcher thread.

                pProgressDialog = new CProgressDialog(pWarningDialog);
                if ((pProgressDialog != NULL) && !pProgressDialog->IsCreated())
                {
                    pProgressDialog->Release();
                    pProgressDialog = NULL;
                }

                //  Attempt to terminate the process if requested by the user.

                fTerminated = TerminatedFirstInstance(hPort, pszImageName);

                //  Once this function returns signal the event (in case the
                //  thread is still waiting). If the thread is still waiting this
                //  effectively cancels the dialog. Either way if the dialog is
                //  shown then close it, wait for the thread to exit and release
                //  the reference to destroy the object.

                if (pProgressDialog != NULL)
                {
                    pProgressDialog->SignalTerminated();
                    pWarningDialog->CloseDialog();
                    pProgressDialog->WaitForCompletion(INFINITE);
                    pProgressDialog->Release();
                }

                //  If there was some failure then let the user know.

                if (!fTerminated)
                {
                    pWarningDialog->ShowFailure();
                }
            }
            pWarningDialog->Release();
        }
        TBOOL(CloseHandle(hPort));
    }
    return(fTerminated);
}

//  --------------------------------------------------------------------------
//  CGracefulTerminateApplication::CanTerminateFirstInstance
//
//  Arguments:  hPort           =   Port to server.
//              pszImageName    =   Image name of process to terminate.
//              pszUser         =   User of process (returned).
//              cchUser         =   Count of characters for buffer.
//
//  Returns:    bool
//
//  Purpose:    Asks the server whether the current user has privileges to
//              terminate the BAM type 1 process of the given image name
//              which is known to be running. The API returns whether the
//              operation is allowed and who the current user of the process
//              is.
//
//  History:    2000-11-03  vtan        created
//  --------------------------------------------------------------------------

bool    CGracefulTerminateApplication::CanTerminateFirstInstance (HANDLE hPort, const WCHAR *pszImageName, WCHAR *pszUser, int cchUser)

{
    bool    fCanTerminate;

    fCanTerminate = false;
    if ((hPort != NULL) && (pszImageName != NULL))
    {
        FUSAPI_PORT_MESSAGE     portMessageIn, portMessageOut;

        ZeroMemory(&portMessageIn, sizeof(portMessageIn));
        ZeroMemory(&portMessageOut, sizeof(portMessageOut));
        portMessageIn.apiBAM.apiGeneric.ulAPINumber = API_BAM_QUERYUSERPERMISSION;
        portMessageIn.apiBAM.apiSpecific.apiQueryUserPermission.in.pszImageName = pszImageName;
        portMessageIn.apiBAM.apiSpecific.apiQueryUserPermission.in.cchImageName = lstrlen(pszImageName) + sizeof('\0');
        portMessageIn.apiBAM.apiSpecific.apiQueryUserPermission.in.pszUser = pszUser;
        portMessageIn.apiBAM.apiSpecific.apiQueryUserPermission.in.cchUser = cchUser;
        portMessageIn.portMessage.u1.s1.DataLength = sizeof(API_BAM);
        portMessageIn.portMessage.u1.s1.TotalLength = static_cast<CSHORT>(sizeof(FUSAPI_PORT_MESSAGE));
        if (NT_SUCCESS(NtRequestWaitReplyPort(hPort, &portMessageIn.portMessage, &portMessageOut.portMessage)) &&
            NT_SUCCESS(portMessageOut.apiBAM.apiGeneric.status))
        {
            fCanTerminate = portMessageOut.apiBAM.apiSpecific.apiQueryUserPermission.out.fCanShutdownApplication;
            pszUser[cchUser - sizeof('\0')] = L'\0';
        }
        else
        {
            pszUser[0] = L'\0';
        }
    }
    return(fCanTerminate);
}

//  --------------------------------------------------------------------------
//  CGracefulTerminateApplication::TerminatedFirstInstance
//
//  Arguments:  hPort           =   Port to server.
//              pszImageName    =   Image name to terminate.
//
//  Returns:    bool
//
//  Purpose:    Asks the server to terminate the first running instance of the
//              BAM type 1 process.
//
//  History:    2000-11-03  vtan        created
//  --------------------------------------------------------------------------

bool    CGracefulTerminateApplication::TerminatedFirstInstance (HANDLE hPort, const WCHAR *pszImageName)

{
    bool    fTerminated;

    fTerminated = false;
    if (hPort != NULL)
    {
        FUSAPI_PORT_MESSAGE     portMessageIn, portMessageOut;

        ZeroMemory(&portMessageIn, sizeof(portMessageIn));
        ZeroMemory(&portMessageOut, sizeof(portMessageOut));
        portMessageIn.apiBAM.apiGeneric.ulAPINumber = API_BAM_TERMINATERUNNING;
        portMessageIn.apiBAM.apiSpecific.apiTerminateRunning.in.pszImageName = pszImageName;
        portMessageIn.apiBAM.apiSpecific.apiTerminateRunning.in.cchImageName = lstrlen(pszImageName) + sizeof('\0');
        portMessageIn.portMessage.u1.s1.DataLength = sizeof(API_BAM);
        portMessageIn.portMessage.u1.s1.TotalLength = static_cast<CSHORT>(sizeof(FUSAPI_PORT_MESSAGE));
        if (NT_SUCCESS(NtRequestWaitReplyPort(hPort, &portMessageIn.portMessage, &portMessageOut.portMessage)) &&
            NT_SUCCESS(portMessageOut.apiBAM.apiGeneric.status))
        {
            fTerminated = portMessageOut.apiBAM.apiSpecific.apiTerminateRunning.out.fResult;
        }
    }
    return(fTerminated);
}

//  --------------------------------------------------------------------------
//  CGracefulTerminateApplication::EnumWindowsProc
//
//  Arguments:  See the platform SDK under EnumWindowsProc.
//
//  Returns:    See the platform SDK under EnumWindowsProc.
//
//  Purpose:    Top level window enumerator callback which compares the
//              process IDs of the windows and whether they are visible. If
//              there is a match on BOTH counts the a WM_CLOSE message is
//              posted to the window to allow a graceful termination.
//
//  History:    2000-10-27  vtan        created
//  --------------------------------------------------------------------------

BOOL    CALLBACK    CGracefulTerminateApplication::EnumWindowsProc (HWND hwnd, LPARAM lParam)

{
    DWORD                           dwThreadID, dwProcessID;
    CGracefulTerminateApplication   *pThis;

    pThis = reinterpret_cast<CGracefulTerminateApplication*>(lParam);
    dwThreadID = GetWindowThreadProcessId(hwnd, &dwProcessID);
    if ((dwProcessID == pThis->_dwProcessID) && IsWindowVisible(hwnd))
    {
        pThis->_fFoundWindow = true;
        TBOOL(PostMessage(hwnd, WM_CLOSE, 0, 0));
    }
    return(TRUE);
}

#endif  /*  _X86_   */