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.
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.
11 #include "chrome/installer/gcapi/gcapi.h"
14 #define STRSAFE_NO_DEPRECATE
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/string16.h"
30 #include "base/string_util.h"
31 #include "base/strings/string_number_conversions.h"
32 #include "base/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"
46 using base::TimeDelta
;
47 using base::win::RegKey
;
48 using base::win::ScopedCOMInitializer
;
49 using base::win::ScopedComPtr
;
50 using base::win::ScopedHandle
;
54 const wchar_t kChromeRegClientsKey
[] =
55 L
"Software\\Google\\Update\\Clients\\"
56 L
"{8A69D345-D564-463c-AFF1-A69D9E530F96}";
57 const wchar_t kChromeRegClientStateKey
[] =
58 L
"Software\\Google\\Update\\ClientState\\"
59 L
"{8A69D345-D564-463c-AFF1-A69D9E530F96}";
60 const wchar_t kChromeRegClientStateMediumKey
[] =
61 L
"Software\\Google\\Update\\ClientStateMedium\\"
62 L
"{8A69D345-D564-463c-AFF1-A69D9E530F96}";
64 const wchar_t kGCAPITempKey
[] = L
"Software\\Google\\GCAPITemp";
66 const wchar_t kChromeRegLaunchCmd
[] = L
"InstallerSuccessLaunchCmdLine";
67 const wchar_t kChromeRegLastLaunchCmd
[] = L
"LastInstallerSuccessLaunchCmdLine";
68 const wchar_t kChromeRegVersion
[] = L
"pv";
69 const wchar_t kNoChromeOfferUntil
[] =
70 L
"SOFTWARE\\Google\\No Chrome Offer Until";
72 // Prefix used to match the window class for Chrome windows.
73 const wchar_t kChromeWindowClassPrefix
[] = L
"Chrome_WidgetWin_";
75 // Return the company name specified in the file version info resource.
76 bool GetCompanyName(const wchar_t* filename
, wchar_t* buffer
, DWORD out_len
) {
77 wchar_t file_version_info
[8192];
79 DWORD buffer_size
= 0;
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
))
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
))
93 // Retrieve the language and codepage code if exists.
95 if (!::VerQueryValue(file_version_info
, TEXT("\\VarFileInfo\\Translation"),
96 reinterpret_cast<LPVOID
*>(&data
), reinterpret_cast<UINT
*>(&data_len
)))
101 wchar_t info_name
[256];
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);
112 if (!::VerQueryValue(file_version_info
, info_name
,
113 reinterpret_cast<LPVOID
*>(&data
), reinterpret_cast<UINT
*>(&data_len
)))
115 if (data_len
<= 0 || data_len
>= (out_len
/ sizeof(wchar_t)))
118 memset(buffer
, 0, out_len
);
119 ::StringCchCopyN(buffer
,
120 (out_len
/ sizeof(wchar_t)),
121 reinterpret_cast<const wchar_t*>(data
),
126 // Return true if we can re-offer Chrome; false, otherwise.
127 // Each partner can only offer Chrome once every six months.
128 bool CanReOfferChrome(BOOL set_flag
) {
129 wchar_t filename
[MAX_PATH
+1];
130 wchar_t company
[MAX_PATH
];
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)
137 if (!GetCompanyName(filename
, company
, sizeof(company
)))
140 bool can_re_offer
= true;
141 DWORD disposition
= 0;
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.
149 DWORD today
= now
.wYear
* 10000 + now
.wMonth
* 100 + now
.wDay
;
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;
163 // Delete the old or invalid value.
164 ::RegDeleteValue(key
, company
);
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;
174 DWORD value
= timer
.wYear
* 10000 + timer
.wMonth
* 100 + timer
.wDay
;
175 ::RegSetValueEx(key
, company
, 0, REG_DWORD
, (LPBYTE
)&value
,
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.
188 bool ReadValueFromRegistry(HKEY root_key
, const wchar_t* sub_key
,
189 const wchar_t* value_name
, wchar_t* value
,
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
)) {
203 bool IsChromeInstalled(HKEY root_key
) {
205 size_t size
= _countof(version
);
206 return ReadValueFromRegistry(root_key
, kChromeRegClientsKey
,
207 kChromeRegVersion
, version
, &size
);
210 enum WindowsVersion
{
211 VERSION_BELOW_XP_SP2
,
212 VERSION_XP_SP2_UP_TO_VISTA
, // "but not including"
213 VERSION_VISTA_OR_HIGHER
,
215 WindowsVersion
GetWindowsVersion() {
216 OSVERSIONINFOEX version_info
= { sizeof version_info
};
217 GetVersionEx(reinterpret_cast<OSVERSIONINFO
*>(&version_info
));
219 // Windows Vista is version 6.0.
220 if (version_info
.dwMajorVersion
>= 6)
221 return VERSION_VISTA_OR_HIGHER
;
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
;
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
;
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.
236 bool VerifyAdminGroup() {
237 SID_IDENTIFIER_AUTHORITY NtAuthority
= SECURITY_NT_AUTHORITY
;
239 BOOL check
= ::AllocateAndInitializeSid(&NtAuthority
, 2,
240 SECURITY_BUILTIN_DOMAIN_RID
,
241 DOMAIN_ALIAS_RID_ADMINS
, 0, 0, 0,
245 if (!::CheckTokenMembership(NULL
, Group
, &check
))
249 return (check
== TRUE
);
252 bool VerifyHKLMAccess() {
253 wchar_t str
[] = L
"test";
255 DWORD disposition
= 0;
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
) {
264 RegDeleteValue(key
, str
);
269 // If we create the main key, delete the entire key.
270 if (disposition
== REG_CREATED_NEW_KEY
)
271 RegDeleteKey(HKEY_LOCAL_MACHINE
, kGCAPITempKey
);
277 bool IsRunningElevated() {
278 // This method should be called only for Vista or later.
279 if ((GetWindowsVersion() < VERSION_VISTA_OR_HIGHER
) ||
283 HANDLE process_token
;
284 if (!::OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY
, &process_token
))
287 TOKEN_ELEVATION_TYPE elevation_type
= TokenElevationTypeDefault
;
288 DWORD size_returned
= 0;
289 if (!::GetTokenInformation(process_token
, TokenElevationType
,
290 &elevation_type
, sizeof(elevation_type
),
292 ::CloseHandle(process_token
);
296 ::CloseHandle(process_token
);
297 return (elevation_type
== TokenElevationTypeFull
);
300 bool GetUserIdForProcess(size_t pid
, wchar_t** user_sid
) {
301 HANDLE process_handle
= ::OpenProcess(PROCESS_QUERY_INFORMATION
, TRUE
, pid
);
302 if (process_handle
== NULL
)
305 HANDLE process_token
;
307 if (::OpenProcessToken(process_handle
, TOKEN_QUERY
, &process_token
)) {
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
,
316 (actual_size
<= size
)) {
317 PSID sid
= reinterpret_cast<TOKEN_USER
*>(token_user
)->User
.Sid
;
318 if (::ConvertSidToStringSid(sid
, user_sid
))
323 ::CloseHandle(process_token
);
325 ::CloseHandle(process_handle
);
329 struct SetWindowPosParams
{
335 HWND window_insert_after
;
337 std::set
<HWND
> shunted_hwnds
;
340 BOOL CALLBACK
ChromeWindowEnumProc(HWND hwnd
, LPARAM lparam
) {
341 wchar_t window_class
[MAX_PATH
] = {};
342 SetWindowPosParams
* params
= reinterpret_cast<SetWindowPosParams
*>(lparam
);
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;
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
358 // Returns true and populates |chrome_exe_path| with the path to chrome.exe if
359 // a valid installation can be found.
360 bool 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
)) {
369 // Now grab the uninstall string from the appropriate ClientState key
370 // and use that as the base for a path to chrome.exe.
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();
381 BOOL __stdcall
GoogleChromeCompatibilityCheck(BOOL set_flag
,
384 DWORD local_reasons
= 0;
386 WindowsVersion windows_version
= GetWindowsVersion();
387 // System requirements?
388 if (windows_version
== VERSION_BELOW_XP_SP2
)
389 local_reasons
|= GCCC_ERROR_OSNOTSUPPORTED
;
391 if (IsChromeInstalled(HKEY_LOCAL_MACHINE
))
392 local_reasons
|= GCCC_ERROR_SYSTEMLEVELALREADYPRESENT
;
394 if (IsChromeInstalled(HKEY_CURRENT_USER
))
395 local_reasons
|= GCCC_ERROR_USERLEVELALREADYPRESENT
;
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
;
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
;
414 // Done. Copy/return results.
416 *reasons
= local_reasons
;
418 return (local_reasons
== 0);
421 BOOL __stdcall
LaunchGoogleChrome() {
422 base::FilePath chrome_exe_path
;
423 if (!GetGoogleChromePath(&chrome_exe_path
))
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
) {
434 bool impersonation_success
= false;
435 if (IsRunningElevated()) {
436 wchar_t* curr_proc_sid
;
437 if (!GetUserIdForProcess(GetCurrentProcessId(), &curr_proc_sid
)) {
442 ::GetWindowThreadProcessId(::GetShellWindow(), &pid
);
444 ::LocalFree(curr_proc_sid
);
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
,
455 if (process_handle
.IsValid()) {
456 HANDLE process_token
= NULL
;
457 HANDLE user_token
= NULL
;
458 if (::OpenProcessToken(process_handle
, TOKEN_DUPLICATE
| TOKEN_QUERY
,
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;
469 ::CloseHandle(user_token
);
471 ::CloseHandle(process_token
);
474 ::LocalFree(exp_proc_sid
);
477 ::LocalFree(curr_proc_sid
);
478 if (!impersonation_success
) {
484 ScopedComPtr
<IProcessLauncher
> ipl
;
485 if (SUCCEEDED(ipl
.CreateInstance(__uuidof(ProcessLauncherClass
),
487 CLSCTX_LOCAL_SERVER
))) {
488 if (SUCCEEDED(ipl
->LaunchCmdLine(chrome_exe_path
.value().c_str())))
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(),
499 if (impersonation_success
)
504 BOOL __stdcall
LaunchGoogleChromeWithDimensions(int x
,
508 bool in_background
) {
510 base::FilePath chrome_exe_path
;
511 if (!GetGoogleChromePath(&chrome_exe_path
))
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
517 CommandLine
chrome_command(chrome_exe_path
);
519 ScopedCOMInitializer com_initializer
;
520 if (!installer::WMIProcess::Launch(chrome_command
.GetCommandLineString(),
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())
529 if (!LaunchGoogleChrome())
533 HWND hwnd_insert_after
= in_background
? HWND_BOTTOM
: NULL
;
534 DWORD set_window_flags
= in_background
? SWP_NOACTIVATE
: SWP_NOZORDER
;
536 if (x
== -1 && y
== -1)
537 set_window_flags
|= SWP_NOMOVE
;
539 if (width
== -1 && height
== -1)
540 set_window_flags
|= SWP_NOSIZE
;
542 SetWindowPosParams enum_params
= { x
, y
, width
, height
, set_window_flags
,
543 hwnd_insert_after
, false };
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
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
));
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
) {
559 timeout
= ms_elapsed
+ 5000;
569 BOOL __stdcall
LaunchGoogleChromeInBackground() {
570 return LaunchGoogleChromeWithDimensions(-1, -1, -1, -1, true);
573 int __stdcall
GoogleChromeDaysSinceLastRun() {
574 int days_since_last_run
= std::numeric_limits
<int>::max();
576 if (IsChromeInstalled(HKEY_LOCAL_MACHINE
) ||
577 IsChromeInstalled(HKEY_CURRENT_USER
)) {
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
;
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
;
600 if (days_since_last_run
== std::numeric_limits
<int>::max()) {
601 days_since_last_run
= -1;
604 return days_since_last_run
;
607 BOOL __stdcall
CanOfferReactivation(const wchar_t* brand_code
,
614 *error_code
= REACTIVATE_ERROR_INVALID_INPUT
;
618 int days_since_last_run
= GoogleChromeDaysSinceLastRun();
619 if (days_since_last_run
>= 0 &&
620 days_since_last_run
< kReactivationMinDaysDormant
) {
622 *error_code
= REACTIVATE_ERROR_NOTDORMANT
;
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
)) {
634 *error_code
= REACTIVATE_ERROR_NOTINSTALLED
;
638 if (HasBeenReactivated()) {
640 *error_code
= REACTIVATE_ERROR_ALREADY_REACTIVATED
;
648 BOOL __stdcall
ReactivateChrome(wchar_t* brand_code
,
652 if (CanOfferReactivation(brand_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
);
663 *error_code
= REACTIVATE_ERROR_REACTIVATION_FAILED
;