/*++

Copyright (c) 1999-2000  Microsoft Corporation

Module Name:

    HelpSess.cpp 

Abstract:

    HelpSess.cpp : Implementation of CRemoteDesktopHelpSession

Author:

    HueiWang    2/17/2000

--*/
#include "stdafx.h"

#include <time.h>
#include <Sddl.h>

#include "global.h"
#include "Sessmgr.h"
#include "rdshost.h"
#include "HelpTab.h"
#include "policy.h"
#include "HelpAcc.h"

#include "HelpMgr.h"
#include "HelpSess.h"
#include <rdshost_i.c>
#include "RemoteDesktopUtils.h"
#include "RemoteDesktop.h"

#include <safsessionresolver_i.c>


/////////////////////////////////////////////////////////////////////////////

HRESULT
ConvertSidToAccountName(
    IN CComBSTR& SidString,
    IN BSTR* ppszDomain,
    IN BSTR* ppszUserAcc
    //IN LPTSTR* ppszDomain,
    //IN LPTSTR* ppszUserAcc
    )
/*++

Description:

    Convert a string SID to domain\account.

Parameters:

    ownerSidString : SID in string form to be converted.
    ppszDomain : Pointer to string pointer to receive domain name
    UserAcc : Pointer to string pointer to receive user name

Returns:

    S_OK or error code.

Note:

    Routine uses LocalAlloc() to allocate memory for ppszDomain 
    and ppszUserAcc.

--*/
{
    DWORD dwStatus = ERROR_SUCCESS;
    PSID pOwnerSid = NULL;
    //LPTSTR pszAccName = NULL;
    BSTR pszAccName = NULL;
    DWORD  cbAccName = 0;
    //LPTSTR pszDomainName = NULL;
    BSTR pszDomainName = NULL;    
    DWORD  cbDomainName = 0;
    SID_NAME_USE SidType;
    BOOL bSuccess;

    //
    // Convert string form SID to PSID
    //
    if( FALSE == ConvertStringSidToSid( (LPCTSTR)SidString, &pOwnerSid ) )
    {
        // this might also fail if system is in shutdown state.
        dwStatus = GetLastError();
        goto CLEANUPANDEXIT;
    }

    if( NULL == ppszDomain || NULL == ppszUserAcc )
    {
        dwStatus = ERROR_INVALID_PARAMETER;
        MYASSERT( FALSE );
        goto CLEANUPANDEXIT;
    }

    //
    // Lookup user account for this SID
    //
    bSuccess = LookupAccountSid(
                            NULL,
                            pOwnerSid,
                            pszAccName,
                            &cbAccName,
                            pszDomainName,
                            &cbDomainName,
                            &SidType
                        );

    if( TRUE == bSuccess || ERROR_INSUFFICIENT_BUFFER == GetLastError() )
    {
        //pszAccName = (LPWSTR) LocalAlloc( LPTR, (cbAccName + 1) * sizeof(WCHAR) );
        //pszDomainName = (LPWSTR) LocalAlloc( LPTR, (cbDomainName + 1)* sizeof(WCHAR) );

        pszAccName = ::SysAllocStringLen( NULL, (cbAccName + 1) );
        pszDomainName = ::SysAllocStringLen( NULL, (cbDomainName + 1) );

        if( NULL != pszAccName && NULL != pszDomainName )
        {
            bSuccess = LookupAccountSid(
                                    NULL,
                                    pOwnerSid,
                                    pszAccName,
                                    &cbAccName,
                                    pszDomainName,
                                    &cbDomainName,
                                    &SidType
                                );
        }
        else
        {
            SetLastError(ERROR_OUTOFMEMORY);
            bSuccess = FALSE;
        }
    }

    if( FALSE == bSuccess )
    {
        dwStatus = GetLastError();
    }
    else
    {
        *ppszDomain = pszDomainName;
        *ppszUserAcc = pszAccName;
        pszDomainName = NULL;
        pszAccName = NULL;
    }

CLEANUPANDEXIT:

    if( NULL != pOwnerSid )
    { 
        LocalFree( pOwnerSid );
    }

    if( NULL != pszAccName )
    {
        //LocalFree( pszAccName );
        ::SysFreeString( pszAccName );
    }

    if( NULL != pszDomainName )
    {
        // LocalFree( pszDomainName );
        ::SysFreeString( pszAccName );
    }

    return HRESULT_FROM_WIN32(dwStatus);
}

/////////////////////////////////////////////////////////////////////////////
//
// CRemoteDesktopHelpSession
//
//

CRemoteDesktopHelpSession::CRemoteDesktopHelpSession() :
    m_ulLogonId(UNKNOWN_LOGONID),
    m_ulHelperSessionId(UNKNOWN_LOGONID),
    m_ulHelpSessionFlag(0)
{
}


CRemoteDesktopHelpSession::~CRemoteDesktopHelpSession()
{
}

void
CRemoteDesktopHelpSession::FinalRelease()
{
    // Releas help entry
    if( NULL != m_pHelpSession )
    {
        DebugPrintf(
                _TEXT("FinalRelease %s on %s\n"),
                (IsUnsolicitedHelp()) ? L"Unsolicted Help" : L"Solicited Help",
                m_bstrHelpSessionId
            );

        // Notify disconnect will check if session is in help and bail out if necessary.
        // there is a timing issue that our SCM notification might came after caller close 
        // all reference counter to our session object, in this case, SCM notification will 
        // trigger reload from database which does not have helper session ID and so will 
        // not notify resolver causing helpee been helped message. 
    
        // We also has AddRef() manually in ResolveXXX call and Release() in 
        // NotifyDisconnect(), this will hold the object in memory until SCM
        // notification comes in.
        NotifyDisconnect();

        CRemoteDesktopHelpSessionMgr::DeleteHelpSessionFromCache( m_bstrHelpSessionId );

        m_pHelpSession->Close();
        m_pHelpSession = NULL;
    }

    ULONG count = _Module.Release();

    DebugPrintf( 
            _TEXT("Module Release by CRemoteDesktopHelpSession() %p %d...\n"),
            this,
            count
        );
}


HRESULT
CRemoteDesktopHelpSession::put_ICSPort(
    IN DWORD newVal
    )
/*++

Description:

    Associate ICS port number with this help session.

Parameters:

    newVal : ICS port number.

Returns:

--*/
{
    HRESULT hRes = S_OK;

    CCriticalSectionLocker l(m_HelpSessionLock);

    if( FALSE == IsSessionValid() )
    {
        MYASSERT(FALSE);

        hRes = E_UNEXPECTED;
        return hRes;
    }

    //
    // Need to immediately update the value...
    //
    m_pHelpSession->m_ICSPort.EnableImmediateUpdate(TRUE);
    m_pHelpSession->m_ICSPort = newVal;
    return hRes;
}


STDMETHODIMP 
CRemoteDesktopHelpSession::get_ConnectParms(
    OUT BSTR* bstrConnectParms
    )
