• 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_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 
25 namespace remoting {
26 
27 namespace {
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.
33 const size_t kMaxConfigFileSize = 1024 * 1024;
34 
35 // The host configuration file name.
36 const base::FilePath::CharType kConfigFileName[] = FILE_PATH_LITERAL("host.json");
37 
38 // The unprivileged configuration file name.
39 const base::FilePath::CharType kUnprivilegedConfigFileName[] =
40     FILE_PATH_LITERAL("host_unprivileged.json");
41 
42 // The extension for the temporary file.
43 const 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.
47 const char kConfigFileSecurityDescriptor[] =
48     "O:BAG:BAD:(A;;GA;;;SY)(A;;GA;;;BA)";
49 
50 const 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().
56 const char* const kReadonlyKeys[] = {
57   kHostIdConfigPath, kHostOwnerConfigPath, kHostOwnerEmailConfigPath,
58   kXmppLoginConfigPath };
59 
60 // The configuration keys whose values may be read by GetConfig().
61 const 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).
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.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 
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.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.
MoveConfigFileFromTemp(const base::FilePath & filename)191 HRESULT 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.
WriteConfig(const char * content,size_t length,HWND owner_window)208 HRESULT 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 
ElevatedController()298 ElevatedController::ElevatedController() : owner_window_(NULL) {
299 }
300 
FinalConstruct()301 HRESULT ElevatedController::FinalConstruct() {
302   return S_OK;
303 }
304 
FinalRelease()305 void ElevatedController::FinalRelease() {
306 }
307 
GetConfig(BSTR * config_out)308 STDMETHODIMP 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 
GetVersion(BSTR * version_out)331 STDMETHODIMP 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 
SetConfig(BSTR config)352 STDMETHODIMP 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 
SetOwnerWindow(LONG_PTR window_handle)365 STDMETHODIMP ElevatedController::SetOwnerWindow(LONG_PTR window_handle) {
366   owner_window_ = reinterpret_cast<HWND>(window_handle);
367   return S_OK;
368 }
369 
StartDaemon()370 STDMETHODIMP 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 
StopDaemon()409 STDMETHODIMP 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 
UpdateConfig(BSTR config)448 STDMETHODIMP 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 
GetUsageStatsConsent(BOOL * allowed,BOOL * set_by_policy)482 STDMETHODIMP 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 
SetUsageStatsConsent(BOOL allowed)495 STDMETHODIMP ElevatedController::SetUsageStatsConsent(BOOL allowed) {
496   if (remoting::SetUsageStatsConsent(!!allowed)) {
497     return S_OK;
498   } else {
499     return E_FAIL;
500   }
501 }
502 
OpenService(ScopedScHandle * service_out)503 HRESULT 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