Chromium Code Reviews| Index: remoting/host/setup/daemon_controller_delegate_win.cc |
| diff --git a/remoting/host/setup/daemon_controller_delegate_win.cc b/remoting/host/setup/daemon_controller_delegate_win.cc |
| index e17432856f7ad47146196e64166db68f800a3156..68277f0d26cd74b3ed1008290d6cd812d3a0657d 100644 |
| --- a/remoting/host/setup/daemon_controller_delegate_win.cc |
| +++ b/remoting/host/setup/daemon_controller_delegate_win.cc |
| @@ -5,26 +5,24 @@ |
| #include "remoting/host/setup/daemon_controller_delegate_win.h" |
| #include "base/basictypes.h" |
| -#include "base/bind.h" |
| -#include "base/bind_helpers.h" |
| -#include "base/compiler_specific.h" |
| +#include "base/file_version_info.h" |
| +#include "base/files/file_path.h" |
| +#include "base/files/file_util.h" |
| #include "base/json/json_reader.h" |
| #include "base/json/json_writer.h" |
| #include "base/logging.h" |
| +#include "base/process/memory.h" |
| #include "base/strings/string16.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/thread_task_runner_handle.h" |
| -#include "base/time/time.h" |
| -#include "base/timer/timer.h" |
| #include "base/values.h" |
| #include "base/win/scoped_bstr.h" |
| -#include "base/win/scoped_comptr.h" |
| #include "base/win/windows_version.h" |
| #include "remoting/base/scoped_sc_handle_win.h" |
| #include "remoting/host/branding.h" |
| -// chromoting_lib.h contains MIDL-generated declarations. |
| -#include "remoting/host/chromoting_lib.h" |
| +#include "remoting/host/host_config.h" |
| #include "remoting/host/usage_stats_consent.h" |
| +#include "remoting/host/win/security_descriptor.h" |
| using base::win::ScopedBstr; |
| using base::win::ScopedComPtr; |
| @@ -33,26 +31,233 @@ namespace remoting { |
| namespace { |
| -// ProgID of the daemon controller. |
| -const wchar_t kDaemonController[] = |
| - L"ChromotingElevatedController.ElevatedController"; |
| +// The maximum size of the configuration file. "1MB ought to be enough" for any |
| +// reasonable configuration we will ever need. 1MB is low enough to make |
| +// the probability of out of memory situation fairly low. OOM is still possible |
| +// and we will crash if it occurs. |
| +const size_t kMaxConfigFileSize = 1024 * 1024; |
| + |
| +// The host configuration file name. |
| +const base::FilePath::CharType kConfigFileName[] = |
| + FILE_PATH_LITERAL("host.json"); |
| + |
| +// The unprivileged configuration file name. |
| +const base::FilePath::CharType kUnprivilegedConfigFileName[] = |
| + FILE_PATH_LITERAL("host_unprivileged.json"); |
| + |
| +// The extension for the temporary file. |
| +const base::FilePath::CharType kTempFileExtension[] = |
| + FILE_PATH_LITERAL("json~"); |
| + |
| +// The host configuration file security descriptor that enables full access to |
| +// Local System and built-in administrators only. |
| +const char kConfigFileSecurityDescriptor[] = |
| + "O:BAG:BAD:(A;;GA;;;SY)(A;;GA;;;BA)"; |
| + |
| +const char kUnprivilegedConfigFileSecurityDescriptor[] = |
| + "O:BAG:BAD:(A;;GA;;;SY)(A;;GA;;;BA)(A;;GR;;;AU)"; |
| + |
| +// Configuration keys. |
| + |
| +// The configuration keys that cannot be specified in UpdateConfig(). |
| +const char* const kReadonlyKeys[] = { |
| + kHostIdConfigPath, kHostOwnerConfigPath, kHostOwnerEmailConfigPath, |
| + kXmppLoginConfigPath }; |
| + |
| +// The configuration keys whose values may be read by GetConfig(). |
| +const char* const kUnprivilegedConfigKeys[] = { |
| + kHostIdConfigPath, kXmppLoginConfigPath }; |
| + |
| +// Reads and parses the configuration file up to |kMaxConfigFileSize| in |
| +// size. |
| +HRESULT ReadConfig(const base::FilePath& filename, |
|
Sergey Ulanov
2015/01/31 01:06:58
return bool instead of HRESULT.
|
| + scoped_ptr<base::DictionaryValue>* config_out) { |
| + |
| + // Read raw data from the configuration file. |
| + base::win::ScopedHandle file( |
| + CreateFileW(filename.value().c_str(), |
| + GENERIC_READ, |
| + FILE_SHARE_READ | FILE_SHARE_WRITE, |
| + nullptr, |
| + OPEN_EXISTING, |
| + FILE_FLAG_SEQUENTIAL_SCAN, |
| + nullptr)); |
| + |
| + if (!file.IsValid()) { |
| + DWORD error = GetLastError(); |
| + PLOG(ERROR) << "Failed to open '" << filename.value() << "'"; |
| + return HRESULT_FROM_WIN32(error); |
| + } |
| + |
| + scoped_ptr<char[]> buffer(new char[kMaxConfigFileSize]); |
| + DWORD size = kMaxConfigFileSize; |
| + if (!::ReadFile(file.Get(), &buffer[0], size, &size, nullptr)) { |
| + DWORD error = GetLastError(); |
| + PLOG(ERROR) << "Failed to read '" << filename.value() << "'"; |
| + return HRESULT_FROM_WIN32(error); |
| + } |
| + |
| + // Parse the JSON configuration, expecting it to contain a dictionary. |
| + std::string file_content(buffer.get(), size); |
| + scoped_ptr<base::Value> value( |
| + base::JSONReader::Read(file_content, base::JSON_ALLOW_TRAILING_COMMAS)); |
| + |
| + base::DictionaryValue* dictionary; |
| + if (value.get() == nullptr || !value->GetAsDictionary(&dictionary)) { |
| + LOG(ERROR) << "Failed to read '" << filename.value() << "'."; |
| + return E_FAIL; |
| + } |
| + |
| + value.release(); |
| + config_out->reset(dictionary); |
| + return S_OK; |
| +} |
| + |
| +base::FilePath GetTempLocationFor(const base::FilePath& filename) { |
| + return filename.ReplaceExtension(kTempFileExtension); |
| +} |
| + |
| +// Writes a config file to a temporary location. |
| +HRESULT WriteConfigFileToTemp(const base::FilePath& filename, |
| + const char* security_descriptor, |
| + const char* content, |
| + size_t length) { |
| + // Create the security descriptor for the configuration file. |
| + ScopedSd sd = ConvertSddlToSd(security_descriptor); |
| + if (!sd) { |
| + DWORD error = GetLastError(); |
| + PLOG(ERROR) |
| + << "Failed to create a security descriptor for the configuration file"; |
| + return HRESULT_FROM_WIN32(error); |
| + } |
| + |
| + SECURITY_ATTRIBUTES security_attributes = {0}; |
| + security_attributes.nLength = sizeof(security_attributes); |
| + security_attributes.lpSecurityDescriptor = sd.get(); |
| + security_attributes.bInheritHandle = FALSE; |
| + |
| + // Create a temporary file and write configuration to it. |
| + base::FilePath tempname = GetTempLocationFor(filename); |
| + base::win::ScopedHandle file( |
| + CreateFileW(tempname.value().c_str(), |
| + GENERIC_WRITE, |
| + 0, |
| + &security_attributes, |
| + CREATE_ALWAYS, |
| + FILE_FLAG_SEQUENTIAL_SCAN, |
| + nullptr)); |
| + |
| + if (!file.IsValid()) { |
| + DWORD error = GetLastError(); |
| + PLOG(ERROR) << "Failed to create '" << filename.value() << "'"; |
| + return HRESULT_FROM_WIN32(error); |
| + } |
| + |
| + DWORD written; |
| + if (!WriteFile(file.Get(), content, static_cast<DWORD>(length), &written, |
| + nullptr)) { |
| + DWORD error = GetLastError(); |
| + PLOG(ERROR) << "Failed to write to '" << filename.value() << "'"; |
| + return HRESULT_FROM_WIN32(error); |
| + } |
| -// The COM elevation moniker for the Elevated Controller. |
| -const wchar_t kDaemonControllerElevationMoniker[] = |
| - L"Elevation:Administrator!new:" |
| - L"ChromotingElevatedController.ElevatedController"; |
| + return S_OK; |
| +} |
| + |
| +// Moves a config file from its temporary location to its permanent location. |
| +HRESULT MoveConfigFileFromTemp(const base::FilePath& filename) { |
| + // Now that the configuration is stored successfully replace the actual |
| + // configuration file. |
| + base::FilePath tempname = GetTempLocationFor(filename); |
| + if (!MoveFileExW(tempname.value().c_str(), |
| + filename.value().c_str(), |
| + MOVEFILE_REPLACE_EXISTING)) { |
| + DWORD error = GetLastError(); |
| + PLOG(ERROR) << "Failed to rename '" << tempname.value() << "' to '" |
| + << filename.value() << "'"; |
| + return HRESULT_FROM_WIN32(error); |
| + } |
| + |
| + return S_OK; |
| +} |
| + |
| +// Writes the configuration file up to |kMaxConfigFileSize| in size. |
| +HRESULT WriteConfig(const char* content, size_t length) { |
| + if (length > kMaxConfigFileSize) { |
| + return E_FAIL; |
| + } |
| + |
| + // Extract the configuration data that the user will verify. |
| + scoped_ptr<base::Value> config_value(base::JSONReader::Read(content)); |
| + if (!config_value.get()) { |
| + return E_FAIL; |
| + } |
| + base::DictionaryValue* config_dict = nullptr; |
| + if (!config_value->GetAsDictionary(&config_dict)) { |
| + return E_FAIL; |
| + } |
| + std::string email; |
| + if (!config_dict->GetString(kHostOwnerEmailConfigPath, &email)) { |
| + if (!config_dict->GetString(kHostOwnerConfigPath, &email)) { |
| + if (!config_dict->GetString(kXmppLoginConfigPath, &email)) { |
| + return E_FAIL; |
| + } |
| + } |
| + } |
| + std::string host_id, host_secret_hash; |
| + if (!config_dict->GetString(kHostIdConfigPath, &host_id) || |
| + !config_dict->GetString(kHostSecretHashConfigPath, &host_secret_hash)) { |
| + return E_FAIL; |
| + } |
| -// The maximum duration of keeping a reference to a privileged instance of |
| -// the Daemon Controller. This effectively reduces number of UAC prompts a user |
| -// sees. |
| -const int kPrivilegedTimeoutSec = 5 * 60; |
| + // Extract the unprivileged fields from the configuration. |
| + base::DictionaryValue unprivileged_config_dict; |
| + for (int i = 0; i < arraysize(kUnprivilegedConfigKeys); ++i) { |
| + const char* key = kUnprivilegedConfigKeys[i]; |
| + base::string16 value; |
| + if (config_dict->GetString(key, &value)) { |
| + unprivileged_config_dict.SetString(key, value); |
| + } |
| + } |
| + std::string unprivileged_config_str; |
| + base::JSONWriter::Write(&unprivileged_config_dict, &unprivileged_config_str); |
| + |
| + // Write the full configuration file to a temporary location. |
| + base::FilePath full_config_file_path = |
| + remoting::GetConfigDir().Append(kConfigFileName); |
| + HRESULT hr = WriteConfigFileToTemp(full_config_file_path, |
| + kConfigFileSecurityDescriptor, |
| + content, |
| + length); |
| + if (FAILED(hr)) { |
| + return hr; |
| + } |
| -// The maximum duration of keeping a reference to an unprivileged instance of |
| -// the Daemon Controller. This interval should not be too long. If upgrade |
| -// happens while there is a live reference to a Daemon Controller instance |
| -// the old binary still can be used. So dropping the references often makes sure |
| -// that the old binary will go away sooner. |
| -const int kUnprivilegedTimeoutSec = 60; |
| + // Write the unprivileged configuration file to a temporary location. |
| + base::FilePath unprivileged_config_file_path = |
| + remoting::GetConfigDir().Append(kUnprivilegedConfigFileName); |
| + hr = WriteConfigFileToTemp(unprivileged_config_file_path, |
| + kUnprivilegedConfigFileSecurityDescriptor, |
| + unprivileged_config_str.data(), |
| + unprivileged_config_str.size()); |
| + if (FAILED(hr)) { |
| + return hr; |
| + } |
| + |
| + // Move the full configuration file to its permanent location. |
| + hr = MoveConfigFileFromTemp(full_config_file_path); |
| + if (FAILED(hr)) { |
| + return hr; |
| + } |
| + |
| + // Move the unprivileged configuration file to its permanent location. |
| + hr = MoveConfigFileFromTemp(unprivileged_config_file_path); |
| + if (FAILED(hr)) { |
| + return hr; |
| + } |
| + |
| + return S_OK; |
| +} |
| void ConfigToString(const base::DictionaryValue& config, ScopedBstr* out) { |
| std::string config_str; |
| @@ -87,7 +292,7 @@ DaemonController::State ConvertToDaemonState(DWORD service_state) { |
| } |
| } |
| -DWORD OpenService(ScopedScHandle* service_out) { |
| +DWORD OpenService(ScopedScHandle* service_out, DWORD access) { |
| // Open the service and query its current state. |
| ScopedScHandle scmanager( |
| ::OpenSCManagerW(nullptr, SERVICES_ACTIVE_DATABASE, |
| @@ -99,7 +304,7 @@ DWORD OpenService(ScopedScHandle* service_out) { |
| } |
| ScopedScHandle service(::OpenServiceW(scmanager.Get(), kWindowsServiceName, |
| - SERVICE_QUERY_STATUS)); |
| + access)); |
| if (!service.IsValid()) { |
| DWORD error = GetLastError(); |
| if (error != ERROR_SERVICE_DOES_NOT_EXIST) { |
| @@ -131,32 +336,9 @@ void InvokeCompletionCallback( |
| done.Run(HResultToAsyncResult(hr)); |
| } |
| -HWND GetTopLevelWindow(HWND window) { |
| - if (window == nullptr) { |
| - return nullptr; |
| - } |
| - |
| - for (;;) { |
| - LONG style = GetWindowLong(window, GWL_STYLE); |
| - if ((style & WS_OVERLAPPEDWINDOW) == WS_OVERLAPPEDWINDOW || |
| - (style & WS_POPUP) == WS_POPUP) { |
| - return window; |
| - } |
| - |
| - HWND parent = GetAncestor(window, GA_PARENT); |
| - if (parent == nullptr) { |
| - return window; |
| - } |
| - |
| - window = parent; |
| - } |
| -} |
| - |
| } // namespace |
| -DaemonControllerDelegateWin::DaemonControllerDelegateWin() |
| - : control_is_elevated_(false), |
| - window_handle_(nullptr) { |
| +DaemonControllerDelegateWin::DaemonControllerDelegateWin() { |
| } |
| DaemonControllerDelegateWin::~DaemonControllerDelegateWin() { |
| @@ -169,7 +351,7 @@ DaemonController::State DaemonControllerDelegateWin::GetState() { |
| // TODO(alexeypa): Make the thread alertable, so we can switch to APC |
| // notifications rather than polling. |
| ScopedScHandle service; |
| - DWORD error = OpenService(&service); |
| + DWORD error = OpenService(&service, SERVICE_QUERY_STATUS); |
| switch (error) { |
| case ERROR_SUCCESS: { |
| @@ -191,14 +373,9 @@ DaemonController::State DaemonControllerDelegateWin::GetState() { |
| } |
| scoped_ptr<base::DictionaryValue> DaemonControllerDelegateWin::GetConfig() { |
| - // Configure and start the Daemon Controller if it is installed already. |
| - HRESULT hr = ActivateController(); |
| - if (FAILED(hr)) |
| - return nullptr; |
| - |
| // Get the host configuration. |
| ScopedBstr host_config; |
| - hr = control_->GetConfig(host_config.Receive()); |
| + HRESULT hr = DoGetConfig(host_config.Receive()); |
| if (FAILED(hr)) |
| return nullptr; |
| @@ -218,12 +395,6 @@ scoped_ptr<base::DictionaryValue> DaemonControllerDelegateWin::GetConfig() { |
| void DaemonControllerDelegateWin::UpdateConfig( |
| scoped_ptr<base::DictionaryValue> config, |
| const DaemonController::CompletionCallback& done) { |
| - HRESULT hr = ActivateElevatedController(); |
| - if (FAILED(hr)) { |
| - InvokeCompletionCallback(done, hr); |
| - return; |
| - } |
| - |
| // Update the configuration. |
| ScopedBstr config_str(nullptr); |
| ConfigToString(*config, &config_str); |
| @@ -232,40 +403,24 @@ void DaemonControllerDelegateWin::UpdateConfig( |
| return; |
| } |
| - // Make sure that the PIN confirmation dialog is focused properly. |
| - hr = control_->SetOwnerWindow( |
| - reinterpret_cast<LONG_PTR>(GetTopLevelWindow(window_handle_))); |
| - if (FAILED(hr)) { |
| - InvokeCompletionCallback(done, hr); |
| - return; |
| - } |
| - |
| - hr = control_->UpdateConfig(config_str); |
| + HRESULT hr = DoUpdateConfig(config_str); |
| InvokeCompletionCallback(done, hr); |
| } |
| void DaemonControllerDelegateWin::Stop( |
| const DaemonController::CompletionCallback& done) { |
| - HRESULT hr = ActivateElevatedController(); |
| - if (SUCCEEDED(hr)) |
| - hr = control_->StopDaemon(); |
| + HRESULT hr = StopDaemon(); |
| InvokeCompletionCallback(done, hr); |
| } |
| void DaemonControllerDelegateWin::SetWindow(void* window_handle) { |
| - window_handle_ = reinterpret_cast<HWND>(window_handle); |
| } |
| std::string DaemonControllerDelegateWin::GetVersion() { |
| - // Configure and start the Daemon Controller if it is installed already. |
| - HRESULT hr = ActivateController(); |
| - if (FAILED(hr)) |
| - return std::string(); |
| - |
| // Get the version string. |
| ScopedBstr version; |
| - hr = control_->GetVersion(version.Receive()); |
| + HRESULT hr = DoGetVersion(version.Receive()); |
| if (FAILED(hr)) |
| return std::string(); |
| @@ -280,23 +435,10 @@ DaemonControllerDelegateWin::GetUsageStatsConsent() { |
| consent.allowed = false; |
| consent.set_by_policy = false; |
| - // Activate the Daemon Controller and see if it supports |IDaemonControl2|. |
| - HRESULT hr = ActivateController(); |
| - if (FAILED(hr)) { |
| - // The host is not installed yet. Assume that the user didn't consent to |
| - // collecting crash dumps. |
| - return consent; |
| - } |
| - |
| - if (control2_.get() == nullptr) { |
| - // The host is installed and does not support crash dump reporting. |
| - return consent; |
| - } |
| - |
| // Get the recorded user's consent. |
| BOOL allowed; |
| BOOL set_by_policy; |
| - hr = control2_->GetUsageStatsConsent(&allowed, &set_by_policy); |
| + HRESULT hr = DoGetUsageStatsConsent(&allowed, &set_by_policy); |
| if (FAILED(hr)) { |
| // If the user's consent is not recorded yet, assume that the user didn't |
| // consent to collecting crash dumps. |
| @@ -308,126 +450,227 @@ DaemonControllerDelegateWin::GetUsageStatsConsent() { |
| return consent; |
| } |
| -HRESULT DaemonControllerDelegateWin::ActivateController() { |
| - if (!control_.get()) { |
| - CLSID class_id; |
| - HRESULT hr = CLSIDFromProgID(kDaemonController, &class_id); |
| - if (FAILED(hr)) { |
| - return hr; |
| - } |
| - |
| - hr = CoCreateInstance(class_id, nullptr, CLSCTX_LOCAL_SERVER, |
| - IID_IDaemonControl, control_.ReceiveVoid()); |
| - if (FAILED(hr)) { |
| - return hr; |
| - } |
| +void DaemonControllerDelegateWin::SetConfigAndStart( |
| + scoped_ptr<base::DictionaryValue> config, |
| + bool consent, |
| + const DaemonController::CompletionCallback& done) { |
| + // Record the user's consent. |
| + HRESULT hr = SetUsageStatsConsent(consent); |
| + if (FAILED(hr)) { |
| + InvokeCompletionCallback(done, hr); |
| + return; |
| + } |
| - // Ignore the error. IID_IDaemonControl2 is optional. |
| - control_.QueryInterface(IID_IDaemonControl2, control2_.ReceiveVoid()); |
| + // Set the configuration. |
| + ScopedBstr config_str(nullptr); |
| + ConfigToString(*config, &config_str); |
| + if (config_str == nullptr) { |
| + InvokeCompletionCallback(done, E_OUTOFMEMORY); |
| + return; |
| + } |
| - // Release |control_| upon expiration of the timeout. |
| - release_timer_.reset(new base::OneShotTimer<DaemonControllerDelegateWin>()); |
| - release_timer_->Start(FROM_HERE, |
| - base::TimeDelta::FromSeconds(kUnprivilegedTimeoutSec), |
| - this, |
| - &DaemonControllerDelegateWin::ReleaseController); |
| + hr = SetConfig(config_str); |
| + if (FAILED(hr)) { |
| + InvokeCompletionCallback(done, hr); |
| + return; |
| } |
| - return S_OK; |
| + // Start daemon. |
| + hr = StartDaemon(); |
| + InvokeCompletionCallback(done, hr); |
| } |
| -HRESULT DaemonControllerDelegateWin::ActivateElevatedController() { |
| - // The COM elevation is supported on Vista and above. |
| - if (base::win::GetVersion() < base::win::VERSION_VISTA) |
| - return ActivateController(); |
| - |
| - // Release an unprivileged instance of the daemon controller if any. |
| - if (!control_is_elevated_) |
| - ReleaseController(); |
| - |
| - if (!control_.get()) { |
| - BIND_OPTS3 bind_options; |
| - memset(&bind_options, 0, sizeof(bind_options)); |
| - bind_options.cbStruct = sizeof(bind_options); |
| - bind_options.hwnd = GetTopLevelWindow(window_handle_); |
| - bind_options.dwClassContext = CLSCTX_LOCAL_SERVER; |
| - |
| - HRESULT hr = ::CoGetObject( |
| - kDaemonControllerElevationMoniker, |
| - &bind_options, |
| - IID_IDaemonControl, |
| - control_.ReceiveVoid()); |
| - if (FAILED(hr)) { |
| - return hr; |
| - } |
| +HRESULT DaemonControllerDelegateWin::DoGetConfig(BSTR* config_out) { |
|
Sergey Ulanov
2015/01/31 01:06:58
Return bool instead of HRESULT here? The caller on
Sergey Ulanov
2015/01/31 01:06:58
You don't really need this function. Just call Rea
|
| + base::FilePath config_dir = remoting::GetConfigDir(); |
| - // Ignore the error. IID_IDaemonControl2 is optional. |
| - control_.QueryInterface(IID_IDaemonControl2, control2_.ReceiveVoid()); |
| + // Read the unprivileged part of host configuration. |
| + scoped_ptr<base::DictionaryValue> config; |
| + HRESULT hr = ReadConfig(config_dir.Append(kUnprivilegedConfigFileName), |
| + &config); |
| + if (FAILED(hr)) { |
| + return hr; |
| + } |
| - // Note that we hold a reference to an elevated instance now. |
| - control_is_elevated_ = true; |
| + // Convert the config back to a string and return it to the caller. |
| + std::string file_content; |
| + base::JSONWriter::Write(config.get(), &file_content); |
| - // Release |control_| upon expiration of the timeout. |
| - release_timer_.reset(new base::OneShotTimer<DaemonControllerDelegateWin>()); |
| - release_timer_->Start(FROM_HERE, |
| - base::TimeDelta::FromSeconds(kPrivilegedTimeoutSec), |
| - this, |
| - &DaemonControllerDelegateWin::ReleaseController); |
| + *config_out = ::SysAllocString(base::UTF8ToUTF16(file_content).c_str()); |
| + if (config_out == nullptr) { |
| + return E_OUTOFMEMORY; |
| } |
| return S_OK; |
| } |
| -void DaemonControllerDelegateWin::ReleaseController() { |
| - control_.Release(); |
| - control2_.Release(); |
| - release_timer_.reset(); |
| - control_is_elevated_ = false; |
| +HRESULT DaemonControllerDelegateWin::DoGetVersion(BSTR* version_out) { |
| + // Report the product version number of the daemon controller binary as |
| + // the host version. |
| + HMODULE binary = base::GetModuleFromAddress( |
| + reinterpret_cast<void*>(&ReadConfig)); |
| + scoped_ptr<FileVersionInfo> version_info( |
| + FileVersionInfo::CreateFileVersionInfoForModule(binary)); |
| + |
| + base::string16 version; |
| + if (version_info.get()) { |
| + version = version_info->product_version(); |
| + } |
| + |
| + *version_out = ::SysAllocString(version.c_str()); |
| + if (version_out == nullptr) { |
| + return E_OUTOFMEMORY; |
| + } |
| + |
| + return S_OK; |
| } |
| -void DaemonControllerDelegateWin::SetConfigAndStart( |
| - scoped_ptr<base::DictionaryValue> config, |
| - bool consent, |
| - const DaemonController::CompletionCallback& done) { |
| - HRESULT hr = ActivateElevatedController(); |
| +HRESULT DaemonControllerDelegateWin::SetConfig(BSTR config) { |
| + // Determine the config directory path and create it if necessary. |
| + base::FilePath config_dir = remoting::GetConfigDir(); |
| + if (!base::CreateDirectory(config_dir)) { |
| + return HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED); |
| + } |
| + |
| + std::string file_content = base::UTF16ToUTF8( |
| + base::string16(static_cast<base::char16*>(config), ::SysStringLen(config))); |
| + |
| + return WriteConfig(file_content.c_str(), file_content.size()); |
| +} |
| + |
| +HRESULT DaemonControllerDelegateWin::StartDaemon() { |
| + ScopedScHandle service; |
| + DWORD access = SERVICE_CHANGE_CONFIG | SERVICE_QUERY_STATUS | |
| + SERVICE_START | SERVICE_STOP; |
| + HRESULT hr = OpenService(&service, access); |
| if (FAILED(hr)) { |
| - InvokeCompletionCallback(done, hr); |
| - return; |
| + return hr; |
| } |
| - // Record the user's consent. |
| - if (control2_.get()) { |
| - hr = control2_->SetUsageStatsConsent(consent); |
| - if (FAILED(hr)) { |
| - InvokeCompletionCallback(done, hr); |
| - return; |
| - } |
| + // Change the service start type to 'auto'. |
| + if (!::ChangeServiceConfigW(service.Get(), |
| + SERVICE_NO_CHANGE, |
| + SERVICE_AUTO_START, |
| + SERVICE_NO_CHANGE, |
| + nullptr, |
| + nullptr, |
| + nullptr, |
| + nullptr, |
| + nullptr, |
| + nullptr, |
| + nullptr)) { |
| + DWORD error = GetLastError(); |
| + PLOG(ERROR) << "Failed to change the '" << kWindowsServiceName |
| + << "'service start type to 'auto'"; |
| + return HRESULT_FROM_WIN32(error); |
| } |
| - // Set the configuration. |
| - ScopedBstr config_str(nullptr); |
| - ConfigToString(*config, &config_str); |
| - if (config_str == nullptr) { |
| - InvokeCompletionCallback(done, E_OUTOFMEMORY); |
| - return; |
| + // Start the service. |
| + if (!StartService(service.Get(), 0, nullptr)) { |
| + DWORD error = GetLastError(); |
| + if (error != ERROR_SERVICE_ALREADY_RUNNING) { |
| + PLOG(ERROR) << "Failed to start the '" << kWindowsServiceName |
| + << "'service"; |
| + |
| + return HRESULT_FROM_WIN32(error); |
| + } |
| } |
| - hr = control_->SetOwnerWindow( |
| - reinterpret_cast<LONG_PTR>(GetTopLevelWindow(window_handle_))); |
| + return S_OK; |
| +} |
| + |
| +HRESULT DaemonControllerDelegateWin::StopDaemon() { |
| + ScopedScHandle service; |
| + DWORD access = SERVICE_CHANGE_CONFIG | SERVICE_QUERY_STATUS | |
| + SERVICE_START | SERVICE_STOP; |
| + HRESULT hr = OpenService(&service, access); |
| if (FAILED(hr)) { |
| - InvokeCompletionCallback(done, hr); |
| - return; |
| + return hr; |
| } |
| - hr = control_->SetConfig(config_str); |
| + // Change the service start type to 'manual'. |
| + if (!::ChangeServiceConfigW(service.Get(), |
| + SERVICE_NO_CHANGE, |
| + SERVICE_DEMAND_START, |
| + SERVICE_NO_CHANGE, |
| + nullptr, |
| + nullptr, |
| + nullptr, |
| + nullptr, |
| + nullptr, |
| + nullptr, |
| + nullptr)) { |
| + DWORD error = GetLastError(); |
| + PLOG(ERROR) << "Failed to change the '" << kWindowsServiceName |
| + << "'service start type to 'manual'"; |
| + return HRESULT_FROM_WIN32(error); |
| + } |
| + |
| + // Stop the service. |
| + SERVICE_STATUS status; |
| + if (!ControlService(service.Get(), SERVICE_CONTROL_STOP, &status)) { |
| + DWORD error = GetLastError(); |
| + if (error != ERROR_SERVICE_NOT_ACTIVE) { |
| + PLOG(ERROR) << "Failed to stop the '" << kWindowsServiceName |
| + << "'service"; |
| + return HRESULT_FROM_WIN32(error); |
| + } |
| + } |
| + |
| + return S_OK; |
| +} |
| + |
| +HRESULT DaemonControllerDelegateWin::DoUpdateConfig(BSTR config) { |
| + // Parse the config. |
| + std::string config_str = base::UTF16ToUTF8( |
| + base::string16(static_cast<base::char16*>(config), ::SysStringLen(config))); |
| + scoped_ptr<base::Value> config_value(base::JSONReader::Read(config_str)); |
| + if (!config_value.get()) { |
| + return E_FAIL; |
| + } |
| + base::DictionaryValue* config_dict = nullptr; |
| + if (!config_value->GetAsDictionary(&config_dict)) { |
| + return E_FAIL; |
| + } |
| + // Check for bad keys. |
| + for (int i = 0; i < arraysize(kReadonlyKeys); ++i) { |
| + if (config_dict->HasKey(kReadonlyKeys[i])) { |
| + return HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED); |
| + } |
| + } |
| + // Get the old config. |
| + base::FilePath config_dir = remoting::GetConfigDir(); |
| + scoped_ptr<base::DictionaryValue> config_old; |
| + HRESULT hr = ReadConfig(config_dir.Append(kConfigFileName), &config_old); |
| if (FAILED(hr)) { |
| - InvokeCompletionCallback(done, hr); |
| - return; |
| + return hr; |
| } |
| + // Merge items from the given config into the old config. |
| + config_old->MergeDictionary(config_dict); |
| + // Write the updated config. |
| + std::string config_updated_str; |
| + base::JSONWriter::Write(config_old.get(), &config_updated_str); |
| + return WriteConfig(config_updated_str.c_str(), config_updated_str.size()); |
| +} |
| - // Start daemon. |
| - hr = control_->StartDaemon(); |
| - InvokeCompletionCallback(done, hr); |
| +HRESULT DaemonControllerDelegateWin::DoGetUsageStatsConsent( |
| + BOOL* allowed, BOOL* set_by_policy) { |
| + bool local_allowed; |
| + bool local_set_by_policy; |
| + if (remoting::GetUsageStatsConsent(&local_allowed, &local_set_by_policy)) { |
| + *allowed = local_allowed; |
| + *set_by_policy = local_set_by_policy; |
| + return S_OK; |
| + } else { |
| + return E_FAIL; |
| + } |
| +} |
| + |
| +HRESULT DaemonControllerDelegateWin::SetUsageStatsConsent(BOOL allowed) { |
| + if (remoting::SetUsageStatsConsent(!!allowed)) { |
| + return S_OK; |
| + } else { |
| + return E_FAIL; |
| + } |
| } |
| scoped_refptr<DaemonController> DaemonController::Create() { |