/*++

Description:

    Retrieve connection parameter for help session.

Parameters:

    bstrConnectParms : Pointer to BSTR to receive connect parms.

Returns:


--*/
{
    HRESULT hRes = S_OK;
    LPTSTR pszAddress = NULL;
    int BufSize;
    DWORD dwBufferRequire;
    DWORD dwNumChars;
    DWORD dwRetry;
    CComBSTR bstrSessId;
    
    CCriticalSectionLocker l(m_HelpSessionLock);

    if( FALSE == IsSessionValid() )
    {
        hRes = E_HANDLE;
        goto CLEANUPANDEXIT;
    }

    //
    // Address might have change which might require bigger buffer, retry 
    //
    //
    for(dwRetry=0; dwRetry < MAX_FETCHIPADDRESSRETRY; dwRetry++)
    {
        if( NULL != pszAddress )
        {
            LocalFree( pszAddress );
        }

        //
        // Fetch all address on local machine.
        //
        dwBufferRequire = FetchAllAddresses( NULL, 0 );
        if( 0 == dwBufferRequire )
        {
            hRes = E_UNEXPECTED;
            MYASSERT(FALSE);
            goto CLEANUPANDEXIT;
        }

        pszAddress = (LPTSTR) LocalAlloc( LPTR, sizeof(TCHAR)*(dwBufferRequire+1) );
        if( NULL == pszAddress )
        {
            hRes = E_OUTOFMEMORY;
            goto CLEANUPANDEXIT;
        }

        dwNumChars = FetchAllAddresses( pszAddress, dwBufferRequire );
        MYASSERT( dwNumChars <= dwBufferRequire );
        if( dwNumChars <= dwBufferRequire )
        {
            break;
        }
    }

    if( NULL == pszAddress )
    {
        hRes = E_UNEXPECTED;
        goto CLEANUPANDEXIT;
    }

    //
    // Store the IP address
    //
    m_pHelpSession->m_IpAddress = pszAddress;

    MYASSERT( ((CComBSTR)m_pHelpSession->m_IpAddress).Length() > 0 );
    DebugPrintf(
            _TEXT("IP Address %s\n"),
            (LPTSTR)(CComBSTR)m_pHelpSession->m_IpAddress
        );

    //
    // Create connection parameters
    //
    hRes = get_HelpSessionId( &bstrSessId );
    if( FAILED(hRes) )
    {
        MYASSERT(FALSE);
        goto CLEANUPANDEXIT;
    }


    MYASSERT( g_TSSecurityBlob.Length() > 0 );

    *bstrConnectParms = CreateConnectParmsString(
                                            REMOTEDESKTOP_TSRDP_PROTOCOL,
                                            CComBSTR(pszAddress),
                                            CComBSTR(SALEM_CONNECTPARM_UNUSEFILED_SUBSTITUTE),
                                            CComBSTR(SALEM_CONNECTPARM_UNUSEFILED_SUBSTITUTE),
                                            bstrSessId,
                                            CComBSTR(SALEM_CONNECTPARM_UNUSEFILED_SUBSTITUTE),
                                            CComBSTR(SALEM_CONNECTPARM_UNUSEFILED_SUBSTITUTE),
                                            g_TSSecurityBlob
                                        );

    #if DBG
    if( NULL != *bstrConnectParms )
    {
        DebugPrintf(
            _TEXT("Connect Parms %s\n"),
            *bstrConnectParms
        );
    }
    #endif


CLEANUPANDEXIT:

    if( NULL != pszAddress )
    {
        LocalFree( pszAddress );
    }

    return hRes;
}


STDMETHODIMP
CRemoteDesktopHelpSession::get_TimeOut(
    /*[out, retval]*/ DWORD* pTimeout
    )
/*++

--*/
{
    HRESULT hRes = S_OK;
    BOOL bSuccess;

    CCriticalSectionLocker l(m_HelpSessionLock);


    if( NULL == pTimeout )
    {
        hRes = E_POINTER;
    }
    else if( FALSE == IsSessionValid() )
    {
        hRes = E_HANDLE;
    }
    else if( NULL != m_pHelpSession )
    {
        FILETIME ft;
        SYSTEMTIME sysTime;

        ft = m_pHelpSession->m_ExpirationTime;
        bSuccess = FileTimeToSystemTime(&ft, &sysTime);
        
        if( TRUE == bSuccess )
        {
            if( sysTime.wYear >= 2038 )
            {
                *pTimeout = INT_MAX;
            }
            else
            {
                struct tm gmTime;

                memset(&gmTime, 0, sizeof(gmTime));
                gmTime.tm_sec = sysTime.wSecond;
                gmTime.tm_min = sysTime.wMinute;
                gmTime.tm_hour = sysTime.wHour;
                gmTime.tm_year = sysTime.wYear - 1900;
                gmTime.tm_mon = sysTime.wMonth - 1;
                gmTime.tm_mday = sysTime.wDay;

                if((*pTimeout = mktime(&gmTime)) == (time_t)-1)
                {
                    *pTimeout = INT_MAX;
                }
            }
        }
        else
        {
            hRes = HRESULT_FROM_WIN32( GetLastError() );
        }
    }
    else
    {
        hRes = E_UNEXPECTED;
        MYASSERT( FALSE );
    }

	return hRes;
}



STDMETHODIMP
CRemoteDesktopHelpSession::put_TimeOut(
    /*[in]*/ DWORD Timeout
    )
/*++

--*/
{
    HRESULT hRes = S_OK;

    CCriticalSectionLocker l(m_HelpSessionLock);

    LONG MaxTicketExpiry;

    //
    // Get default timeout value from registry, not a critical 
    // error, if we failed, we just default to 30 days
    //
    hRes = PolicyGetMaxTicketExpiry( &MaxTicketExpiry );
    if( FAILED(hRes) || 0 == MaxTicketExpiry )
    {
        MaxTicketExpiry = DEFAULT_MAXTICKET_EXPIRY;
    }

    if( Timeout > MaxTicketExpiry )
    {
        hRes = S_FALSE;
        Timeout = MaxTicketExpiry;
    }

    time_t curTime;
    FILETIME ftTimeOut;

    // Get the current time.
    time(&curTime);

    // time out in seconds
    curTime += Timeout;

    // Convert to FILETIME
    UnixTimeToFileTime( curTime, &ftTimeOut );

    if( FALSE == IsSessionValid() )
    {
        hRes = E_HANDLE;
    }
    else if( NULL != m_pHelpSession )
    {
        if( FALSE == m_pHelpSession->IsEntryExpired() )
        {
            //
            // operator =() update registry immediately
            //
            m_pHelpSession->m_ExpirationTime = ftTimeOut;
        }
    }
    else
    {
        hRes = E_UNEXPECTED;
    }

    return hRes;
}


STDMETHODIMP 
CRemoteDesktopHelpSession::get_HelpSessionId(
    OUT BSTR *pVal
    )
/*++

Routine Description:

    Get help session ID.

Parameters:

    pVal : return Help session ID of this help session instance.

Returns:

    S_OK
    E_OUTOFMEMORY
    E_UNEXPECTED

--*/
{
    HRESULT hRes = S_OK;

    CCriticalSectionLocker l(m_HelpSessionLock);


    if( NULL == pVal )
    {
        hRes = E_POINTER;
    }
    else if( FALSE == IsSessionValid() )
    {
        hRes = E_HANDLE;
    }
    else if( NULL != m_pHelpSession )
    {

        DebugPrintf(
                _TEXT("get_HelpSessionId() on %s\n"),
                m_bstrHelpSessionId
            );

        MYASSERT( m_pHelpSession->m_SessionId->Length() > 0 );
        if( m_pHelpSession->m_SessionId->Length() > 0 )
        {
	        *pVal = m_pHelpSession->m_SessionId->Copy();

            if( NULL == *pVal )
            {
                hRes = E_OUTOFMEMORY;
            }
        }
        else
        {
            hRes = E_UNEXPECTED;
        }
    }
    else
    {
        hRes = E_UNEXPECTED;
        MYASSERT( FALSE );
    }

	return hRes;
}


STDMETHODIMP 
CRemoteDesktopHelpSession::get_UserLogonId(
    OUT long *pVal
    )
/*++

Routine Description:

    Get user's TS session ID, note, non-ts session or Win9x always
    has 0 as user logon id.

Parameters:

    pVal : Return user logon ID.

Returns:

    S_OK

--*/
{
    HRESULT hRes = S_OK;

    CCriticalSectionLocker l(m_HelpSessionLock);

    if( NULL == pVal )
    {
        hRes = E_POINTER;
    }
    else if(FALSE == IsSessionValid())
    {
        hRes = E_HANDLE;
    }
    else if( NULL != m_pHelpSession )
    {
        *pVal = m_ulLogonId;
        if( UNKNOWN_LOGONID == m_ulLogonId )
        {
            hRes = S_FALSE;
        }
    }
    else
    {
        MYASSERT( FALSE );
        hRes = E_UNEXPECTED;
    }
           
	return hRes;
}


STDMETHODIMP 
CRemoteDesktopHelpSession::get_AssistantAccountName(
    OUT BSTR *pVal
    )
/*++

Routine Description:

    Get help assistant account name associated with this
    help session.

Parameters:

    pVal : Return help assistant account name associated 
           with this help session.

Returns:

    S_OK
    E_OUTOFMEMORY

--*/
{
    HRESULT hRes = S_OK;

    // Don't need a lock here.

    if( NULL != pVal )
    {
        CComBSTR accName;

        hRes = g_HelpAccount.GetHelpAccountNameEx( accName );
        if( SUCCEEDED(hRes) )
        {
            *pVal = accName.Copy();
            if( NULL == *pVal )
            {
                hRes = E_OUTOFMEMORY;
            }
        }
    }
    else
    {
        hRes = E_POINTER;
    }

	return hRes;
}

STDMETHODIMP 
CRemoteDesktopHelpSession::get_HelpSessionName(
    OUT BSTR *pVal
    )
