/*++

Copyright (c) 1999-2000  Microsoft Corporation

Module Name:

    RDPRemoteDesktopSession

Abstract:

    The CRemoteDesktopSession class is the parent 
    class for the Remote Desktop class hierarchy on the server-side.  
    It helps the CRemoteDesktopServerHost class to implement 
    the ISAFRemoteDesktopSession interface.  
    
    The Remote Desktop class hierarchy provides a pluggable C++ interface 
    for remote desktop access, by abstracting the implementation 
    specific details of remote desktop access for the server-side.

Author:

    Tad Brockway 02/00

Revision History:

--*/

#include <RemoteDesktop.h>
#include "stdafx.h"

#ifdef TRC_FILE
#undef TRC_FILE
#endif

#define TRC_FILE  "_srdses"

#include "RDSHost.h"
#include "RemoteDesktopSession.h"
#include "RemoteDesktopServerHost.h"
#include <sessmgr_i.c>
#include <objidl.h>
#include <objbase.h>


///////////////////////////////////////////////////////
//
//  CRemoteDesktopSession Methods
//

HRESULT 
CRemoteDesktopSession::FinalConstruct()
/*++

Routine Description:

    Final Constructor

Arguments:

Return Value:

    S_OK on success.  Otherwise, an error code is returned.

 --*/
{
    DC_BEGIN_FN("CRemoteDesktopSession::FinalConstruct");

    DC_END_FN();

    return S_OK;
}

CRemoteDesktopSession::~CRemoteDesktopSession()
/*++

Routine Description:

    Destructor

Arguments:

Return Value:

 --*/
{
    DC_BEGIN_FN("CRemoteDesktopSession::~CRemoteDesktopSession");

    Shutdown();

    //
    //  Release any lingering outgoing interfaces.  Need to catch
    //  exceptions here in case the outgoing interface application
    //  has already gone away.
    //
    try {
        if (m_OnConnected != NULL) {
            m_OnConnected->Release();
        }
        if (m_OnDisconnected != NULL) {
            m_OnDisconnected->Release();
        }
    }
    catch (...) {
        TRC_ALT((TB, L"Exception caught in outgoing interface release."));
    }

    DC_END_FN();
}

HRESULT
CRemoteDesktopSession::Initialize(
    BSTR connectParms,
    CRemoteDesktopServerHost *hostObject,
    REMOTE_DESKTOP_SHARING_CLASS sharingClass,
    BOOL bEnableCallback,
    DWORD timeOut,
    BSTR userHelpCreateBlob,
    LONG tsSessionID,
    BSTR userSid
    )
