blob: bce3b055a83b0b562c916ca9b9fada5df86bc26a [file] [log] [blame]
// 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(&current_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