/*++

Routine Description:

    Get help session name associated with this
    help session.

Parameters:

    pVal : Return help session name associated 
           with this help session.

Returns:

    S_OK
    S_FALSE         No Help Session Name
    E_OUTOFMEMORY   Out of memory
    E_POINTER       Invalid parameter
    HRESULT_FROM_WIN32( ERROR_LOCK_VIOLATION );

--*/
{
    HRESULT hRes = S_OK;

    CCriticalSectionLocker l(m_HelpSessionLock);


    if(FALSE == IsSessionValid())
    {
        hRes = E_HANDLE;
    }
    else if( NULL == pVal )
    {
        hRes = E_POINTER;
    }
    else if( NULL != m_pHelpSession )
    {
        if( m_pHelpSession->m_SessionName->Length() > 0 )
        {
	        *pVal = m_pHelpSession->m_SessionName->Copy();
            if( NULL == *pVal )
            {
                hRes = E_OUTOFMEMORY;
            }
        }
        else
        {
            hRes = S_FALSE;
        }
    }
    else
    {
        hRes = E_UNEXPECTED;
        MYASSERT(FALSE);
    }

	return hRes;
}


STDMETHODIMP 
CRemoteDesktopHelpSession::put_HelpSessionName(
    IN BSTR newVal
    )
/*++

Routine Description:

    Set help session name associated with this
    help session.

Parameters:

    newVal : New help session name.

Returns:

    S_OK
    E_OUTOFMEMORY

--*/
{
    HRESULT hRes = S_OK;

    CCriticalSectionLocker l(m_HelpSessionLock);

    if(FALSE == IsSessionValid())
    {
        hRes = E_HANDLE;
    }
    else if( FALSE == IsClientSessionCreator() )
    {
        hRes = HRESULT_FROM_WIN32( ERROR_ACCESS_DENIED );
    }
    else if( m_pHelpSession != NULL )
    {
        //
        // NULL will reset help session name
        //

        //
        // operator =() update registry immediately
        //
        m_pHelpSession->m_SessionName = newVal;
        if( !((CComBSTR)m_pHelpSession->m_SessionName == newVal) )
        {
            hRes = E_OUTOFMEMORY;
        }
    }
    else
    {
        MYASSERT(FALSE);
        hRes = E_UNEXPECTED;
    }
       
	return hRes;
}

STDMETHODIMP 
CRemoteDesktopHelpSession::put_HelpSessionPassword(
    IN BSTR newVal
    )
/*++

Routine Description:

    Change help session password associated with this
    help session.

Parameters:

    newVal : New help session password.

Returns:

    S_OK
    E_OUTOFMEMORY       Out of memory

--*/
{
    HRESULT hRes = S_OK;

    CCriticalSectionLocker l(m_HelpSessionLock);

    if( FALSE == IsSessionValid() )
    {
        hRes = E_HANDLE;
    }
    else if( FALSE == IsClientSessionCreator() )
    {
        hRes = HRESULT_FROM_WIN32( ERROR_ACCESS_DENIED );
    }
    else if( NULL != m_pHelpSession )
    {

        //
        // NULL will reset password
        //

        //
        // operator =() update registry immediately
        //
	    m_pHelpSession->m_SessionPwd = newVal;

        if( !((CComBSTR)m_pHelpSession->m_SessionPwd == newVal) )
        {
            hRes = E_OUTOFMEMORY;
        }
    }
    else
    {
        hRes = E_UNEXPECTED;
        MYASSERT( FALSE );
    }

	return hRes;
}


STDMETHODIMP 
CRemoteDesktopHelpSession::get_HelpSessionDescription(
    OUT BSTR *pVal
    )
/*++

Routine Description:

    Get help session description associated with this
    help session.

Parameters:

    pVal : Return help session description associated 
           with this help session.

Returns:

    S_OK
    S_FALSE         No Description
    E_OUTOFMEMORY   Out of memory
    E_POINTER       Invalid argument
    
--*/
{
    HRESULT hRes = S_OK;

    CCriticalSectionLocker l(m_HelpSessionLock);

    if( NULL == pVal )
    {
        hRes = E_POINTER;
    }
    else if( FALSE == IsSessionValid() )
    {
        hRes = E_HANDLE;
    }
    else if( m_pHelpSession != NULL )
    {

        if( m_pHelpSession->m_SessionDesc->Length() > 0 )
        {
            *pVal = m_pHelpSession->m_SessionDesc->Copy();
            if( NULL == *pVal )
            {
                hRes = E_OUTOFMEMORY;
            }
        }
        else
        {
            hRes = S_FALSE;
        }
    }
    else
    {
        hRes = E_UNEXPECTED;
        MYASSERT(FALSE);
    }

	return hRes;
}


STDMETHODIMP 
CRemoteDesktopHelpSession::put_HelpSessionDescription(
    IN BSTR newVal
    )
/*++

Routine Description:

    Change help session description associated with this
    help session.

Parameters:

    newVal : new help session description associated 
           with this help session.

Returns:

    S_OK

Note:

    Help Session name and description is reserved for future use

--*/
{
    HRESULT hRes = S_OK;

    CCriticalSectionLocker l(m_HelpSessionLock);

    if( FALSE == IsSessionValid() )
    {
        hRes = E_HANDLE;
    }
    else if( FALSE == IsClientSessionCreator() )
    {
        hRes = HRESULT_FROM_WIN32( ERROR_ACCESS_DENIED );
    }
    else if( NULL != m_pHelpSession )
    {

        //
        // NULL will reset description
        //

        //
        // operator =() update registry immediately
        //
        m_pHelpSession->m_SessionDesc = newVal;

        if( !((CComBSTR)m_pHelpSession->m_SessionDesc == newVal) )
        {
            hRes = E_OUTOFMEMORY;
        }
    }
    else
    {
        hRes = E_UNEXPECTED;
        MYASSERT(FALSE);
    }

	return hRes;
}

STDMETHODIMP
CRemoteDesktopHelpSession::get_EnableResolver(
    OUT BOOL* pVal
    )
/*++

Routine Description:

    Return Session Resolver's CLSID for this help session.

Parameters:

    pVal : Pointer to BSTR to receive pointer to Resolver's CLSID.

Returns:

    S_OK
    E_POINTER           Invalid argument

--*/
{
    HRESULT hRes = S_OK;

    CCriticalSectionLocker l(m_HelpSessionLock);

    if( NULL == pVal )
    {
        hRes = E_POINTER;
    }
    else if(FALSE == IsSessionValid())
    {
        hRes = E_HANDLE;
    }
    else if( NULL != m_pHelpSession )
    {
        *pVal = ((long)m_pHelpSession->m_EnableResolver > 0) ? TRUE : FALSE;
    }
    else
    {
        hRes = E_UNEXPECTED;
        MYASSERT(FALSE);
    }
            
    return hRes;
}


STDMETHODIMP
CRemoteDesktopHelpSession::put_EnableResolver(
    IN BOOL newVal
    )
/*++

Routine Description:

    Set Session Resolver's CLSID, if input is NULL or empty 
    string, Help Session Manager will not invoke resolver.

Parameters:

    Val : Resolver's CLSID

Returns:

    S_OK 
    E_OUTOFMEMORY

--*/
{
    HRESULT hRes = S_OK;

    CCriticalSectionLocker l(m_HelpSessionLock);


    if( FALSE == IsSessionValid() )
    {
        hRes = E_HANDLE;
    }
    else if( FALSE == IsClientSessionCreator() )
    {
        hRes = HRESULT_FROM_WIN32( ERROR_ACCESS_DENIED );
    }
    else if( NULL != m_pHelpSession )
    {
        //
        // NULL will reset resolver's ID for this session
        //

        //
        // operator =() update registry immediately
        //
        m_pHelpSession->m_EnableResolver = newVal;
    }
    else
    {
        hRes = E_UNEXPECTED;
        MYASSERT(FALSE);
    }

    return hRes;
}


STDMETHODIMP
CRemoteDesktopHelpSession::get_ResolverBlob(
    OUT BSTR* pVal
    )
/*++

Routine Description:

    Return blob for session resolver to map help session
    to user session/

Parameters:

    pVal : Pointer to BSTR to receive blob.

Returns:

    S_OK
    S_FALSE             No blob
    E_OUTOFMEMORY
    E_POINTER

--*/
{
    HRESULT hRes = S_OK;

    CCriticalSectionLocker l(m_HelpSessionLock);

    if( NULL == pVal )
    {
        hRes = E_POINTER;
    }
    else if(FALSE == IsSessionValid())
    {
        hRes = E_HANDLE;
    }
    else if( NULL != m_pHelpSession )
    {

        if( m_pHelpSession->m_SessResolverBlob->Length() > 0 )
        {
            *pVal = m_pHelpSession->m_SessResolverBlob->Copy();
            if( NULL == *pVal )
            {
                hRes = E_OUTOFMEMORY;
            }
        }
        else
        {
            hRes = S_FALSE;
        }
    }
    else
    {
        hRes = E_UNEXPECTED;

        MYASSERT(FALSE);
    }
            
    return hRes;
}


