1 // Copyright (c) 2011 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 "chrome_frame/ready_mode/internal/registry_ready_mode_state.h" 6 7 #include <windows.h> 8 9 #include "base/logging.h" 10 #include "base/process/launch.h" 11 #include "base/strings/string_util.h" 12 #include "base/strings/stringprintf.h" 13 #include "base/time/time.h" 14 #include "base/win/registry.h" 15 #include "base/win/scoped_comptr.h" 16 #include "base/win/scoped_handle.h" 17 #include "base/win/windows_version.h" 18 #include "chrome/installer/util/browser_distribution.h" 19 #include "chrome/installer/util/google_update_constants.h" 20 #include "chrome/installer/util/master_preferences.h" 21 #include "chrome/installer/util/util_constants.h" 22 #include "chrome_frame/chrome_launcher_utils.h" 23 #include "chrome_frame/ready_mode/ready_mode.h" 24 25 namespace { 26 27 // Looks up a command entry in the registry and attempts to execute it directly. 28 // Returns the new process handle, which the caller is responsible for closing, 29 // or NULL upon failure. 30 HANDLE LaunchCommandDirectly(const std::wstring& command_field) { 31 BrowserDistribution* dist = BrowserDistribution::GetDistribution(); 32 std::wstring version_key_name(dist->GetVersionKey()); 33 34 HKEY roots[] = {HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE}; 35 36 for (int i = 0; i < arraysize(roots); i++) { 37 base::win::RegKey version_key; 38 if (version_key.Open(roots[i], version_key_name.c_str(), 39 KEY_QUERY_VALUE) == ERROR_SUCCESS) { 40 std::wstring command_line; 41 if (version_key.ReadValue(command_field.c_str(), 42 &command_line) == ERROR_SUCCESS) { 43 HANDLE launched_process = NULL; 44 base::LaunchOptions options; 45 options.start_hidden = true; 46 if (base::LaunchProcess(command_line, options, &launched_process)) { 47 return launched_process; 48 } 49 } 50 } 51 } 52 return NULL; 53 } 54 55 // Attempts to launch a command using the ProcessLauncher. Returns a handle to 56 // the launched process, which the caller is responsible for closing, or NULL 57 // upon failure. 58 HANDLE LaunchCommandViaProcessLauncher(const std::wstring& command_field) { 59 HANDLE launched_process = NULL; 60 61 scoped_ptr<CommandLine> command_line; 62 if (chrome_launcher::CreateUpdateCommandLine(command_field, &command_line)) { 63 DCHECK(command_line != NULL); 64 base::LaunchOptions options; 65 options.start_hidden = true; 66 base::LaunchProcess(*command_line, options, &launched_process); 67 } 68 69 return launched_process; 70 } 71 72 // Waits for the provided process to exit, and verifies that its exit code 73 // corresponds to one of the known "success" codes for the installer. If the 74 // exit code cannot be retrieved, or if it signals failure, returns false. 75 bool CheckProcessExitCode(HANDLE handle) { 76 // TODO(erikwright): Use RegisterWaitForSingleObject to wait 77 // asynchronously. 78 DWORD wait_result = WaitForSingleObject(handle, 5000); // (ms) 79 80 if (wait_result == WAIT_OBJECT_0) { 81 DWORD exit_code = 0; 82 if (!::GetExitCodeProcess(handle, &exit_code)) { 83 DPLOG(ERROR) << "GetExitCodeProcess failed."; 84 return false; 85 } 86 87 // These are the only two success codes returned by the installer. 88 // All other codes are errors. 89 if (exit_code != 0 && exit_code != installer::UNINSTALL_REQUIRES_REBOOT) { 90 DLOG(ERROR) << "Process failed with exit code " << exit_code << "."; 91 return false; 92 } 93 94 return true; 95 } 96 97 if (wait_result == WAIT_FAILED) 98 DPLOG(ERROR) << "Error while waiting for elevated child process."; 99 100 if (wait_result == WAIT_ABANDONED) 101 DLOG(ERROR) << "Unexpeced WAIT_ABANDONED while waiting on child process."; 102 103 if (wait_result == WAIT_TIMEOUT) 104 DLOG(ERROR) << "Timeout while waiting on child process."; 105 106 return false; 107 } 108 109 // If we are running on XP (no protected mode) or in a high-integrity process, 110 // we can invoke the installer directly. If not, we will have to go via the 111 // ProcessLauncher. 112 bool CanLaunchDirectly() { 113 if (base::win::GetVersion() < base::win::VERSION_VISTA) 114 return true; 115 116 base::IntegrityLevel integrity_level = base::INTEGRITY_UNKNOWN; 117 if (!base::GetProcessIntegrityLevel(base::GetCurrentProcessHandle(), 118 &integrity_level)) { 119 DLOG(ERROR) << "Failed to determine process integrity level."; 120 return false; 121 } 122 123 return integrity_level == base::HIGH_INTEGRITY; 124 } 125 126 // Attempts to launch the specified command either directly or via the 127 // ProcessLauncher. Returns true if the command is launched and returns a 128 // success code. 129 bool LaunchAndCheckCommand(const std::wstring& command_field) { 130 base::win::ScopedHandle handle; 131 132 if (CanLaunchDirectly()) 133 handle.Set(LaunchCommandDirectly(command_field)); 134 else 135 handle.Set(LaunchCommandViaProcessLauncher(command_field)); 136 137 if (handle.IsValid() && CheckProcessExitCode(handle)) 138 return true; 139 140 DLOG(ERROR) << "Command " << command_field << " could not be launched."; 141 return false; 142 } 143 144 } // namespace 145 146 RegistryReadyModeState::RegistryReadyModeState( 147 const std::wstring& key_name, base::TimeDelta temporary_decline_duration, 148 Observer* observer) 149 : key_name_(key_name), 150 temporary_decline_duration_(temporary_decline_duration), 151 observer_(observer) { 152 } 153 154 RegistryReadyModeState::~RegistryReadyModeState() { 155 } 156 157 base::Time RegistryReadyModeState::GetNow() { 158 return base::Time::Now(); 159 } 160 161 ReadyModeStatus RegistryReadyModeState::GetStatus() { 162 bool exists = false; 163 int64 value = 0; 164 165 if (!GetStateFromRegistry(&value, &exists)) 166 return READY_MODE_ACTIVE; 167 168 if (!exists) 169 return READY_MODE_ACCEPTED; 170 171 if (value == 0) 172 return READY_MODE_PERMANENTLY_DECLINED; 173 174 if (value == 1) 175 return READY_MODE_ACTIVE; 176 177 base::Time when_declined(base::Time::FromInternalValue(value)); 178 base::Time now(GetNow()); 179 180 // If the decline duration has passed, or is further in the future than 181 // the total timeout, consider it expired. 182 bool expired = (now - when_declined) > temporary_decline_duration_ || 183 (when_declined - now) > temporary_decline_duration_; 184 185 if (expired) 186 return READY_MODE_TEMPORARY_DECLINE_EXPIRED; 187 else 188 return READY_MODE_TEMPORARILY_DECLINED; 189 } 190 191 void RegistryReadyModeState::NotifyObserver() { 192 if (observer_ != NULL) 193 observer_->OnStateChange(GetStatus()); 194 } 195 196 bool RegistryReadyModeState::GetStateFromRegistry(int64* value, bool* exists) { 197 *exists = false; 198 *value = 0; 199 200 HKEY roots[] = { HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE }; 201 LONG result = ERROR_SUCCESS; 202 for (int i = 0; i < arraysize(roots); i++) { 203 base::win::RegKey config_key; 204 result = config_key.Open(roots[i], key_name_.c_str(), KEY_QUERY_VALUE); 205 if (result == ERROR_SUCCESS) { 206 result = config_key.ReadInt64(installer::kChromeFrameReadyModeField, 207 value); 208 if (result == ERROR_SUCCESS) { 209 *exists = true; 210 return true; 211 } 212 if (result != ERROR_FILE_NOT_FOUND) { 213 DLOG(ERROR) << "Failed to read from registry key " << key_name_ 214 << " and value " << installer::kChromeFrameReadyModeField 215 << " error: " << result; 216 return false; 217 } 218 } 219 } 220 221 return true; 222 } 223 224 void RegistryReadyModeState::RefreshStateAndNotify() { 225 HRESULT hr = UrlMkSetSessionOption(URLMON_OPTION_USERAGENT_REFRESH, 226 NULL, 0, 0); 227 if (FAILED(hr)) { 228 DLOG(ERROR) << "Failed to refresh user agent string from registry. " 229 << "UrlMkSetSessionOption returned " 230 << base::StringPrintf("0x%08x", hr); 231 } else { 232 NotifyObserver(); 233 } 234 } 235 236 void RegistryReadyModeState::ExpireTemporaryDecline() { 237 if (LaunchAndCheckCommand(google_update::kRegCFEndTempOptOutCmdField)) 238 RefreshStateAndNotify(); 239 } 240 241 void RegistryReadyModeState::TemporarilyDeclineChromeFrame() { 242 if (LaunchAndCheckCommand(google_update::kRegCFTempOptOutCmdField)) 243 RefreshStateAndNotify(); 244 } 245 246 void RegistryReadyModeState::PermanentlyDeclineChromeFrame() { 247 if (LaunchAndCheckCommand(google_update::kRegCFOptOutCmdField)) 248 RefreshStateAndNotify(); 249 } 250 251 void RegistryReadyModeState::AcceptChromeFrame() { 252 if (LaunchAndCheckCommand(google_update::kRegCFOptInCmdField)) 253 NotifyObserver(); 254 } 255