gcapi.cc revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
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
46using base::Time;
47using base::TimeDelta;
48using base::win::RegKey;
49using base::win::ScopedCOMInitializer;
50using base::win::ScopedComPtr;
51using base::win::ScopedHandle;
52
53namespace {
54
55const wchar_t kChromeRegClientsKey[] =
56    L"Software\\Google\\Update\\Clients\\"
57    L"{8A69D345-D564-463c-AFF1-A69D9E530F96}";
58const wchar_t kChromeRegClientStateKey[] =
59    L"Software\\Google\\Update\\ClientState\\"
60    L"{8A69D345-D564-463c-AFF1-A69D9E530F96}";
61const wchar_t kChromeRegClientStateMediumKey[] =
62    L"Software\\Google\\Update\\ClientStateMedium\\"
63    L"{8A69D345-D564-463c-AFF1-A69D9E530F96}";
64
65const wchar_t kGCAPITempKey[] = L"Software\\Google\\GCAPITemp";
66
67const wchar_t kChromeRegLaunchCmd[] = L"InstallerSuccessLaunchCmdLine";
68const wchar_t kChromeRegLastLaunchCmd[] = L"LastInstallerSuccessLaunchCmdLine";
69const wchar_t kChromeRegVersion[] = L"pv";
70const wchar_t kNoChromeOfferUntil[] =
71    L"SOFTWARE\\Google\\No Chrome Offer Until";
72
73const wchar_t kC1FPendingKey[] =
74    L"Software\\Google\\Common\\Rlz\\Events\\C";
75const wchar_t kC1FSentKey[] =
76    L"Software\\Google\\Common\\Rlz\\StatefulEvents\\C";
77const wchar_t kC1FKey[] = L"C1F";
78
79const wchar_t kRelaunchBrandcodeValue[] = L"RelaunchBrandcode";
80const wchar_t kRelaunchAllowedAfterValue[] = L"RelaunchAllowedAfter";
81
82// Prefix used to match the window class for Chrome windows.
83const wchar_t kChromeWindowClassPrefix[] = L"Chrome_WidgetWin_";
84
85// Return the company name specified in the file version info resource.
86bool 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.
138DWORD 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.
154bool 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
204bool 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.
213bool 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
221bool 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
232enum WindowsVersion {
233  VERSION_BELOW_XP_SP2,
234  VERSION_XP_SP2_UP_TO_VISTA,  // "but not including"
235  VERSION_VISTA_OR_HIGHER,
236};
237WindowsVersion 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.
258bool 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
274bool 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
300bool 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
323bool 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
352struct 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
363BOOL 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.
383bool 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
404BOOL __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
444BOOL __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.Get(),
482                                 TOKEN_DUPLICATE | TOKEN_QUERY,
483                                 &process_token) &&
484              ::DuplicateTokenEx(process_token,
485                                 TOKEN_IMPERSONATE | TOKEN_QUERY |
486                                     TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE,
487                                 NULL, SecurityImpersonation,
488                                 TokenPrimary, &user_token) &&
489              (::ImpersonateLoggedOnUser(user_token) != 0)) {
490            impersonation_success = true;
491          }
492          if (user_token)
493            ::CloseHandle(user_token);
494          if (process_token)
495            ::CloseHandle(process_token);
496        }
497      }
498      ::LocalFree(exp_proc_sid);
499    }
500
501    ::LocalFree(curr_proc_sid);
502    if (!impersonation_success) {
503      return false;
504    }
505  }
506
507  CommandLine chrome_command(chrome_exe_path);
508
509  bool ret = false;
510  ScopedComPtr<IProcessLauncher> ipl;
511  if (SUCCEEDED(ipl.CreateInstance(__uuidof(ProcessLauncherClass),
512                                   NULL,
513                                   CLSCTX_LOCAL_SERVER))) {
514    if (SUCCEEDED(ipl->LaunchCmdLine(
515            chrome_command.GetCommandLineString().c_str())))
516      ret = true;
517    ipl.Release();
518  } else {
519    // Couldn't get Omaha's process launcher, Omaha may not be installed at
520    // system level. Try just running Chrome instead.
521    ret = base::LaunchProcess(chrome_command.GetCommandLineString(),
522                              base::LaunchOptions(),
523                              NULL);
524  }
525
526  if (impersonation_success)
527    ::RevertToSelf();
528  return ret;
529}
530
531BOOL __stdcall LaunchGoogleChromeWithDimensions(int x,
532                                                int y,
533                                                int width,
534                                                int height,
535                                                bool in_background) {
536  if (in_background) {
537    base::FilePath chrome_exe_path;
538    if (!GetGoogleChromePath(&chrome_exe_path))
539      return false;
540
541    // When launching in the background, use WMI to ensure that chrome.exe is
542    // is not our child process. This prevents it from pushing itself to
543    // foreground.
544    CommandLine chrome_command(chrome_exe_path);
545
546    ScopedCOMInitializer com_initializer;
547    if (!installer::WMIProcess::Launch(chrome_command.GetCommandLineString(),
548                                       NULL)) {
549      // For some reason WMI failed. Try and launch the old fashioned way,
550      // knowing that visual glitches will occur when the window pops up.
551      if (!LaunchGoogleChrome())
552        return false;
553    }
554
555  } else {
556    if (!LaunchGoogleChrome())
557      return false;
558  }
559
560  HWND hwnd_insert_after = in_background ? HWND_BOTTOM : NULL;
561  DWORD set_window_flags = in_background ? SWP_NOACTIVATE : SWP_NOZORDER;
562
563  if (x == -1 && y == -1)
564    set_window_flags |= SWP_NOMOVE;
565
566  if (width == -1 && height == -1)
567    set_window_flags |= SWP_NOSIZE;
568
569  SetWindowPosParams enum_params = { x, y, width, height, set_window_flags,
570                                     hwnd_insert_after, false };
571
572  // Chrome may have been launched, but the window may not have appeared
573  // yet. Wait for it to appear for 10 seconds, but exit if it takes longer
574  // than that.
575  int ms_elapsed = 0;
576  int timeout = 10000;
577  bool found_window = false;
578  while (ms_elapsed < timeout) {
579    // Enum all top-level windows looking for Chrome windows.
580    ::EnumWindows(ChromeWindowEnumProc, reinterpret_cast<LPARAM>(&enum_params));
581
582    // Give it five more seconds after finding the first window until we stop
583    // shoving new windows into the background.
584    if (!found_window && enum_params.success) {
585      found_window = true;
586      timeout = ms_elapsed + 5000;
587    }
588
589    Sleep(10);
590    ms_elapsed += 10;
591  }
592
593  return found_window;
594}
595
596BOOL __stdcall LaunchGoogleChromeInBackground() {
597  return LaunchGoogleChromeWithDimensions(-1, -1, -1, -1, true);
598}
599
600int __stdcall GoogleChromeDaysSinceLastRun() {
601  int days_since_last_run = std::numeric_limits<int>::max();
602
603  if (IsChromeInstalled(HKEY_LOCAL_MACHINE) ||
604      IsChromeInstalled(HKEY_CURRENT_USER)) {
605    RegKey client_state(HKEY_CURRENT_USER,
606                        kChromeRegClientStateKey,
607                        KEY_QUERY_VALUE | KEY_WOW64_32KEY);
608    if (client_state.Valid()) {
609      base::string16 last_run;
610      int64 last_run_value = 0;
611      if (client_state.ReadValue(google_update::kRegLastRunTimeField,
612                                 &last_run) == ERROR_SUCCESS &&
613          base::StringToInt64(last_run, &last_run_value)) {
614        Time last_run_time = Time::FromInternalValue(last_run_value);
615        TimeDelta difference = Time::NowFromSystemTime() - last_run_time;
616
617        // We can end up with negative numbers here, given changes in system
618        // clock time or due to TimeDelta's int64 -> int truncation.
619        int new_days_since_last_run = difference.InDays();
620        if (new_days_since_last_run >= 0 &&
621            new_days_since_last_run < days_since_last_run) {
622          days_since_last_run = new_days_since_last_run;
623        }
624      }
625    }
626  }
627
628  if (days_since_last_run == std::numeric_limits<int>::max()) {
629    days_since_last_run = -1;
630  }
631
632  return days_since_last_run;
633}
634
635BOOL __stdcall CanOfferReactivation(const wchar_t* brand_code,
636                                    int shell_mode,
637                                    DWORD* error_code) {
638  DCHECK(error_code);
639
640  if (!brand_code) {
641    if (error_code)
642      *error_code = REACTIVATE_ERROR_INVALID_INPUT;
643    return FALSE;
644  }
645
646  int days_since_last_run = GoogleChromeDaysSinceLastRun();
647  if (days_since_last_run >= 0 &&
648      days_since_last_run < kReactivationMinDaysDormant) {
649    if (error_code)
650      *error_code = REACTIVATE_ERROR_NOTDORMANT;
651    return FALSE;
652  }
653
654  // Only run the code below when this function is invoked from a standard,
655  // non-elevated cmd shell.  This is because this section of code looks at
656  // values in HKEY_CURRENT_USER, and we only want to look at the logged-in
657  // user's HKCU, not the admin user's HKCU.
658  if (shell_mode == GCAPI_INVOKED_STANDARD_SHELL) {
659    if (!IsChromeInstalled(HKEY_LOCAL_MACHINE) &&
660        !IsChromeInstalled(HKEY_CURRENT_USER)) {
661      if (error_code)
662        *error_code = REACTIVATE_ERROR_NOTINSTALLED;
663      return FALSE;
664    }
665
666    if (HasBeenReactivated()) {
667      if (error_code)
668        *error_code = REACTIVATE_ERROR_ALREADY_REACTIVATED;
669      return FALSE;
670    }
671  }
672
673  return TRUE;
674}
675
676BOOL __stdcall ReactivateChrome(const wchar_t* brand_code,
677                                int shell_mode,
678                                DWORD* error_code) {
679  BOOL result = FALSE;
680  if (CanOfferReactivation(brand_code,
681                           shell_mode,
682                           error_code)) {
683    if (SetReactivationBrandCode(brand_code, shell_mode)) {
684      // Currently set this as a best-effort thing. We return TRUE if
685      // reactivation succeeded regardless of the experiment label result.
686      SetReactivationExperimentLabels(brand_code, shell_mode);
687
688      result = TRUE;
689    } else {
690      if (error_code)
691        *error_code = REACTIVATE_ERROR_REACTIVATION_FAILED;
692    }
693  }
694
695  return result;
696}
697
698BOOL __stdcall CanOfferRelaunch(const wchar_t** partner_brandcode_list,
699                                int partner_brandcode_list_length,
700                                int shell_mode,
701                                DWORD* error_code) {
702  DCHECK(error_code);
703
704  if (!partner_brandcode_list || partner_brandcode_list_length <= 0) {
705    if (error_code)
706      *error_code = RELAUNCH_ERROR_INVALID_INPUT;
707    return FALSE;
708  }
709
710  // These conditions need to be satisfied for relaunch:
711  // a) Chrome should be installed;
712  if (!IsChromeInstalled(HKEY_LOCAL_MACHINE) &&
713      (shell_mode != GCAPI_INVOKED_STANDARD_SHELL ||
714          !IsChromeInstalled(HKEY_CURRENT_USER))) {
715    if (error_code)
716      *error_code = RELAUNCH_ERROR_NOTINSTALLED;
717    return FALSE;
718  }
719
720  // b) the installed brandcode should belong to that partner (in
721  // brandcode_list);
722  base::string16 installed_brandcode;
723  bool valid_brandcode = false;
724  if (GoogleUpdateSettings::GetBrand(&installed_brandcode)) {
725    for (int i = 0; i < partner_brandcode_list_length; ++i) {
726      if (!_wcsicmp(installed_brandcode.c_str(), partner_brandcode_list[i])) {
727        valid_brandcode = true;
728        break;
729      }
730    }
731  }
732
733  if (!valid_brandcode) {
734    if (error_code)
735      *error_code = RELAUNCH_ERROR_INVALID_PARTNER;
736    return FALSE;
737  }
738
739  // c) C1F ping should not have been sent;
740  if (IsC1FSent()) {
741    if (error_code)
742      *error_code = RELAUNCH_ERROR_PINGS_SENT;
743    return FALSE;
744  }
745
746  // d) a minimum period (30 days) must have passed since Chrome was last used;
747  int days_since_last_run = GoogleChromeDaysSinceLastRun();
748  if (days_since_last_run >= 0 &&
749      days_since_last_run < kRelaunchMinDaysDormant) {
750    if (error_code)
751      *error_code = RELAUNCH_ERROR_NOTDORMANT;
752    return FALSE;
753  }
754
755  // e) a minimum period (6 months) must have passed since the previous
756  // relaunch offer for the current user;
757  RegKey key;
758  DWORD min_relaunch_date;
759  if (key.Open(HKEY_CURRENT_USER,
760               kChromeRegClientStateKey,
761               KEY_QUERY_VALUE | KEY_WOW64_32KEY) == ERROR_SUCCESS &&
762      key.ReadValueDW(kRelaunchAllowedAfterValue,
763                      &min_relaunch_date) == ERROR_SUCCESS &&
764      FormatDateOffsetByMonths(0) < min_relaunch_date) {
765    if (error_code)
766      *error_code = RELAUNCH_ERROR_ALREADY_RELAUNCHED;
767    return FALSE;
768  }
769
770  return TRUE;
771}
772
773BOOL __stdcall SetRelaunchOffered(const wchar_t** partner_brandcode_list,
774                                  int partner_brandcode_list_length,
775                                  const wchar_t* relaunch_brandcode,
776                                  int shell_mode,
777                                  DWORD* error_code) {
778  if (!CanOfferRelaunch(partner_brandcode_list, partner_brandcode_list_length,
779                        shell_mode, error_code))
780    return FALSE;
781
782  // Store the relaunched brand code and the minimum date for relaunch (6 months
783  // from now), and set the Omaha experiment label.
784  RegKey key;
785  if (key.Create(HKEY_CURRENT_USER,
786                 kChromeRegClientStateKey,
787                 KEY_SET_VALUE | KEY_WOW64_32KEY) != ERROR_SUCCESS ||
788      key.WriteValue(kRelaunchBrandcodeValue,
789                     relaunch_brandcode) != ERROR_SUCCESS ||
790      key.WriteValue(kRelaunchAllowedAfterValue,
791                     FormatDateOffsetByMonths(6)) != ERROR_SUCCESS ||
792      !SetRelaunchExperimentLabels(relaunch_brandcode, shell_mode)) {
793    if (error_code)
794      *error_code = RELAUNCH_ERROR_RELAUNCH_FAILED;
795    return FALSE;
796  }
797
798  return TRUE;
799}
800