STDMETHODIMP
CRemoteDesktopHelpSession::put_ResolverBlob(
    IN BSTR newVal
    )
/*++

Routine Description:

    Add/change blob which will be passed to session 
    resolver to map/find user session associated with this
    help session, Help Session Manager does not interpret this
    blob.

Parameters:

    newVal : Pointer to new blob.

Returns:

    S_OK
    E_OUTOFMEMORY       Out of memory

--*/
{
    HRESULT hRes = S_OK;

    CCriticalSectionLocker l(m_HelpSessionLock);

    if( FALSE == IsSessionValid() )
    {
        hRes = E_HANDLE;
    }
    else if( FALSE == IsClientSessionCreator() )
    {
        hRes = HRESULT_FROM_WIN32( ERROR_ACCESS_DENIED );
    }
    else if( NULL != m_pHelpSession )
    {
        //
        // NULL will reset resolver's ID for this session
        //

        //
        // operator =() update registry immediately
        //
        m_pHelpSession->m_SessResolverBlob = newVal;
        if( !((CComBSTR)m_pHelpSession->m_SessResolverBlob == newVal) )
        {
            hRes = E_OUTOFMEMORY;
        }
    }
    else
    {
        hRes = E_UNEXPECTED;
        MYASSERT(FALSE);
    }

    return hRes;
}


STDMETHODIMP
CRemoteDesktopHelpSession::get_HelpSessionCreateBlob(
    OUT BSTR* pVal
    )
/*++

Routine Description:

    Return blob for session resolver to map help session
    to user session/

Parameters:

    pVal : Pointer to BSTR to receive blob.

Returns:

    S_OK
    S_FALSE             No blob
    E_OUTOFMEMORY
    E_POINTER

--*/
{
    HRESULT hRes = S_OK;

    CCriticalSectionLocker l(m_HelpSessionLock);

    if( NULL == pVal )
    {
        hRes = E_POINTER;
    }
    else if(FALSE == IsSessionValid())
    {
        hRes = E_HANDLE;
    }
    else if( NULL != m_pHelpSession )
    {

        if( m_pHelpSession->m_SessionCreateBlob->Length() > 0 )
        {
            *pVal = m_pHelpSession->m_SessionCreateBlob->Copy();
            if( NULL == *pVal )
            {
                hRes = E_OUTOFMEMORY;
            }
        }
        else
        {
            hRes = S_FALSE;
        }
    }
    else
    {
        hRes = E_UNEXPECTED;

        MYASSERT(FALSE);
    }
            
    return hRes;
}


STDMETHODIMP
CRemoteDesktopHelpSession::put_HelpSessionCreateBlob(
    IN BSTR newVal
    )
/*++

Routine Description:

    Add/change blob which will be passed to session 
    resolver to map/find user session associated with this
    help session, Help Session Manager does not interpret this
    blob.

Parameters:

    newVal : Pointer to new blob.

Returns:

    S_OK
    E_OUTOFMEMORY       Out of memory

--*/
{
    HRESULT hRes = S_OK;

    CCriticalSectionLocker l(m_HelpSessionLock);


    if( FALSE == IsSessionValid() )
    {
        hRes = E_HANDLE;
    }
    else if( FALSE == IsClientSessionCreator() )
    {
        hRes = HRESULT_FROM_WIN32( ERROR_ACCESS_DENIED );
    }
    else if( NULL != m_pHelpSession )
    {
        //
        // operator =() update registry immediately
        //
        m_pHelpSession->m_SessionCreateBlob = newVal;
        if( !((CComBSTR)m_pHelpSession->m_SessionCreateBlob == newVal) )
        {
            hRes = E_OUTOFMEMORY;
        }
    }
    else
    {
        hRes = E_UNEXPECTED;
        MYASSERT(FALSE);
    }

    return hRes;
}


STDMETHODIMP 
CRemoteDesktopHelpSession::get_UserHelpSessionRemoteDesktopSharingSetting(
    /*[out, retval]*/ REMOTE_DESKTOP_SHARING_CLASS* pSetting
    )
/*++

Routine Description:

    Return help session's RDS setting.

Parameters:

    pSetting : Pointer to REMOTE_DESKTOP_SHARING_CLASS to 
               receive session's RDS setting.

Returns:

    S_OK
    E_POINTER       Invalid argument.

--*/
{
    HRESULT hRes = S_OK;

    CCriticalSectionLocker l(m_HelpSessionLock);

    if( NULL == pSetting )
    {
        hRes = E_POINTER;
    }
    else if(FALSE == IsSessionValid())
    {
        hRes = E_HANDLE;
    }
    else if( NULL != m_pHelpSession )
    {
        *pSetting = (REMOTE_DESKTOP_SHARING_CLASS)(long)m_pHelpSession->m_SessionRdsSetting;
    }
    else
    {
        hRes = E_UNEXPECTED;

        MYASSERT(FALSE);
    }

    return hRes;
}


STDMETHODIMP
CRemoteDesktopHelpSession::put_UserHelpSessionRemoteDesktopSharingSetting(
    /*[in]*/ REMOTE_DESKTOP_SHARING_CLASS Setting
    )
/*++

Routine Description:

    Set help session's RDS setting.

Parameters:

    Setting : New RDS setting.

Returns:

    S_OK
    S_FALSE                                             New setting is overrided with 
                                                        policy setting.
    HRESULT_FROM_WIN32( ERROR_ACCESS_DENIED )           User not allow to get help.
    HRESULT_FROM_WIN32( ERROR_SHARING_VIOLATION )       Other help session already has this set
    HRESULT_FROM_WIN32( ERROR_VC_DISCONNECTED )         Session is not connected

    HRESULT_FROM_WIN32( WinStationQueryInformation() );
    E_OUTOFMEMORY

Note:

    Only one help session can change the RDS setting, all other help session
    will get HRESULT_FROM_WIN32( ERROR_SHARING_VIOLATION ) error return.

    REMOTE_DESKTOP_SHARING_CLASS also define priviledge
    level, that is user with NO_DESKTOP_SHARING can't
    adjust his/her sharing class, user with CONTROLDESKTOP_PERMISSION_REQUIRE
    can't adjust his/her sharing class to CONTROLDESKTOP_PERMISSION_NOT_REQUIRE
    however, he/she can reset to NO_DESKTOP_SHARING, VIEWDESKTOP_PERMISSION_REQUIRE

--*/
{
    HRESULT hRes = S_OK;

    CCriticalSectionLocker l(m_HelpSessionLock);

    if( FALSE == IsSessionValid() )
    {
        hRes = E_HANDLE;
    }
    else if( FALSE == IsClientSessionCreator() )
    {
        hRes = HRESULT_FROM_WIN32( ERROR_ACCESS_DENIED );
    }
    else if( NULL != m_pHelpSession )
    {
        //
        // operator =() update registry immediately
        //
        m_pHelpSession->m_SessionRdsSetting = Setting;
    }
    else
    {
        hRes = E_UNEXPECTED;

        MYASSERT(FALSE);
    }

    return hRes;
}


STDMETHODIMP
CRemoteDesktopHelpSession::get_AllowToGetHelp(
    /*[out, retval]*/ BOOL* pVal
    )
/*++

Routine Description:

    Determine if user created this help session is
    allowed to get help or not, this is possible that policy change
    after user re-logon.

Parameters:

    pVal : Pointer to BOOL to receive result.

Returns:

    S_OK
    HRESULT_FROM_WIN32( ERROR_VC_DISCONNECTED )  User is not connected any more.
    E_UNEXPECTED;   Internal error.
    
--*/
{
    HRESULT hRes = S_OK;

    CCriticalSectionLocker l(m_HelpSessionLock);
   
    
    if( NULL == pVal )
    {
        hRes = E_POINTER;
    }
    else if( FALSE == IsSessionValid() )
    {
        hRes = E_HANDLE;
    }
    else if( NULL != m_pHelpSession )
    {
        if( UNKNOWN_LOGONID == m_ulLogonId )
        {
            hRes = HRESULT_FROM_WIN32( ERROR_VC_DISCONNECTED );
        }
        else if( m_pHelpSession->m_UserSID->Length() == 0 )
        {
            hRes = E_UNEXPECTED;
            MYASSERT(FALSE);
        }
        else
        {
            *pVal = IsUserAllowToGetHelp(
                                    m_ulLogonId,
                                    (LPCTSTR) CComBSTRtoLPTSTR( (CComBSTR)m_pHelpSession->m_UserSID )
                                );
        }
    }
    else
    {
        hRes = E_UNEXPECTED;
        MYASSERT(FALSE);
    }


    return hRes;
}


