Home | History | Annotate | Download | only in gcapi
      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 // NOTE: This code is a legacy utility API for partners to check whether
      6 //       Chrome can be installed and launched. Recent updates are being made
      7 //       to add new functionality. These updates use code from Chromium, the old
      8 //       coded against the win32 api directly. If you have an itch to shave a
      9 //       yak, feel free to re-write the old code too.
     10 
     11 #include "chrome/installer/gcapi/gcapi.h"
     12 
     13 #include <sddl.h>
     14 #define STRSAFE_NO_DEPRECATE
     15 #include <windows.h>
     16 #include <strsafe.h>
     17 #include <tlhelp32.h>
     18 
     19 #include <cstdlib>
     20 #include <iterator>
     21 #include <limits>
     22 #include <set>
     23 #include <string>
     24 
     25 #include "base/basictypes.h"
     26 #include "base/command_line.h"
     27 #include "base/files/file_path.h"
     28 #include "base/process/launch.h"
     29 #include "base/strings/string16.h"
     30 #include "base/strings/string_number_conversions.h"
     31 #include "base/strings/string_util.h"
     32 #include "base/time/time.h"
     33 #include "base/win/registry.h"
     34 #include "base/win/scoped_com_initializer.h"
     35 #include "base/win/scoped_comptr.h"
     36 #include "base/win/scoped_handle.h"
     37 #include "chrome/installer/gcapi/gcapi_omaha_experiment.h"
     38 #include "chrome/installer/gcapi/gcapi_reactivation.h"
     39 #include "chrome/installer/launcher_support/chrome_launcher_support.h"
     40 #include "chrome/installer/util/google_update_constants.h"
     41 #include "chrome/installer/util/google_update_settings.h"
     42 #include "chrome/installer/util/util_constants.h"
     43 #include "chrome/installer/util/wmi.h"
     44 #include "google_update/google_update_idl.h"
     45 
     46 using base::Time;
     47 using base::TimeDelta;
     48 using base::win::RegKey;
     49 using base::win::ScopedCOMInitializer;
     50 using base::win::ScopedComPtr;
     51 using base::win::ScopedHandle;
     52 
     53 namespace {
     54 
     55 const wchar_t kChromeRegClientsKey[] =
     56     L"Software\\Google\\Update\\Clients\\"
     57     L"{8A69D345-D564-463c-AFF1-A69D9E530F96}";
     58 const wchar_t kChromeRegClientStateKey[] =
     59     L"Software\\Google\\Update\\ClientState\\"
     60     L"{8A69D345-D564-463c-AFF1-A69D9E530F96}";
     61 const wchar_t kChromeRegClientStateMediumKey[] =
     62     L"Software\\Google\\Update\\ClientStateMedium\\"
     63     L"{8A69D345-D564-463c-AFF1-A69D9E530F96}";
     64 
     65 const wchar_t kGCAPITempKey[] = L"Software\\Google\\GCAPITemp";
     66 
     67 const wchar_t kChromeRegLaunchCmd[] = L"InstallerSuccessLaunchCmdLine";
     68 const wchar_t kChromeRegLastLaunchCmd[] = L"LastInstallerSuccessLaunchCmdLine";
     69 const wchar_t kChromeRegVersion[] = L"pv";
     70 const wchar_t kNoChromeOfferUntil[] =
     71     L"SOFTWARE\\Google\\No Chrome Offer Until";
     72 
     73 const wchar_t kC1FPendingKey[] =
     74     L"Software\\Google\\Common\\Rlz\\Events\\C";
     75 const wchar_t kC1FSentKey[] =
     76     L"Software\\Google\\Common\\Rlz\\StatefulEvents\\C";
     77 const wchar_t kC1FKey[] = L"C1F";
     78 
     79 const wchar_t kRelaunchBrandcodeValue[] = L"RelaunchBrandcode";
     80 const wchar_t kRelaunchAllowedAfterValue[] = L"RelaunchAllowedAfter";
     81 
     82 // Prefix used to match the window class for Chrome windows.
     83 const wchar_t kChromeWindowClassPrefix[] = L"Chrome_WidgetWin_";
     84 
     85 // Return the company name specified in the file version info resource.
     86 bool GetCompanyName(const wchar_t* filename, wchar_t* buffer, DWORD out_len) {
     87   wchar_t file_version_info[8192];
     88   DWORD handle = 0;
     89   DWORD buffer_size = 0;
     90 
     91   buffer_size = ::GetFileVersionInfoSize(filename, &handle);
     92   // Cannot stats the file or our buffer size is too small (very unlikely).
     93   if (buffer_size == 0 || buffer_size > _countof(file_version_info))
     94     return false;
     95 
     96   buffer_size = _countof(file_version_info);
     97   memset(file_version_info, 0, buffer_size);
     98   if (!::GetFileVersionInfo(filename, handle, buffer_size, file_version_info))
     99     return false;
    100 
    101   DWORD data_len = 0;
    102   LPVOID data = NULL;
    103   // Retrieve the language and codepage code if exists.
    104   buffer_size = 0;
    105   if (!::VerQueryValue(file_version_info, TEXT("\\VarFileInfo\\Translation"),
    106       reinterpret_cast<LPVOID *>(&data), reinterpret_cast<UINT *>(&data_len)))
    107     return false;
    108   if (data_len != 4)
    109     return false;
    110 
    111   wchar_t info_name[256];
    112   DWORD lang = 0;
    113   // Formulate the string to retrieve the company name of the specific
    114   // language codepage.
    115   memcpy(&lang, data, 4);
    116   ::StringCchPrintf(info_name, _countof(info_name),
    117       L"\\StringFileInfo\\%02X%02X%02X%02X\\CompanyName",
    118       (lang & 0xff00)>>8, (lang & 0xff), (lang & 0xff000000)>>24,
    119       (lang & 0xff0000)>>16);
    120 
    121   data_len = 0;
    122   if (!::VerQueryValue(file_version_info, info_name,
    123       reinterpret_cast<LPVOID *>(&data), reinterpret_cast<UINT *>(&data_len)))
    124     return false;
    125   if (data_len <= 0 || data_len >= (out_len / sizeof(wchar_t)))
    126     return false;
    127 
    128   memset(buffer, 0, out_len);
    129   ::StringCchCopyN(buffer,
    130                    (out_len / sizeof(wchar_t)),
    131                    reinterpret_cast<const wchar_t*>(data),
    132                    data_len);
    133   return true;
    134 }
    135 
    136 // Offsets the current date by |months|. |months| must be between 0 and 12.
    137 // The returned date is in the YYYYMMDD format.
    138 DWORD FormatDateOffsetByMonths(int months) {
    139   DCHECK(months >= 0 && months <= 12);
    140 
    141   SYSTEMTIME now;
    142   GetLocalTime(&now);
    143   now.wMonth += months;
    144   if (now.wMonth > 12) {
    145     now.wMonth -= 12;
    146     now.wYear += 1;
    147   }
    148 
    149   return now.wYear * 10000 + now.wMonth * 100 + now.wDay;
    150 }
    151 
    152 // Return true if we can re-offer Chrome; false, otherwise.
    153 // Each partner can only offer Chrome once every six months.
    154 bool CanReOfferChrome(BOOL set_flag) {
    155   wchar_t filename[MAX_PATH+1];
    156   wchar_t company[MAX_PATH];
    157 
    158   // If we cannot retrieve the version info of the executable or company
    159   // name, we allow the Chrome to be offered because there is no past
    160   // history to be found.
    161   if (::GetModuleFileName(NULL, filename, MAX_PATH) == 0)
    162     return true;
    163   if (!GetCompanyName(filename, company, sizeof(company)))
    164     return true;
    165 
    166   bool can_re_offer = true;
    167   DWORD disposition = 0;
    168   HKEY key = NULL;
    169   if (::RegCreateKeyEx(HKEY_LOCAL_MACHINE, kNoChromeOfferUntil,
    170       0, NULL, REG_OPTION_NON_VOLATILE, KEY_READ | KEY_WRITE,
    171       NULL, &key, &disposition) == ERROR_SUCCESS) {
    172     // Get today's date, and format it as YYYYMMDD numeric value.
    173     DWORD today = FormatDateOffsetByMonths(0);
    174 
    175     // Cannot re-offer, if the timer already exists and is not expired yet.
    176     DWORD value_type = REG_DWORD;
    177     DWORD value_data = 0;
    178     DWORD value_length = sizeof(DWORD);
    179     if (::RegQueryValueEx(key, company, 0, &value_type,
    180                           reinterpret_cast<LPBYTE>(&value_data),
    181                           &value_length) == ERROR_SUCCESS &&
    182         REG_DWORD == value_type &&
    183         value_data > today) {
    184       // The time has not expired, we cannot offer Chrome.
    185       can_re_offer = false;
    186     } else {
    187       // Delete the old or invalid value.
    188       ::RegDeleteValue(key, company);
    189       if (set_flag) {
    190         // Set expiration date for offer as six months from today,
    191         // represented as a YYYYMMDD numeric value.
    192         DWORD value = FormatDateOffsetByMonths(6);
    193         ::RegSetValueEx(key, company, 0, REG_DWORD, (LPBYTE)&value,
    194                         sizeof(DWORD));
    195       }
    196     }
    197 
    198     ::RegCloseKey(key);
    199   }
    200 
    201   return can_re_offer;
    202 }
    203 
    204 bool IsChromeInstalled(HKEY root_key) {
    205   RegKey key;
    206   return key.Open(root_key,
    207                   kChromeRegClientsKey,
    208                   KEY_READ | KEY_WOW64_32KEY) == ERROR_SUCCESS &&
    209          key.HasValue(kChromeRegVersion);
    210 }
    211 
    212 // Returns true if the |subkey| in |root| has the kC1FKey entry set to 1.
    213 bool RegKeyHasC1F(HKEY root, const wchar_t* subkey) {
    214   RegKey key;
    215   DWORD value;
    216   return key.Open(root, subkey, KEY_READ | KEY_WOW64_32KEY) == ERROR_SUCCESS &&
    217       key.ReadValueDW(kC1FKey, &value) == ERROR_SUCCESS &&
    218       value == static_cast<DWORD>(1);
    219 }
    220 
    221 bool IsC1FSent() {
    222   // The C1F RLZ key can either be in HKCU or in HKLM (the HKLM RLZ key is made
    223   // readable to all-users via rlz_lib::CreateMachineState()) and can either be
    224   // in sent or pending state. Return true if there is a match for any of these
    225   // 4 states.
    226   return RegKeyHasC1F(HKEY_CURRENT_USER, kC1FSentKey) ||
    227       RegKeyHasC1F(HKEY_CURRENT_USER, kC1FPendingKey) ||
    228       RegKeyHasC1F(HKEY_LOCAL_MACHINE, kC1FSentKey) ||
    229       RegKeyHasC1F(HKEY_LOCAL_MACHINE, kC1FPendingKey);
    230 }
    231 
    232 enum WindowsVersion {
    233   VERSION_BELOW_XP_SP2,
    234   VERSION_XP_SP2_UP_TO_VISTA,  // "but not including"
    235   VERSION_VISTA_OR_HIGHER,
    236 };
    237 WindowsVersion GetWindowsVersion() {
    238   OSVERSIONINFOEX version_info = { sizeof version_info };
    239   GetVersionEx(reinterpret_cast<OSVERSIONINFO*>(&version_info));
    240 
    241   // Windows Vista is version 6.0.
    242   if (version_info.dwMajorVersion >= 6)
    243     return VERSION_VISTA_OR_HIGHER;
    244 
    245   // Windows XP is version 5.1.  (5.2 is Windows Server 2003/XP Pro x64.)
    246   if ((version_info.dwMajorVersion < 5) || (version_info.dwMinorVersion < 1))
    247     return VERSION_BELOW_XP_SP2;
    248 
    249   // For XP itself, we only support SP2 and above.
    250   return ((version_info.dwMinorVersion > 1) ||
    251           (version_info.wServicePackMajor >= 2)) ?
    252       VERSION_XP_SP2_UP_TO_VISTA : VERSION_BELOW_XP_SP2;
    253 }
    254 
    255 // Note this function should not be called on old Windows versions where these
    256 // Windows API are not available. We always invoke this function after checking
    257 // that current OS is Vista or later.
    258 bool VerifyAdminGroup() {
    259   SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
    260   PSID Group;
    261   BOOL check = ::AllocateAndInitializeSid(&NtAuthority, 2,
    262                                           SECURITY_BUILTIN_DOMAIN_RID,
    263                                           DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0,
    264                                           0, 0, 0,
    265                                           &Group);
    266   if (check) {
    267     if (!::CheckTokenMembership(NULL, Group, &check))
    268       check = FALSE;
    269   }
    270   ::FreeSid(Group);
    271   return (check == TRUE);
    272 }
    273 
    274 bool VerifyHKLMAccess() {
    275   wchar_t str[] = L"test";
    276   bool result = false;
    277   DWORD disposition = 0;
    278   HKEY key = NULL;
    279 
    280   if (::RegCreateKeyEx(HKEY_LOCAL_MACHINE, kGCAPITempKey, 0, NULL,
    281                        REG_OPTION_NON_VOLATILE,
    282                        KEY_READ | KEY_WRITE | KEY_WOW64_32KEY, NULL,
    283                        &key, &disposition) == ERROR_SUCCESS) {
    284     if (::RegSetValueEx(key, str, 0, REG_SZ, (LPBYTE)str,
    285         (DWORD)lstrlen(str)) == ERROR_SUCCESS) {
    286       result = true;
    287       RegDeleteValue(key, str);
    288     }
    289 
    290     RegCloseKey(key);
    291 
    292     //  If we create the main key, delete the entire key.
    293     if (disposition == REG_CREATED_NEW_KEY)
    294       RegDeleteKey(HKEY_LOCAL_MACHINE, kGCAPITempKey);
    295   }
    296 
    297   return result;
    298 }
    299 
    300 bool IsRunningElevated() {
    301   // This method should be called only for Vista or later.
    302   if ((GetWindowsVersion() < VERSION_VISTA_OR_HIGHER) ||
    303       !VerifyAdminGroup())
    304     return false;
    305 
    306   HANDLE process_token;
    307   if (!::OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &process_token))
    308     return false;
    309 
    310   TOKEN_ELEVATION_TYPE elevation_type = TokenElevationTypeDefault;
    311   DWORD size_returned = 0;
    312   if (!::GetTokenInformation(process_token, TokenElevationType,
    313                              &elevation_type, sizeof(elevation_type),
    314                              &size_returned)) {
    315     ::CloseHandle(process_token);
    316     return false;
    317   }
    318 
    319   ::CloseHandle(process_token);
    320   return (elevation_type == TokenElevationTypeFull);
    321 }
    322 
    323 bool GetUserIdForProcess(size_t pid, wchar_t** user_sid) {
    324   HANDLE process_handle = ::OpenProcess(PROCESS_QUERY_INFORMATION, TRUE, pid);
    325   if (process_handle == NULL)
    326     return false;
    327 
    328   HANDLE process_token;
    329   bool result = false;
    330   if (::OpenProcessToken(process_handle, TOKEN_QUERY, &process_token)) {
    331     DWORD size = 0;
    332     ::GetTokenInformation(process_token, TokenUser, NULL, 0, &size);
    333     if (::GetLastError() == ERROR_INSUFFICIENT_BUFFER ||
    334         ::GetLastError() == ERROR_SUCCESS) {
    335       DWORD actual_size = 0;
    336       BYTE* token_user = new BYTE[size];
    337       if ((::GetTokenInformation(process_token, TokenUser, token_user, size,
    338                                 &actual_size)) &&
    339           (actual_size <= size)) {
    340         PSID sid = reinterpret_cast<TOKEN_USER*>(token_user)->User.Sid;
    341         if (::ConvertSidToStringSid(sid, user_sid))
    342           result = true;
    343       }
    344       delete[] token_user;
    345     }
    346     ::CloseHandle(process_token);
    347   }
    348   ::CloseHandle(process_handle);
    349   return result;
    350 }
    351 
    352 struct SetWindowPosParams {
    353   int x;
    354   int y;
    355   int width;
    356   int height;
    357   DWORD flags;
    358   HWND window_insert_after;
    359   bool success;
    360   std::set<HWND> shunted_hwnds;
    361 };
    362 
    363 BOOL CALLBACK ChromeWindowEnumProc(HWND hwnd, LPARAM lparam) {
    364   wchar_t window_class[MAX_PATH] = {};
    365   SetWindowPosParams* params = reinterpret_cast<SetWindowPosParams*>(lparam);
    366 
    367   if (!params->shunted_hwnds.count(hwnd) &&
    368       ::GetClassName(hwnd, window_class, arraysize(window_class)) &&
    369       StartsWith(window_class, kChromeWindowClassPrefix, false) &&
    370       ::SetWindowPos(hwnd, params->window_insert_after, params->x,
    371                      params->y, params->width, params->height, params->flags)) {
    372     params->shunted_hwnds.insert(hwnd);
    373     params->success = true;
    374   }
    375 
    376   // Return TRUE to ensure we hit all possible top-level Chrome windows as per
    377   // http://msdn.microsoft.com/en-us/library/windows/desktop/ms633498.aspx
    378   return TRUE;
    379 }
    380 
    381 // Returns true and populates |chrome_exe_path| with the path to chrome.exe if
    382 // a valid installation can be found.
    383 bool GetGoogleChromePath(base::FilePath* chrome_exe_path) {
    384   HKEY install_key = HKEY_LOCAL_MACHINE;
    385   if (!IsChromeInstalled(install_key)) {
    386     install_key = HKEY_CURRENT_USER;
    387     if (!IsChromeInstalled(install_key)) {
    388       return false;
    389     }
    390   }
    391 
    392   // Now grab the uninstall string from the appropriate ClientState key
    393   // and use that as the base for a path to chrome.exe.
    394   *chrome_exe_path =
    395       chrome_launcher_support::GetChromePathForInstallationLevel(
    396           install_key == HKEY_LOCAL_MACHINE ?
    397               chrome_launcher_support::SYSTEM_LEVEL_INSTALLATION :
    398               chrome_launcher_support::USER_LEVEL_INSTALLATION);
    399   return !chrome_exe_path->empty();
    400 }
    401 
    402 }  // namespace
    403 
    404 BOOL __stdcall GoogleChromeCompatibilityCheck(BOOL set_flag,
    405                                               int shell_mode,
    406                                               DWORD* reasons) {
    407   DWORD local_reasons = 0;
    408 
    409   WindowsVersion windows_version = GetWindowsVersion();
    410   // System requirements?
    411   if (windows_version == VERSION_BELOW_XP_SP2)
    412     local_reasons |= GCCC_ERROR_OSNOTSUPPORTED;
    413 
    414   if (IsChromeInstalled(HKEY_LOCAL_MACHINE))
    415     local_reasons |= GCCC_ERROR_SYSTEMLEVELALREADYPRESENT;
    416 
    417   if (IsChromeInstalled(HKEY_CURRENT_USER))
    418     local_reasons |= GCCC_ERROR_USERLEVELALREADYPRESENT;
    419 
    420   if (shell_mode == GCAPI_INVOKED_UAC_ELEVATION) {
    421     // Only check that we have HKLM write permissions if we specify that
    422     // GCAPI is being invoked from an elevated shell, or in admin mode
    423     if (!VerifyHKLMAccess()) {
    424     local_reasons |= GCCC_ERROR_ACCESSDENIED;
    425     } else if ((windows_version == VERSION_VISTA_OR_HIGHER) &&
    426          !VerifyAdminGroup()) {
    427     // For Vista or later check for elevation since even for admin user we could
    428     // be running in non-elevated mode. We require integrity level High.
    429     local_reasons |= GCCC_ERROR_INTEGRITYLEVEL;
    430     }
    431   }
    432 
    433   // Then only check whether we can re-offer, if everything else is OK.
    434   if (local_reasons == 0 && !CanReOfferChrome(set_flag))
    435     local_reasons |= GCCC_ERROR_ALREADYOFFERED;
    436 
    437   // Done. Copy/return results.
    438   if (reasons != NULL)
    439     *reasons = local_reasons;
    440 
    441   return (local_reasons == 0);
    442 }
    443 
    444 BOOL __stdcall LaunchGoogleChrome() {
    445   base::FilePath chrome_exe_path;
    446   if (!GetGoogleChromePath(&chrome_exe_path))
    447     return false;
    448 
    449   ScopedCOMInitializer com_initializer;
    450   if (::CoInitializeSecurity(NULL, -1, NULL, NULL,
    451                              RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
    452                              RPC_C_IMP_LEVEL_IDENTIFY, NULL,
    453                              EOAC_DYNAMIC_CLOAKING, NULL) != S_OK) {
    454     return false;
    455   }
    456 
    457   bool impersonation_success = false;
    458   if (IsRunningElevated()) {
    459     wchar_t* curr_proc_sid;
    460     if (!GetUserIdForProcess(GetCurrentProcessId(), &curr_proc_sid)) {
    461       return false;
    462     }
    463 
    464     DWORD pid = 0;
    465     ::GetWindowThreadProcessId(::GetShellWindow(), &pid);
    466     if (pid <= 0) {
    467       ::LocalFree(curr_proc_sid);
    468       return false;
    469     }
    470 
    471     wchar_t* exp_proc_sid;
    472     if (GetUserIdForProcess(pid, &exp_proc_sid)) {
    473       if (_wcsicmp(curr_proc_sid, exp_proc_sid) == 0) {
    474         ScopedHandle process_handle(
    475             ::OpenProcess(PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION,
    476                           TRUE,
    477                           pid));
    478         if (process_handle.IsValid()) {
    479           HANDLE process_token = NULL;
    480           HANDLE user_token = NULL;
    481           if (::OpenProcessToken(process_handle, TOKEN_DUPLICATE | TOKEN_QUERY,
    482                                  &process_token) &&
    483               ::DuplicateTokenEx(process_token,
    484                                  TOKEN_IMPERSONATE | TOKEN_QUERY |
    485                                      TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE,
    486                                  NULL, SecurityImpersonation,
    487                                  TokenPrimary, &user_token) &&
    488               (::ImpersonateLoggedOnUser(user_token) != 0)) {
    489             impersonation_success = true;
    490           }
    491           if (user_token)
    492             ::CloseHandle(user_token);
    493           if (process_token)
    494             ::CloseHandle(process_token);
    495         }
    496       }
    497       ::LocalFree(exp_proc_sid);
    498     }
    499 
    500     ::LocalFree(curr_proc_sid);
    501     if (!impersonation_success) {
    502       return false;
    503     }
    504   }
    505 
    506   bool ret = false;
    507   ScopedComPtr<IProcessLauncher> ipl;
    508   if (SUCCEEDED(ipl.CreateInstance(__uuidof(ProcessLauncherClass),
    509                                    NULL,
    510                                    CLSCTX_LOCAL_SERVER))) {
    511     if (SUCCEEDED(ipl->LaunchCmdLine(chrome_exe_path.value().c_str())))
    512       ret = true;
    513     ipl.Release();
    514   } else {
    515     // Couldn't get Omaha's process launcher, Omaha may not be installed at
    516     // system level. Try just running Chrome instead.
    517     ret = base::LaunchProcess(chrome_exe_path.value(),
    518                               base::LaunchOptions(),
    519                               NULL);
    520   }
    521 
    522   if (impersonation_success)
    523     ::RevertToSelf();
    524   return ret;
    525 }
    526 
    527 BOOL __stdcall LaunchGoogleChromeWithDimensions(int x,
    528                                                 int y,
    529                                                 int width,
    530                                                 int height,
    531                                                 bool in_background) {
    532   if (in_background) {
    533     base::FilePath chrome_exe_path;
    534     if (!GetGoogleChromePath(&chrome_exe_path))
    535       return false;
    536 
    537     // When launching in the background, use WMI to ensure that chrome.exe is
    538     // is not our child process. This prevents it from pushing itself to
    539     // foreground.
    540     CommandLine chrome_command(chrome_exe_path);
    541 
    542     ScopedCOMInitializer com_initializer;
    543     if (!installer::WMIProcess::Launch(chrome_command.GetCommandLineString(),
    544                                        NULL)) {
    545       // For some reason WMI failed. Try and launch the old fashioned way,
    546       // knowing that visual glitches will occur when the window pops up.
    547       if (!LaunchGoogleChrome())
    548         return false;
    549     }
    550 
    551   } else {
    552     if (!LaunchGoogleChrome())
    553       return false;
    554   }
    555 
    556   HWND hwnd_insert_after = in_background ? HWND_BOTTOM : NULL;
    557   DWORD set_window_flags = in_background ? SWP_NOACTIVATE : SWP_NOZORDER;
    558 
    559   if (x == -1 && y == -1)
    560     set_window_flags |= SWP_NOMOVE;
    561 
    562   if (width == -1 && height == -1)
    563     set_window_flags |= SWP_NOSIZE;
    564 
    565   SetWindowPosParams enum_params = { x, y, width, height, set_window_flags,
    566                                      hwnd_insert_after, false };
    567 
    568   // Chrome may have been launched, but the window may not have appeared
    569   // yet. Wait for it to appear for 10 seconds, but exit if it takes longer
    570   // than that.
    571   int ms_elapsed = 0;
    572   int timeout = 10000;
    573   bool found_window = false;
    574   while (ms_elapsed < timeout) {
    575     // Enum all top-level windows looking for Chrome windows.
    576     ::EnumWindows(ChromeWindowEnumProc, reinterpret_cast<LPARAM>(&enum_params));
    577 
    578     // Give it five more seconds after finding the first window until we stop
    579     // shoving new windows into the background.
    580     if (!found_window && enum_params.success) {
    581       found_window = true;
    582       timeout = ms_elapsed + 5000;
    583     }
    584 
    585     Sleep(10);
    586     ms_elapsed += 10;
    587   }
    588 
    589   return found_window;
    590 }
    591 
    592 BOOL __stdcall LaunchGoogleChromeInBackground() {
    593   return LaunchGoogleChromeWithDimensions(-1, -1, -1, -1, true);
    594 }
    595 
    596 int __stdcall GoogleChromeDaysSinceLastRun() {
    597   int days_since_last_run = std::numeric_limits<int>::max();
    598 
    599   if (IsChromeInstalled(HKEY_LOCAL_MACHINE) ||
    600       IsChromeInstalled(HKEY_CURRENT_USER)) {
    601     RegKey client_state(HKEY_CURRENT_USER,
    602                         kChromeRegClientStateKey,
    603                         KEY_QUERY_VALUE | KEY_WOW64_32KEY);
    604     if (client_state.Valid()) {
    605       std::wstring last_run;
    606       int64 last_run_value = 0;
    607       if (client_state.ReadValue(google_update::kRegLastRunTimeField,
    608                                  &last_run) == ERROR_SUCCESS &&
    609           base::StringToInt64(last_run, &last_run_value)) {
    610         Time last_run_time = Time::FromInternalValue(last_run_value);
    611         TimeDelta difference = Time::NowFromSystemTime() - last_run_time;
    612 
    613         // We can end up with negative numbers here, given changes in system
    614         // clock time or due to TimeDelta's int64 -> int truncation.
    615         int new_days_since_last_run = difference.InDays();
    616         if (new_days_since_last_run >= 0 &&
    617             new_days_since_last_run < days_since_last_run) {
    618           days_since_last_run = new_days_since_last_run;
    619         }
    620       }
    621     }
    622   }
    623 
    624   if (days_since_last_run == std::numeric_limits<int>::max()) {
    625     days_since_last_run = -1;
    626   }
    627 
    628   return days_since_last_run;
    629 }
    630 
    631 BOOL __stdcall CanOfferReactivation(const wchar_t* brand_code,
    632                                     int shell_mode,
    633                                     DWORD* error_code) {
    634   DCHECK(error_code);
    635 
    636   if (!brand_code) {
    637     if (error_code)
    638       *error_code = REACTIVATE_ERROR_INVALID_INPUT;
    639     return FALSE;
    640   }
    641 
    642   int days_since_last_run = GoogleChromeDaysSinceLastRun();
    643   if (days_since_last_run >= 0 &&
    644       days_since_last_run < kReactivationMinDaysDormant) {
    645     if (error_code)
    646       *error_code = REACTIVATE_ERROR_NOTDORMANT;
    647     return FALSE;
    648   }
    649 
    650   // Only run the code below when this function is invoked from a standard,
    651   // non-elevated cmd shell.  This is because this section of code looks at
    652   // values in HKEY_CURRENT_USER, and we only want to look at the logged-in
    653   // user's HKCU, not the admin user's HKCU.
    654   if (shell_mode == GCAPI_INVOKED_STANDARD_SHELL) {
    655     if (!IsChromeInstalled(HKEY_LOCAL_MACHINE) &&
    656         !IsChromeInstalled(HKEY_CURRENT_USER)) {
    657       if (error_code)
    658         *error_code = REACTIVATE_ERROR_NOTINSTALLED;
    659       return FALSE;
    660     }
    661 
    662     if (HasBeenReactivated()) {
    663       if (error_code)
    664         *error_code = REACTIVATE_ERROR_ALREADY_REACTIVATED;
    665       return FALSE;
    666     }
    667   }
    668 
    669   return TRUE;
    670 }
    671 
    672 BOOL __stdcall ReactivateChrome(wchar_t* brand_code,
    673                                 int shell_mode,
    674                                 DWORD* error_code) {
    675   BOOL result = FALSE;
    676   if (CanOfferReactivation(brand_code,
    677                            shell_mode,
    678                            error_code)) {
    679     if (SetReactivationBrandCode(brand_code, shell_mode)) {
    680       // Currently set this as a best-effort thing. We return TRUE if
    681       // reactivation succeeded regardless of the experiment label result.
    682       SetReactivationExperimentLabels(brand_code, shell_mode);
    683 
    684       result = TRUE;
    685     } else {
    686       if (error_code)
    687         *error_code = REACTIVATE_ERROR_REACTIVATION_FAILED;
    688     }
    689   }
    690 
    691   return result;
    692 }
    693 
    694 BOOL __stdcall CanOfferRelaunch(const wchar_t** partner_brandcode_list,
    695                                 int partner_brandcode_list_length,
    696                                 int shell_mode,
    697                                 DWORD* error_code) {
    698   DCHECK(error_code);
    699 
    700   if (!partner_brandcode_list || partner_brandcode_list_length <= 0) {
    701     if (error_code)
    702       *error_code = RELAUNCH_ERROR_INVALID_INPUT;
    703     return FALSE;
    704   }
    705 
    706   // These conditions need to be satisfied for relaunch:
    707   // a) Chrome should be installed;
    708   if (!IsChromeInstalled(HKEY_LOCAL_MACHINE) &&
    709       (shell_mode != GCAPI_INVOKED_STANDARD_SHELL ||
    710           !IsChromeInstalled(HKEY_CURRENT_USER))) {
    711     if (error_code)
    712       *error_code = RELAUNCH_ERROR_NOTINSTALLED;
    713     return FALSE;
    714   }
    715 
    716   // b) the installed brandcode should belong to that partner (in
    717   // brandcode_list);
    718   std::wstring installed_brandcode;
    719   bool valid_brandcode = false;
    720   if (GoogleUpdateSettings::GetBrand(&installed_brandcode)) {
    721     for (int i = 0; i < partner_brandcode_list_length; ++i) {
    722       if (!_wcsicmp(installed_brandcode.c_str(), partner_brandcode_list[i])) {
    723         valid_brandcode = true;
    724         break;
    725       }
    726     }
    727   }
    728 
    729   if (!valid_brandcode) {
    730     if (error_code)
    731       *error_code = RELAUNCH_ERROR_INVALID_PARTNER;
    732     return FALSE;
    733   }
    734 
    735   // c) C1F ping should not have been sent;
    736   if (IsC1FSent()) {
    737     if (error_code)
    738       *error_code = RELAUNCH_ERROR_PINGS_SENT;
    739     return FALSE;
    740   }
    741 
    742   // d) a minimum period (30 days) must have passed since Chrome was last used;
    743   int days_since_last_run = GoogleChromeDaysSinceLastRun();
    744   if (days_since_last_run >= 0 &&
    745       days_since_last_run < kRelaunchMinDaysDormant) {
    746     if (error_code)
    747       *error_code = RELAUNCH_ERROR_NOTDORMANT;
    748     return FALSE;
    749   }
    750 
    751   // e) a minimum period (6 months) must have passed since the previous
    752   // relaunch offer for the current user;
    753   RegKey key;
    754   DWORD min_relaunch_date;
    755   if (key.Open(HKEY_CURRENT_USER,
    756                kChromeRegClientStateKey,
    757                KEY_QUERY_VALUE | KEY_WOW64_32KEY) == ERROR_SUCCESS &&
    758       key.ReadValueDW(kRelaunchAllowedAfterValue,
    759                       &min_relaunch_date) == ERROR_SUCCESS &&
    760       FormatDateOffsetByMonths(0) < min_relaunch_date) {
    761     if (error_code)
    762       *error_code = RELAUNCH_ERROR_ALREADY_RELAUNCHED;
    763     return FALSE;
    764   }
    765 
    766   return TRUE;
    767 }
    768 
    769 BOOL __stdcall SetRelaunchOffered(const wchar_t** partner_brandcode_list,
    770                                   int partner_brandcode_list_length,
    771                                   const wchar_t* relaunch_brandcode,
    772                                   int shell_mode,
    773                                   DWORD* error_code) {
    774   if (!CanOfferRelaunch(partner_brandcode_list, partner_brandcode_list_length,
    775                         shell_mode, error_code))
    776     return FALSE;
    777 
    778   // Store the relaunched brand code and the minimum date for relaunch (6 months
    779   // from now), and set the Omaha experiment label.
    780   RegKey key;
    781   if (key.Create(HKEY_CURRENT_USER,
    782                  kChromeRegClientStateKey,
    783                  KEY_SET_VALUE | KEY_WOW64_32KEY) != ERROR_SUCCESS ||
    784       key.WriteValue(kRelaunchBrandcodeValue,
    785                      relaunch_brandcode) != ERROR_SUCCESS ||
    786       key.WriteValue(kRelaunchAllowedAfterValue,
    787                      FormatDateOffsetByMonths(6)) != ERROR_SUCCESS ||
    788       !SetRelaunchExperimentLabels(relaunch_brandcode, shell_mode)) {
    789     if (error_code)
    790       *error_code = RELAUNCH_ERROR_RELAUNCH_FAILED;
    791     return FALSE;
    792   }
    793 
    794   return TRUE;
    795 }
    796