Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / remoting / host / setup / daemon_controller_delegate_win.cc
blob053d86755239ec6166325c9f307bcf87ca30ecd7
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 =
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() << "'.";
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 StartDaemon() {
269 DWORD access = SERVICE_CHANGE_CONFIG | SERVICE_QUERY_STATUS |
270 SERVICE_START | SERVICE_STOP;
271 ScopedScHandle service = OpenService(access);
272 if (!service.IsValid())
273 return false;
275 // Change the service start type to 'auto'.
276 if (!::ChangeServiceConfigW(service.Get(),
277 SERVICE_NO_CHANGE,
278 SERVICE_AUTO_START,
279 SERVICE_NO_CHANGE,
280 nullptr,
281 nullptr,
282 nullptr,
283 nullptr,
284 nullptr,
285 nullptr,
286 nullptr)) {
287 PLOG(ERROR) << "Failed to change the '" << kWindowsServiceName
288 << "'service start type to 'auto'";
289 return false;
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;
299 return false;
303 return true;
306 bool StopDaemon() {
307 DWORD access = SERVICE_CHANGE_CONFIG | SERVICE_QUERY_STATUS |
308 SERVICE_START | SERVICE_STOP;
309 ScopedScHandle service = OpenService(access);
310 if (!service.IsValid())
311 return false;
313 // Change the service start type to 'manual'.
314 if (!::ChangeServiceConfigW(service.Get(),
315 SERVICE_NO_CHANGE,
316 SERVICE_DEMAND_START,
317 SERVICE_NO_CHANGE,
318 nullptr,
319 nullptr,
320 nullptr,
321 nullptr,
322 nullptr,
323 nullptr,
324 nullptr)) {
325 PLOG(ERROR) << "Failed to change the '" << kWindowsServiceName
326 << "'service start type to 'manual'";
327 return false;
330 // Stop the service.
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;
337 return false;
341 return true;
344 } // namespace
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))
375 return nullptr;
377 return 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);
389 return;
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);
397 return;
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.
426 bool allowed;
427 bool set_by_policy;
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;
435 return consent;
438 void DaemonControllerDelegateWin::SetConfigAndStart(
439 scoped_ptr<base::DictionaryValue> config,
440 bool consent,
441 const DaemonController::CompletionCallback& done) {
442 // Record the user's consent.
443 if (!remoting::SetUsageStatsConsent(consent)) {
444 InvokeCompletionCallback(done, false);
445 return;
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);
457 return;
460 if (!WriteConfig(config_str)) {
461 InvokeCompletionCallback(done, false);
462 return;
465 // Start daemon.
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