summaryrefslogtreecommitdiffstats
path: root/remoting/host/setup
diff options
context:
space:
mode:
authorsergeyu@chromium.org <sergeyu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-10-01 18:11:34 +0000
committersergeyu@chromium.org <sergeyu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-10-01 18:11:34 +0000
commit39e537e316d35c2dd5181deb445f0420d4dc23b1 (patch)
treee8d96a98c3548f49bd01235d2032a6d76f2a54d9 /remoting/host/setup
parentc8dd262eaa7ac2a5273c729fc6e16e6ee69c15fa (diff)
downloadchromium_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.h151
-rw-r--r--remoting/host/setup/daemon_controller_linux.cc343
-rw-r--r--remoting/host/setup/daemon_controller_mac.cc368
-rw-r--r--remoting/host/setup/daemon_controller_win.cc698
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