diff options
author | sergeyu@chromium.org <sergeyu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-10-01 18:11:34 +0000 |
---|---|---|
committer | sergeyu@chromium.org <sergeyu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-10-01 18:11:34 +0000 |
commit | 39e537e316d35c2dd5181deb445f0420d4dc23b1 (patch) | |
tree | e8d96a98c3548f49bd01235d2032a6d76f2a54d9 /remoting/host/setup | |
parent | c8dd262eaa7ac2a5273c729fc6e16e6ee69c15fa (diff) | |
download | chromium_src-39e537e316d35c2dd5181deb445f0420d4dc23b1.zip chromium_src-39e537e316d35c2dd5181deb445f0420d4dc23b1.tar.gz chromium_src-39e537e316d35c2dd5181deb445f0420d4dc23b1.tar.bz2 |
Move DaemonController to remoting/host/setup
DaemonController will be used outside of the plugin, so it doesn't make
sense to keep it in the plugin directory.
Review URL: https://chromiumcodereview.appspot.com/10985086
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@159525 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'remoting/host/setup')
-rw-r--r-- | remoting/host/setup/daemon_controller.h | 151 | ||||
-rw-r--r-- | remoting/host/setup/daemon_controller_linux.cc | 343 | ||||
-rw-r--r-- | remoting/host/setup/daemon_controller_mac.cc | 368 | ||||
-rw-r--r-- | remoting/host/setup/daemon_controller_win.cc | 698 |
4 files changed, 1560 insertions, 0 deletions
diff --git a/remoting/host/setup/daemon_controller.h b/remoting/host/setup/daemon_controller.h new file mode 100644 index 0000000..4fda7c9 --- /dev/null +++ b/remoting/host/setup/daemon_controller.h @@ -0,0 +1,151 @@ +// 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. + +#ifndef REMOTING_HOST_SETUP_DAEMON_CONTROLLER_H_ +#define REMOTING_HOST_SETUP_DAEMON_CONTROLLER_H_ + +#include <string> + +#include "base/callback_forward.h" +#include "base/memory/scoped_ptr.h" + +namespace base { +class DictionaryValue; +} // namespace base + +namespace remoting { + +class DaemonController { + public: + // Note that these enumeration values are duplicated in daemon_plugin.js and + // must be kept in sync. + enum State { + // Placeholder state for platforms on which the daemon process is not + // implemented. The web-app will not show the corresponding UI. This value + // will eventually be deprecated or removed. + STATE_NOT_IMPLEMENTED = -1, + // The daemon is not installed. This is functionally equivalent to + // STATE_STOPPED, but the start method is expected to be significantly + // slower, and might involve user interaction. It might be appropriate to + // indicate this in the UI. + STATE_NOT_INSTALLED = 0, + // The daemon is being installed. + STATE_INSTALLING = 1, + // The daemon is installed but not running. Call Start to start it. + STATE_STOPPED = 2, + // The daemon process is starting. + STATE_STARTING = 3, + // The daemon process is running. Call Start again to change the PIN or + // Stop to stop it. + STATE_STARTED = 4, + // The daemon process is stopping. + STATE_STOPPING = 5, + // The state cannot be determined. This could indicate that the plugin + // has not been provided with sufficient information, for example, the + // user for which to query state on a multi-user system. + STATE_UNKNOWN = 6 + }; + + // Enum used for completion callback. + enum AsyncResult { + RESULT_OK = 0, + + // The operation has FAILED. + RESULT_FAILED = 1, + + // User has cancelled the action (e.g. rejected UAC prompt). + // TODO(sergeyu): Current implementations don't return this value. + RESULT_CANCELLED = 2, + + // Failed to access host directory. + RESULT_FAILED_DIRECTORY = 3 + + // TODO(sergeyu): Add more error codes when we know how to handle + // them in the webapp. + }; + + // Callback type for GetConfig(). If the host is configured then a dictionary + // is returned containing host_id and xmpp_login, with security-sensitive + // fields filtered out. An empty dictionary is returned if the host is not + // configured, and NULL if the configuration is corrupt or cannot be read. + typedef base::Callback<void (scoped_ptr<base::DictionaryValue> config)> + GetConfigCallback; + + // Callback used for asynchronous operations, e.g. when + // starting/stopping the service. + typedef base::Callback<void (AsyncResult result)> CompletionCallback; + + // Callback type for GetVersion(). + typedef base::Callback<void (const std::string&)> GetVersionCallback; + + // Callback type for GetUsageStatsConsent(). |supported| indicates whether + // crash dump reporting is supported by the host. |allowed| indicates if + // crash dump reporting is allowed by the user. |set_by_policy| carries + // information whether the crash dump reporting is controlled by policy. + typedef base::Callback<void ( + bool supported, + bool allowed, + bool set_by_policy)> GetUsageStatsConsentCallback; + + virtual ~DaemonController() {} + + // Return the "installed/running" state of the daemon process. + // + // TODO(sergeyu): This method is called synchronously from the + // webapp. In most cases it requires IO operations, so it may block + // the user interface. Replace it with asynchronous notifications, + // e.g. with StartStateNotifications()/StopStateNotifications() methods. + virtual State GetState() = 0; + + // Queries current host configuration. The |callback| is called + // after the configuration is read, and any values that might be security + // sensitive have been filtered out. + virtual void GetConfig(const GetConfigCallback& callback) = 0; + + // Start the daemon process. This may require that the daemon be + // downloaded and installed. |done_callback| is called when the + // operation is finished or fails. + // + // TODO(sergeyu): This method writes config and starts the host - + // these two steps are merged for simplicity. Consider splitting it + // into SetConfig() and Start() once we have basic host setup flow + // working. + virtual void SetConfigAndStart(scoped_ptr<base::DictionaryValue> config, + bool consent, + const CompletionCallback& done) = 0; + + // Updates current host configuration with the values specified in + // |config|. Changes must take effect before the call completes. + // Any value in the existing configuration that isn't specified in |config| + // is preserved. |config| must not contain host_id or xmpp_login values, + // because implementations of this method cannot change them. + virtual void UpdateConfig(scoped_ptr<base::DictionaryValue> config, + const CompletionCallback& done_callback) = 0; + + // Stop the daemon process. It is permitted to call Stop while the daemon + // process is being installed, in which case the installation should be + // aborted if possible; if not then it is sufficient to ensure that the + // daemon process is not started automatically upon successful installation. + // As with Start, Stop may return before the operation is complete--poll + // GetState until the state is STATE_STOPPED. + virtual void Stop(const CompletionCallback& done_callback) = 0; + + // Caches the native handle of the plugin window so it can be used to focus + // elevation prompts properly. + virtual void SetWindow(void* window_handle) = 0; + + // Get the version of the daemon as a dotted decimal string of the form + // major.minor.build.patch, if it is installed, or "" otherwise. + virtual void GetVersion(const GetVersionCallback& done_callback) = 0; + + // Get the user's consent to crash reporting. + virtual void GetUsageStatsConsent( + const GetUsageStatsConsentCallback& done) = 0; + + static scoped_ptr<DaemonController> Create(); +}; + +} // namespace remoting + +#endif // REMOTING_HOST_SETUP_DAEMON_CONTROLLER_H_ diff --git a/remoting/host/setup/daemon_controller_linux.cc b/remoting/host/setup/daemon_controller_linux.cc new file mode 100644 index 0000000..fee859c --- /dev/null +++ b/remoting/host/setup/daemon_controller_linux.cc @@ -0,0 +1,343 @@ +// 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_controller.h" + +#include <unistd.h> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/command_line.h" +#include "base/compiler_specific.h" +#include "base/environment.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/json/json_writer.h" +#include "base/logging.h" +#include "base/md5.h" +#include "base/process_util.h" +#include "base/string_number_conversions.h" +#include "base/string_split.h" +#include "base/string_util.h" +#include "base/threading/thread.h" +#include "base/values.h" +#include "net/base/net_util.h" +#include "remoting/host/host_config.h" +#include "remoting/host/json_host_config.h" +#include "remoting/host/usage_stats_consent.h" + +namespace remoting { + +namespace { + +const char kDaemonScript[] = + "/opt/google/chrome-remote-desktop/chrome-remote-desktop"; + +// Timeout for running daemon script. +const int64 kDaemonTimeoutMs = 5000; + +// Timeout for commands that require password prompt- 1 minute; +const int64 kSudoTimeoutMs = 60000; + +std::string GetMd5(const std::string& value) { + base::MD5Context ctx; + base::MD5Init(&ctx); + base::MD5Update(&ctx, value); + base::MD5Digest digest; + base::MD5Final(&digest, &ctx); + return StringToLowerASCII(base::HexEncode(digest.a, sizeof(digest.a))); +} + +class DaemonControllerLinux : public remoting::DaemonController { + public: + DaemonControllerLinux(); + + virtual State GetState() OVERRIDE; + virtual void GetConfig(const GetConfigCallback& callback) OVERRIDE; + virtual void SetConfigAndStart( + scoped_ptr<base::DictionaryValue> config, + bool consent, + const CompletionCallback& done) OVERRIDE; + virtual void UpdateConfig(scoped_ptr<base::DictionaryValue> config, + const CompletionCallback& done_callback) OVERRIDE; + virtual void Stop(const CompletionCallback& done_callback) OVERRIDE; + virtual void SetWindow(void* window_handle) OVERRIDE; + virtual void GetVersion(const GetVersionCallback& done_callback) OVERRIDE; + virtual void GetUsageStatsConsent( + const GetUsageStatsConsentCallback& done) OVERRIDE; + + private: + FilePath GetConfigPath(); + + void DoGetConfig(const GetConfigCallback& callback); + void DoSetConfigAndStart(scoped_ptr<base::DictionaryValue> config, + const CompletionCallback& done); + void DoUpdateConfig(scoped_ptr<base::DictionaryValue> config, + const CompletionCallback& done_callback); + void DoStop(const CompletionCallback& done_callback); + void DoGetVersion(const GetVersionCallback& done_callback); + + base::Thread file_io_thread_; + + DISALLOW_COPY_AND_ASSIGN(DaemonControllerLinux); +}; + +DaemonControllerLinux::DaemonControllerLinux() + : file_io_thread_("DaemonControllerFileIO") { + file_io_thread_.Start(); +} + +static bool GetScriptPath(FilePath* result) { + FilePath candidate_exe(kDaemonScript); + if (access(candidate_exe.value().c_str(), X_OK) == 0) { + *result = candidate_exe; + return true; + } + return false; +} + +static bool RunHostScriptWithTimeout( + const std::vector<std::string>& args, + base::TimeDelta timeout, + int* exit_code) { + // As long as we're relying on running an external binary from the + // PATH, don't do it as root. + if (getuid() == 0) { + return false; + } + FilePath script_path; + if (!GetScriptPath(&script_path)) { + return false; + } + CommandLine command_line(script_path); + for (unsigned int i = 0; i < args.size(); ++i) { + command_line.AppendArg(args[i]); + } + base::ProcessHandle process_handle; + bool result = base::LaunchProcess(command_line, + base::LaunchOptions(), + &process_handle); + if (result) { + if (exit_code) { + result = base::WaitForExitCodeWithTimeout( + process_handle, exit_code, timeout); + } + base::CloseProcessHandle(process_handle); + } + return result; +} + +static bool RunHostScript(const std::vector<std::string>& args, + int* exit_code) { + return RunHostScriptWithTimeout( + args, base::TimeDelta::FromMilliseconds(kDaemonTimeoutMs), exit_code); +} + +remoting::DaemonController::State DaemonControllerLinux::GetState() { + std::vector<std::string> args; + args.push_back("--check-running"); + int exit_code = 0; + if (!RunHostScript(args, &exit_code)) { + // TODO(jamiewalch): When we have a good story for installing, return + // NOT_INSTALLED rather than NOT_IMPLEMENTED (the former suppresses + // the relevant UI in the web-app). + return remoting::DaemonController::STATE_NOT_IMPLEMENTED; + } + + if (exit_code == 0) { + return remoting::DaemonController::STATE_STARTED; + } else { + return remoting::DaemonController::STATE_STOPPED; + } +} + +void DaemonControllerLinux::GetConfig(const GetConfigCallback& callback) { + // base::Unretained() is safe because we control lifetime of the thread. + file_io_thread_.message_loop()->PostTask(FROM_HERE, base::Bind( + &DaemonControllerLinux::DoGetConfig, base::Unretained(this), callback)); +} + +void DaemonControllerLinux::GetUsageStatsConsent( + const GetUsageStatsConsentCallback& done) { + // Crash dump collection is not implemented on Linux yet. + // http://crbug.com/130678. + done.Run(false, false, false); +} + +void DaemonControllerLinux::SetConfigAndStart( + scoped_ptr<base::DictionaryValue> config, + bool /* consent */, + const CompletionCallback& done) { + // base::Unretained() is safe because we control lifetime of the thread. + file_io_thread_.message_loop()->PostTask(FROM_HERE, base::Bind( + &DaemonControllerLinux::DoSetConfigAndStart, base::Unretained(this), + base::Passed(&config), done)); +} + +void DaemonControllerLinux::UpdateConfig( + scoped_ptr<base::DictionaryValue> config, + const CompletionCallback& done_callback) { + file_io_thread_.message_loop()->PostTask(FROM_HERE, base::Bind( + &DaemonControllerLinux::DoUpdateConfig, base::Unretained(this), + base::Passed(&config), done_callback)); +} + +void DaemonControllerLinux::Stop(const CompletionCallback& done_callback) { + file_io_thread_.message_loop()->PostTask(FROM_HERE, base::Bind( + &DaemonControllerLinux::DoStop, base::Unretained(this), + done_callback)); +} + +void DaemonControllerLinux::SetWindow(void* window_handle) { + // noop +} + +void DaemonControllerLinux::GetVersion( + const GetVersionCallback& done_callback) { + file_io_thread_.message_loop()->PostTask(FROM_HERE, base::Bind( + &DaemonControllerLinux::DoGetVersion, base::Unretained(this), + done_callback)); +} + +FilePath DaemonControllerLinux::GetConfigPath() { + std::string filename = "host#" + GetMd5(net::GetHostName()) + ".json"; + return file_util::GetHomeDir(). + Append(".config/chrome-remote-desktop").Append(filename); +} + +void DaemonControllerLinux::DoGetConfig(const GetConfigCallback& callback) { + scoped_ptr<base::DictionaryValue> result(new base::DictionaryValue()); + + if (GetState() != remoting::DaemonController::STATE_NOT_IMPLEMENTED) { + JsonHostConfig config(GetConfigPath()); + if (config.Read()) { + std::string value; + if (config.GetString(kHostIdConfigPath, &value)) { + result->SetString(kHostIdConfigPath, value); + } + if (config.GetString(kXmppLoginConfigPath, &value)) { + result->SetString(kXmppLoginConfigPath, value); + } + } else { + result.reset(); // Return NULL in case of error. + } + } + + callback.Run(result.Pass()); +} + +void DaemonControllerLinux::DoSetConfigAndStart( + scoped_ptr<base::DictionaryValue> config, + const CompletionCallback& done_callback) { + + // Add the user to chrome-remote-desktop group first. + std::vector<std::string> args; + args.push_back("--add-user"); + int exit_code; + if (!RunHostScriptWithTimeout( + args, base::TimeDelta::FromMilliseconds(kSudoTimeoutMs), + &exit_code) || + exit_code != 0) { + LOG(ERROR) << "Failed to add user to chrome-remote-desktop group."; + done_callback.Run(RESULT_FAILED); + return; + } + + // Write config. + JsonHostConfig config_file(GetConfigPath()); + if (!config_file.CopyFrom(config.get()) || + !config_file.Save()) { + LOG(ERROR) << "Failed to update config file."; + done_callback.Run(RESULT_FAILED); + return; + } + + // Finally start the host. + args.clear(); + args.push_back("--start"); + AsyncResult result; + if (RunHostScript(args, &exit_code)) { + result = (exit_code == 0) ? RESULT_OK : RESULT_FAILED; + } else { + result = RESULT_FAILED; + } + done_callback.Run(result); +} + +void DaemonControllerLinux::DoUpdateConfig( + scoped_ptr<base::DictionaryValue> config, + const CompletionCallback& done_callback) { + JsonHostConfig config_file(GetConfigPath()); + if (!config_file.Read() || + !config_file.CopyFrom(config.get()) || + !config_file.Save()) { + LOG(ERROR) << "Failed to update config file."; + done_callback.Run(RESULT_FAILED); + return; + } + + std::vector<std::string> args; + args.push_back("--reload"); + AsyncResult result; + int exit_code; + if (RunHostScript(args, &exit_code)) { + result = (exit_code == 0) ? RESULT_OK : RESULT_FAILED; + } else { + result = RESULT_FAILED; + } + + done_callback.Run(result); +} + +void DaemonControllerLinux::DoStop(const CompletionCallback& done_callback) { + std::vector<std::string> args; + args.push_back("--stop"); + int exit_code = 0; + AsyncResult result; + if (RunHostScript(args, &exit_code)) { + result = (exit_code == 0) ? RESULT_OK : RESULT_FAILED; + } else { + result = RESULT_FAILED; + } + done_callback.Run(result); +} + +void DaemonControllerLinux::DoGetVersion( + const GetVersionCallback& done_callback) { + FilePath script_path; + if (!GetScriptPath(&script_path)) { + done_callback.Run(""); + return; + } + CommandLine command_line(script_path); + command_line.AppendArg("--host-version"); + + std::string version; + int exit_code = 0; + int result = + base::GetAppOutputWithExitCode(command_line, &version, &exit_code); + if (!result || exit_code != 0) { + LOG(ERROR) << "Failed to run \"" << command_line.GetCommandLineString() + << "\". Exit code: " << exit_code; + done_callback.Run(""); + return; + } + + TrimWhitespaceASCII(version, TRIM_ALL, &version); + if (!ContainsOnlyChars(version, "0123456789.")) { + LOG(ERROR) << "Received invalid host version number: " << version; + done_callback.Run(""); + return; + } + + done_callback.Run(version); +} + +} // namespace + +scoped_ptr<DaemonController> remoting::DaemonController::Create() { + return scoped_ptr<DaemonController>(new DaemonControllerLinux()); +} + +} // namespace remoting diff --git a/remoting/host/setup/daemon_controller_mac.cc b/remoting/host/setup/daemon_controller_mac.cc new file mode 100644 index 0000000..f82dabd --- /dev/null +++ b/remoting/host/setup/daemon_controller_mac.cc @@ -0,0 +1,368 @@ +// 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_controller.h" + +#include <launch.h> +#include <stdio.h> +#include <sys/types.h> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/compiler_specific.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/json/json_writer.h" +#include "base/logging.h" +#include "base/mac/foundation_util.h" +#include "base/mac/launchd.h" +#include "base/mac/mac_logging.h" +#include "base/mac/mac_util.h" +#include "base/mac/scoped_launch_data.h" +#include "base/threading/thread.h" +#include "base/time.h" +#include "base/values.h" +#include "remoting/host/constants_mac.h" +#include "remoting/host/json_host_config.h" +#include "remoting/host/usage_stats_consent.h" + +namespace remoting { + +namespace { + +// The NSSystemDirectories.h header has a conflicting definition of +// NSSearchPathDirectory with the one in base/mac/foundation_util.h. +// Foundation.h would work, but it can only be included from Objective-C files. +// Therefore, we define the needed constants here. +const int NSLibraryDirectory = 5; + +class DaemonControllerMac : public remoting::DaemonController { + public: + DaemonControllerMac(); + virtual ~DaemonControllerMac(); + + virtual State GetState() OVERRIDE; + virtual void GetConfig(const GetConfigCallback& callback) OVERRIDE; + virtual void SetConfigAndStart( + scoped_ptr<base::DictionaryValue> config, + bool consent, + const CompletionCallback& done) OVERRIDE; + virtual void UpdateConfig(scoped_ptr<base::DictionaryValue> config, + const CompletionCallback& done_callback) OVERRIDE; + virtual void Stop(const CompletionCallback& done_callback) OVERRIDE; + virtual void SetWindow(void* window_handle) OVERRIDE; + virtual void GetVersion(const GetVersionCallback& done_callback) OVERRIDE; + virtual void GetUsageStatsConsent( + const GetUsageStatsConsentCallback& callback) OVERRIDE; + + private: + void DoGetConfig(const GetConfigCallback& callback); + void DoGetVersion(const GetVersionCallback& callback); + void DoSetConfigAndStart(scoped_ptr<base::DictionaryValue> config, + const CompletionCallback& done); + void DoUpdateConfig(scoped_ptr<base::DictionaryValue> config, + const CompletionCallback& done_callback); + void DoStop(const CompletionCallback& done_callback); + + void ShowPreferencePane(const std::string& config_data, + const CompletionCallback& done_callback); + void RegisterForPreferencePaneNotifications( + const CompletionCallback &done_callback); + void DeregisterForPreferencePaneNotifications(); + void PreferencePaneCallbackDelegate(CFStringRef name); + static bool DoShowPreferencePane(const std::string& config_data); + static void PreferencePaneCallback(CFNotificationCenterRef center, + void* observer, + CFStringRef name, + const void* object, + CFDictionaryRef user_info); + + base::Thread auth_thread_; + CompletionCallback current_callback_; + + DISALLOW_COPY_AND_ASSIGN(DaemonControllerMac); +}; + +DaemonControllerMac::DaemonControllerMac() + : auth_thread_("Auth thread") { + auth_thread_.Start(); +} + +DaemonControllerMac::~DaemonControllerMac() { + auth_thread_.Stop(); + DeregisterForPreferencePaneNotifications(); +} + +void DaemonControllerMac::DeregisterForPreferencePaneNotifications() { + CFNotificationCenterRemoveObserver( + CFNotificationCenterGetDistributedCenter(), + this, + CFSTR(UPDATE_SUCCEEDED_NOTIFICATION_NAME), + NULL); + CFNotificationCenterRemoveObserver( + CFNotificationCenterGetDistributedCenter(), + this, + CFSTR(UPDATE_FAILED_NOTIFICATION_NAME), + NULL); +} + +DaemonController::State DaemonControllerMac::GetState() { + pid_t job_pid = base::mac::PIDForJob(kServiceName); + if (job_pid < 0) { + return DaemonController::STATE_NOT_INSTALLED; + } else if (job_pid == 0) { + // Service is stopped, or a start attempt failed. + return DaemonController::STATE_STOPPED; + } else { + return DaemonController::STATE_STARTED; + } +} + +void DaemonControllerMac::GetConfig(const GetConfigCallback& callback) { + // base::Unretained() is safe, since this object owns the thread and therefore + // outlives it. + auth_thread_.message_loop_proxy()->PostTask( + FROM_HERE, + base::Bind(&DaemonControllerMac::DoGetConfig, base::Unretained(this), + callback)); +} + +void DaemonControllerMac::SetConfigAndStart( + scoped_ptr<base::DictionaryValue> config, + bool /* consent */, + const CompletionCallback& done) { + auth_thread_.message_loop_proxy()->PostTask( + FROM_HERE, base::Bind( + &DaemonControllerMac::DoSetConfigAndStart, base::Unretained(this), + base::Passed(&config), done)); +} + +void DaemonControllerMac::UpdateConfig( + scoped_ptr<base::DictionaryValue> config, + const CompletionCallback& done_callback) { + auth_thread_.message_loop()->PostTask(FROM_HERE, base::Bind( + &DaemonControllerMac::DoUpdateConfig, base::Unretained(this), + base::Passed(&config), done_callback)); +} + +void DaemonControllerMac::Stop(const CompletionCallback& done_callback) { + auth_thread_.message_loop_proxy()->PostTask( + FROM_HERE, base::Bind( + &DaemonControllerMac::DoStop, base::Unretained(this), done_callback)); +} + +void DaemonControllerMac::SetWindow(void* window_handle) { + // noop +} + +void DaemonControllerMac::GetVersion(const GetVersionCallback& callback) { + auth_thread_.message_loop_proxy()->PostTask( + FROM_HERE, + base::Bind(&DaemonControllerMac::DoGetVersion, base::Unretained(this), + callback)); +} + +void DaemonControllerMac::GetUsageStatsConsent( + const GetUsageStatsConsentCallback& callback) { + // Crash dump collection is not implemented on Mac yet. + // http://crbug.com/130678. + callback.Run(false, false, false); +} + +void DaemonControllerMac::DoGetConfig(const GetConfigCallback& callback) { + FilePath config_path(kHostConfigFilePath); + JsonHostConfig host_config(config_path); + scoped_ptr<base::DictionaryValue> config; + + if (host_config.Read()) { + config.reset(new base::DictionaryValue()); + std::string value; + if (host_config.GetString(kHostIdConfigPath, &value)) + config.get()->SetString(kHostIdConfigPath, value); + if (host_config.GetString(kXmppLoginConfigPath, &value)) + config.get()->SetString(kXmppLoginConfigPath, value); + } + + callback.Run(config.Pass()); +} + +void DaemonControllerMac::DoGetVersion(const GetVersionCallback& callback) { + std::string version = ""; + std::string command_line = remoting::kHostHelperScriptPath; + command_line += " --host-version"; + FILE* script_output = popen(command_line.c_str(), "r"); + if (script_output) { + char buffer[100]; + char* result = fgets(buffer, sizeof(buffer), script_output); + pclose(script_output); + if (result) { + // The string is guaranteed to be null-terminated, but probably contains + // a newline character, which we don't want. + for (int i = 0; result[i]; ++i) { + if (result[i] < ' ') { + result[i] = 0; + break; + } + } + version = result; + } + } + callback.Run(version); +} + +void DaemonControllerMac::DoSetConfigAndStart( + scoped_ptr<base::DictionaryValue> config, + const CompletionCallback& done) { + std::string config_data; + base::JSONWriter::Write(config.get(), &config_data); + ShowPreferencePane(config_data, done); +} + +void DaemonControllerMac::DoUpdateConfig( + scoped_ptr<base::DictionaryValue> config, + const CompletionCallback& done_callback) { + FilePath config_file_path(kHostConfigFilePath); + JsonHostConfig config_file(config_file_path); + if (!config_file.Read()) { + done_callback.Run(RESULT_FAILED); + return; + } + if (!config_file.CopyFrom(config.get())) { + LOG(ERROR) << "Failed to update configuration."; + done_callback.Run(RESULT_FAILED); + return; + } + + std::string config_data = config_file.GetSerializedData(); + ShowPreferencePane(config_data, done_callback); +} + +void DaemonControllerMac::ShowPreferencePane( + const std::string& config_data, const CompletionCallback& done_callback) { + if (DoShowPreferencePane(config_data)) { + RegisterForPreferencePaneNotifications(done_callback); + } else { + done_callback.Run(RESULT_FAILED); + } +} + +bool DaemonControllerMac::DoShowPreferencePane(const std::string& config_data) { + if (!config_data.empty()) { + FilePath config_path; + if (!file_util::GetTempDir(&config_path)) { + LOG(ERROR) << "Failed to get filename for saving configuration data."; + return false; + } + config_path = config_path.Append(kHostConfigFileName); + + int written = file_util::WriteFile(config_path, config_data.data(), + config_data.size()); + if (written != static_cast<int>(config_data.size())) { + LOG(ERROR) << "Failed to save configuration data to: " + << config_path.value(); + return false; + } + } + + FilePath pane_path; + // TODO(lambroslambrou): Use NSPreferencePanesDirectory once we start + // building against SDK 10.6. + if (!base::mac::GetLocalDirectory(NSLibraryDirectory, &pane_path)) { + LOG(ERROR) << "Failed to get directory for local preference panes."; + return false; + } + pane_path = pane_path.Append("PreferencePanes").Append(kPrefPaneFileName); + + FSRef pane_path_ref; + if (!base::mac::FSRefFromPath(pane_path.value(), &pane_path_ref)) { + LOG(ERROR) << "Failed to create FSRef"; + return false; + } + OSStatus status = LSOpenFSRef(&pane_path_ref, NULL); + if (status != noErr) { + OSSTATUS_LOG(ERROR, status) << "LSOpenFSRef failed for path: " + << pane_path.value(); + return false; + } + + CFNotificationCenterRef center = + CFNotificationCenterGetDistributedCenter(); + base::mac::ScopedCFTypeRef<CFStringRef> service_name( + CFStringCreateWithCString(kCFAllocatorDefault, remoting::kServiceName, + kCFStringEncodingUTF8)); + CFNotificationCenterPostNotification(center, service_name, NULL, NULL, + TRUE); + return true; +} + +void DaemonControllerMac::DoStop(const CompletionCallback& done_callback) { + ShowPreferencePane("", done_callback); +} + +// CFNotificationCenterAddObserver ties the thread on which distributed +// notifications are received to the one on which it is first called. +// This is safe because HostNPScriptObject::InvokeAsyncResultCallback +// bounces the invocation to the correct thread, so it doesn't matter +// which thread CompletionCallbacks are called on. +void DaemonControllerMac::RegisterForPreferencePaneNotifications( + const CompletionCallback& done_callback) { + // We can only have one callback registered at a time. This is enforced by the + // UX flow of the web-app. + DCHECK(current_callback_.is_null()); + current_callback_ = done_callback; + + CFNotificationCenterAddObserver( + CFNotificationCenterGetDistributedCenter(), + this, + &DaemonControllerMac::PreferencePaneCallback, + CFSTR(UPDATE_SUCCEEDED_NOTIFICATION_NAME), + NULL, + CFNotificationSuspensionBehaviorDeliverImmediately); + CFNotificationCenterAddObserver( + CFNotificationCenterGetDistributedCenter(), + this, + &DaemonControllerMac::PreferencePaneCallback, + CFSTR(UPDATE_FAILED_NOTIFICATION_NAME), + NULL, + CFNotificationSuspensionBehaviorDeliverImmediately); +} + +void DaemonControllerMac::PreferencePaneCallbackDelegate(CFStringRef name) { + AsyncResult result = RESULT_FAILED; + if (CFStringCompare(name, CFSTR(UPDATE_SUCCEEDED_NOTIFICATION_NAME), 0) == + kCFCompareEqualTo) { + result = RESULT_OK; + } else if (CFStringCompare(name, CFSTR(UPDATE_FAILED_NOTIFICATION_NAME), 0) == + kCFCompareEqualTo) { + result = RESULT_FAILED; + } else { + LOG(WARNING) << "Ignoring unexpected notification: " << name; + return; + } + DCHECK(!current_callback_.is_null()); + current_callback_.Run(result); + current_callback_.Reset(); + DeregisterForPreferencePaneNotifications(); +} + +void DaemonControllerMac::PreferencePaneCallback(CFNotificationCenterRef center, + void* observer, + CFStringRef name, + const void* object, + CFDictionaryRef user_info) { + DaemonControllerMac* self = reinterpret_cast<DaemonControllerMac*>(observer); + if (self) { + self->PreferencePaneCallbackDelegate(name); + } else { + LOG(WARNING) << "Ignoring notification with NULL observer: " << name; + } +} + +} // namespace + +scoped_ptr<DaemonController> remoting::DaemonController::Create() { + return scoped_ptr<DaemonController>(new DaemonControllerMac()); +} + +} // namespace remoting diff --git a/remoting/host/setup/daemon_controller_win.cc b/remoting/host/setup/daemon_controller_win.cc new file mode 100644 index 0000000..64973ba --- /dev/null +++ b/remoting/host/setup/daemon_controller_win.cc @@ -0,0 +1,698 @@ +// 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_controller.h" + +#include <objbase.h> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/compiler_specific.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/json/json_reader.h" +#include "base/json/json_writer.h" +#include "base/logging.h" +#include "base/string16.h" +#include "base/threading/thread.h" +#include "base/time.h" +#include "base/timer.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "base/win/scoped_bstr.h" +#include "base/win/scoped_comptr.h" +#include "base/win/windows_version.h" +#include "remoting/base/scoped_sc_handle_win.h" +#include "remoting/host/branding.h" +#include "remoting/host/plugin/daemon_installer_win.h" +#include "remoting/host/usage_stats_consent.h" + +// MIDL-generated declarations and definitions. +#include "remoting/host/elevated_controller.h" + +using base::win::ScopedBstr; +using base::win::ScopedComPtr; + +namespace remoting { + +namespace { + +// ProgID of the daemon controller. +const wchar_t kDaemonController[] = + L"ChromotingElevatedController.ElevatedController"; + +// The COM elevation moniker for the Elevated Controller. +const wchar_t kDaemonControllerElevationMoniker[] = + L"Elevation:Administrator!new:" + L"ChromotingElevatedController.ElevatedController"; + +// Name of the Daemon Controller's worker thread. +const char kDaemonControllerThreadName[] = "Daemon Controller thread"; + +// The maximum duration of keeping a reference to a privileged instance of +// the Daemon Controller. This effectively reduces number of UAC prompts a user +// sees. +const int kPrivilegedTimeoutSec = 5 * 60; + +// The maximum duration of keeping a reference to an unprivileged instance of +// the Daemon Controller. This interval should not be too long. If upgrade +// happens while there is a live reference to a Daemon Controller instance +// the old binary still can be used. So dropping the references often makes sure +// that the old binary will go away sooner. +const int kUnprivilegedTimeoutSec = 60; + +// A base::Thread implementation that initializes COM on the new thread. +class ComThread : public base::Thread { + public: + explicit ComThread(const char* name); + + bool Start(); + + protected: + virtual void Init() OVERRIDE; + virtual void CleanUp() OVERRIDE; + + DISALLOW_COPY_AND_ASSIGN(ComThread); +}; + +class DaemonControllerWin : public remoting::DaemonController { + public: + DaemonControllerWin(); + virtual ~DaemonControllerWin(); + + virtual State GetState() OVERRIDE; + virtual void GetConfig(const GetConfigCallback& callback) OVERRIDE; + virtual void SetConfigAndStart( + scoped_ptr<base::DictionaryValue> config, + bool consent, + const CompletionCallback& done) OVERRIDE; + virtual void UpdateConfig(scoped_ptr<base::DictionaryValue> config, + const CompletionCallback& done_callback) OVERRIDE; + virtual void Stop(const CompletionCallback& done_callback) OVERRIDE; + virtual void SetWindow(void* window_handle) OVERRIDE; + virtual void GetVersion(const GetVersionCallback& done_callback) OVERRIDE; + virtual void GetUsageStatsConsent( + const GetUsageStatsConsentCallback& done) OVERRIDE; + + private: + // Activates an unprivileged instance of the daemon controller and caches it. + HRESULT ActivateController(); + + // Activates an instance of the daemon controller and caches it. If COM + // Elevation is supported (Vista+) the activated instance is elevated, + // otherwise it is activated under credentials of the caller. + HRESULT ActivateElevatedController(); + + // Releases the cached instance of the controller. + void ReleaseController(); + + // Procedes with the daemon configuration if the installation succeeded, + // otherwise reports the error. + void OnInstallationComplete(scoped_ptr<base::DictionaryValue> config, + bool consent, + const CompletionCallback& done, + HRESULT result); + + // Opens the Chromoting service returning its handle in |service_out|. + DWORD OpenService(ScopedScHandle* service_out); + + // Converts a config dictionary to a scoped BSTR. + static void ConfigToString(const base::DictionaryValue& config, + ScopedBstr* out); + + // Converts a Windows service status code to a Daemon state. + static State ConvertToDaemonState(DWORD service_state); + + // Converts HRESULT to the AsyncResult. + static AsyncResult HResultToAsyncResult(HRESULT hr); + + // The functions that actually do the work. They should be called in + // the context of |worker_thread_|; + void DoGetConfig(const GetConfigCallback& callback); + void DoInstallAsNeededAndStart(scoped_ptr<base::DictionaryValue> config, + bool consent, + const CompletionCallback& done_callback); + void DoSetConfigAndStart(scoped_ptr<base::DictionaryValue> config, + bool consent, + const CompletionCallback& done); + void DoUpdateConfig(scoped_ptr<base::DictionaryValue> config, + const CompletionCallback& done_callback); + void DoStop(const CompletionCallback& done_callback); + void DoSetWindow(void* window_handle); + void DoGetVersion(const GetVersionCallback& callback); + void DoGetUsageStatsConsent( + const GetUsageStatsConsentCallback& done); + + // |control_| and |control2_| hold references to an instance of the daemon + // controller to prevent a UAC prompt on every operation. + ScopedComPtr<IDaemonControl> control_; + ScopedComPtr<IDaemonControl2> control2_; + + // True if |control_| holds a reference to an elevated instance of the daemon + // controller. + bool control_is_elevated_; + + // This timer is used to release |control_| after a timeout. + scoped_ptr<base::OneShotTimer<DaemonControllerWin> > release_timer_; + + // Handle of the plugin window. + HWND window_handle_; + + // The worker thread used for servicing long running operations. + ComThread worker_thread_; + + scoped_ptr<DaemonInstallerWin> installer_; + + DISALLOW_COPY_AND_ASSIGN(DaemonControllerWin); +}; + +ComThread::ComThread(const char* name) : base::Thread(name) { +} + +bool ComThread::Start() { + // N.B. The single threaded COM apartment must be run on a UI message loop. + base::Thread::Options thread_options(MessageLoop::TYPE_UI, 0); + return StartWithOptions(thread_options); +} + +void ComThread::Init() { + CoInitialize(NULL); +} + +void ComThread::CleanUp() { + CoUninitialize(); +} + +DaemonControllerWin::DaemonControllerWin() + : control_is_elevated_(false), + window_handle_(NULL), + worker_thread_(kDaemonControllerThreadName) { + if (!worker_thread_.Start()) { + LOG(FATAL) << "Failed to start the Daemon Controller worker thread."; + } +} + +DaemonControllerWin::~DaemonControllerWin() { + // Clean up resources allocated on the worker thread. + worker_thread_.message_loop_proxy()->PostTask( + FROM_HERE, + base::Bind(&DaemonControllerWin::ReleaseController, + base::Unretained(this))); + worker_thread_.Stop(); +} + +remoting::DaemonController::State DaemonControllerWin::GetState() { + if (base::win::GetVersion() < base::win::VERSION_XP) { + return STATE_NOT_IMPLEMENTED; + } + // TODO(alexeypa): Make the thread alertable, so we can switch to APC + // notifications rather than polling. + ScopedScHandle service; + DWORD error = OpenService(&service); + + switch (error) { + case ERROR_SUCCESS: { + SERVICE_STATUS status; + if (::QueryServiceStatus(service, &status)) { + return ConvertToDaemonState(status.dwCurrentState); + } else { + LOG_GETLASTERROR(ERROR) + << "Failed to query the state of the '" << kWindowsServiceName + << "' service"; + return STATE_UNKNOWN; + } + break; + } + case ERROR_SERVICE_DOES_NOT_EXIST: + return STATE_NOT_INSTALLED; + default: + return STATE_UNKNOWN; + } +} + +void DaemonControllerWin::GetConfig(const GetConfigCallback& callback) { + worker_thread_.message_loop_proxy()->PostTask( + FROM_HERE, + base::Bind(&DaemonControllerWin::DoGetConfig, + base::Unretained(this), callback)); +} + +void DaemonControllerWin::SetConfigAndStart( + scoped_ptr<base::DictionaryValue> config, + bool consent, + const CompletionCallback& done) { + worker_thread_.message_loop_proxy()->PostTask( + FROM_HERE, base::Bind( + &DaemonControllerWin::DoInstallAsNeededAndStart, + base::Unretained(this), base::Passed(&config), consent, done)); +} + +void DaemonControllerWin::UpdateConfig( + scoped_ptr<base::DictionaryValue> config, + const CompletionCallback& done_callback) { + worker_thread_.message_loop_proxy()->PostTask( + FROM_HERE, base::Bind( + &DaemonControllerWin::DoUpdateConfig, + base::Unretained(this), base::Passed(&config), done_callback)); +} + +void DaemonControllerWin::Stop(const CompletionCallback& done_callback) { + worker_thread_.message_loop_proxy()->PostTask( + FROM_HERE, base::Bind( + &DaemonControllerWin::DoStop, base::Unretained(this), + done_callback)); +} + +void DaemonControllerWin::SetWindow(void* window_handle) { + worker_thread_.message_loop_proxy()->PostTask( + FROM_HERE, base::Bind( + &DaemonControllerWin::DoSetWindow, base::Unretained(this), + window_handle)); +} + +void DaemonControllerWin::GetVersion(const GetVersionCallback& callback) { + worker_thread_.message_loop_proxy()->PostTask( + FROM_HERE, + base::Bind(&DaemonControllerWin::DoGetVersion, + base::Unretained(this), callback)); +} + +void DaemonControllerWin::GetUsageStatsConsent( + const GetUsageStatsConsentCallback& done) { + worker_thread_.message_loop_proxy()->PostTask( + FROM_HERE, + base::Bind(&DaemonControllerWin::DoGetUsageStatsConsent, + base::Unretained(this), done)); +} + +HRESULT DaemonControllerWin::ActivateController() { + DCHECK(worker_thread_.message_loop_proxy()->BelongsToCurrentThread()); + + if (control_.get() == NULL) { + CLSID class_id; + HRESULT hr = CLSIDFromProgID(kDaemonController, &class_id); + if (FAILED(hr)) { + return hr; + } + + hr = CoCreateInstance(class_id, NULL, CLSCTX_LOCAL_SERVER, + IID_IDaemonControl, control_.ReceiveVoid()); + if (FAILED(hr)) { + return hr; + } + + // Ignore the error. IID_IDaemonControl2 is optional. + control_.QueryInterface(IID_IDaemonControl2, control2_.ReceiveVoid()); + + // Release |control_| upon expiration of the timeout. + release_timer_.reset(new base::OneShotTimer<DaemonControllerWin>()); + release_timer_->Start(FROM_HERE, + base::TimeDelta::FromSeconds(kUnprivilegedTimeoutSec), + this, + &DaemonControllerWin::ReleaseController); + } + + return S_OK; +} + +HRESULT DaemonControllerWin::ActivateElevatedController() { + DCHECK(worker_thread_.message_loop_proxy()->BelongsToCurrentThread()); + + // The COM elevation is supported on Vista and above. + if (base::win::GetVersion() < base::win::VERSION_VISTA) { + return ActivateController(); + } + + // Release an unprivileged instance of the daemon controller if any. + if (!control_is_elevated_) { + ReleaseController(); + } + + if (control_.get() == NULL) { + 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; + + HRESULT hr = ::CoGetObject( + kDaemonControllerElevationMoniker, + &bind_options, + IID_IDaemonControl, + control_.ReceiveVoid()); + if (FAILED(hr)) { + return hr; + } + + // Ignore the error. IID_IDaemonControl2 is optional. + control_.QueryInterface(IID_IDaemonControl2, control2_.ReceiveVoid()); + + // Note that we hold a reference to an elevated instance now. + control_is_elevated_ = true; + + // Release |control_| upon expiration of the timeout. + release_timer_.reset(new base::OneShotTimer<DaemonControllerWin>()); + release_timer_->Start(FROM_HERE, + base::TimeDelta::FromSeconds(kPrivilegedTimeoutSec), + this, + &DaemonControllerWin::ReleaseController); + } + + return S_OK; +} + +void DaemonControllerWin::ReleaseController() { + DCHECK(worker_thread_.message_loop_proxy()->BelongsToCurrentThread()); + + control_.Release(); + control2_.Release(); + release_timer_.reset(); + control_is_elevated_ = false; +} + +void DaemonControllerWin::OnInstallationComplete( + scoped_ptr<base::DictionaryValue> config, + bool consent, + const CompletionCallback& done, + HRESULT result) { + DCHECK(worker_thread_.message_loop_proxy()->BelongsToCurrentThread()); + + if (SUCCEEDED(result)) { + DoSetConfigAndStart(config.Pass(), consent, done); + } else { + LOG(ERROR) << "Failed to install the Chromoting Host " + << "(error: 0x" << std::hex << result << std::dec << ")."; + done.Run(HResultToAsyncResult(result)); + } + + DCHECK(installer_.get() != NULL); + installer_.reset(); +} + +DWORD DaemonControllerWin::OpenService(ScopedScHandle* service_out) { + // Open the service and query its current state. + ScopedScHandle scmanager( + ::OpenSCManagerW(NULL, SERVICES_ACTIVE_DATABASE, + SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE)); + if (!scmanager.IsValid()) { + DWORD error = GetLastError(); + LOG_GETLASTERROR(ERROR) + << "Failed to connect to the service control manager"; + return error; + } + + ScopedScHandle service( + ::OpenServiceW(scmanager, kWindowsServiceName, SERVICE_QUERY_STATUS)); + if (!service.IsValid()) { + DWORD error = GetLastError(); + if (error != ERROR_SERVICE_DOES_NOT_EXIST) { + LOG_GETLASTERROR(ERROR) + << "Failed to open to the '" << kWindowsServiceName << "' service"; + } + return error; + } + + service_out->Set(service.Take()); + return ERROR_SUCCESS; +} + +// static +void DaemonControllerWin::ConfigToString(const base::DictionaryValue& config, + ScopedBstr* out) { + std::string config_str; + base::JSONWriter::Write(&config, &config_str); + ScopedBstr config_scoped_bstr(UTF8ToUTF16(config_str).c_str()); + out->Swap(config_scoped_bstr); +} + +// static +remoting::DaemonController::State DaemonControllerWin::ConvertToDaemonState( + DWORD service_state) { + switch (service_state) { + case SERVICE_RUNNING: + return STATE_STARTED; + + case SERVICE_CONTINUE_PENDING: + case SERVICE_START_PENDING: + return STATE_STARTING; + break; + + case SERVICE_PAUSE_PENDING: + case SERVICE_STOP_PENDING: + return STATE_STOPPING; + break; + + case SERVICE_PAUSED: + case SERVICE_STOPPED: + return STATE_STOPPED; + break; + + default: + NOTREACHED(); + return STATE_UNKNOWN; + } +} + +// static +DaemonController::AsyncResult DaemonControllerWin::HResultToAsyncResult( + HRESULT hr) { + // TODO(sergeyu): Report other errors to the webapp once it knows + // how to handle them. + return FAILED(hr) ? RESULT_FAILED : RESULT_OK; +} + +void DaemonControllerWin::DoGetConfig(const GetConfigCallback& callback) { + DCHECK(worker_thread_.message_loop_proxy()->BelongsToCurrentThread()); + + scoped_ptr<base::DictionaryValue> dictionary_null(NULL); + + // Configure and start the Daemon Controller if it is installed already. + HRESULT hr = ActivateController(); + if (FAILED(hr)) { + callback.Run(dictionary_null.Pass()); + return; + } + + // Get the host configuration. + ScopedBstr host_config; + hr = control_->GetConfig(host_config.Receive()); + if (FAILED(hr)) { + callback.Run(dictionary_null.Pass()); + return; + } + + // Parse the string into a dictionary. + string16 file_content(static_cast<BSTR>(host_config), host_config.Length()); + scoped_ptr<base::Value> config( + base::JSONReader::Read(UTF16ToUTF8(file_content), + base::JSON_ALLOW_TRAILING_COMMAS)); + + base::DictionaryValue* dictionary; + if (config.get() == NULL || !config->GetAsDictionary(&dictionary)) { + callback.Run(dictionary_null.Pass()); + return; + } + // Release |config|, because dictionary points to the same object. + config.release(); + + callback.Run(scoped_ptr<base::DictionaryValue>(dictionary)); +} + +void DaemonControllerWin::DoInstallAsNeededAndStart( + scoped_ptr<base::DictionaryValue> config, + bool consent, + const CompletionCallback& done) { + DCHECK(worker_thread_.message_loop_proxy()->BelongsToCurrentThread()); + + // Configure and start the Daemon Controller if it is installed already. + HRESULT hr = ActivateElevatedController(); + if (SUCCEEDED(hr)) { + DoSetConfigAndStart(config.Pass(), consent, done); + return; + } + + // Otherwise, install it if its COM registration entry is missing. + if (hr == CO_E_CLASSSTRING) { + scoped_ptr<DaemonInstallerWin> installer = DaemonInstallerWin::Create( + GetTopLevelWindow(window_handle_), + base::Bind(&DaemonControllerWin::OnInstallationComplete, + base::Unretained(this), + base::Passed(&config), + consent, + done)); + if (installer.get()) { + DCHECK(!installer_.get()); + installer_ = installer.Pass(); + installer_->Install(); + } + } else { + LOG(ERROR) << "Failed to initiate the Chromoting Host installation " + << "(error: 0x" << std::hex << hr << std::dec << ")."; + done.Run(HResultToAsyncResult(hr)); + } +} + +void DaemonControllerWin::DoSetConfigAndStart( + scoped_ptr<base::DictionaryValue> config, + bool consent, + const CompletionCallback& done) { + DCHECK(worker_thread_.message_loop_proxy()->BelongsToCurrentThread()); + + HRESULT hr = ActivateElevatedController(); + if (FAILED(hr)) { + done.Run(HResultToAsyncResult(hr)); + return; + } + + // Record the user's consent. + if (control2_.get()) { + hr = control2_->SetUsageStatsConsent(consent); + if (FAILED(hr)) { + done.Run(HResultToAsyncResult(hr)); + return; + } + } + + // Set the configuration. + ScopedBstr config_str(NULL); + ConfigToString(*config, &config_str); + if (config_str == NULL) { + done.Run(HResultToAsyncResult(E_OUTOFMEMORY)); + return; + } + + hr = control_->SetOwnerWindow( + reinterpret_cast<LONG_PTR>(GetTopLevelWindow(window_handle_))); + if (FAILED(hr)) { + done.Run(HResultToAsyncResult(hr)); + return; + } + + hr = control_->SetConfig(config_str); + if (FAILED(hr)) { + done.Run(HResultToAsyncResult(hr)); + return; + } + + // Start daemon. + hr = control_->StartDaemon(); + done.Run(HResultToAsyncResult(hr)); +} + +void DaemonControllerWin::DoUpdateConfig( + scoped_ptr<base::DictionaryValue> config, + const CompletionCallback& done_callback) { + DCHECK(worker_thread_.message_loop_proxy()->BelongsToCurrentThread()); + + HRESULT hr = ActivateElevatedController(); + if (FAILED(hr)) { + done_callback.Run(HResultToAsyncResult(hr)); + return; + } + + // Update the configuration. + ScopedBstr config_str(NULL); + ConfigToString(*config, &config_str); + if (config_str == NULL) { + done_callback.Run(HResultToAsyncResult(E_OUTOFMEMORY)); + return; + } + + // Make sure that the PIN confirmation dialog is focused properly. + hr = control_->SetOwnerWindow( + reinterpret_cast<LONG_PTR>(GetTopLevelWindow(window_handle_))); + if (FAILED(hr)) { + done_callback.Run(HResultToAsyncResult(hr)); + return; + } + + hr = control_->UpdateConfig(config_str); + done_callback.Run(HResultToAsyncResult(hr)); +} + +void DaemonControllerWin::DoStop(const CompletionCallback& done_callback) { + DCHECK(worker_thread_.message_loop_proxy()->BelongsToCurrentThread()); + + HRESULT hr = ActivateElevatedController(); + if (FAILED(hr)) { + done_callback.Run(HResultToAsyncResult(hr)); + return; + } + + hr = control_->StopDaemon(); + done_callback.Run(HResultToAsyncResult(hr)); +} + +void DaemonControllerWin::DoSetWindow(void* window_handle) { + window_handle_ = reinterpret_cast<HWND>(window_handle); +} + +void DaemonControllerWin::DoGetVersion(const GetVersionCallback& callback) { + DCHECK(worker_thread_.message_loop_proxy()->BelongsToCurrentThread()); + + std::string version_null; + + // Configure and start the Daemon Controller if it is installed already. + HRESULT hr = ActivateController(); + if (FAILED(hr)) { + callback.Run(version_null); + return; + } + + // Get the version string. + ScopedBstr version; + hr = control_->GetVersion(version.Receive()); + if (FAILED(hr)) { + callback.Run(version_null); + return; + } + + callback.Run(UTF16ToUTF8( + string16(static_cast<BSTR>(version), version.Length()))); +} + +void DaemonControllerWin::DoGetUsageStatsConsent( + const GetUsageStatsConsentCallback& done) { + DCHECK(worker_thread_.message_loop_proxy()->BelongsToCurrentThread()); + + // Activate the Daemon Controller and see if it supports |IDaemonControl2|. + HRESULT hr = ActivateController(); + if (FAILED(hr)) { + // The host is not installed yet. Assume that the user's consent is not + // recorded yet and set the default value to true. This value will not come + // into effect until the user installs the host and agrees to crash + // dump reporting. + done.Run(true, true, false); + return; + } + + if (control2_.get() == NULL) { + // The host is installed and does not support crash dump reporting. + done.Run(false, false, false); + return; + } + + // Get the recorded user's consent. + BOOL allowed; + BOOL set_by_policy; + hr = control2_->GetUsageStatsConsent(&allowed, &set_by_policy); + if (FAILED(hr)) { + // If the user's consent is not recorded yet, set the default value to true. + // This value will not come into effect until the user agrees to crash + // dump reporting. + done.Run(true, true, false); + return; + } + + done.Run(true, !!allowed, !!set_by_policy); +} + +} // namespace + +scoped_ptr<DaemonController> remoting::DaemonController::Create() { + return scoped_ptr<DaemonController>(new DaemonControllerWin()); +} + +} // namespace remoting |