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. 4 5#include "remoting/host/win/elevated_controller.h" 6 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" 24 25namespace remoting { 26 27namespace { 28 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. 33const size_t kMaxConfigFileSize = 1024 * 1024; 34 35// The host configuration file name. 36const base::FilePath::CharType kConfigFileName[] = FILE_PATH_LITERAL("host.json"); 37 38// The unprivileged configuration file name. 39const base::FilePath::CharType kUnprivilegedConfigFileName[] = 40 FILE_PATH_LITERAL("host_unprivileged.json"); 41 42// The extension for the temporary file. 43const base::FilePath::CharType kTempFileExtension[] = FILE_PATH_LITERAL("json~"); 44 45// The host configuration file security descriptor that enables full access to 46// Local System and built-in administrators only. 47const char kConfigFileSecurityDescriptor[] = 48 "O:BAG:BAD:(A;;GA;;;SY)(A;;GA;;;BA)"; 49 50const char kUnprivilegedConfigFileSecurityDescriptor[] = 51 "O:BAG:BAD:(A;;GA;;;SY)(A;;GA;;;BA)(A;;GR;;;AU)"; 52 53// Configuration keys. 54 55// The configuration keys that cannot be specified in UpdateConfig(). 56const char* const kReadonlyKeys[] = { 57 kHostIdConfigPath, kHostOwnerConfigPath, kHostOwnerEmailConfigPath, 58 kXmppLoginConfigPath }; 59 60// The configuration keys whose values may be read by GetConfig(). 61const char* const kUnprivilegedConfigKeys[] = { 62 kHostIdConfigPath, kXmppLoginConfigPath }; 63 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). 67bool IsClientAdmin() { 68 HRESULT hr = CoImpersonateClient(); 69 if (FAILED(hr)) { 70 return false; 71 } 72 73 SID_IDENTIFIER_AUTHORITY nt_authority = SECURITY_NT_AUTHORITY; 74 PSID administrators_group = NULL; 75 BOOL result = AllocateAndInitializeSid(&nt_authority, 76 2, 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; 84 } 85 FreeSid(administrators_group); 86 } 87 88 hr = CoRevertToSelf(); 89 CHECK(SUCCEEDED(hr)); 90 91 return !!result; 92} 93 94// Reads and parses the configuration file up to |kMaxConfigFileSize| in 95// size. 96HRESULT ReadConfig(const base::FilePath& filename, 97 scoped_ptr<base::DictionaryValue>* config_out) { 98 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)); 108 109 if (!file.IsValid()) { 110 DWORD error = GetLastError(); 111 PLOG(ERROR) << "Failed to open '" << filename.value() << "'"; 112 return HRESULT_FROM_WIN32(error); 113 } 114 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); 121 } 122 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)); 127 128 base::DictionaryValue* dictionary; 129 if (value.get() == NULL || !value->GetAsDictionary(&dictionary)) { 130 LOG(ERROR) << "Failed to read '" << filename.value() << "'."; 131 return E_FAIL; 132 } 133 134 value.release(); 135 config_out->reset(dictionary); 136 return S_OK; 137} 138 139base::FilePath GetTempLocationFor(const base::FilePath& filename) { 140 return filename.ReplaceExtension(kTempFileExtension); 141} 142 143// Writes a config file to a temporary location. 144HRESULT 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); 155 } 156 157 SECURITY_ATTRIBUTES security_attributes = {0}; 158 security_attributes.nLength = sizeof(security_attributes); 159 security_attributes.lpSecurityDescriptor = sd.get(); 160 security_attributes.bInheritHandle = FALSE; 161 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, 167 0, 168 &security_attributes, 169 CREATE_ALWAYS, 170 FILE_FLAG_SEQUENTIAL_SCAN, 171 NULL)); 172 173 if (!file.IsValid()) { 174 DWORD error = GetLastError(); 175 PLOG(ERROR) << "Failed to create '" << filename.value() << "'"; 176 return HRESULT_FROM_WIN32(error); 177 } 178 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); 185 } 186 187 return S_OK; 188} 189 190// Moves a config file from its temporary location to its permanent location. 191HRESULT 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); 202 } 203 204 return S_OK; 205} 206 207// Writes the configuration file up to |kMaxConfigFileSize| in size. 208HRESULT WriteConfig(const char* content, size_t length, HWND owner_window) { 209 if (length > kMaxConfigFileSize) { 210 return E_FAIL; 211 } 212 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; 217 } 218 base::DictionaryValue* config_dict = NULL; 219 if (!config_value->GetAsDictionary(&config_dict)) { 220 return E_FAIL; 221 } 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; 227 } 228 } 229 } 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; 234 } 235 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); 244 } 245 } 246 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); 254 } 255 } 256 std::string unprivileged_config_str; 257 base::JSONWriter::Write(&unprivileged_config_dict, &unprivileged_config_str); 258 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; 268 } 269 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; 279 } 280 281 // Move the full configuration file to its permanent location. 282 hr = MoveConfigFileFromTemp(full_config_file_path); 283 if (FAILED(hr)) { 284 return hr; 285 } 286 287 // Move the unprivileged configuration file to its permanent location. 288 hr = MoveConfigFileFromTemp(unprivileged_config_file_path); 289 if (FAILED(hr)) { 290 return hr; 291 } 292 293 return S_OK; 294} 295 296} // namespace 297 298ElevatedController::ElevatedController() : owner_window_(NULL) { 299} 300 301HRESULT ElevatedController::FinalConstruct() { 302 return S_OK; 303} 304 305void ElevatedController::FinalRelease() { 306} 307 308STDMETHODIMP ElevatedController::GetConfig(BSTR* config_out) { 309 base::FilePath config_dir = remoting::GetConfigDir(); 310 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; 317 } 318 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); 322 323 *config_out = ::SysAllocString(base::UTF8ToUTF16(file_content).c_str()); 324 if (config_out == NULL) { 325 return E_OUTOFMEMORY; 326 } 327 328 return S_OK; 329} 330 331STDMETHODIMP 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)); 338 339 base::string16 version; 340 if (version_info.get()) { 341 version = version_info->product_version(); 342 } 343 344 *version_out = ::SysAllocString(version.c_str()); 345 if (version_out == NULL) { 346 return E_OUTOFMEMORY; 347 } 348 349 return S_OK; 350} 351 352STDMETHODIMP 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); 357 } 358 359 std::string file_content = base::UTF16ToUTF8( 360 base::string16(static_cast<base::char16*>(config), ::SysStringLen(config))); 361 362 return WriteConfig(file_content.c_str(), file_content.size(), owner_window_); 363} 364 365STDMETHODIMP ElevatedController::SetOwnerWindow(LONG_PTR window_handle) { 366 owner_window_ = reinterpret_cast<HWND>(window_handle); 367 return S_OK; 368} 369 370STDMETHODIMP ElevatedController::StartDaemon() { 371 ScopedScHandle service; 372 HRESULT hr = OpenService(&service); 373 if (FAILED(hr)) { 374 return hr; 375 } 376 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); 393 } 394 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"; 401 402 return HRESULT_FROM_WIN32(error); 403 } 404 } 405 406 return S_OK; 407} 408 409STDMETHODIMP ElevatedController::StopDaemon() { 410 ScopedScHandle service; 411 HRESULT hr = OpenService(&service); 412 if (FAILED(hr)) { 413 return hr; 414 } 415 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); 432 } 433 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); 442 } 443 } 444 445 return S_OK; 446} 447 448STDMETHODIMP 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; 455 } 456 base::DictionaryValue* config_dict = NULL; 457 if (!config_value->GetAsDictionary(&config_dict)) { 458 return E_FAIL; 459 } 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); 464 } 465 } 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; 472 } 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_); 480} 481 482STDMETHODIMP 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; 492 } 493} 494 495STDMETHODIMP ElevatedController::SetUsageStatsConsent(BOOL allowed) { 496 if (remoting::SetUsageStatsConsent(!!allowed)) { 497 return S_OK; 498 } else { 499 return E_FAIL; 500 } 501} 502 503HRESULT ElevatedController::OpenService(ScopedScHandle* service_out) { 504 DWORD error; 505 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"; 512 513 return HRESULT_FROM_WIN32(error); 514 } 515 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"; 524 525 return HRESULT_FROM_WIN32(error); 526 } 527 528 service_out->Set(service.Take()); 529 return S_OK; 530} 531 532} // namespace remoting 533