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"
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();
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
.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() << "'.";
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
.Get(), content
, static_cast<DWORD
>(length
), &written
,
182 DWORD error
= GetLastError();
183 PLOG(ERROR
) << "Failed to write to '" << filename
.value() << "'";
184 return HRESULT_FROM_WIN32(error
);
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
);
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
) {
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()) {
218 base::DictionaryValue
* config_dict
= NULL
;
219 if (!config_value
->GetAsDictionary(&config_dict
)) {
223 if (!config_dict
->GetString(kHostOwnerEmailConfigPath
, &email
)) {
224 if (!config_dict
->GetString(kHostOwnerConfigPath
, &email
)) {
225 if (!config_dict
->GetString(kXmppLoginConfigPath
, &email
)) {
230 std::string host_id
, host_secret_hash
;
231 if (!config_dict
->GetString(kHostIdConfigPath
, &host_id
) ||
232 !config_dict
->GetString(kHostSecretHashConfigPath
, &host_secret_hash
)) {
236 // Ask the user to verify the configuration (unless the client is admin
238 if (!IsClientAdmin()) {
239 remoting::VerifyConfigWindowWin
verify_win(email
, host_id
,
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
,
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());
281 // Move the full configuration file to its permanent location.
282 hr
= MoveConfigFileFromTemp(full_config_file_path
);
287 // Move the unprivileged configuration file to its permanent location.
288 hr
= MoveConfigFileFromTemp(unprivileged_config_file_path
);
298 ElevatedController::ElevatedController() : owner_window_(NULL
) {
301 HRESULT
ElevatedController::FinalConstruct() {
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
),
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
;
331 STDMETHODIMP
ElevatedController::GetVersion(BSTR
* version_out
) {
332 // Report the product version number of the daemon controller binary as
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
;
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
);
370 STDMETHODIMP
ElevatedController::StartDaemon() {
371 ScopedScHandle service
;
372 HRESULT hr
= OpenService(&service
);
377 // Change the service start type to 'auto'.
378 if (!::ChangeServiceConfigW(service
.Get(),
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
402 return HRESULT_FROM_WIN32(error
);
409 STDMETHODIMP
ElevatedController::StopDaemon() {
410 ScopedScHandle service
;
411 HRESULT hr
= OpenService(&service
);
416 // Change the service start type to 'manual'.
417 if (!::ChangeServiceConfigW(service
.Get(),
419 SERVICE_DEMAND_START
,
428 DWORD error
= GetLastError();
429 PLOG(ERROR
) << "Failed to change the '" << kWindowsServiceName
430 << "'service start type to 'manual'";
431 return HRESULT_FROM_WIN32(error
);
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
441 return HRESULT_FROM_WIN32(error
);
448 STDMETHODIMP
ElevatedController::UpdateConfig(BSTR 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()) {
456 base::DictionaryValue
* config_dict
= NULL
;
457 if (!config_value
->GetAsDictionary(&config_dict
)) {
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
);
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(),
482 STDMETHODIMP
ElevatedController::GetUsageStatsConsent(BOOL
* allowed
,
483 BOOL
* set_by_policy
) {
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
;
495 STDMETHODIMP
ElevatedController::SetUsageStatsConsent(BOOL allowed
) {
496 if (remoting::SetUsageStatsConsent(!!allowed
)) {
503 HRESULT
ElevatedController::OpenService(ScopedScHandle
* service_out
) {
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
525 return HRESULT_FROM_WIN32(error
);
528 service_out
->Set(service
.Take());
532 } // namespace remoting