| // Copyright 2007-2010 Google Inc. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // ======================================================================== |
| // |
| // Defines: GoopdateUtils, helper functions for goopdate. |
| |
| #include "omaha/goopdate/goopdate_utils.h" |
| #include "omaha/goopdate/goopdate_utils-internal.h" |
| |
| #include <windows.h> |
| #include <corerror.h> |
| #include <lmcons.h> |
| #include <atlpath.h> |
| #include <atlsecurity.h> |
| #include <map> |
| #include <utility> |
| #include <vector> |
| #include "omaha/common/app_util.h" |
| #include "omaha/common/browser_utils.h" |
| #include "omaha/common/const_addresses.h" |
| #include "omaha/common/const_cmd_line.h" |
| #include "omaha/common/const_object_names.h" |
| #include "omaha/common/const_utils.h" |
| #include "omaha/common/debug.h" |
| #include "omaha/common/error.h" |
| #include "omaha/common/file.h" |
| #include "omaha/common/omaha_version.h" |
| #include "omaha/common/path.h" |
| #include "omaha/common/proc_utils.h" |
| #include "omaha/common/process.h" |
| #include "omaha/common/reg_key.h" |
| #include "omaha/common/scope_guard.h" |
| #include "omaha/common/scoped_impersonation.h" |
| #include "omaha/common/scoped_ptr_cotask.h" |
| #include "omaha/common/service_utils.h" |
| #include "omaha/common/shell.h" |
| #include "omaha/common/signatures.h" |
| #include "omaha/common/string.h" |
| #include "omaha/common/system.h" |
| #include "omaha/common/system_info.h" |
| #include "omaha/common/time.h" |
| #include "omaha/common/user_info.h" |
| #include "omaha/common/utils.h" |
| #include "omaha/common/vistautil.h" |
| #include "omaha/common/vista_utils.h" |
| #include "omaha/common/window_utils.h" |
| #include "omaha/goopdate/command_line_builder.h" |
| #include "omaha/goopdate/config_manager.h" |
| #include "omaha/goopdate/const_goopdate.h" |
| #include "omaha/goopdate/event_logger.h" |
| #include "omaha/goopdate/goopdate_xml_parser.h" |
| #include "omaha/goopdate/resources/goopdateres/goopdate.grh" |
| #include "omaha/goopdate/stats_uploader.h" |
| #include "omaha/goopdate/update_response.h" |
| #include "omaha/net/http_client.h" |
| #include "omaha/net/network_config.h" |
| #include "omaha/net/network_request.h" |
| |
| // Generated by MIDL in the "BUILD_MODE.OBJ_ROOT + SETTINGS.SUBDIR". |
| #include "goopdate/google_update_idl.h" |
| // Generated by MIDL in the "BUILD_MODE.OBJ_ROOT + SETTINGS.SUBDIR". |
| #include "goopdate/google_update_idl_i.c" |
| |
| namespace omaha { |
| |
| namespace goopdate_utils { |
| |
| namespace { |
| |
| const int kApplicationGuidOffset = 38; |
| const int kTerminateBrowserTimeoutMs = 60000; |
| |
| const TCHAR* const kTrue = _T("true"); |
| const TCHAR* const kFalse = _T("false"); |
| |
| // Query element name-value pair. |
| typedef std::pair<CString, CString> QueryElement; |
| |
| // Builds a query string from the provided name-value pairs. |
| // The string begins with a '&' and ends with the last value. |
| // query must be empty. |
| HRESULT BuildQueryString(const std::vector<QueryElement>& elements, |
| CString* query) { |
| ASSERT1(query); |
| |
| if (elements.empty() || !query->IsEmpty()) { |
| return E_INVALIDARG; |
| } |
| |
| for (size_t i = 0; i < elements.size(); ++i) { |
| CString escaped_str; |
| HRESULT hr = StringEscape(elements[i].second, false, &escaped_str); |
| if (FAILED(hr)) { |
| CORE_LOG(LEVEL_WARNING, (_T("[StringEscape failed][0x%08x]"), hr)); |
| return hr; |
| } |
| |
| CString element; |
| element.Format(_T("%s%=%s&"), elements[i].first, escaped_str); |
| query->Append(element); |
| } |
| |
| ASSERT1(!query->IsEmpty()); |
| if (_T('&') == query->GetAt(query->GetLength() - 1)) { |
| query->Truncate(query->GetLength() - 1); |
| } |
| |
| return S_OK; |
| } |
| |
| bool IsMachineProcessWithoutPrivileges(bool is_machine_process) { |
| return is_machine_process && !vista_util::IsUserAdmin(); |
| } |
| |
| HRESULT LaunchImpersonatedCmdLine(const CString& cmd_line) { |
| CORE_LOG(L3, (_T("[LaunchImpersonatedCmdLine][%s]"), cmd_line)); |
| |
| scoped_handle impersonation_token; |
| HRESULT hr = vista::GetLoggedOnUserToken(address(impersonation_token)); |
| if (FAILED(hr)) { |
| UTIL_LOG(LE, (_T("[GetLoggedOnUserToken failed][0x%x]"), hr)); |
| return hr; |
| } |
| |
| scoped_impersonation impersonate_user(get(impersonation_token)); |
| hr = HRESULT_FROM_WIN32(impersonate_user.result()); |
| if (FAILED(hr)) { |
| UTIL_LOG(LE, (_T("[impersonation failed][0x%x]"), hr)); |
| return hr; |
| } |
| |
| CComPtr<IProcessLauncher> launcher; |
| hr = launcher.CoCreateInstance(CLSID_ProcessLauncherClass, |
| NULL, |
| CLSCTX_LOCAL_SERVER); |
| if (FAILED(hr)) { |
| UTIL_LOG(LE, (_T("[CoCreateInstance IProcessLauncher failed][0x%x]"), hr)); |
| return hr; |
| } |
| |
| hr = launcher->LaunchCmdLine(cmd_line); |
| if (FAILED(hr)) { |
| UTIL_LOG(LE, (_T("[IProcessLauncher.LaunchBrowser failed][0x%x]"), hr)); |
| return hr; |
| } |
| |
| return S_OK; |
| } |
| |
| HRESULT LaunchImpersonatedBrowser(BrowserType type, const CString& url) { |
| CORE_LOG(L3, (_T("[LaunchImpersonatedBrowser][%u][%s]"), type, url)); |
| |
| scoped_handle impersonation_token; |
| HRESULT hr = vista::GetLoggedOnUserToken(address(impersonation_token)); |
| if (FAILED(hr)) { |
| UTIL_LOG(LE, (_T("[GetLoggedOnUserToken failed][0x%x]"), hr)); |
| return hr; |
| } |
| |
| scoped_impersonation impersonate_user(get(impersonation_token)); |
| hr = HRESULT_FROM_WIN32(impersonate_user.result()); |
| if (FAILED(hr)) { |
| UTIL_LOG(LE, (_T("[impersonation failed][0x%x]"), hr)); |
| return hr; |
| } |
| |
| CComPtr<IProcessLauncher> launcher; |
| hr = launcher.CoCreateInstance(CLSID_ProcessLauncherClass, |
| NULL, |
| CLSCTX_LOCAL_SERVER); |
| if (FAILED(hr)) { |
| UTIL_LOG(LE, (_T("[CoCreateInstance IProcessLauncher failed][0x%x]"), hr)); |
| return hr; |
| } |
| |
| hr = launcher->LaunchBrowser(type, url); |
| if (FAILED(hr)) { |
| UTIL_LOG(LE, (_T("[IProcessLauncher.LaunchBrowser failed][0x%x]"), hr)); |
| return hr; |
| } |
| |
| return S_OK; |
| } |
| |
| } // namespace |
| |
| namespace internal { |
| |
| bool IsInstalledScheduledTask(const TCHAR* task_name) { |
| ASSERT1(task_name && *task_name); |
| |
| CComPtr<ITaskScheduler> scheduler; |
| HRESULT hr = scheduler.CoCreateInstance(CLSID_CTaskScheduler, |
| NULL, |
| CLSCTX_INPROC_SERVER); |
| if (FAILED(hr)) { |
| ASSERT(false, (_T("ITaskScheduler.CoCreateInstance failed 0x%x"), hr)); |
| return false; |
| } |
| |
| CComPtr<ITask> task; |
| hr = scheduler->Activate(task_name, |
| __uuidof(ITask), |
| reinterpret_cast<IUnknown**>(&task)); |
| |
| CORE_LOG(L3, (_T("[IsInstalledScheduledTask returned][0x%x]"), hr)); |
| return COR_E_FILENOTFOUND == hr ? false : true; |
| } |
| |
| HRESULT GetScheduledTaskStatus(const TCHAR* task_name) { |
| ASSERT1(task_name && *task_name); |
| |
| CComPtr<ITaskScheduler> scheduler; |
| HRESULT hr = scheduler.CoCreateInstance(CLSID_CTaskScheduler, |
| NULL, |
| CLSCTX_INPROC_SERVER); |
| if (FAILED(hr)) { |
| ASSERT(false, (_T("ITaskScheduler.CoCreateInstance failed 0x%x"), hr)); |
| return hr; |
| } |
| |
| CComPtr<ITask> task; |
| hr = scheduler->Activate(task_name, |
| __uuidof(ITask), |
| reinterpret_cast<IUnknown**>(&task)); |
| |
| if (FAILED(hr)) { |
| CORE_LOG(LE, (_T("[GetScheduledTaskStatus: Activate failed][0x%x]"), hr)); |
| return hr; |
| } |
| |
| HRESULT task_status(S_OK); |
| hr = task->GetStatus(&task_status); |
| if (FAILED(hr)) { |
| ASSERT(false, (_T("ITask.GetStatus failed 0x%x"), hr)); |
| return hr; |
| } |
| |
| return task_status; |
| } |
| |
| HRESULT GetScheduledTaskExitCode(const TCHAR* task_name) { |
| ASSERT1(task_name && *task_name); |
| |
| CComPtr<ITaskScheduler> scheduler; |
| HRESULT hr = scheduler.CoCreateInstance(CLSID_CTaskScheduler, |
| NULL, |
| CLSCTX_INPROC_SERVER); |
| if (FAILED(hr)) { |
| ASSERT(false, (_T("ITaskScheduler.CoCreateInstance failed 0x%x"), hr)); |
| return hr; |
| } |
| |
| CComPtr<ITask> task; |
| hr = scheduler->Activate(task_name, |
| __uuidof(ITask), |
| reinterpret_cast<IUnknown**>(&task)); |
| |
| if (FAILED(hr)) { |
| CORE_LOG(LE, (_T("[ITask.Activate failed][0x%x]"), hr)); |
| return hr; |
| } |
| |
| DWORD exit_code(0); |
| hr = task->GetExitCode(&exit_code); |
| if (FAILED(hr)) { |
| CORE_LOG(LE, (_T("ITask.GetExitCode failed 0x%x"), hr)); |
| return hr; |
| } |
| |
| return hr == SCHED_S_TASK_HAS_NOT_RUN ? hr : exit_code; |
| } |
| |
| HRESULT StartScheduledTask(const TCHAR* task_name) { |
| ASSERT1(task_name && *task_name); |
| |
| if (GetScheduledTaskStatus(task_name) == SCHED_S_TASK_RUNNING) { |
| return S_OK; |
| } |
| |
| CComPtr<ITaskScheduler> scheduler; |
| HRESULT hr = scheduler.CoCreateInstance(CLSID_CTaskScheduler, |
| NULL, |
| CLSCTX_INPROC_SERVER); |
| if (FAILED(hr)) { |
| ASSERT(false, (_T("ITaskScheduler.CoCreateInstance failed 0x%x"), hr)); |
| return hr; |
| } |
| |
| CComPtr<ITask> task; |
| hr = scheduler->Activate(task_name, |
| __uuidof(ITask), |
| reinterpret_cast<IUnknown**>(&task)); |
| |
| if (FAILED(hr)) { |
| ASSERT(false, (_T("ITaskScheduler.Activate failed 0x%x"), hr)); |
| return hr; |
| } |
| |
| hr = task->Run(); |
| if (FAILED(hr)) { |
| ASSERT(false, (_T("ITask.Run failed 0x%x"), hr)); |
| return hr; |
| } |
| |
| return hr; |
| } |
| |
| HRESULT StopScheduledTask(const TCHAR* task_name) { |
| ASSERT1(task_name && *task_name); |
| |
| if (GetScheduledTaskStatus(task_name) != SCHED_S_TASK_RUNNING) { |
| return S_OK; |
| } |
| |
| CComPtr<ITaskScheduler> scheduler; |
| HRESULT hr = scheduler.CoCreateInstance(CLSID_CTaskScheduler, |
| NULL, |
| CLSCTX_INPROC_SERVER); |
| if (FAILED(hr)) { |
| ASSERT(false, (_T("ITaskScheduler.CoCreateInstance failed 0x%x"), hr)); |
| return hr; |
| } |
| |
| CComPtr<ITask> task; |
| hr = scheduler->Activate(task_name, |
| __uuidof(ITask), |
| reinterpret_cast<IUnknown**>(&task)); |
| |
| if (FAILED(hr)) { |
| ASSERT(false, (_T("ITaskScheduler.Activate failed 0x%x"), hr)); |
| return hr; |
| } |
| |
| hr = task->Terminate(); |
| if (FAILED(hr)) { |
| ASSERT(false, (_T("ITask.Run failed 0x%x"), hr)); |
| return hr; |
| } |
| |
| return hr; |
| } |
| |
| HRESULT CreateLogonTrigger(ITask* task) { |
| ASSERT1(task); |
| |
| CComPtr<ITaskTrigger> trigger; |
| WORD index = 0; |
| |
| // Create a trigger to run on every user logon. |
| HRESULT hr = task->CreateTrigger(&index, &trigger); |
| if (FAILED(hr)) { |
| ASSERT(false, (_T("ITask.CreateTrigger failed 0x%x"), hr)); |
| return hr; |
| } |
| |
| TASK_TRIGGER trigger_config = {0}; |
| trigger_config.cbTriggerSize = sizeof(trigger_config); |
| // These are required parameters. A past start date is good. |
| trigger_config.wBeginDay = 1; |
| trigger_config.wBeginMonth = 1; |
| trigger_config.wBeginYear = 1999; |
| |
| // Run on every user logon. |
| trigger_config.TriggerType = TASK_EVENT_TRIGGER_AT_LOGON; |
| |
| hr = trigger->SetTrigger(&trigger_config); |
| if (FAILED(hr)) { |
| ASSERT(false, (_T("ITaskTrigger.SetTrigger failed 0x%x"), hr)); |
| return hr; |
| } |
| |
| return S_OK; |
| } |
| |
| HRESULT CreatePeriodicTrigger(ITask* task, bool create_hourly_trigger) { |
| ASSERT1(task); |
| |
| CComPtr<ITaskTrigger> trigger; |
| WORD index = 0; |
| |
| // Create a trigger to run every day. |
| HRESULT hr = task->CreateTrigger(&index, &trigger); |
| if (FAILED(hr)) { |
| ASSERT(false, (_T("ITask.CreateTrigger failed 0x%x"), hr)); |
| return hr; |
| } |
| |
| // Start time set to 5 minutes from the current time. |
| time64 start_time = GetCurrent100NSTime() + (5 * kMinsTo100ns); |
| SYSTEMTIME sys_time = Time64ToSystemTime(start_time); |
| SYSTEMTIME locale_time = {0}; |
| hr = SystemTimeToTzSpecificLocalTime(NULL, &sys_time, &locale_time); |
| if (FAILED(hr)) { |
| ASSERT(false, (_T("SystemTimeToTzSpecificLocalTime failed 0x%x"), hr)); |
| return hr; |
| } |
| |
| TASK_TRIGGER trigger_config = {0}; |
| trigger_config.cbTriggerSize = sizeof(trigger_config); |
| trigger_config.wBeginYear = locale_time.wYear; |
| trigger_config.wBeginMonth = locale_time.wMonth; |
| trigger_config.wBeginDay = locale_time.wDay; |
| trigger_config.wStartHour = locale_time.wHour; |
| trigger_config.wStartMinute = locale_time.wMinute; |
| |
| trigger_config.TriggerType = TASK_TIME_TRIGGER_DAILY; |
| trigger_config.Type.Daily.DaysInterval = 1; // every 1 day |
| |
| if (create_hourly_trigger) { |
| // The task will be run daily at 24 hour intervals. And the task will be |
| // repeated every au_timer_interval_minutes within a single 24 hour |
| // interval. |
| const DWORD kTaskTrigger24HoursDuration = 24 * 60; |
| int au_timer_interval_minutes = |
| ConfigManager::Instance()->GetAutoUpdateTimerIntervalMs() / (60 * 1000); |
| ASSERT1(au_timer_interval_minutes > 0 && |
| au_timer_interval_minutes < kTaskTrigger24HoursDuration); |
| |
| trigger_config.MinutesDuration = kTaskTrigger24HoursDuration; |
| trigger_config.MinutesInterval = au_timer_interval_minutes; |
| } |
| |
| hr = trigger->SetTrigger(&trigger_config); |
| if (FAILED(hr)) { |
| ASSERT(false, (_T("ITaskTrigger.SetTrigger failed 0x%x"), hr)); |
| return hr; |
| } |
| |
| return S_OK; |
| } |
| |
| HRESULT CreateScheduledTask(ITask* task, |
| const TCHAR* task_path, |
| const TCHAR* task_parameters, |
| const TCHAR* task_comment, |
| bool is_machine, |
| bool create_logon_trigger, |
| bool create_daily_trigger, |
| bool create_hourly_trigger) { |
| ASSERT1(task); |
| ASSERT1(task_path && *task_path); |
| ASSERT1(task_parameters); |
| ASSERT1(task_comment && *task_comment); |
| ASSERT1(create_logon_trigger || create_daily_trigger); |
| ASSERT1(!create_logon_trigger || (create_logon_trigger && is_machine)); |
| ASSERT1(!create_hourly_trigger || |
| (create_hourly_trigger && create_daily_trigger)); |
| |
| CORE_LOG(L3, (_T("CreateScheduledTask[%s][%s][%d]"), |
| task_path, task_parameters, is_machine)); |
| |
| HRESULT hr = task->SetApplicationName(task_path); |
| if (FAILED(hr)) { |
| ASSERT(false, (_T("ITask.SetApplicationName failed 0x%x"), hr)); |
| return hr; |
| } |
| |
| hr = task->SetParameters(task_parameters); |
| if (FAILED(hr)) { |
| ASSERT(false, (_T("ITask.SetParameters failed 0x%x"), hr)); |
| return hr; |
| } |
| |
| hr = task->SetComment(task_comment); |
| if (FAILED(hr)) { |
| ASSERT(false, (_T("ITask.SetComment failed 0x%x"), hr)); |
| return hr; |
| } |
| |
| if (is_machine) { |
| // Run using SYSTEM credentials, by passing in an empty username string. |
| hr = task->SetAccountInformation(_T(""), NULL); |
| } else { |
| // Run as current user. |
| // For the user task, we set TASK_FLAG_RUN_ONLY_IF_LOGGED_ON, so that we do |
| // not need the user password for task creation. |
| hr = task->SetFlags(TASK_FLAG_RUN_ONLY_IF_LOGGED_ON); |
| if (FAILED(hr)) { |
| ASSERT(false, (_T("ITask.SetFlags failed 0x%x"), hr)); |
| return hr; |
| } |
| |
| CString user_name; |
| DWORD buffer_size = UNLEN + 1; |
| if (!::GetUserName(CStrBuf(user_name, buffer_size), &buffer_size)) { |
| hr = HRESULTFromLastError(); |
| ASSERT(false, (_T("::GetUserName failed 0x%x"), hr)); |
| return hr; |
| } |
| hr = task->SetAccountInformation(user_name, NULL); |
| } |
| |
| if (FAILED(hr)) { |
| ASSERT(false, (_T("ITask.SetAccountInformation failed 0x%x"), hr)); |
| return hr; |
| } |
| |
| // The default is to run for a finite number of days. We want to run |
| // indefinitely. |
| // Due to a bug introduced in Vista, and propogated to Windows 7, setting the |
| // MaxRunTime to INFINITE results in the task only running for 72 hours. For |
| // these operating systems, setting the RunTime to "INFINITE - 1" gets the |
| // desired behavior of allowing an "infinite" run of the task. |
| DWORD max_time = INFINITE - (SystemInfo::IsRunningOnVistaOrLater() ? 1 : 0); |
| hr = task->SetMaxRunTime(max_time); |
| if (FAILED(hr)) { |
| ASSERT(false, (_T("ITask.SetMaxRunTime failed 0x%x"), hr)); |
| return hr; |
| } |
| |
| CComPtr<ITaskTrigger> trigger; |
| WORD index = 0; |
| |
| if (create_logon_trigger && is_machine) { |
| // Create a trigger to run on every user logon. Non-admin users are not able |
| // to create logon triggers, so we create only for machine. |
| hr = CreateLogonTrigger(task); |
| if (FAILED(hr)) { |
| return hr; |
| } |
| } |
| |
| if (create_daily_trigger) { |
| hr = CreatePeriodicTrigger(task, create_hourly_trigger); |
| if (FAILED(hr)) { |
| return hr; |
| } |
| } |
| |
| // Save task. |
| CComQIPtr<IPersistFile> persist(task); |
| if (!persist) { |
| hr = E_NOINTERFACE; |
| ASSERT(false, (_T("ITask.QueryInterface IPersistFile failed 0x%x"), hr)); |
| return hr; |
| } |
| |
| hr = persist->Save(NULL, TRUE); |
| if (FAILED(hr)) { |
| CORE_LOG(LE, (_T("IPersistFile.Save failed 0x%x"), hr)); |
| return hr; |
| } |
| |
| if (is_machine) { |
| return S_OK; |
| } |
| |
| // Adjust privileges to explicitly allow the current user to be able to |
| // manipulate this task. User applications, and consequently, Omaha, can be |
| // installed in an elevated mode. This can happen, for instance, if the user |
| // installs on XP, then upgrades to Vista. Or chooses "Run as Administrator" |
| // when running the meta-installer on Vista. Subsequently, Omaha running at |
| // medium integrity needs to be able to manipulate the installed task. |
| scoped_ptr_cotask<OLECHAR> job_file; |
| hr = persist->GetCurFile(address(job_file)); |
| if (FAILED(hr)) { |
| ASSERT(false, (_T("IPersistFile.GetCurFile failed 0x%x"), hr)); |
| return hr; |
| } |
| |
| persist.Release(); |
| |
| CAccessToken token; |
| CSid current_sid; |
| if (!token.GetEffectiveToken(TOKEN_QUERY) || !token.GetUser(¤t_sid)) { |
| hr = HRESULTFromLastError(); |
| ASSERT(false, (_T("[Failed to get current user sid[0x%x]"), hr)); |
| return hr; |
| } |
| |
| hr = AddAllowedAce(job_file.get(), |
| SE_FILE_OBJECT, |
| current_sid, |
| FILE_ALL_ACCESS, |
| 0); |
| if (FAILED(hr)) { |
| ASSERT(false, (_T("Could not adjust DACL[%s][0x%x]"), job_file.get(), hr)); |
| return hr; |
| } |
| |
| return S_OK; |
| } |
| |
| HRESULT UpgradeScheduledTask(const TCHAR* task_name, |
| const TCHAR* task_path, |
| const TCHAR* task_parameters, |
| const TCHAR* task_comment, |
| bool is_machine, |
| bool create_logon_trigger, |
| bool create_daily_trigger, |
| bool create_hourly_trigger) { |
| ASSERT1(task_name && *task_name); |
| ASSERT1(IsInstalledScheduledTask(task_name)); |
| |
| CORE_LOG(L3, (_T("UpgradeScheduledTask[%s][%s][%s][%d]"), |
| task_name, task_path, task_parameters, is_machine)); |
| |
| // TODO(Omaha): Perhaps pass the ITaskScheduler around where possible. |
| CComPtr<ITaskScheduler> scheduler; |
| HRESULT hr = scheduler.CoCreateInstance(CLSID_CTaskScheduler, |
| NULL, |
| CLSCTX_INPROC_SERVER); |
| if (FAILED(hr)) { |
| ASSERT(false, (_T("ITaskScheduler.CoCreateInstance failed 0x%x"), hr)); |
| return hr; |
| } |
| |
| CComPtr<ITask> task; |
| hr = scheduler->Activate(task_name, |
| __uuidof(ITask), |
| reinterpret_cast<IUnknown**>(&task)); |
| |
| if (FAILED(hr)) { |
| CORE_LOG(LE, (_T("UpgradeScheduledTask: Activate failed[0x%x]"), hr)); |
| return hr; |
| } |
| |
| // Delete existing triggers. CreateScheduledTask() will recreate them anew. |
| WORD trigger_count(0); |
| hr = task->GetTriggerCount(&trigger_count); |
| if (FAILED(hr)) { |
| ASSERT(false, (_T("ITaskScheduler.GetTriggerCount failed 0x%x"), hr)); |
| return hr; |
| } |
| |
| for (int i = 0; i < trigger_count; ++i) { |
| hr = task->DeleteTrigger(0); |
| if (FAILED(hr)) { |
| ASSERT(false, (_T("ITaskScheduler.DeleteTrigger failed 0x%x"), hr)); |
| return hr; |
| } |
| } |
| |
| return CreateScheduledTask(task, |
| task_path, |
| task_parameters, |
| task_comment, |
| is_machine, |
| create_logon_trigger, |
| create_daily_trigger, |
| create_hourly_trigger); |
| } |
| |
| // TODO(Omaha): Change the apis to avoid specifying hourly and daily triggers. |
| HRESULT InstallScheduledTask(const TCHAR* task_name, |
| const TCHAR* task_path, |
| const TCHAR* task_parameters, |
| const TCHAR* task_comment, |
| bool is_machine, |
| bool create_logon_trigger, |
| bool create_daily_trigger, |
| bool create_hourly_trigger) { |
| if (IsInstalledScheduledTask(task_name)) { |
| return UpgradeScheduledTask(task_name, |
| task_path, |
| task_parameters, |
| task_comment, |
| is_machine, |
| create_logon_trigger, |
| create_daily_trigger, |
| create_hourly_trigger); |
| } |
| |
| CComPtr<ITaskScheduler> scheduler; |
| HRESULT hr = scheduler.CoCreateInstance(CLSID_CTaskScheduler, |
| NULL, |
| CLSCTX_INPROC_SERVER); |
| if (FAILED(hr)) { |
| ASSERT(false, (_T("ITaskScheduler.CoCreateInstance failed 0x%x"), hr)); |
| return hr; |
| } |
| |
| CComPtr<ITask> task; |
| hr = scheduler->NewWorkItem(task_name, |
| CLSID_CTask, |
| __uuidof(ITask), |
| reinterpret_cast<IUnknown**>(&task)); |
| |
| if (FAILED(hr)) { |
| ASSERT(false, (_T("ITaskScheduler.NewWorkItem failed 0x%x"), hr)); |
| return hr; |
| } |
| |
| return CreateScheduledTask(task, |
| task_path, |
| task_parameters, |
| task_comment, |
| is_machine, |
| create_logon_trigger, |
| create_daily_trigger, |
| create_hourly_trigger); |
| } |
| |
| HRESULT UninstallScheduledTask(const TCHAR* task_name) { |
| ASSERT1(task_name && *task_name); |
| |
| CComPtr<ITaskScheduler> scheduler; |
| HRESULT hr = scheduler.CoCreateInstance(CLSID_CTaskScheduler, |
| NULL, |
| CLSCTX_INPROC_SERVER); |
| if (FAILED(hr)) { |
| ASSERT(false, (_T("ITaskScheduler.CoCreateInstance failed 0x%x"), hr)); |
| return hr; |
| } |
| |
| // Stop the task before deleting it. Ignore return value. |
| VERIFY1(SUCCEEDED(StopScheduledTask(task_name))); |
| |
| // delete the task. |
| hr = scheduler->Delete(task_name); |
| if (FAILED(hr) && COR_E_FILENOTFOUND != hr) { |
| CORE_LOG(LE, (_T("GetScheduledTaskStatus: Delete failed[0x%x]"), hr)); |
| return hr; |
| } |
| |
| return S_OK; |
| } |
| |
| HRESULT UninstallScheduledTasks(const TCHAR* task_prefix) { |
| ASSERT1(task_prefix && *task_prefix); |
| |
| CComPtr<ITaskScheduler> scheduler; |
| HRESULT hr = scheduler.CoCreateInstance(CLSID_CTaskScheduler, |
| NULL, |
| CLSCTX_INPROC_SERVER); |
| if (FAILED(hr)) { |
| ASSERT(false, (_T("ITaskScheduler.CoCreateInstance failed 0x%x"), hr)); |
| return hr; |
| } |
| |
| CComPtr<IEnumWorkItems> enum_items; |
| hr = scheduler->Enum(&enum_items); |
| if (FAILED(hr)) { |
| ASSERT(false, (_T("ITaskScheduler.Enum failed 0x%x"), hr)); |
| return hr; |
| } |
| |
| TCHAR** task_names = NULL; |
| DWORD task_count = 0; |
| while (enum_items->Next(1, &task_names, &task_count) == S_OK) { |
| ASSERT1(task_count == 1); |
| scoped_co_task_ptr task_names_guard(task_names); |
| scoped_co_task_ptr task_name_guard(task_names[0]); |
| |
| if (String_StartsWith(task_names[0], task_prefix, true)) { |
| UninstallScheduledTask(task_names[0]); |
| } |
| } |
| |
| return S_OK; |
| } |
| |
| // Returns the task name Omaha used to install in Omaha 1.2.x. |
| CString GetOmaha1LegacyTaskName(bool is_machine) { |
| const TCHAR* const kLegacyOmaha1TaskNameMachine = _T("GoogleUpdateTask"); |
| const TCHAR* const kLegacyOmaha1TaskNameUser = _T("GoogleUpdateTaskUser"); |
| return is_machine ? kLegacyOmaha1TaskNameMachine : kLegacyOmaha1TaskNameUser; |
| } |
| |
| // Returns the task name Omaha used to install in Omaha 2 before the |
| // "GoogleUpdate.exe does not run all the time" refactoring. |
| CString GetOmaha2LegacyTaskName(bool is_machine) { |
| const TCHAR* kLegacyOmaha2TaskNameUserPrefix = _T("GoogleUpdateTaskUser"); |
| const TCHAR* kLegacyOmaha2TaskNameMachine = _T("GoogleUpdateTaskMachine"); |
| if (is_machine) { |
| return kLegacyOmaha2TaskNameMachine; |
| } |
| |
| CString task_name_user = kLegacyOmaha2TaskNameUserPrefix; |
| CString user_sid; |
| VERIFY1(SUCCEEDED(user_info::GetCurrentUser(NULL, NULL, &user_sid))); |
| task_name_user += user_sid; |
| return task_name_user; |
| } |
| |
| } // namespace internal |
| |
| CString GetAppClientsKey(bool is_machine, const CString& app_guid) { |
| return AppendRegKeyPath( |
| ConfigManager::Instance()->registry_clients(is_machine), |
| app_guid); |
| } |
| |
| CString GetAppClientStateKey(bool is_machine, const CString& app_guid) { |
| return AppendRegKeyPath( |
| ConfigManager::Instance()->registry_client_state(is_machine), |
| app_guid); |
| } |
| |
| CString GetAppClientStateMediumKey(bool is_machine, const CString& app_guid) { |
| ASSERT1(is_machine); |
| UNREFERENCED_PARAMETER(is_machine); |
| return AppendRegKeyPath( |
| ConfigManager::Instance()->machine_registry_client_state_medium(), |
| app_guid); |
| } |
| |
| // Returns the application registration location given the user SID. |
| CString GetUserAllAppsStatePath(const CString& user_sid) { |
| return AppendRegKeyPath(USERS_KEY, user_sid, |
| GOOPDATE_REG_RELATIVE_CLIENT_STATE); |
| } |
| |
| // Returns the application state path for a user given the user SID. |
| CString GetUserAllAppsRegPath(const CString& user_sid) { |
| return AppendRegKeyPath(USERS_KEY, user_sid, GOOPDATE_REG_RELATIVE_CLIENTS); |
| } |
| |
| // Returns the application state path for a particular user and for the |
| // given application id. |
| CString GetUserAppStatePath(const CString& user_sid, |
| const CString& app_guid) { |
| return AppendRegKeyPath(GetUserAllAppsStatePath(user_sid), app_guid); |
| } |
| |
| // Returns the application registrration path for a particular user |
| // and for the given application id. |
| CString GetUserAppRegPath(const CString& user_sid, |
| const CString& app_guid) { |
| return AppendRegKeyPath(GetUserAllAppsRegPath(user_sid), app_guid); |
| } |
| |
| CString BuildGoogleUpdateExeDir(bool is_machine) { |
| ConfigManager& cm = *ConfigManager::Instance(); |
| return is_machine ? cm.GetMachineGoopdateInstallDir() : |
| cm.GetUserGoopdateInstallDir(); |
| } |
| |
| CString BuildGoogleUpdateExePath(bool is_machine) { |
| CORE_LOG(L3, (_T("[BuildGoogleUpdateExePath][%d]"), is_machine)); |
| |
| CPath full_file_path(BuildGoogleUpdateExeDir(is_machine)); |
| VERIFY1(full_file_path.Append(kGoopdateFileName)); |
| |
| return full_file_path; |
| } |
| |
| CString BuildGoogleUpdateServicesPath(bool is_machine) { |
| CORE_LOG(L3, (_T("[BuildGoogleUpdateServicesPath][%d]"), is_machine)); |
| |
| CPath full_file_path( |
| goopdate_utils::BuildInstallDirectory(is_machine, GetVersionString())); |
| VERIFY1(full_file_path.Append(kGoopdateCrashHandlerFileName)); |
| |
| return full_file_path; |
| } |
| |
| HRESULT StartElevatedSelfWithArgsAndWait(const TCHAR* args) { |
| ASSERT1(args); |
| CORE_LOG(L3, (_T("[StartElevatedSelfWithArgsAndWait]"))); |
| |
| // Get the process executable. |
| TCHAR filename[MAX_PATH] = {0}; |
| if (::GetModuleFileName(NULL, filename, MAX_PATH) == 0) { |
| HRESULT hr = HRESULTFromLastError(); |
| CORE_LOG(LEVEL_ERROR, (_T("[GetModuleFileName failed][0x%08x]"), hr)); |
| return hr; |
| } |
| |
| // Launch self elevated and wait. |
| DWORD exit_code = 0; |
| CORE_LOG(L1, |
| (_T("[RunElevated filename='%s'][arguments='%s']"), filename, args)); |
| // According to the MSDN documentation for ::ShowWindow: "nCmdShow. This |
| // parameter is ignored the first time an application calls ShowWindow, if |
| // the program that launched the application provides a STARTUPINFO |
| // structure.". We want to force showing the UI window. So we pass in |
| // SW_SHOWNORMAL. |
| HRESULT hr = vista_util::RunElevated(filename, |
| args, |
| SW_SHOWNORMAL, |
| &exit_code); |
| CORE_LOG(L2, (_T("[elevated instance exit code][%u]"), exit_code)); |
| if (FAILED(hr)) { |
| CORE_LOG(LEVEL_ERROR, (_T("[RunElevated failed][0x%08x]"), hr)); |
| return hr; |
| } |
| |
| return S_OK; |
| } |
| |
| HRESULT StartGoogleUpdateWithArgs(bool is_machine, |
| const TCHAR* args, |
| HANDLE* process) { |
| CORE_LOG(L3, (_T("[StartGoogleUpdateWithArgs][%d][%s]"), |
| is_machine, args ? args : _T(""))); |
| |
| CString exe_path = BuildGoogleUpdateExePath(is_machine); |
| |
| CORE_LOG(L3, (_T("[command line][%s][%s]"), exe_path, args ? args : _T(""))); |
| |
| HRESULT hr = System::ShellExecuteProcess(exe_path, args, NULL, process); |
| if (FAILED(hr)) { |
| CORE_LOG(LE, (_T("[can't start process][%s][0x%08x]"), exe_path, hr)); |
| return hr; |
| } |
| return S_OK; |
| } |
| |
| bool IsRunningFromOfficialGoopdateDir(bool is_machine) { |
| const ConfigManager& cm = *ConfigManager::Instance(); |
| bool is_official_dir = is_machine ? |
| cm.IsRunningFromMachineGoopdateInstallDir() : |
| cm.IsRunningFromUserGoopdateInstallDir(); |
| CORE_LOG(L3, (_T("[running from official dir][%d]"), is_official_dir)); |
| return is_official_dir; |
| } |
| |
| CString GetHKRoot() { |
| return IsRunningFromOfficialGoopdateDir(true) ? _T("HKLM") : _T("HKCU"); |
| } |
| |
| HRESULT InitializeSecurity() { |
| // Creates a security descriptor in absolute format and includes the owner |
| // and the primary group. We grant access to admins and system. |
| CSecurityDesc security_descriptor; |
| if (SystemInfo::IsRunningOnVistaOrLater()) { |
| // To allow for low-integrity IE to call into IGoogleUpdate. |
| security_descriptor.FromString(LOW_INTEGRITY_SDDL_SACL); |
| } |
| security_descriptor.SetOwner(Sids::Admins()); |
| security_descriptor.SetGroup(Sids::Admins()); |
| CDacl dacl; |
| dacl.AddAllowedAce(Sids::System(), COM_RIGHTS_EXECUTE); |
| dacl.AddAllowedAce(Sids::Admins(), COM_RIGHTS_EXECUTE); |
| dacl.AddAllowedAce(Sids::AuthenticatedUser(), COM_RIGHTS_EXECUTE); |
| |
| security_descriptor.SetDacl(dacl); |
| security_descriptor.MakeAbsolute(); |
| |
| SECURITY_DESCRIPTOR* sd = const_cast<SECURITY_DESCRIPTOR*>( |
| security_descriptor.GetPSECURITY_DESCRIPTOR()); |
| |
| return ::CoInitializeSecurity( |
| sd, |
| -1, |
| NULL, // Let COM choose what authentication services to register. |
| NULL, |
| RPC_C_AUTHN_LEVEL_PKT_PRIVACY, // Data integrity and encryption. |
| RPC_C_IMP_LEVEL_IDENTIFY, // Only allow a server to identify. |
| NULL, |
| EOAC_DYNAMIC_CLOAKING | EOAC_DISABLE_AAA | EOAC_NO_CUSTOM_MARSHAL, |
| NULL); |
| } |
| |
| // This is only used for legacy handoff support. |
| CString GetProductName(const CString& app_guid) { |
| const TCHAR* product_name = NULL; |
| const TCHAR gears_guid[] = _T("{283EAF47-8817-4c2b-A801-AD1FADFB7BAA}"); |
| const TCHAR google_talk_plugin[] = |
| _T("{D0AB2EBC-931B-4013-9FEB-C9C4C2225C8C}"); |
| const TCHAR youtube_uploader_guid[] = |
| _T("{A4F7B07B-B9BD-4a33-B136-96D2ADFB60CB}"); |
| |
| if (app_guid.CompareNoCase(gears_guid) == 0) { |
| product_name = _T("Gears"); |
| } else if (app_guid.CompareNoCase(google_talk_plugin) == 0) { |
| product_name = _T("Google Talk Plugin"); |
| } else if (app_guid.CompareNoCase(youtube_uploader_guid) == 0) { |
| product_name = _T("YouTube Uploader"); |
| } else { |
| product_name = _T("Google App"); |
| } |
| return product_name; |
| } |
| |
| HRESULT ReadPersistentId(const CString& key_name, |
| const CString& value_name, |
| CString* id) { |
| ASSERT1(id); |
| CString key_path = AppendRegKeyPath(key_name, GOOPDATE_MAIN_KEY); |
| return RegKey::GetValue(key_path, value_name, id); |
| } |
| |
| HRESULT BuildHttpGetString(const CString& url, |
| DWORD error_code, |
| DWORD extra_code1, |
| DWORD extra_code2, |
| const CString& app_guid, |
| const CString& goopdate_version, |
| bool is_machine, |
| const CString& language, |
| const GUID& iid, |
| const CString& brand_code, |
| const CString& source_id, |
| CString* get_request) { |
| ASSERT1(get_request); |
| if (url.IsEmpty()) { |
| return E_INVALIDARG; |
| } |
| ASSERT1(_T('?') == url.GetAt(url.GetLength() - 1) || |
| _T('&') == url.GetAt(url.GetLength() - 1)); |
| |
| CString errorcode_str; |
| CString extracode1_str; |
| CString extracode2_str; |
| errorcode_str.Format(_T("0x%08x"), error_code); |
| extracode1_str.Format(_T("0x%08x"), extra_code1); |
| extracode2_str.Format(_T("%u"), extra_code2); |
| |
| CString os_version; |
| CString service_pack; |
| HRESULT hr = GetOSInfo(&os_version, &service_pack); |
| if (FAILED(hr)) { |
| CORE_LOG(LEVEL_WARNING, (_T("[GetOSInfo failed][0x%08x]"), hr)); |
| } |
| const CString iid_string = ::IsEqualGUID(GUID_NULL, iid) ? _T("") : |
| GuidToString(iid); |
| |
| std::vector<QueryElement> elements; |
| elements.push_back(QueryElement(_T("hl"), language)); |
| elements.push_back(QueryElement(_T("errorcode"), errorcode_str)); |
| elements.push_back(QueryElement(_T("extracode1"), extracode1_str)); |
| elements.push_back(QueryElement(_T("extracode2"), extracode2_str)); |
| elements.push_back(QueryElement(_T("app"), app_guid)); |
| elements.push_back(QueryElement(_T("guver"), goopdate_version)); |
| elements.push_back(QueryElement(_T("ismachine"), |
| is_machine ? _T("1") : _T("0"))); |
| elements.push_back(QueryElement(_T("os"), os_version)); |
| elements.push_back(QueryElement(_T("sp"), service_pack)); |
| elements.push_back(QueryElement(_T("iid"), iid_string)); |
| elements.push_back(QueryElement(_T("brand"), brand_code)); |
| elements.push_back(QueryElement(_T("source"), source_id)); |
| |
| CString test_source = ConfigManager::Instance()->GetTestSource(); |
| if (!test_source.IsEmpty()) { |
| elements.push_back(QueryElement(_T("testsource"), test_source)); |
| } |
| |
| CString query; |
| hr = BuildQueryString(elements, &query); |
| if (FAILED(hr)) { |
| CORE_LOG(LEVEL_WARNING, (_T("[BuildQueryString failed][0x%08x]"), hr)); |
| return hr; |
| } |
| get_request->Format(_T("%s%s"), url, query); |
| |
| // The length should be smaller than the maximum allowed get length. |
| ASSERT1(get_request->GetLength() <= INTERNET_MAX_URL_LENGTH); |
| if (get_request->GetLength() > INTERNET_MAX_URL_LENGTH) { |
| return E_FAIL; |
| } |
| |
| return S_OK; |
| } |
| |
| HRESULT RedirectHKCR(bool is_machine) { |
| RegKey classes_key; |
| HRESULT hr = classes_key.Open(is_machine ? |
| HKEY_LOCAL_MACHINE : |
| HKEY_CURRENT_USER, |
| _T("Software\\Classes"), |
| KEY_ALL_ACCESS); |
| if (FAILED(hr)) { |
| ASSERT(FALSE, (_T("RedirectHKCR - key.Open(%d) fail %d"), is_machine, hr)); |
| return hr; |
| } |
| |
| LONG result = ::RegOverridePredefKey(HKEY_CLASSES_ROOT, classes_key.Key()); |
| if (result != ERROR_SUCCESS) { |
| ASSERT(false, (_T("RedirectHKCR - RegOverridePredefKey fail %d"), result)); |
| return HRESULT_FROM_WIN32(result); |
| } |
| |
| return S_OK; |
| } |
| |
| HRESULT RemoveRedirectHKCR() { |
| LONG result = ::RegOverridePredefKey(HKEY_CLASSES_ROOT, NULL); |
| if (result != ERROR_SUCCESS) { |
| ASSERT(FALSE, (_T("RemoveRedirectHKCR - RegOverridePredefKey %d"), result)); |
| return HRESULT_FROM_WIN32(result); |
| } |
| |
| return S_OK; |
| } |
| |
| HRESULT RegisterTypeLib(bool is_admin, |
| const CComBSTR& path, |
| ITypeLib* type_lib) { |
| // Typelib registration. |
| CORE_LOG(L3, (_T("[Registering TypeLib]"))); |
| HRESULT hr = S_OK; |
| if (!is_admin && |
| SUCCEEDED(goopdate_utils::RegisterTypeLibForUser(type_lib, path, NULL))) { |
| return S_OK; |
| } |
| |
| // For Admin cases, we use ::RegisterTypeLib(). |
| // For platforms where ::RegisterTypeLibForUser is not available, we register |
| // with ::RegisterTypeLib, and rely on HKCR=>HKCU redirection. |
| hr = ::RegisterTypeLib(type_lib, path, NULL); |
| ASSERT(SUCCEEDED(hr), (_T("[TypeLib registration failed][0x%08x]"), hr)); |
| return hr; |
| } |
| |
| HRESULT UnRegisterTypeLib(bool is_admin, const CComBSTR&, ITypeLib* type_lib) { |
| // Typelib unregistration. |
| CORE_LOG(L3, (_T("[Unregistering Typelib]"))); |
| TLIBATTR* tlib_attr = NULL; |
| HRESULT hr = type_lib->GetLibAttr(&tlib_attr); |
| ASSERT(SUCCEEDED(hr), (_T("[GetLibAttr failed][0x%08x]"), hr)); |
| if (FAILED(hr)) { |
| return hr; |
| } |
| ON_SCOPE_EXIT_OBJ(*type_lib, &ITypeLib::ReleaseTLibAttr, tlib_attr); |
| |
| if (!is_admin && |
| SUCCEEDED(goopdate_utils::UnRegisterTypeLibForUser( |
| tlib_attr->guid, |
| tlib_attr->wMajorVerNum, |
| tlib_attr->wMinorVerNum, |
| tlib_attr->lcid, |
| tlib_attr->syskind))) { |
| return S_OK; |
| } |
| |
| // For Admin cases, we use ::UnRegisterTypeLib(). |
| // For platforms where ::UnRegisterTypeLibForUser is not available, we |
| // unregister with ::UnRegisterTypeLib, and rely on HKCR=>HKCU redirection. |
| hr = ::UnRegisterTypeLib(tlib_attr->guid, |
| tlib_attr->wMajorVerNum, |
| tlib_attr->wMinorVerNum, |
| tlib_attr->lcid, |
| tlib_attr->syskind); |
| |
| // We assert before the check for TYPE_E_REGISTRYACCESS below because we want |
| // to catch the case where we're trying to unregister more than once because |
| // that would be a bug. |
| ASSERT(SUCCEEDED(hr), |
| (_T("[UnRegisterTypeLib failed. ") |
| _T("This is likely a multiple unregister bug.][0x%08x]"), hr)); |
| |
| // If you try to unregister a type library that's already unregistered, |
| // it will return with this failure, which is OK. |
| if (hr == TYPE_E_REGISTRYACCESS) { |
| hr = S_OK; |
| } |
| |
| return hr; |
| } |
| |
| HRESULT RegisterOrUnregisterModule(bool register_server, |
| RegisterOrUnregisterFunction registrar) { |
| ASSERT1(registrar); |
| |
| bool is_machine = IsRunningFromOfficialGoopdateDir(true); |
| // ATL by default registers the control to HKCR and we want to register |
| // either in HKLM, or in HKCU, depending on whether we are laying down |
| // the system googleupdate, or the user googleupdate. |
| // We solve this for the user goopdate case by: |
| // * Having the RGS file take a HKROOT parameter that translates to either |
| // HKLM or HKCU. |
| // * Redirecting HKCR to HKCU\software\classes, for a user installation, to |
| // cover Proxy registration. |
| // For the machine case, we still redirect HKCR to HKLM\\Software\\Classes, |
| // to ensure that Proxy registration happens in HKLM. |
| HRESULT hr = RedirectHKCR(is_machine); |
| ASSERT1(SUCCEEDED(hr)); |
| if (FAILED(hr)) { |
| return hr; |
| } |
| // We need to stop redirecting at the end of this function. |
| ON_SCOPE_EXIT(RemoveRedirectHKCR); |
| |
| hr = (*registrar)(register_server); |
| if (FAILED(hr)) { |
| CORE_LOG(LW, (_T("[RegisterOrUnregisterModule failed][%d][0x%08x]"), |
| register_server, hr)); |
| ASSERT1(HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) == hr && !register_server); |
| } |
| |
| return hr; |
| } |
| |
| HRESULT RegisterOrUnregisterModuleWithTypelib( |
| bool register_server, |
| RegisterOrUnregisterFunction registrar) { |
| ASSERT1(registrar); |
| |
| bool is_machine = IsRunningFromOfficialGoopdateDir(true); |
| // By default, ATL registers the control to HKCR and we want to register |
| // either in HKLM, or in HKCU, depending on whether we are laying down |
| // the machine googleupdate, or the user googleupdate. |
| // We solve this for the user goopdate case by: |
| // * Having the RGS file take a HKROOT parameter that translates to either |
| // HKLM or HKCU. |
| // * Redirecting HKCR to HKCU\software\classes, for a user installation, to |
| // cover AppId and TypeLib registration |
| // * All the above makes ATL work correctly for 2K/XP. However on Win2K3 |
| // and Vista, redirection does not work by itself, because in these |
| // platforms, RegisterTypeLib writes explicitly to HKLM\Software\Classes. |
| // We need to specifically call the new RegisterTypeLibForUser() API. |
| // So, we do that as well. |
| // For the machine case, we still redirect HKCR to HKLM\\Software\\Classes, |
| // because otherwise RegisterTypeLib ends up overwriting HKCU if the key |
| // already exists in HKCU. |
| HRESULT hr = RedirectHKCR(is_machine); |
| ASSERT1(SUCCEEDED(hr)); |
| if (FAILED(hr)) { |
| return hr; |
| } |
| // We need to stop redirecting at the end of this function. |
| ON_SCOPE_EXIT(RemoveRedirectHKCR); |
| |
| // load the type library. |
| CComPtr<ITypeLib> type_lib; |
| CComBSTR path; |
| hr = ::AtlLoadTypeLib(_AtlBaseModule.GetModuleInstance(), NULL, &path, |
| &type_lib); |
| if (FAILED(hr)) { |
| ASSERT(false, (_T("[AtlLoadTypeLib failed][0x%08x]"), hr)); |
| return hr; |
| } |
| |
| if (register_server) { |
| hr = (*registrar)(register_server); |
| if (FAILED(hr)) { |
| ASSERT(false, (_T("[Module registration failed][0x%08x]"), hr)); |
| return hr; |
| } |
| |
| return RegisterTypeLib(is_machine, path, type_lib); |
| } else { |
| hr = UnRegisterTypeLib(is_machine, path, type_lib); |
| if (FAILED(hr)) { |
| ASSERT(false, (_T("[UnRegisterTypeLib failed][0x%08x]"), hr)); |
| return hr; |
| } |
| |
| return (*registrar)(register_server); |
| } |
| } |
| |
| HRESULT RegisterTypeLibForUser(ITypeLib* lib, |
| OLECHAR* path, |
| OLECHAR* help_dir) { |
| CORE_LOG(L3, (_T("[RegisterTypeLibForUser]"))); |
| ASSERT1(lib); |
| ASSERT1(path); |
| // help_dir can be NULL. |
| |
| const TCHAR* library_name = _T("oleaut32.dll"); |
| scoped_library module(static_cast<HINSTANCE>(::LoadLibrary(library_name))); |
| if (!module) { |
| HRESULT hr = HRESULTFromLastError(); |
| CORE_LOG(LEVEL_ERROR, |
| (_T("[LoadLibrary failed][%s][0x%08x]"), library_name, hr)); |
| return hr; |
| } |
| |
| // RegisterTypeLibForUser function from oleaut32.dll. |
| typedef HRESULT(__stdcall *PF)(ITypeLib*, OLECHAR*, OLECHAR*); |
| |
| const char* function_name = "RegisterTypeLibForUser"; |
| PF fp = reinterpret_cast<PF>(::GetProcAddress(get(module), function_name)); |
| if (!fp) { |
| HRESULT hr = HRESULTFromLastError(); |
| CORE_LOG(LEVEL_ERROR, |
| (_T("[GetProcAddress failed][%s][0x%08x]"), |
| function_name, library_name, hr)); |
| return hr; |
| } |
| |
| CORE_LOG(L3, (_T("[Calling RegisterTypelibForUser in oleaut]"))); |
| HRESULT hr = fp(lib, path, help_dir); |
| if (FAILED(hr)) { |
| CORE_LOG(LEVEL_ERROR, (_T("[regtypelib_for_user failed][0x%08x]"), hr)); |
| return hr; |
| } |
| |
| return S_OK; |
| } |
| |
| HRESULT UnRegisterTypeLibForUser(REFGUID lib_id, |
| WORD major_ver_num, |
| WORD minor_ver_num, |
| LCID lcid, |
| SYSKIND syskind) { |
| CORE_LOG(L3, (_T("[UnRegisterTypeLibForUser]"))); |
| |
| const TCHAR* library_name = _T("oleaut32.dll"); |
| scoped_library module(static_cast<HINSTANCE>(::LoadLibrary(library_name))); |
| if (!module) { |
| HRESULT hr = HRESULTFromLastError(); |
| CORE_LOG(LEVEL_ERROR, |
| (_T("[LoadLibrary failed][%s][0x%08x]"), library_name, hr)); |
| return hr; |
| } |
| |
| // UnRegisterTypeLibForUser function from oleaut32.dll. |
| typedef HRESULT (__stdcall *PF)(REFGUID, WORD, WORD, LCID, SYSKIND); |
| |
| const char* function_name = "UnRegisterTypeLibForUser"; |
| PF fp = reinterpret_cast<PF>(::GetProcAddress(get(module), function_name)); |
| if (!fp) { |
| HRESULT hr = HRESULTFromLastError(); |
| CORE_LOG(LEVEL_ERROR, |
| (_T("[GetProcAddress failed][%s][0x%08x]"), |
| function_name, library_name, hr)); |
| return hr; |
| } |
| |
| CORE_LOG(L3, (_T("[Calling UnRegisterTypeLibForUser in oleaut]"))); |
| HRESULT hr = fp(lib_id, major_ver_num, minor_ver_num, lcid, syskind); |
| if (FAILED(hr)) { |
| CORE_LOG(LEVEL_ERROR, (_T("[unregtypelib_for_user failed][0x%08x]"), hr)); |
| return hr; |
| } |
| |
| return S_OK; |
| } |
| |
| // This method assumes that the caller has permissions to open |
| // the process token of the user's explorer.exe process. |
| HRESULT GetImpersonationToken(bool is_interactive, |
| uint32 explorer_pid, |
| HANDLE* out_token) { |
| CORE_LOG(L3, (_T("[GetImpersonationToken]"))); |
| ASSERT1(out_token); |
| |
| // If the job is an interactive job, then we can use the explorer pid |
| // that is passed in for impersonation. If this is an |
| // update job, then we need to get a list of all the logged on users |
| // and pick one to perform the impersonation. |
| // TODO(omaha): Try to use the token of the user that is a domain account, |
| // since we are trying to solve the integrated proxy authentication issue. |
| // One way to do this might be to try the GetWindowsAccountDomainSid API. |
| if (is_interactive) { |
| scoped_handle exp(::OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, |
| false, explorer_pid)); |
| if (!exp) { |
| HRESULT hr = HRESULTFromLastError(); |
| CORE_LOG(LEVEL_ERROR, (_T("[OpenProcess failed][0x%08x]"), hr)); |
| return hr; |
| } |
| |
| if (!::OpenProcessToken(get(exp), |
| TOKEN_QUERY | TOKEN_IMPERSONATE | TOKEN_DUPLICATE, |
| out_token)) { |
| HRESULT hr = HRESULTFromLastError(); |
| CORE_LOG(LEVEL_ERROR, (_T("[OpenProcessToken failed][0x%08x]"), hr)); |
| return hr; |
| } |
| } else { |
| HRESULT hr = vista::GetExplorerTokenForLoggedInUser(out_token); |
| if (FAILED(hr)) { |
| CORE_LOG(LEVEL_ERROR, (_T("[GetExplorerTokenForLoggedInUser failed]") |
| _T("[0x%08x]"), hr)); |
| return hr; |
| } |
| } |
| |
| return S_OK; |
| } |
| |
| HRESULT UndoImpersonation(bool impersonated) { |
| CORE_LOG(L3, (_T("[UndoImpersonation]"))); |
| |
| if (impersonated && !::RevertToSelf()) { |
| // TODO(omaha): For now we assume that this never fails, change this |
| // impersonation to occur on a different thread, so that even if this fails, |
| // we can simply kill the thread. |
| // If this function call fails, we have a problem. We need to shut down the |
| // googleupdate process, since we are now running the system googleupdate |
| // as the user, and a number of assumptions about the code will fail. |
| HRESULT hr = HRESULTFromLastError(); |
| CORE_LOG(LEVEL_ERROR, (_T("[RevertToSelf failed][0x%08x]"), hr)); |
| return hr; |
| } |
| |
| return S_OK; |
| } |
| |
| HRESULT ImpersonateUser(bool is_interactive, uint32 explorer_pid) { |
| CORE_LOG(L3, (_T("[ImpersonateUser]"))); |
| |
| HANDLE handle = NULL; |
| HRESULT hr = GetImpersonationToken(is_interactive, explorer_pid, &handle); |
| if (FAILED(hr)) { |
| CORE_LOG(LEVEL_ERROR, (_T("[GetImpersonationToken failed][0x%08x]"), hr)); |
| return hr; |
| } |
| ASSERT1(handle); |
| |
| // TODO(omaha): The impersonation will fail if the user is running on a |
| // Win2K SP3 or earlier, or WinXP SP1 or earlier. This is because the |
| // seImpersonateProvilage is needed to call this method, and this privilege |
| // does not exist in these systems. |
| // One way to work around this would be to launch a separate process |
| // using CreateProcessAsUser on these systems. |
| scoped_handle token(handle); |
| if (!::ImpersonateLoggedOnUser(get(token))) { |
| hr = HRESULTFromLastError(); |
| CORE_LOG(LEVEL_ERROR, (_T("[ImpersonateLoggedOnUser failed][0x%08x]"), hr)); |
| return hr; |
| } |
| |
| return S_OK; |
| } |
| |
| // The EULA is assumed to be accepted unless eualaccepted=0 in the ClientState |
| // key. For machine apps in this case, eulaccepted=1 in ClientStateMedium also |
| // indicates acceptance and the value in ClientState is updated. |
| bool IsAppEulaAccepted(bool is_machine, |
| const CString& app_guid, |
| bool require_explicit_acceptance) { |
| const CString state_key = GetAppClientStateKey(is_machine, app_guid); |
| |
| DWORD eula_accepted = 0; |
| if (SUCCEEDED(RegKey::GetValue(state_key, |
| kRegValueEulaAccepted, |
| &eula_accepted))) { |
| if (0 != eula_accepted) { |
| return true; |
| } |
| } else { |
| if (!require_explicit_acceptance) { |
| return true; |
| } |
| } |
| |
| if (!is_machine) { |
| return false; |
| } |
| |
| eula_accepted = 0; |
| if (SUCCEEDED(RegKey::GetValue( |
| GetAppClientStateMediumKey(is_machine, app_guid), |
| kRegValueEulaAccepted, |
| &eula_accepted))) { |
| if (0 == eula_accepted) { |
| return false; |
| } |
| } else { |
| return false; |
| } |
| |
| VERIFY1(SUCCEEDED(RegKey::SetValue(state_key, |
| kRegValueEulaAccepted, |
| eula_accepted))); |
| return true; |
| } |
| |
| // Does not need to set ClientStateMedium. |
| HRESULT SetAppEulaNotAccepted(bool is_machine, const CString& app_guid) { |
| return RegKey::SetValue(GetAppClientStateKey(is_machine, app_guid), |
| kRegValueEulaAccepted, |
| static_cast<DWORD>(0)); |
| } |
| |
| // Deletes eulaaccepted from ClientState and ClientStateMedium. |
| HRESULT ClearAppEulaNotAccepted(bool is_machine, const CString& app_guid) { |
| const CString state_key = GetAppClientStateKey(is_machine, app_guid); |
| if (RegKey::HasKey(state_key)) { |
| HRESULT hr = RegKey::DeleteValue(state_key, kRegValueEulaAccepted); |
| if (FAILED(hr)) { |
| return hr; |
| } |
| } |
| |
| if (!is_machine) { |
| return S_OK; |
| } |
| |
| const CString state_medium_key = |
| GetAppClientStateMediumKey(is_machine, app_guid); |
| if (RegKey::HasKey(state_medium_key)) { |
| HRESULT hr = RegKey::DeleteValue(state_medium_key, kRegValueEulaAccepted); |
| if (FAILED(hr)) { |
| return hr; |
| } |
| } |
| |
| return S_OK; |
| } |
| |
| // For machine apps, ClientStateMedium takes precedence. |
| // Does not propogate the ClientStateMedium value to ClientState. |
| bool AreAppUsageStatsEnabled(bool is_machine, const CString& app_guid) { |
| if (is_machine) { |
| DWORD stats_enabled = 0; |
| if (SUCCEEDED(RegKey::GetValue(GetAppClientStateMediumKey(is_machine, |
| app_guid), |
| kRegValueUsageStats, |
| &stats_enabled))) { |
| return (TRISTATE_TRUE == stats_enabled); |
| } |
| } |
| |
| DWORD stats_enabled = 0; |
| if (SUCCEEDED(RegKey::GetValue(GetAppClientStateKey(is_machine, app_guid), |
| kRegValueUsageStats, |
| &stats_enabled))) { |
| return (TRISTATE_TRUE == stats_enabled); |
| } |
| |
| return false; |
| } |
| |
| // Does nothing if usage_stats_enable is TRISTATE_NONE. |
| // For machine apps, clears ClientStateMedium because the app may be reading it |
| // if present. |
| HRESULT SetUsageStatsEnable(bool is_machine, |
| const CString& app_guid, |
| Tristate usage_stats_enable) { |
| if (TRISTATE_NONE == usage_stats_enable) { |
| return S_OK; |
| } |
| |
| const DWORD stats_enabled = (TRISTATE_TRUE == usage_stats_enable) ? 1 : 0; |
| |
| HRESULT hr = RegKey::SetValue(GetAppClientStateKey(is_machine, app_guid), |
| kRegValueUsageStats, |
| stats_enabled); |
| if (FAILED(hr)) { |
| CORE_LOG(LW, (_T("[Failed to set usagestats][0x%08x]"), hr)); |
| return hr; |
| } |
| |
| if (!is_machine) { |
| return S_OK; |
| } |
| |
| const CString state_medium_key = |
| GetAppClientStateMediumKey(is_machine, app_guid); |
| if (RegKey::HasKey(state_medium_key)) { |
| hr = RegKey::DeleteValue(state_medium_key, kRegValueUsageStats); |
| if (FAILED(hr)) { |
| return hr; |
| } |
| } |
| |
| return S_OK; |
| } |
| |
| // Writes usagestats in Google Update's ClientState key. This is the only case |
| // in which usagestats is written to Google Update's ClientState key. Normally, |
| // we write to the ClientState key(s) for the specific app(s). |
| HRESULT ConvertLegacyUsageStats(bool is_machine) { |
| DWORD existing_usage_stats(0); |
| |
| ConfigManager& config_mgr = *ConfigManager::Instance(); |
| const CString legacy_key_name = config_mgr.registry_update(is_machine); |
| if (FAILED(RegKey::GetValue(legacy_key_name, |
| kLegacyRegValueCollectUsageStats, |
| &existing_usage_stats))) { |
| return S_OK; |
| } |
| |
| const CString new_key_name = GetAppClientStateKey(is_machine, |
| kGoogleUpdateAppId); |
| HRESULT hr = RegKey::SetValue(new_key_name, |
| kRegValueUsageStats, |
| existing_usage_stats); |
| if (FAILED(hr)) { |
| return hr; |
| } |
| |
| VERIFY1(SUCCEEDED(RegKey::DeleteValue(legacy_key_name, |
| kLegacyRegValueCollectUsageStats))); |
| return S_OK; |
| } |
| |
| CString GetDefaultGoopdateTaskName(bool is_machine, CommandLineMode mode) { |
| ASSERT1(mode == COMMANDLINE_MODE_CORE || mode == COMMANDLINE_MODE_UA); |
| |
| CString task_name; |
| if (is_machine) { |
| task_name = kScheduledTaskNameMachinePrefix; |
| } else { |
| task_name = kScheduledTaskNameUserPrefix; |
| CString user_sid; |
| VERIFY1(SUCCEEDED(user_info::GetCurrentUser(NULL, NULL, &user_sid))); |
| task_name += user_sid; |
| } |
| |
| task_name += (mode == COMMANDLINE_MODE_CORE) ? kScheduledTaskNameCoreSuffix : |
| kScheduledTaskNameUASuffix; |
| return task_name; |
| } |
| |
| HRESULT InstallGoopdateTaskForMode(const TCHAR* task_path, |
| bool is_machine, |
| CommandLineMode mode) { |
| ASSERT1(mode == COMMANDLINE_MODE_CORE || mode == COMMANDLINE_MODE_UA); |
| |
| CommandLineBuilder builder(mode); |
| if (mode == COMMANDLINE_MODE_UA) { |
| builder.set_install_source(kCmdLineInstallSourceScheduler); |
| } |
| |
| CString task_description; |
| VERIFY1(task_description.LoadString(IDS_SCHEDULED_TASK_DESCRIPTION)); |
| |
| CString task_name(mode == COMMANDLINE_MODE_CORE ? |
| ConfigManager::GetCurrentTaskNameCore(is_machine) : |
| ConfigManager::GetCurrentTaskNameUA(is_machine)); |
| if (internal::IsInstalledScheduledTask(task_name)) { |
| HRESULT hr = internal::InstallScheduledTask(task_name, |
| task_path, |
| builder.GetCommandLineArgs(), |
| task_description, |
| is_machine, |
| mode == COMMANDLINE_MODE_CORE && |
| is_machine, |
| true, |
| mode == COMMANDLINE_MODE_UA); |
| |
| if (SUCCEEDED(hr)) { |
| return hr; |
| } |
| |
| // Try to uninstall the task that we failed to upgrade. Then create a new |
| // task name, and fall through to install that. |
| internal::UninstallScheduledTask(task_name); |
| if (mode == COMMANDLINE_MODE_CORE) { |
| VERIFY1(SUCCEEDED( |
| ConfigManager::CreateAndSetVersionedTaskNameCoreInRegistry(is_machine))); |
| task_name = ConfigManager::GetCurrentTaskNameCore(is_machine); |
| } else { |
| VERIFY1(SUCCEEDED( |
| ConfigManager::CreateAndSetVersionedTaskNameUAInRegistry(is_machine))); |
| task_name = ConfigManager::GetCurrentTaskNameUA(is_machine); |
| } |
| ASSERT1(!internal::IsInstalledScheduledTask(task_name)); |
| } |
| |
| return internal::InstallScheduledTask(task_name, |
| task_path, |
| builder.GetCommandLineArgs(), |
| task_description, |
| is_machine, |
| mode == COMMANDLINE_MODE_CORE && |
| is_machine, |
| true, |
| mode == COMMANDLINE_MODE_UA); |
| } |
| |
| HRESULT InstallGoopdateTasks(const TCHAR* task_path, bool is_machine) { |
| HRESULT hr = InstallGoopdateTaskForMode(task_path, |
| is_machine, |
| COMMANDLINE_MODE_CORE); |
| if (FAILED(hr)) { |
| return hr; |
| } |
| |
| return InstallGoopdateTaskForMode(task_path, is_machine, COMMANDLINE_MODE_UA); |
| } |
| |
| HRESULT UninstallGoopdateTasks(bool is_machine) { |
| VERIFY1(SUCCEEDED(internal::UninstallScheduledTask( |
| ConfigManager::GetCurrentTaskNameCore(is_machine)))); |
| VERIFY1(SUCCEEDED(internal::UninstallScheduledTask( |
| ConfigManager::GetCurrentTaskNameUA(is_machine)))); |
| |
| // Try to uninstall any tasks that we failed to update during a previous |
| // overinstall. It is possible that we fail to uninstall these again here. |
| VERIFY1(SUCCEEDED(internal::UninstallScheduledTasks( |
| goopdate_utils::GetDefaultGoopdateTaskName(is_machine, |
| COMMANDLINE_MODE_CORE)))); |
| VERIFY1(SUCCEEDED(internal::UninstallScheduledTasks( |
| goopdate_utils::GetDefaultGoopdateTaskName(is_machine, |
| COMMANDLINE_MODE_UA)))); |
| return S_OK; |
| } |
| |
| HRESULT UninstallLegacyGoopdateTasks(bool is_machine) { |
| const CString& legacy_omaha1_task = |
| internal::GetOmaha1LegacyTaskName(is_machine); |
| VERIFY1(SUCCEEDED(internal::UninstallScheduledTask(legacy_omaha1_task))); |
| |
| const CString& legacy_omaha2_task = |
| internal::GetOmaha2LegacyTaskName(is_machine); |
| VERIFY1(SUCCEEDED(internal::UninstallScheduledTask(legacy_omaha2_task))); |
| |
| return S_OK; |
| } |
| |
| HRESULT StartGoopdateTaskCore(bool is_machine) { |
| return internal::StartScheduledTask( |
| ConfigManager::GetCurrentTaskNameCore(is_machine)); |
| } |
| |
| bool IsInstalledGoopdateTaskUA(bool is_machine) { |
| return internal::IsInstalledScheduledTask( |
| ConfigManager::GetCurrentTaskNameUA(is_machine)); |
| } |
| |
| bool IsDisabledGoopdateTaskUA(bool is_machine) { |
| const CString& task_name(ConfigManager::GetCurrentTaskNameUA(is_machine)); |
| return internal::GetScheduledTaskStatus(task_name) == SCHED_S_TASK_DISABLED; |
| } |
| |
| HRESULT GetExitCodeGoopdateTaskUA(bool is_machine) { |
| const CString& task_name(ConfigManager::GetCurrentTaskNameUA(is_machine)); |
| return internal::GetScheduledTaskExitCode(task_name); |
| } |
| |
| HRESULT GetClientsStringValueFromRegistry(bool is_machine, |
| const CString& app_guid, |
| const CString& value_name, |
| CString* value) { |
| CORE_LOG(L3, (_T("[GetClientsStringValueFromRegistry][%d][%s][%s]"), |
| is_machine, app_guid, value_name)); |
| |
| ASSERT1(value); |
| |
| CString app_client_key_name = GetAppClientsKey(is_machine, app_guid); |
| |
| return RegKey::GetValue(app_client_key_name, value_name, value); |
| } |
| |
| HRESULT GetVerFromRegistry(bool is_machine, |
| const CString& app_guid, |
| CString* version) { |
| ASSERT1(version); |
| return GetClientsStringValueFromRegistry(is_machine, |
| app_guid, |
| kRegValueProductVersion, |
| version); |
| } |
| |
| HRESULT TerminateAllBrowsers( |
| BrowserType type, |
| TerminateBrowserResult* browser_res, |
| TerminateBrowserResult* default_res) { |
| UTIL_LOG(L3, (_T("[TerminateAllBrowsers][%d]"), type)); |
| ASSERT1(default_res); |
| ASSERT1(browser_res); |
| |
| if (type == BROWSER_UNKNOWN || |
| type == BROWSER_DEFAULT || |
| type >= BROWSER_MAX) { |
| ASSERT1(false); |
| return E_INVALIDARG; |
| } |
| |
| const BrowserType kFirstBrowser = BROWSER_IE; |
| const int kNumSupportedBrowsers = BROWSER_MAX - kFirstBrowser; |
| |
| BrowserType default_type = BROWSER_UNKNOWN; |
| HRESULT hr = GetDefaultBrowserType(&default_type); |
| if (FAILED(hr)) { |
| UTIL_LOG(LW, (_T("[GetDefaultBrowserType failed][0x%08x]"), hr)); |
| return hr; |
| } |
| |
| TerminateBrowserResult terminate_results[kNumSupportedBrowsers]; |
| |
| for (int browser = 0; browser < kNumSupportedBrowsers; ++browser) { |
| const BrowserType browser_type = |
| static_cast<BrowserType>(kFirstBrowser + browser); |
| hr = TerminateBrowserProcess(browser_type, |
| CString(), |
| 0, |
| &terminate_results[browser].found); |
| if (FAILED(hr)) { |
| UTIL_LOG(LW, (_T("[TerminateBrowserProcess failed][%u][0x%08x]"), |
| browser_type, hr)); |
| } |
| } |
| |
| // Now wait for the all browser instances to die. |
| // TODO(omaha): Wait for all processes at once rather than waiting for |
| // (kTerminateBrowserTimeoutMs * # supported browsers) ms. |
| for (int browser = 0; browser < kNumSupportedBrowsers; ++browser) { |
| const BrowserType browser_type = |
| static_cast<BrowserType>(kFirstBrowser + browser); |
| hr = WaitForBrowserToDie(browser_type, |
| CString(), |
| kTerminateBrowserTimeoutMs); |
| if (FAILED(hr)) { |
| UTIL_LOG(LW, (_T("[WaitForBrowserToDie failed][%u][0x%08x]"), |
| browser_type, hr)); |
| } else { |
| terminate_results[browser].could_terminate = true; |
| } |
| } |
| |
| *browser_res = terminate_results[type - kFirstBrowser]; |
| *default_res = terminate_results[default_type - kFirstBrowser]; |
| |
| return S_OK; |
| } |
| |
| // default_type can be BROWSER_UNKNOWN. |
| // If browsers that must be closed could not be terminated, false is returned. |
| // This method and TerminateBrowserProcesses assume the user did not shutdown |
| // the specified browser. They restart and shutdown, respectively, the default |
| // browser when the specified browser is not found. The reason for this may have |
| // been that the the specified (stamped) browser could be in a bad state on the |
| // machine and trying to start it would fail. |
| // This may also be required to support hosted cases (i.e. AOL and Maxthon). |
| // TODO(omaha): If we assume the stamped browser is okay, check whether the |
| // specified browser is installed rather than relying on whether the browser was |
| // running. It is perfectly valid for the browser to not be running. |
| // TODO(omaha): Why not try the default browser if browsers that require |
| // shutdown failed to terminate. |
| bool GetBrowserToRestart(BrowserType type, |
| BrowserType default_type, |
| const TerminateBrowserResult& res, |
| const TerminateBrowserResult& def_res, |
| BrowserType* browser_type) { |
| ASSERT1(browser_type); |
| ASSERT1(type != BROWSER_UNKNOWN && |
| type != BROWSER_DEFAULT && |
| type < BROWSER_MAX); |
| ASSERT1(default_type != BROWSER_DEFAULT && default_type < BROWSER_MAX); |
| UTIL_LOG(L3, (_T("[GetBrowserToRestart][%d]"), type)); |
| |
| *browser_type = BROWSER_UNKNOWN; |
| |
| if (res.found) { |
| switch (type) { |
| case BROWSER_IE: |
| *browser_type = BROWSER_IE; |
| return true; |
| case BROWSER_FIREFOX: // Only one process. |
| case BROWSER_CHROME: // One process per plug-in, even for upgrades. |
| if (res.could_terminate) { |
| *browser_type = type; |
| return true; |
| } |
| return false; |
| case BROWSER_UNKNOWN: |
| case BROWSER_DEFAULT: |
| case BROWSER_MAX: |
| default: |
| break; |
| } |
| } |
| |
| // We did not find the browser that we wanted to restart. Hence we need to |
| // determine if we could shutdown the default browser. |
| switch (default_type) { |
| case BROWSER_IE: |
| *browser_type = BROWSER_IE; |
| return true; |
| case BROWSER_FIREFOX: |
| case BROWSER_CHROME: |
| if (!def_res.found || def_res.found && def_res.could_terminate) { |
| *browser_type = default_type; |
| return true; |
| } |
| break; |
| case BROWSER_UNKNOWN: |
| case BROWSER_DEFAULT: |
| case BROWSER_MAX: |
| default: |
| break; |
| } |
| |
| return false; |
| } |
| |
| // See the comments about the default browser above GetBrowserToRestart. |
| HRESULT TerminateBrowserProcesses(BrowserType type, |
| TerminateBrowserResult* browser_res, |
| TerminateBrowserResult* default_res) { |
| UTIL_LOG(L3, (_T("[TerminateBrowserProcesses][%d]"), type)); |
| ASSERT1(browser_res); |
| ASSERT1(default_res); |
| |
| browser_res->could_terminate = false; |
| default_res->could_terminate = false; |
| |
| if (type == BROWSER_UNKNOWN || |
| type == BROWSER_DEFAULT || |
| type >= BROWSER_MAX) { |
| ASSERT1(false); |
| return E_UNEXPECTED; |
| } |
| |
| CString sid; |
| HRESULT hr = user_info::GetCurrentUser(NULL, NULL, &sid); |
| if (FAILED(hr)) { |
| UTIL_LOG(LE, (_T("[GetCurrentUser failed][0x%08x]"), hr)); |
| return hr; |
| } |
| |
| hr = TerminateBrowserProcess(type, |
| sid, |
| kTerminateBrowserTimeoutMs, |
| &browser_res->found); |
| if (FAILED(hr)) { |
| UTIL_LOG(LW, (_T("[TerminateBrowserProcess failed][0x%08x]"), hr)); |
| } else { |
| browser_res->could_terminate = true; |
| } |
| |
| // Since no instances of the browser type exist, we try to find and kill |
| // all instances of the default browser. |
| if (!browser_res->found) { |
| // We dont want to try and terminate the default browser, if it is the |
| // same as the browser that we tried above. |
| |
| BrowserType default_type = BROWSER_UNKNOWN; |
| hr = GetDefaultBrowserType(&default_type); |
| if (FAILED(hr)) { |
| UTIL_LOG(LW, (_T("[GetDefaultBrowserType failed][0x%08x]"), hr)); |
| } |
| |
| UTIL_LOG(L3, (_T("[Trying to kill the default browser %d]"), default_type)); |
| if (default_type != type) { |
| hr = TerminateBrowserProcess(BROWSER_DEFAULT, |
| sid, |
| kTerminateBrowserTimeoutMs, |
| &default_res->found); |
| if (FAILED(hr)) { |
| UTIL_LOG(LW, (_T("[TerminateBrowserProcess failed][0x%08x]"), hr)); |
| } else { |
| default_res->could_terminate = true; |
| } |
| } |
| } |
| |
| return hr; |
| } |
| |
| HRESULT StartBrowserWithProcessToken(bool is_machine, |
| BrowserType type, |
| const CString& url, |
| uint32 explorer_pid) { |
| UTIL_LOG(L3, (_T("[StartBrowserWithProcessToken.]") |
| _T("[is_machine = %d][type = %d]"), is_machine, type)); |
| |
| // In case of machine goopdate we need to CreateProcessAsUser, else we ask |
| // the shell to create a new browser process. |
| if (is_machine) { |
| CString browser_path; |
| HRESULT hr = GetBrowserImagePath(type, &browser_path); |
| if (FAILED(hr)) { |
| UTIL_LOG(LEVEL_ERROR, (_T("[GetBrowserImagePath failed.][0x%08x]"), hr)); |
| return hr; |
| } |
| ASSERT1(!browser_path.IsEmpty()); |
| |
| CString command_line; |
| EnclosePath(&browser_path); |
| command_line.Format(_T("%s %s"), browser_path, url); |
| UTIL_LOG(L3, (_T("[Executing command line %s.]"), command_line)); |
| hr = vista::StartProcessWithTokenOfProcess(explorer_pid, command_line); |
| if (FAILED(hr)) { |
| UTIL_LOG(LW, (_T("[StartProcessWithTokenOfProcess failed][0x%08x]"), hr)); |
| return hr; |
| } |
| } else { |
| return ShellExecuteBrowser(type, url); |
| } |
| |
| return S_OK; |
| } |
| |
| HRESULT GetBrowserImagePathFromProcess(BrowserType type, |
| uint32 explorer_pid, |
| CString* path) { |
| ASSERT1(path); |
| |
| if (type == BROWSER_UNKNOWN || type >= BROWSER_MAX) { |
| ASSERT1(false); |
| return E_UNEXPECTED; |
| } |
| |
| if (type == BROWSER_DEFAULT) { |
| return GetDefaultBrowserPath(path); |
| } |
| |
| CString user_sid; |
| HRESULT hr = Process::GetProcessOwner(explorer_pid, &user_sid); |
| if (FAILED(hr)) { |
| UTIL_LOG(LEVEL_WARNING, (_T("[GetProcessOwner failed.][0x%08x]"), hr)); |
| return hr; |
| } |
| |
| CString browser_name; |
| hr = BrowserTypeToProcessName(type, &browser_name); |
| if (FAILED(hr)) { |
| UTIL_LOG(LW, (_T("[BrowserTypeToProcessName failed.][0x%08x]"), hr)); |
| return hr; |
| } |
| |
| hr = Process::GetImagePath(browser_name, user_sid, path); |
| if (FAILED(hr)) { |
| UTIL_LOG(LW, (_T("[GetImagePath failed.][0x%08x]"), hr)); |
| return hr; |
| } |
| |
| return S_OK; |
| } |
| |
| HRESULT ConvertStringToBrowserType(const CString& text, BrowserType* type) { |
| ASSERT1(type != NULL); |
| |
| if (text.GetLength() != 1) { |
| return GOOPDATEUTILS_E_BROWSERTYPE; |
| } |
| |
| int browser_type = 0; |
| if (!String_StringToDecimalIntChecked(text, &browser_type)) { |
| return GOOPDATEUTILS_E_BROWSERTYPE; |
| } |
| |
| if (browser_type >= BROWSER_MAX) { |
| return GOOPDATEUTILS_E_BROWSERTYPE; |
| } |
| |
| *type = static_cast<BrowserType>(browser_type); |
| return S_OK; |
| } |
| |
| CString ConvertBrowserTypeToString(BrowserType type) { |
| CString text = itostr(static_cast<int>(type)); |
| ASSERT1(!text.IsEmpty()); |
| return text; |
| } |
| |
| bool IsServiceInstalled() { |
| return ServiceInstall::IsServiceInstalled( |
| ConfigManager::GetCurrentServiceName()); |
| } |
| |
| HRESULT GetOSInfo(CString* os_version, CString* service_pack) { |
| ASSERT1(os_version); |
| ASSERT1(service_pack); |
| |
| OSVERSIONINFO os_version_info = { 0 }; |
| os_version_info.dwOSVersionInfoSize = sizeof(os_version_info); |
| if (!::GetVersionEx(&os_version_info)) { |
| HRESULT hr = HRESULTFromLastError(); |
| UTIL_LOG(LW, (_T("[GetVersionEx failed][0x%08x]"), hr)); |
| return hr; |
| } |
| |
| os_version->Format(_T("%d.%d"), |
| os_version_info.dwMajorVersion, |
| os_version_info.dwMinorVersion); |
| *service_pack = os_version_info.szCSDVersion; |
| return S_OK; |
| } |
| |
| CPath BuildInstallDirectory(bool is_machine, const CString& version) { |
| ConfigManager& cm = *ConfigManager::Instance(); |
| CPath install_dir(is_machine ? cm.GetMachineGoopdateInstallDir() : |
| cm.GetUserGoopdateInstallDir()); |
| VERIFY1(install_dir.Append(version)); |
| |
| return install_dir; |
| } |
| |
| HRESULT LaunchCmdLine(const CString& cmd_line) { |
| CORE_LOG(L3, (_T("[LaunchCmdLine][%s]"), cmd_line)); |
| |
| bool is_split_token = false; |
| HRESULT hr = vista_util::IsUserRunningSplitToken(&is_split_token); |
| if (FAILED(hr)) { |
| UTIL_LOG(LE, (_T("[IsUserRunningSplitToken failed][0x%x]"), hr)); |
| return hr; |
| } |
| |
| bool run_with_lower_privileges = is_split_token && vista_util::IsUserAdmin(); |
| |
| if (run_with_lower_privileges) { |
| return LaunchImpersonatedCmdLine(cmd_line); |
| } |
| |
| hr = System::ShellExecuteCommandLine(cmd_line, NULL, NULL); |
| if (FAILED(hr)) { |
| UTIL_LOG(LE, (_T("[ShellExecuteCommandLine failed][0x%x]"), hr)); |
| return hr; |
| } |
| |
| return S_OK; |
| } |
| |
| HRESULT LaunchBrowser(BrowserType type, const CString& url) { |
| CORE_LOG(L3, (_T("[LaunchBrowser][%u][%s]"), type, url)); |
| |
| bool is_split_token = false; |
| HRESULT hr = vista_util::IsUserRunningSplitToken(&is_split_token); |
| if (FAILED(hr)) { |
| UTIL_LOG(LE, (_T("[IsUserRunningSplitToken failed][0x%x]"), hr)); |
| return hr; |
| } |
| |
| bool run_with_lower_privileges = is_split_token && vista_util::IsUserAdmin(); |
| |
| if (run_with_lower_privileges) { |
| // Other than having a service launch the browser using CreateProcessAsUser, |
| // there is no easy solution if we are unable to launch the browser |
| // impersonated. |
| return LaunchImpersonatedBrowser(type, url); |
| } |
| |
| hr = ShellExecuteBrowser(type, url); |
| if (FAILED(hr)) { |
| UTIL_LOG(LE, (_T("[ShellExecuteBrowser failed][0x%x]"), hr)); |
| return hr; |
| } |
| |
| return S_OK; |
| } |
| |
| // This method formats all the data that is present inside the UpdateResponse |
| // in the form of the extra arguments command line. |
| // The values that are converted include: |
| // appguid, appname (will not be present in pre-I18N builds.), needsadmin |
| // iid, ap, browser; values not used in pre-I18N builds are not supported. |
| HRESULT ConvertResponseDataToExtraArgs(const UpdateResponseData& response_data, |
| CString* extra) { |
| ASSERT1(extra); |
| *extra = _T(""); |
| |
| // Append the application guid. |
| if (response_data.guid() == GUID_NULL) { |
| // The guid should always be present. |
| return E_INVALIDARG; |
| } |
| CString str_guid = GuidToString(response_data.guid()); |
| ASSERT1(!str_guid.IsEmpty()); |
| extra->AppendFormat(_T("%s=%s"), kExtraArgAppGuid, str_guid); |
| |
| // Convert the application name into the command line format. In case of |
| // pre-I18N installers, the name is not known to the installer, hence if |
| // the name is empty we use a hard coded name. |
| CString product_name; |
| if (!response_data.app_name().IsEmpty()) { |
| HRESULT hr = WideStringToUtf8UrlEncodedString(response_data.app_name(), |
| &product_name); |
| if (FAILED(hr)) { |
| return hr; |
| } |
| } else { |
| product_name = GetProductName(str_guid); |
| } |
| ASSERT1(!product_name.IsEmpty()); |
| extra->AppendFormat(_T("&%s=%s"), kExtraArgAppName, product_name); |
| |
| // Append needs admin. |
| CString needs_admin_str; |
| HRESULT hr = ConvertNeedsAdminToString(response_data.needs_admin(), |
| &needs_admin_str); |
| |
| if (FAILED(hr)) { |
| return hr; |
| } |
| ASSERT1(!needs_admin_str.IsEmpty()); |
| extra->AppendFormat(_T("&%s=%s"), kExtraArgNeedsAdmin, needs_admin_str); |
| |
| if (response_data.installation_id() != GUID_NULL) { |
| CString str_installationid = GuidToString(response_data.installation_id()); |
| extra->AppendFormat(_T("&%s=%s"), kExtraArgInstallationId, |
| str_installationid); |
| } |
| // Append the browser tag. |
| if (response_data.browser_type() != BROWSER_UNKNOWN) { |
| CString browser_type = |
| ConvertBrowserTypeToString(response_data.browser_type()); |
| extra->AppendFormat(_T("&%s=%s"), kExtraArgBrowserType, browser_type); |
| } |
| |
| // Append ap tag. |
| if (!response_data.ap().IsEmpty()) { |
| extra->AppendFormat(_T("&%s=%s"), |
| kExtraArgAdditionalParameters, |
| response_data.ap()); |
| } |
| |
| // Append TT tag. |
| if (!response_data.tt_token().IsEmpty()) { |
| extra->AppendFormat(_T("&%s=%s"), |
| kExtraArgTTToken, |
| response_data.tt_token()); |
| } |
| |
| return S_OK; |
| } |
| |
| HRESULT ConvertNeedsAdminToString(NeedsAdmin needs_admin, CString* text) { |
| ASSERT1(text); |
| switch (needs_admin) { |
| case omaha::NEEDS_ADMIN_YES: |
| *text = kTrue; |
| break; |
| case omaha::NEEDS_ADMIN_NO: |
| *text = kFalse; |
| break; |
| default: |
| return E_INVALIDARG; |
| } |
| |
| return S_OK; |
| } |
| |
| HRESULT ConvertStringToNeedsAdmin(const CString& text, NeedsAdmin* admin) { |
| ASSERT1(admin); |
| if (_tcsicmp(text, kTrue) == 0) { |
| *admin = omaha::NEEDS_ADMIN_YES; |
| } else if (_tcsicmp(text, kFalse) == 0) { |
| *admin = omaha::NEEDS_ADMIN_NO; |
| } else { |
| return GOOPDATEXML_E_NEEDSADMIN; |
| } |
| |
| return S_OK; |
| } |
| |
| HRESULT HandleLegacyManifestHandoff(const CString& manifest_filename, |
| bool is_machine) { |
| OPT_LOG(L1, (_T("[HandleLegacyManifestHandoff]"))); |
| |
| // Read the manifest file. |
| std::vector<byte> xml_contents; |
| HRESULT hr = GoopdateXmlParser::LoadXmlFileToMemory(manifest_filename, |
| &xml_contents); |
| if (FAILED(hr)) { |
| OPT_LOG(LE, (_T("[Could not load manifest file to memory][%s][0x%08x]"), |
| manifest_filename, hr)); |
| // There is something wrong with the file. Move the file to a .bad file. |
| CString bad(manifest_filename); |
| bad.Append(_T(".bad")); |
| File::Move(manifest_filename, bad, true); |
| return hr; |
| } |
| VERIFY1(SUCCEEDED(File::Remove(manifest_filename))); |
| |
| // Parse the manifest. |
| UpdateResponses responses; |
| hr = GoopdateXmlParser::ParseManifestBytes(xml_contents, &responses); |
| if (FAILED(hr)) { |
| OPT_LOG(LE, (_T("[Could not parse manifest][%s]"), manifest_filename)); |
| return hr; |
| } |
| ASSERT1(!responses.empty()); |
| |
| // We we support one application in legacy Omaha. |
| ASSERT1(1 == responses.size()); |
| UpdateResponses::const_iterator iter = responses.begin(); |
| const UpdateResponse& response = (*iter).second; |
| if (response.update_response_data().needs_admin() == |
| NEEDS_ADMIN_YES && !is_machine) { |
| return GOOPDATE_E_NON_ADMINS_CANNOT_INSTALL_ADMIN; |
| } |
| |
| // Convert the contents of the manifest file into the extraargs command line |
| // format which is the standard omaha2 worker command line. |
| // We re-launch the worker with the new command line instead of handling the |
| // legacy handoff right here, as this allows the other parts of setup |
| // and worker to only know about the new command line and not have to deal |
| // with the legacy commandline. One place where setup and the worker |
| // get simplified because of this is during search for processes with |
| // needsadmin=true. |
| CString extra_args; |
| hr = ConvertResponseDataToExtraArgs(response.update_response_data(), |
| &extra_args); |
| if (FAILED(hr)) { |
| OPT_LOG(LE, (_T("[ConvertResponseDataToExtraArgs failed][0x%08x]"), hr)); |
| return hr; |
| } |
| |
| // /handoff "extraargs" |
| CommandLineBuilder builder(COMMANDLINE_MODE_HANDOFF_INSTALL); |
| builder.set_extra_args(extra_args); |
| CString cmd_line = builder.GetCommandLineArgs(); |
| |
| hr = StartGoogleUpdateWithArgs(is_machine, cmd_line, NULL); |
| if (FAILED(hr)) { |
| OPT_LOG(LE, (_T("[StartGoogleUpdateWithArgs failed][0x%08x]"), hr)); |
| return hr; |
| } |
| |
| return S_OK; |
| } |
| |
| // This method does a very specific job of searching for install workers, |
| // for the user and machine omaha. It also includes the on-demand updates COM |
| // server, because we treat it similar to interactive installs. |
| // |
| // In machine case we search in all the accounts since the install worker can be |
| // running in any admin account and the machine update worker runs as SYSTEM. |
| // In the user case, we only search the user's account. |
| // In both cases, the Needsadmin command line parameter is checked for |
| // true/false in the machine/user case, respectively. |
| // |
| // Only adds processes to the input vector; does not clear it. |
| // |
| // TODO(omaha): For now we search for the needs_admin=true in the command |
| // line to determine a machine install. Another option of identifying omaha's |
| // is to use the presence of a named mutex. So the user omaha will create |
| // Global\<sid>\Mutex and the machine will create Global\Mutex, in here then |
| // we can test for the presence of the name to decide if an interactive |
| // omaha is running. |
| // TODO(omaha): Consider further filtering the processes based on whether |
| // the owner is elevated in case of machine omaha. |
| HRESULT GetInstallWorkerProcesses(bool is_machine, |
| std::vector<uint32>* processes) { |
| ASSERT1(processes); |
| |
| CString user_sid; |
| DWORD flags = EXCLUDE_CURRENT_PROCESS | |
| INCLUDE_PROCESS_COMMAND_LINE_CONTAINING_STRING; |
| |
| std::vector<CString> command_lines; |
| CString command_line_to_include; |
| command_line_to_include.Format(_T("/%s"), kCmdLineAppHandoffInstall); |
| command_lines.push_back(command_line_to_include); |
| command_line_to_include.Format(_T("/%s"), kCmdLineFinishGoogleUpdateInstall); |
| command_lines.push_back(command_line_to_include); |
| command_lines.push_back(kCmdLineComServerDash); |
| |
| if (!is_machine) { |
| // Search only the same sid as the current user. |
| flags |= INCLUDE_ONLY_PROCESS_OWNED_BY_USER; |
| |
| HRESULT hr = user_info::GetCurrentUser(NULL, NULL, &user_sid); |
| if (FAILED(hr)) { |
| CORE_LOG(LE, (_T("[GetCurrentUser failed][0x%08x]"), hr)); |
| return hr; |
| } |
| } |
| |
| std::vector<uint32> all_install_worker_processes; |
| HRESULT hr = Process::FindProcesses(flags, |
| kGoopdateFileName, |
| true, |
| user_sid, |
| command_lines, |
| &all_install_worker_processes); |
| if (FAILED(hr)) { |
| CORE_LOG(LE, (_T("[FindProcesses failed][0x%08x]"), hr)); |
| return hr; |
| } |
| |
| CString needsadmin_arg; |
| needsadmin_arg.Format(_T("%s=%s"), kExtraArgNeedsAdmin, is_machine ? |
| _T("True") : |
| _T("False")); |
| needsadmin_arg.MakeLower(); |
| |
| CString official_path; |
| hr = GetFolderPath(is_machine ? CSIDL_PROGRAM_FILES : CSIDL_LOCAL_APPDATA, |
| &official_path); |
| ASSERT1(SUCCEEDED(hr)); |
| ASSERT1(!official_path.IsEmpty()); |
| |
| for (size_t i = 0; i < all_install_worker_processes.size(); ++i) { |
| CString cmd_line; |
| const uint32 process = all_install_worker_processes[i]; |
| if (SUCCEEDED(Process::GetCommandLine(process, &cmd_line))) { |
| cmd_line.MakeLower(); |
| // TODO(omaha): FindProcess method does not allow regex's to be specified |
| // along with the include command line. Change Process to allow this. |
| if (cmd_line.Find(needsadmin_arg) != -1) { |
| CORE_LOG(L4, (_T("[Including process][%s]"), cmd_line)); |
| processes->push_back(process); |
| } |
| |
| // The -Embedding does not have a needsAdmin. Decide whether to include it |
| // if it matches the official path for the requested instance type. |
| CString exe_path; |
| if (cmd_line.Find(kCmdLineComServerDash) != -1 && |
| SUCCEEDED(GetExePathFromCommandLine(cmd_line, &exe_path)) && |
| String_StrNCmp(official_path, exe_path, official_path.GetLength(), |
| true) == 0) { |
| CORE_LOG(L4, (_T("[Including process][%s]"), cmd_line)); |
| processes->push_back(process); |
| } |
| } |
| } |
| |
| return S_OK; |
| } |
| |
| // The event name saved to the environment variable does not contain the |
| // decoration added by GetNamedObjectAttributes. |
| HRESULT CreateUniqueEventInEnvironment(const CString& var_name, |
| bool is_machine, |
| HANDLE* unique_event) { |
| ASSERT1(unique_event); |
| |
| GUID event_guid = GUID_NULL; |
| HRESULT hr = ::CoCreateGuid(&event_guid); |
| if (FAILED(hr)) { |
| CORE_LOG(LE, (_T("[::CoCreateGuid failed][0x%08x]"), hr)); |
| return hr; |
| } |
| |
| CString event_name(GuidToString(event_guid)); |
| NamedObjectAttributes attr; |
| GetNamedObjectAttributes(event_name, is_machine, &attr); |
| |
| hr = CreateEvent(&attr, unique_event); |
| if (FAILED(hr)) { |
| CORE_LOG(LW, (_T("[CreateEvent failed in CreateUniqueEventInEnvironment]"), |
| _T("[%s][0x%08x]"), var_name, hr)); |
| return hr; |
| } |
| |
| CORE_LOG(L3, (_T("[created unique event][%s][%s]"), var_name, event_name)); |
| |
| if (!::SetEnvironmentVariable(var_name, event_name)) { |
| DWORD error = ::GetLastError(); |
| CORE_LOG(LE, (_T("[::SetEnvironmentVariable failed][%d]"), error)); |
| return HRESULT_FROM_WIN32(error); |
| } |
| |
| return S_OK; |
| } |
| |
| HRESULT OpenUniqueEventFromEnvironment(const CString& var_name, |
| bool is_machine, |
| HANDLE* unique_event) { |
| ASSERT1(unique_event); |
| |
| TCHAR event_name[MAX_PATH] = {0}; |
| if (!::GetEnvironmentVariable(var_name, event_name, arraysize(event_name))) { |
| DWORD error = ::GetLastError(); |
| CORE_LOG(LW, (_T("[Failed to read environment variable][%s][%d]"), |
| var_name, error)); |
| return HRESULT_FROM_WIN32(error); |
| } |
| |
| CORE_LOG(L3, (_T("[read unique event][%s][%s]"), var_name, event_name)); |
| |
| NamedObjectAttributes attr; |
| GetNamedObjectAttributes(event_name, is_machine, &attr); |
| *unique_event = ::OpenEvent(EVENT_ALL_ACCESS, false, attr.name); |
| |
| if (!*unique_event) { |
| DWORD error = ::GetLastError(); |
| CORE_LOG(LW, (_T("[::OpenEvent failed][%s][%d]"), attr.name, error)); |
| return HRESULT_FROM_WIN32(error); |
| } |
| |
| return S_OK; |
| } |
| |
| // The caller is responsible for reseting the event and closing the handle. |
| HRESULT CreateEvent(NamedObjectAttributes* event_attr, HANDLE* event_handle) { |
| ASSERT1(event_handle); |
| ASSERT1(event_attr); |
| ASSERT1(!event_attr->name.IsEmpty()); |
| *event_handle = ::CreateEvent(&event_attr->sa, |
| true, // manual reset |
| false, // not signaled |
| event_attr->name); |
| |
| if (!*event_handle) { |
| DWORD error = ::GetLastError(); |
| CORE_LOG(LEVEL_ERROR, (_T("[::SetEvent failed][%d]"), error)); |
| return HRESULT_FROM_WIN32(error); |
| } |
| |
| return S_OK; |
| } |
| |
| HRESULT ConfigureNetwork(bool is_machine, bool is_local_system) { |
| CORE_LOG(L3, (_T("[goopdate_utils::ConfigureNetwork]"))); |
| scoped_handle impersonation_token; |
| if (is_local_system) { |
| // Get an impersonation token corresponding to a primary |
| // token for any of the logged on users. |
| vista::GetLoggedOnUserToken(address(impersonation_token)); |
| } |
| NetworkConfig& network_config = NetworkConfig::Instance(); |
| HRESULT hr = network_config.Initialize(is_machine, get(impersonation_token)); |
| if (FAILED(hr)) { |
| NET_LOG(LE, (_T("[NetworkConfig::Initialize() failed][0x%x]"), hr)); |
| return hr; |
| } |
| |
| // The NetworkConfig singleton owns the impersonation token. |
| release(impersonation_token); |
| |
| // Detecting the default browser requires impersonation so that calling code |
| // running as system is able to detect user proxy settings. |
| HANDLE token = network_config.session().impersonation_token; |
| scoped_impersonation impersonate_user(token); |
| if (token) { |
| DWORD result = impersonate_user.result(); |
| ASSERT(result == ERROR_SUCCESS, (_T("impersonation failed %d"), result)); |
| } |
| network_config.Add(new GoogleProxyDetector(MACHINE_REG_UPDATE_DEV)); |
| BrowserType browser_type(BROWSER_UNKNOWN); |
| GetDefaultBrowserType(&browser_type); |
| if (browser_type == BROWSER_FIREFOX) { |
| network_config.Add(new FirefoxProxyDetector()); |
| } |
| // There is no Chrome detector because it uses the same proxy settings as IE. |
| network_config.Add(new IEProxyDetector()); |
| network_config.Add(new DefaultProxyDetector); |
| |
| // Use a global network configuration override if available. |
| ConfigManager* config_manager = ConfigManager::Instance(); |
| CString net_config; |
| if (SUCCEEDED(config_manager->GetNetConfig(&net_config))) { |
| Config configuration_override = NetworkConfig::ParseNetConfig(net_config); |
| network_config.SetConfigurationOverride(&configuration_override); |
| } |
| |
| return S_OK; |
| } |
| |
| bool IsTestSource() { |
| return !ConfigManager::Instance()->GetTestSource().IsEmpty(); |
| } |
| |
| HRESULT ReadNameValuePairsFromFile(const CString& file_path, |
| const CString& group_name, |
| std::map<CString, CString>* pairs) { |
| ASSERT1(pairs); |
| |
| if (!File::Exists(file_path)) { |
| return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); |
| } |
| |
| pairs->clear(); |
| |
| TCHAR str_buf[32768] = {0}; |
| |
| // Retrieve all key names in the section requested. |
| DWORD buf_count = ::GetPrivateProfileString(group_name, |
| NULL, |
| NULL, |
| str_buf, |
| arraysize(str_buf), |
| file_path); |
| |
| DWORD offset = 0; |
| while (offset < buf_count) { |
| TCHAR val_buf[1024] = {0}; |
| CString current_key = &(str_buf[offset]); |
| DWORD val_count = ::GetPrivateProfileString(group_name, |
| current_key, |
| NULL, |
| val_buf, |
| arraysize(val_buf), |
| file_path); |
| (*pairs)[current_key] = val_buf; |
| offset += current_key.GetLength() + 1; |
| } |
| |
| return S_OK; |
| } |
| |
| HRESULT WriteNameValuePairsToFile(const CString& file_path, |
| const CString& group_name, |
| const std::map<CString, CString>& pairs) { |
| std::map<CString, CString>::const_iterator it = pairs.begin(); |
| for (; it != pairs.end(); ++it) { |
| if (!::WritePrivateProfileString(group_name, |
| it->first, |
| it->second, |
| file_path)) { |
| return HRESULTFromLastError(); |
| } |
| } |
| |
| return S_OK; |
| } |
| |
| // Google Update does not have a referral_id. Everything else is the same as for |
| // apps. |
| HRESULT SetGoogleUpdateBranding(const CString& client_state_key_path, |
| const CString& brand_code, |
| const CString& client_id) { |
| HRESULT hr(SetAppBranding(client_state_key_path, |
| brand_code, |
| client_id, |
| CString())); |
| |
| if (FAILED(hr)) { |
| return hr; |
| } |
| |
| RegKey state_key; |
| hr = state_key.Open(client_state_key_path); |
| if (FAILED(hr)) { |
| return hr; |
| } |
| |
| // Legacy support for older versions that do not write the FirstInstallTime. |
| // This code ensures that FirstInstallTime always has a valid non-zero value. |
| DWORD install_time(0); |
| if (FAILED(state_key.GetValue(kRegValueInstallTimeSec, &install_time)) || |
| !install_time) { |
| const DWORD now = Time64ToInt32(GetCurrent100NSTime()); |
| VERIFY1(SUCCEEDED(state_key.SetValue(kRegValueInstallTimeSec, now))); |
| SETUP_LOG(L3, (_T("[InstallTime missing. Setting it here.][%u]"), now)); |
| } |
| |
| return S_OK; |
| } |
| |
| // Branding information is only written if a brand code is not already present. |
| // We should only write it if this is the first install of Omaha to avoid giving |
| // undue credit to a later installer source. Writing a default brand code |
| // prevents future branded installations from setting their brand. |
| // As suggested by PSO, there is no default client ID. |
| // Assumes the specified Client State key has been created. |
| HRESULT SetAppBranding(const CString& client_state_key_path, |
| const CString& brand_code, |
| const CString& client_id, |
| const CString& referral_id) { |
| SETUP_LOG(L3, (_T("[goopdate_utils::SetAppBranding][%s][%s][%s][%s]"), |
| client_state_key_path, brand_code, client_id, referral_id)); |
| |
| if (brand_code.GetLength() > kBrandIdLength) { |
| return E_INVALIDARG; |
| } |
| |
| RegKey state_key; |
| HRESULT hr = state_key.Open(client_state_key_path); |
| if (FAILED(hr)) { |
| return hr; |
| } |
| |
| CString existing_brand_code; |
| hr = state_key.GetValue(kRegValueBrandCode, &existing_brand_code); |
| if (!existing_brand_code.IsEmpty()) { |
| ASSERT1(SUCCEEDED(hr)); |
| if (existing_brand_code.GetLength() > kBrandIdLength) { |
| // Bug 1358852: Brand code garbled with one click. |
| VERIFY1(SUCCEEDED(state_key.SetValue(kRegValueBrandCode, |
| existing_brand_code.Left(kBrandIdLength)))); |
| } |
| return S_OK; |
| } |
| |
| const TCHAR* brand_code_to_write = brand_code.IsEmpty() ? |
| kDefaultGoogleUpdateBrandCode : |
| brand_code; |
| hr = state_key.SetValue(kRegValueBrandCode, brand_code_to_write); |
| if (FAILED(hr)) { |
| return hr; |
| } |
| |
| if (!client_id.IsEmpty()) { |
| hr = state_key.SetValue(kRegValueClientId, client_id); |
| if (FAILED(hr)) { |
| return hr; |
| } |
| } |
| |
| if (!referral_id.IsEmpty()) { |
| hr = state_key.SetValue(kRegValueReferralId, referral_id); |
| if (FAILED(hr)) { |
| return hr; |
| } |
| } |
| |
| const DWORD now = Time64ToInt32(GetCurrent100NSTime()); |
| VERIFY1(SUCCEEDED(state_key.SetValue(kRegValueInstallTimeSec, now))); |
| |
| return S_OK; |
| } |
| |
| bool IsAppInstallWorkerRunning(bool is_machine) { |
| CORE_LOG(L3, (_T("[IsAppInstallWorkerRunning][%d]"), is_machine)); |
| std::vector<uint32> processes; |
| VERIFY1(SUCCEEDED(GetInstallWorkerProcesses(is_machine, &processes))); |
| return !processes.empty(); |
| } |
| |
| // TODO(omaha): needs_admin is only used for one case. Can we avoid it? |
| bool IsMachineProcess(CommandLineMode mode, |
| bool is_running_from_official_machine_directory, |
| bool is_local_system, |
| bool is_machine_override, |
| Tristate needs_admin) { |
| switch (mode) { |
| // These "install" operations may not be running from the installed |
| // location. |
| case COMMANDLINE_MODE_INSTALL: |
| case COMMANDLINE_MODE_IG: |
| case COMMANDLINE_MODE_HANDOFF_INSTALL: |
| case COMMANDLINE_MODE_REGISTER_PRODUCT: |
| case COMMANDLINE_MODE_UNREGISTER_PRODUCT: |
| ASSERT1(TRISTATE_NONE != needs_admin); |
| return TRISTATE_TRUE == needs_admin; |
| |
| // The following is a Code Red repair executable, which runs from temp dir. |
| case COMMANDLINE_MODE_RECOVER: |
| return is_machine_override; |
| |
| // The following are all user-initiated installs with UI. |
| // They always run as the user for both user and machine installs. |
| // Silent installs for Pack should go here too. |
| case COMMANDLINE_MODE_NOARGS: // Legacy install |
| case COMMANDLINE_MODE_LEGACYUI: |
| case COMMANDLINE_MODE_LEGACY_MANIFEST_HANDOFF: |
| return is_running_from_official_machine_directory; |
| |
| // The following always runs as the user and may provide UI for on-demand |
| // installs and browser launches. |
| // The install location determines user vs. machine. |
| case COMMANDLINE_MODE_COMSERVER: |
| return is_running_from_official_machine_directory; |
| |
| // The following always runs as the user and is user-initiated. |
| case COMMANDLINE_MODE_WEBPLUGIN: |
| // The install location determines user vs. machine. |
| // This may not be the desired value when doing a cross-install or using |
| // the opposite plugin (i.e. user plugin is often used before the machine |
| // one). |
| return is_running_from_official_machine_directory; |
| |
| // The following all run silently as the user for user installs or Local |
| // System for machine installs. |
| case COMMANDLINE_MODE_UPDATE: |
| case COMMANDLINE_MODE_UA: |
| case COMMANDLINE_MODE_CODE_RED_CHECK: |
| return is_local_system; |
| |
| // The following normally runs silently as the user for user installs or |
| // Local System for machine installs. When launched by recovery it can also |
| // a machine instance can also be running as an elevated admin. |
| case COMMANDLINE_MODE_UG: |
| return is_machine_override || is_local_system; |
| |
| // The following runs silently as the user for user installs or Local System |
| // for machine installs. |
| case COMMANDLINE_MODE_CORE: |
| case COMMANDLINE_MODE_CRASH_HANDLER: |
| return is_local_system; |
| |
| // The following runs silently as Local System. |
| case COMMANDLINE_MODE_SERVICE: |
| ASSERT1(is_local_system); |
| return is_local_system; |
| |
| // The following run as machine for all installs. |
| case COMMANDLINE_MODE_SERVICE_REGISTER: |
| case COMMANDLINE_MODE_SERVICE_UNREGISTER: |
| return true; |
| |
| // The crashing process determines whether it was a machine or user omaha |
| // and correctly sets the /machine switch. |
| case COMMANDLINE_MODE_REPORTCRASH: |
| return is_machine_override; |
| |
| // The following all run silently as the user for all installs. |
| case COMMANDLINE_MODE_REGSERVER: |
| case COMMANDLINE_MODE_UNREGSERVER: |
| return is_running_from_official_machine_directory; |
| |
| // The following are miscellaneous modes that we do not expect to be running |
| // in the wild. |
| case COMMANDLINE_MODE_UNKNOWN: |
| case COMMANDLINE_MODE_NETDIAGS: |
| case COMMANDLINE_MODE_CRASH: |
| default: |
| return is_running_from_official_machine_directory; |
| } |
| } |
| |
| // Returns true if the version does not begin with "1.0." or "1.1.". |
| bool IsGoogleUpdate2OrLater(const CString& version) { |
| const ULONGLONG kFirstOmaha2Version = MAKEDLLVERULL(1, 2, 0, 0); |
| ULONGLONG version_number = VersionFromString(version); |
| ASSERT1(0 != version_number); |
| |
| if (kFirstOmaha2Version <= version_number) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool FormatMessageForNetworkError(HRESULT error, |
| const CString app_name, |
| CString* msg) { |
| ASSERT1(msg); |
| |
| switch (error) { |
| case GOOPDATE_E_NO_NETWORK: |
| msg->FormatMessage(IDS_NO_NETWORK_PRESENT_ERROR, |
| kGoopdateFileName, |
| error); |
| break; |
| case GOOPDATE_E_NETWORK_UNAUTHORIZED: |
| msg->FormatMessage(IDS_ERROR_HTTPSTATUS_UNAUTHORIZED, app_name, error); |
| break; |
| case GOOPDATE_E_NETWORK_FORBIDDEN: |
| msg->FormatMessage(IDS_ERROR_HTTPSTATUS_FORBIDDEN, app_name, error); |
| break; |
| case GOOPDATE_E_NETWORK_PROXYAUTHREQUIRED: |
| msg->FormatMessage(IDS_ERROR_HTTPSTATUS_PROXY_AUTH_REQUIRED, |
| app_name, |
| error); |
| break; |
| default: |
| msg->FormatMessage(IDS_NO_NETWORK_PRESENT_ERROR, |
| kGoopdateFileName, |
| error); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void AddNetworkRequestDataToEventLog(NetworkRequest* network_request, |
| HRESULT hr) { |
| ASSERT1(network_request); |
| |
| CString msg; |
| msg.Format(_T("Network Request Error.\r\n") |
| _T("Error: 0x%x. Http status code: %d.\r\n%s"), |
| hr, network_request->http_status_code(), network_request->trace()); |
| |
| LogEvent(static_cast<WORD>(EVENTLOG_ERROR_TYPE), kNetworkRequestEventId, msg); |
| } |
| |
| HRESULT WriteInstallerDataToTempFile(const CString& installer_data, |
| CString* installer_data_file_path) { |
| ASSERT1(installer_data_file_path); |
| |
| // TODO(omaha): consider eliminating the special case and simply create an |
| // empty file. |
| CORE_LOG(L2, (_T("[WriteInstallerDataToTempFile][data=%s]"), installer_data)); |
| if (installer_data.IsEmpty()) { |
| return S_FALSE; |
| } |
| |
| CString temp_file; |
| if (!::GetTempFileName(app_util::GetTempDir(), |
| _T("gui"), |
| 0, |
| CStrBuf(temp_file, MAX_PATH))) { |
| HRESULT hr = HRESULTFromLastError(); |
| CORE_LOG(LE, (_T("[::GetTempFileName failed][0x08%x]"), hr)); |
| return hr; |
| } |
| |
| scoped_handle file_handle(::CreateFile(temp_file, |
| GENERIC_WRITE, |
| FILE_SHARE_READ, |
| NULL, |
| CREATE_ALWAYS, |
| FILE_ATTRIBUTE_NORMAL, |
| NULL)); |
| if (!file_handle) { |
| HRESULT hr = HRESULTFromLastError(); |
| CORE_LOG(LE, (_T("[::CreateFile failed][0x08%x]"), hr)); |
| return hr; |
| } |
| |
| CStringA installer_data_utf8_bom; |
| installer_data_utf8_bom.Format("%c%c%c%s", |
| 0xEF, 0xBB, 0xBF, WideToUtf8(installer_data)); |
| DWORD bytes_written = 0; |
| if (!::WriteFile(get(file_handle), |
| installer_data_utf8_bom, |
| installer_data_utf8_bom.GetLength(), |
| &bytes_written, |
| NULL)) { |
| HRESULT hr = HRESULTFromLastError(); |
| CORE_LOG(LE, (_T("[::WriteFile failed][0x08%x]"), hr)); |
| return hr; |
| } |
| |
| *installer_data_file_path = temp_file; |
| return S_OK; |
| } |
| |
| HRESULT GetNumClients(bool is_machine, size_t* num_clients) { |
| ASSERT1(num_clients); |
| RegKey reg_key; |
| HKEY root_key = is_machine ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; |
| HRESULT hr = reg_key.Open(root_key, GOOPDATE_REG_RELATIVE_CLIENTS, KEY_READ); |
| if (FAILED(hr)) { |
| return hr; |
| } |
| DWORD num_subkeys(0); |
| LONG error = ::RegQueryInfoKey(reg_key.Key(), NULL, NULL, NULL, &num_subkeys, |
| NULL, NULL, NULL, NULL, NULL, NULL, NULL); |
| if (error != ERROR_SUCCESS) { |
| return HRESULT_FROM_WIN32(error); |
| } |
| *num_clients = num_subkeys; |
| return S_OK; |
| } |
| |
| HRESULT ValidateDownloadedFile(const CString& file_name, |
| const CString& hash, |
| uint32 size) { |
| CORE_LOG(L3, (_T("[ValidateDownloadedFile][%s][%s][%u]"), |
| file_name, hash, size)); |
| ASSERT1(File::Exists(file_name)); |
| |
| std::vector<CString> files; |
| files.push_back(file_name); |
| HRESULT hr = AuthenticateFiles(files, hash); |
| if (SUCCEEDED(hr)) { |
| return hr; |
| } |
| |
| uint32 file_size(0); |
| if (FAILED(File::GetFileSizeUnopen(file_name, &file_size))) { |
| return hr; |
| } |
| |
| if (0 == file_size) { |
| return GOOPDATEDOWNLOAD_E_FILE_SIZE_ZERO; |
| } else if (file_size < size) { |
| return GOOPDATEDOWNLOAD_E_FILE_SIZE_SMALLER; |
| } else if (file_size > size) { |
| return GOOPDATEDOWNLOAD_E_FILE_SIZE_LARGER; |
| } |
| |
| ASSERT1(size == file_size); |
| return hr; |
| } |
| |
| // Make sure this is not a silent process before calling this method. |
| // Uses primary_app_name in the title if provided, otherwise displays a generic |
| // title. |
| void DisplayErrorInMessageBox(const CString& error_text, |
| const CString& primary_app_name) { |
| CString msg_box_title; |
| if (primary_app_name.IsEmpty()) { |
| VERIFY1(msg_box_title.LoadString(IDS_GENERIC_INSTALLER_DISPLAY_NAME)); |
| } else { |
| msg_box_title.FormatMessage(IDS_WINDOW_TITLE, primary_app_name); |
| } |
| VERIFY1(IDOK == ::MessageBox(NULL, error_text, msg_box_title, MB_OK)); |
| } |
| |
| } // namespace goopdate_utils |
| |
| } // namespace omaha |
| |