Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "remoting/host/elevated_controller_win.h" | 5 #include "remoting/host/elevated_controller_win.h" |
| 6 | 6 |
| 7 #include <sddl.h> | 7 #include <sddl.h> |
| 8 | 8 |
| 9 #include "base/file_util.h" | 9 #include "base/file_util.h" |
| 10 #include "base/logging.h" | 10 #include "base/logging.h" |
| 11 #include "base/json/json_reader.h" | 11 #include "base/json/json_reader.h" |
| 12 #include "base/json/json_writer.h" | 12 #include "base/json/json_writer.h" |
| 13 #include "base/memory/scoped_ptr.h" | 13 #include "base/memory/scoped_ptr.h" |
| 14 #include "base/path_service.h" | 14 #include "base/path_service.h" |
| 15 #include "base/stringize_macros.h" | 15 #include "base/stringize_macros.h" |
| 16 #include "base/utf_string_conversions.h" | 16 #include "base/utf_string_conversions.h" |
| 17 #include "base/values.h" | 17 #include "base/values.h" |
| 18 #include "base/win/scoped_handle.h" | 18 #include "base/win/scoped_handle.h" |
| 19 #include "remoting/host/branding.h" | 19 #include "remoting/host/branding.h" |
| 20 #include "remoting/host/daemon_controller_common_win.h" | |
| 20 #include "remoting/host/elevated_controller_resource.h" | 21 #include "remoting/host/elevated_controller_resource.h" |
| 21 #include "remoting/host/verify_config_window_win.h" | 22 #include "remoting/host/verify_config_window_win.h" |
| 22 | 23 |
| 23 namespace { | 24 namespace { |
| 24 | 25 |
| 25 // The host configuration file name. | 26 // The host configuration file name. |
| 26 const FilePath::CharType kConfigFileName[] = FILE_PATH_LITERAL("host.json"); | 27 const FilePath::CharType kConfigFileName[] = FILE_PATH_LITERAL("host.json"); |
| 27 | 28 |
| 28 // The extension for the temporary file. | 29 // The extension for the temporary file. |
| 29 const FilePath::CharType kTempFileExtension[] = FILE_PATH_LITERAL("json~"); | 30 const FilePath::CharType kTempFileExtension[] = FILE_PATH_LITERAL("json~"); |
| 30 | 31 |
| 31 // The host configuration file security descriptor that enables full access to | 32 // The host configuration file security descriptor that enables full access to |
| 32 // Local System and built-in administrators only. | 33 // Local System and built-in administrators only. |
| 33 const char16 kConfigFileSecurityDescriptor[] = | 34 const char16 kConfigFileSecurityDescriptor[] = |
| 34 TO_L_STRING("O:BAG:BAD:(A;;GA;;;SY)(A;;GA;;;BA)"); | 35 TO_L_STRING("O:BAG:BAD:(A;;GA;;;SY)(A;;GA;;;BA)"); |
| 35 | 36 |
| 36 // The maximum size of the configuration file. "1MB ought to be enough" for any | 37 const char16 kUnprivilegedConfigFileSecurityDescriptor[] = |
| 37 // reasonable configuration we will ever need. 1MB is low enough to make | 38 TO_L_STRING("O:BAG:BAD:(A;;GA;;;SY)(A;;GA;;;BA)(A;;GR;;;AU)"); |
| 38 // the probability of out of memory situation fairly low. OOM is still possible | |
| 39 // and we will crash if it occurs. | |
| 40 const size_t kMaxConfigFileSize = 1024 * 1024; | |
| 41 | 39 |
| 42 // ReadConfig() filters the configuration file stripping all variables except of | 40 // Configuration keys. |
| 43 // the following two. | |
| 44 const char kHostId[] = "host_id"; | 41 const char kHostId[] = "host_id"; |
| 45 const char kXmppLogin[] = "xmpp_login"; | 42 const char kXmppLogin[] = "xmpp_login"; |
| 43 const char kHostSecretHash[] = "host_secret_hash"; | |
| 46 | 44 |
| 47 // The configuration keys that cannot be specified in UpdateConfig(). | 45 // The configuration keys that cannot be specified in UpdateConfig(). |
| 48 const char* const kReadonlyKeys[] = { kHostId, kXmppLogin }; | 46 const char* const kReadonlyKeys[] = { kHostId, kXmppLogin }; |
| 49 | 47 |
| 48 // The configuration keys whose values may be read by GetConfig(). | |
| 49 const char* const kUnprivilegedConfigKeys[] = { kHostId, kXmppLogin }; | |
|
alexeypa (please no reviews)
2012/04/23 17:35:10
kReadonlyKeys and kUnprivilegedConfigKeys strangel
simonmorris
2012/04/23 19:39:49
Yes, but is it clear that the two sets will always
alexeypa (please no reviews)
2012/04/24 16:54:56
As per our discussion these lists are not the same
simonmorris
2012/04/25 00:33:32
crbug.com/124825
| |
| 50 | |
| 50 // Reads and parses the configuration file up to |kMaxConfigFileSize| in | 51 // Reads and parses the configuration file up to |kMaxConfigFileSize| in |
| 51 // size. | 52 // size. |
| 52 HRESULT ReadConfig(const FilePath& filename, | 53 HRESULT ReadConfig(const FilePath& filename, |
|
alexeypa (please no reviews)
2012/04/23 17:35:10
Is ReadConfig used now, when GetCOnfig is implemen
simonmorris
2012/04/23 19:39:49
No, but there's no need to use this CL to remove i
alexeypa (please no reviews)
2012/04/24 16:54:56
Please include this into the COM interfaces cleanu
simonmorris
2012/04/25 00:33:32
crbug.com/124937
| |
| 53 scoped_ptr<base::DictionaryValue>* config_out) { | 54 scoped_ptr<base::DictionaryValue>* config_out) { |
| 54 | 55 |
| 55 // Read raw data from the configuration file. | 56 // Read raw data from the configuration file. |
| 56 base::win::ScopedHandle file( | 57 base::win::ScopedHandle file( |
| 57 CreateFileW(filename.value().c_str(), | 58 CreateFileW(filename.value().c_str(), |
| 58 GENERIC_READ, | 59 GENERIC_READ, |
| 59 FILE_SHARE_READ | FILE_SHARE_WRITE, | 60 FILE_SHARE_READ | FILE_SHARE_WRITE, |
| 60 NULL, | 61 NULL, |
| 61 OPEN_EXISTING, | 62 OPEN_EXISTING, |
| 62 FILE_FLAG_SEQUENTIAL_SCAN, | 63 FILE_FLAG_SEQUENTIAL_SCAN, |
| 63 NULL)); | 64 NULL)); |
| 64 | 65 |
| 65 if (!file.IsValid()) { | 66 if (!file.IsValid()) { |
| 66 DWORD error = GetLastError(); | 67 DWORD error = GetLastError(); |
| 67 LOG_GETLASTERROR(ERROR) | 68 LOG_GETLASTERROR(ERROR) |
| 68 << "Failed to open '" << filename.value() << "'"; | 69 << "Failed to open '" << filename.value() << "'"; |
| 69 return HRESULT_FROM_WIN32(error); | 70 return HRESULT_FROM_WIN32(error); |
| 70 } | 71 } |
| 71 | 72 |
| 72 scoped_array<char> buffer(new char[kMaxConfigFileSize]); | 73 scoped_array<char> buffer(new char[remoting::kMaxConfigFileSize]); |
| 73 DWORD size = kMaxConfigFileSize; | 74 DWORD size = remoting::kMaxConfigFileSize; |
| 74 if (!::ReadFile(file, &buffer[0], size, &size, NULL)) { | 75 if (!::ReadFile(file, &buffer[0], size, &size, NULL)) { |
| 75 DWORD error = GetLastError(); | 76 DWORD error = GetLastError(); |
| 76 LOG_GETLASTERROR(ERROR) | 77 LOG_GETLASTERROR(ERROR) |
| 77 << "Failed to read '" << filename.value() << "'"; | 78 << "Failed to read '" << filename.value() << "'"; |
| 78 return HRESULT_FROM_WIN32(error); | 79 return HRESULT_FROM_WIN32(error); |
| 79 } | 80 } |
| 80 | 81 |
| 81 // Parse the JSON configuration, expecting it to contain a dictionary. | 82 // Parse the JSON configuration, expecting it to contain a dictionary. |
| 82 std::string file_content(buffer.get(), size); | 83 std::string file_content(buffer.get(), size); |
| 83 scoped_ptr<base::Value> value( | 84 scoped_ptr<base::Value> value( |
| 84 base::JSONReader::Read(file_content, base::JSON_ALLOW_TRAILING_COMMAS)); | 85 base::JSONReader::Read(file_content, base::JSON_ALLOW_TRAILING_COMMAS)); |
| 85 | 86 |
| 86 base::DictionaryValue* dictionary; | 87 base::DictionaryValue* dictionary; |
| 87 if (value.get() == NULL || !value->GetAsDictionary(&dictionary)) { | 88 if (value.get() == NULL || !value->GetAsDictionary(&dictionary)) { |
| 88 LOG(ERROR) << "Failed to read '" << filename.value() << "'."; | 89 LOG(ERROR) << "Failed to read '" << filename.value() << "'."; |
| 89 return E_FAIL; | 90 return E_FAIL; |
| 90 } | 91 } |
| 91 | 92 |
| 92 value.release(); | 93 value.release(); |
| 93 config_out->reset(dictionary); | 94 config_out->reset(dictionary); |
| 94 return S_OK; | 95 return S_OK; |
| 95 } | 96 } |
| 96 | 97 |
| 97 // Writes the configuration file up to |kMaxConfigFileSize| in size. | 98 HRESULT WriteConfigFile(bool privileged, const char* content, size_t length) { |
| 98 HRESULT WriteConfig(const FilePath& filename, | |
| 99 const char* content, | |
| 100 size_t length) { | |
| 101 if (length > kMaxConfigFileSize) { | |
| 102 return E_FAIL; | |
| 103 } | |
| 104 | |
| 105 // Extract the configuration data that the user will verify. | |
| 106 scoped_ptr<base::Value> config_value(base::JSONReader::Read(content)); | |
| 107 if (!config_value.get()) { | |
| 108 return E_FAIL; | |
| 109 } | |
| 110 base::DictionaryValue* config_dict = NULL; | |
| 111 if (!config_value->GetAsDictionary(&config_dict)) { | |
| 112 return E_FAIL; | |
| 113 } | |
| 114 std::string email, host_id, host_secret_hash; | |
| 115 if (!config_dict->GetString("xmpp_login", &email) || | |
| 116 !config_dict->GetString("host_id", &host_id) || | |
| 117 !config_dict->GetString("host_secret_hash", &host_secret_hash)) { | |
| 118 return E_FAIL; | |
| 119 } | |
| 120 | |
| 121 // Ask the user to verify the configuration. | |
| 122 remoting::VerifyConfigWindowWin verify_win(email, host_id, host_secret_hash); | |
| 123 if (!verify_win.Run()) { | |
| 124 return E_FAIL; | |
| 125 } | |
| 126 | |
| 127 // Create a security descriptor for the configuration file. | 99 // Create a security descriptor for the configuration file. |
| 128 SECURITY_ATTRIBUTES security_attributes; | 100 SECURITY_ATTRIBUTES security_attributes; |
| 129 security_attributes.nLength = sizeof(security_attributes); | 101 security_attributes.nLength = sizeof(security_attributes); |
| 130 security_attributes.bInheritHandle = FALSE; | 102 security_attributes.bInheritHandle = FALSE; |
| 131 | 103 |
| 132 ULONG security_descriptor_length = 0; | 104 ULONG security_descriptor_length = 0; |
| 105 const char16* descriptor = privileged ? | |
|
alexeypa (please no reviews)
2012/04/23 17:35:10
I somehow feel that passing ACL and filename to th
simonmorris
2012/04/23 19:39:49
Done.
| |
| 106 kConfigFileSecurityDescriptor : | |
| 107 kUnprivilegedConfigFileSecurityDescriptor; | |
| 133 if (!ConvertStringSecurityDescriptorToSecurityDescriptorW( | 108 if (!ConvertStringSecurityDescriptorToSecurityDescriptorW( |
| 134 kConfigFileSecurityDescriptor, | 109 descriptor, |
| 135 SDDL_REVISION_1, | 110 SDDL_REVISION_1, |
| 136 reinterpret_cast<PSECURITY_DESCRIPTOR*>( | 111 reinterpret_cast<PSECURITY_DESCRIPTOR*>( |
| 137 &security_attributes.lpSecurityDescriptor), | 112 &security_attributes.lpSecurityDescriptor), |
| 138 &security_descriptor_length)) { | 113 &security_descriptor_length)) { |
| 139 DWORD error = GetLastError(); | 114 DWORD error = GetLastError(); |
| 140 LOG_GETLASTERROR(ERROR) << | 115 LOG_GETLASTERROR(ERROR) << |
| 141 "Failed to create a security descriptor for the configuration file"; | 116 "Failed to create a security descriptor for the configuration file"; |
| 142 return HRESULT_FROM_WIN32(error); | 117 return HRESULT_FROM_WIN32(error); |
| 143 } | 118 } |
| 144 | 119 |
| 145 // Create a temporary file and write configuration to it. | 120 // Create a temporary file and write configuration to it. |
| 121 const FilePath::CharType* basename = privileged ? | |
| 122 kConfigFileName : | |
| 123 remoting::kUnprivilegedConfigFileName; | |
| 124 FilePath filename = remoting::GetConfigDir().Append(basename); | |
| 146 FilePath tempname = filename.ReplaceExtension(kTempFileExtension); | 125 FilePath tempname = filename.ReplaceExtension(kTempFileExtension); |
| 147 { | 126 { |
| 148 base::win::ScopedHandle file( | 127 base::win::ScopedHandle file( |
| 149 CreateFileW(tempname.value().c_str(), | 128 CreateFileW(tempname.value().c_str(), |
| 150 GENERIC_WRITE, | 129 GENERIC_WRITE, |
| 151 0, | 130 0, |
| 152 &security_attributes, | 131 &security_attributes, |
| 153 CREATE_ALWAYS, | 132 CREATE_ALWAYS, |
| 154 FILE_FLAG_SEQUENTIAL_SCAN, | 133 FILE_FLAG_SEQUENTIAL_SCAN, |
| 155 NULL)); | 134 NULL)); |
| 156 | 135 |
| 157 if (!file.IsValid()) { | 136 if (!file.IsValid()) { |
| 158 DWORD error = GetLastError(); | 137 DWORD error = GetLastError(); |
| 159 LOG_GETLASTERROR(ERROR) | 138 LOG_GETLASTERROR(ERROR) |
| 160 << "Failed to create '" << filename.value() << "'"; | 139 << "Failed to create '" << filename.value() << "'"; |
| 161 return HRESULT_FROM_WIN32(error); | 140 return HRESULT_FROM_WIN32(error); |
| 162 } | 141 } |
| 163 | 142 |
| 164 DWORD written; | 143 DWORD written; |
| 165 if (!WriteFile(file, content, static_cast<DWORD>(length), &written, NULL)) { | 144 if (!WriteFile(file, content, static_cast<DWORD>(length), &written, NULL)) { |
| 166 DWORD error = GetLastError(); | 145 DWORD error = GetLastError(); |
| 167 LOG_GETLASTERROR(ERROR) | 146 LOG_GETLASTERROR(ERROR) |
| 168 << "Failed to write to '" << filename.value() << "'"; | 147 << "Failed to write to '" << filename.value() << "'"; |
| 169 return HRESULT_FROM_WIN32(error); | 148 return HRESULT_FROM_WIN32(error); |
| 170 } | 149 } |
| 171 } | 150 } |
| 172 | 151 |
| 173 // Now that the configuration is stored successfully replace the actual | 152 // Now that the configuration is stored successfully replace the actual |
| 174 // configuration file. | 153 // configuration file. |
| 175 if (!MoveFileExW(tempname.value().c_str(), | 154 if (!MoveFileExW(tempname.value().c_str(), |
|
alexeypa (please no reviews)
2012/04/23 17:35:10
Both MoveFileEx should the last thing to happen (u
simonmorris
2012/04/23 19:39:49
I'll make this change when we're sure that the unp
alexeypa (please no reviews)
2012/04/24 16:54:56
Just to document what we discussed. Both files wil
simonmorris
2012/04/25 00:33:32
Done.
| |
| 176 filename.value().c_str(), | 155 filename.value().c_str(), |
| 177 MOVEFILE_REPLACE_EXISTING)) { | 156 MOVEFILE_REPLACE_EXISTING)) { |
| 178 DWORD error = GetLastError(); | 157 DWORD error = GetLastError(); |
| 179 LOG_GETLASTERROR(ERROR) | 158 LOG_GETLASTERROR(ERROR) |
| 180 << "Failed to rename '" << tempname.value() << "' to '" | 159 << "Failed to rename '" << tempname.value() << "' to '" |
| 181 << filename.value() << "'"; | 160 << filename.value() << "'"; |
| 182 return HRESULT_FROM_WIN32(error); | 161 return HRESULT_FROM_WIN32(error); |
| 183 } | 162 } |
| 184 | 163 |
| 185 return S_OK; | 164 return S_OK; |
| 186 } | 165 } |
| 187 | 166 |
| 167 // Writes the configuration file up to |kMaxConfigFileSize| in size. | |
| 168 HRESULT WriteConfig(const char* content, size_t length) { | |
| 169 if (length > remoting::kMaxConfigFileSize) { | |
| 170 return E_FAIL; | |
| 171 } | |
| 172 | |
| 173 // Extract the configuration data that the user will verify. | |
| 174 scoped_ptr<base::Value> config_value(base::JSONReader::Read(content)); | |
| 175 if (!config_value.get()) { | |
| 176 return E_FAIL; | |
| 177 } | |
| 178 base::DictionaryValue* config_dict = NULL; | |
| 179 if (!config_value->GetAsDictionary(&config_dict)) { | |
| 180 return E_FAIL; | |
| 181 } | |
| 182 std::string email, host_id, host_secret_hash; | |
| 183 if (!config_dict->GetString(kXmppLogin, &email) || | |
| 184 !config_dict->GetString(kHostId, &host_id) || | |
| 185 !config_dict->GetString(kHostSecretHash, &host_secret_hash)) { | |
| 186 return E_FAIL; | |
| 187 } | |
| 188 | |
| 189 // Ask the user to verify the configuration. | |
| 190 remoting::VerifyConfigWindowWin verify_win(email, host_id, host_secret_hash); | |
| 191 if (!verify_win.Run()) { | |
| 192 return E_FAIL; | |
| 193 } | |
| 194 | |
| 195 // Write the full configuration file. | |
| 196 HRESULT hr = WriteConfigFile(true, content, length); | |
| 197 if (FAILED(hr)) { | |
| 198 return hr; | |
| 199 } | |
| 200 | |
| 201 // Extract the unprivileged fields from the configuration. | |
| 202 base::DictionaryValue unprivileged_config_dict; | |
| 203 for (int i = 0; i < arraysize(kUnprivilegedConfigKeys); ++i) { | |
| 204 const char* key = kUnprivilegedConfigKeys[i]; | |
| 205 string16 value; | |
| 206 if (config_dict->GetString(key, &value)) { | |
| 207 unprivileged_config_dict.SetString(key, value); | |
| 208 } | |
| 209 } | |
| 210 std::string unprivileged_config_str; | |
| 211 base::JSONWriter::Write(&unprivileged_config_dict, &unprivileged_config_str); | |
| 212 | |
| 213 // Write the unprivileged configuration file. | |
| 214 hr = WriteConfigFile(false, | |
| 215 unprivileged_config_str.data(), | |
| 216 unprivileged_config_str.size()); | |
| 217 if (FAILED(hr)) { | |
| 218 return hr; | |
| 219 } | |
| 220 | |
| 221 return S_OK; | |
| 222 } | |
| 188 | 223 |
| 189 } // namespace | 224 } // namespace |
| 190 | 225 |
| 191 namespace remoting { | 226 namespace remoting { |
| 192 | 227 |
| 193 ElevatedControllerWin::ElevatedControllerWin() { | 228 ElevatedControllerWin::ElevatedControllerWin() { |
| 194 } | 229 } |
| 195 | 230 |
| 196 HRESULT ElevatedControllerWin::FinalConstruct() { | 231 HRESULT ElevatedControllerWin::FinalConstruct() { |
| 197 return S_OK; | 232 return S_OK; |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 237 STDMETHODIMP ElevatedControllerWin::SetConfig(BSTR config) { | 272 STDMETHODIMP ElevatedControllerWin::SetConfig(BSTR config) { |
| 238 // Determine the config directory path and create it if necessary. | 273 // Determine the config directory path and create it if necessary. |
| 239 FilePath config_dir = remoting::GetConfigDir(); | 274 FilePath config_dir = remoting::GetConfigDir(); |
| 240 if (!file_util::CreateDirectory(config_dir)) { | 275 if (!file_util::CreateDirectory(config_dir)) { |
| 241 return HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED); | 276 return HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED); |
| 242 } | 277 } |
| 243 | 278 |
| 244 std::string file_content = UTF16ToUTF8( | 279 std::string file_content = UTF16ToUTF8( |
| 245 string16(static_cast<char16*>(config), ::SysStringLen(config))); | 280 string16(static_cast<char16*>(config), ::SysStringLen(config))); |
| 246 | 281 |
| 247 return WriteConfig(config_dir.Append(kConfigFileName), | 282 return WriteConfig(file_content.c_str(), file_content.size()); |
| 248 file_content.c_str(), | |
| 249 file_content.size()); | |
| 250 } | 283 } |
| 251 | 284 |
| 252 STDMETHODIMP ElevatedControllerWin::StartDaemon() { | 285 STDMETHODIMP ElevatedControllerWin::StartDaemon() { |
| 253 ScopedScHandle service; | 286 ScopedScHandle service; |
| 254 HRESULT hr = OpenService(&service); | 287 HRESULT hr = OpenService(&service); |
| 255 if (FAILED(hr)) { | 288 if (FAILED(hr)) { |
| 256 return hr; | 289 return hr; |
| 257 } | 290 } |
| 258 | 291 |
| 259 // Change the service start type to 'auto'. | 292 // Change the service start type to 'auto'. |
| (...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 352 scoped_ptr<base::DictionaryValue> config_old; | 385 scoped_ptr<base::DictionaryValue> config_old; |
| 353 HRESULT hr = ReadConfig(config_dir.Append(kConfigFileName), &config_old); | 386 HRESULT hr = ReadConfig(config_dir.Append(kConfigFileName), &config_old); |
| 354 if (FAILED(hr)) { | 387 if (FAILED(hr)) { |
| 355 return hr; | 388 return hr; |
| 356 } | 389 } |
| 357 // Merge items from the given config into the old config. | 390 // Merge items from the given config into the old config. |
| 358 config_old->MergeDictionary(config_dict); | 391 config_old->MergeDictionary(config_dict); |
| 359 // Write the updated config. | 392 // Write the updated config. |
| 360 std::string config_updated_str; | 393 std::string config_updated_str; |
| 361 base::JSONWriter::Write(config_old.get(), &config_updated_str); | 394 base::JSONWriter::Write(config_old.get(), &config_updated_str); |
| 362 return WriteConfig(config_dir.Append(kConfigFileName), | 395 return WriteConfig(config_updated_str.c_str(), config_updated_str.size()); |
| 363 config_updated_str.c_str(), | |
| 364 config_updated_str.size()); | |
| 365 } | 396 } |
| 366 | 397 |
| 367 HRESULT ElevatedControllerWin::OpenService(ScopedScHandle* service_out) { | 398 HRESULT ElevatedControllerWin::OpenService(ScopedScHandle* service_out) { |
| 368 DWORD error; | 399 DWORD error; |
| 369 | 400 |
| 370 ScopedScHandle scmanager( | 401 ScopedScHandle scmanager( |
| 371 ::OpenSCManagerW(NULL, SERVICES_ACTIVE_DATABASE, | 402 ::OpenSCManagerW(NULL, SERVICES_ACTIVE_DATABASE, |
| 372 SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE)); | 403 SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE)); |
| 373 if (!scmanager.IsValid()) { | 404 if (!scmanager.IsValid()) { |
| 374 error = GetLastError(); | 405 error = GetLastError(); |
| (...skipping 13 matching lines...) Expand all Loading... | |
| 388 << "Failed to open to the '" << kWindowsServiceName << "' service"; | 419 << "Failed to open to the '" << kWindowsServiceName << "' service"; |
| 389 | 420 |
| 390 return HRESULT_FROM_WIN32(error); | 421 return HRESULT_FROM_WIN32(error); |
| 391 } | 422 } |
| 392 | 423 |
| 393 service_out->Set(service.Take()); | 424 service_out->Set(service.Take()); |
| 394 return S_OK; | 425 return S_OK; |
| 395 } | 426 } |
| 396 | 427 |
| 397 } // namespace remoting | 428 } // namespace remoting |
| OLD | NEW |