/*++

Routine Description:

    The Initialize method prepares the COM object for connection by 
    the client-side Remote Desktop Host ActiveX Control.

Arguments:

    connectParms    -   If parms are non-NULL, then the session already exists.  
                        Otherwise, a new session should be created.
    hostObject      -   Back pointer to containing RDS Host object.
    sharingClass    -   Level of desktop sharing for a new session.
    bEnableCallback -   TRUE to instruct sessmgr to call session resolver, FALSE otherwise.
    timeOut         -   Help session timeout value.  0, if not specified.
    userHelpCreateBlob - user specified help session creation blob.
    tsSessionID     - Terminal Services Session ID or -1 if
                      undefined.  
    userSid         - User SID or "" if undefined.

Return Value:

    S_OK on success.  Otherwise, an error code is returned.

 --*/
{
    DC_BEGIN_FN("CRemoteDesktopSession::Initialize");

    HRESULT hr;
    DWORD ret;
    DWORD protocolType;
    CComBSTR parmsMachineName;
    CComBSTR parmsAssistantAccount;
    CComBSTR parmsAssistantAccountPwd;
    CComBSTR parmsHelpSessionName;
    CComBSTR parmsHelpSessionPwd;
    CComBSTR parmsProtocolSpecificParms;
    CComBSTR helpSessionName;
    CComBSTR sessionDescr;
    DWORD dwVersion;

    ASSERT(IsValid());
    if (!IsValid()) {
        return E_FAIL;
    }

    TRC_NRM((TB, L"***Ref count is:  %ld", m_dwRef));

    //
    //  Keep a back pointer to the RDS host object.
    //
    m_RDSHost = hostObject;

    //
    //  Open an instance of the Remote Desktop Help Session Manager service.
    //
    ASSERT(m_HelpSessionManager == NULL);
    hr = m_HelpSessionManager.CoCreateInstance(CLSID_RemoteDesktopHelpSessionMgr);
    if (!SUCCEEDED(hr)) {
        TRC_ERR((TB, TEXT("Can't create help session manager:  %08X"), hr));
        goto CLEANUPANDEXIT;
    }

    //
    //  Set the security level to impersonate.  This is required by 
    //  the session manager.
    //
    hr = CoSetProxyBlanket(
                (IUnknown *)m_HelpSessionManager,
                RPC_C_AUTHN_DEFAULT,
                RPC_C_AUTHZ_DEFAULT,
                NULL,
                RPC_C_AUTHN_LEVEL_DEFAULT,
                RPC_C_IMP_LEVEL_IMPERSONATE,
                NULL,
                EOAC_NONE
                );
    if (!SUCCEEDED(hr)) {
        TRC_ERR((TB, TEXT("CoSetProxyBlanket:  %08X"), hr));
        goto CLEANUPANDEXIT;
    }

    //
    //  Create a new help session if we don't already have connection 
    //  parms.
    //
    if (connectParms == NULL) {
        TRC_NRM((TB, L"Creating new help session."));
        GetSessionName(helpSessionName);
        GetSessionDescription(sessionDescr);

        hr = m_HelpSessionManager->CreateHelpSessionEx(
                                            sharingClass,
                                            bEnableCallback,
                                            timeOut,
                                            tsSessionID,
                                            userSid,
                                            userHelpCreateBlob,
                                            &m_HelpSession
                                            );
        if (!SUCCEEDED(hr)) {
            TRC_ERR((TB, L"CreateHelpSession:  %08X", hr));
            goto CLEANUPANDEXIT;
        }

        hr = m_HelpSession->get_HelpSessionId(&m_HelpSessionID);
        if (!SUCCEEDED(hr)) {
            TRC_ERR((TB, L"get_HelpSessionId:  %08X", hr));
            goto CLEANUPANDEXIT;
        }
    }
    else {

        //
        //  Parse the connection parms to get the help
        //  session ID.
        //
        ret = ParseConnectParmsString(
                            connectParms,
                            &dwVersion,
                            &protocolType,
                            parmsMachineName,
                            parmsAssistantAccount,
                            parmsAssistantAccountPwd,
                            m_HelpSessionID,
                            parmsHelpSessionName,
                            parmsHelpSessionPwd,
                            parmsProtocolSpecificParms
                            );
        if (ret != ERROR_SUCCESS) {
            hr = HRESULT_FROM_WIN32(ret);
            goto CLEANUPANDEXIT;
        }

        //
        //  Open the help session interface.
        //
        hr = m_HelpSessionManager->RetrieveHelpSession(
                            m_HelpSessionID,
                            &m_HelpSession
                            );
        if (!SUCCEEDED(hr)) {
            TRC_ERR((TB, L"Failed to open existing help session %s:  %08X.", 
                    m_HelpSessionID, hr));
            goto CLEANUPANDEXIT;
        }
        else {
            TRC_NRM((TB, L"Opened existing help session %s.", m_HelpSessionID));
        }

    }

CLEANUPANDEXIT:

    DC_END_FN();
    return hr;
}

void 
CRemoteDesktopSession::Shutdown()
/*++

Routine Description:

  The Shutdown method causes the COM object to no longer be 
  prepared for connection by the client-side Remote Desktop Host 
  ActiveX Control.

Arguments:

Return Value:

    S_OK on success.  Otherwise, an error code is returned.

 --*/
{
    DC_BEGIN_FN("CRemoteDesktopSession::Shutdown");

    //
    //  Delete the help session created via the call to StartListening
    //  and delete the instance of the Remote Desktop Help Session Manager 
    //  service.
    //
    if (m_HelpSessionManager != NULL) {
        m_HelpSessionManager->DeleteHelpSession(m_HelpSessionID);
        m_HelpSessionManager = NULL;
    }

CLEANUPANDEXIT:

    DC_END_FN();
}

STDMETHODIMP 
CRemoteDesktopSession::put_SharingClass(
    REMOTE_DESKTOP_SHARING_CLASS sharingClass
    )
/*++

Routine Description:

    Set the desktop sharing level.

Arguments:

Return Value:

 --*/
{
    DC_BEGIN_FN("CRemoteDesktopSession::put_SharingClass");

    HRESULT hr = m_HelpSession->put_UserHelpSessionRemoteDesktopSharingSetting(
                                                sharingClass
                                                );
    DC_END_FN();
    return hr;
}

