mojo: Fix map of booleans for python bindings.
[chromium-blink-merge.git] / remoting / host / win / elevated_controller.cc
blob2336df72ab7bd263feeb2881b5713938b629b8fb
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "remoting/host/win/elevated_controller.h"
7 #include "base/file_version_info.h"
8 #include "base/files/file_util.h"
9 #include "base/json/json_reader.h"
10 #include "base/json/json_writer.h"
11 #include "base/logging.h"
12 #include "base/memory/scoped_ptr.h"
13 #include "base/path_service.h"
14 #include "base/process/memory.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "base/values.h"
17 #include "base/win/scoped_handle.h"
18 #include "remoting/host/branding.h"
19 #include "remoting/host/host_config.h"
20 #include "remoting/host/usage_stats_consent.h"
21 #include "remoting/host/verify_config_window_win.h"
22 #include "remoting/host/win/core_resource.h"
23 #include "remoting/host/win/security_descriptor.h"
25 namespace remoting {
27 namespace {
29 // The maximum size of the configuration file. "1MB ought to be enough" for any
30 // reasonable configuration we will ever need. 1MB is low enough to make
31 // the probability of out of memory situation fairly low. OOM is still possible
32 // and we will crash if it occurs.
33 const size_t kMaxConfigFileSize = 1024 * 1024;
35 // The host configuration file name.
36 const base::FilePath::CharType kConfigFileName[] = FILE_PATH_LITERAL("host.json");
38 // The unprivileged configuration file name.
39 const base::FilePath::CharType kUnprivilegedConfigFileName[] =
40 FILE_PATH_LITERAL("host_unprivileged.json");
42 // The extension for the temporary file.
43 const base::FilePath::CharType kTempFileExtension[] = FILE_PATH_LITERAL("json~");
45 // The host configuration file security descriptor that enables full access to
46 // Local System and built-in administrators only.
47 const char kConfigFileSecurityDescriptor[] =
48 "O:BAG:BAD:(A;;GA;;;SY)(A;;GA;;;BA)";
50 const char kUnprivilegedConfigFileSecurityDescriptor[] =
51 "O:BAG:BAD:(A;;GA;;;SY)(A;;GA;;;BA)(A;;GR;;;AU)";
53 // Configuration keys.
55 // The configuration keys that cannot be specified in UpdateConfig().
56 const char* const kReadonlyKeys[] = {
57 kHostIdConfigPath, kHostOwnerConfigPath, kHostOwnerEmailConfigPath,
58 kXmppLoginConfigPath };
60 // The configuration keys whose values may be read by GetConfig().
61 const char* const kUnprivilegedConfigKeys[] = {
62 kHostIdConfigPath, kXmppLoginConfigPath };
64 // Determines if the client runs in the security context that allows performing
65 // administrative tasks (i.e. the user belongs to the adminstrators group and
66 // the client runs elevated).
67 bool IsClientAdmin() {
68 HRESULT hr = CoImpersonateClient();
69 if (FAILED(hr)) {
70 return false;
73 SID_IDENTIFIER_AUTHORITY nt_authority = SECURITY_NT_AUTHORITY;
74 PSID administrators_group = NULL;
75 BOOL result = AllocateAndInitializeSid(&nt_authority,
77 SECURITY_BUILTIN_DOMAIN_RID,
78 DOMAIN_ALIAS_RID_ADMINS,
79 0, 0, 0, 0, 0, 0,
80 &administrators_group);
81 if (result) {
82 if (!CheckTokenMembership(NULL, administrators_group, &result)) {
83 result = false;
85 FreeSid(administrators_group);
88 hr = CoRevertToSelf();
89 CHECK(SUCCEEDED(hr));
91 return !!result;
94 // Reads and parses the configuration file up to |kMaxConfigFileSize| in
95 // size.
96 HRESULT ReadConfig(const base::FilePath& filename,
97 scoped_ptr<base::DictionaryValue>* config_out) {
99 // Read raw data from the configuration file.
100 base::win::ScopedHandle file(
101 CreateFileW(filename.value().c_str(),
102 GENERIC_READ,
103 FILE_SHARE_READ | FILE_SHARE_WRITE,
104 NULL,
105 OPEN_EXISTING,
106 FILE_FLAG_SEQUENTIAL_SCAN,
107 NULL));
109 if (!file.IsValid()) {
110 DWORD error = GetLastError();
111 PLOG(ERROR) << "Failed to open '" << filename.value() << "'";
112 return HRESULT_FROM_WIN32(error);
115 scoped_ptr<char[]> buffer(new char[kMaxConfigFileSize]);
116 DWORD size = kMaxConfigFileSize;
117 if (!::ReadFile(file.Get(), &buffer[0], size, &size, NULL)) {
118 DWORD error = GetLastError();
119 PLOG(ERROR) << "Failed to read '" << filename.value() << "'";
120 return HRESULT_FROM_WIN32(error);
123 // Parse the JSON configuration, expecting it to contain a dictionary.
124 std::string file_content(buffer.get(), size);
125 scoped_ptr<base::Value> value(
126 base::JSONReader::Read(file_content, base::JSON_ALLOW_TRAILING_COMMAS));
128 base::DictionaryValue* dictionary;
129 if (value.get() == NULL || !value->GetAsDictionary(&dictionary)) {
130 LOG(ERROR) << "Failed to read '" << filename.value() << "'.";
131 return E_FAIL;
134 value.release();
135 config_out->reset(dictionary);
136 return S_OK;
139 base::FilePath GetTempLocationFor(const base::FilePath& filename) {
140 return filename.ReplaceExtension(kTempFileExtension);
143 // Writes a config file to a temporary location.
144 HRESULT WriteConfigFileToTemp(const base::FilePath& filename,
145 const char* security_descriptor,
146 const char* content,
147 size_t length) {
148 // Create the security descriptor for the configuration file.
149 ScopedSd sd = ConvertSddlToSd(security_descriptor);
150 if (!sd) {
151 DWORD error = GetLastError();
152 PLOG(ERROR)
153 << "Failed to create a security descriptor for the configuration file";
154 return HRESULT_FROM_WIN32(error);
157 SECURITY_ATTRIBUTES security_attributes = {0};
158 security_attributes.nLength = sizeof(security_attributes);
159 security_attributes.lpSecurityDescriptor = sd.get();
160 security_attributes.bInheritHandle = FALSE;
162 // Create a temporary file and write configuration to it.
163 base::FilePath tempname = GetTempLocationFor(filename);
164 base::win::ScopedHandle file(
165 CreateFileW(tempname.value().c_str(),
166 GENERIC_WRITE,
168 &security_attributes,
169 CREATE_ALWAYS,
170 FILE_FLAG_SEQUENTIAL_SCAN,
171 NULL));
173 if (!file.IsValid()) {
174 DWORD error = GetLastError();
175 PLOG(ERROR) << "Failed to create '" << filename.value() << "'";
176 return HRESULT_FROM_WIN32(error);
179 DWORD written;
180 if (!WriteFile(file.Get(), content, static_cast<DWORD>(length), &written,
181 NULL)) {
182 DWORD error = GetLastError();
183 PLOG(ERROR) << "Failed to write to '" << filename.value() << "'";
184 return HRESULT_FROM_WIN32(error);
187 return S_OK;
190 // Moves a config file from its temporary location to its permanent location.
191 HRESULT MoveConfigFileFromTemp(const base::FilePath& filename) {
192 // Now that the configuration is stored successfully replace the actual
193 // configuration file.
194 base::FilePath tempname = GetTempLocationFor(filename);
195 if (!MoveFileExW(tempname.value().c_str(),
196 filename.value().c_str(),
197 MOVEFILE_REPLACE_EXISTING)) {
198 DWORD error = GetLastError();
199 PLOG(ERROR) << "Failed to rename '" << tempname.value() << "' to '"
200 << filename.value() << "'";
201 return HRESULT_FROM_WIN32(error);
204 return S_OK;
207 // Writes the configuration file up to |kMaxConfigFileSize| in size.
208 HRESULT WriteConfig(const char* content, size_t length, HWND owner_window) {
209 if (length > kMaxConfigFileSize) {
210 return E_FAIL;
213 // Extract the configuration data that the user will verify.
214 scoped_ptr<base::Value> config_value(base::JSONReader::Read(content));
215 if (!config_value.get()) {
216 return E_FAIL;
218 base::DictionaryValue* config_dict = NULL;
219 if (!config_value->GetAsDictionary(&config_dict)) {
220 return E_FAIL;
222 std::string email;
223 if (!config_dict->GetString(kHostOwnerEmailConfigPath, &email)) {
224 if (!config_dict->GetString(kHostOwnerConfigPath, &email)) {
225 if (!config_dict->GetString(kXmppLoginConfigPath, &email)) {
226 return E_FAIL;
230 std::string host_id, host_secret_hash;
231 if (!config_dict->GetString(kHostIdConfigPath, &host_id) ||
232 !config_dict->GetString(kHostSecretHashConfigPath, &host_secret_hash)) {
233 return E_FAIL;
236 // Ask the user to verify the configuration (unless the client is admin
237 // already).
238 if (!IsClientAdmin()) {
239 remoting::VerifyConfigWindowWin verify_win(email, host_id,
240 host_secret_hash);
241 DWORD error = verify_win.DoModal(owner_window);
242 if (error != ERROR_SUCCESS) {
243 return HRESULT_FROM_WIN32(error);
247 // Extract the unprivileged fields from the configuration.
248 base::DictionaryValue unprivileged_config_dict;
249 for (int i = 0; i < arraysize(kUnprivilegedConfigKeys); ++i) {
250 const char* key = kUnprivilegedConfigKeys[i];
251 base::string16 value;
252 if (config_dict->GetString(key, &value)) {
253 unprivileged_config_dict.SetString(key, value);
256 std::string unprivileged_config_str;
257 base::JSONWriter::Write(&unprivileged_config_dict, &unprivileged_config_str);
259 // Write the full configuration file to a temporary location.
260 base::FilePath full_config_file_path =
261 remoting::GetConfigDir().Append(kConfigFileName);
262 HRESULT hr = WriteConfigFileToTemp(full_config_file_path,
263 kConfigFileSecurityDescriptor,
264 content,
265 length);
266 if (FAILED(hr)) {
267 return hr;
270 // Write the unprivileged configuration file to a temporary location.
271 base::FilePath unprivileged_config_file_path =
272 remoting::GetConfigDir().Append(kUnprivilegedConfigFileName);
273 hr = WriteConfigFileToTemp(unprivileged_config_file_path,
274 kUnprivilegedConfigFileSecurityDescriptor,
275 unprivileged_config_str.data(),
276 unprivileged_config_str.size());
277 if (FAILED(hr)) {
278 return hr;
281 // Move the full configuration file to its permanent location.
282 hr = MoveConfigFileFromTemp(full_config_file_path);
283 if (FAILED(hr)) {
284 return hr;
287 // Move the unprivileged configuration file to its permanent location.
288 hr = MoveConfigFileFromTemp(unprivileged_config_file_path);
289 if (FAILED(hr)) {
290 return hr;
293 return S_OK;
296 } // namespace
298 ElevatedController::ElevatedController() : owner_window_(NULL) {
301 HRESULT ElevatedController::FinalConstruct() {
302 return S_OK;
305 void ElevatedController::FinalRelease() {
308 STDMETHODIMP ElevatedController::GetConfig(BSTR* config_out) {
309 base::FilePath config_dir = remoting::GetConfigDir();
311 // Read the unprivileged part of host configuration.
312 scoped_ptr<base::DictionaryValue> config;
313 HRESULT hr = ReadConfig(config_dir.Append(kUnprivilegedConfigFileName),
314 &config);
315 if (FAILED(hr)) {
316 return hr;
319 // Convert the config back to a string and return it to the caller.
320 std::string file_content;
321 base::JSONWriter::Write(config.get(), &file_content);
323 *config_out = ::SysAllocString(base::UTF8ToUTF16(file_content).c_str());
324 if (config_out == NULL) {
325 return E_OUTOFMEMORY;
328 return S_OK;
331 STDMETHODIMP ElevatedController::GetVersion(BSTR* version_out) {
332 // Report the product version number of the daemon controller binary as
333 // the host version.
334 HMODULE binary = base::GetModuleFromAddress(
335 reinterpret_cast<void*>(&ReadConfig));
336 scoped_ptr<FileVersionInfo> version_info(
337 FileVersionInfo::CreateFileVersionInfoForModule(binary));
339 base::string16 version;
340 if (version_info.get()) {
341 version = version_info->product_version();
344 *version_out = ::SysAllocString(version.c_str());
345 if (version_out == NULL) {
346 return E_OUTOFMEMORY;
349 return S_OK;
352 STDMETHODIMP ElevatedController::SetConfig(BSTR config) {
353 // Determine the config directory path and create it if necessary.
354 base::FilePath config_dir = remoting::GetConfigDir();
355 if (!base::CreateDirectory(config_dir)) {
356 return HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED);
359 std::string file_content = base::UTF16ToUTF8(
360 base::string16(static_cast<base::char16*>(config), ::SysStringLen(config)));
362 return WriteConfig(file_content.c_str(), file_content.size(), owner_window_);
365 STDMETHODIMP ElevatedController::SetOwnerWindow(LONG_PTR window_handle) {
366 owner_window_ = reinterpret_cast<HWND>(window_handle);
367 return S_OK;
370 STDMETHODIMP ElevatedController::StartDaemon() {
371 ScopedScHandle service;
372 HRESULT hr = OpenService(&service);
373 if (FAILED(hr)) {
374 return hr;
377 // Change the service start type to 'auto'.
378 if (!::ChangeServiceConfigW(service.Get(),
379 SERVICE_NO_CHANGE,
380 SERVICE_AUTO_START,
381 SERVICE_NO_CHANGE,
382 NULL,
383 NULL,
384 NULL,
385 NULL,
386 NULL,
387 NULL,
388 NULL)) {
389 DWORD error = GetLastError();
390 PLOG(ERROR) << "Failed to change the '" << kWindowsServiceName
391 << "'service start type to 'auto'";
392 return HRESULT_FROM_WIN32(error);
395 // Start the service.
396 if (!StartService(service.Get(), 0, NULL)) {
397 DWORD error = GetLastError();
398 if (error != ERROR_SERVICE_ALREADY_RUNNING) {
399 PLOG(ERROR) << "Failed to start the '" << kWindowsServiceName
400 << "'service";
402 return HRESULT_FROM_WIN32(error);
406 return S_OK;
409 STDMETHODIMP ElevatedController::StopDaemon() {
410 ScopedScHandle service;
411 HRESULT hr = OpenService(&service);
412 if (FAILED(hr)) {
413 return hr;
416 // Change the service start type to 'manual'.
417 if (!::ChangeServiceConfigW(service.Get(),
418 SERVICE_NO_CHANGE,
419 SERVICE_DEMAND_START,
420 SERVICE_NO_CHANGE,
421 NULL,
422 NULL,
423 NULL,
424 NULL,
425 NULL,
426 NULL,
427 NULL)) {
428 DWORD error = GetLastError();
429 PLOG(ERROR) << "Failed to change the '" << kWindowsServiceName
430 << "'service start type to 'manual'";
431 return HRESULT_FROM_WIN32(error);
434 // Stop the service.
435 SERVICE_STATUS status;
436 if (!ControlService(service.Get(), SERVICE_CONTROL_STOP, &status)) {
437 DWORD error = GetLastError();
438 if (error != ERROR_SERVICE_NOT_ACTIVE) {
439 PLOG(ERROR) << "Failed to stop the '" << kWindowsServiceName
440 << "'service";
441 return HRESULT_FROM_WIN32(error);
445 return S_OK;
448 STDMETHODIMP ElevatedController::UpdateConfig(BSTR config) {
449 // Parse the config.
450 std::string config_str = base::UTF16ToUTF8(
451 base::string16(static_cast<base::char16*>(config), ::SysStringLen(config)));
452 scoped_ptr<base::Value> config_value(base::JSONReader::Read(config_str));
453 if (!config_value.get()) {
454 return E_FAIL;
456 base::DictionaryValue* config_dict = NULL;
457 if (!config_value->GetAsDictionary(&config_dict)) {
458 return E_FAIL;
460 // Check for bad keys.
461 for (int i = 0; i < arraysize(kReadonlyKeys); ++i) {
462 if (config_dict->HasKey(kReadonlyKeys[i])) {
463 return HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED);
466 // Get the old config.
467 base::FilePath config_dir = remoting::GetConfigDir();
468 scoped_ptr<base::DictionaryValue> config_old;
469 HRESULT hr = ReadConfig(config_dir.Append(kConfigFileName), &config_old);
470 if (FAILED(hr)) {
471 return hr;
473 // Merge items from the given config into the old config.
474 config_old->MergeDictionary(config_dict);
475 // Write the updated config.
476 std::string config_updated_str;
477 base::JSONWriter::Write(config_old.get(), &config_updated_str);
478 return WriteConfig(config_updated_str.c_str(), config_updated_str.size(),
479 owner_window_);
482 STDMETHODIMP ElevatedController::GetUsageStatsConsent(BOOL* allowed,
483 BOOL* set_by_policy) {
484 bool local_allowed;
485 bool local_set_by_policy;
486 if (remoting::GetUsageStatsConsent(&local_allowed, &local_set_by_policy)) {
487 *allowed = local_allowed;
488 *set_by_policy = local_set_by_policy;
489 return S_OK;
490 } else {
491 return E_FAIL;
495 STDMETHODIMP ElevatedController::SetUsageStatsConsent(BOOL allowed) {
496 if (remoting::SetUsageStatsConsent(!!allowed)) {
497 return S_OK;
498 } else {
499 return E_FAIL;
503 HRESULT ElevatedController::OpenService(ScopedScHandle* service_out) {
504 DWORD error;
506 ScopedScHandle scmanager(
507 ::OpenSCManagerW(NULL, SERVICES_ACTIVE_DATABASE,
508 SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE));
509 if (!scmanager.IsValid()) {
510 error = GetLastError();
511 PLOG(ERROR) << "Failed to connect to the service control manager";
513 return HRESULT_FROM_WIN32(error);
516 DWORD desired_access = SERVICE_CHANGE_CONFIG | SERVICE_QUERY_STATUS |
517 SERVICE_START | SERVICE_STOP;
518 ScopedScHandle service(
519 ::OpenServiceW(scmanager.Get(), kWindowsServiceName, desired_access));
520 if (!service.IsValid()) {
521 error = GetLastError();
522 PLOG(ERROR) << "Failed to open to the '" << kWindowsServiceName
523 << "' service";
525 return HRESULT_FROM_WIN32(error);
528 service_out->Set(service.Take());
529 return S_OK;
532 } // namespace remoting