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
=
76 base::JSONReader::Read(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
);
269 DWORD access
= SERVICE_CHANGE_CONFIG
| SERVICE_QUERY_STATUS
|
270 SERVICE_START
| SERVICE_STOP
;
271 ScopedScHandle service
= OpenService(access
);
272 if (!service
.IsValid())
275 // Change the service start type to 'auto'.
276 if (!::ChangeServiceConfigW(service
.Get(),
287 PLOG(ERROR
) << "Failed to change the '" << kWindowsServiceName
288 << "'service start type to 'auto'";
292 // Start the service.
293 if (!StartService(service
.Get(), 0, nullptr)) {
294 DWORD error
= GetLastError();
295 if (error
!= ERROR_SERVICE_ALREADY_RUNNING
) {
296 LOG(ERROR
) << "Failed to start the '" << kWindowsServiceName
297 << "'service: " << error
;
307 DWORD access
= SERVICE_CHANGE_CONFIG
| SERVICE_QUERY_STATUS
|
308 SERVICE_START
| SERVICE_STOP
;
309 ScopedScHandle service
= OpenService(access
);
310 if (!service
.IsValid())
313 // Change the service start type to 'manual'.
314 if (!::ChangeServiceConfigW(service
.Get(),
316 SERVICE_DEMAND_START
,
325 PLOG(ERROR
) << "Failed to change the '" << kWindowsServiceName
326 << "'service start type to 'manual'";
331 SERVICE_STATUS status
;
332 if (!ControlService(service
.Get(), SERVICE_CONTROL_STOP
, &status
)) {
333 DWORD error
= GetLastError();
334 if (error
!= ERROR_SERVICE_NOT_ACTIVE
) {
335 LOG(ERROR
) << "Failed to stop the '" << kWindowsServiceName
336 << "'service: " << error
;
346 DaemonControllerDelegateWin::DaemonControllerDelegateWin() {
349 DaemonControllerDelegateWin::~DaemonControllerDelegateWin() {
352 DaemonController::State
DaemonControllerDelegateWin::GetState() {
353 // TODO(alexeypa): Make the thread alertable, so we can switch to APC
354 // notifications rather than polling.
355 ScopedScHandle service
= OpenService(SERVICE_QUERY_STATUS
);
356 if (!service
.IsValid())
357 return DaemonController::STATE_UNKNOWN
;
359 SERVICE_STATUS status
;
360 if (!::QueryServiceStatus(service
.Get(), &status
)) {
361 PLOG(ERROR
) << "Failed to query the state of the '"
362 << kWindowsServiceName
<< "' service";
363 return DaemonController::STATE_UNKNOWN
;
366 return ConvertToDaemonState(status
.dwCurrentState
);
369 scoped_ptr
<base::DictionaryValue
> DaemonControllerDelegateWin::GetConfig() {
370 base::FilePath config_dir
= remoting::GetConfigDir();
372 // Read the unprivileged part of host configuration.
373 scoped_ptr
<base::DictionaryValue
> config
;
374 if (!ReadConfig(config_dir
.Append(kUnprivilegedConfigFileName
), &config
))
380 void DaemonControllerDelegateWin::UpdateConfig(
381 scoped_ptr
<base::DictionaryValue
> config
,
382 const DaemonController::CompletionCallback
& done
) {
383 // Check for bad keys.
384 for (int i
= 0; i
< arraysize(kReadonlyKeys
); ++i
) {
385 if (config
->HasKey(kReadonlyKeys
[i
])) {
386 LOG(ERROR
) << "Cannot update config: '" << kReadonlyKeys
[i
]
387 << "' is read only.";
388 InvokeCompletionCallback(done
, false);
392 // Get the old config.
393 base::FilePath config_dir
= remoting::GetConfigDir();
394 scoped_ptr
<base::DictionaryValue
> config_old
;
395 if (!ReadConfig(config_dir
.Append(kConfigFileName
), &config_old
)) {
396 InvokeCompletionCallback(done
, false);
400 // Merge items from the given config into the old config.
401 config_old
->MergeDictionary(config
.release());
403 // Write the updated config.
404 std::string config_updated_str
;
405 base::JSONWriter::Write(*config_old
, &config_updated_str
);
406 bool result
= WriteConfig(config_updated_str
);
408 InvokeCompletionCallback(done
, result
);
411 void DaemonControllerDelegateWin::Stop(
412 const DaemonController::CompletionCallback
& done
) {
413 bool result
= StopDaemon();
415 InvokeCompletionCallback(done
, result
);
418 DaemonController::UsageStatsConsent
419 DaemonControllerDelegateWin::GetUsageStatsConsent() {
420 DaemonController::UsageStatsConsent consent
;
421 consent
.supported
= true;
422 consent
.allowed
= false;
423 consent
.set_by_policy
= false;
425 // Get the recorded user's consent.
428 // If the user's consent is not recorded yet, assume that the user didn't
429 // consent to collecting crash dumps.
430 if (remoting::GetUsageStatsConsent(&allowed
, &set_by_policy
)) {
431 consent
.allowed
= allowed
;
432 consent
.set_by_policy
= set_by_policy
;
438 void DaemonControllerDelegateWin::SetConfigAndStart(
439 scoped_ptr
<base::DictionaryValue
> config
,
441 const DaemonController::CompletionCallback
& done
) {
442 // Record the user's consent.
443 if (!remoting::SetUsageStatsConsent(consent
)) {
444 InvokeCompletionCallback(done
, false);
448 // Set the configuration.
449 std::string config_str
;
450 base::JSONWriter::Write(*config
, &config_str
);
452 // Determine the config directory path and create it if necessary.
453 base::FilePath config_dir
= remoting::GetConfigDir();
454 if (!base::CreateDirectory(config_dir
)) {
455 PLOG(ERROR
) << "Failed to create the config directory.";
456 InvokeCompletionCallback(done
, false);
460 if (!WriteConfig(config_str
)) {
461 InvokeCompletionCallback(done
, false);
466 InvokeCompletionCallback(done
, StartDaemon());
469 scoped_refptr
<DaemonController
> DaemonController::Create() {
470 scoped_ptr
<DaemonController::Delegate
> delegate(
471 new DaemonControllerDelegateWin());
472 return new DaemonController(delegate
.Pass());
475 } // namespace remoting