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