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.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 531 BOOL __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 596 BOOL __stdcall LaunchGoogleChromeInBackground() { 597 return LaunchGoogleChromeWithDimensions(-1, -1, -1, -1, true); 598 } 599 600 int __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 635 BOOL __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 676 BOOL __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 698 BOOL __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 773 BOOL __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