// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "remoting/host/setup/daemon_installer_win.h"
#include <windows.h>
#include "base/bind.h"
#include "base/message_loop/message_loop.h"
#include "base/process/launch.h"
#include "base/strings/string16.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "base/win/object_watcher.h"
#include "base/win/registry.h"
#include "base/win/scoped_bstr.h"
#include "base/win/scoped_comptr.h"
#include "base/win/scoped_handle.h"
#include "base/win/scoped_variant.h"
#include "base/win/windows_version.h"
#include "google_update/google_update_idl.h"
#include "remoting/base/dispatch_win.h"
#include "remoting/host/win/omaha.h"
using base::win::ScopedBstr;
using base::win::ScopedComPtr;
using base::win::ScopedVariant;
namespace {
// ProgID of the per-machine Omaha COM server.
const wchar_t kGoogleUpdate[] = L"GoogleUpdate.Update3WebMachine";
// The COM elevation moniker for the per-machine Omaha COM server.
const wchar_t kGoogleUpdateElevationMoniker[] =
L"Elevation:Administrator!new:GoogleUpdate.Update3WebMachine";
// The registry key where the configuration of Omaha is stored.
const wchar_t kOmahaUpdateKeyName[] = L"Software\\Google\\Update";
// The name of the value where the full path to GoogleUpdate.exe is stored.
const wchar_t kOmahaPathValueName[] = L"path";
// The command line format string for GoogleUpdate.exe
const wchar_t kGoogleUpdateCommandLineFormat[] =
L"\"%ls\" /install \"bundlename=Chromoting%%20Host&appguid=%ls&"
L"appname=Chromoting%%20Host&needsadmin=True&lang=%ls\"";
// TODO(alexeypa): Get the desired laungage from the web app.
const wchar_t kOmahaLanguage[] = L"en";
// An empty string for optional parameters.
const wchar_t kOmahaEmpty[] = L"";
// The installation status polling interval.
const int kOmahaPollIntervalMs = 500;
} // namespace
namespace remoting {
// This class implements on-demand installation of the Chromoting Host via
// per-machine Omaha instance.
class DaemonComInstallerWin : public DaemonInstallerWin {
public:
DaemonComInstallerWin(const ScopedComPtr<IDispatch>& update3,
const CompletionCallback& done);
// DaemonInstallerWin implementation.
virtual void Install() OVERRIDE;
private:
// Polls the installation status performing state-specific actions (such as
// starting installation once download has finished).
void PollInstallationStatus();
// Omaha interfaces.
ScopedVariant app_;
ScopedVariant bundle_;
ScopedComPtr<IDispatch> update3_;
base::Timer polling_timer_;
};
// This class implements on-demand installation of the Chromoting Host by
// launching a per-user instance of Omaha and requesting elevation.
class DaemonCommandLineInstallerWin
: public DaemonInstallerWin,
public base::win::ObjectWatcher::Delegate {
public:
DaemonCommandLineInstallerWin(const CompletionCallback& done);
~DaemonCommandLineInstallerWin();
// DaemonInstallerWin implementation.
virtual void Install() OVERRIDE;
// base::win::ObjectWatcher::Delegate implementation.
virtual void OnObjectSignaled(HANDLE object) OVERRIDE;
private:
// Handle of the launched process.
base::win::ScopedHandle process_;
// Used to determine when the launched process terminates.
base::win::ObjectWatcher process_watcher_;
};
DaemonComInstallerWin::DaemonComInstallerWin(
const ScopedComPtr<IDispatch>& update3,
const CompletionCallback& done)
: DaemonInstallerWin(done),
update3_(update3),
polling_timer_(
FROM_HERE,
base::TimeDelta::FromMilliseconds(kOmahaPollIntervalMs),
base::Bind(&DaemonComInstallerWin::PollInstallationStatus,
base::Unretained(this)),
false) {
}
void DaemonComInstallerWin::Install() {
// Create an app bundle.
HRESULT hr = dispatch::Invoke(update3_.get(), L"createAppBundleWeb",
DISPATCH_METHOD, bundle_.Receive());
if (FAILED(hr)) {
Done(hr);
return;
}
if (bundle_.type() != VT_DISPATCH) {
Done(DISP_E_TYPEMISMATCH);
return;
}
hr = dispatch::Invoke(V_DISPATCH(&bundle_), L"initialize", DISPATCH_METHOD,
NULL);
if (FAILED(hr)) {
Done(hr);
return;
}
// Add Chromoting Host to the bundle.
ScopedVariant appid(kHostOmahaAppid);
ScopedVariant empty(kOmahaEmpty);
ScopedVariant language(kOmahaLanguage);
hr = dispatch::Invoke(V_DISPATCH(&bundle_), L"createApp", DISPATCH_METHOD,
appid, empty, language, empty, NULL);
if (FAILED(hr)) {
Done(hr);
return;
}
hr = dispatch::Invoke(V_DISPATCH(&bundle_), L"checkForUpdate",
DISPATCH_METHOD, NULL);
if (FAILED(hr)) {
Done(hr);
return;
}
hr = dispatch::Invoke(V_DISPATCH(&bundle_), L"appWeb",
DISPATCH_PROPERTYGET, ScopedVariant(0), app_.Receive());
if (FAILED(hr)) {
Done(hr);
return;
}
if (app_.type() != VT_DISPATCH) {
Done(DISP_E_TYPEMISMATCH);
return;
}
// Now poll for the installation status.
PollInstallationStatus();
}
void DaemonComInstallerWin::PollInstallationStatus() {
// Get the current application installation state.
// N.B. The object underlying the ICurrentState interface has static data that
// does not get updated as the server state changes. To get the most "current"
// state, the currentState property needs to be queried again.
ScopedVariant current_state;
HRESULT hr = dispatch::Invoke(V_DISPATCH(&app_), L"currentState",
DISPATCH_PROPERTYGET, current_state.Receive());
if (FAILED(hr)) {
Done(hr);
return;
}
if (current_state.type() != VT_DISPATCH) {
Done(DISP_E_TYPEMISMATCH);
return;
}
ScopedVariant state;
hr = dispatch::Invoke(V_DISPATCH(¤t_state), L"stateValue",
DISPATCH_PROPERTYGET, state.Receive());
if (state.type() != VT_I4) {
Done(DISP_E_TYPEMISMATCH);
return;
}
// Perform state-specific actions.
switch (V_I4(&state)) {
case STATE_INIT:
case STATE_WAITING_TO_CHECK_FOR_UPDATE:
case STATE_CHECKING_FOR_UPDATE:
case STATE_WAITING_TO_DOWNLOAD:
case STATE_RETRYING_DOWNLOAD:
case STATE_DOWNLOADING:
case STATE_WAITING_TO_INSTALL:
case STATE_INSTALLING:
case STATE_PAUSED:
break;
case STATE_UPDATE_AVAILABLE:
hr = dispatch::Invoke(V_DISPATCH(&bundle_), L"download",
DISPATCH_METHOD, NULL);
if (FAILED(hr)) {
Done(hr);
return;
}
break;
case STATE_DOWNLOAD_COMPLETE:
case STATE_EXTRACTING:
case STATE_APPLYING_DIFFERENTIAL_PATCH:
case STATE_READY_TO_INSTALL:
hr = dispatch::Invoke(V_DISPATCH(&bundle_), L"install",
DISPATCH_METHOD, NULL);
if (FAILED(hr)) {
Done(hr);
return;
}
break;
case STATE_INSTALL_COMPLETE:
case STATE_NO_UPDATE:
// Installation complete or not required. Report success.
Done(S_OK);
return;
case STATE_ERROR: {
ScopedVariant error_code;
hr = dispatch::Invoke(V_DISPATCH(¤t_state), L"errorCode",
DISPATCH_PROPERTYGET, error_code.Receive());
if (FAILED(hr)) {
Done(hr);
return;
}
if (error_code.type() != VT_UI4) {
Done(DISP_E_TYPEMISMATCH);
return;
}
Done(V_UI4(&error_code));
return;
}
default:
LOG(ERROR) << "Unknown bundle state: " << V_I4(&state) << ".";
Done(E_FAIL);
return;
}
// Keep polling.
polling_timer_.Reset();
}
DaemonCommandLineInstallerWin::DaemonCommandLineInstallerWin(
const CompletionCallback& done) : DaemonInstallerWin(done) {
}
DaemonCommandLineInstallerWin::~DaemonCommandLineInstallerWin() {
process_watcher_.StopWatching();
}
void DaemonCommandLineInstallerWin::Install() {
// Get the full path to GoogleUpdate.exe from the registry.
base::win::RegKey update_key;
LONG result = update_key.Open(HKEY_CURRENT_USER,
kOmahaUpdateKeyName,
KEY_READ);
if (result != ERROR_SUCCESS) {
Done(HRESULT_FROM_WIN32(result));
return;
}
// presubmit: allow wstring
std::wstring google_update;
result = update_key.ReadValue(kOmahaPathValueName, &google_update);
if (result != ERROR_SUCCESS) {
Done(HRESULT_FROM_WIN32(result));
return;
}
// Launch the updater process and wait for its termination.
base::string16 command_line = base::WideToUTF16(
base::StringPrintf(kGoogleUpdateCommandLineFormat,
google_update.c_str(),
kHostOmahaAppid,
kOmahaLanguage));
base::LaunchOptions options;
if (!base::LaunchProcess(command_line, options, &process_)) {
result = GetLastError();
Done(HRESULT_FROM_WIN32(result));
return;
}
if (!process_watcher_.StartWatching(process_.Get(), this)) {
result = GetLastError();
Done(HRESULT_FROM_WIN32(result));
return;
}
}
void DaemonCommandLineInstallerWin::OnObjectSignaled(HANDLE object) {
// Check if the updater process returned success.
DWORD exit_code;
if (GetExitCodeProcess(process_.Get(), &exit_code) && exit_code == 0) {
Done(S_OK);
} else {
Done(E_FAIL);
}
}
DaemonInstallerWin::DaemonInstallerWin(const CompletionCallback& done)
: done_(done) {
}
DaemonInstallerWin::~DaemonInstallerWin() {
}
void DaemonInstallerWin::Done(HRESULT result) {
CompletionCallback done = done_;
done_.Reset();
done.Run(result);
}
// static
scoped_ptr<DaemonInstallerWin> DaemonInstallerWin::Create(
HWND window_handle,
CompletionCallback done) {
HRESULT result = E_FAIL;
ScopedComPtr<IDispatch> update3;
// Check if the machine instance of Omaha is available. The COM elevation is
// supported on Vista+, so on XP/W2K3 we assume that we are running under
// a privileged user and get ACCESS_DENIED later if we are not.
if (base::win::GetVersion() < base::win::VERSION_VISTA) {
CLSID class_id;
result = CLSIDFromProgID(kGoogleUpdate, &class_id);
if (SUCCEEDED(result)) {
result = CoCreateInstance(class_id,
NULL,
CLSCTX_LOCAL_SERVER,
IID_IDispatch,
update3.ReceiveVoid());
}
} else {
BIND_OPTS3 bind_options;
memset(&bind_options, 0, sizeof(bind_options));
bind_options.cbStruct = sizeof(bind_options);
bind_options.hwnd = GetTopLevelWindow(window_handle);
bind_options.dwClassContext = CLSCTX_LOCAL_SERVER;
result = CoGetObject(kGoogleUpdateElevationMoniker,
&bind_options,
IID_IDispatch,
update3.ReceiveVoid());
}
if (SUCCEEDED(result)) {
// The machine instance of Omaha is available and we successfully passed
// the UAC prompt.
return scoped_ptr<DaemonInstallerWin>(
new DaemonComInstallerWin(update3, done));
} else if (result == CO_E_CLASSSTRING) {
// The machine instance of Omaha is not available so we will have to run
// GoogleUpdate.exe manually passing "needsadmin=True". This will cause
// Omaha to install the machine instance first and then install Chromoting
// Host.
return scoped_ptr<DaemonInstallerWin>(
new DaemonCommandLineInstallerWin(done));
} else {
// The user declined the UAC prompt or some other error occured.
done.Run(result);
return scoped_ptr<DaemonInstallerWin>();
}
}
HWND GetTopLevelWindow(HWND window) {
if (window == NULL) {
return NULL;
}
for (;;) {
LONG style = GetWindowLong(window, GWL_STYLE);
if ((style & WS_OVERLAPPEDWINDOW) == WS_OVERLAPPEDWINDOW ||
(style & WS_POPUP) == WS_POPUP) {
return window;
}
HWND parent = GetAncestor(window, GA_PARENT);
if (parent == NULL) {
return window;
}
window = parent;
}
}
} // namespace remoting