STDMETHODIMP 
CRemoteDesktopHelpSession::DeleteHelp()
/*++

Routine Description:

    Delete this help session

Parameters:

    None.

Returns:

    S_OK or error code from 
    Help Session Manager's DeleteHelpSession().

--*/
{
    HRESULT hRes = S_OK;

    CRemoteDesktopHelpSessionMgr::LockIDToSessionMapCache();

    CCriticalSectionLocker l(m_HelpSessionLock);

    if( FALSE == IsSessionValid() )
    {
        hRes = E_HANDLE;
    }
    else if( NULL != m_pHelpSession )
    {
        DebugPrintf(
                _TEXT("CRemoteDesktopHelpSession::DeleteHelp() %s...\n"),
                m_bstrHelpSessionId
            );

        // 
        // BUGBUG : Refer to timing bug, no notification about terminating 
        // shadow, and we can't reset session's shadow class. force a 
        // reset here for now.
        //
        ResetSessionRDSSetting();

        //
        // if we are not been help, just delete it, if we are in the middle
        // of help, deleting it from cache and database entry will cause
        // Resolver's OnDisconnect() never got call resulting in user
        // lock in resolver never got release so we update the expiration
        // date to current, expiration thread or next load will trigger
        // actual delete.
        //
        if(GetHelperSessionId() == UNKNOWN_LOGONID)
        { 
            CRemoteDesktopHelpSessionMgr::DeleteHelpSessionFromCache( (CComBSTR) m_pHelpSession->m_SessionId );
            if( (DWORD)(long)m_pHelpSession->m_ICSPort > 0 )
            {
                //
                // Destructor does not close ICS port, we only close
                // ICS port when help is deleted.
                //
                ClosePort( (DWORD)(long)m_pHelpSession->m_ICSPort );
            }

            // Delete will release the entry ref. count
            hRes = m_pHelpSession->Delete();
            m_pHelpSession = NULL;
            m_bDeleted = TRUE;
        }
        else
        {
            put_TimeOut(0);
        }
    }
    else
    {
        hRes = E_UNEXPECTED;

        MYASSERT(FALSE);
    }

    CRemoteDesktopHelpSessionMgr::UnlockIDToSessionMapCache();
	return hRes;
}

void
CRemoteDesktopHelpSession::ResolveTicketOwner()
/*++

Description:

    Convert ticket owner SID to domain\account.

Parameters:

    None.

Returns:

--*/
{
    //LPTSTR pszNoviceDomain = NULL;
    //LPTSTR pszNoviceName = NULL;

    BSTR pszNoviceDomain = NULL;
    BSTR pszNoviceName = NULL;

    HRESULT hRes = S_OK;
    MYASSERT( IsSessionValid() );

    if( IsSessionValid() )
    {
        hRes = ConvertSidToAccountName( 
                                    (CComBSTR)m_pHelpSession->m_UserSID, 
                                    &pszNoviceDomain,
                                    &pszNoviceName
                                );
    }
    else
    {
        // help session ticket is deleted
        hRes = E_HANDLE;
    }


    //
    // NO ASSERT, ConvertSidToAccountName() already assert.
    //

    if( SUCCEEDED(hRes) )
    {
        //
        // DO NOT FREE the memory, once string is attached to CComBSTR, 
        // CComBSTR will free it at destructor
        //
        m_EventLogInfo.bstrNoviceDomain.Attach(pszNoviceDomain);
        m_EventLogInfo.bstrNoviceAccount.Attach(pszNoviceName);

        //m_EventLogInfo.bstrNoviceDomain = pszNoviceDomain;
        //m_EventLogInfo.bstrNoviceAccount = pszNoviceName;

        //LocalFree(pszNoviceDomain);
        //LocalFree(pszNoviceName);
    }
    else
    {
        m_EventLogInfo.bstrNoviceDomain = g_UnknownString;
        m_EventLogInfo.bstrNoviceAccount = (CComBSTR)m_pHelpSession->m_UserSID;
    }

    return;
}

void
CRemoteDesktopHelpSession::ResolveHelperInformation(
    IN ULONG HelperSessionId,
    OUT CComBSTR& bstrExpertIpAddressFromClient, 
    OUT CComBSTR& bstrExpertIpAddressFromServer
    )
/*++

Description:

    Retrieve from TermSrv regarding HelpAssistant session's IP address send from
    expert (mstscax send this) and IP address of client machine retrive from TCPIP

Parameters:

    HelperSessionId : TS session ID of help assistant session.
    bstrExpertIpAddressFromClient : IP address send from mstscax.
    bstrExpertIpAddressFromServer : IP address that TS retrieve from tcpip stack.

Returns:

    

--*/
{
    HRESULT hRes = S_OK;
    WINSTATIONCLIENT winstationClient;
    WINSTATIONREMOTEADDRESS winstationRemoteAddress;
    ULONG winstationInfoLen;
    DWORD dwLength = 0;
    DWORD dwStatus = ERROR_SUCCESS;

    //
    // Retrieve client IP address passed from client    
    winstationInfoLen = 0;
    ZeroMemory( &winstationClient, sizeof(winstationClient) );
    if(!WinStationQueryInformation(
                              SERVERNAME_CURRENT,
                              HelperSessionId,
                              WinStationClient,
                              (PVOID)&winstationClient,
                              sizeof(winstationClient),
                              &winstationInfoLen
                          ))
    {
        dwStatus = GetLastError();
        DebugPrintf(
                _TEXT("WinStationQueryInformation() query WinStationClient return %d\n"), dwStatus
            );
     
        // Critical error?, fro now, log as 'unknown'.
        bstrExpertIpAddressFromClient = g_UnknownString;
    }
    else
    {
        bstrExpertIpAddressFromClient = winstationClient.ClientAddress;
    }

    //
    // Retrieve client IP address retrieve from server TCPIP
    winstationInfoLen = 0;
    ZeroMemory( &winstationRemoteAddress, sizeof(winstationRemoteAddress) );

    if(!WinStationQueryInformation(
                              SERVERNAME_CURRENT,
                              HelperSessionId,
                              WinStationRemoteAddress,
                              (PVOID)&winstationRemoteAddress,
                              sizeof(winstationRemoteAddress),
                              &winstationInfoLen
                          ))
    {
        dwStatus = GetLastError();

        DebugPrintf(
                _TEXT("WinStationQueryInformation() query WinStationRemoteAddress return %d %d %d\n"), 
                dwStatus,
                sizeof(winstationRemoteAddress),
                winstationInfoLen
            );

        // Critical error?, for now, log as 'unknown'.
        bstrExpertIpAddressFromServer = g_UnknownString;
    }
    else
    {
        if( AF_INET == winstationRemoteAddress.sin_family )
        {
            // refer to in_addr structure.
            struct in_addr S;
            S.S_un.S_addr = winstationRemoteAddress.ipv4.in_addr;

            bstrExpertIpAddressFromServer = inet_ntoa(S);
            if(bstrExpertIpAddressFromServer.Length() == 0 )
            {
                MYASSERT(FALSE);
                bstrExpertIpAddressFromServer = g_UnknownString;
            }
        }
        else
        {
            // we are not yet support IPV6 address, calling WSAAddressToString() will fail with error.
            bstrExpertIpAddressFromServer = g_UnknownString;
        }
    }


CLEANUPANDEXIT:

    return;
}


STDMETHODIMP
CRemoteDesktopHelpSession::ResolveUserSession(
    IN BSTR resolverBlob,
    IN BSTR expertBlob,
    LONG CallerProcessId,
    OUT ULONG_PTR* phHelpCtr,
    OUT LONG* pResolverErrCode,
    OUT long* plUserSession
    )
