Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / remoting / host / setup / daemon_controller_delegate_win.cc
blob7b3366492ecc5b0f6b637306b39996b3488f6c3b
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"
23 namespace remoting {
25 namespace {
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
65 // size.
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() << "'.";
71 return false;
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() << "'.";
81 return false;
84 value.release();
85 config_out->reset(dictionary);
86 return true;
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);
99 if (!sd) {
100 PLOG(ERROR)
101 << "Failed to create a security descriptor for the configuration file";
102 return false;
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(),
114 GENERIC_WRITE,
116 &security_attributes,
117 CREATE_ALWAYS,
118 FILE_FLAG_SEQUENTIAL_SCAN,
119 nullptr));
121 if (!file.IsValid()) {
122 PLOG(ERROR) << "Failed to create '" << filename.value() << "'";
123 return false;
126 DWORD written;
127 if (!::WriteFile(file.Get(), content.c_str(), content.length(),
128 &written, nullptr)) {
129 PLOG(ERROR) << "Failed to write to '" << filename.value() << "'";
130 return false;
133 return true;
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() << "'";
146 return false;
149 return true;
152 // Writes the configuration file up to |kMaxConfigFileSize| in size.
153 bool WriteConfig(const std::string& content) {
154 if (content.length() > kMaxConfigFileSize) {
155 return false;
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()) {
161 return false;
163 base::DictionaryValue* config_dict = nullptr;
164 if (!config_value->GetAsDictionary(&config_dict)) {
165 return false;
167 std::string email;
168 if (!config_dict->GetString(kHostOwnerEmailConfigPath, &email) &&
169 !config_dict->GetString(kHostOwnerConfigPath, &email) &&
170 !config_dict->GetString(kXmppLoginConfigPath, &email)) {
171 return false;
173 std::string host_id, host_secret_hash;
174 if (!config_dict->GetString(kHostIdConfigPath, &host_id) ||
175 !config_dict->GetString(kHostSecretHashConfigPath, &host_secret_hash)) {
176 return false;
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,
196 content)) {
197 return false;
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)) {
206 return false;
209 // Move the full and unprivileged configuration files to their permanent
210 // locations.
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;
223 break;
225 case SERVICE_PAUSE_PENDING:
226 case SERVICE_STOP_PENDING:
227 return DaemonController::STATE_STOPPING;
228 break;
230 case SERVICE_PAUSED:
231 case SERVICE_STOPPED:
232 return DaemonController::STATE_STOPPED;
233 break;
235 default:
236 NOTREACHED();
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,
252 access));
253 if (!service.IsValid()) {
254 PLOG(ERROR) << "Failed to open to the '" << kWindowsServiceName
255 << "' service";
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.";
273 return false;
276 return WriteConfig(config);
279 bool StartDaemon() {
280 DWORD access = SERVICE_CHANGE_CONFIG | SERVICE_QUERY_STATUS |
281 SERVICE_START | SERVICE_STOP;
282 ScopedScHandle service = OpenService(access);
283 if (!service.IsValid())
284 return false;
286 // Change the service start type to 'auto'.
287 if (!::ChangeServiceConfigW(service.Get(),
288 SERVICE_NO_CHANGE,
289 SERVICE_AUTO_START,
290 SERVICE_NO_CHANGE,
291 nullptr,
292 nullptr,
293 nullptr,
294 nullptr,
295 nullptr,
296 nullptr,
297 nullptr)) {
298 PLOG(ERROR) << "Failed to change the '" << kWindowsServiceName
299 << "'service start type to 'auto'";
300 return false;
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;
310 return false;
314 return true;
317 bool StopDaemon() {
318 DWORD access = SERVICE_CHANGE_CONFIG | SERVICE_QUERY_STATUS |
319 SERVICE_START | SERVICE_STOP;
320 ScopedScHandle service = OpenService(access);
321 if (!service.IsValid())
322 return false;
324 // Change the service start type to 'manual'.
325 if (!::ChangeServiceConfigW(service.Get(),
326 SERVICE_NO_CHANGE,
327 SERVICE_DEMAND_START,
328 SERVICE_NO_CHANGE,
329 nullptr,
330 nullptr,
331 nullptr,
332 nullptr,
333 nullptr,
334 nullptr,
335 nullptr)) {
336 PLOG(ERROR) << "Failed to change the '" << kWindowsServiceName
337 << "'service start type to 'manual'";
338 return false;
341 // Stop the service.
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;
348 return false;
352 return true;
355 } // namespace
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))
386 return nullptr;
388 return 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);
400 return;
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);
408 return;
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.
437 bool allowed;
438 bool set_by_policy;
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;
446 return consent;
449 void DaemonControllerDelegateWin::SetConfigAndStart(
450 scoped_ptr<base::DictionaryValue> config,
451 bool consent,
452 const DaemonController::CompletionCallback& done) {
453 // Record the user's consent.
454 if (!remoting::SetUsageStatsConsent(consent)) {
455 InvokeCompletionCallback(done, false);
456 return;
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);
468 return;
471 if (!WriteConfig(config_str)) {
472 InvokeCompletionCallback(done, false);
473 return;
476 // Start daemon.
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