/* * Copyright 2013 Red Hat, Inc. and/or its affiliates. * * Licensed under the GNU General Public License, version 2 or any * later version. See the file COPYING in the top-level directory of * this distribution or http://www.gnu.org/licenses/gpl-2.0.txt. */ // OSpiceX.cpp : Implementation of COSpiceX #include "stdafx.h" #pragma warning(disable: 4995) // 'gets' (and more): name was marked as #pragma deprecated #include #include "OSpiceX.h" #include #include #include // MSDN: The maximum length of this string is 32K characters. // ms-help://MS.VSCC.v80/MS.MSDN.v80/MS.WIN32COM.v10.en/dllproc/base/createprocess.htm #define CMDLINE_LENGTH 32768 #define SPICE_X_DLL _T("SpiceX.DLL") // COSpiceX DWORD WINAPI COSpiceX::event_thread(PVOID pv) { COSpiceX* spicex = (COSpiceX*)pv; HANDLE events[2] = {spicex->m_OverlappedRead.hEvent, spicex->m_OverlappedWrite.hEvent}; bool running = true; ControllerValue msg; DWORD error_code; DWORD exit_code; DWORD wait_res; //Handles only ControllerValue msgs ReadFile(spicex->m_hClientPipe, &msg, sizeof(msg), NULL, &spicex->m_OverlappedRead); while (running) { wait_res = WaitForMultipleObjects(2, events, FALSE, INFINITE); switch (wait_res) { case WAIT_OBJECT_0: running = spicex->handle_in_msg(msg); break; case WAIT_OBJECT_0 + 1: running = spicex->handle_out_msg(); break; case WAIT_FAILED: LOG_INFO("WaitForMultipleObjects failed, err=%lu", GetLastError()); running = false; break; } } //Wait for proccess to terminate wait_res = WaitForSingleObject(spicex->m_hClientProcess, 3000); //Handle errors if (wait_res != WAIT_OBJECT_0 || !GetExitCodeProcess(spicex->m_hClientProcess, &exit_code) || exit_code == STILL_ACTIVE) { TerminateProcess(spicex->m_hClientProcess, 0); CloseHandle(spicex->m_hClientProcess); spicex->m_hClientProcess = NULL; exit_code = SPICEC_ERROR_CODE_ERROR; } switch (exit_code) { case SPICEC_ERROR_CODE_SUCCESS: error_code = RDP_ERROR_CODE_LOCAL_DISCONNECTION; break; case SPICEC_ERROR_CODE_GETHOSTBYNAME_FAILED: error_code = RDP_ERROR_CODE_HOST_NOT_FOUND; break; case SPICEC_ERROR_CODE_CONNECT_FAILED: error_code = RDP_ERROR_CODE_WINSOCK_CONNECT_FAILED; break; case SPICEC_ERROR_CODE_ERROR: case SPICEC_ERROR_CODE_SOCKET_FAILED: error_code = RDP_ERROR_CODE_INTERNAL_ERROR; break; case SPICEC_ERROR_CODE_RECV_FAILED: error_code = RDP_ERROR_RECV_WINSOCK_FAILED; break; case SPICEC_ERROR_CODE_SEND_FAILED: error_code = RDP_ERROR_SEND_WINSOCK_FAILED; break; case SPICEC_ERROR_CODE_NOT_ENOUGH_MEMORY: error_code = RDP_ERROR_CODE_OUT_OF_MEMORY; break; case SPICEC_ERROR_CODE_AGENT_TIMEOUT: error_code = RDP_ERROR_CODE_TIMEOUT; break; case SPICEC_ERROR_CODE_AGENT_ERROR: default: error_code = exit_code + SPICE_ERROR_BASE; } LOG_INFO("exit_code=%lu error_code=%lu", exit_code, error_code); spicex->PostMessage(SPICEX_ON_DISCONNECTED_MSG, error_code, 0); spicex->cleanup(); return 0; } bool COSpiceX::handle_in_msg(ControllerValue& msg) { DWORD bytes_read; bool res; if (!GetOverlappedResult(m_hClientPipe, &m_OverlappedRead, &bytes_read, FALSE) || bytes_read != sizeof(msg) || msg.base.size != sizeof(msg)) { return false; } switch(msg.base.id) { case CONTROLLER_MENU_ITEM_CLICK: DBG(3, "menu-item-click: value=%u", msg.value); ((COSpiceX*)this)->PostMessage(SPICEX_ON_MENU_ITEM_SELECTED_MSG, msg.value, 0); break; default: LOG_INFO("unknown message: %u", msg.base.id); return false; } res = ReadFile(m_hClientPipe, &msg, sizeof(msg), NULL, &m_OverlappedRead) || GetLastError() == ERROR_IO_PENDING; return res; } bool COSpiceX::handle_out_msg() { bool res = true; DWORD written; size_t size; EnterCriticalSection(&m_WriteLock); if (!GetOverlappedResult(m_hClientPipe, &m_OverlappedWrite, &written, FALSE)) { LeaveCriticalSection(&m_WriteLock); return false; } m_WritePending = false; m_WriteStart = m_WriteBuffer + (m_WriteStart - m_WriteBuffer + written) % PIPE_BUF_SIZE; if (m_WriteStart <= m_WriteEnd) { size = m_WriteEnd - m_WriteStart; } else { size = m_WriteBuffer + PIPE_BUF_SIZE - m_WriteStart; } if (size) { if (WriteFile(m_hClientPipe, m_WriteStart, (DWORD)size, NULL, &m_OverlappedWrite) || GetLastError() == ERROR_IO_PENDING) { m_WritePending = true; } else { res = false; } } LeaveCriticalSection(&m_WriteLock); return res; } DWORD WINAPI COSpiceX::UsbCtrlWatchDog(PVOID pv) { COSpiceX *pThis = reinterpret_cast(pv); if (pThis != NULL) { if (::WaitForSingleObject(pThis->m_hUsbCtrlProcess, INFINITE) == WAIT_OBJECT_0) { DWORD dwExitCode; ::CloseHandle(pThis->m_hUsbCtrlLog); pThis->m_hUsbCtrlLog = INVALID_HANDLE_VALUE; if (::GetExitCodeProcess(pThis->m_hUsbCtrlProcess, &dwExitCode) == TRUE) { pThis->m_hUsbCtrlProcess = NULL; if (dwExitCode != 0) { LOG_INFO("Restarting USB controller"); // The USB controller was terminated, re-run it again. pThis->ExecuteUsbCtrl(); } } } } return 0; } STDMETHODIMP COSpiceX::Connect(void) { wchar_t lpCommandLine[CMDLINE_LENGTH] = {0}; LPTSTR lpProcName; DWORD dwRetVal; HMODULE hModule; STARTUPINFO si; HKEY hkey; LONG lret; if (m_hClientProcess != NULL) { return SPICEX_ERROR_CLIENT_PROCESS_ALREADY_CONNECTED; } ZeroMemory(&si, sizeof(si)); si.cb = sizeof(si); ZeroMemory(&m_pi, sizeof(m_pi)); // FIXME: do we need the GetModuleXXX calls? // we assume the Spice client binary is located in the same dir as the ActiveX dll if (hModule = GetModuleHandle(SPICE_X_DLL)) { if (dwRetVal = GetModuleFileName(hModule, lpCommandLine, MAX_PATH)) { // locate the 1st char of the ActiveX name (past the last '\') lpProcName = (wcsrchr(lpCommandLine, L'\\') + 1); // zero it so we can append the red client name if (lpProcName) { lpProcName[0] = 0; } } } lret = RegOpenKeyEx(HKEY_CURRENT_USER, L"Software\\spice-space.org\\spicex", 0, KEY_READ, &hkey); if (lret == ERROR_SUCCESS) { DWORD dwType = REG_SZ; DWORD dwSize = sizeof(lpCommandLine); lret = RegQueryValueEx(hkey, L"client", NULL, &dwType, (LPBYTE)lpCommandLine, &dwSize); RegCloseKey(hkey); } if (lret != ERROR_SUCCESS) { lret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"Software\\spice-space.org\\spicex", 0, KEY_READ, &hkey); if (lret == ERROR_SUCCESS) { DWORD dwType = REG_SZ; DWORD dwSize = sizeof(lpCommandLine); lret = RegQueryValueEx(hkey, L"client", NULL, &dwType, (LPBYTE)lpCommandLine, &dwSize); RegCloseKey(hkey); } } if (lret != ERROR_SUCCESS) { StringCchPrintf(lpCommandLine, CMDLINE_LENGTH, L"%s%s --controller", lpCommandLine, RED_CLIENT_FILE_NAME); } DBG(0, "Running spicec (%S)", lpCommandLine); if (!CreateProcess(NULL, lpCommandLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &m_pi)) { DWORD err = GetLastError(); LOG_ERROR("Failed to run spicec (%S) -- %lu", lpCommandLine, err); return MAKE_HRESULT(1, FACILITY_CREATE_RED_PROCESS, err); } LOG_INFO("spicec pid %lu", ::GetProcessId(m_pi.hProcess)); m_hClientProcess = m_pi.hProcess; if (WaitForInputIdle(m_hClientProcess, 5000) != 0) LOG_WARN("Waiting for client idle failed, trying to connnect"); DBG(0, "connecting to spice client's pipe"); wchar_t lpszClientPipeName[RED_CLIENT_PIPE_NAME_MAX_LEN]; StringCchPrintf(lpszClientPipeName, RED_CLIENT_PIPE_NAME_MAX_LEN, RED_CLIENT_PIPE_NAME, m_pi.dwProcessId); for (int retry = 0; retry < 5; retry++) { m_hClientPipe = CreateFile(lpszClientPipeName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, SECURITY_SQOS_PRESENT | SECURITY_ANONYMOUS | FILE_FLAG_OVERLAPPED, NULL); if (m_hClientPipe != INVALID_HANDLE_VALUE) { break; } Sleep(1000); } // Verify the named-pipe-server owner is the current user. // Do it here so upon failure use next condition cleanup code. if (m_hClientPipe != INVALID_HANDLE_VALUE) { if (!is_same_user(m_hClientPipe)) { LOG_ERROR("Closing pipe to spicec -- it is not safe"); CloseHandle(m_hClientPipe); m_hClientPipe = INVALID_HANDLE_VALUE; } } if (m_hClientPipe == INVALID_HANDLE_VALUE) { LOG_ERROR("failed to connect to spice client pipe"); TerminateProcess(m_hClientProcess, 0); CloseHandle(m_hClientProcess); m_hClientProcess = NULL; return MAKE_HRESULT(1, FACILITY_CREATE_RED_PIPE, GetLastError()); } m_hEventThread = CreateThread(NULL, 0, event_thread, this, 0, NULL); if (m_hEventThread == NULL) { LOG_ERROR("failed to create event thread"); cleanup(); return MAKE_HRESULT(1, FACILITY_CREATE_RED_PIPE, GetLastError()); } send_init(); send_wstr(CONTROLLER_HOST, m_HostIP, true); send_value(CONTROLLER_PORT, m_Port); send_value(CONTROLLER_SPORT, m_SecurePort); send_wstr(CONTROLLER_PASSWORD, m_szPassword, true); send_wstr(CONTROLLER_SET_TITLE, m_Title, true); send_value(CONTROLLER_FULL_SCREEN, (m_FullScreen ? CONTROLLER_SET_FULL_SCREEN : 0) | (m_AdminConsole ? 0 : CONTROLLER_AUTO_DISPLAY_RES)); send_wstr(CONTROLLER_HOTKEYS, m_HotKey.c_str(), true); send_wstr(CONTROLLER_CREATE_MENU, m_strMenuResource, true); send_wstr(CONTROLLER_SECURE_CHANNELS, m_szSSLChannels, true); send_wstr(CONTROLLER_TLS_CIPHERS, m_szCipherSuite, true); send_wstr(CONTROLLER_HOST_SUBJECT, m_HostSubject.c_str(), true); send_wstr(CONTROLLER_PROXY, m_Proxy.c_str(), true); if (!m_TrustStore.empty()) { WCHAR szCAFileName[MAX_PATH]; if (SUCCEEDED(CreateCAFile(szCAFileName, sizeof(szCAFileName)))) { send_wstr(CONTROLLER_CA_FILE, szCAFileName, true); } } if (m_ColorDepth) { send_value(CONTROLLER_COLOR_DEPTH, m_ColorDepth); } if (!m_DisableEffects.empty()) { send_wstr(CONTROLLER_DISABLE_EFFECTS, m_DisableEffects.c_str(), true); } send_bool(CONTROLLER_ENABLE_SMARTCARD, !!m_SmartCard); send_bool(CONTROLLER_SEND_CAD, !!m_SendCtrlAltDelete); send_msg(CONTROLLER_CONNECT); send_msg(CONTROLLER_SHOW); if (m_UsbListenPort != 0) { /* old USB, disable new USB solution */ send_value(CONTROLLER_ENABLE_USB, FALSE); ExecuteUsbCtrl(); } else { send_value(CONTROLLER_ENABLE_USB, TRUE); send_bool(CONTROLLER_ENABLE_USB_AUTOSHARE, !!m_UsbAutoShare); send_wstr(CONTROLLER_USB_FILTER, m_UsbFilter.c_str()); } return S_OK; } STDMETHODIMP COSpiceX::Disconnect(void) { if (m_hClientProcess == NULL) { return SPICEX_ERROR_CLIENT_PROCESS_NOT_CONNECTED; } cleanup(); return S_OK; } void COSpiceX::cleanup() { if (m_hClientProcess) { TerminateProcess(m_hClientProcess, 0); CloseHandle(m_hClientProcess); m_hClientProcess = NULL; } if (m_hClientPipe) { CloseHandle(m_hClientPipe); m_hClientPipe = NULL; } if (m_hEventThread) { CloseHandle(m_hEventThread); m_hEventThread = NULL; } } STDMETHODIMP COSpiceX::SendCAD(void) { // TODO: Add your implementation code here return S_OK; } STDMETHODIMP COSpiceX::Show(void) { return send_msg(CONTROLLER_SHOW); } STDMETHODIMP COSpiceX::Hide(void) { return send_msg(CONTROLLER_HIDE); } STDMETHODIMP COSpiceX::SetSecondaryAddress(void) { // TODO: Add your implementation code here return S_OK; } STDMETHODIMP COSpiceX::Load(IPropertyBag *pPropBag, IErrorLog* pErrorLog) { CComVariant pVar; HRESULT hRes; pVar.vt = VT_UI2; hRes = pPropBag->Read(L"Port", &pVar, pErrorLog); if (hRes == S_OK) { m_Port = pVar.uiVal; } pVar.vt = VT_UI2; hRes = pPropBag->Read(L"SecurePort", &pVar, pErrorLog); if (hRes == S_OK) { m_SecurePort = pVar.uiVal; } pVar.vt = VT_BSTR; hRes = pPropBag->Read(L"HostIP", &pVar, pErrorLog); if (hRes == S_OK) { StringCchCopyN(m_HostIP, MAX_PATH, pVar.bstrVal, MAX_PATH); } pVar.vt = VT_BSTR; hRes = pPropBag->Read(L"Title", &pVar, pErrorLog); if (hRes == S_OK) { StringCchCopyN(m_Title, RED_CLIENT_MAX_TITLE_SIZE, pVar.bstrVal, RED_CLIENT_MAX_TITLE_SIZE); } pVar.vt = VT_BSTR; hRes = pPropBag->Read(L"CipherSuite", &pVar, pErrorLog); if (hRes == S_OK && lstrlen(V_BSTR(&pVar)) > 0) { m_bIsSSL = true; StringCchCopyN(m_szCipherSuite, MAX_PATH, V_BSTR(&pVar), MAX_PATH); } return S_OK; } STDMETHODIMP COSpiceX::get_CipherSuite(BSTR* pVal) { *pVal=::SysAllocString(m_szCipherSuite); return S_OK; } STDMETHODIMP COSpiceX::put_CipherSuite(BSTR newVal) { if (newVal) { wcscpy_s(m_szCipherSuite, MAX_PATH, newVal); } return S_OK; } STDMETHODIMP COSpiceX::get_SSLChannels(BSTR* pVal) { *pVal=::SysAllocString(m_szSSLChannels); return S_OK; } STDMETHODIMP COSpiceX::put_SSLChannels(BSTR newVal) { ZeroMemory(m_szSSLChannels, sizeof(m_szSSLChannels)); // validate input arg if (newVal != NULL && lstrlen(newVal) > 0) { m_bIsSSL = true; StringCchCopyN(m_szSSLChannels, MAX_PATH, newVal, MAX_PATH); /* * Backward Compatibility: * Remove leading 's' from m_ssl_channels, e.g. "main" not "smain" * RHEL5 uses 'smain' and 'sinpusts * RHEL6 uses 'main' and 'inputs' */ const wchar_t* chan_names[] = { L"smain", L"sdisplay", L"sinputs", L"scursor", L"splayback", L"srecord", L"susbredir", L"ssmartcard", L"stunnel" }; const int nnames = sizeof(chan_names) / sizeof(chan_names[0]); wchar_t *p; const wchar_t *cn; size_t len, n, i, j; n = wcslen(m_szSSLChannels); for (i=0 ; i (b) ? (b) : (a)) HRESULT COSpiceX::write_to_pipe(const void* data, uint32_t size) { bool res = true; EnterCriticalSection(&m_WriteLock); size_t free_size = (PIPE_BUF_SIZE + m_WriteStart - m_WriteEnd - 1) % PIPE_BUF_SIZE; if (size > free_size) { res = false; } else if (m_WriteEnd < m_WriteStart) { memcpy(m_WriteEnd, data, size); m_WriteEnd += size; } else { size_t n = MIN(size, (size_t)(m_WriteBuffer + PIPE_BUF_SIZE - m_WriteEnd)); memcpy(m_WriteEnd, data, n); if (size > n) { memcpy(m_WriteBuffer, (uint8_t*)data + n, size - n); } m_WriteEnd = m_WriteBuffer + (m_WriteEnd - m_WriteBuffer + size) % PIPE_BUF_SIZE; } if (res && !m_WritePending) { if (WriteFile(m_hClientPipe, data, size, NULL, &m_OverlappedWrite) || GetLastError() == ERROR_IO_PENDING) { res = m_WritePending = true; } } LeaveCriticalSection(&m_WriteLock); return (res ? S_OK : MAKE_HRESULT(SEVERITY_ERROR, FACILITY_PIPE_OPERATION, GetLastError())); } HRESULT COSpiceX::send_init() { DBG(0, "sending init"); ControllerInit msg = {{CONTROLLER_MAGIC, CONTROLLER_VERSION, sizeof(msg)}, 0, CONTROLLER_FLAG_EXCLUSIVE}; return write_to_pipe(&msg, sizeof(msg)); } HRESULT COSpiceX::send_msg(uint32_t id) { ControllerMsg msg = {id, sizeof(msg)}; DBG(0, "sending msg id %u", id); return write_to_pipe(&msg, sizeof(msg)); } HRESULT COSpiceX::send_value(uint32_t id, uint32_t value) { if (!value) { return S_OK; } DBG(0, "sending msg id %u value %u", id, value); ControllerValue msg = {{id, sizeof(msg)}, value}; return write_to_pipe(&msg, sizeof(msg)); } HRESULT COSpiceX::send_bool(uint32_t id, uint32_t value) { DBG(0, "sending msg id %u bool %u", id, !!value); ControllerValue msg = {{id, sizeof(msg)}, !!value}; return write_to_pipe(&msg, sizeof(msg)); } HRESULT COSpiceX::send_wstr(uint32_t id, const wchar_t* str, bool use_mb_str) { HRESULT res; if (!str || !wcslen(str)) { return S_OK; } if (id == CONTROLLER_PASSWORD) { DBG(0, "sending password"); } else { DBG(0, "sending msg id %u : str %S (%d)", id, str, (int)use_mb_str); } size_t size = sizeof(ControllerData) + (wcslen(str) + 1) * (use_mb_str ? 1 : sizeof(wchar_t)); ControllerData* msg = (ControllerData*)malloc(size); msg->base.id = id; msg->base.size = (uint32_t)size; if (use_mb_str) { sprintf_s((char*)msg->data, wcslen(str) + 1, "%S", str); } else { wcscpy_s((wchar_t*)msg->data, wcslen(str) + 1, str); } res = write_to_pipe(msg, (uint32_t)size); free(msg); return res; } STDMETHODIMP COSpiceX::get_multimediaPorts(BSTR* pVal) { *pVal=::SysAllocString(m_AudioGuestPorts); return S_OK; } STDMETHODIMP COSpiceX::put_multimediaPorts(BSTR newVal) { ZeroMemory(m_AudioGuestPorts, sizeof(m_AudioGuestPorts)); // validate input arg if (newVal != NULL) { StringCchCopyN(m_AudioGuestPorts, MAX_PATH, newVal, MAX_PATH); DBG(3, "multi-media ports %S", m_AudioGuestPorts); } return S_OK; } STDMETHODIMP COSpiceX::get_DynamicMenu(BSTR* pVal) { // TODO: Add your implementation code here return S_OK; } STDMETHODIMP COSpiceX::put_DynamicMenu(BSTR newVal) { // TODO: Add your implementation code here DBG(0, "DynamicMenu %S", newVal); return CreateMenu(newVal); } STDMETHODIMP COSpiceX::get_NumberOfMonitors(ULONG* pVal) { // TODO: Add your implementation code here *pVal = m_ulNumberOfMonitors; return S_OK; } STDMETHODIMP COSpiceX::put_NumberOfMonitors(ULONG newVal) { // TODO: Add your implementation code here if(newVal) m_ulNumberOfMonitors = newVal; return S_OK; } STDMETHODIMP COSpiceX::get_UsbListenPort(USHORT* pVal) { if (pVal == NULL) { return E_INVALIDARG; } *pVal = m_UsbListenPort; return S_OK; } STDMETHODIMP COSpiceX::put_UsbListenPort(USHORT newVal) { m_UsbListenPort = newVal; DBG(3, "New USB listen port %u", m_UsbListenPort); return S_OK; } STDMETHODIMP COSpiceX::get_AdminConsole(BOOL* pVal) { if (pVal == NULL) { return E_INVALIDARG; } *pVal = m_AdminConsole; return S_OK; } STDMETHODIMP COSpiceX::put_AdminConsole(BOOL newVal) { m_AdminConsole = newVal; DBG(3, "New AdminConsole %u", (int)(!!m_AdminConsole)); return S_OK; } STDMETHODIMP COSpiceX::get_SendCtrlAltDelete(BOOL* pVal) { if (pVal == NULL) { return E_INVALIDARG; } *pVal = m_SendCtrlAltDelete; return S_OK; } STDMETHODIMP COSpiceX::put_SendCtrlAltDelete(BOOL newVal) { m_SendCtrlAltDelete = newVal; DBG(3, "New SendCtrlAltDelete %u", (int)(!!m_SendCtrlAltDelete)); return S_OK; } STDMETHODIMP COSpiceX::get_NoTaskMgrExecution(BOOL* pVal) { if (pVal == NULL) { return E_INVALIDARG; } *pVal = m_NoTaskMgrExecution; return S_OK; } STDMETHODIMP COSpiceX::put_NoTaskMgrExecution(BOOL newVal) { m_NoTaskMgrExecution = newVal; DBG(3, "New NoTaskMgr %u", (int)(!!m_NoTaskMgrExecution)); return S_OK; } STDMETHODIMP COSpiceX::SetUsbFilter(BSTR newVal) { if (newVal == NULL) { return E_INVALIDARG; } m_UsbFilter = newVal; DBG(3, "New UsbFilter %S", m_UsbFilter.c_str()); return S_OK; } STDMETHODIMP COSpiceX::get_Version(BSTR* pVal) { DWORD dwDummy; UINT nSize; nSize = ::GetFileVersionInfoSize(SPICE_X_DLL, &dwDummy); if (nSize == 0) { // No version information available. return HRESULT_FROM_WIN32(::GetLastError()); } LPBYTE pVersionInfo = reinterpret_cast(_alloca(nSize)); if (!::GetFileVersionInfo(SPICE_X_DLL, 0L, nSize, pVersionInfo)) { return HRESULT_FROM_WIN32(::GetLastError()); } LPVOID pData; if (!::VerQueryValue(pVersionInfo, _T("\\"), &pData, &nSize)) { return HRESULT_FROM_WIN32(::GetLastError()); } VS_FIXEDFILEINFO ffi; ::CopyMemory(&ffi, pData, nSize); WCHAR szVersion[16]; StringCchPrintf(szVersion, sizeof(szVersion), _T("%d.%d.%d.%d"), HIWORD(ffi.dwFileVersionMS), LOWORD(ffi.dwFileVersionMS), HIWORD(ffi.dwFileVersionLS), LOWORD(ffi.dwFileVersionLS)); *pVal = ::SysAllocString(szVersion); return S_OK; } STDMETHODIMP COSpiceX::get_UsbAutoShare(BOOL* pVal) { if (pVal == NULL) { return E_INVALIDARG; } *pVal = m_UsbAutoShare; return S_OK; } STDMETHODIMP COSpiceX::put_UsbAutoShare(BOOL newVal) { m_UsbAutoShare = newVal; DBG(3, "New UsbAutoShare %u", (int)(!!m_UsbAutoShare)); return S_OK; } STDMETHODIMP COSpiceX::SetLanguageStrings(BSTR bzSection, BSTR bzLangStr) { if ((bzSection == NULL) || (bzLangStr == NULL)) { return E_POINTER; } if ((lstrlen(bzSection) == 0) || (lstrlen(bzLangStr) == 0)) { return E_INVALIDARG; } m_Language[bzSection] = bzLangStr; return S_OK; } STDMETHODIMP COSpiceX::get_TrustStore(BSTR* pVal) { *pVal = ::SysAllocString(m_TrustStore.c_str()); return S_OK; } STDMETHODIMP COSpiceX::put_TrustStore(BSTR newVal) { if (newVal == NULL) { return E_INVALIDARG; } m_TrustStore = newVal; DBG(3, "New TrustStore"); return S_OK; } STDMETHODIMP COSpiceX::get_HostSubject(BSTR* pVal) { *pVal = ::SysAllocString(m_HostSubject.c_str()); return S_OK; } STDMETHODIMP COSpiceX::put_HostSubject(BSTR newVal) { if (newVal == NULL) { return E_INVALIDARG; } m_HostSubject = newVal; DBG(3, "New HostSubject %S", m_HostSubject.c_str()); return S_OK; } STDMETHODIMP COSpiceX::get_ColorDepth(USHORT* pVal) { if (pVal == NULL) { return E_INVALIDARG; } *pVal = m_ColorDepth; return S_OK; } STDMETHODIMP COSpiceX::put_ColorDepth(USHORT newVal) { m_ColorDepth = newVal; DBG(3, "New ColorDepth %u", m_ColorDepth); return S_OK; } STDMETHODIMP COSpiceX::get_DisableEffects(BSTR* pVal) { *pVal = ::SysAllocString(m_DisableEffects.c_str()); return S_OK; } STDMETHODIMP COSpiceX::put_DisableEffects(BSTR newVal) { if (newVal == NULL) { return E_INVALIDARG; } m_DisableEffects= newVal; DBG(3, "New DisableEffects %S", m_DisableEffects.c_str()); return S_OK; } STDMETHODIMP COSpiceX::get_SmartCard(BOOL* pVal) { if (pVal == NULL) { return E_INVALIDARG; } *pVal = m_SmartCard; return S_OK; } STDMETHODIMP COSpiceX::put_SmartCard(BOOL newVal) { m_SmartCard = newVal; DBG(3, "New SmartCard %u", !!m_SmartCard); return S_OK; } STDMETHODIMP COSpiceX::get_Proxy(BSTR* pVal) { *pVal = ::SysAllocString(m_Proxy.c_str()); return S_OK; } STDMETHODIMP COSpiceX::put_Proxy(BSTR newVal) { if (newVal == NULL) { return E_INVALIDARG; } m_Proxy = newVal; DBG(3, "New Proxy %S", m_Proxy.c_str()); return S_OK; } HRESULT COSpiceX::ExecuteUsbCtrl() { if ((m_hClientProcess == NULL) || (m_UsbListenPort == 0)) { LOG_INFO("USB sharing is not requested"); return S_OK; } // Find executable path. CRegKey reg; LONG result; const wchar_t* const reg_key = \ L"SOFTWARE\\RedHat\\Red Hat Enterprise Virtualization Tools\\USB Controller"; /* first try the default registry */ result = reg.Open(HKEY_LOCAL_MACHINE, reg_key, KEY_READ); #ifdef _WIN64 if (result != ERROR_SUCCESS) { /* For 64 bit machines, 64 bit SpiceX, look at 32 bit registry too */ LOG_INFO("Looking for USB path in 32 bit registry"); result = reg.Open(HKEY_LOCAL_MACHINE, reg_key, KEY_READ | KEY_WOW64_32KEY); } #else if (result != ERROR_SUCCESS) { /* For 64 bit machines, 32 bit SpiceX, look at 64 bit registry too */ LOG_INFO("Looking for USB path in 64 bit registry"); result = reg.Open(HKEY_LOCAL_MACHINE, reg_key, KEY_READ | KEY_WOW64_64KEY); } #endif if (result != ERROR_SUCCESS) { LOG_INFO("Could not find registry entry for USB Controller"); return HRESULT_FROM_WIN32(result); } WCHAR szCmdLine[MAX_PATH]; ULONG ulCmdLineLen = sizeof(szCmdLine) / sizeof(WCHAR); result = reg.QueryStringValue(L"InstallDir", szCmdLine, &ulCmdLineLen); if (result != ERROR_SUCCESS) { return HRESULT_FROM_WIN32(result); } reg.Close(); // Build command line. std::wostringstream cmdline; cmdline << szCmdLine << " " << m_GuestHostName << L" " << m_UsbListenPort << " -c \\\\.\\pipe\\SpiceForeignMenu-" << ::GetProcessId(m_hClientProcess); if (!m_Language[L"USB"].empty()) { cmdline << " -l \"" << m_Language[L"USB"].c_str() << "\""; } if (!m_UsbFilter.empty()) { cmdline << " -f " << m_UsbFilter.c_str(); } if (m_UsbAutoShare) { cmdline << " -a"; } // Prepare a log file. WCHAR szLogFile[MAX_PATH]; if (::ExpandEnvironmentStrings(L"%TEMP%", szLogFile, sizeof(szLogFile))) { SECURITY_ATTRIBUTES sa; sa.nLength = sizeof(sa); sa.bInheritHandle = TRUE; sa.lpSecurityDescriptor = NULL; // GetCurrentProcessId() return the IE process id and the usbrdrctrl // is not executed. So we use the spice client process id. StringCchPrintf((STRSAFE_LPWSTR)szLogFile, sizeof(szLogFile), L"%s\\usbrdrctrl-%d.log", szLogFile, ::GetProcessId(m_hClientProcess)); m_hUsbCtrlLog = ::CreateFile(szLogFile, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ, &sa, CREATE_ALWAYS, 0, NULL); } // Execute application. PROCESS_INFORMATION pi; STARTUPINFO si; ZeroMemory(&pi, sizeof(pi)); ZeroMemory(&si, sizeof(si)); si.cb = sizeof(si); if (m_hUsbCtrlLog != INVALID_HANDLE_VALUE) { DBG(0, "redirecting usb out/err to logfile '%S'", szLogFile); si.dwFlags |= STARTF_USESTDHANDLES; si.hStdError = m_hUsbCtrlLog; si.hStdOutput = m_hUsbCtrlLog; } else { LOG_INFO("Failed to open log file (%S) -- %lu", szLogFile, ::GetLastError()); } DBG(0, "Running usbrdr (%S)", cmdline.str().c_str()); ::CreateProcess(NULL, const_cast(cmdline.str().c_str()), NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi); m_hUsbCtrlProcess = pi.hProcess; if (m_hUsbCtrlProcess != NULL) { LOG_INFO("usbrdr pid %lu", ::GetProcessId(m_hUsbCtrlProcess)); ::CreateThread(NULL, 0, UsbCtrlWatchDog, this, 0, NULL); } else { LOG_ERROR("Failed to run usbrdr (%S) -- %lu", cmdline.str().c_str(), ::GetLastError()); ::CloseHandle(m_hUsbCtrlLog); m_hUsbCtrlLog = INVALID_HANDLE_VALUE; } return S_OK; } HRESULT COSpiceX::CreateCAFile(OUT LPWSTR szCAFileName, DWORD nSize) { USES_CONVERSION; SECURITY_ATTRIBUTES sa; SECURITY_DESCRIPTOR sd; PACL dacl = NULL; // Allow access only to current user if (DWORD err = get_security_attributes(&sa, &sd, &dacl)) { return HRESULT_FROM_WIN32(err); } if (::ExpandEnvironmentStrings(L"%TEMP%\\truststore.pem", szCAFileName, nSize) == 0) { LocalFree(dacl); return HRESULT_FROM_WIN32(::GetLastError()); } HANDLE hTrustStore = ::CreateFile(szCAFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, &sa); LocalFree(dacl); if (hTrustStore == INVALID_HANDLE_VALUE) { return HRESULT_FROM_WIN32(::GetLastError()); } std::string truststore = W2A(m_TrustStore.c_str()); DWORD dwBytesWritten; if ((::WriteFile(hTrustStore, truststore.c_str(), DWORD(truststore.length() + 1), &dwBytesWritten, NULL) == FALSE) || (dwBytesWritten != (truststore.length() + 1))) { ::CloseHandle(hTrustStore); return HRESULT_FROM_WIN32(::GetLastError()); } ::CloseHandle(hTrustStore); return S_OK; }