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/process/memory.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "base/values.h"
16 #include "base/win/scoped_handle.h"
17 #include "remoting/host/branding.h"
18 #include "remoting/host/host_config.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.
54 // The configuration keys that cannot be specified in UpdateConfig().
55 const char* const kReadonlyKeys
[] = {
56 kHostIdConfigPath
, kHostOwnerConfigPath
, kHostOwnerEmailConfigPath
,
57 kXmppLoginConfigPath
};
59 // The configuration keys whose values may be read by GetConfig().
60 const char* const kUnprivilegedConfigKeys
[] = {
61 kHostIdConfigPath
, kXmppLoginConfigPath
};
63 // Determines if the client runs in the security context that allows performing
64 // administrative tasks (i.e. the user belongs to the adminstrators group and
65 // the client runs elevated).
66 bool IsClientAdmin() {
67 HRESULT hr
= CoImpersonateClient();
72 SID_IDENTIFIER_AUTHORITY nt_authority
= SECURITY_NT_AUTHORITY
;
73 PSID administrators_group
= NULL
;
74 BOOL result
= AllocateAndInitializeSid(&nt_authority
,
76 SECURITY_BUILTIN_DOMAIN_RID
,
77 DOMAIN_ALIAS_RID_ADMINS
,
79 &administrators_group
);
81 if (!CheckTokenMembership(NULL
, administrators_group
, &result
)) {
84 FreeSid(administrators_group
);
87 hr
= CoRevertToSelf();
93 // Reads and parses the configuration file up to |kMaxConfigFileSize| in
95 HRESULT
ReadConfig(const base::FilePath
& filename
,
96 scoped_ptr
<base::DictionaryValue
>* config_out
) {
98 // Read raw data from the configuration file.
99 base::win::ScopedHandle
file(
100 CreateFileW(filename
.value().c_str(),
102 FILE_SHARE_READ
| FILE_SHARE_WRITE
,
105 FILE_FLAG_SEQUENTIAL_SCAN
,
108 if (!file
.IsValid()) {
109 DWORD error
= GetLastError();
110 PLOG(ERROR
) << "Failed to open '" << filename
.value() << "'";
111 return HRESULT_FROM_WIN32(error
);
114 scoped_ptr
<char[]> buffer(new char[kMaxConfigFileSize
]);
115 DWORD size
= kMaxConfigFileSize
;
116 if (!::ReadFile(file
.Get(), &buffer
[0], size
, &size
, NULL
)) {
117 DWORD error
= GetLastError();
118 PLOG(ERROR
) << "Failed to read '" << filename
.value() << "'";
119 return HRESULT_FROM_WIN32(error
);
122 // Parse the JSON configuration, expecting it to contain a dictionary.
123 std::string
file_content(buffer
.get(), size
);
124 scoped_ptr
<base::Value
> value(
125 base::JSONReader::Read(file_content
, base::JSON_ALLOW_TRAILING_COMMAS
));
127 base::DictionaryValue
* dictionary
;
128 if (value
.get() == NULL
|| !value
->GetAsDictionary(&dictionary
)) {
129 LOG(ERROR
) << "Failed to read '" << filename
.value() << "'.";
134 config_out
->reset(dictionary
);
138 base::FilePath
GetTempLocationFor(const base::FilePath
& filename
) {
139 return filename
.ReplaceExtension(kTempFileExtension
);
142 // Writes a config file to a temporary location.
143 HRESULT
WriteConfigFileToTemp(const base::FilePath
& filename
,
144 const char* security_descriptor
,
147 // Create the security descriptor for the configuration file.
148 ScopedSd sd
= ConvertSddlToSd(security_descriptor
);
150 DWORD error
= GetLastError();
152 << "Failed to create a security descriptor for the configuration file";
153 return HRESULT_FROM_WIN32(error
);
156 SECURITY_ATTRIBUTES security_attributes
= {0};
157 security_attributes
.nLength
= sizeof(security_attributes
);
158 security_attributes
.lpSecurityDescriptor
= sd
.get();
159 security_attributes
.bInheritHandle
= FALSE
;
161 // Create a temporary file and write configuration to it.
162 base::FilePath tempname
= GetTempLocationFor(filename
);
163 base::win::ScopedHandle
file(
164 CreateFileW(tempname
.value().c_str(),
167 &security_attributes
,
169 FILE_FLAG_SEQUENTIAL_SCAN
,
172 if (!file
.IsValid()) {
173 DWORD error
= GetLastError();
174 PLOG(ERROR
) << "Failed to create '" << filename
.value() << "'";
175 return HRESULT_FROM_WIN32(error
);
179 if (!WriteFile(file
.Get(), content
, static_cast<DWORD
>(length
), &written
,
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(kHostOwnerEmailConfigPath
, &email
)) {
223 if (!config_dict
->GetString(kHostOwnerConfigPath
, &email
)) {
224 if (!config_dict
->GetString(kXmppLoginConfigPath
, &email
)) {
229 std::string host_id
, host_secret_hash
;
230 if (!config_dict
->GetString(kHostIdConfigPath
, &host_id
) ||
231 !config_dict
->GetString(kHostSecretHashConfigPath
, &host_secret_hash
)) {
235 // Ask the user to verify the configuration (unless the client is admin
237 if (!IsClientAdmin()) {
238 remoting::VerifyConfigWindowWin
verify_win(email
, host_id
,
240 DWORD error
= verify_win
.DoModal(owner_window
);
241 if (error
!= ERROR_SUCCESS
) {
242 return HRESULT_FROM_WIN32(error
);
246 // Extract the unprivileged fields from the configuration.
247 base::DictionaryValue unprivileged_config_dict
;
248 for (int i
= 0; i
< arraysize(kUnprivilegedConfigKeys
); ++i
) {
249 const char* key
= kUnprivilegedConfigKeys
[i
];
250 base::string16 value
;
251 if (config_dict
->GetString(key
, &value
)) {
252 unprivileged_config_dict
.SetString(key
, value
);
255 std::string unprivileged_config_str
;
256 base::JSONWriter::Write(&unprivileged_config_dict
, &unprivileged_config_str
);
258 // Write the full configuration file to a temporary location.
259 base::FilePath full_config_file_path
=
260 remoting::GetConfigDir().Append(kConfigFileName
);
261 HRESULT hr
= WriteConfigFileToTemp(full_config_file_path
,
262 kConfigFileSecurityDescriptor
,
269 // Write the unprivileged configuration file to a temporary location.
270 base::FilePath unprivileged_config_file_path
=
271 remoting::GetConfigDir().Append(kUnprivilegedConfigFileName
);
272 hr
= WriteConfigFileToTemp(unprivileged_config_file_path
,
273 kUnprivilegedConfigFileSecurityDescriptor
,
274 unprivileged_config_str
.data(),
275 unprivileged_config_str
.size());
280 // Move the full configuration file to its permanent location.
281 hr
= MoveConfigFileFromTemp(full_config_file_path
);
286 // Move the unprivileged configuration file to its permanent location.
287 hr
= MoveConfigFileFromTemp(unprivileged_config_file_path
);
297 ElevatedController::ElevatedController() : owner_window_(NULL
) {
300 HRESULT
ElevatedController::FinalConstruct() {
304 void ElevatedController::FinalRelease() {
307 STDMETHODIMP
ElevatedController::GetConfig(BSTR
* config_out
) {
308 base::FilePath config_dir
= remoting::GetConfigDir();
310 // Read the unprivileged part of host configuration.
311 scoped_ptr
<base::DictionaryValue
> config
;
312 HRESULT hr
= ReadConfig(config_dir
.Append(kUnprivilegedConfigFileName
),
318 // Convert the config back to a string and return it to the caller.
319 std::string file_content
;
320 base::JSONWriter::Write(config
.get(), &file_content
);
322 *config_out
= ::SysAllocString(base::UTF8ToUTF16(file_content
).c_str());
323 if (config_out
== NULL
) {
324 return E_OUTOFMEMORY
;
330 STDMETHODIMP
ElevatedController::GetVersion(BSTR
* version_out
) {
331 // Report the product version number of the daemon controller binary as
333 HMODULE binary
= base::GetModuleFromAddress(
334 reinterpret_cast<void*>(&ReadConfig
));
335 scoped_ptr
<FileVersionInfo
> version_info(
336 FileVersionInfo::CreateFileVersionInfoForModule(binary
));
338 base::string16 version
;
339 if (version_info
.get()) {
340 version
= version_info
->product_version();
343 *version_out
= ::SysAllocString(version
.c_str());
344 if (version_out
== NULL
) {
345 return E_OUTOFMEMORY
;
351 STDMETHODIMP
ElevatedController::SetConfig(BSTR config
) {
352 // Determine the config directory path and create it if necessary.
353 base::FilePath config_dir
= remoting::GetConfigDir();
354 if (!base::CreateDirectory(config_dir
)) {
355 return HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED
);
358 std::string file_content
= base::UTF16ToUTF8(
359 base::string16(static_cast<base::char16
*>(config
), ::SysStringLen(config
)));
361 return WriteConfig(file_content
.c_str(), file_content
.size(), owner_window_
);
364 STDMETHODIMP
ElevatedController::SetOwnerWindow(LONG_PTR window_handle
) {
365 owner_window_
= reinterpret_cast<HWND
>(window_handle
);
369 STDMETHODIMP
ElevatedController::StartDaemon() {
370 ScopedScHandle service
;
371 HRESULT hr
= OpenService(&service
);
376 // Change the service start type to 'auto'.
377 if (!::ChangeServiceConfigW(service
.Get(),
388 DWORD error
= GetLastError();
389 PLOG(ERROR
) << "Failed to change the '" << kWindowsServiceName
390 << "'service start type to 'auto'";
391 return HRESULT_FROM_WIN32(error
);
394 // Start the service.
395 if (!StartService(service
.Get(), 0, NULL
)) {
396 DWORD error
= GetLastError();
397 if (error
!= ERROR_SERVICE_ALREADY_RUNNING
) {
398 PLOG(ERROR
) << "Failed to start the '" << kWindowsServiceName
401 return HRESULT_FROM_WIN32(error
);
408 STDMETHODIMP
ElevatedController::StopDaemon() {
409 ScopedScHandle service
;
410 HRESULT hr
= OpenService(&service
);
415 // Change the service start type to 'manual'.
416 if (!::ChangeServiceConfigW(service
.Get(),
418 SERVICE_DEMAND_START
,
427 DWORD error
= GetLastError();
428 PLOG(ERROR
) << "Failed to change the '" << kWindowsServiceName
429 << "'service start type to 'manual'";
430 return HRESULT_FROM_WIN32(error
);
434 SERVICE_STATUS status
;
435 if (!ControlService(service
.Get(), SERVICE_CONTROL_STOP
, &status
)) {
436 DWORD error
= GetLastError();
437 if (error
!= ERROR_SERVICE_NOT_ACTIVE
) {
438 PLOG(ERROR
) << "Failed to stop the '" << kWindowsServiceName
440 return HRESULT_FROM_WIN32(error
);
447 STDMETHODIMP
ElevatedController::UpdateConfig(BSTR config
) {
449 std::string config_str
= base::UTF16ToUTF8(
450 base::string16(static_cast<base::char16
*>(config
), ::SysStringLen(config
)));
451 scoped_ptr
<base::Value
> config_value(base::JSONReader::Read(config_str
));
452 if (!config_value
.get()) {
455 base::DictionaryValue
* config_dict
= NULL
;
456 if (!config_value
->GetAsDictionary(&config_dict
)) {
459 // Check for bad keys.
460 for (int i
= 0; i
< arraysize(kReadonlyKeys
); ++i
) {
461 if (config_dict
->HasKey(kReadonlyKeys
[i
])) {
462 return HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED
);
465 // Get the old config.
466 base::FilePath config_dir
= remoting::GetConfigDir();
467 scoped_ptr
<base::DictionaryValue
> config_old
;
468 HRESULT hr
= ReadConfig(config_dir
.Append(kConfigFileName
), &config_old
);
472 // Merge items from the given config into the old config.
473 config_old
->MergeDictionary(config_dict
);
474 // Write the updated config.
475 std::string config_updated_str
;
476 base::JSONWriter::Write(config_old
.get(), &config_updated_str
);
477 return WriteConfig(config_updated_str
.c_str(), config_updated_str
.size(),
481 STDMETHODIMP
ElevatedController::GetUsageStatsConsent(BOOL
* allowed
,
482 BOOL
* set_by_policy
) {
484 bool local_set_by_policy
;
485 if (remoting::GetUsageStatsConsent(&local_allowed
, &local_set_by_policy
)) {
486 *allowed
= local_allowed
;
487 *set_by_policy
= local_set_by_policy
;
494 STDMETHODIMP
ElevatedController::SetUsageStatsConsent(BOOL allowed
) {
495 if (remoting::SetUsageStatsConsent(!!allowed
)) {
502 HRESULT
ElevatedController::OpenService(ScopedScHandle
* service_out
) {
505 ScopedScHandle
scmanager(
506 ::OpenSCManagerW(NULL
, SERVICES_ACTIVE_DATABASE
,
507 SC_MANAGER_CONNECT
| SC_MANAGER_ENUMERATE_SERVICE
));
508 if (!scmanager
.IsValid()) {
509 error
= GetLastError();
510 PLOG(ERROR
) << "Failed to connect to the service control manager";
512 return HRESULT_FROM_WIN32(error
);
515 DWORD desired_access
= SERVICE_CHANGE_CONFIG
| SERVICE_QUERY_STATUS
|
516 SERVICE_START
| SERVICE_STOP
;
517 ScopedScHandle
service(
518 ::OpenServiceW(scmanager
.Get(), kWindowsServiceName
, desired_access
));
519 if (!service
.IsValid()) {
520 error
= GetLastError();
521 PLOG(ERROR
) << "Failed to open to the '" << kWindowsServiceName
524 return HRESULT_FROM_WIN32(error
);
527 service_out
->Set(service
.Take());
531 } // namespace remoting