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_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"
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;
34 // The host configuration file name.
35 const base::FilePath::CharType kConfigFileName
[] = FILE_PATH_LITERAL("host.json");
37 // The unprivileged configuration file name.
38 const base::FilePath::CharType kUnprivilegedConfigFileName
[] =
39 FILE_PATH_LITERAL("host_unprivileged.json");
41 // The extension for the temporary file.
42 const base::FilePath::CharType kTempFileExtension
[] = FILE_PATH_LITERAL("json~");
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)";
49 const char kUnprivilegedConfigFileSecurityDescriptor
[] =
50 "O:BAG:BAD:(A;;GA;;;SY)(A;;GA;;;BA)(A;;GR;;;AU)";
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";
58 // The configuration keys that cannot be specified in UpdateConfig().
59 const char* const kReadonlyKeys
[] = { kHostId
, kHostOwner
, kXmppLogin
};
61 // The configuration keys whose values may be read by GetConfig().
62 const char* const kUnprivilegedConfigKeys
[] = { kHostId
, kXmppLogin
};
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();
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
,
80 &administrators_group
);
82 if (!CheckTokenMembership(NULL
, administrators_group
, &result
)) {
85 FreeSid(administrators_group
);
88 hr
= CoRevertToSelf();
94 // Reads and parses the configuration file up to |kMaxConfigFileSize| in
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(),
103 FILE_SHARE_READ
| FILE_SHARE_WRITE
,
106 FILE_FLAG_SEQUENTIAL_SCAN
,
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
, &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() << "'.";
135 config_out
->reset(dictionary
);
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
,
148 // Create the security descriptor for the configuration file.
149 ScopedSd sd
= ConvertSddlToSd(security_descriptor
);
151 DWORD error
= GetLastError();
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(),
168 &security_attributes
,
170 FILE_FLAG_SEQUENTIAL_SCAN
,
173 if (!file
.IsValid()) {
174 DWORD error
= GetLastError();
175 PLOG(ERROR
) << "Failed to create '" << filename
.value() << "'";
176 return HRESULT_FROM_WIN32(error
);
180 if (!WriteFile(file
, content
, static_cast<DWORD
>(length
), &written
, NULL
)) {
181 DWORD error
= GetLastError();
182 PLOG(ERROR
) << "Failed to write to '" << filename
.value() << "'";
183 return HRESULT_FROM_WIN32(error
);
189 // Moves a config file from its temporary location to its permanent location.
190 HRESULT
MoveConfigFileFromTemp(const base::FilePath
& filename
) {
191 // Now that the configuration is stored successfully replace the actual
192 // configuration file.
193 base::FilePath tempname
= GetTempLocationFor(filename
);
194 if (!MoveFileExW(tempname
.value().c_str(),
195 filename
.value().c_str(),
196 MOVEFILE_REPLACE_EXISTING
)) {
197 DWORD error
= GetLastError();
198 PLOG(ERROR
) << "Failed to rename '" << tempname
.value() << "' to '"
199 << filename
.value() << "'";
200 return HRESULT_FROM_WIN32(error
);
206 // Writes the configuration file up to |kMaxConfigFileSize| in size.
207 HRESULT
WriteConfig(const char* content
, size_t length
, HWND owner_window
) {
208 if (length
> kMaxConfigFileSize
) {
212 // Extract the configuration data that the user will verify.
213 scoped_ptr
<base::Value
> config_value(base::JSONReader::Read(content
));
214 if (!config_value
.get()) {
217 base::DictionaryValue
* config_dict
= NULL
;
218 if (!config_value
->GetAsDictionary(&config_dict
)) {
222 if (!config_dict
->GetString(kHostOwner
, &email
)) {
223 if (!config_dict
->GetString(kXmppLogin
, &email
)) {
227 std::string host_id
, host_secret_hash
;
228 if (!config_dict
->GetString(kHostId
, &host_id
) ||
229 !config_dict
->GetString(kHostSecretHash
, &host_secret_hash
)) {
233 // Ask the user to verify the configuration (unless the client is admin
235 if (!IsClientAdmin()) {
236 remoting::VerifyConfigWindowWin
verify_win(email
, host_id
,
238 DWORD error
= verify_win
.DoModal(owner_window
);
239 if (error
!= ERROR_SUCCESS
) {
240 return HRESULT_FROM_WIN32(error
);
244 // Extract the unprivileged fields from the configuration.
245 base::DictionaryValue unprivileged_config_dict
;
246 for (int i
= 0; i
< arraysize(kUnprivilegedConfigKeys
); ++i
) {
247 const char* key
= kUnprivilegedConfigKeys
[i
];
248 base::string16 value
;
249 if (config_dict
->GetString(key
, &value
)) {
250 unprivileged_config_dict
.SetString(key
, value
);
253 std::string unprivileged_config_str
;
254 base::JSONWriter::Write(&unprivileged_config_dict
, &unprivileged_config_str
);
256 // Write the full configuration file to a temporary location.
257 base::FilePath full_config_file_path
=
258 remoting::GetConfigDir().Append(kConfigFileName
);
259 HRESULT hr
= WriteConfigFileToTemp(full_config_file_path
,
260 kConfigFileSecurityDescriptor
,
267 // Write the unprivileged configuration file to a temporary location.
268 base::FilePath unprivileged_config_file_path
=
269 remoting::GetConfigDir().Append(kUnprivilegedConfigFileName
);
270 hr
= WriteConfigFileToTemp(unprivileged_config_file_path
,
271 kUnprivilegedConfigFileSecurityDescriptor
,
272 unprivileged_config_str
.data(),
273 unprivileged_config_str
.size());
278 // Move the full configuration file to its permanent location.
279 hr
= MoveConfigFileFromTemp(full_config_file_path
);
284 // Move the unprivileged configuration file to its permanent location.
285 hr
= MoveConfigFileFromTemp(unprivileged_config_file_path
);
295 ElevatedController::ElevatedController() : owner_window_(NULL
) {
298 HRESULT
ElevatedController::FinalConstruct() {
302 void ElevatedController::FinalRelease() {
305 STDMETHODIMP
ElevatedController::GetConfig(BSTR
* config_out
) {
306 base::FilePath config_dir
= remoting::GetConfigDir();
308 // Read the unprivileged part of host configuration.
309 scoped_ptr
<base::DictionaryValue
> config
;
310 HRESULT hr
= ReadConfig(config_dir
.Append(kUnprivilegedConfigFileName
),
316 // Convert the config back to a string and return it to the caller.
317 std::string file_content
;
318 base::JSONWriter::Write(config
.get(), &file_content
);
320 *config_out
= ::SysAllocString(base::UTF8ToUTF16(file_content
).c_str());
321 if (config_out
== NULL
) {
322 return E_OUTOFMEMORY
;
328 STDMETHODIMP
ElevatedController::GetVersion(BSTR
* version_out
) {
329 // Report the product version number of the daemon controller binary as
331 HMODULE binary
= base::GetModuleFromAddress(
332 reinterpret_cast<void*>(&ReadConfig
));
333 scoped_ptr
<FileVersionInfo
> version_info(
334 FileVersionInfo::CreateFileVersionInfoForModule(binary
));
336 base::string16 version
;
337 if (version_info
.get()) {
338 version
= version_info
->product_version();
341 *version_out
= ::SysAllocString(version
.c_str());
342 if (version_out
== NULL
) {
343 return E_OUTOFMEMORY
;
349 STDMETHODIMP
ElevatedController::SetConfig(BSTR config
) {
350 // Determine the config directory path and create it if necessary.
351 base::FilePath config_dir
= remoting::GetConfigDir();
352 if (!base::CreateDirectory(config_dir
)) {
353 return HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED
);
356 std::string file_content
= base::UTF16ToUTF8(
357 base::string16(static_cast<base::char16
*>(config
), ::SysStringLen(config
)));
359 return WriteConfig(file_content
.c_str(), file_content
.size(), owner_window_
);
362 STDMETHODIMP
ElevatedController::SetOwnerWindow(LONG_PTR window_handle
) {
363 owner_window_
= reinterpret_cast<HWND
>(window_handle
);
367 STDMETHODIMP
ElevatedController::StartDaemon() {
368 ScopedScHandle service
;
369 HRESULT hr
= OpenService(&service
);
374 // Change the service start type to 'auto'.
375 if (!::ChangeServiceConfigW(service
,
386 DWORD error
= GetLastError();
387 PLOG(ERROR
) << "Failed to change the '" << kWindowsServiceName
388 << "'service start type to 'auto'";
389 return HRESULT_FROM_WIN32(error
);
392 // Start the service.
393 if (!StartService(service
, 0, NULL
)) {
394 DWORD error
= GetLastError();
395 if (error
!= ERROR_SERVICE_ALREADY_RUNNING
) {
396 PLOG(ERROR
) << "Failed to start the '" << kWindowsServiceName
399 return HRESULT_FROM_WIN32(error
);
406 STDMETHODIMP
ElevatedController::StopDaemon() {
407 ScopedScHandle service
;
408 HRESULT hr
= OpenService(&service
);
413 // Change the service start type to 'manual'.
414 if (!::ChangeServiceConfigW(service
,
416 SERVICE_DEMAND_START
,
425 DWORD error
= GetLastError();
426 PLOG(ERROR
) << "Failed to change the '" << kWindowsServiceName
427 << "'service start type to 'manual'";
428 return HRESULT_FROM_WIN32(error
);
432 SERVICE_STATUS status
;
433 if (!ControlService(service
, SERVICE_CONTROL_STOP
, &status
)) {
434 DWORD error
= GetLastError();
435 if (error
!= ERROR_SERVICE_NOT_ACTIVE
) {
436 PLOG(ERROR
) << "Failed to stop the '" << kWindowsServiceName
438 return HRESULT_FROM_WIN32(error
);
445 STDMETHODIMP
ElevatedController::UpdateConfig(BSTR config
) {
447 std::string config_str
= base::UTF16ToUTF8(
448 base::string16(static_cast<base::char16
*>(config
), ::SysStringLen(config
)));
449 scoped_ptr
<base::Value
> config_value(base::JSONReader::Read(config_str
));
450 if (!config_value
.get()) {
453 base::DictionaryValue
* config_dict
= NULL
;
454 if (!config_value
->GetAsDictionary(&config_dict
)) {
457 // Check for bad keys.
458 for (int i
= 0; i
< arraysize(kReadonlyKeys
); ++i
) {
459 if (config_dict
->HasKey(kReadonlyKeys
[i
])) {
460 return HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED
);
463 // Get the old config.
464 base::FilePath config_dir
= remoting::GetConfigDir();
465 scoped_ptr
<base::DictionaryValue
> config_old
;
466 HRESULT hr
= ReadConfig(config_dir
.Append(kConfigFileName
), &config_old
);
470 // Merge items from the given config into the old config.
471 config_old
->MergeDictionary(config_dict
);
472 // Write the updated config.
473 std::string config_updated_str
;
474 base::JSONWriter::Write(config_old
.get(), &config_updated_str
);
475 return WriteConfig(config_updated_str
.c_str(), config_updated_str
.size(),
479 STDMETHODIMP
ElevatedController::GetUsageStatsConsent(BOOL
* allowed
,
480 BOOL
* set_by_policy
) {
482 bool local_set_by_policy
;
483 if (remoting::GetUsageStatsConsent(&local_allowed
, &local_set_by_policy
)) {
484 *allowed
= local_allowed
;
485 *set_by_policy
= local_set_by_policy
;
492 STDMETHODIMP
ElevatedController::SetUsageStatsConsent(BOOL allowed
) {
493 if (remoting::SetUsageStatsConsent(!!allowed
)) {
500 HRESULT
ElevatedController::OpenService(ScopedScHandle
* service_out
) {
503 ScopedScHandle
scmanager(
504 ::OpenSCManagerW(NULL
, SERVICES_ACTIVE_DATABASE
,
505 SC_MANAGER_CONNECT
| SC_MANAGER_ENUMERATE_SERVICE
));
506 if (!scmanager
.IsValid()) {
507 error
= GetLastError();
508 PLOG(ERROR
) << "Failed to connect to the service control manager";
510 return HRESULT_FROM_WIN32(error
);
513 DWORD desired_access
= SERVICE_CHANGE_CONFIG
| SERVICE_QUERY_STATUS
|
514 SERVICE_START
| SERVICE_STOP
;
515 ScopedScHandle
service(
516 ::OpenServiceW(scmanager
, kWindowsServiceName
, desired_access
));
517 if (!service
.IsValid()) {
518 error
= GetLastError();
519 PLOG(ERROR
) << "Failed to open to the '" << kWindowsServiceName
522 return HRESULT_FROM_WIN32(error
);
525 service_out
->Set(service
.Take());
529 } // namespace remoting