1 // Copyright 2013 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/setup/daemon_controller_delegate_win.h"
7 #include "base/basictypes.h"
8 #include "base/files/file_path.h"
9 #include "base/files/file_util.h"
10 #include "base/json/json_reader.h"
11 #include "base/json/json_writer.h"
12 #include "base/logging.h"
13 #include "base/thread_task_runner_handle.h"
14 #include "base/values.h"
15 #include "base/win/scoped_bstr.h"
16 #include "base/win/windows_version.h"
17 #include "remoting/base/scoped_sc_handle_win.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/win/security_descriptor.h"
27 // The maximum size of the configuration file. "1MB ought to be enough" for any
28 // reasonable configuration we will ever need. 1MB is low enough to make
29 // the probability of out of memory situation fairly low. OOM is still possible
30 // and we will crash if it occurs.
31 const size_t kMaxConfigFileSize
= 1024 * 1024;
33 // The host configuration file name.
34 const base::FilePath::CharType kConfigFileName
[] =
35 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
[] =
43 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 // Reads and parses the configuration file up to |kMaxConfigFileSize| in
66 bool ReadConfig(const base::FilePath
& filename
,
67 scoped_ptr
<base::DictionaryValue
>* config_out
) {
68 std::string file_content
;
69 if (!base::ReadFileToString(filename
, &file_content
, kMaxConfigFileSize
)) {
70 PLOG(ERROR
) << "Failed to read '" << filename
.value() << "'.";
74 // Parse the JSON configuration, expecting it to contain a dictionary.
75 scoped_ptr
<base::Value
> value(base::JSONReader::DeprecatedRead(
76 file_content
, base::JSON_ALLOW_TRAILING_COMMAS
));
78 base::DictionaryValue
* dictionary
;
79 if (!value
|| !value
->GetAsDictionary(&dictionary
)) {
80 LOG(ERROR
) << "Failed to parse '" << filename
.value() << "'.";
85 config_out
->reset(dictionary
);
89 base::FilePath
GetTempLocationFor(const base::FilePath
& filename
) {
90 return filename
.ReplaceExtension(kTempFileExtension
);
93 // Writes a config file to a temporary location.
94 bool WriteConfigFileToTemp(const base::FilePath
& filename
,
95 const char* security_descriptor
,
96 const std::string
& content
) {
97 // Create the security descriptor for the configuration file.
98 ScopedSd sd
= ConvertSddlToSd(security_descriptor
);
101 << "Failed to create a security descriptor for the configuration file";
105 SECURITY_ATTRIBUTES security_attributes
= {0};
106 security_attributes
.nLength
= sizeof(security_attributes
);
107 security_attributes
.lpSecurityDescriptor
= sd
.get();
108 security_attributes
.bInheritHandle
= FALSE
;
110 // Create a temporary file and write configuration to it.
111 base::FilePath tempname
= GetTempLocationFor(filename
);
112 base::win::ScopedHandle
file(
113 CreateFileW(tempname
.value().c_str(),
116 &security_attributes
,
118 FILE_FLAG_SEQUENTIAL_SCAN
,
121 if (!file
.IsValid()) {
122 PLOG(ERROR
) << "Failed to create '" << filename
.value() << "'";
127 if (!::WriteFile(file
.Get(), content
.c_str(), content
.length(),
128 &written
, nullptr)) {
129 PLOG(ERROR
) << "Failed to write to '" << filename
.value() << "'";
136 // Moves a config file from its temporary location to its permanent location.
137 bool MoveConfigFileFromTemp(const base::FilePath
& filename
) {
138 // Now that the configuration is stored successfully replace the actual
139 // configuration file.
140 base::FilePath tempname
= GetTempLocationFor(filename
);
141 if (!MoveFileExW(tempname
.value().c_str(),
142 filename
.value().c_str(),
143 MOVEFILE_REPLACE_EXISTING
)) {
144 PLOG(ERROR
) << "Failed to rename '" << tempname
.value() << "' to '"
145 << filename
.value() << "'";
152 // Writes the configuration file up to |kMaxConfigFileSize| in size.
153 bool WriteConfig(const std::string
& content
) {
154 if (content
.length() > kMaxConfigFileSize
) {
158 // Extract the configuration data that the user will verify.
159 scoped_ptr
<base::Value
> config_value
= base::JSONReader::Read(content
);
160 if (!config_value
.get()) {
163 base::DictionaryValue
* config_dict
= nullptr;
164 if (!config_value
->GetAsDictionary(&config_dict
)) {
168 if (!config_dict
->GetString(kHostOwnerEmailConfigPath
, &email
) &&
169 !config_dict
->GetString(kHostOwnerConfigPath
, &email
) &&
170 !config_dict
->GetString(kXmppLoginConfigPath
, &email
)) {
173 std::string host_id
, host_secret_hash
;
174 if (!config_dict
->GetString(kHostIdConfigPath
, &host_id
) ||
175 !config_dict
->GetString(kHostSecretHashConfigPath
, &host_secret_hash
)) {
179 // Extract the unprivileged fields from the configuration.
180 base::DictionaryValue unprivileged_config_dict
;
181 for (int i
= 0; i
< arraysize(kUnprivilegedConfigKeys
); ++i
) {
182 const char* key
= kUnprivilegedConfigKeys
[i
];
183 base::string16 value
;
184 if (config_dict
->GetString(key
, &value
)) {
185 unprivileged_config_dict
.SetString(key
, value
);
188 std::string unprivileged_config_str
;
189 base::JSONWriter::Write(unprivileged_config_dict
, &unprivileged_config_str
);
191 // Write the full configuration file to a temporary location.
192 base::FilePath full_config_file_path
=
193 remoting::GetConfigDir().Append(kConfigFileName
);
194 if (!WriteConfigFileToTemp(full_config_file_path
,
195 kConfigFileSecurityDescriptor
,
200 // Write the unprivileged configuration file to a temporary location.
201 base::FilePath unprivileged_config_file_path
=
202 remoting::GetConfigDir().Append(kUnprivilegedConfigFileName
);
203 if (!WriteConfigFileToTemp(unprivileged_config_file_path
,
204 kUnprivilegedConfigFileSecurityDescriptor
,
205 unprivileged_config_str
)) {
209 // Move the full and unprivileged configuration files to their permanent
211 return MoveConfigFileFromTemp(full_config_file_path
) &&
212 MoveConfigFileFromTemp(unprivileged_config_file_path
);
215 DaemonController::State
ConvertToDaemonState(DWORD service_state
) {
216 switch (service_state
) {
217 case SERVICE_RUNNING
:
218 return DaemonController::STATE_STARTED
;
220 case SERVICE_CONTINUE_PENDING
:
221 case SERVICE_START_PENDING
:
222 return DaemonController::STATE_STARTING
;
225 case SERVICE_PAUSE_PENDING
:
226 case SERVICE_STOP_PENDING
:
227 return DaemonController::STATE_STOPPING
;
231 case SERVICE_STOPPED
:
232 return DaemonController::STATE_STOPPED
;
237 return DaemonController::STATE_UNKNOWN
;
241 ScopedScHandle
OpenService(DWORD access
) {
242 // Open the service and query its current state.
243 ScopedScHandle
scmanager(
244 ::OpenSCManagerW(nullptr, SERVICES_ACTIVE_DATABASE
,
245 SC_MANAGER_CONNECT
| SC_MANAGER_ENUMERATE_SERVICE
));
246 if (!scmanager
.IsValid()) {
247 PLOG(ERROR
) << "Failed to connect to the service control manager";
248 return ScopedScHandle();
251 ScopedScHandle
service(::OpenServiceW(scmanager
.Get(), kWindowsServiceName
,
253 if (!service
.IsValid()) {
254 PLOG(ERROR
) << "Failed to open to the '" << kWindowsServiceName
258 return service
.Pass();
261 void InvokeCompletionCallback(
262 const DaemonController::CompletionCallback
& done
, bool success
) {
263 DaemonController::AsyncResult async_result
=
264 success
? DaemonController::RESULT_OK
: DaemonController::RESULT_FAILED
;
265 done
.Run(async_result
);
268 bool SetConfig(const std::string
& config
) {
269 // Determine the config directory path and create it if necessary.
270 base::FilePath config_dir
= remoting::GetConfigDir();
271 if (!base::CreateDirectory(config_dir
)) {
272 PLOG(ERROR
) << "Failed to create the config directory.";
276 return WriteConfig(config
);
280 DWORD access
= SERVICE_CHANGE_CONFIG
| SERVICE_QUERY_STATUS
|
281 SERVICE_START
| SERVICE_STOP
;
282 ScopedScHandle service
= OpenService(access
);
283 if (!service
.IsValid())
286 // Change the service start type to 'auto'.
287 if (!::ChangeServiceConfigW(service
.Get(),
298 PLOG(ERROR
) << "Failed to change the '" << kWindowsServiceName
299 << "'service start type to 'auto'";
303 // Start the service.
304 if (!StartService(service
.Get(), 0, nullptr)) {
305 DWORD error
= GetLastError();
306 if (error
!= ERROR_SERVICE_ALREADY_RUNNING
) {
307 LOG(ERROR
) << "Failed to start the '" << kWindowsServiceName
308 << "'service: " << error
;
318 DWORD access
= SERVICE_CHANGE_CONFIG
| SERVICE_QUERY_STATUS
|
319 SERVICE_START
| SERVICE_STOP
;
320 ScopedScHandle service
= OpenService(access
);
321 if (!service
.IsValid())
324 // Change the service start type to 'manual'.
325 if (!::ChangeServiceConfigW(service
.Get(),
327 SERVICE_DEMAND_START
,
336 PLOG(ERROR
) << "Failed to change the '" << kWindowsServiceName
337 << "'service start type to 'manual'";
342 SERVICE_STATUS status
;
343 if (!ControlService(service
.Get(), SERVICE_CONTROL_STOP
, &status
)) {
344 DWORD error
= GetLastError();
345 if (error
!= ERROR_SERVICE_NOT_ACTIVE
) {
346 LOG(ERROR
) << "Failed to stop the '" << kWindowsServiceName
347 << "'service: " << error
;
357 DaemonControllerDelegateWin::DaemonControllerDelegateWin() {
360 DaemonControllerDelegateWin::~DaemonControllerDelegateWin() {
363 DaemonController::State
DaemonControllerDelegateWin::GetState() {
364 // TODO(alexeypa): Make the thread alertable, so we can switch to APC
365 // notifications rather than polling.
366 ScopedScHandle service
= OpenService(SERVICE_QUERY_STATUS
);
367 if (!service
.IsValid())
368 return DaemonController::STATE_UNKNOWN
;
370 SERVICE_STATUS status
;
371 if (!::QueryServiceStatus(service
.Get(), &status
)) {
372 PLOG(ERROR
) << "Failed to query the state of the '"
373 << kWindowsServiceName
<< "' service";
374 return DaemonController::STATE_UNKNOWN
;
377 return ConvertToDaemonState(status
.dwCurrentState
);
380 scoped_ptr
<base::DictionaryValue
> DaemonControllerDelegateWin::GetConfig() {
381 base::FilePath config_dir
= remoting::GetConfigDir();
383 // Read the unprivileged part of host configuration.
384 scoped_ptr
<base::DictionaryValue
> config
;
385 if (!ReadConfig(config_dir
.Append(kUnprivilegedConfigFileName
), &config
))
391 void DaemonControllerDelegateWin::UpdateConfig(
392 scoped_ptr
<base::DictionaryValue
> config
,
393 const DaemonController::CompletionCallback
& done
) {
394 // Check for bad keys.
395 for (int i
= 0; i
< arraysize(kReadonlyKeys
); ++i
) {
396 if (config
->HasKey(kReadonlyKeys
[i
])) {
397 LOG(ERROR
) << "Cannot update config: '" << kReadonlyKeys
[i
]
398 << "' is read only.";
399 InvokeCompletionCallback(done
, false);
403 // Get the old config.
404 base::FilePath config_dir
= remoting::GetConfigDir();
405 scoped_ptr
<base::DictionaryValue
> config_old
;
406 if (!ReadConfig(config_dir
.Append(kConfigFileName
), &config_old
)) {
407 InvokeCompletionCallback(done
, false);
411 // Merge items from the given config into the old config.
412 config_old
->MergeDictionary(config
.release());
414 // Write the updated config.
415 std::string config_updated_str
;
416 base::JSONWriter::Write(*config_old
, &config_updated_str
);
417 bool result
= WriteConfig(config_updated_str
);
419 InvokeCompletionCallback(done
, result
);
422 void DaemonControllerDelegateWin::Stop(
423 const DaemonController::CompletionCallback
& done
) {
424 bool result
= StopDaemon();
426 InvokeCompletionCallback(done
, result
);
429 DaemonController::UsageStatsConsent
430 DaemonControllerDelegateWin::GetUsageStatsConsent() {
431 DaemonController::UsageStatsConsent consent
;
432 consent
.supported
= true;
433 consent
.allowed
= false;
434 consent
.set_by_policy
= false;
436 // Get the recorded user's consent.
439 // If the user's consent is not recorded yet, assume that the user didn't
440 // consent to collecting crash dumps.
441 if (remoting::GetUsageStatsConsent(&allowed
, &set_by_policy
)) {
442 consent
.allowed
= allowed
;
443 consent
.set_by_policy
= set_by_policy
;
449 void DaemonControllerDelegateWin::SetConfigAndStart(
450 scoped_ptr
<base::DictionaryValue
> config
,
452 const DaemonController::CompletionCallback
& done
) {
453 // Record the user's consent.
454 if (!remoting::SetUsageStatsConsent(consent
)) {
455 InvokeCompletionCallback(done
, false);
459 // Set the configuration.
460 std::string config_str
;
461 base::JSONWriter::Write(*config
, &config_str
);
463 // Determine the config directory path and create it if necessary.
464 base::FilePath config_dir
= remoting::GetConfigDir();
465 if (!base::CreateDirectory(config_dir
)) {
466 PLOG(ERROR
) << "Failed to create the config directory.";
467 InvokeCompletionCallback(done
, false);
471 if (!WriteConfig(config_str
)) {
472 InvokeCompletionCallback(done
, false);
477 InvokeCompletionCallback(done
, StartDaemon());
480 scoped_refptr
<DaemonController
> DaemonController::Create() {
481 scoped_ptr
<DaemonController::Delegate
> delegate(
482 new DaemonControllerDelegateWin());
483 return new DaemonController(delegate
.Pass());
486 } // namespace remoting