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