/*++

Routine Description:

    Resolve a user help session to user TS session.

Parameters:

    plUserSession : Pointer to long to receive user TS session.

Returns:

    S_OK
    HRESULT_FROM_WIN32( ERROR_NO_ASSOCIATION )    No resolver for this help session
    HRESULT_FROM_WIN32( ERROR_INVALID_DATA )      Can't convert 
    result from CoCreateInstance() or IRDSCallback

-*/
{
    HRESULT hRes = S_OK;
    UUID ResolverUuid;
    RPC_STATUS rpcStatus;
    ISAFRemoteDesktopCallback* pIResolver;
    long sessionId;
    long HelperSessionId;
    int resolverRetCode;
    WINSTATIONINFORMATION HelperWinstationInfo;
    DWORD dwStatus;
    ULONG winstationInfoLen;

    CComBSTR bstrExpertAddressFromClient;
    CComBSTR bstrExpertAddressFromTSServer;

    CCriticalSectionLocker l(m_HelpSessionLock);

    DWORD dwEventLogCode;

    if( FALSE == IsSessionValid() )
    {
        hRes = E_HANDLE;
        *pResolverErrCode = SAFERROR_HELPSESSIONEXPIRED;
        return hRes;
    }
    
    if( NULL == m_pHelpSession || NULL == pResolverErrCode )
    {
        hRes = E_POINTER;
        *pResolverErrCode = SAFERROR_INVALIDPARAMETERSTRING;
        MYASSERT(FALSE);
        return hRes;
    }

    if( m_pHelpSession->m_UserSID->Length() == 0 )
    {
        hRes = E_UNEXPECTED;
        *pResolverErrCode = SAFERROR_UNKNOWNSESSMGRERROR;
        goto CLEANUPANDEXIT;
    }


    //
    // must have user logon ID if we are not using resolver,
    // in pure SALEM SDK, multiple expert can connect
    // using same help ticket, only one can shadow.
    //
    *pResolverErrCode = SAFERROR_NOERROR;
    if( (long)m_pHelpSession->m_EnableResolver == 0 )
    {
        if( UNKNOWN_LOGONID != m_ulLogonId )
        {
            *plUserSession = (long) m_ulLogonId;
        }
        else
        {            
            // no resolver for this help session
            hRes = HRESULT_FROM_WIN32( ERROR_NO_ASSOCIATION );

            // user already logoff
            *pResolverErrCode = SAFERROR_USERNOTFOUND;
        }

        // 
        // We are not using resolver, bail out.
        //
        goto CLEANUPANDEXIT;
    }

      
    //
    // Retrieve Caller's TS session ID
    //
    hRes = ImpersonateClient();

    if( FAILED(hRes) )
    {
        *pResolverErrCode = SAFERROR_UNKNOWNSESSMGRERROR;
        return hRes;
    }

    HelperSessionId = GetUserTSLogonId();

    EndImpersonateClient();

    ResolveHelperInformation(
                            HelperSessionId, 
                            bstrExpertAddressFromClient, 
                            bstrExpertAddressFromTSServer 
                        );

    DebugPrintf(
            _TEXT("Expert Session ID %d, Expert Address %s %s\n"),
            HelperSessionId,
            bstrExpertAddressFromClient,
            bstrExpertAddressFromTSServer
        );
   
    DebugPrintf(
            _TEXT("Novice %s %s\n"),
            m_EventLogInfo.bstrNoviceDomain,
            m_EventLogInfo.bstrNoviceAccount
        );


    // 
    // Check if helper session is still active, under stress, we might 
    // get this call after help assistant session is gone.
    // 
    ZeroMemory( &HelperWinstationInfo, sizeof(HelperWinstationInfo) );
    winstationInfoLen = 0;
    if(!WinStationQueryInformation(
                              SERVERNAME_CURRENT,
                              HelperSessionId,
                              WinStationInformation,
                              (PVOID)&HelperWinstationInfo,
                              sizeof(HelperWinstationInfo),
                              &winstationInfoLen
                          ))
    {
        dwStatus = GetLastError();

        DebugPrintf(
                _TEXT("WinStationQueryInformation() return %d\n"), dwStatus
            );

        hRes = HRESULT_FROM_WIN32( dwStatus );
        *pResolverErrCode = SAFERROR_SESSIONNOTCONNECTED;
        goto CLEANUPANDEXIT;
    }

    if( HelperWinstationInfo.ConnectState != State_Active )
    {
        DebugPrintf(
                _TEXT("Helper session is %d"), 
                HelperWinstationInfo.ConnectState
            );

        // Helper Session is not active, can't provide help
        hRes = HRESULT_FROM_WIN32( ERROR_NO_ASSOCIATION );
        *pResolverErrCode = SAFERROR_SESSIONNOTCONNECTED;
        goto CLEANUPANDEXIT;
    }

    //
    // Either resolver is pending or already in progress,
    // we have exclusive lock so we are safe to reference 
    // m_hExpertDisconnect.
    //
    if( UNKNOWN_LOGONID != m_ulHelperSessionId )
    {
        //
        // LOGEVENT : SESSMGR_I_REMOTEASSISTANCE_USERALREADYHELP
        //
        _Module.LogSessmgrEventLog( 
                            EVENTLOG_INFORMATION_TYPE,
                            SESSMGR_I_REMOTEASSISTANCE_USERALREADYHELP,
                            m_EventLogInfo.bstrNoviceDomain,
                            m_EventLogInfo.bstrNoviceAccount,
                            (IsUnsolicitedHelp())? g_URAString : g_RAString,
                            bstrExpertAddressFromClient, 
                            bstrExpertAddressFromTSServer,
                            SAFERROR_HELPEEALREADYBEINGHELPED
                        );
                              
        *pResolverErrCode = SAFERROR_HELPEEALREADYBEINGHELPED;
        hRes = HRESULT_FROM_WIN32( ERROR_ACCESS_DENIED );
        goto CLEANUPANDEXIT;
    }

    //
    // We assume User is going to accept help 
    // 1) When expert disconnect before user accept/deny request, 
    //    our service logoff notification can find this object and invoke
    //    OnDisconnect() into resolver.
    // 2) If another expert connect with same ticket and resolver still
    //    pending response from user, we can bail out right away.
    // 
    InterlockedExchange( (LPLONG)&m_ulHelperSessionId, (LONG)HelperSessionId );

    // 
    // Cache the SID in object
    //
    m_HelpSessionOwnerSid = (CComBSTR)m_pHelpSession->m_UserSID;

    hRes = CoInitialize( NULL );
    if( FAILED(hRes) )
    {
        // failed to coinitialize,
        *pResolverErrCode = SAFERROR_INTERNALERROR;
        goto CLEANUPANDEXIT;
    }

    //
    // Load resolver
    hRes = LoadResolverFromGIT( &pIResolver );

    if( SUCCEEDED(hRes) )
    {
        CComBSTR bstrResolverBlob;

        bstrResolverBlob.Attach(resolverBlob);

        sessionId = (long)m_ulLogonId;

        DebugPrintf(
                _TEXT("User Session ID %d\n"),
                m_ulLogonId
            );

        //
        // keep a copy of blob, we need this to send to resolver on
        // disconnect, note, caller can pass its blob so we need to 
        // keep a copy of it.
        //
        if( bstrResolverBlob.Length() == 0 )
        {
            m_ResolverConnectBlob = (CComBSTR)m_pHelpSession->m_SessResolverBlob;
        }
        else
        {
            m_ResolverConnectBlob = bstrResolverBlob;
        }

        hRes = pIResolver->ResolveUserSessionID( 
                                            m_ResolverConnectBlob, 
                                            (CComBSTR)m_pHelpSession->m_UserSID,
                                            expertBlob,
                                            (CComBSTR)m_pHelpSession->m_SessionCreateBlob,
                                            &sessionId,
                                            CallerProcessId,
                                            phHelpCtr,
                                            &resolverRetCode
                                        );

        *pResolverErrCode = resolverRetCode;
        bstrResolverBlob.Detach();
        pIResolver->Release();

        DebugPrintf(
                _TEXT("Resolver returns 0x%08x\n"),
                hRes
            );

        if( SUCCEEDED(hRes) )
        {
            *plUserSession = sessionId;

            //
            // Update session ID, take the value return from Resolver.
            //
            m_ulLogonId = sessionId;

            //
            // Add this expert to logoff monitor list, when expert session's
            // rdsaddin terminates, we will inform resolver, reason for this
            // is TS might not notify us of expert session disconnect because
            // some system component popup a dialog in help assistant session
            // and termsrv has no other way but to terminate entire session.
            //
            dwStatus = MonitorExpertLogoff( 
                                        CallerProcessId, 
                                        HelperSessionId,
                                        m_bstrHelpSessionId
                                    );

            if( ERROR_SUCCESS != dwStatus )
            {
                //
                // If we can't add to resolver list, we immediate notify 
                // resolver and return error or we will run into 'helpee
                // already been help problem
                //
                
                DebugPrintf(
                        _TEXT("MonitorExpertLogoff() failed with %d\n"), dwStatus
                    );

                // directly invoke resolver here.
                hRes = pIResolver->OnDisconnect( 
                                        m_ResolverConnectBlob,
                                        m_HelpSessionOwnerSid,
                                        m_ulLogonId
                                    );

                MYASSERT( SUCCEEDED(hRes) );
                resolverRetCode = SAFERROR_UNKNOWNSESSMGRERROR;
            }
            else
            {
    
                //
                // It is possible for caller to close all reference counter to our object
                // and cause a release of our object, if SCM notification comes in after
                // our object is deleted from cache, SCM will reload from database entry
                // and that does not have helper session ID and will not invoke NotifyDisconnect().
                //
                AddRef();
            }
        }
        else
        {
            //
            // User does not accept help from this helpassistant session,
            // reset HelpAssistant session ID
            //
            InterlockedExchange( (LPLONG)&m_ulHelperSessionId, (LONG)UNKNOWN_LOGONID );
        }

        switch( resolverRetCode )
        {
            case SAFERROR_NOERROR :

                // LOGEVENT : SESSMGR_I_REMOTEASSISTANCE_BEGIN

                //
                // Cache event log info so we don't have to retrieve it again.
                //
                m_EventLogInfo.bstrExpertIpAddressFromClient = bstrExpertAddressFromClient;
                m_EventLogInfo.bstrExpertIpAddressFromServer = bstrExpertAddressFromTSServer;
                dwEventLogCode = SESSMGR_I_REMOTEASSISTANCE_BEGIN;
                break;

            case SAFERROR_HELPEECONSIDERINGHELP:
            case SAFERROR_HELPEEALREADYBEINGHELPED:

                // LOGEVENT : SESSMGR_I_REMOTEASSISTANCE_USERALREADYHELP
                dwEventLogCode = SESSMGR_I_REMOTEASSISTANCE_USERALREADYHELP;
                break;

            case SAFERROR_HELPEENOTFOUND:

                // LOGEVENT : SESSMGR_I_REMOTEASSISTANCE_INACTIVEUSER
                dwEventLogCode = SESSMGR_I_REMOTEASSISTANCE_INACTIVEUSER;
                break;

            case SAFERROR_HELPEENEVERRESPONDED:

                // LOGEVENT : SESSMGR_I_REMOTEASSISTANCE_TIMEOUT
                dwEventLogCode = SESSMGR_I_REMOTEASSISTANCE_TIMEOUT;
                break;

            case SAFERROR_HELPEESAIDNO:

                // LOGEVENT : SESSMGR_I_REMOTEASSISTANCE_USERREJECT
                dwEventLogCode = SESSMGR_I_REMOTEASSISTANCE_USERREJECT;
                break;

            default:

                // LOGEVENT : SESSMGR_I_REMOTEASSISTANCE_UNKNOWNRESOLVERERRORCODE
                dwEventLogCode = SESSMGR_I_REMOTEASSISTANCE_UNKNOWNRESOLVERERRORCODE;
                break;
        }

        _Module.LogSessmgrEventLog( 
                            EVENTLOG_INFORMATION_TYPE,
                            dwEventLogCode,
                            m_EventLogInfo.bstrNoviceDomain,
                            m_EventLogInfo.bstrNoviceAccount,
                            (IsUnsolicitedHelp())? g_URAString : g_RAString,
                            bstrExpertAddressFromClient, 
                            bstrExpertAddressFromTSServer,
                            resolverRetCode
                        );

    }
    else
    {
        *pResolverErrCode = SAFERROR_CANTOPENRESOLVER;
    } 

    CoUninitialize();

CLEANUPANDEXIT:

    DebugPrintf(
            _TEXT("ResolverUserSession returns 0x%08x\n"),
            hRes
        );

    return hRes;
}
  


