Home | History | Annotate | Download | only in internal
      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