- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / installer / gcapi / gcapi.cc
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, kChromeRegClientsKey, KEY_READ) == ERROR_SUCCESS &&
207          key.HasValue(kChromeRegVersion);
208 }
209
210 // Returns true if the |subkey| in |root| has the kC1FKey entry set to 1.
211 bool RegKeyHasC1F(HKEY root, const wchar_t* subkey) {
212   RegKey key;
213   DWORD value;
214   return key.Open(root, subkey, KEY_READ) == ERROR_SUCCESS &&
215       key.ReadValueDW(kC1FKey, &value) == ERROR_SUCCESS &&
216       value == static_cast<DWORD>(1);
217 }
218
219 bool IsC1FSent() {
220   // The C1F RLZ key can either be in HKCU or in HKLM (the HKLM RLZ key is made
221   // readable to all-users via rlz_lib::CreateMachineState()) and can either be
222   // in sent or pending state. Return true if there is a match for any of these
223   // 4 states.
224   return RegKeyHasC1F(HKEY_CURRENT_USER, kC1FSentKey) ||
225       RegKeyHasC1F(HKEY_CURRENT_USER, kC1FPendingKey) ||
226       RegKeyHasC1F(HKEY_LOCAL_MACHINE, kC1FSentKey) ||
227       RegKeyHasC1F(HKEY_LOCAL_MACHINE, kC1FPendingKey);
228 }
229
230 enum WindowsVersion {
231   VERSION_BELOW_XP_SP2,
232   VERSION_XP_SP2_UP_TO_VISTA,  // "but not including"
233   VERSION_VISTA_OR_HIGHER,
234 };
235 WindowsVersion GetWindowsVersion() {
236   OSVERSIONINFOEX version_info = { sizeof version_info };
237   GetVersionEx(reinterpret_cast<OSVERSIONINFO*>(&version_info));
238
239   // Windows Vista is version 6.0.
240   if (version_info.dwMajorVersion >= 6)
241     return VERSION_VISTA_OR_HIGHER;
242
243   // Windows XP is version 5.1.  (5.2 is Windows Server 2003/XP Pro x64.)
244   if ((version_info.dwMajorVersion < 5) || (version_info.dwMinorVersion < 1))
245     return VERSION_BELOW_XP_SP2;
246
247   // For XP itself, we only support SP2 and above.
248   return ((version_info.dwMinorVersion > 1) ||
249           (version_info.wServicePackMajor >= 2)) ?
250       VERSION_XP_SP2_UP_TO_VISTA : VERSION_BELOW_XP_SP2;
251 }
252
253 // Note this function should not be called on old Windows versions where these
254 // Windows API are not available. We always invoke this function after checking
255 // that current OS is Vista or later.
256 bool VerifyAdminGroup() {
257   SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
258   PSID Group;
259   BOOL check = ::AllocateAndInitializeSid(&NtAuthority, 2,
260                                           SECURITY_BUILTIN_DOMAIN_RID,
261                                           DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0,
262                                           0, 0, 0,
263                                           &Group);
264   if (check) {
265     if (!::CheckTokenMembership(NULL, Group, &check))
266       check = FALSE;
267   }
268   ::FreeSid(Group);
269   return (check == TRUE);
270 }
271
272 bool VerifyHKLMAccess() {
273   wchar_t str[] = L"test";
274   bool result = false;
275   DWORD disposition = 0;
276   HKEY key = NULL;
277
278   if (::RegCreateKeyEx(HKEY_LOCAL_MACHINE, kGCAPITempKey, 0, NULL,
279                        REG_OPTION_NON_VOLATILE, KEY_READ | KEY_WRITE, NULL,
280                        &key, &disposition) == ERROR_SUCCESS) {
281     if (::RegSetValueEx(key, str, 0, REG_SZ, (LPBYTE)str,
282         (DWORD)lstrlen(str)) == ERROR_SUCCESS) {
283       result = true;
284       RegDeleteValue(key, str);
285     }
286
287     RegCloseKey(key);
288
289     //  If we create the main key, delete the entire key.
290     if (disposition == REG_CREATED_NEW_KEY)
291       RegDeleteKey(HKEY_LOCAL_MACHINE, kGCAPITempKey);
292   }
293
294   return result;
295 }
296
297 bool IsRunningElevated() {
298   // This method should be called only for Vista or later.
299   if ((GetWindowsVersion() < VERSION_VISTA_OR_HIGHER) ||
300       !VerifyAdminGroup())
301     return false;
302
303   HANDLE process_token;
304   if (!::OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &process_token))
305     return false;
306
307   TOKEN_ELEVATION_TYPE elevation_type = TokenElevationTypeDefault;
308   DWORD size_returned = 0;
309   if (!::GetTokenInformation(process_token, TokenElevationType,
310                              &elevation_type, sizeof(elevation_type),
311                              &size_returned)) {
312     ::CloseHandle(process_token);
313     return false;
314   }
315
316   ::CloseHandle(process_token);
317   return (elevation_type == TokenElevationTypeFull);
318 }
319
320 bool GetUserIdForProcess(size_t pid, wchar_t** user_sid) {
321   HANDLE process_handle = ::OpenProcess(PROCESS_QUERY_INFORMATION, TRUE, pid);
322   if (process_handle == NULL)
323     return false;
324
325   HANDLE process_token;
326   bool result = false;
327   if (::OpenProcessToken(process_handle, TOKEN_QUERY, &process_token)) {
328     DWORD size = 0;
329     ::GetTokenInformation(process_token, TokenUser, NULL, 0, &size);
330     if (::GetLastError() == ERROR_INSUFFICIENT_BUFFER ||
331         ::GetLastError() == ERROR_SUCCESS) {
332       DWORD actual_size = 0;
333       BYTE* token_user = new BYTE[size];
334       if ((::GetTokenInformation(process_token, TokenUser, token_user, size,
335                                 &actual_size)) &&
336           (actual_size <= size)) {
337         PSID sid = reinterpret_cast<TOKEN_USER*>(token_user)->User.Sid;
338         if (::ConvertSidToStringSid(sid, user_sid))
339           result = true;
340       }
341       delete[] token_user;
342     }
343     ::CloseHandle(process_token);
344   }
345   ::CloseHandle(process_handle);
346   return result;
347 }
348
349 struct SetWindowPosParams {
350   int x;
351   int y;
352   int width;
353   int height;
354   DWORD flags;
355   HWND window_insert_after;
356   bool success;
357   std::set<HWND> shunted_hwnds;
358 };
359
360 BOOL CALLBACK ChromeWindowEnumProc(HWND hwnd, LPARAM lparam) {
361   wchar_t window_class[MAX_PATH] = {};
362   SetWindowPosParams* params = reinterpret_cast<SetWindowPosParams*>(lparam);
363
364   if (!params->shunted_hwnds.count(hwnd) &&
365       ::GetClassName(hwnd, window_class, arraysize(window_class)) &&
366       StartsWith(window_class, kChromeWindowClassPrefix, false) &&
367       ::SetWindowPos(hwnd, params->window_insert_after, params->x,
368                      params->y, params->width, params->height, params->flags)) {
369     params->shunted_hwnds.insert(hwnd);
370     params->success = true;
371   }
372
373   // Return TRUE to ensure we hit all possible top-level Chrome windows as per
374   // http://msdn.microsoft.com/en-us/library/windows/desktop/ms633498.aspx
375   return TRUE;
376 }
377
378 // Returns true and populates |chrome_exe_path| with the path to chrome.exe if
379 // a valid installation can be found.
380 bool GetGoogleChromePath(base::FilePath* chrome_exe_path) {
381   HKEY install_key = HKEY_LOCAL_MACHINE;
382   if (!IsChromeInstalled(install_key)) {
383     install_key = HKEY_CURRENT_USER;
384     if (!IsChromeInstalled(install_key)) {
385       return false;
386     }
387   }
388
389   // Now grab the uninstall string from the appropriate ClientState key
390   // and use that as the base for a path to chrome.exe.
391   *chrome_exe_path =
392       chrome_launcher_support::GetChromePathForInstallationLevel(
393           install_key == HKEY_LOCAL_MACHINE ?
394               chrome_launcher_support::SYSTEM_LEVEL_INSTALLATION :
395               chrome_launcher_support::USER_LEVEL_INSTALLATION);
396   return !chrome_exe_path->empty();
397 }
398
399 }  // namespace
400
401 BOOL __stdcall GoogleChromeCompatibilityCheck(BOOL set_flag,
402                                               int shell_mode,
403                                               DWORD* reasons) {
404   DWORD local_reasons = 0;
405
406   WindowsVersion windows_version = GetWindowsVersion();
407   // System requirements?
408   if (windows_version == VERSION_BELOW_XP_SP2)
409     local_reasons |= GCCC_ERROR_OSNOTSUPPORTED;
410
411   if (IsChromeInstalled(HKEY_LOCAL_MACHINE))
412     local_reasons |= GCCC_ERROR_SYSTEMLEVELALREADYPRESENT;
413
414   if (IsChromeInstalled(HKEY_CURRENT_USER))
415     local_reasons |= GCCC_ERROR_USERLEVELALREADYPRESENT;
416
417   if (shell_mode == GCAPI_INVOKED_UAC_ELEVATION) {
418     // Only check that we have HKLM write permissions if we specify that
419     // GCAPI is being invoked from an elevated shell, or in admin mode
420     if (!VerifyHKLMAccess()) {
421     local_reasons |= GCCC_ERROR_ACCESSDENIED;
422     } else if ((windows_version == VERSION_VISTA_OR_HIGHER) &&
423          !VerifyAdminGroup()) {
424     // For Vista or later check for elevation since even for admin user we could
425     // be running in non-elevated mode. We require integrity level High.
426     local_reasons |= GCCC_ERROR_INTEGRITYLEVEL;
427     }
428   }
429
430   // Then only check whether we can re-offer, if everything else is OK.
431   if (local_reasons == 0 && !CanReOfferChrome(set_flag))
432     local_reasons |= GCCC_ERROR_ALREADYOFFERED;
433
434   // Done. Copy/return results.
435   if (reasons != NULL)
436     *reasons = local_reasons;
437
438   return (local_reasons == 0);
439 }
440
441 BOOL __stdcall LaunchGoogleChrome() {
442   base::FilePath chrome_exe_path;
443   if (!GetGoogleChromePath(&chrome_exe_path))
444     return false;
445
446   ScopedCOMInitializer com_initializer;
447   if (::CoInitializeSecurity(NULL, -1, NULL, NULL,
448                              RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
449                              RPC_C_IMP_LEVEL_IDENTIFY, NULL,
450                              EOAC_DYNAMIC_CLOAKING, NULL) != S_OK) {
451     return false;
452   }
453
454   bool impersonation_success = false;
455   if (IsRunningElevated()) {
456     wchar_t* curr_proc_sid;
457     if (!GetUserIdForProcess(GetCurrentProcessId(), &curr_proc_sid)) {
458       return false;
459     }
460
461     DWORD pid = 0;
462     ::GetWindowThreadProcessId(::GetShellWindow(), &pid);
463     if (pid <= 0) {
464       ::LocalFree(curr_proc_sid);
465       return false;
466     }
467
468     wchar_t* exp_proc_sid;
469     if (GetUserIdForProcess(pid, &exp_proc_sid)) {
470       if (_wcsicmp(curr_proc_sid, exp_proc_sid) == 0) {
471         ScopedHandle process_handle(
472             ::OpenProcess(PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION,
473                           TRUE,
474                           pid));
475         if (process_handle.IsValid()) {
476           HANDLE process_token = NULL;
477           HANDLE user_token = NULL;
478           if (::OpenProcessToken(process_handle, TOKEN_DUPLICATE | TOKEN_QUERY,
479                                  &process_token) &&
480               ::DuplicateTokenEx(process_token,
481                                  TOKEN_IMPERSONATE | TOKEN_QUERY |
482                                      TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE,
483                                  NULL, SecurityImpersonation,
484                                  TokenPrimary, &user_token) &&
485               (::ImpersonateLoggedOnUser(user_token) != 0)) {
486             impersonation_success = true;
487           }
488           if (user_token)
489             ::CloseHandle(user_token);
490           if (process_token)
491             ::CloseHandle(process_token);
492         }
493       }
494       ::LocalFree(exp_proc_sid);
495     }
496
497     ::LocalFree(curr_proc_sid);
498     if (!impersonation_success) {
499       return false;
500     }
501   }
502
503   bool ret = false;
504   ScopedComPtr<IProcessLauncher> ipl;
505   if (SUCCEEDED(ipl.CreateInstance(__uuidof(ProcessLauncherClass),
506                                    NULL,
507                                    CLSCTX_LOCAL_SERVER))) {
508     if (SUCCEEDED(ipl->LaunchCmdLine(chrome_exe_path.value().c_str())))
509       ret = true;
510     ipl.Release();
511   } else {
512     // Couldn't get Omaha's process launcher, Omaha may not be installed at
513     // system level. Try just running Chrome instead.
514     ret = base::LaunchProcess(chrome_exe_path.value(),
515                               base::LaunchOptions(),
516                               NULL);
517   }
518
519   if (impersonation_success)
520     ::RevertToSelf();
521   return ret;
522 }
523
524 BOOL __stdcall LaunchGoogleChromeWithDimensions(int x,
525                                                 int y,
526                                                 int width,
527                                                 int height,
528                                                 bool in_background) {
529   if (in_background) {
530     base::FilePath chrome_exe_path;
531     if (!GetGoogleChromePath(&chrome_exe_path))
532       return false;
533
534     // When launching in the background, use WMI to ensure that chrome.exe is
535     // is not our child process. This prevents it from pushing itself to
536     // foreground.
537     CommandLine chrome_command(chrome_exe_path);
538
539     ScopedCOMInitializer com_initializer;
540     if (!installer::WMIProcess::Launch(chrome_command.GetCommandLineString(),
541                                        NULL)) {
542       // For some reason WMI failed. Try and launch the old fashioned way,
543       // knowing that visual glitches will occur when the window pops up.
544       if (!LaunchGoogleChrome())
545         return false;
546     }
547
548   } else {
549     if (!LaunchGoogleChrome())
550       return false;
551   }
552
553   HWND hwnd_insert_after = in_background ? HWND_BOTTOM : NULL;
554   DWORD set_window_flags = in_background ? SWP_NOACTIVATE : SWP_NOZORDER;
555
556   if (x == -1 && y == -1)
557     set_window_flags |= SWP_NOMOVE;
558
559   if (width == -1 && height == -1)
560     set_window_flags |= SWP_NOSIZE;
561
562   SetWindowPosParams enum_params = { x, y, width, height, set_window_flags,
563                                      hwnd_insert_after, false };
564
565   // Chrome may have been launched, but the window may not have appeared
566   // yet. Wait for it to appear for 10 seconds, but exit if it takes longer
567   // than that.
568   int ms_elapsed = 0;
569   int timeout = 10000;
570   bool found_window = false;
571   while (ms_elapsed < timeout) {
572     // Enum all top-level windows looking for Chrome windows.
573     ::EnumWindows(ChromeWindowEnumProc, reinterpret_cast<LPARAM>(&enum_params));
574
575     // Give it five more seconds after finding the first window until we stop
576     // shoving new windows into the background.
577     if (!found_window && enum_params.success) {
578       found_window = true;
579       timeout = ms_elapsed + 5000;
580     }
581
582     Sleep(10);
583     ms_elapsed += 10;
584   }
585
586   return found_window;
587 }
588
589 BOOL __stdcall LaunchGoogleChromeInBackground() {
590   return LaunchGoogleChromeWithDimensions(-1, -1, -1, -1, true);
591 }
592
593 int __stdcall GoogleChromeDaysSinceLastRun() {
594   int days_since_last_run = std::numeric_limits<int>::max();
595
596   if (IsChromeInstalled(HKEY_LOCAL_MACHINE) ||
597       IsChromeInstalled(HKEY_CURRENT_USER)) {
598     RegKey client_state(
599         HKEY_CURRENT_USER, kChromeRegClientStateKey, KEY_QUERY_VALUE);
600     if (client_state.Valid()) {
601       std::wstring last_run;
602       int64 last_run_value = 0;
603       if (client_state.ReadValue(google_update::kRegLastRunTimeField,
604                                  &last_run) == ERROR_SUCCESS &&
605           base::StringToInt64(last_run, &last_run_value)) {
606         Time last_run_time = Time::FromInternalValue(last_run_value);
607         TimeDelta difference = Time::NowFromSystemTime() - last_run_time;
608
609         // We can end up with negative numbers here, given changes in system
610         // clock time or due to TimeDelta's int64 -> int truncation.
611         int new_days_since_last_run = difference.InDays();
612         if (new_days_since_last_run >= 0 &&
613             new_days_since_last_run < days_since_last_run) {
614           days_since_last_run = new_days_since_last_run;
615         }
616       }
617     }
618   }
619
620   if (days_since_last_run == std::numeric_limits<int>::max()) {
621     days_since_last_run = -1;
622   }
623
624   return days_since_last_run;
625 }
626
627 BOOL __stdcall CanOfferReactivation(const wchar_t* brand_code,
628                                     int shell_mode,
629                                     DWORD* error_code) {
630   DCHECK(error_code);
631
632   if (!brand_code) {
633     if (error_code)
634       *error_code = REACTIVATE_ERROR_INVALID_INPUT;
635     return FALSE;
636   }
637
638   int days_since_last_run = GoogleChromeDaysSinceLastRun();
639   if (days_since_last_run >= 0 &&
640       days_since_last_run < kReactivationMinDaysDormant) {
641     if (error_code)
642       *error_code = REACTIVATE_ERROR_NOTDORMANT;
643     return FALSE;
644   }
645
646   // Only run the code below when this function is invoked from a standard,
647   // non-elevated cmd shell.  This is because this section of code looks at
648   // values in HKEY_CURRENT_USER, and we only want to look at the logged-in
649   // user's HKCU, not the admin user's HKCU.
650   if (shell_mode == GCAPI_INVOKED_STANDARD_SHELL) {
651     if (!IsChromeInstalled(HKEY_LOCAL_MACHINE) &&
652         !IsChromeInstalled(HKEY_CURRENT_USER)) {
653       if (error_code)
654         *error_code = REACTIVATE_ERROR_NOTINSTALLED;
655       return FALSE;
656     }
657
658     if (HasBeenReactivated()) {
659       if (error_code)
660         *error_code = REACTIVATE_ERROR_ALREADY_REACTIVATED;
661       return FALSE;
662     }
663   }
664
665   return TRUE;
666 }
667
668 BOOL __stdcall ReactivateChrome(wchar_t* brand_code,
669                                 int shell_mode,
670                                 DWORD* error_code) {
671   BOOL result = FALSE;
672   if (CanOfferReactivation(brand_code,
673                            shell_mode,
674                            error_code)) {
675     if (SetReactivationBrandCode(brand_code, shell_mode)) {
676       // Currently set this as a best-effort thing. We return TRUE if
677       // reactivation succeeded regardless of the experiment label result.
678       SetReactivationExperimentLabels(brand_code, shell_mode);
679
680       result = TRUE;
681     } else {
682       if (error_code)
683         *error_code = REACTIVATE_ERROR_REACTIVATION_FAILED;
684     }
685   }
686
687   return result;
688 }
689
690 BOOL __stdcall CanOfferRelaunch(const wchar_t** partner_brandcode_list,
691                                 int partner_brandcode_list_length,
692                                 int shell_mode,
693                                 DWORD* error_code) {
694   DCHECK(error_code);
695
696   if (!partner_brandcode_list || partner_brandcode_list_length <= 0) {
697     if (error_code)
698       *error_code = RELAUNCH_ERROR_INVALID_INPUT;
699     return FALSE;
700   }
701
702   // These conditions need to be satisfied for relaunch:
703   // a) Chrome should be installed;
704   if (!IsChromeInstalled(HKEY_LOCAL_MACHINE) &&
705       (shell_mode != GCAPI_INVOKED_STANDARD_SHELL ||
706           !IsChromeInstalled(HKEY_CURRENT_USER))) {
707     if (error_code)
708       *error_code = RELAUNCH_ERROR_NOTINSTALLED;
709     return FALSE;
710   }
711
712   // b) the installed brandcode should belong to that partner (in
713   // brandcode_list);
714   std::wstring installed_brandcode;
715   bool valid_brandcode = false;
716   if (GoogleUpdateSettings::GetBrand(&installed_brandcode)) {
717     for (int i = 0; i < partner_brandcode_list_length; ++i) {
718       if (!_wcsicmp(installed_brandcode.c_str(), partner_brandcode_list[i])) {
719         valid_brandcode = true;
720         break;
721       }
722     }
723   }
724
725   if (!valid_brandcode) {
726     if (error_code)
727       *error_code = RELAUNCH_ERROR_INVALID_PARTNER;
728     return FALSE;
729   }
730
731   // c) C1F ping should not have been sent;
732   if (IsC1FSent()) {
733     if (error_code)
734       *error_code = RELAUNCH_ERROR_PINGS_SENT;
735     return FALSE;
736   }
737
738   // d) a minimum period (30 days) must have passed since Chrome was last used;
739   int days_since_last_run = GoogleChromeDaysSinceLastRun();
740   if (days_since_last_run >= 0 &&
741       days_since_last_run < kRelaunchMinDaysDormant) {
742     if (error_code)
743       *error_code = RELAUNCH_ERROR_NOTDORMANT;
744     return FALSE;
745   }
746
747   // e) a minimum period (6 months) must have passed since the previous
748   // relaunch offer for the current user;
749   RegKey key;
750   DWORD min_relaunch_date;
751   if (key.Open(HKEY_CURRENT_USER, kChromeRegClientStateKey,
752                KEY_QUERY_VALUE) == ERROR_SUCCESS &&
753       key.ReadValueDW(kRelaunchAllowedAfterValue,
754                       &min_relaunch_date) == ERROR_SUCCESS &&
755       FormatDateOffsetByMonths(0) < min_relaunch_date) {
756     if (error_code)
757       *error_code = RELAUNCH_ERROR_ALREADY_RELAUNCHED;
758     return FALSE;
759   }
760
761   return TRUE;
762 }
763
764 BOOL __stdcall SetRelaunchOffered(const wchar_t** partner_brandcode_list,
765                                   int partner_brandcode_list_length,
766                                   const wchar_t* relaunch_brandcode,
767                                   int shell_mode,
768                                   DWORD* error_code) {
769   if (!CanOfferRelaunch(partner_brandcode_list, partner_brandcode_list_length,
770                         shell_mode, error_code))
771     return FALSE;
772
773   // Store the relaunched brand code and the minimum date for relaunch (6 months
774   // from now), and set the Omaha experiment label.
775   RegKey key;
776   if (key.Create(HKEY_CURRENT_USER, kChromeRegClientStateKey,
777                  KEY_SET_VALUE) != ERROR_SUCCESS ||
778       key.WriteValue(kRelaunchBrandcodeValue,
779                      relaunch_brandcode) != ERROR_SUCCESS ||
780       key.WriteValue(kRelaunchAllowedAfterValue,
781                      FormatDateOffsetByMonths(6)) != ERROR_SUCCESS ||
782       !SetRelaunchExperimentLabels(relaunch_brandcode, shell_mode)) {
783     if (error_code)
784       *error_code = RELAUNCH_ERROR_RELAUNCH_FAILED;
785     return FALSE;
786   }
787
788   return TRUE;
789 }