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