• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
24 namespace remoting {
25 
26 namespace {
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.
32 const size_t kMaxConfigFileSize = 1024 * 1024;
33 
34 // The host configuration file name.
35 const base::FilePath::CharType kConfigFileName[] = FILE_PATH_LITERAL("host.json");
36 
37 // The unprivileged configuration file name.
38 const base::FilePath::CharType kUnprivilegedConfigFileName[] =
39     FILE_PATH_LITERAL("host_unprivileged.json");
40 
41 // The extension for the temporary file.
42 const 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.
46 const char kConfigFileSecurityDescriptor[] =
47     "O:BAG:BAD:(A;;GA;;;SY)(A;;GA;;;BA)";
48 
49 const char kUnprivilegedConfigFileSecurityDescriptor[] =
50     "O:BAG:BAD:(A;;GA;;;SY)(A;;GA;;;BA)(A;;GR;;;AU)";
51 
52 // Configuration keys.
53 const char kHostId[] = "host_id";
54 const char kXmppLogin[] = "xmpp_login";
55 const char kHostOwner[] = "host_owner";
56 const char kHostSecretHash[] = "host_secret_hash";
57 
58 // The configuration keys that cannot be specified in UpdateConfig().
59 const char* const kReadonlyKeys[] = { kHostId, kHostOwner, kXmppLogin };
60 
61 // The configuration keys whose values may be read by GetConfig().
62 const 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).
IsClientAdmin()67 bool 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.
ReadConfig(const base::FilePath & filename,scoped_ptr<base::DictionaryValue> * config_out)96 HRESULT 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 
GetTempLocationFor(const base::FilePath & filename)141 base::FilePath GetTempLocationFor(const base::FilePath& filename) {
142   return filename.ReplaceExtension(kTempFileExtension);
143 }
144 
145 // Writes a config file to a temporary location.
WriteConfigFileToTemp(const base::FilePath & filename,const char * security_descriptor,const char * content,size_t length)146 HRESULT 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.
MoveConfigFileFromTemp(const base::FilePath & filename)194 HRESULT 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.
WriteConfig(const char * content,size_t length,HWND owner_window)212 HRESULT 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 
ElevatedController()300 ElevatedController::ElevatedController() : owner_window_(NULL) {
301 }
302 
FinalConstruct()303 HRESULT ElevatedController::FinalConstruct() {
304   return S_OK;
305 }
306 
FinalRelease()307 void ElevatedController::FinalRelease() {
308 }
309 
GetConfig(BSTR * config_out)310 STDMETHODIMP 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 
GetVersion(BSTR * version_out)333 STDMETHODIMP 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 
SetConfig(BSTR config)354 STDMETHODIMP 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 
SetOwnerWindow(LONG_PTR window_handle)367 STDMETHODIMP ElevatedController::SetOwnerWindow(LONG_PTR window_handle) {
368   owner_window_ = reinterpret_cast<HWND>(window_handle);
369   return S_OK;
370 }
371 
StartDaemon()372 STDMETHODIMP 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 
StopDaemon()412 STDMETHODIMP 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 
UpdateConfig(BSTR config)452 STDMETHODIMP 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 
GetUsageStatsConsent(BOOL * allowed,BOOL * set_by_policy)486 STDMETHODIMP 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 
SetUsageStatsConsent(BOOL allowed)499 STDMETHODIMP ElevatedController::SetUsageStatsConsent(BOOL allowed) {
500   if (remoting::SetUsageStatsConsent(!!allowed)) {
501     return S_OK;
502   } else {
503     return E_FAIL;
504   }
505 }
506 
OpenService(ScopedScHandle * service_out)507 HRESULT 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