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 LOG_GETLASTERROR(ERROR
)
112 << "Failed to open '" << filename
.value() << "'";
113 return HRESULT_FROM_WIN32(error
);
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
);
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
));
130 base::DictionaryValue
* dictionary
;
131 if (value
.get() == NULL
|| !value
->GetAsDictionary(&dictionary
)) {
132 LOG(ERROR
) << "Failed to read '" << filename
.value() << "'.";
137 config_out
->reset(dictionary
);
141 base::FilePath
GetTempLocationFor(const base::FilePath
& filename
) {
142 return filename
.ReplaceExtension(kTempFileExtension
);
145 // Writes a config file to a temporary location.
146 HRESULT
WriteConfigFileToTemp(const base::FilePath
& filename
,
147 const char* security_descriptor
,
150 // Create the security descriptor for the configuration file.
151 ScopedSd sd
= ConvertSddlToSd(security_descriptor
);
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
);
159 SECURITY_ATTRIBUTES security_attributes
= {0};
160 security_attributes
.nLength
= sizeof(security_attributes
);
161 security_attributes
.lpSecurityDescriptor
= sd
.get();
162 security_attributes
.bInheritHandle
= FALSE
;
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(),
170 &security_attributes
,
172 FILE_FLAG_SEQUENTIAL_SCAN
,
175 if (!file
.IsValid()) {
176 DWORD error
= GetLastError();
177 LOG_GETLASTERROR(ERROR
)
178 << "Failed to create '" << filename
.value() << "'";
179 return HRESULT_FROM_WIN32(error
);
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
);
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
);
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
) {
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()) {
222 base::DictionaryValue
* config_dict
= NULL
;
223 if (!config_value
->GetAsDictionary(&config_dict
)) {
227 if (!config_dict
->GetString(kHostOwner
, &email
)) {
228 if (!config_dict
->GetString(kXmppLogin
, &email
)) {
232 std::string host_id
, host_secret_hash
;
233 if (!config_dict
->GetString(kHostId
, &host_id
) ||
234 !config_dict
->GetString(kHostSecretHash
, &host_secret_hash
)) {
238 // Ask the user to verify the configuration (unless the client is admin
240 if (!IsClientAdmin()) {
241 remoting::VerifyConfigWindowWin
verify_win(email
, host_id
,
243 DWORD error
= verify_win
.DoModal(owner_window
);
244 if (error
!= ERROR_SUCCESS
) {
245 return HRESULT_FROM_WIN32(error
);
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
);
258 std::string unprivileged_config_str
;
259 base::JSONWriter::Write(&unprivileged_config_dict
, &unprivileged_config_str
);
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
,
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());
283 // Move the full configuration file to its permanent location.
284 hr
= MoveConfigFileFromTemp(full_config_file_path
);
289 // Move the unprivileged configuration file to its permanent location.
290 hr
= MoveConfigFileFromTemp(unprivileged_config_file_path
);
300 ElevatedController::ElevatedController() : owner_window_(NULL
) {
303 HRESULT
ElevatedController::FinalConstruct() {
307 void ElevatedController::FinalRelease() {
310 STDMETHODIMP
ElevatedController::GetConfig(BSTR
* config_out
) {
311 base::FilePath config_dir
= remoting::GetConfigDir();
313 // Read the unprivileged part of host configuration.
314 scoped_ptr
<base::DictionaryValue
> config
;
315 HRESULT hr
= ReadConfig(config_dir
.Append(kUnprivilegedConfigFileName
),
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
);
325 *config_out
= ::SysAllocString(base::UTF8ToUTF16(file_content
).c_str());
326 if (config_out
== NULL
) {
327 return E_OUTOFMEMORY
;
333 STDMETHODIMP
ElevatedController::GetVersion(BSTR
* version_out
) {
334 // Report the product version number of the daemon controller binary as
336 HMODULE binary
= base::GetModuleFromAddress(
337 reinterpret_cast<void*>(&ReadConfig
));
338 scoped_ptr
<FileVersionInfo
> version_info(
339 FileVersionInfo::CreateFileVersionInfoForModule(binary
));
341 base::string16 version
;
342 if (version_info
.get()) {
343 version
= version_info
->product_version();
346 *version_out
= ::SysAllocString(version
.c_str());
347 if (version_out
== NULL
) {
348 return E_OUTOFMEMORY
;
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
);
361 std::string file_content
= base::UTF16ToUTF8(
362 base::string16(static_cast<base::char16
*>(config
), ::SysStringLen(config
)));
364 return WriteConfig(file_content
.c_str(), file_content
.size(), owner_window_
);
367 STDMETHODIMP
ElevatedController::SetOwnerWindow(LONG_PTR window_handle
) {
368 owner_window_
= reinterpret_cast<HWND
>(window_handle
);
372 STDMETHODIMP
ElevatedController::StartDaemon() {
373 ScopedScHandle service
;
374 HRESULT hr
= OpenService(&service
);
379 // Change the service start type to 'auto'.
380 if (!::ChangeServiceConfigW(service
,
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
);
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";
405 return HRESULT_FROM_WIN32(error
);
412 STDMETHODIMP
ElevatedController::StopDaemon() {
413 ScopedScHandle service
;
414 HRESULT hr
= OpenService(&service
);
419 // Change the service start type to 'manual'.
420 if (!::ChangeServiceConfigW(service
,
422 SERVICE_DEMAND_START
,
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
);
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
);
452 STDMETHODIMP
ElevatedController::UpdateConfig(BSTR config
) {
454 std::string config_str
= base::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()) {
460 base::DictionaryValue
* config_dict
= NULL
;
461 if (!config_value
->GetAsDictionary(&config_dict
)) {
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
);
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
);
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(),
486 STDMETHODIMP
ElevatedController::GetUsageStatsConsent(BOOL
* allowed
,
487 BOOL
* set_by_policy
) {
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
;
499 STDMETHODIMP
ElevatedController::SetUsageStatsConsent(BOOL allowed
) {
500 if (remoting::SetUsageStatsConsent(!!allowed
)) {
507 HRESULT
ElevatedController::OpenService(ScopedScHandle
* service_out
) {
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";
518 return HRESULT_FROM_WIN32(error
);
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";
530 return HRESULT_FROM_WIN32(error
);
533 service_out
->Set(service
.Take());
537 } // namespace remoting