HRESULT
CRemoteDesktopHelpSession::NotifyDisconnect()
/*++

Routine Description:

    Notify Session Resolver that client is dis-connecting to help session.

Parameters:

    bstrBlob : Blob to be passed to resolver, NULL if 
               use ResolverBlob property.

Returns:

E_HANDLE							        Invalid session, database entry has been deleted but refcount > 0
E_UNEXPECTED						        Internal error
HRESULT_FROM_WIN32( ERROR_VC_DISCONNECTED )	Client disconnected
HRESULT_FROM_WIN32( ERROR_NO_ASSOCIATION )	No Resolver
S_FALSE                                     No Resolver
HRESULT_FROM_WIN32( ERROR_INVALID_DATA )	Invalid Resolver ID

Error code from CoCreateInstance() and resolver's OnConnect() method.

--*/
{
    HRESULT hRes = S_OK;
    ISAFRemoteDesktopCallback* pIResolver;

    DebugPrintf(
            _TEXT("OnDisconnect() - Helper Session ID %d\n"),
            m_ulHelperSessionId
        );

    CCriticalSectionLocker l(m_HelpSessionLock);

    //
    // If we are not been help, just bail out.
    //
    if( UNKNOWN_LOGONID != m_ulHelperSessionId )
    {
        //
        // LOGEVENT : SESSMGR_I_REMOTEASSISTANCE_END
        //

        //
        // always cache help session creator at ResolveUserSession()
        // so this value can't be empty.
        //
        MYASSERT( m_HelpSessionOwnerSid.Length() > 0 );
        MYASSERT( m_ResolverConnectBlob.Length() > 0 );
        if( m_HelpSessionOwnerSid.Length() == 0 ||
            m_ResolverConnectBlob.Length() == 0 )
        {
            MYASSERT(FALSE);
            hRes = E_UNEXPECTED;
            goto CLEANUPANDEXIT;
        }

        hRes = CoInitialize( NULL );
    
        if( FAILED(hRes) )
        {
            goto CLEANUPANDEXIT;
        }

        //
        // Load resolver
        hRes = LoadResolverFromGIT( &pIResolver );

        MYASSERT( SUCCEEDED(hRes) );

        if( SUCCEEDED(hRes) )
        {
            DebugPrintf(
                        _TEXT("OnDisconnect() - Notify Resolver, %s\n%s\n%d\n"),
                        m_ResolverConnectBlob,
                        m_HelpSessionOwnerSid,
                        m_ulLogonId
                    );
                
            hRes = pIResolver->OnDisconnect( 
                                    m_ResolverConnectBlob,
                                    m_HelpSessionOwnerSid,
                                    m_ulLogonId
                                );

            pIResolver->Release();
            m_ResolverConnectBlob.Empty();
            m_HelpSessionOwnerSid.Empty();

            InterlockedExchange( (LPLONG)&m_ulHelperSessionId, (LONG)UNKNOWN_LOGONID );

            //
            // It is possible for caller to close all reference counter to our object
            // and cause a release of our object, if SCM notification comes in after
            // our object is deleted from cache, SCM will reload from database entry
            // and that does not have helper session ID and will not invoke NotifyDisconnect().
            //
            Release();

            _Module.LogSessmgrEventLog( 
                                EVENTLOG_INFORMATION_TYPE,
                                SESSMGR_I_REMOTEASSISTANCE_END,
                                m_EventLogInfo.bstrNoviceDomain,
                                m_EventLogInfo.bstrNoviceAccount,
                                (IsUnsolicitedHelp())? g_URAString : g_RAString,
                                m_EventLogInfo.bstrExpertIpAddressFromClient, 
                                m_EventLogInfo.bstrExpertIpAddressFromServer,
                                ERROR_SUCCESS
                            );
        }

        CoUninitialize();
    }
       

CLEANUPANDEXIT:

    return hRes;
}

STDMETHODIMP
CRemoteDesktopHelpSession::EnableUserSessionRdsSetting(
    IN BOOL bEnable
    )
/*++

Routine Description:

    Enable/restore user session shadow setting.



--*/
{
    HRESULT hRes = S_OK;

    CCriticalSectionLocker l(m_HelpSessionLock);

    if( FALSE == IsSessionValid() )
    {
        hRes = E_HANDLE;
    }
    else
    {
        if( TRUE == bEnable )
        {
            hRes = ActivateSessionRDSSetting();
        }
        else
        {
            hRes = ResetSessionRDSSetting();
        }
    }

    return hRes;
}


HRESULT
CRemoteDesktopHelpSession::ActivateSessionRDSSetting()
{
    HRESULT hRes = S_OK;
    DWORD dwStatus;
    REMOTE_DESKTOP_SHARING_CLASS userRDSDefault;
    BOOL bAllowToGetHelp;

    MYASSERT( TRUE == IsSessionValid() );

    //
    // check if help session user is logon
    //
    if( UNKNOWN_LOGONID == m_ulLogonId )
    {
        hRes = HRESULT_FROM_WIN32( ERROR_VC_DISCONNECTED );
        goto CLEANUPANDEXIT;
    }

    //
    // Make sure user can get help, this is possible since
    // policy might change after user re-logon to help session
    //
    hRes = get_AllowToGetHelp( &bAllowToGetHelp );

    if( FAILED(hRes) )
    {
        goto CLEANUPANDEXIT;
    }

    if( FALSE == bAllowToGetHelp )
    {
        hRes = HRESULT_FROM_WIN32( ERROR_ACCESS_DENIED );
        goto CLEANUPANDEXIT;
    }

    //
    // Retrieve current user session's shadow setting.
    //
    dwStatus = GetUserRDSLevel( m_ulLogonId, &userRDSDefault );
    if( ERROR_SUCCESS != dwStatus )
    {
        hRes = HRESULT_FROM_WIN32( dwStatus );
        goto CLEANUPANDEXIT;
    }

    if( NO_DESKTOP_SHARING != userRDSDefault )
    {
        //
        // Force reset on user session shadow setting only if 
        // user session is allowed remote control.
        //
        dwStatus = ConfigUserSessionRDSLevel( m_ulLogonId, m_pHelpSession->m_SessionRdsSetting );
        hRes = HRESULT_FROM_WIN32( dwStatus );

        DebugPrintf(
                _TEXT("ConfigUserSessionRDSLevel to %d returns 0x%08x\n"),
                (DWORD)m_pHelpSession->m_SessionRdsSetting,
                hRes
            );
    }
    else
    {
        // return S_FALSE and let shadow fail
        hRes = S_FALSE;

        DebugPrintf( _TEXT("TS User session does not remote control\n") );
    }



CLEANUPANDEXIT:

    return hRes;
}