STDMETHODIMP 
CRemoteDesktopSession::get_SharingClass(
    REMOTE_DESKTOP_SHARING_CLASS *sharingClass
    )
/*++

Routine Description:

    Get the desktop sharing level.

Arguments:

Return Value:

 --*/
{
    DC_BEGIN_FN("CRemoteDesktopSession::get_SharingClass");

    HRESULT hr = m_HelpSession->get_UserHelpSessionRemoteDesktopSharingSetting(
                                            sharingClass
                                            );

    DC_END_FN();
    return hr;
}

STDMETHODIMP 
CRemoteDesktopSession::put_OnConnected(
    IDispatch *iDisp
    ) 
/*++

Routine Description:

    Assign the outgoing interface for 'connected' events.
    Only one interface can be assigned at a time.

Arguments:

Return Value:

 --*/
{
    DC_BEGIN_FN("CRemoteDesktopSession::put_OnConnected");

    HRESULT hr = S_OK;

    if (m_OnConnected != NULL) {
        //
        //  The client proc may have gone away, so we need 
        //  to catch exceptions on the release.
        //
        try {
            m_OnConnected->Release();
        }
        catch (...) {
        }
    }

    m_OnConnected = iDisp;
    if (m_OnConnected != NULL) {
        try {
            m_OnConnected->AddRef();
        }
        catch (...) {
            m_OnConnected = NULL;
            TRC_ERR((TB, L"Exception caught in AddRef"));
            hr = E_FAIL;
        }
    }

    DC_END_FN();
    return hr; 
}

STDMETHODIMP 
CRemoteDesktopSession::put_OnDisconnected(
    IDispatch *iDisp
    ) 
/*++

Routine Description:

    Assign the outgoing interface for 'disconnected' events.
    Only one interface can be assigned at a time.

Arguments:

Return Value:

 --*/
{
    DC_BEGIN_FN("CRemoteDesktopSession::put_OnDisconnected(");

    HRESULT hr = S_OK;
    if (m_OnDisconnected != NULL) {
        //
        //  The client proc may have gone away, so we need 
        //  to catch exceptions on the release.
        //
        try {
            m_OnDisconnected->Release();
        }
        catch (...) {
        }
    }

    m_OnDisconnected = iDisp;
    if (m_OnDisconnected != NULL) {
        try {
            m_OnDisconnected->AddRef();
        }
        catch (...) {
            m_OnDisconnected = NULL;
            TRC_ERR((TB, L"Exception caught in AddRef"));
            hr = E_FAIL;
        }
    }

    DC_END_FN();
    return hr; 
}

STDMETHODIMP 
CRemoteDesktopSession::CloseRemoteDesktopSession()
/*++

Routine Description:

    Remove RDS session from the containing host object.  Note that 
    this function does not dereference the ISAFRemoteDesktopSession 
    interface.

Arguments:

Return Value:

 --*/
{
    DC_BEGIN_FN("CRemoteDesktopSession::CloseRemoteDesktopSession");

    HRESULT hr = m_RDSHost->CloseRemoteDesktopSession(this);

    DC_END_FN();
    return hr;
}

VOID
CRemoteDesktopSession::ClientConnected()
/*++

Routine Description:

    Called when a connection to the client has been established.

Arguments:

Return Value:

 --*/
{
    DC_BEGIN_FN("CRemoteDesktopSession::Connected");

    ASSERT(IsValid());

    //
    //  We will catch and ignore exceptions here.  The interface may
    //  have been implemented in a client application that has 'gone
    //  away.'
    //
    try {
        Fire_ClientConnected(m_OnConnected);
    }
    catch (...) {
        TRC_ALT((TB, L"Exception caught."));
    }

    DC_END_FN();
}

VOID
CRemoteDesktopSession::ClientDisconnected()
/*++

Routine Description:

    Called when a connection to the client has been terminated.
cd 
Arguments:

Return Value:

 --*/
{
    DC_BEGIN_FN("CRemoteDesktopSession::Disconnected");

    ASSERT(IsValid());

    //
    //  We will catch and ignore exceptions here.  The interface may
    //  have been implemented in a client application that has 'gone
    //  away.'
    //
    try {
        Fire_ClientDisconnected(m_OnDisconnected);
    }
    catch (...) {
        TRC_ALT((TB, L"Exception caught."));
    }

    DC_END_FN();
}