OLD | NEW |
| (Empty) |
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/elevated_controller_win.h" | |
6 | |
7 #include <sddl.h> | |
8 | |
9 #include "base/file_util.h" | |
10 #include "base/file_version_info.h" | |
11 #include "base/logging.h" | |
12 #include "base/json/json_reader.h" | |
13 #include "base/json/json_writer.h" | |
14 #include "base/memory/scoped_ptr.h" | |
15 #include "base/path_service.h" | |
16 #include "base/process_util.h" | |
17 #include "base/utf_string_conversions.h" | |
18 #include "base/values.h" | |
19 #include "base/win/scoped_handle.h" | |
20 #include "remoting/host/branding.h" | |
21 #include "remoting/host/elevated_controller_resource.h" | |
22 #include "remoting/host/usage_stats_consent.h" | |
23 #include "remoting/host/verify_config_window_win.h" | |
24 | |
25 namespace { | |
26 | |
27 // The maximum size of the configuration file. "1MB ought to be enough" for any | |
28 // reasonable configuration we will ever need. 1MB is low enough to make | |
29 // the probability of out of memory situation fairly low. OOM is still possible | |
30 // and we will crash if it occurs. | |
31 const size_t kMaxConfigFileSize = 1024 * 1024; | |
32 | |
33 // The host configuration file name. | |
34 const FilePath::CharType kConfigFileName[] = FILE_PATH_LITERAL("host.json"); | |
35 | |
36 // The unprivileged configuration file name. | |
37 const FilePath::CharType kUnprivilegedConfigFileName[] = | |
38 FILE_PATH_LITERAL("host_unprivileged.json"); | |
39 | |
40 // The extension for the temporary file. | |
41 const FilePath::CharType kTempFileExtension[] = FILE_PATH_LITERAL("json~"); | |
42 | |
43 // The host configuration file security descriptor that enables full access to | |
44 // Local System and built-in administrators only. | |
45 const wchar_t kConfigFileSecurityDescriptor[] = | |
46 L"O:BAG:BAD:(A;;GA;;;SY)(A;;GA;;;BA)"; | |
47 | |
48 const wchar_t kUnprivilegedConfigFileSecurityDescriptor[] = | |
49 L"O:BAG:BAD:(A;;GA;;;SY)(A;;GA;;;BA)(A;;GR;;;AU)"; | |
50 | |
51 // Configuration keys. | |
52 const char kHostId[] = "host_id"; | |
53 const char kXmppLogin[] = "xmpp_login"; | |
54 const char kHostSecretHash[] = "host_secret_hash"; | |
55 | |
56 // The configuration keys that cannot be specified in UpdateConfig(). | |
57 const char* const kReadonlyKeys[] = { kHostId, kXmppLogin }; | |
58 | |
59 // The configuration keys whose values may be read by GetConfig(). | |
60 const char* const kUnprivilegedConfigKeys[] = { kHostId, kXmppLogin }; | |
61 | |
62 // Determines if the client runs in the security context that allows performing | |
63 // administrative tasks (i.e. the user belongs to the adminstrators group and | |
64 // the client runs elevated). | |
65 bool IsClientAdmin() { | |
66 HRESULT hr = CoImpersonateClient(); | |
67 if (FAILED(hr)) { | |
68 return false; | |
69 } | |
70 | |
71 SID_IDENTIFIER_AUTHORITY nt_authority = SECURITY_NT_AUTHORITY; | |
72 PSID administrators_group = NULL; | |
73 BOOL result = AllocateAndInitializeSid(&nt_authority, | |
74 2, | |
75 SECURITY_BUILTIN_DOMAIN_RID, | |
76 DOMAIN_ALIAS_RID_ADMINS, | |
77 0, 0, 0, 0, 0, 0, | |
78 &administrators_group); | |
79 if (result) { | |
80 if (!CheckTokenMembership(NULL, administrators_group, &result)) { | |
81 result = false; | |
82 } | |
83 FreeSid(administrators_group); | |
84 } | |
85 | |
86 hr = CoRevertToSelf(); | |
87 CHECK(SUCCEEDED(hr)); | |
88 | |
89 return !!result; | |
90 } | |
91 | |
92 // Reads and parses the configuration file up to |kMaxConfigFileSize| in | |
93 // size. | |
94 HRESULT ReadConfig(const FilePath& filename, | |
95 scoped_ptr<base::DictionaryValue>* config_out) { | |
96 | |
97 // Read raw data from the configuration file. | |
98 base::win::ScopedHandle file( | |
99 CreateFileW(filename.value().c_str(), | |
100 GENERIC_READ, | |
101 FILE_SHARE_READ | FILE_SHARE_WRITE, | |
102 NULL, | |
103 OPEN_EXISTING, | |
104 FILE_FLAG_SEQUENTIAL_SCAN, | |
105 NULL)); | |
106 | |
107 if (!file.IsValid()) { | |
108 DWORD error = GetLastError(); | |
109 LOG_GETLASTERROR(ERROR) | |
110 << "Failed to open '" << filename.value() << "'"; | |
111 return HRESULT_FROM_WIN32(error); | |
112 } | |
113 | |
114 scoped_array<char> buffer(new char[kMaxConfigFileSize]); | |
115 DWORD size = kMaxConfigFileSize; | |
116 if (!::ReadFile(file, &buffer[0], size, &size, NULL)) { | |
117 DWORD error = GetLastError(); | |
118 LOG_GETLASTERROR(ERROR) | |
119 << "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 FilePath GetTempLocationFor(const FilePath& filename) { | |
140 return filename.ReplaceExtension(kTempFileExtension); | |
141 } | |
142 | |
143 // Writes a config file to a temporary location. | |
144 HRESULT WriteConfigFileToTemp(const FilePath& filename, | |
145 const wchar_t* security_descriptor, | |
146 const char* content, | |
147 size_t length) { | |
148 // Create a security descriptor for the configuration file. | |
149 SECURITY_ATTRIBUTES security_attributes; | |
150 security_attributes.nLength = sizeof(security_attributes); | |
151 security_attributes.bInheritHandle = FALSE; | |
152 | |
153 ULONG security_descriptor_length = 0; | |
154 if (!ConvertStringSecurityDescriptorToSecurityDescriptorW( | |
155 security_descriptor, | |
156 SDDL_REVISION_1, | |
157 reinterpret_cast<PSECURITY_DESCRIPTOR*>( | |
158 &security_attributes.lpSecurityDescriptor), | |
159 &security_descriptor_length)) { | |
160 DWORD error = GetLastError(); | |
161 LOG_GETLASTERROR(ERROR) << | |
162 "Failed to create a security descriptor for the configuration file"; | |
163 return HRESULT_FROM_WIN32(error); | |
164 } | |
165 | |
166 // Create a temporary file and write configuration to it. | |
167 FilePath tempname = GetTempLocationFor(filename); | |
168 base::win::ScopedHandle file( | |
169 CreateFileW(tempname.value().c_str(), | |
170 GENERIC_WRITE, | |
171 0, | |
172 &security_attributes, | |
173 CREATE_ALWAYS, | |
174 FILE_FLAG_SEQUENTIAL_SCAN, | |
175 NULL)); | |
176 | |
177 if (!file.IsValid()) { | |
178 DWORD error = GetLastError(); | |
179 LOG_GETLASTERROR(ERROR) | |
180 << "Failed to create '" << filename.value() << "'"; | |
181 return HRESULT_FROM_WIN32(error); | |
182 } | |
183 | |
184 DWORD written; | |
185 if (!WriteFile(file, content, static_cast<DWORD>(length), &written, NULL)) { | |
186 DWORD error = GetLastError(); | |
187 LOG_GETLASTERROR(ERROR) | |
188 << "Failed to write to '" << filename.value() << "'"; | |
189 return HRESULT_FROM_WIN32(error); | |
190 } | |
191 | |
192 return S_OK; | |
193 } | |
194 | |
195 // Moves a config file from its temporary location to its permanent location. | |
196 HRESULT MoveConfigFileFromTemp(const FilePath& filename) { | |
197 // Now that the configuration is stored successfully replace the actual | |
198 // configuration file. | |
199 FilePath tempname = GetTempLocationFor(filename); | |
200 if (!MoveFileExW(tempname.value().c_str(), | |
201 filename.value().c_str(), | |
202 MOVEFILE_REPLACE_EXISTING)) { | |
203 DWORD error = GetLastError(); | |
204 LOG_GETLASTERROR(ERROR) | |
205 << "Failed to rename '" << tempname.value() << "' to '" | |
206 << filename.value() << "'"; | |
207 return HRESULT_FROM_WIN32(error); | |
208 } | |
209 | |
210 return S_OK; | |
211 } | |
212 | |
213 // Writes the configuration file up to |kMaxConfigFileSize| in size. | |
214 HRESULT WriteConfig(const char* content, size_t length, HWND owner_window) { | |
215 if (length > kMaxConfigFileSize) { | |
216 return E_FAIL; | |
217 } | |
218 | |
219 // Extract the configuration data that the user will verify. | |
220 scoped_ptr<base::Value> config_value(base::JSONReader::Read(content)); | |
221 if (!config_value.get()) { | |
222 return E_FAIL; | |
223 } | |
224 base::DictionaryValue* config_dict = NULL; | |
225 if (!config_value->GetAsDictionary(&config_dict)) { | |
226 return E_FAIL; | |
227 } | |
228 std::string email, host_id, host_secret_hash; | |
229 if (!config_dict->GetString(kXmppLogin, &email) || | |
230 !config_dict->GetString(kHostId, &host_id) || | |
231 !config_dict->GetString(kHostSecretHash, &host_secret_hash)) { | |
232 return E_FAIL; | |
233 } | |
234 | |
235 // Ask the user to verify the configuration (unless the client is admin | |
236 // already). | |
237 if (!IsClientAdmin()) { | |
238 remoting::VerifyConfigWindowWin verify_win(email, host_id, | |
239 host_secret_hash); | |
240 if (verify_win.DoModal(owner_window) != IDOK) { | |
241 return E_FAIL; | |
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 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 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 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 namespace remoting { | |
297 | |
298 ElevatedControllerWin::ElevatedControllerWin() : owner_window_(NULL) { | |
299 } | |
300 | |
301 HRESULT ElevatedControllerWin::FinalConstruct() { | |
302 return S_OK; | |
303 } | |
304 | |
305 void ElevatedControllerWin::FinalRelease() { | |
306 } | |
307 | |
308 STDMETHODIMP ElevatedControllerWin::GetConfig(BSTR* config_out) { | |
309 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(UTF8ToUTF16(file_content).c_str()); | |
324 if (config_out == NULL) { | |
325 return E_OUTOFMEMORY; | |
326 } | |
327 | |
328 return S_OK; | |
329 } | |
330 | |
331 STDMETHODIMP ElevatedControllerWin::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 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 | |
352 STDMETHODIMP ElevatedControllerWin::SetConfig(BSTR config) { | |
353 // Determine the config directory path and create it if necessary. | |
354 FilePath config_dir = remoting::GetConfigDir(); | |
355 if (!file_util::CreateDirectory(config_dir)) { | |
356 return HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED); | |
357 } | |
358 | |
359 std::string file_content = UTF16ToUTF8( | |
360 string16(static_cast<char16*>(config), ::SysStringLen(config))); | |
361 | |
362 return WriteConfig(file_content.c_str(), file_content.size(), owner_window_); | |
363 } | |
364 | |
365 STDMETHODIMP ElevatedControllerWin::SetOwnerWindow(LONG_PTR window_handle) { | |
366 owner_window_ = reinterpret_cast<HWND>(window_handle); | |
367 return S_OK; | |
368 } | |
369 | |
370 STDMETHODIMP ElevatedControllerWin::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, | |
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 LOG_GETLASTERROR(ERROR) | |
391 << "Failed to change the '" << kWindowsServiceName | |
392 << "'service start type to 'auto'"; | |
393 return HRESULT_FROM_WIN32(error); | |
394 } | |
395 | |
396 // Start the service. | |
397 if (!StartService(service, 0, NULL)) { | |
398 DWORD error = GetLastError(); | |
399 if (error != ERROR_SERVICE_ALREADY_RUNNING) { | |
400 LOG_GETLASTERROR(ERROR) | |
401 << "Failed to start the '" << kWindowsServiceName << "'service"; | |
402 | |
403 return HRESULT_FROM_WIN32(error); | |
404 } | |
405 } | |
406 | |
407 return S_OK; | |
408 } | |
409 | |
410 STDMETHODIMP ElevatedControllerWin::StopDaemon() { | |
411 ScopedScHandle service; | |
412 HRESULT hr = OpenService(&service); | |
413 if (FAILED(hr)) { | |
414 return hr; | |
415 } | |
416 | |
417 // Change the service start type to 'manual'. | |
418 if (!::ChangeServiceConfigW(service, | |
419 SERVICE_NO_CHANGE, | |
420 SERVICE_DEMAND_START, | |
421 SERVICE_NO_CHANGE, | |
422 NULL, | |
423 NULL, | |
424 NULL, | |
425 NULL, | |
426 NULL, | |
427 NULL, | |
428 NULL)) { | |
429 DWORD error = GetLastError(); | |
430 LOG_GETLASTERROR(ERROR) | |
431 << "Failed to change the '" << kWindowsServiceName | |
432 << "'service start type to 'manual'"; | |
433 return HRESULT_FROM_WIN32(error); | |
434 } | |
435 | |
436 // Stop the service. | |
437 SERVICE_STATUS status; | |
438 if (!ControlService(service, SERVICE_CONTROL_STOP, &status)) { | |
439 DWORD error = GetLastError(); | |
440 if (error != ERROR_SERVICE_NOT_ACTIVE) { | |
441 LOG_GETLASTERROR(ERROR) | |
442 << "Failed to stop the '" << kWindowsServiceName << "'service"; | |
443 return HRESULT_FROM_WIN32(error); | |
444 } | |
445 } | |
446 | |
447 return S_OK; | |
448 } | |
449 | |
450 STDMETHODIMP ElevatedControllerWin::UpdateConfig(BSTR config) { | |
451 // Parse the config. | |
452 std::string config_str = UTF16ToUTF8( | |
453 string16(static_cast<char16*>(config), ::SysStringLen(config))); | |
454 scoped_ptr<base::Value> config_value(base::JSONReader::Read(config_str)); | |
455 if (!config_value.get()) { | |
456 return E_FAIL; | |
457 } | |
458 base::DictionaryValue* config_dict = NULL; | |
459 if (!config_value->GetAsDictionary(&config_dict)) { | |
460 return E_FAIL; | |
461 } | |
462 // Check for bad keys. | |
463 for (int i = 0; i < arraysize(kReadonlyKeys); ++i) { | |
464 if (config_dict->HasKey(kReadonlyKeys[i])) { | |
465 return HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED); | |
466 } | |
467 } | |
468 // Get the old config. | |
469 FilePath config_dir = remoting::GetConfigDir(); | |
470 scoped_ptr<base::DictionaryValue> config_old; | |
471 HRESULT hr = ReadConfig(config_dir.Append(kConfigFileName), &config_old); | |
472 if (FAILED(hr)) { | |
473 return hr; | |
474 } | |
475 // Merge items from the given config into the old config. | |
476 config_old->MergeDictionary(config_dict); | |
477 // Write the updated config. | |
478 std::string config_updated_str; | |
479 base::JSONWriter::Write(config_old.get(), &config_updated_str); | |
480 return WriteConfig(config_updated_str.c_str(), config_updated_str.size(), | |
481 owner_window_); | |
482 } | |
483 | |
484 STDMETHODIMP ElevatedControllerWin::GetUsageStatsConsent(BOOL* allowed, | |
485 BOOL* set_by_policy) { | |
486 bool local_allowed; | |
487 bool local_set_by_policy; | |
488 if (remoting::GetUsageStatsConsent(&local_allowed, &local_set_by_policy)) { | |
489 *allowed = local_allowed; | |
490 *set_by_policy = local_set_by_policy; | |
491 return S_OK; | |
492 } else { | |
493 return E_FAIL; | |
494 } | |
495 } | |
496 | |
497 STDMETHODIMP ElevatedControllerWin::SetUsageStatsConsent(BOOL allowed) { | |
498 if (remoting::SetUsageStatsConsent(!!allowed)) { | |
499 return S_OK; | |
500 } else { | |
501 return E_FAIL; | |
502 } | |
503 } | |
504 | |
505 HRESULT ElevatedControllerWin::OpenService(ScopedScHandle* service_out) { | |
506 DWORD error; | |
507 | |
508 ScopedScHandle scmanager( | |
509 ::OpenSCManagerW(NULL, SERVICES_ACTIVE_DATABASE, | |
510 SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE)); | |
511 if (!scmanager.IsValid()) { | |
512 error = GetLastError(); | |
513 LOG_GETLASTERROR(ERROR) | |
514 << "Failed to connect to the service control manager"; | |
515 | |
516 return HRESULT_FROM_WIN32(error); | |
517 } | |
518 | |
519 DWORD desired_access = SERVICE_CHANGE_CONFIG | SERVICE_QUERY_STATUS | | |
520 SERVICE_START | SERVICE_STOP; | |
521 ScopedScHandle service( | |
522 ::OpenServiceW(scmanager, kWindowsServiceName, desired_access)); | |
523 if (!service.IsValid()) { | |
524 error = GetLastError(); | |
525 LOG_GETLASTERROR(ERROR) | |
526 << "Failed to open to the '" << kWindowsServiceName << "' service"; | |
527 | |
528 return HRESULT_FROM_WIN32(error); | |
529 } | |
530 | |
531 service_out->Set(service.Take()); | |
532 return S_OK; | |
533 } | |
534 | |
535 } // namespace remoting | |
OLD | NEW |