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/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
.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(kHostOwner
, &email
)) {
224 if (!config_dict
->GetString(kXmppLogin
, &email
)) {
228 std::string host_id
, host_secret_hash
;
229 if (!config_dict
->GetString(kHostId
, &host_id
) ||
230 !config_dict
->GetString(kHostSecretHash
, &host_secret_hash
)) {
234 // Ask the user to verify the configuration (unless the client is admin
236 if (!IsClientAdmin()) {
237 remoting::VerifyConfigWindowWin
verify_win(email
, host_id
,
239 DWORD error
= verify_win
.DoModal(owner_window
);
240 if (error
!= ERROR_SUCCESS
) {
241 return HRESULT_FROM_WIN32(error
);
245 // Extract the unprivileged fields from the configuration.
246 base::DictionaryValue unprivileged_config_dict
;
247 for (int i
= 0; i
< arraysize(kUnprivilegedConfigKeys
); ++i
) {
248 const char* key
= kUnprivilegedConfigKeys
[i
];
249 base::string16 value
;
250 if (config_dict
->GetString(key
, &value
)) {
251 unprivileged_config_dict
.SetString(key
, value
);
254 std::string unprivileged_config_str
;
255 base::JSONWriter::Write(&unprivileged_config_dict
, &unprivileged_config_str
);
257 // Write the full configuration file to a temporary location.
258 base::FilePath full_config_file_path
=
259 remoting::GetConfigDir().Append(kConfigFileName
);
260 HRESULT hr
= WriteConfigFileToTemp(full_config_file_path
,
261 kConfigFileSecurityDescriptor
,
268 // Write the unprivileged configuration file to a temporary location.
269 base::FilePath unprivileged_config_file_path
=
270 remoting::GetConfigDir().Append(kUnprivilegedConfigFileName
);
271 hr
= WriteConfigFileToTemp(unprivileged_config_file_path
,
272 kUnprivilegedConfigFileSecurityDescriptor
,
273 unprivileged_config_str
.data(),
274 unprivileged_config_str
.size());
279 // Move the full configuration file to its permanent location.
280 hr
= MoveConfigFileFromTemp(full_config_file_path
);
285 // Move the unprivileged configuration file to its permanent location.
286 hr
= MoveConfigFileFromTemp(unprivileged_config_file_path
);
296 ElevatedController::ElevatedController() : owner_window_(NULL
) {
299 HRESULT
ElevatedController::FinalConstruct() {
303 void ElevatedController::FinalRelease() {
306 STDMETHODIMP
ElevatedController::GetConfig(BSTR
* config_out
) {
307 base::FilePath config_dir
= remoting::GetConfigDir();
309 // Read the unprivileged part of host configuration.
310 scoped_ptr
<base::DictionaryValue
> config
;
311 HRESULT hr
= ReadConfig(config_dir
.Append(kUnprivilegedConfigFileName
),
317 // Convert the config back to a string and return it to the caller.
318 std::string file_content
;
319 base::JSONWriter::Write(config
.get(), &file_content
);
321 *config_out
= ::SysAllocString(base::UTF8ToUTF16(file_content
).c_str());
322 if (config_out
== NULL
) {
323 return E_OUTOFMEMORY
;
329 STDMETHODIMP
ElevatedController::GetVersion(BSTR
* version_out
) {
330 // Report the product version number of the daemon controller binary as
332 HMODULE binary
= base::GetModuleFromAddress(
333 reinterpret_cast<void*>(&ReadConfig
));
334 scoped_ptr
<FileVersionInfo
> version_info(
335 FileVersionInfo::CreateFileVersionInfoForModule(binary
));
337 base::string16 version
;
338 if (version_info
.get()) {
339 version
= version_info
->product_version();
342 *version_out
= ::SysAllocString(version
.c_str());
343 if (version_out
== NULL
) {
344 return E_OUTOFMEMORY
;
350 STDMETHODIMP
ElevatedController::SetConfig(BSTR config
) {
351 // Determine the config directory path and create it if necessary.
352 base::FilePath config_dir
= remoting::GetConfigDir();
353 if (!base::CreateDirectory(config_dir
)) {
354 return HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED
);
357 std::string file_content
= base::UTF16ToUTF8(
358 base::string16(static_cast<base::char16
*>(config
), ::SysStringLen(config
)));
360 return WriteConfig(file_content
.c_str(), file_content
.size(), owner_window_
);
363 STDMETHODIMP
ElevatedController::SetOwnerWindow(LONG_PTR window_handle
) {
364 owner_window_
= reinterpret_cast<HWND
>(window_handle
);
368 STDMETHODIMP
ElevatedController::StartDaemon() {
369 ScopedScHandle service
;
370 HRESULT hr
= OpenService(&service
);
375 // Change the service start type to 'auto'.
376 if (!::ChangeServiceConfigW(service
.Get(),
387 DWORD error
= GetLastError();
388 PLOG(ERROR
) << "Failed to change the '" << kWindowsServiceName
389 << "'service start type to 'auto'";
390 return HRESULT_FROM_WIN32(error
);
393 // Start the service.
394 if (!StartService(service
.Get(), 0, NULL
)) {
395 DWORD error
= GetLastError();
396 if (error
!= ERROR_SERVICE_ALREADY_RUNNING
) {
397 PLOG(ERROR
) << "Failed to start the '" << kWindowsServiceName
400 return HRESULT_FROM_WIN32(error
);
407 STDMETHODIMP
ElevatedController::StopDaemon() {
408 ScopedScHandle service
;
409 HRESULT hr
= OpenService(&service
);
414 // Change the service start type to 'manual'.
415 if (!::ChangeServiceConfigW(service
.Get(),
417 SERVICE_DEMAND_START
,
426 DWORD error
= GetLastError();
427 PLOG(ERROR
) << "Failed to change the '" << kWindowsServiceName
428 << "'service start type to 'manual'";
429 return HRESULT_FROM_WIN32(error
);
433 SERVICE_STATUS status
;
434 if (!ControlService(service
.Get(), SERVICE_CONTROL_STOP
, &status
)) {
435 DWORD error
= GetLastError();
436 if (error
!= ERROR_SERVICE_NOT_ACTIVE
) {
437 PLOG(ERROR
) << "Failed to stop the '" << kWindowsServiceName
439 return HRESULT_FROM_WIN32(error
);
446 STDMETHODIMP
ElevatedController::UpdateConfig(BSTR config
) {
448 std::string config_str
= base::UTF16ToUTF8(
449 base::string16(static_cast<base::char16
*>(config
), ::SysStringLen(config
)));
450 scoped_ptr
<base::Value
> config_value(base::JSONReader::Read(config_str
));
451 if (!config_value
.get()) {
454 base::DictionaryValue
* config_dict
= NULL
;
455 if (!config_value
->GetAsDictionary(&config_dict
)) {
458 // Check for bad keys.
459 for (int i
= 0; i
< arraysize(kReadonlyKeys
); ++i
) {
460 if (config_dict
->HasKey(kReadonlyKeys
[i
])) {
461 return HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED
);
464 // Get the old config.
465 base::FilePath config_dir
= remoting::GetConfigDir();
466 scoped_ptr
<base::DictionaryValue
> config_old
;
467 HRESULT hr
= ReadConfig(config_dir
.Append(kConfigFileName
), &config_old
);
471 // Merge items from the given config into the old config.
472 config_old
->MergeDictionary(config_dict
);
473 // Write the updated config.
474 std::string config_updated_str
;
475 base::JSONWriter::Write(config_old
.get(), &config_updated_str
);
476 return WriteConfig(config_updated_str
.c_str(), config_updated_str
.size(),
480 STDMETHODIMP
ElevatedController::GetUsageStatsConsent(BOOL
* allowed
,
481 BOOL
* set_by_policy
) {
483 bool local_set_by_policy
;
484 if (remoting::GetUsageStatsConsent(&local_allowed
, &local_set_by_policy
)) {
485 *allowed
= local_allowed
;
486 *set_by_policy
= local_set_by_policy
;
493 STDMETHODIMP
ElevatedController::SetUsageStatsConsent(BOOL allowed
) {
494 if (remoting::SetUsageStatsConsent(!!allowed
)) {
501 HRESULT
ElevatedController::OpenService(ScopedScHandle
* service_out
) {
504 ScopedScHandle
scmanager(
505 ::OpenSCManagerW(NULL
, SERVICES_ACTIVE_DATABASE
,
506 SC_MANAGER_CONNECT
| SC_MANAGER_ENUMERATE_SERVICE
));
507 if (!scmanager
.IsValid()) {
508 error
= GetLastError();
509 PLOG(ERROR
) << "Failed to connect to the service control manager";
511 return HRESULT_FROM_WIN32(error
);
514 DWORD desired_access
= SERVICE_CHANGE_CONFIG
| SERVICE_QUERY_STATUS
|
515 SERVICE_START
| SERVICE_STOP
;
516 ScopedScHandle
service(
517 ::OpenServiceW(scmanager
.Get(), kWindowsServiceName
, desired_access
));
518 if (!service
.IsValid()) {
519 error
= GetLastError();
520 PLOG(ERROR
) << "Failed to open to the '" << kWindowsServiceName
523 return HRESULT_FROM_WIN32(error
);
526 service_out
->Set(service
.Take());
530 } // namespace remoting