• 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     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, &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 
GetTempLocationFor(const base::FilePath & filename)139 base::FilePath GetTempLocationFor(const base::FilePath& filename) {
140   return filename.ReplaceExtension(kTempFileExtension);
141 }
142 
143 // Writes a config file to a temporary location.
WriteConfigFileToTemp(const base::FilePath & filename,const char * security_descriptor,const char * content,size_t length)144 HRESULT 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, content, static_cast<DWORD>(length), &written, NULL)) {
181     DWORD error = GetLastError();
182     PLOG(ERROR) << "Failed to write to '" << filename.value() << "'";
183     return HRESULT_FROM_WIN32(error);
184   }
185 
186   return S_OK;
187 }
188 
189 // Moves a config file from its temporary location to its permanent location.
MoveConfigFileFromTemp(const base::FilePath & filename)190 HRESULT MoveConfigFileFromTemp(const base::FilePath& filename) {
191   // Now that the configuration is stored successfully replace the actual
192   // configuration file.
193   base::FilePath tempname = GetTempLocationFor(filename);
194   if (!MoveFileExW(tempname.value().c_str(),
195                    filename.value().c_str(),
196                    MOVEFILE_REPLACE_EXISTING)) {
197       DWORD error = GetLastError();
198       PLOG(ERROR) << "Failed to rename '" << tempname.value() << "' to '"
199                   << filename.value() << "'";
200       return HRESULT_FROM_WIN32(error);
201   }
202 
203   return S_OK;
204 }
205 
206 // Writes the configuration file up to |kMaxConfigFileSize| in size.
WriteConfig(const char * content,size_t length,HWND owner_window)207 HRESULT WriteConfig(const char* content, size_t length, HWND owner_window) {
208   if (length > kMaxConfigFileSize) {
209       return E_FAIL;
210   }
211 
212   // Extract the configuration data that the user will verify.
213   scoped_ptr<base::Value> config_value(base::JSONReader::Read(content));
214   if (!config_value.get()) {
215     return E_FAIL;
216   }
217   base::DictionaryValue* config_dict = NULL;
218   if (!config_value->GetAsDictionary(&config_dict)) {
219     return E_FAIL;
220   }
221   std::string email;
222   if (!config_dict->GetString(kHostOwner, &email)) {
223     if (!config_dict->GetString(kXmppLogin, &email)) {
224       return E_FAIL;
225     }
226   }
227   std::string host_id, host_secret_hash;
228   if (!config_dict->GetString(kHostId, &host_id) ||
229       !config_dict->GetString(kHostSecretHash, &host_secret_hash)) {
230     return E_FAIL;
231   }
232 
233   // Ask the user to verify the configuration (unless the client is admin
234   // already).
235   if (!IsClientAdmin()) {
236     remoting::VerifyConfigWindowWin verify_win(email, host_id,
237                                                host_secret_hash);
238     DWORD error = verify_win.DoModal(owner_window);
239     if (error != ERROR_SUCCESS) {
240       return HRESULT_FROM_WIN32(error);
241     }
242   }
243 
244   // Extract the unprivileged fields from the configuration.
245   base::DictionaryValue unprivileged_config_dict;
246   for (int i = 0; i < arraysize(kUnprivilegedConfigKeys); ++i) {
247     const char* key = kUnprivilegedConfigKeys[i];
248     base::string16 value;
249     if (config_dict->GetString(key, &value)) {
250       unprivileged_config_dict.SetString(key, value);
251     }
252   }
253   std::string unprivileged_config_str;
254   base::JSONWriter::Write(&unprivileged_config_dict, &unprivileged_config_str);
255 
256   // Write the full configuration file to a temporary location.
257   base::FilePath full_config_file_path =
258       remoting::GetConfigDir().Append(kConfigFileName);
259   HRESULT hr = WriteConfigFileToTemp(full_config_file_path,
260                                      kConfigFileSecurityDescriptor,
261                                      content,
262                                      length);
263   if (FAILED(hr)) {
264     return hr;
265   }
266 
267   // Write the unprivileged configuration file to a temporary location.
268   base::FilePath unprivileged_config_file_path =
269       remoting::GetConfigDir().Append(kUnprivilegedConfigFileName);
270   hr = WriteConfigFileToTemp(unprivileged_config_file_path,
271                              kUnprivilegedConfigFileSecurityDescriptor,
272                              unprivileged_config_str.data(),
273                              unprivileged_config_str.size());
274   if (FAILED(hr)) {
275     return hr;
276   }
277 
278   // Move the full configuration file to its permanent location.
279   hr = MoveConfigFileFromTemp(full_config_file_path);
280   if (FAILED(hr)) {
281     return hr;
282   }
283 
284   // Move the unprivileged configuration file to its permanent location.
285   hr = MoveConfigFileFromTemp(unprivileged_config_file_path);
286   if (FAILED(hr)) {
287     return hr;
288   }
289 
290   return S_OK;
291 }
292 
293 } // namespace
294 
ElevatedController()295 ElevatedController::ElevatedController() : owner_window_(NULL) {
296 }
297 
FinalConstruct()298 HRESULT ElevatedController::FinalConstruct() {
299   return S_OK;
300 }
301 
FinalRelease()302 void ElevatedController::FinalRelease() {
303 }
304 
GetConfig(BSTR * config_out)305 STDMETHODIMP ElevatedController::GetConfig(BSTR* config_out) {
306   base::FilePath config_dir = remoting::GetConfigDir();
307 
308   // Read the unprivileged part of host configuration.
309   scoped_ptr<base::DictionaryValue> config;
310   HRESULT hr = ReadConfig(config_dir.Append(kUnprivilegedConfigFileName),
311                           &config);
312   if (FAILED(hr)) {
313     return hr;
314   }
315 
316   // Convert the config back to a string and return it to the caller.
317   std::string file_content;
318   base::JSONWriter::Write(config.get(), &file_content);
319 
320   *config_out = ::SysAllocString(base::UTF8ToUTF16(file_content).c_str());
321   if (config_out == NULL) {
322     return E_OUTOFMEMORY;
323   }
324 
325   return S_OK;
326 }
327 
GetVersion(BSTR * version_out)328 STDMETHODIMP ElevatedController::GetVersion(BSTR* version_out) {
329   // Report the product version number of the daemon controller binary as
330   // the host version.
331   HMODULE binary = base::GetModuleFromAddress(
332       reinterpret_cast<void*>(&ReadConfig));
333   scoped_ptr<FileVersionInfo> version_info(
334       FileVersionInfo::CreateFileVersionInfoForModule(binary));
335 
336   base::string16 version;
337   if (version_info.get()) {
338     version = version_info->product_version();
339   }
340 
341   *version_out = ::SysAllocString(version.c_str());
342   if (version_out == NULL) {
343     return E_OUTOFMEMORY;
344   }
345 
346   return S_OK;
347 }
348 
SetConfig(BSTR config)349 STDMETHODIMP ElevatedController::SetConfig(BSTR config) {
350   // Determine the config directory path and create it if necessary.
351   base::FilePath config_dir = remoting::GetConfigDir();
352   if (!base::CreateDirectory(config_dir)) {
353     return HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED);
354   }
355 
356   std::string file_content = base::UTF16ToUTF8(
357     base::string16(static_cast<base::char16*>(config), ::SysStringLen(config)));
358 
359   return WriteConfig(file_content.c_str(), file_content.size(), owner_window_);
360 }
361 
SetOwnerWindow(LONG_PTR window_handle)362 STDMETHODIMP ElevatedController::SetOwnerWindow(LONG_PTR window_handle) {
363   owner_window_ = reinterpret_cast<HWND>(window_handle);
364   return S_OK;
365 }
366 
StartDaemon()367 STDMETHODIMP ElevatedController::StartDaemon() {
368   ScopedScHandle service;
369   HRESULT hr = OpenService(&service);
370   if (FAILED(hr)) {
371     return hr;
372   }
373 
374   // Change the service start type to 'auto'.
375   if (!::ChangeServiceConfigW(service,
376                               SERVICE_NO_CHANGE,
377                               SERVICE_AUTO_START,
378                               SERVICE_NO_CHANGE,
379                               NULL,
380                               NULL,
381                               NULL,
382                               NULL,
383                               NULL,
384                               NULL,
385                               NULL)) {
386     DWORD error = GetLastError();
387     PLOG(ERROR) << "Failed to change the '" << kWindowsServiceName
388                 << "'service start type to 'auto'";
389     return HRESULT_FROM_WIN32(error);
390   }
391 
392   // Start the service.
393   if (!StartService(service, 0, NULL)) {
394     DWORD error = GetLastError();
395     if (error != ERROR_SERVICE_ALREADY_RUNNING) {
396       PLOG(ERROR) << "Failed to start the '" << kWindowsServiceName
397                   << "'service";
398 
399       return HRESULT_FROM_WIN32(error);
400     }
401   }
402 
403   return S_OK;
404 }
405 
StopDaemon()406 STDMETHODIMP ElevatedController::StopDaemon() {
407   ScopedScHandle service;
408   HRESULT hr = OpenService(&service);
409   if (FAILED(hr)) {
410     return hr;
411   }
412 
413   // Change the service start type to 'manual'.
414   if (!::ChangeServiceConfigW(service,
415                               SERVICE_NO_CHANGE,
416                               SERVICE_DEMAND_START,
417                               SERVICE_NO_CHANGE,
418                               NULL,
419                               NULL,
420                               NULL,
421                               NULL,
422                               NULL,
423                               NULL,
424                               NULL)) {
425     DWORD error = GetLastError();
426     PLOG(ERROR) << "Failed to change the '" << kWindowsServiceName
427                 << "'service start type to 'manual'";
428     return HRESULT_FROM_WIN32(error);
429   }
430 
431   // Stop the service.
432   SERVICE_STATUS status;
433   if (!ControlService(service, SERVICE_CONTROL_STOP, &status)) {
434     DWORD error = GetLastError();
435     if (error != ERROR_SERVICE_NOT_ACTIVE) {
436       PLOG(ERROR) << "Failed to stop the '" << kWindowsServiceName
437                   << "'service";
438       return HRESULT_FROM_WIN32(error);
439     }
440   }
441 
442   return S_OK;
443 }
444 
UpdateConfig(BSTR config)445 STDMETHODIMP ElevatedController::UpdateConfig(BSTR config) {
446   // Parse the config.
447   std::string config_str = base::UTF16ToUTF8(
448     base::string16(static_cast<base::char16*>(config), ::SysStringLen(config)));
449   scoped_ptr<base::Value> config_value(base::JSONReader::Read(config_str));
450   if (!config_value.get()) {
451     return E_FAIL;
452   }
453   base::DictionaryValue* config_dict = NULL;
454   if (!config_value->GetAsDictionary(&config_dict)) {
455     return E_FAIL;
456   }
457   // Check for bad keys.
458   for (int i = 0; i < arraysize(kReadonlyKeys); ++i) {
459     if (config_dict->HasKey(kReadonlyKeys[i])) {
460       return HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED);
461     }
462   }
463   // Get the old config.
464   base::FilePath config_dir = remoting::GetConfigDir();
465   scoped_ptr<base::DictionaryValue> config_old;
466   HRESULT hr = ReadConfig(config_dir.Append(kConfigFileName), &config_old);
467   if (FAILED(hr)) {
468     return hr;
469   }
470   // Merge items from the given config into the old config.
471   config_old->MergeDictionary(config_dict);
472   // Write the updated config.
473   std::string config_updated_str;
474   base::JSONWriter::Write(config_old.get(), &config_updated_str);
475   return WriteConfig(config_updated_str.c_str(), config_updated_str.size(),
476                      owner_window_);
477 }
478 
GetUsageStatsConsent(BOOL * allowed,BOOL * set_by_policy)479 STDMETHODIMP ElevatedController::GetUsageStatsConsent(BOOL* allowed,
480                                                          BOOL* set_by_policy) {
481   bool local_allowed;
482   bool local_set_by_policy;
483   if (remoting::GetUsageStatsConsent(&local_allowed, &local_set_by_policy)) {
484     *allowed = local_allowed;
485     *set_by_policy = local_set_by_policy;
486     return S_OK;
487   } else {
488     return E_FAIL;
489   }
490 }
491 
SetUsageStatsConsent(BOOL allowed)492 STDMETHODIMP ElevatedController::SetUsageStatsConsent(BOOL allowed) {
493   if (remoting::SetUsageStatsConsent(!!allowed)) {
494     return S_OK;
495   } else {
496     return E_FAIL;
497   }
498 }
499 
OpenService(ScopedScHandle * service_out)500 HRESULT ElevatedController::OpenService(ScopedScHandle* service_out) {
501   DWORD error;
502 
503   ScopedScHandle scmanager(
504       ::OpenSCManagerW(NULL, SERVICES_ACTIVE_DATABASE,
505                        SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE));
506   if (!scmanager.IsValid()) {
507     error = GetLastError();
508     PLOG(ERROR) << "Failed to connect to the service control manager";
509 
510     return HRESULT_FROM_WIN32(error);
511   }
512 
513   DWORD desired_access = SERVICE_CHANGE_CONFIG | SERVICE_QUERY_STATUS |
514                          SERVICE_START | SERVICE_STOP;
515   ScopedScHandle service(
516       ::OpenServiceW(scmanager, kWindowsServiceName, desired_access));
517   if (!service.IsValid()) {
518     error = GetLastError();
519     PLOG(ERROR) << "Failed to open to the '" << kWindowsServiceName
520                 << "' service";
521 
522     return HRESULT_FROM_WIN32(error);
523   }
524 
525   service_out->Set(service.Take());
526   return S_OK;
527 }
528 
529 } // namespace remoting
530