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