#include "stdafx.h" #include "rcbdyctl.h" #include "IMSession.h" #include "wincrypt.h" #include "auth.h" #include "assert.h" #include "wininet.h" #include "msgrua.h" #include "msgrua_i.c" #include "utils.h" #include "lock_i.c" #include "sessions.h" #include "sessions_i.c" #include "helpservicetypelib.h" #include "helpservicetypelib_i.c" #include "safrcfiledlg.h" #include "safrcfiledlg_i.c" ///////////////////////////////////////////////////////////////////////// // CIMSession // Global help functions declaration. HWND InitInstance(HINSTANCE hInstance, int nCmdShow); LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); DWORD WINAPI HSCInviteThread(LPVOID lpParam); HRESULT UnlockSession(CIMSession* pThis); VOID CALLBACK ConnectToExpertCallback(HWND hwnd,UINT uMsg,UINT_PTR idEvent,DWORD dwTime); UINT_PTR g_timerID; CIMSession * g_pThis; HWND g_StatusDlg = NULL; // Window class name TCHAR szWindowClass[] = TEXT("Microsoft Remote Assistance Messenger UNLOCK window"); extern HINSTANCE g_hInstance; #define VIESDESKTOP_PERMISSION_NOT_REQUIRE 0x4 #define SESSION_EXPIRY 305 #define RA_TIMER_UNLOCK_ID 0x1 #define RA_TIMEOUT_UNLOCK 180 * 1000 // 3 minutes. #define RA_TIMEOUT_USER 1800 * 1000 // 30 minutes HANDLE g_hLockEvent = NULL; BOOL g_bActionCancel = FALSE; HWND g_hWnd = NULL; LPSTREAM g_spInvitee = NULL; LPSTREAM g_spStatus = NULL; CIMSession::CIMSession() { m_pSessObj = NULL; m_pSessMgr = NULL; m_pMsgrLockKey = NULL; m_bIsInviter = TRUE; m_hCryptProv = NULL; m_hPublicKey = NULL; m_pSessionEvent = NULL; m_iState = 0; m_pfnSessionStatus = NULL; m_pSessionMgrEvent = NULL; m_bIsHSC = FALSE; m_pInvitee = NULL; m_bLocked = TRUE; m_bExchangeUser = FALSE; } CIMSession::~CIMSession() { if (m_pSessObj) { if (m_pSessionEvent) m_pSessionEvent->DispEventUnadvise(m_pSessObj); m_pSessObj->Release(); } if (m_pSessionMgrEvent) m_pSessionMgrEvent->Release(); if (m_pSessionEvent) m_pSessionEvent->Release(); if (m_pMsgrLockKey) m_pMsgrLockKey->Release(); if (m_pSessMgr) m_pSessMgr->Release(); if (m_hPublicKey) CryptDestroyKey(m_hPublicKey); if (m_hCryptProv) CryptReleaseContext(m_hCryptProv, 0); } STDMETHODIMP CIMSession::put_OnSessionStatus(IDispatch* pfn) { m_pfnSessionStatus = pfn; return S_OK; } STDMETHODIMP CIMSession::get_ReceivedUserTicket(BSTR* pSalemTicket) { if (pSalemTicket == NULL) return E_INVALIDARG; *pSalemTicket = m_bstrSalemTicket.Copy(); return S_OK; } STDMETHODIMP CIMSession::Hook(IMsgrSession*, HWND hWnd) { HRESULT hr = S_OK; m_hWnd = hWnd; return hr; } STDMETHODIMP CIMSession::ContextDataProperty(BSTR pName, BSTR* ppValue) { HRESULT hr = S_OK; CComPtr cpSetting; if (*ppValue != NULL) { SysFreeString(*ppValue); *ppValue = NULL; } if (m_bstrContextData.Length() == 0) goto done; if (pName == NULL || *pName == L'\0') { *ppValue = m_bstrContextData.Copy(); goto done; } hr = cpSetting.CoCreateInstance( CLSID_RASetting, NULL, CLSCTX_INPROC_SERVER); if (FAILED_HR(_T("ISetting->CoCreateInstance failed: %s"), hr)) goto done; cpSetting->get_GetPropertyInBlob(m_bstrContextData, pName, ppValue); done: return hr; } STDMETHODIMP CIMSession::get_User(IDispatch** ppUser) { HRESULT hr = S_OK; if (ppUser == NULL) { hr = E_INVALIDARG; goto done; } if (m_pSessObj) { hr = m_pSessObj->get_User(ppUser); if (FAILED_HR(_T("get_User failed %s"), hr)) goto done; } else { DEBUG_MSG(_T("No Session found")); hr = S_OK; *ppUser = NULL; } done: return hr; } STDMETHODIMP CIMSession::get_IsInviter(BOOL* pVal) { if (pVal == NULL) return E_INVALIDARG; *pVal = m_bIsInviter; return S_OK; } STDMETHODIMP CIMSession::CloseRA() { HRESULT hr = S_OK; if(m_bIsInviter && m_hWnd) // for inviter, this is the last function to call. SendMessage(m_hWnd, WM_CLOSE, NULL, NULL); return hr; } HRESULT CIMSession::GenEncryptdNoviceBlob(BSTR pPublicKeyBlob, BSTR pSalemTicket, BSTR* pBlob) { TraceSpew(_T("Funct: GenEncryptedNoviceBlob")); HRESULT hr; if (!pPublicKeyBlob) return FALSE; DWORD dwLen, dwBlobLen, dwSessionKeyCount, dwSalemCount; LPBYTE pBuf =NULL; HCRYPTKEY hSessKey =NULL; HCRYPTKEY hPublicKey =NULL; BSTR pSessionKeyBlob =NULL; BSTR pSalemBlob =NULL; TCHAR szHeader[20]; CComBSTR bstrBlob; if (FAILED(hr = InitCSP(FALSE))) goto done; // Import public key if (FAILED(hr = StringToBinary(pPublicKeyBlob, wcslen(pPublicKeyBlob), &pBuf, &dwLen))) goto done; if (!CryptImportKey(m_hCryptProv, pBuf, (UINT)dwLen, 0, 0, &hPublicKey)) { DEBUG_MSG(_T("Can't import public key")); hr = HRESULT_FROM_WIN32(GetLastError()); goto done; } // Gen session key. if (!CryptGenKey(m_hCryptProv, CALG_RC2, CRYPT_EXPORTABLE, &hSessKey)) { DEBUG_MSG(_T("Create Session key failed")); hr = HRESULT_FROM_WIN32(GetLastError()); goto done; } DWORD dwBitLength = 40; if (!CryptSetKeyParam(hSessKey, KP_EFFECTIVE_KEYLEN, (PBYTE) &dwBitLength, 0)) { DEBUG_MSG(_T("Set KEYLEN failed")); hr = HRESULT_FROM_WIN32(GetLastError()); goto done; } if (FAILED(hr=GetKeyExportString(hSessKey, hPublicKey, SIMPLEBLOB, &pSessionKeyBlob, &dwSessionKeyCount))) goto done; // Encrypt SalemTicket dwBlobLen = dwLen = wcslen(pSalemTicket) * sizeof(OLECHAR); if (!CryptEncrypt(hSessKey, NULL, TRUE, 0, NULL, &dwBlobLen, dwLen)) { DEBUG_MSG(_T("Can't calculate salem ticket buffer length.")); hr = HRESULT_FROM_WIN32(GetLastError()); goto done; } if (pBuf) free(pBuf); if((pBuf = (LPBYTE)malloc(dwBlobLen)) == NULL) { hr = E_OUTOFMEMORY; goto done; } ZeroMemory(pBuf, dwBlobLen); memcpy(pBuf, (LPBYTE)pSalemTicket, dwLen); if (!CryptEncrypt(hSessKey, NULL, TRUE, 0, pBuf, &dwLen, dwBlobLen)) { DEBUG_MSG(_T("Can't calculate user ticket length")); hr = HRESULT_FROM_WIN32(GetLastError()); goto done; } // Need to generate salem ticket blob if (FAILED(hr=BinaryToString(pBuf, dwBlobLen, &pSalemBlob, &dwSalemCount))) goto done; // Generate final Blob wsprintf(szHeader, _T("%d;S="), dwSessionKeyCount + 2); bstrBlob = szHeader; bstrBlob.AppendBSTR(pSessionKeyBlob); wsprintf(szHeader, _T("%d;U="), dwSalemCount + 2); bstrBlob.Append(szHeader); bstrBlob.AppendBSTR(pSalemBlob); if (!InternetGetConnectedState(&dwLen, 0)) { DEBUG_MSG(_T("No Internet connection")); } else { if (dwLen & INTERNET_CONNECTION_MODEM) // connected through Modem { bstrBlob.Append("3;L=1"); } } *pBlob = bstrBlob.Detach(); done: if (pBuf) free(pBuf); if (pSessionKeyBlob) SysFreeString(pSessionKeyBlob); if (pSalemBlob) SysFreeString(pSalemBlob); if (hPublicKey) CryptDestroyKey(hPublicKey); if (hSessKey) CryptDestroyKey(hSessKey); return hr; } HRESULT CIMSession::InviterSendSalemTicket(BSTR pContext) { TraceSpew(_T("Funct: InviterSendSalemTicket")); HRESULT hr; CComPtr cpSetting; ISAFRemoteDesktopSession *pRCS = NULL; BSTR pPublicKeyBlob = NULL; CComBSTR bstrExpertTicket; CComBSTR bstrSalemTicket; CComBSTR bstrBlob, bstrExpertName, bstrUserBlob; CComPtr fact; CComQIPtr disp; CComPtr cpDisp; CComPtr cpExpert; TCHAR szHeader[100]; hr = cpSetting.CoCreateInstance( CLSID_RASetting, NULL, CLSCTX_INPROC_SERVER); if (FAILED_HR(_T("ISetting->CoCreateInstance failed: %s"), hr)) goto done; // bstrExpertBlob has 2 part: Expert ticket and expert public key. Names: "ET" and "PK" cpSetting->get_GetPropertyInBlob(pContext, CComBSTR("ET"), &m_bstrExpertTicket); if (m_bstrExpertTicket.Length() == 0) { DEBUG_MSG(_T("No expert ticket")); goto done; } cpSetting->get_GetPropertyInBlob(pContext, CComBSTR("PK"), &pPublicKeyBlob); // Generate SALEM Ticket. hr =::CoGetClassObject(CLSID_PCHService, CLSCTX_ALL, NULL, IID_IClassFactory, (void**)&fact ); if (FAILED_HR(_T("CoGetClassObject failed: %s"), hr)) goto done; // Get Expert name and put it into userblob hr = m_pSessObj->get_User(&cpDisp); if (FAILED_HR(_T("get_User failed %s"), hr)) goto done; hr = cpDisp->QueryInterface(IID_IMessengerContact, (LPVOID*)&cpExpert); if (FAILED_HR(_T("QI IMsgrUser failed: %s"), hr)) goto done; hr = cpExpert->get_FriendlyName(&bstrExpertName); if (FAILED_HR(_T("get_FriendlyName failed %s"), hr)) goto done; wsprintf(szHeader, _T("%d;EXP_NAME="), bstrExpertName.Length() + 9); bstrUserBlob = szHeader; bstrUserBlob.AppendBSTR(bstrExpertName); bstrUserBlob.Append("4;IM=1"); disp = fact; //... it would run QI automatically. hr = disp->CreateObject_RemoteDesktopSession( (REMOTE_DESKTOP_SHARING_CLASS)VIESDESKTOP_PERMISSION_NOT_REQUIRE, SESSION_EXPIRY, // expired in 5 minutes. CComBSTR(""), bstrUserBlob, &pRCS ); if (FAILED_HR(_T("CreateRemoteDesktopSession failed %s"), hr)) goto done; hr = pRCS->get_ConnectParms(&bstrSalemTicket); if (FAILED_HR(_T("GetConnectParms failed: %s"), hr)) goto done; // Encrypt ticket with the key and send it back. if (pPublicKeyBlob) { if (FAILED(hr = GenEncryptdNoviceBlob(pPublicKeyBlob, bstrSalemTicket, &bstrBlob))) goto done; } else { TCHAR sbuf[20]; wsprintf(sbuf, _T("%d;U="), wcslen(bstrSalemTicket) + 2); bstrBlob = sbuf; bstrBlob += bstrSalemTicket; } hr = m_pSessObj->SendContextData((BSTR)bstrBlob); if (FAILED_HR(TEXT("Send Context data filed: %s"), hr)) goto done; done: if (pRCS) pRCS->Release(); if (pPublicKeyBlob) SysFreeString(pPublicKeyBlob); return hr; } #define IM_STATE_GET_TICKET 1 #define IM_STATE_COMPLETE 2 STDMETHODIMP CIMSession::ProcessContext(BSTR pContext) { TraceSpewW(L"Funct: ProcessContext %s", (pContext==NULL?L"NULL":pContext)); HRESULT hr = S_OK; hr = ProcessNotify(pContext); // Is it a notification? if (SUCCEEDED(hr)) { goto done; } m_iState++; m_bstrContextData = pContext; if (m_bIsInviter) { switch(m_iState) { case IM_STATE_GET_TICKET: // Received Expert ticket if (!m_bIsHSC) { hr = InviterSendSalemTicket(pContext); if (FAILED(hr)) { // Need to notify expert side. Notify(RA_IM_FAILED); // Also let the local session know the status. DoSessionStatus(RA_IM_FAILED); CloseRA(); // close inviter side rcimlby.exe. } else { if (m_bExchangeUser) { // Set a timer to callback and then we call ConnectToExpert. g_pThis = this; g_timerID = SetTimer (NULL, NULL, 1000, (TIMERPROC)ConnectToExpertCallback); if (!g_timerID) { // SetTimer failed! This means that we have to bail out of the IM request // since the call to ConnectToExpert will never happen. // Need to notify expert side. Notify(RA_IM_FAILED); // Also let the local session know the status. DoSessionStatus(RA_IM_FAILED); CloseRA(); // close inviter side rcimlby.exe. } } else { CComPtr fact; CComQIPtr disp; LONG lError; TraceSpew(_T("Connect to Expert")); hr =::CoGetClassObject(CLSID_PCHService, CLSCTX_ALL, NULL, IID_IClassFactory, (void**)&fact ); if (!FAILED_HR(_T("CoGetClass CLSID_PCHService failed: %s"), hr)) { disp = fact; //... it would run QI automatically. hr = disp->ConnectToExpert(m_bstrExpertTicket, 10, &lError); if (!FAILED_HR(_T("ConnectToExpert failed: %s"), hr)) DoSessionStatus(RA_IM_CONNECTTOEXPERT); } CloseRA(); // close inviter side rcimlby.exe } } } else // Inviter HelpCtr status update. { DoSessionStatus(RA_IM_WAITFORCONNECT); } break; #if 0 // Connection complete: currently not used. case IM_STATE_COMPLETE: // If host is rcimlby.exe, close it. if (m_hWnd) { DestroyWindow(m_hWnd); } else { DoSessionStatus(RA_IM_COMPLETE); } break; #endif default: // Noise? break; } } else // Expert side. { switch(m_iState) { case IM_STATE_GET_TICKET: // Get Novice salem ticket // Extract this ticket to member variable and signal the call back to let host start to connect. hr = ExtractSalemTicket(pContext); if (FAILED(hr)) { // need to notify Novice that connection failed. Notify(RA_IM_FAILED); DoSessionStatus(RA_IM_FAILED); } else { DoSessionStatus(RA_IM_CONNECTTOSERVER); } break; default: // Noise? break; } } done: return hr; } VOID CALLBACK ConnectToExpertCallback( HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime ) { // Kill the Timer KillTimer(NULL, g_timerID); HRESULT hr = S_OK; CComPtr fact; CComQIPtr disp; LONG lError; TraceSpew(_T("Connect to Expert")); hr =::CoGetClassObject(CLSID_PCHService, CLSCTX_ALL, NULL, IID_IClassFactory, (void**)&fact ); if (!FAILED_HR(_T("CoGetClass CLSID_PCHService failed: %s"), hr)) { disp = fact; //... it would run QI automatically. hr = disp->ConnectToExpert(g_pThis->m_bstrExpertTicket, 10, &lError); if (!FAILED_HR(_T("ConnectToExpert failed: %s"), hr)) g_pThis->DoSessionStatus(RA_IM_CONNECTTOEXPERT); } g_pThis->CloseRA(); // close inviter side rcimlby.exe. } /////////////////////////////////////////////////////////////////////////////////////////// // We can't notify the other party too much time. The context data can be set only 5 times. HRESULT CIMSession::ProcessNotify(BSTR pContext) { TraceSpewW(L"Funct: ProcessNotify %s", (pContext?pContext:L"NULL")); HRESULT hr = S_OK; CComPtr cpSetting; CComBSTR bstrData; int lStatus; hr = cpSetting.CoCreateInstance( CLSID_RASetting, NULL, CLSCTX_INPROC_SERVER); if (FAILED_HR(_T("ISetting->CoCreateInstance failed: %s"), hr)) goto done; cpSetting->get_GetPropertyInBlob(pContext, CComBSTR("NOTIFY"), &bstrData); if (bstrData.Length() == 0) { hr = E_FAIL; // Not a notification. goto done; } lStatus = _wtoi((BSTR)bstrData); switch (lStatus) { case RA_IM_COMPLETE: DoSessionStatus(RA_IM_COMPLETE); break; case RA_IM_TERMINATED: DoSessionStatus(RA_IM_TERMINATED); break; case RA_IM_FAILED: DoSessionStatus(RA_IM_FAILED); break; default: // ignore the others. break; } done: return hr; } HRESULT CIMSession::DoSessionStatus(int iState) { // Used for trace purpose. static TCHAR *szMsg[] = { _T("Unknown session status"), _T("RA_IM_COMPLETE"), // 0x1 _T("RA_IM_WAITFORCONNECT"), // 0x2 _T("RA_IM_CONNECTTOSERVER"), // 0x3 _T("RA_IM_APPSHUTDOWN"), // 0x4 _T("RA_IM_SENDINVITE"), // 0x5 _T("RA_IM_ACCEPTED"), // 0x6 _T("RA_IM_DECLINED"), // 0x7 _T("RA_IM_NOAPP"), // 0x8 _T("RA_IM_TERMINATED"), // 0x9 _T("RA_IM_CANCELLED"), // 0xA _T("RA_IM_UNLOCK_WAIT"), // 0xB _T("RA_IM_UNLOCK_FAILED"), // 0xC _T("RA_IM_UNLOCK_SUCCEED"), // 0xD _T("RA_IM_UNLOCK_TIMEOUT"), // 0xE _T("RA_IM_CONNECTTOEXPERT"), // 0xF _T("RA_IM_EXPERT_TICKET_OUT")// 0x10 }; TCHAR *pMsg = NULL; if (iState > 0 && iState < (sizeof(szMsg) / sizeof(TCHAR*))) pMsg = szMsg[iState]; else pMsg = szMsg[0]; TraceSpew(_T("DoSessionStatus: %s"), pMsg); if (m_pfnSessionStatus) { DISPPARAMS disp; VARIANTARG varg[1]; disp.rgvarg = varg; disp.rgdispidNamedArgs = NULL; disp.cArgs = 1; disp.cNamedArgs = 0; varg[0].vt = VT_I4; varg[0].lVal = iState; if (m_pfnSessionStatus) m_pfnSessionStatus->Invoke(0x0, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &disp, NULL, NULL, NULL); } else //Jose: If m_pfnSessionStatus is not set send message to the status dialog if it is created { if (g_StatusDlg) ::SendMessage(g_StatusDlg,MSG_STATUS,(WPARAM)iState,NULL); } if ((iState == RA_IM_TERMINATED || iState == RA_IM_FAILED) && m_bIsInviter && !m_bIsHSC) { // need to close inviter RA lobby CloseRA(); } return S_OK; } HRESULT CIMSession::InitSessionEvent(IMsgrSession* pSessObj) { HRESULT hr = S_OK; if (!m_pSessionEvent) { hr = CComObject::CreateInstance(&m_pSessionEvent); if (FAILED_HR(_T("CreateInstance SessionEvent failed: %s"), hr)) goto done; m_pSessionEvent->AddRef(); } m_pSessionEvent->Init(this, pSessObj); done: return hr; } HRESULT CIMSession::InitCSP(BOOL bGenPublicKey /* = TRUE */) { TraceSpew(_T("Funct: InitCSP")); HRESULT hr = S_OK; TCHAR szUser[] = _T("RemoteAssistanceIMIntegration"); if (!m_hCryptProv) { // 1. If it doesn't exist then create a new one. if (!CryptAcquireContext(&m_hCryptProv, NULL, MS_DEF_PROV, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { DEBUG_MSG(_T("Create CSP failed")); hr = HRESULT_FROM_WIN32(GetLastError()); goto done; } } // Get public key if(bGenPublicKey && !m_hPublicKey && !CryptGetUserKey(m_hCryptProv, AT_KEYEXCHANGE, &m_hPublicKey)) { // Check to see if one needs to be created. if(GetLastError() == NTE_NO_KEY) { // Create an key exchange key pair. if(!CryptGenKey(m_hCryptProv,AT_KEYEXCHANGE,0,&m_hPublicKey)) { DEBUG_MSG(_T("Error occurred attempting to create an exchange key.")); hr = HRESULT_FROM_WIN32(GetLastError()); goto done; } } else { DEBUG_MSG(_T("Error occurred when access Public key")); hr = HRESULT_FROM_WIN32(GetLastError()); goto done; } } done: return hr; } HRESULT CIMSession::ExtractSalemTicket(BSTR pContext) { TraceSpewW(L"Funct: ExtraceSalemTicket %s", pContext?pContext:L"NULL"); HRESULT hr = S_OK; // This ContextData could contains S (sessionkey) and U (user=ticket) name pairs. CComBSTR bstrU, bstrS; CComPtr cpSetting; DWORD dwLen; HCRYPTKEY hSessKey = NULL; LPBYTE pBuf = NULL; BSTR pBlob = NULL; hr = cpSetting.CoCreateInstance(CLSID_Setting, NULL, CLSCTX_INPROC_SERVER); if (FAILED_HR(_T("ISetting->CoCreateInstance failed: %s"), hr)) goto done; cpSetting->get_GetPropertyInBlob(pContext, CComBSTR("U"), &bstrU); cpSetting->get_GetPropertyInBlob(pContext, CComBSTR("S"), &bstrS); dwLen = bstrS.Length(); if (dwLen > 0) { // need to decrypt user ticket TraceSpewW(L"Decrypt user ticket using Expert's public key..."); if (!m_hCryptProv || !m_hPublicKey) { DEBUG_MSG(_T("Can't find Cryptographic handler")); hr = FALSE; goto done; } if (FAILED(hr = StringToBinary((BSTR)bstrS, dwLen, &pBuf, &dwLen))) goto done; if (!CryptImportKey(m_hCryptProv, pBuf, dwLen, m_hPublicKey, 0, &hSessKey)) { DEBUG_MSG(_T("Can't import Session Key")); hr = HRESULT_FROM_WIN32(GetLastError()); goto done; } free(pBuf); pBuf=NULL; if (FAILED(hr = StringToBinary((BSTR)bstrU, bstrU.Length(), &pBuf, &dwLen))) goto done; if (!CryptDecrypt(hSessKey, 0, TRUE, 0, pBuf, &dwLen)) { DEBUG_MSG(_T("Can't decrypt salem ticket")); hr = HRESULT_FROM_WIN32(GetLastError()); goto done; } pBlob = SysAllocStringByteLen((char*)pBuf, dwLen); m_bstrSalemTicket.Attach(pBlob); } else { TraceSpew(_T("No expert's public key, use plain text to send salem ticket.")); m_bstrSalemTicket = bstrU; } done: if (pBuf) free(pBuf); if (hSessKey) CryptDestroyKey(hSessKey); return hr; } #if 0 // No need for SP1, server or later DWORD CIMSession::GetExchangeRegValue() { CRegKey cKey; LONG lRet = 0x0; DWORD dwValue = 0x0; lRet = cKey.Open(HKEY_LOCAL_MACHINE, TEXT("SYSTEM\\CurrentControlSet\\Control\\Terminal Server"), KEY_READ ); if (lRet == ERROR_SUCCESS) { lRet = cKey.QueryValue(dwValue,TEXT("UseExchangeIM")); if (lRet == ERROR_SUCCESS) { // Success } } return dwValue; } #endif //////////////////////////////////////////////////////////////////// // This is used for recipient to get his session object. STDMETHODIMP CIMSession::GetLaunchingSession(LONG lID) { HRESULT hr; IDispatch *pDisp = NULL; LONG lFlags; LONG lRet; CComPtr cpDispUser; CComPtr cpMessContact; CComBSTR bstrServiceId; CComBSTR bstrNetGUID; CRegKey cKey; if (!m_pSessMgr) { hr = CoCreateInstance (CLSID_MsgrSessionManager, NULL, CLSCTX_LOCAL_SERVER, IID_IMsgrSessionManager, (LPVOID*)&m_pSessMgr); if (FAILED_HR(_T("CoCreate IMsgrSessionManager failed: %s"), hr)) goto done; } hr = UnlockSession(this); if (FAILED(hr)) goto done; hr = m_pSessMgr->GetLaunchingSession(lID, (IDispatch**)&pDisp); if (FAILED_HR(TEXT("GetLaunchingSession failed: %s"), hr)) goto done; hr = pDisp->QueryInterface(IID_IMsgrSession, (LPVOID*)&m_pSessObj); if (FAILED_HR(_T("QI IID_IMsgrSession failed: %s"), hr)) goto done; // Grab the user hr = m_pSessObj->get_User((IDispatch**)&cpDispUser); if (FAILED_HR(_T("get_User failed: %s"), hr)) goto done; // QI for the IMessengerContact hr = cpDispUser->QueryInterface(IID_IMessengerContact, (void **)&cpMessContact); if (FAILED_HR(_T("QI failed getting IID_IMessengerContact hr=%s"),hr)) goto done; // Grab the Service ID from the Messenger Contact hr = cpMessContact->get_ServiceId(&bstrServiceId); if (FAILED_HR(_T("get_ServiceId failed! hr=%s"),hr)) goto done; // If the service ID is {9b017612-c9f1-11d2-8d9f-0000f875c541}, then set the // flag to unlock the API. // bstrNetGUID = L"{9b017612-c9f1-11d2-8d9f-0000f875c541}"; // Messenger GUID bstrNetGUID = L"{83D4679E-B6D7-11D2-BF36-00C04FB90A03}"; // Exchange Service GUID if (bstrNetGUID == bstrServiceId) { m_bExchangeUser = TRUE; } // Else continue... // *************************************************************************** // Hook up everything if (FAILED(hr = InitSessionEvent(m_pSessObj))) goto done; hr = m_pSessObj->get_Flags(&lFlags); if (FAILED_HR(TEXT("Session Get flags failed: %s"), hr)) goto done; if (lFlags & SF_INVITEE) // Inviter. Only happened when Messenger UI sends this invitation. { m_bIsInviter = FALSE; } done: if (pDisp) pDisp->Release(); return hr; } HRESULT CIMSession::OnLockChallenge(BSTR pChallenge , LONG lCookie) { // Send response. // // id = assist@msnmsgr.com // key = L2P3B7C6V9J4T8D5 // USES_CONVERSION; HRESULT hr = S_OK; CComBSTR bstrID = "assist@msnmsgr.com"; CComBSTR bstrResponse; LPSTR pszKey = "L2P3B7C6V9J4T8D5"; PSTR pszParam1 = NULL; LPSTR pszResponse = NULL; pszResponse = CAuthentication::GetAuthentication()->GetMD5Result(W2A(pChallenge), pszKey); bstrResponse = pszResponse; hr = m_pMsgrLockKey->SendResponse(bstrID, bstrResponse, lCookie); if (FAILED_HR(_T("SendResponse failed %s"), hr)) goto done; done: if (pszResponse) delete pszResponse; return hr; } #define WM_APP_LOCKNOTIFY WM_APP + 0x1 #define WM_APP_LOCKNOTIFY_OK WM_APP + 0x2 #define WM_APP_LOCKNOTIFY_FAIL WM_APP + 0x3 #define WM_APP_LOCKNOTIFY_INTHREAD WM_APP + 0x4 HRESULT CIMSession::OnLockResult(BOOL fSucceed, LONG lCookie) { // Notify UnlockSession that we've get response.. assert(g_hWnd); SendNotifyMessage(g_hWnd, WM_APP_LOCKNOTIFY, (WPARAM)fSucceed, NULL); DoSessionStatus(fSucceed ? RA_IM_UNLOCK_SUCCEED : RA_IM_UNLOCK_FAILED); return S_OK; } /////////////////////////////////////////////////////////////////////// // This method will be used only from inside HSC STDMETHODIMP CIMSession::HSC_Invite(IDispatch* pUser) { // Create a Invitation thread and return. // Need a lock for user to click cancel. HRESULT hr = S_OK; assert(g_hLockEvent == NULL); // If it's not NULL, there is a bug. g_hLockEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if (!g_hLockEvent) { hr = HRESULT_FROM_WIN32(GetLastError()); goto done; } if (pUser == NULL) { hr = E_INVALIDARG; goto done; } CoMarshalInterThreadInterfaceInStream(IID_IDispatch,pUser,&g_spInvitee); if (this->m_pfnSessionStatus) { CoMarshalInterThreadInterfaceInStream(IID_IDispatch, this->m_pfnSessionStatus, &g_spStatus); this->m_pfnSessionStatus = NULL; } if (!CreateThread(NULL, 0, HSCInviteThread, NULL, 0, NULL)) { hr = HRESULT_FROM_WIN32(GetLastError()); goto done; } //Jose: If m_pfnSessionStatus is not set the component displays its own status dialog. if (!this->m_pfnSessionStatus) { if (IDCANCEL == m_StatusDlg.DoModal()) { Notify(RA_IM_CANCELLED); } } done: if (FAILED(hr) && g_hLockEvent != NULL) { CloseHandle(g_hLockEvent); g_hLockEvent = NULL; } return hr; } DWORD WINAPI HSCInviteThread(LPVOID lpParam) { CComObject *pThis = NULL; HRESULT hr; CComBSTR bstrAPPID(C_RA_APPID); CComPtr cpDisp; LockStatus ls=LOCK_NOTINITIALIZED; hr = CComObject::CreateInstance(&pThis); if (FAILED(hr)) { goto done; } if (g_spStatus) // Rebuild StatusCallback { CoGetInterfaceAndReleaseStream(g_spStatus,IID_IDispatch,(void**)&pThis->m_pfnSessionStatus); g_spStatus = NULL; } // 1. Create SessionManager if (!pThis->m_pSessMgr) { hr = UnlockSession(pThis); if (FAILED(hr)) goto done; } // Check Lock status hr = pThis->m_pMsgrLockKey->get_Status(&ls); if (ls != LOCK_UNLOCKED) pThis->DoSessionStatus(RA_IM_UNLOCK_SUCCEED); else pThis->DoSessionStatus(RA_IM_UNLOCK_FAILED); if (ls != LOCK_UNLOCKED) { pThis->DoSessionStatus(RA_IM_UNLOCK_FAILED); goto done; } // 3. create session object hr = pThis->m_pSessMgr->CreateSession((IDispatch**)&cpDisp); if (FAILED(hr)) goto done; hr = cpDisp->QueryInterface(IID_IMsgrSession, (void **)&pThis->m_pSessObj); if (FAILED(hr)) goto done; // Hook up enent sink if (FAILED(hr = pThis->InitSessionEvent(pThis->m_pSessObj))) goto done; // 4. Set session option hr = pThis->m_pSessObj->put_Application((BSTR)bstrAPPID); if (FAILED_HR(_T("put_Application failed: %s"), hr)) goto done; // OK. I'm from HelpCtr. pThis->m_bIsHSC = TRUE; // Invite if (!cpDisp) cpDisp.Release(); CoGetInterfaceAndReleaseStream(g_spInvitee,IID_IDispatch,(void**)&cpDisp); g_spInvitee = NULL; if (g_bActionCancel) // It's cancelled already. goto done; if(FAILED(hr = pThis->Invite(cpDisp))) goto done; // This loop is only used if user wants to cancel this invitation. while (1) { // User has 10 minutes to click cancel. // If regular connection doesn't happen in 10 minutes, it timeout too. DWORD dwWaitState = WaitForSingleObject(g_hLockEvent, RA_TIMEOUT_USER); if (dwWaitState == WAIT_OBJECT_0 && g_bActionCancel == TRUE) // at this moment, we don't have anyother action. { hr = pThis->m_pSessObj->Cancel(MSGR_E_CANCEL, NULL); } break; // For now, we always get out of the loop. } done: if (g_hLockEvent) { CloseHandle(g_hLockEvent); g_hLockEvent = NULL; } if (pThis) pThis->Release(); g_bActionCancel = FALSE; //reset this global variable. return hr; } //////////////////////////////////////////////////////////////// // This function only used from inside HSC HRESULT CIMSession::Invite(IDispatch* pUser) { HRESULT hr = S_OK; if (m_pSessObj == NULL) { hr = E_FAIL; goto done; } // Send invitation without ticket. Ticket will be sent from ContextData. hr = m_pSessObj->Invite(pUser, NULL); if (FAILED_HR(TEXT("Invite failed %s"), hr)) goto done; DoSessionStatus(RA_IM_SENDINVITE); done: return hr; } STDMETHODIMP CIMSession::Notify(int iIMStatus) { HRESULT hr = S_OK; TCHAR szHeader[1024]; CComBSTR bstrData; if (iIMStatus == RA_IM_CANCELLED || iIMStatus == RA_IM_CLOSE_INVITE_UI) // Doesn't need to use ContextData to notify this msg { assert(m_bIsHSC == TRUE); // Only helpctr scenario would do this. if (g_hLockEvent) // if it's NULL, that means this thread has already terminated itself. { g_bActionCancel = (iIMStatus == RA_IM_CANCELLED); // It's possible that user just want to close the UI. SetEvent(g_hLockEvent); } goto done; } if (m_pSessObj) { wsprintf(szHeader, _T("%d;NOTIFY=%d"), GetDigit(iIMStatus) + 7, iIMStatus); bstrData = szHeader; if (bstrData.Length() > 0) { hr = m_pSessObj->SendContextData((BSTR)bstrData); if (FAILED_HR(_T("Notify: SendContextData failed %s"), hr)) goto done; } } done: return S_OK; } //////////////////////////////////////////////////////////////// // This function sends expert ticket to user through ContextData STDMETHODIMP CIMSession::SendOutExpertTicket(BSTR bstrTicket) { TraceSpewW(L"Funct: SendOutExpertTicket %s", bstrTicket?bstrTicket:L"NULL"); HRESULT hr = S_OK; CComBSTR bstrPublicKeyBlob; CComBSTR bstrBlob; DWORD dwCount=0, dwLen; TCHAR szHeader[100]; if (!m_pSessObj) return FALSE; // 1. Get public blob. if (FAILED(hr = InitCSP())) goto done; // 2. Create Blob with predefined format. if(FAILED(hr = GetKeyExportString(m_hPublicKey, 0, PUBLICKEYBLOB, &bstrPublicKeyBlob, &dwCount))) goto done; dwLen = wcslen(bstrTicket); wsprintf(szHeader, _T("%d;ET="), dwLen+3); bstrBlob = szHeader; bstrBlob.AppendBSTR(bstrTicket); if (dwCount) { wsprintf(szHeader, _T("%d;PK="), dwCount+3); bstrBlob.Append(szHeader); bstrBlob += bstrPublicKeyBlob; } // 3. Send it out. hr = m_pSessObj->SendContextData((BSTR)bstrBlob); if (FAILED(hr)) goto done; DoSessionStatus(RA_IM_EXPERT_TICKET_OUT); done: return hr; } HRESULT CIMSession::GetKeyExportString(HCRYPTKEY hKey, HCRYPTKEY hExKey, DWORD dwBlobType, BSTR* pBlob, DWORD *pdwCount) { HRESULT hr = S_OK; DWORD dwKeyLen; LPBYTE pBinBuf = NULL; if (!pBlob) return FALSE; // Calculate how big the destination buffer size we need. if (!CryptExportKey(hKey, hExKey, dwBlobType, 0, NULL, &dwKeyLen)) { DEBUG_MSG(_T("Can't calculate public key length")); hr = HRESULT_FROM_WIN32(GetLastError()); goto done; } pBinBuf = (LPBYTE)malloc(dwKeyLen); if (!pBinBuf) { hr = E_OUTOFMEMORY; goto done; } if (!CryptExportKey(hKey, hExKey, dwBlobType, 0, pBinBuf, &dwKeyLen)) { DEBUG_MSG(_T("Can't write public key to blob")); hr = HRESULT_FROM_WIN32(GetLastError()); goto done; } if (FAILED(hr=BinaryToString(pBinBuf, dwKeyLen, pBlob, pdwCount))) goto done; done: if (pBinBuf) free(pBinBuf); return hr; } HRESULT CIMSession::BinaryToString(LPBYTE pBinBuf, DWORD dwLen, BSTR* pBlob, DWORD *pdwCount) { HRESULT hr = S_OK; TCHAR *pBuf = NULL; CComBSTR bstrBlob; if (!pBlob) return FALSE; if (!CryptBinaryToString(pBinBuf, dwLen, CRYPT_STRING_BASE64, NULL, pdwCount)) { DEBUG_MSG(_T("Can't calculate string len for blob converstion")); hr = HRESULT_FROM_WIN32(GetLastError()); goto done; } if (NULL == (pBuf = (TCHAR*)malloc(*pdwCount * sizeof(TCHAR)))) { hr = E_OUTOFMEMORY; goto done; } if (!CryptBinaryToString(pBinBuf, dwLen, CRYPT_STRING_BASE64, pBuf, pdwCount)) { DEBUG_MSG(_T("Can't convert key blob to string")); hr = HRESULT_FROM_WIN32(GetLastError()); goto done; } bstrBlob.Append(pBuf); *pBlob = bstrBlob.Detach(); done: if (pBuf) free(pBuf); return hr; } HRESULT CIMSession::StringToBinary(BSTR pBlob, DWORD dwCount, LPBYTE *ppBuf, DWORD* pdwLen) { HRESULT hr=S_OK; DWORD dwSkip, dwFlag; if (!CryptStringToBinary(pBlob, dwCount, CRYPT_STRING_BASE64, NULL, pdwLen, &dwSkip, &dwFlag)) { DEBUG_MSG(_T("Can't calculate needed binary buffer length")); hr = HRESULT_FROM_WIN32(GetLastError()); goto done; } *ppBuf = (LPBYTE)malloc(*pdwLen); if (!CryptStringToBinary(pBlob, dwCount, CRYPT_STRING_BASE64, *ppBuf, pdwLen, &dwSkip, &dwFlag)) { DEBUG_MSG(_T("Can't convert to binary blob")); hr = HRESULT_FROM_WIN32(GetLastError()); goto done; } done: return hr; } HRESULT CIMSession::UninitObjects() { HRESULT hr = S_OK; // Make sure we don't follow the null pointer if (m_pSessMgr && m_pSessionMgrEvent) { m_pSessionMgrEvent->Unadvise(); } return hr; } HRESULT UnlockSession(CIMSession* pThis) { TraceSpew(_T("Funct: UnlockSession")); HRESULT hr = S_OK; MSG msg; CComPtr cpCPC; CComPtr cpCP; LockStatus ls=LOCK_NOTINITIALIZED; BOOL bRet; assert(pThis->m_pSessMgr == NULL); hr = CoCreateInstance( CLSID_MsgrSessionManager, NULL, CLSCTX_LOCAL_SERVER, IID_IMsgrSessionManager, (LPVOID*)&pThis->m_pSessMgr); if (FAILED_HR(_T("CoCreate CLSID_MsgrSessionManager failed: %s"), hr)) goto done; // 2. Create Lock object hr = pThis->m_pSessMgr->QueryInterface(IID_IMsgrLock, (LPVOID*)&pThis->m_pMsgrLockKey); if (FAILED_HR(_T("Can't create MsgrLock object: %s"), hr)) goto done; // 2. Hook SessionManager events pThis->m_pSessionMgrEvent = new CSessionMgrEvent(pThis); if (!pThis->m_pSessionMgrEvent) { hr = E_OUTOFMEMORY; goto done; } pThis->m_pSessionMgrEvent->AddRef(); hr = pThis->m_pSessMgr->QueryInterface(IID_IConnectionPointContainer, (void**)&cpCPC); if (FAILED_HR(_T("QI: IConnectionPointContainer of SessionMgr failed %s"), hr)) goto done; hr = cpCPC->FindConnectionPoint(DIID_DMsgrSessionManagerEvents, &cpCP); if (FAILED_HR(_T("FindConnectionPoint DMessengerEvents failed %s"), hr)) goto done; hr = pThis->m_pSessionMgrEvent->Advise(cpCP); if (FAILED(hr)) goto done; g_hWnd = InitInstance(g_hInstance, 0); // Set up credential with server. hr = pThis->m_pMsgrLockKey->get_Status(&ls); if (ls == LOCK_UNLOCKED) { hr = S_OK; goto done; } SetTimer(g_hWnd, RA_TIMER_UNLOCK_ID, RA_TIMEOUT_UNLOCK, NULL); // 3 minutes. // Send challenge pThis->DoSessionStatus(RA_IM_UNLOCK_WAIT); hr = pThis->m_pMsgrLockKey->RequestChallenge(70); // Random number: 70 if (FAILED_HR(_T("RequestChallenge failed: %s"), hr)) goto done; // Wait until permission get granted or timeout. while (bRet = GetMessage(&msg, NULL, 0, 0)) { if (msg.message == WM_APP_LOCKNOTIFY_INTHREAD) { hr = ((BOOL)msg.wParam)?S_OK:E_FAIL; break; } TranslateMessage(&msg); DispatchMessage(&msg); } done: if (g_hWnd) { // kill control window. DestroyWindow(g_hWnd); g_hWnd = NULL; } TraceSpew(_T("Leave UnlockSession hr=%s"),GetStringFromError(hr)); return hr; } HWND InitInstance(HINSTANCE hInstance, int nCmdShow) { HWND hWnd; WNDCLASSEX wcex; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = WndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = NULL; wcex.hCursor = NULL; wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wcex.lpszMenuName = NULL; wcex.lpszClassName = szWindowClass; wcex.hIconSm = NULL; RegisterClassEx(&wcex); hWnd = CreateWindow(szWindowClass, TEXT("Remote Assistance"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 500, 500, NULL, NULL, hInstance, NULL); return hWnd; } LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_CREATE: TraceSpew(_T("WndProc: WM_CREATE called")); g_hWnd = hWnd; break; case WM_TIMER: { if (wParam == RA_TIMER_UNLOCK_ID) { TraceSpew(_T("WndProc: WM_TIMER RA_TIMER_UNLOCK_ID fired")); PostMessage(NULL, WM_APP_LOCKNOTIFY_INTHREAD, (WPARAM)FALSE, NULL); } } break; case WM_APP_LOCKNOTIFY: { //PostQuitMessage(0); // Used for single thread TraceSpew(_T("WndProc: WM_APP_LOCKNOTIFY fired")); KillTimer(g_hWnd, RA_TIMER_UNLOCK_ID); PostMessage(NULL, WM_APP_LOCKNOTIFY_INTHREAD, wParam, lParam); } break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; }