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_util.h" 8 #include "base/file_version_info.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/usage_stats_consent.h" 20 #include "remoting/host/verify_config_window_win.h" 21 #include "remoting/host/win/core_resource.h" 22 #include "remoting/host/win/security_descriptor.h" 23 24 namespace remoting { 25 26 namespace { 27 28 // The maximum size of the configuration file. "1MB ought to be enough" for any 29 // reasonable configuration we will ever need. 1MB is low enough to make 30 // the probability of out of memory situation fairly low. OOM is still possible 31 // and we will crash if it occurs. 32 const size_t kMaxConfigFileSize = 1024 * 1024; 33 34 // The host configuration file name. 35 const base::FilePath::CharType kConfigFileName[] = FILE_PATH_LITERAL("host.json"); 36 37 // The unprivileged configuration file name. 38 const base::FilePath::CharType kUnprivilegedConfigFileName[] = 39 FILE_PATH_LITERAL("host_unprivileged.json"); 40 41 // The extension for the temporary file. 42 const base::FilePath::CharType kTempFileExtension[] = FILE_PATH_LITERAL("json~"); 43 44 // The host configuration file security descriptor that enables full access to 45 // Local System and built-in administrators only. 46 const char kConfigFileSecurityDescriptor[] = 47 "O:BAG:BAD:(A;;GA;;;SY)(A;;GA;;;BA)"; 48 49 const char kUnprivilegedConfigFileSecurityDescriptor[] = 50 "O:BAG:BAD:(A;;GA;;;SY)(A;;GA;;;BA)(A;;GR;;;AU)"; 51 52 // Configuration keys. 53 const char kHostId[] = "host_id"; 54 const char kXmppLogin[] = "xmpp_login"; 55 const char kHostOwner[] = "host_owner"; 56 const char kHostSecretHash[] = "host_secret_hash"; 57 58 // The configuration keys that cannot be specified in UpdateConfig(). 59 const char* const kReadonlyKeys[] = { kHostId, kHostOwner, kXmppLogin }; 60 61 // The configuration keys whose values may be read by GetConfig(). 62 const char* const kUnprivilegedConfigKeys[] = { kHostId, kXmppLogin }; 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). 67 bool 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. 96 HRESULT 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 LOG_GETLASTERROR(ERROR) 112 << "Failed to open '" << filename.value() << "'"; 113 return HRESULT_FROM_WIN32(error); 114 } 115 116 scoped_ptr<char[]> buffer(new char[kMaxConfigFileSize]); 117 DWORD size = kMaxConfigFileSize; 118 if (!::ReadFile(file, &buffer[0], size, &size, NULL)) { 119 DWORD error = GetLastError(); 120 LOG_GETLASTERROR(ERROR) 121 << "Failed to read '" << filename.value() << "'"; 122 return HRESULT_FROM_WIN32(error); 123 } 124 125 // Parse the JSON configuration, expecting it to contain a dictionary. 126 std::string file_content(buffer.get(), size); 127 scoped_ptr<base::Value> value( 128 base::JSONReader::Read(file_content, base::JSON_ALLOW_TRAILING_COMMAS)); 129 130 base::DictionaryValue* dictionary; 131 if (value.get() == NULL || !value->GetAsDictionary(&dictionary)) { 132 LOG(ERROR) << "Failed to read '" << filename.value() << "'."; 133 return E_FAIL; 134 } 135 136 value.release(); 137 config_out->reset(dictionary); 138 return S_OK; 139 } 140 141 base::FilePath GetTempLocationFor(const base::FilePath& filename) { 142 return filename.ReplaceExtension(kTempFileExtension); 143 } 144 145 // Writes a config file to a temporary location. 146 HRESULT WriteConfigFileToTemp(const base::FilePath& filename, 147 const char* security_descriptor, 148 const char* content, 149 size_t length) { 150 // Create the security descriptor for the configuration file. 151 ScopedSd sd = ConvertSddlToSd(security_descriptor); 152 if (!sd) { 153 DWORD error = GetLastError(); 154 LOG_GETLASTERROR(ERROR) << 155 "Failed to create a security descriptor for the configuration file"; 156 return HRESULT_FROM_WIN32(error); 157 } 158 159 SECURITY_ATTRIBUTES security_attributes = {0}; 160 security_attributes.nLength = sizeof(security_attributes); 161 security_attributes.lpSecurityDescriptor = sd.get(); 162 security_attributes.bInheritHandle = FALSE; 163 164 // Create a temporary file and write configuration to it. 165 base::FilePath tempname = GetTempLocationFor(filename); 166 base::win::ScopedHandle file( 167 CreateFileW(tempname.value().c_str(), 168 GENERIC_WRITE, 169 0, 170 &security_attributes, 171 CREATE_ALWAYS, 172 FILE_FLAG_SEQUENTIAL_SCAN, 173 NULL)); 174 175 if (!file.IsValid()) { 176 DWORD error = GetLastError(); 177 LOG_GETLASTERROR(ERROR) 178 << "Failed to create '" << filename.value() << "'"; 179 return HRESULT_FROM_WIN32(error); 180 } 181 182 DWORD written; 183 if (!WriteFile(file, content, static_cast<DWORD>(length), &written, NULL)) { 184 DWORD error = GetLastError(); 185 LOG_GETLASTERROR(ERROR) 186 << "Failed to write to '" << filename.value() << "'"; 187 return HRESULT_FROM_WIN32(error); 188 } 189 190 return S_OK; 191 } 192 193 // Moves a config file from its temporary location to its permanent location. 194 HRESULT MoveConfigFileFromTemp(const base::FilePath& filename) { 195 // Now that the configuration is stored successfully replace the actual 196 // configuration file. 197 base::FilePath tempname = GetTempLocationFor(filename); 198 if (!MoveFileExW(tempname.value().c_str(), 199 filename.value().c_str(), 200 MOVEFILE_REPLACE_EXISTING)) { 201 DWORD error = GetLastError(); 202 LOG_GETLASTERROR(ERROR) 203 << "Failed to rename '" << tempname.value() << "' to '" 204 << filename.value() << "'"; 205 return HRESULT_FROM_WIN32(error); 206 } 207 208 return S_OK; 209 } 210 211 // Writes the configuration file up to |kMaxConfigFileSize| in size. 212 HRESULT WriteConfig(const char* content, size_t length, HWND owner_window) { 213 if (length > kMaxConfigFileSize) { 214 return E_FAIL; 215 } 216 217 // Extract the configuration data that the user will verify. 218 scoped_ptr<base::Value> config_value(base::JSONReader::Read(content)); 219 if (!config_value.get()) { 220 return E_FAIL; 221 } 222 base::DictionaryValue* config_dict = NULL; 223 if (!config_value->GetAsDictionary(&config_dict)) { 224 return E_FAIL; 225 } 226 std::string email; 227 if (!config_dict->GetString(kHostOwner, &email)) { 228 if (!config_dict->GetString(kXmppLogin, &email)) { 229 return E_FAIL; 230 } 231 } 232 std::string host_id, host_secret_hash; 233 if (!config_dict->GetString(kHostId, &host_id) || 234 !config_dict->GetString(kHostSecretHash, &host_secret_hash)) { 235 return E_FAIL; 236 } 237 238 // Ask the user to verify the configuration (unless the client is admin 239 // already). 240 if (!IsClientAdmin()) { 241 remoting::VerifyConfigWindowWin verify_win(email, host_id, 242 host_secret_hash); 243 DWORD error = verify_win.DoModal(owner_window); 244 if (error != ERROR_SUCCESS) { 245 return HRESULT_FROM_WIN32(error); 246 } 247 } 248 249 // Extract the unprivileged fields from the configuration. 250 base::DictionaryValue unprivileged_config_dict; 251 for (int i = 0; i < arraysize(kUnprivilegedConfigKeys); ++i) { 252 const char* key = kUnprivilegedConfigKeys[i]; 253 base::string16 value; 254 if (config_dict->GetString(key, &value)) { 255 unprivileged_config_dict.SetString(key, value); 256 } 257 } 258 std::string unprivileged_config_str; 259 base::JSONWriter::Write(&unprivileged_config_dict, &unprivileged_config_str); 260 261 // Write the full configuration file to a temporary location. 262 base::FilePath full_config_file_path = 263 remoting::GetConfigDir().Append(kConfigFileName); 264 HRESULT hr = WriteConfigFileToTemp(full_config_file_path, 265 kConfigFileSecurityDescriptor, 266 content, 267 length); 268 if (FAILED(hr)) { 269 return hr; 270 } 271 272 // Write the unprivileged configuration file to a temporary location. 273 base::FilePath unprivileged_config_file_path = 274 remoting::GetConfigDir().Append(kUnprivilegedConfigFileName); 275 hr = WriteConfigFileToTemp(unprivileged_config_file_path, 276 kUnprivilegedConfigFileSecurityDescriptor, 277 unprivileged_config_str.data(), 278 unprivileged_config_str.size()); 279 if (FAILED(hr)) { 280 return hr; 281 } 282 283 // Move the full configuration file to its permanent location. 284 hr = MoveConfigFileFromTemp(full_config_file_path); 285 if (FAILED(hr)) { 286 return hr; 287 } 288 289 // Move the unprivileged configuration file to its permanent location. 290 hr = MoveConfigFileFromTemp(unprivileged_config_file_path); 291 if (FAILED(hr)) { 292 return hr; 293 } 294 295 return S_OK; 296 } 297 298 } // namespace 299 300 ElevatedController::ElevatedController() : owner_window_(NULL) { 301 } 302 303 HRESULT ElevatedController::FinalConstruct() { 304 return S_OK; 305 } 306 307 void ElevatedController::FinalRelease() { 308 } 309 310 STDMETHODIMP ElevatedController::GetConfig(BSTR* config_out) { 311 base::FilePath config_dir = remoting::GetConfigDir(); 312 313 // Read the unprivileged part of host configuration. 314 scoped_ptr<base::DictionaryValue> config; 315 HRESULT hr = ReadConfig(config_dir.Append(kUnprivilegedConfigFileName), 316 &config); 317 if (FAILED(hr)) { 318 return hr; 319 } 320 321 // Convert the config back to a string and return it to the caller. 322 std::string file_content; 323 base::JSONWriter::Write(config.get(), &file_content); 324 325 *config_out = ::SysAllocString(UTF8ToUTF16(file_content).c_str()); 326 if (config_out == NULL) { 327 return E_OUTOFMEMORY; 328 } 329 330 return S_OK; 331 } 332 333 STDMETHODIMP ElevatedController::GetVersion(BSTR* version_out) { 334 // Report the product version number of the daemon controller binary as 335 // the host version. 336 HMODULE binary = base::GetModuleFromAddress( 337 reinterpret_cast<void*>(&ReadConfig)); 338 scoped_ptr<FileVersionInfo> version_info( 339 FileVersionInfo::CreateFileVersionInfoForModule(binary)); 340 341 base::string16 version; 342 if (version_info.get()) { 343 version = version_info->product_version(); 344 } 345 346 *version_out = ::SysAllocString(version.c_str()); 347 if (version_out == NULL) { 348 return E_OUTOFMEMORY; 349 } 350 351 return S_OK; 352 } 353 354 STDMETHODIMP ElevatedController::SetConfig(BSTR config) { 355 // Determine the config directory path and create it if necessary. 356 base::FilePath config_dir = remoting::GetConfigDir(); 357 if (!base::CreateDirectory(config_dir)) { 358 return HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED); 359 } 360 361 std::string file_content = UTF16ToUTF8( 362 base::string16(static_cast<base::char16*>(config), ::SysStringLen(config))); 363 364 return WriteConfig(file_content.c_str(), file_content.size(), owner_window_); 365 } 366 367 STDMETHODIMP ElevatedController::SetOwnerWindow(LONG_PTR window_handle) { 368 owner_window_ = reinterpret_cast<HWND>(window_handle); 369 return S_OK; 370 } 371 372 STDMETHODIMP ElevatedController::StartDaemon() { 373 ScopedScHandle service; 374 HRESULT hr = OpenService(&service); 375 if (FAILED(hr)) { 376 return hr; 377 } 378 379 // Change the service start type to 'auto'. 380 if (!::ChangeServiceConfigW(service, 381 SERVICE_NO_CHANGE, 382 SERVICE_AUTO_START, 383 SERVICE_NO_CHANGE, 384 NULL, 385 NULL, 386 NULL, 387 NULL, 388 NULL, 389 NULL, 390 NULL)) { 391 DWORD error = GetLastError(); 392 LOG_GETLASTERROR(ERROR) 393 << "Failed to change the '" << kWindowsServiceName 394 << "'service start type to 'auto'"; 395 return HRESULT_FROM_WIN32(error); 396 } 397 398 // Start the service. 399 if (!StartService(service, 0, NULL)) { 400 DWORD error = GetLastError(); 401 if (error != ERROR_SERVICE_ALREADY_RUNNING) { 402 LOG_GETLASTERROR(ERROR) 403 << "Failed to start the '" << kWindowsServiceName << "'service"; 404 405 return HRESULT_FROM_WIN32(error); 406 } 407 } 408 409 return S_OK; 410 } 411 412 STDMETHODIMP ElevatedController::StopDaemon() { 413 ScopedScHandle service; 414 HRESULT hr = OpenService(&service); 415 if (FAILED(hr)) { 416 return hr; 417 } 418 419 // Change the service start type to 'manual'. 420 if (!::ChangeServiceConfigW(service, 421 SERVICE_NO_CHANGE, 422 SERVICE_DEMAND_START, 423 SERVICE_NO_CHANGE, 424 NULL, 425 NULL, 426 NULL, 427 NULL, 428 NULL, 429 NULL, 430 NULL)) { 431 DWORD error = GetLastError(); 432 LOG_GETLASTERROR(ERROR) 433 << "Failed to change the '" << kWindowsServiceName 434 << "'service start type to 'manual'"; 435 return HRESULT_FROM_WIN32(error); 436 } 437 438 // Stop the service. 439 SERVICE_STATUS status; 440 if (!ControlService(service, SERVICE_CONTROL_STOP, &status)) { 441 DWORD error = GetLastError(); 442 if (error != ERROR_SERVICE_NOT_ACTIVE) { 443 LOG_GETLASTERROR(ERROR) 444 << "Failed to stop the '" << kWindowsServiceName << "'service"; 445 return HRESULT_FROM_WIN32(error); 446 } 447 } 448 449 return S_OK; 450 } 451 452 STDMETHODIMP ElevatedController::UpdateConfig(BSTR config) { 453 // Parse the config. 454 std::string config_str = UTF16ToUTF8( 455 base::string16(static_cast<base::char16*>(config), ::SysStringLen(config))); 456 scoped_ptr<base::Value> config_value(base::JSONReader::Read(config_str)); 457 if (!config_value.get()) { 458 return E_FAIL; 459 } 460 base::DictionaryValue* config_dict = NULL; 461 if (!config_value->GetAsDictionary(&config_dict)) { 462 return E_FAIL; 463 } 464 // Check for bad keys. 465 for (int i = 0; i < arraysize(kReadonlyKeys); ++i) { 466 if (config_dict->HasKey(kReadonlyKeys[i])) { 467 return HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED); 468 } 469 } 470 // Get the old config. 471 base::FilePath config_dir = remoting::GetConfigDir(); 472 scoped_ptr<base::DictionaryValue> config_old; 473 HRESULT hr = ReadConfig(config_dir.Append(kConfigFileName), &config_old); 474 if (FAILED(hr)) { 475 return hr; 476 } 477 // Merge items from the given config into the old config. 478 config_old->MergeDictionary(config_dict); 479 // Write the updated config. 480 std::string config_updated_str; 481 base::JSONWriter::Write(config_old.get(), &config_updated_str); 482 return WriteConfig(config_updated_str.c_str(), config_updated_str.size(), 483 owner_window_); 484 } 485 486 STDMETHODIMP ElevatedController::GetUsageStatsConsent(BOOL* allowed, 487 BOOL* set_by_policy) { 488 bool local_allowed; 489 bool local_set_by_policy; 490 if (remoting::GetUsageStatsConsent(&local_allowed, &local_set_by_policy)) { 491 *allowed = local_allowed; 492 *set_by_policy = local_set_by_policy; 493 return S_OK; 494 } else { 495 return E_FAIL; 496 } 497 } 498 499 STDMETHODIMP ElevatedController::SetUsageStatsConsent(BOOL allowed) { 500 if (remoting::SetUsageStatsConsent(!!allowed)) { 501 return S_OK; 502 } else { 503 return E_FAIL; 504 } 505 } 506 507 HRESULT ElevatedController::OpenService(ScopedScHandle* service_out) { 508 DWORD error; 509 510 ScopedScHandle scmanager( 511 ::OpenSCManagerW(NULL, SERVICES_ACTIVE_DATABASE, 512 SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE)); 513 if (!scmanager.IsValid()) { 514 error = GetLastError(); 515 LOG_GETLASTERROR(ERROR) 516 << "Failed to connect to the service control manager"; 517 518 return HRESULT_FROM_WIN32(error); 519 } 520 521 DWORD desired_access = SERVICE_CHANGE_CONFIG | SERVICE_QUERY_STATUS | 522 SERVICE_START | SERVICE_STOP; 523 ScopedScHandle service( 524 ::OpenServiceW(scmanager, kWindowsServiceName, desired_access)); 525 if (!service.IsValid()) { 526 error = GetLastError(); 527 LOG_GETLASTERROR(ERROR) 528 << "Failed to open to the '" << kWindowsServiceName << "' service"; 529 530 return HRESULT_FROM_WIN32(error); 531 } 532 533 service_out->Set(service.Take()); 534 return S_OK; 535 } 536 537 } // namespace remoting 538