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.
4
5#include "remoting/host/win/elevated_controller.h"
6
7#include "base/file_version_info.h"
8#include "base/files/file_util.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/host_config.h"
20#include "remoting/host/usage_stats_consent.h"
21#include "remoting/host/verify_config_window_win.h"
22#include "remoting/host/win/core_resource.h"
23#include "remoting/host/win/security_descriptor.h"
24
25namespace remoting {
26
27namespace {
28
29// The maximum size of the configuration file. "1MB ought to be enough" for any
30// reasonable configuration we will ever need. 1MB is low enough to make
31// the probability of out of memory situation fairly low. OOM is still possible
32// and we will crash if it occurs.
33const size_t kMaxConfigFileSize = 1024 * 1024;
34
35// The host configuration file name.
36const base::FilePath::CharType kConfigFileName[] = FILE_PATH_LITERAL("host.json");
37
38// The unprivileged configuration file name.
39const base::FilePath::CharType kUnprivilegedConfigFileName[] =
40    FILE_PATH_LITERAL("host_unprivileged.json");
41
42// The extension for the temporary file.
43const base::FilePath::CharType kTempFileExtension[] = FILE_PATH_LITERAL("json~");
44
45// The host configuration file security descriptor that enables full access to
46// Local System and built-in administrators only.
47const char kConfigFileSecurityDescriptor[] =
48    "O:BAG:BAD:(A;;GA;;;SY)(A;;GA;;;BA)";
49
50const char kUnprivilegedConfigFileSecurityDescriptor[] =
51    "O:BAG:BAD:(A;;GA;;;SY)(A;;GA;;;BA)(A;;GR;;;AU)";
52
53// Configuration keys.
54
55// The configuration keys that cannot be specified in UpdateConfig().
56const char* const kReadonlyKeys[] = {
57  kHostIdConfigPath, kHostOwnerConfigPath, kHostOwnerEmailConfigPath,
58  kXmppLoginConfigPath };
59
60// The configuration keys whose values may be read by GetConfig().
61const char* const kUnprivilegedConfigKeys[] = {
62  kHostIdConfigPath, kXmppLoginConfigPath };
63
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).
67bool IsClientAdmin() {
68  HRESULT hr = CoImpersonateClient();
69  if (FAILED(hr)) {
70    return false;
71  }
72
73  SID_IDENTIFIER_AUTHORITY nt_authority = SECURITY_NT_AUTHORITY;
74  PSID administrators_group = NULL;
75  BOOL result = AllocateAndInitializeSid(&nt_authority,
76                                         2,
77                                         SECURITY_BUILTIN_DOMAIN_RID,
78                                         DOMAIN_ALIAS_RID_ADMINS,
79                                         0, 0, 0, 0, 0, 0,
80                                         &administrators_group);
81  if (result) {
82    if (!CheckTokenMembership(NULL, administrators_group, &result)) {
83      result = false;
84    }
85    FreeSid(administrators_group);
86  }
87
88  hr = CoRevertToSelf();
89  CHECK(SUCCEEDED(hr));
90
91  return !!result;
92}
93
94// Reads and parses the configuration file up to |kMaxConfigFileSize| in
95// size.
96HRESULT ReadConfig(const base::FilePath& filename,
97                   scoped_ptr<base::DictionaryValue>* config_out) {
98
99  // Read raw data from the configuration file.
100  base::win::ScopedHandle file(
101      CreateFileW(filename.value().c_str(),
102                  GENERIC_READ,
103                  FILE_SHARE_READ | FILE_SHARE_WRITE,
104                  NULL,
105                  OPEN_EXISTING,
106                  FILE_FLAG_SEQUENTIAL_SCAN,
107                  NULL));
108
109  if (!file.IsValid()) {
110    DWORD error = GetLastError();
111    PLOG(ERROR) << "Failed to open '" << filename.value() << "'";
112    return HRESULT_FROM_WIN32(error);
113  }
114
115  scoped_ptr<char[]> buffer(new char[kMaxConfigFileSize]);
116  DWORD size = kMaxConfigFileSize;
117  if (!::ReadFile(file.Get(), &buffer[0], size, &size, NULL)) {
118    DWORD error = GetLastError();
119    PLOG(ERROR) << "Failed to read '" << filename.value() << "'";
120    return HRESULT_FROM_WIN32(error);
121  }
122
123  // Parse the JSON configuration, expecting it to contain a dictionary.
124  std::string file_content(buffer.get(), size);
125  scoped_ptr<base::Value> value(
126      base::JSONReader::Read(file_content, base::JSON_ALLOW_TRAILING_COMMAS));
127
128  base::DictionaryValue* dictionary;
129  if (value.get() == NULL || !value->GetAsDictionary(&dictionary)) {
130    LOG(ERROR) << "Failed to read '" << filename.value() << "'.";
131    return E_FAIL;
132  }
133
134  value.release();
135  config_out->reset(dictionary);
136  return S_OK;
137}
138
139base::FilePath GetTempLocationFor(const base::FilePath& filename) {
140  return filename.ReplaceExtension(kTempFileExtension);
141}
142
143// Writes a config file to a temporary location.
144HRESULT WriteConfigFileToTemp(const base::FilePath& filename,
145                              const char* security_descriptor,
146                              const char* content,
147                              size_t length) {
148  // Create the security descriptor for the configuration file.
149  ScopedSd sd = ConvertSddlToSd(security_descriptor);
150  if (!sd) {
151    DWORD error = GetLastError();
152    PLOG(ERROR)
153        << "Failed to create a security descriptor for the configuration file";
154    return HRESULT_FROM_WIN32(error);
155  }
156
157  SECURITY_ATTRIBUTES security_attributes = {0};
158  security_attributes.nLength = sizeof(security_attributes);
159  security_attributes.lpSecurityDescriptor = sd.get();
160  security_attributes.bInheritHandle = FALSE;
161
162  // Create a temporary file and write configuration to it.
163  base::FilePath tempname = GetTempLocationFor(filename);
164  base::win::ScopedHandle file(
165      CreateFileW(tempname.value().c_str(),
166                  GENERIC_WRITE,
167                  0,
168                  &security_attributes,
169                  CREATE_ALWAYS,
170                  FILE_FLAG_SEQUENTIAL_SCAN,
171                  NULL));
172
173  if (!file.IsValid()) {
174    DWORD error = GetLastError();
175    PLOG(ERROR) << "Failed to create '" << filename.value() << "'";
176    return HRESULT_FROM_WIN32(error);
177  }
178
179  DWORD written;
180  if (!WriteFile(file.Get(), content, static_cast<DWORD>(length), &written,
181                 NULL)) {
182    DWORD error = GetLastError();
183    PLOG(ERROR) << "Failed to write to '" << filename.value() << "'";
184    return HRESULT_FROM_WIN32(error);
185  }
186
187  return S_OK;
188}
189
190// Moves a config file from its temporary location to its permanent location.
191HRESULT MoveConfigFileFromTemp(const base::FilePath& filename) {
192  // Now that the configuration is stored successfully replace the actual
193  // configuration file.
194  base::FilePath tempname = GetTempLocationFor(filename);
195  if (!MoveFileExW(tempname.value().c_str(),
196                   filename.value().c_str(),
197                   MOVEFILE_REPLACE_EXISTING)) {
198      DWORD error = GetLastError();
199      PLOG(ERROR) << "Failed to rename '" << tempname.value() << "' to '"
200                  << filename.value() << "'";
201      return HRESULT_FROM_WIN32(error);
202  }
203
204  return S_OK;
205}
206
207// Writes the configuration file up to |kMaxConfigFileSize| in size.
208HRESULT WriteConfig(const char* content, size_t length, HWND owner_window) {
209  if (length > kMaxConfigFileSize) {
210      return E_FAIL;
211  }
212
213  // Extract the configuration data that the user will verify.
214  scoped_ptr<base::Value> config_value(base::JSONReader::Read(content));
215  if (!config_value.get()) {
216    return E_FAIL;
217  }
218  base::DictionaryValue* config_dict = NULL;
219  if (!config_value->GetAsDictionary(&config_dict)) {
220    return E_FAIL;
221  }
222  std::string email;
223  if (!config_dict->GetString(kHostOwnerEmailConfigPath, &email)) {
224    if (!config_dict->GetString(kHostOwnerConfigPath, &email)) {
225      if (!config_dict->GetString(kXmppLoginConfigPath, &email)) {
226        return E_FAIL;
227      }
228    }
229  }
230  std::string host_id, host_secret_hash;
231  if (!config_dict->GetString(kHostIdConfigPath, &host_id) ||
232      !config_dict->GetString(kHostSecretHashConfigPath, &host_secret_hash)) {
233    return E_FAIL;
234  }
235
236  // Ask the user to verify the configuration (unless the client is admin
237  // already).
238  if (!IsClientAdmin()) {
239    remoting::VerifyConfigWindowWin verify_win(email, host_id,
240                                               host_secret_hash);
241    DWORD error = verify_win.DoModal(owner_window);
242    if (error != ERROR_SUCCESS) {
243      return HRESULT_FROM_WIN32(error);
244    }
245  }
246
247  // Extract the unprivileged fields from the configuration.
248  base::DictionaryValue unprivileged_config_dict;
249  for (int i = 0; i < arraysize(kUnprivilegedConfigKeys); ++i) {
250    const char* key = kUnprivilegedConfigKeys[i];
251    base::string16 value;
252    if (config_dict->GetString(key, &value)) {
253      unprivileged_config_dict.SetString(key, value);
254    }
255  }
256  std::string unprivileged_config_str;
257  base::JSONWriter::Write(&unprivileged_config_dict, &unprivileged_config_str);
258
259  // Write the full configuration file to a temporary location.
260  base::FilePath full_config_file_path =
261      remoting::GetConfigDir().Append(kConfigFileName);
262  HRESULT hr = WriteConfigFileToTemp(full_config_file_path,
263                                     kConfigFileSecurityDescriptor,
264                                     content,
265                                     length);
266  if (FAILED(hr)) {
267    return hr;
268  }
269
270  // Write the unprivileged configuration file to a temporary location.
271  base::FilePath unprivileged_config_file_path =
272      remoting::GetConfigDir().Append(kUnprivilegedConfigFileName);
273  hr = WriteConfigFileToTemp(unprivileged_config_file_path,
274                             kUnprivilegedConfigFileSecurityDescriptor,
275                             unprivileged_config_str.data(),
276                             unprivileged_config_str.size());
277  if (FAILED(hr)) {
278    return hr;
279  }
280
281  // Move the full configuration file to its permanent location.
282  hr = MoveConfigFileFromTemp(full_config_file_path);
283  if (FAILED(hr)) {
284    return hr;
285  }
286
287  // Move the unprivileged configuration file to its permanent location.
288  hr = MoveConfigFileFromTemp(unprivileged_config_file_path);
289  if (FAILED(hr)) {
290    return hr;
291  }
292
293  return S_OK;
294}
295
296} // namespace
297
298ElevatedController::ElevatedController() : owner_window_(NULL) {
299}
300
301HRESULT ElevatedController::FinalConstruct() {
302  return S_OK;
303}
304
305void ElevatedController::FinalRelease() {
306}
307
308STDMETHODIMP ElevatedController::GetConfig(BSTR* config_out) {
309  base::FilePath config_dir = remoting::GetConfigDir();
310
311  // Read the unprivileged part of host configuration.
312  scoped_ptr<base::DictionaryValue> config;
313  HRESULT hr = ReadConfig(config_dir.Append(kUnprivilegedConfigFileName),
314                          &config);
315  if (FAILED(hr)) {
316    return hr;
317  }
318
319  // Convert the config back to a string and return it to the caller.
320  std::string file_content;
321  base::JSONWriter::Write(config.get(), &file_content);
322
323  *config_out = ::SysAllocString(base::UTF8ToUTF16(file_content).c_str());
324  if (config_out == NULL) {
325    return E_OUTOFMEMORY;
326  }
327
328  return S_OK;
329}
330
331STDMETHODIMP ElevatedController::GetVersion(BSTR* version_out) {
332  // Report the product version number of the daemon controller binary as
333  // the host version.
334  HMODULE binary = base::GetModuleFromAddress(
335      reinterpret_cast<void*>(&ReadConfig));
336  scoped_ptr<FileVersionInfo> version_info(
337      FileVersionInfo::CreateFileVersionInfoForModule(binary));
338
339  base::string16 version;
340  if (version_info.get()) {
341    version = version_info->product_version();
342  }
343
344  *version_out = ::SysAllocString(version.c_str());
345  if (version_out == NULL) {
346    return E_OUTOFMEMORY;
347  }
348
349  return S_OK;
350}
351
352STDMETHODIMP ElevatedController::SetConfig(BSTR config) {
353  // Determine the config directory path and create it if necessary.
354  base::FilePath config_dir = remoting::GetConfigDir();
355  if (!base::CreateDirectory(config_dir)) {
356    return HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED);
357  }
358
359  std::string file_content = base::UTF16ToUTF8(
360    base::string16(static_cast<base::char16*>(config), ::SysStringLen(config)));
361
362  return WriteConfig(file_content.c_str(), file_content.size(), owner_window_);
363}
364
365STDMETHODIMP ElevatedController::SetOwnerWindow(LONG_PTR window_handle) {
366  owner_window_ = reinterpret_cast<HWND>(window_handle);
367  return S_OK;
368}
369
370STDMETHODIMP ElevatedController::StartDaemon() {
371  ScopedScHandle service;
372  HRESULT hr = OpenService(&service);
373  if (FAILED(hr)) {
374    return hr;
375  }
376
377  // Change the service start type to 'auto'.
378  if (!::ChangeServiceConfigW(service.Get(),
379                              SERVICE_NO_CHANGE,
380                              SERVICE_AUTO_START,
381                              SERVICE_NO_CHANGE,
382                              NULL,
383                              NULL,
384                              NULL,
385                              NULL,
386                              NULL,
387                              NULL,
388                              NULL)) {
389    DWORD error = GetLastError();
390    PLOG(ERROR) << "Failed to change the '" << kWindowsServiceName
391                << "'service start type to 'auto'";
392    return HRESULT_FROM_WIN32(error);
393  }
394
395  // Start the service.
396  if (!StartService(service.Get(), 0, NULL)) {
397    DWORD error = GetLastError();
398    if (error != ERROR_SERVICE_ALREADY_RUNNING) {
399      PLOG(ERROR) << "Failed to start the '" << kWindowsServiceName
400                  << "'service";
401
402      return HRESULT_FROM_WIN32(error);
403    }
404  }
405
406  return S_OK;
407}
408
409STDMETHODIMP ElevatedController::StopDaemon() {
410  ScopedScHandle service;
411  HRESULT hr = OpenService(&service);
412  if (FAILED(hr)) {
413    return hr;
414  }
415
416  // Change the service start type to 'manual'.
417  if (!::ChangeServiceConfigW(service.Get(),
418                              SERVICE_NO_CHANGE,
419                              SERVICE_DEMAND_START,
420                              SERVICE_NO_CHANGE,
421                              NULL,
422                              NULL,
423                              NULL,
424                              NULL,
425                              NULL,
426                              NULL,
427                              NULL)) {
428    DWORD error = GetLastError();
429    PLOG(ERROR) << "Failed to change the '" << kWindowsServiceName
430                << "'service start type to 'manual'";
431    return HRESULT_FROM_WIN32(error);
432  }
433
434  // Stop the service.
435  SERVICE_STATUS status;
436  if (!ControlService(service.Get(), SERVICE_CONTROL_STOP, &status)) {
437    DWORD error = GetLastError();
438    if (error != ERROR_SERVICE_NOT_ACTIVE) {
439      PLOG(ERROR) << "Failed to stop the '" << kWindowsServiceName
440                  << "'service";
441      return HRESULT_FROM_WIN32(error);
442    }
443  }
444
445  return S_OK;
446}
447
448STDMETHODIMP ElevatedController::UpdateConfig(BSTR config) {
449  // Parse the config.
450  std::string config_str = base::UTF16ToUTF8(
451    base::string16(static_cast<base::char16*>(config), ::SysStringLen(config)));
452  scoped_ptr<base::Value> config_value(base::JSONReader::Read(config_str));
453  if (!config_value.get()) {
454    return E_FAIL;
455  }
456  base::DictionaryValue* config_dict = NULL;
457  if (!config_value->GetAsDictionary(&config_dict)) {
458    return E_FAIL;
459  }
460  // Check for bad keys.
461  for (int i = 0; i < arraysize(kReadonlyKeys); ++i) {
462    if (config_dict->HasKey(kReadonlyKeys[i])) {
463      return HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED);
464    }
465  }
466  // Get the old config.
467  base::FilePath config_dir = remoting::GetConfigDir();
468  scoped_ptr<base::DictionaryValue> config_old;
469  HRESULT hr = ReadConfig(config_dir.Append(kConfigFileName), &config_old);
470  if (FAILED(hr)) {
471    return hr;
472  }
473  // Merge items from the given config into the old config.
474  config_old->MergeDictionary(config_dict);
475  // Write the updated config.
476  std::string config_updated_str;
477  base::JSONWriter::Write(config_old.get(), &config_updated_str);
478  return WriteConfig(config_updated_str.c_str(), config_updated_str.size(),
479                     owner_window_);
480}
481
482STDMETHODIMP ElevatedController::GetUsageStatsConsent(BOOL* allowed,
483                                                         BOOL* set_by_policy) {
484  bool local_allowed;
485  bool local_set_by_policy;
486  if (remoting::GetUsageStatsConsent(&local_allowed, &local_set_by_policy)) {
487    *allowed = local_allowed;
488    *set_by_policy = local_set_by_policy;
489    return S_OK;
490  } else {
491    return E_FAIL;
492  }
493}
494
495STDMETHODIMP ElevatedController::SetUsageStatsConsent(BOOL allowed) {
496  if (remoting::SetUsageStatsConsent(!!allowed)) {
497    return S_OK;
498  } else {
499    return E_FAIL;
500  }
501}
502
503HRESULT ElevatedController::OpenService(ScopedScHandle* service_out) {
504  DWORD error;
505
506  ScopedScHandle scmanager(
507      ::OpenSCManagerW(NULL, SERVICES_ACTIVE_DATABASE,
508                       SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE));
509  if (!scmanager.IsValid()) {
510    error = GetLastError();
511    PLOG(ERROR) << "Failed to connect to the service control manager";
512
513    return HRESULT_FROM_WIN32(error);
514  }
515
516  DWORD desired_access = SERVICE_CHANGE_CONFIG | SERVICE_QUERY_STATUS |
517                         SERVICE_START | SERVICE_STOP;
518  ScopedScHandle service(
519      ::OpenServiceW(scmanager.Get(), kWindowsServiceName, desired_access));
520  if (!service.IsValid()) {
521    error = GetLastError();
522    PLOG(ERROR) << "Failed to open to the '" << kWindowsServiceName
523                << "' service";
524
525    return HRESULT_FROM_WIN32(error);
526  }
527
528  service_out->Set(service.Take());
529  return S_OK;
530}
531
532} // namespace remoting
533