HRESULT
CRemoteDesktopHelpSession::ResetSessionRDSSetting()
{
    HRESULT hRes = S_OK;

    MYASSERT( TRUE == IsSessionValid() );

    //
    // check if user is log on
    //
    if( UNKNOWN_LOGONID == m_ulLogonId )
    {
        hRes = HRESULT_FROM_WIN32( ERROR_VC_DISCONNECTED );
    }

    //
    // We don't do anything since TermSrv will reset shadow
    // config back to original value if shadower is help 
    // assistant.
    //

CLEANUPANDEXIT:

    return hRes;
}


///////////////////////////////////////////////////////////////
//
// Private Function
// 

HRESULT
CRemoteDesktopHelpSession::put_UserLogonId(
    IN long newVal
    )
/*++

Routine Description:

    Set user TS session for current Help Session

Parameters:

    newVal : New TS user session

Returns:

    S_OK

--*/
{
    HRESULT hRes = S_OK;

    CCriticalSectionLocker l(m_HelpSessionLock);
    

    if( FALSE == IsSessionValid() )
    {
        hRes = E_HANDLE;
    }
    else if( NULL != m_pHelpSession )
    {
        //MYASSERT( UNKNOWN_LOGONID == m_ulLogonId );

        //
        // User TS session ID is not persisted to registry
        //
        m_ulLogonId = newVal;
    }
    else
    {
        hRes = E_UNEXPECTED;
    }

    // private routine, assert if failed
    MYASSERT( SUCCEEDED(hRes) );

	return hRes;
}

BOOL
CRemoteDesktopHelpSession::IsEqualSid(
    IN const CComBSTR& bstrSid
    )
/*++

Routine Description:

    Compare user's SID.

Parameters:

    bstrSid : SID to be compared.

Returns:

    TRUE/FALSE

--*/
{
    CCriticalSectionLocker l(m_HelpSessionLock);

    if( NULL == m_pHelpSession )
    {
        MYASSERT(FALSE);
        return FALSE;
    }

    return (TRUE == IsSessionValid()) ? ((CComBSTR)m_pHelpSession->m_UserSID == bstrSid) : FALSE;
}


BOOL
CRemoteDesktopHelpSession::VerifyUserSession(
    IN const CComBSTR& bstrUserSid,
    IN const CComBSTR& bstrSessPwd
    )
/*++

Routine Description:

    Verify user help session password.

Parameters:

    bstrUserSid : calling client's user SID.
    bstrSessName : Help session name, not use currently.
    bstrSessPwd : Help Session Password to be verified.

Returns:

    TRUE/FALSE

--*/
{
    DWORD dwStatus;
    BOOL bReturn = FALSE;

    CCriticalSectionLocker l(m_HelpSessionLock);

    if( NULL == m_pHelpSession )
    {
        MYASSERT(FALSE);
        goto CLEANUPANDEXIT;
    }


    #if DISABLESECURITYCHECKS 
    if( (CComBSTR)m_pHelpSession->m_SessionName == HELPSESSION_UNSOLICATED )
    {
        // use console session
        m_ulLogonId = 0;
    }
    #endif

    if( FALSE == IsSessionValid() )
    {
        // Help Session is invalid
        goto CLEANUPANDEXIT;
    }

    bReturn = TRUE;

CLEANUPANDEXIT:

    return bReturn;
}


HRESULT
CRemoteDesktopHelpSession::InitInstance(
    IN CRemoteDesktopHelpSessionMgr* pMgr,
    IN CComBSTR& bstrClientSid,
    IN PHELPENTRY pHelpEntry
    )
/*++

Routine Description:

    Initialize a CRemoteDesktopHelpSession object.

Parameters:


Returns:

    S_OK

--*/
{
    HRESULT hRes = S_OK;

    if( NULL != pHelpEntry )
    {
        m_pSessMgr = pMgr;
        m_pHelpSession = pHelpEntry;
        m_bstrClientSid = bstrClientSid;
        m_bstrHelpSessionId = pHelpEntry->m_SessionId;
    }
    else
    {
        hRes = HRESULT_FROM_WIN32( ERROR_INTERNAL_ERROR );
        MYASSERT( SUCCEEDED(hRes) );
    }

    return hRes;
}


HRESULT
CRemoteDesktopHelpSession::CreateInstance(
    IN CRemoteDesktopHelpSessionMgr* pMgr,
    IN CComBSTR& bstrClientSid,
    IN PHELPENTRY pHelpEntry,
    OUT RemoteDesktopHelpSessionObj** ppRemoteDesktopHelpSession
    )
/*++

Routine Description:

    Create an instance of help session.

Parameters:

    pMgr : Pointer to help session manager object.
    ppRemoteDesktopHelpSession : Return a pointer to help session instance.

Returns:

    S_OK
    E_OUTOFMEMORY
    Error code in impersonating client

--*/
{
    HRESULT hRes = S_OK;
    RemoteDesktopHelpSessionObj* p = NULL;

    hRes = RemoteDesktopHelpSessionObj::CreateInstance( &p );
    if( SUCCEEDED(hRes) )
    {
        hRes = p->InitInstance( 
                            pMgr, 
                            bstrClientSid,
                            pHelpEntry
                        );

        if( SUCCEEDED(hRes) )
        {
            p->AddRef();
            *ppRemoteDesktopHelpSession = p;
        }
        else
        {
            p->Release();
        }
    }

    return hRes;
}

HRESULT
CRemoteDesktopHelpSession::BeginUpdate()
{
    HRESULT hRes;

    MYASSERT( NULL != m_pHelpSession );

    if( NULL != m_pHelpSession )
    {
        hRes = m_pHelpSession->BeginUpdate();
    }
    else
    {
        hRes = E_UNEXPECTED;
    }

    return hRes;
}

HRESULT
CRemoteDesktopHelpSession::CommitUpdate()
{
    HRESULT hRes;

    //
    // Update all entries.
    //
    MYASSERT( NULL != m_pHelpSession );

    if( NULL != m_pHelpSession )
    {
        hRes = m_pHelpSession->CommitUpdate();
    }
    else
    {
        hRes = E_UNEXPECTED;
    }

    return hRes;
}

HRESULT
CRemoteDesktopHelpSession::AbortUpdate()
{
    HRESULT hRes;

    //
    // Update all entries.
    //
    MYASSERT( NULL != m_pHelpSession );
    if( NULL != m_pHelpSession )
    {
        hRes = m_pHelpSession->AbortUpdate();
    }
    else
    {
        hRes = E_UNEXPECTED;
    }

    return hRes;
}

BOOL
CRemoteDesktopHelpSession::IsHelpSessionExpired()
{
    MYASSERT( NULL != m_pHelpSession );

    return (NULL != m_pHelpSession) ? m_pHelpSession->IsEntryExpired() : TRUE;
}


BOOL
CRemoteDesktopHelpSession::IsClientSessionCreator()
{
    BOOL bStatus;

    //
    //  NOTE:  This function checks to make sure the caller is the user that
    //         created the Help Session.  For Whistler, we enforce that Help
    //         Sessions only be created by apps running as SYSTEM.  Once
    //         created, the creating app can pass the object to any other app
    //         running in any other context.  This function will get in the
    //         way of this capability so it simply returns TRUE for now.
    //   
    return TRUE;      

    if( m_pHelpSession )
    {
        bStatus = (/* (CComBSTR) */m_pHelpSession->m_UserSID == m_bstrClientSid);
        if( FALSE == bStatus )
        {
            bStatus = (m_pHelpSession->m_UserSID == g_LocalSystemSID);
        }
    }
    else
    {
        bStatus = FALSE;
    }

    return bStatus;
}