root/remoting/host/setup/daemon_controller_delegate_linux.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. GetMd5
  2. GetConfigPath
  3. GetScriptPath
  4. RunHostScriptWithTimeout
  5. RunHostScript
  6. GetState
  7. GetConfig
  8. SetConfigAndStart
  9. UpdateConfig
  10. Stop
  11. SetWindow
  12. GetVersion
  13. GetUsageStatsConsent
  14. Create

// Copyright 2013 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_delegate_linux.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_util.h"
#include "base/files/file_path.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/md5.h"
#include "base/process/kill.h"
#include "base/process/launch.h"
#include "base/process/process_handle.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/thread_task_runner_handle.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. The script itself sets a timeout when
// waiting for the host to come online, so the setting here should be at least
// as long.
const int64 kDaemonTimeoutMs = 60000;

// Timeout for commands that require password prompt - 5 minutes.
const int64 kSudoTimeoutSeconds = 5 * 60;

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)));
}

base::FilePath GetConfigPath() {
  std::string filename = "host#" + GetMd5(net::GetHostName()) + ".json";
  return base::GetHomeDir().
      Append(".config/chrome-remote-desktop").Append(filename);
}

bool GetScriptPath(base::FilePath* result) {
  base::FilePath candidate_exe(kDaemonScript);
  if (access(candidate_exe.value().c_str(), X_OK) == 0) {
    *result = candidate_exe;
    return true;
  }
  return false;
}

bool RunHostScriptWithTimeout(
    const std::vector<std::string>& args,
    base::TimeDelta timeout,
    int* exit_code) {
  DCHECK(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) {
    LOG(ERROR) << "Refusing to run script as root.";
    return false;
  }
  base::FilePath script_path;
  if (!GetScriptPath(&script_path)) {
    LOG(ERROR) << "GetScriptPath() failed.";
    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;

  // Redirect the child's stdout to the parent's stderr. In the case where this
  // parent process is a Native Messaging host, its stdout is used to send
  // messages to the web-app.
  base::FileHandleMappingVector fds_to_remap;
  fds_to_remap.push_back(std::pair<int, int>(STDERR_FILENO, STDOUT_FILENO));
  base::LaunchOptions options;
  options.fds_to_remap = &fds_to_remap;
  if (!base::LaunchProcess(command_line, options, &process_handle)) {
    LOG(ERROR) << "Failed to run command: "
               << command_line.GetCommandLineString();
    return false;
  }

  if (!base::WaitForExitCodeWithTimeout(process_handle, exit_code, timeout)) {
    base::KillProcess(process_handle, 0, false);
    LOG(ERROR) << "Timeout exceeded for command: "
               << command_line.GetCommandLineString();
    return false;
  }

  return true;
}

bool RunHostScript(const std::vector<std::string>& args, int* exit_code) {
  return RunHostScriptWithTimeout(
      args, base::TimeDelta::FromMilliseconds(kDaemonTimeoutMs), exit_code);
}

}  // namespace

DaemonControllerDelegateLinux::DaemonControllerDelegateLinux() {
}

DaemonControllerDelegateLinux::~DaemonControllerDelegateLinux() {
}

DaemonController::State DaemonControllerDelegateLinux::GetState() {
  base::FilePath script_path;
  if (!GetScriptPath(&script_path)) {
    return DaemonController::STATE_NOT_IMPLEMENTED;
  }
  CommandLine command_line(script_path);
  command_line.AppendArg("--get-status");

  std::string status;
  int exit_code = 0;
  bool result =
      base::GetAppOutputWithExitCode(command_line, &status, &exit_code);
  if (!result) {
    // 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 DaemonController::STATE_NOT_IMPLEMENTED;
  }

  if (exit_code != 0) {
    LOG(ERROR) << "Failed to run \"" << command_line.GetCommandLineString()
               << "\". Exit code: " << exit_code;
    return DaemonController::STATE_UNKNOWN;
  }

  base::TrimWhitespaceASCII(status, base::TRIM_ALL, &status);

  if (status == "STARTED") {
    return DaemonController::STATE_STARTED;
  } else if (status == "STOPPED") {
    return DaemonController::STATE_STOPPED;
  } else if (status == "NOT_IMPLEMENTED") {
    return DaemonController::STATE_NOT_IMPLEMENTED;
  } else {
    LOG(ERROR) << "Unknown status string returned from  \""
               << command_line.GetCommandLineString()
               << "\": " << status;
    return DaemonController::STATE_UNKNOWN;
  }
}

scoped_ptr<base::DictionaryValue> DaemonControllerDelegateLinux::GetConfig() {
  scoped_ptr<base::DictionaryValue> result(new base::DictionaryValue());

  if (GetState() != 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.
    }
  }

  return result.Pass();
}

void DaemonControllerDelegateLinux::SetConfigAndStart(
    scoped_ptr<base::DictionaryValue> config,
    bool consent,
    const DaemonController::CompletionCallback& done) {
  // 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::FromSeconds(kSudoTimeoutSeconds),
          &exit_code) ||
      exit_code != 0) {
    LOG(ERROR) << "Failed to add user to chrome-remote-desktop group.";
    done.Run(DaemonController::RESULT_FAILED);
    return;
  }

  // Ensure the configuration directory exists.
  base::FilePath config_dir = GetConfigPath().DirName();
  if (!base::DirectoryExists(config_dir) &&
      !base::CreateDirectory(config_dir)) {
    LOG(ERROR) << "Failed to create config directory " << config_dir.value();
    done.Run(DaemonController::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.Run(DaemonController::RESULT_FAILED);
    return;
  }

  // Finally start the host.
  args.clear();
  args.push_back("--start");
  DaemonController::AsyncResult result = DaemonController::RESULT_FAILED;
  if (RunHostScript(args, &exit_code) && (exit_code == 0))
    result = DaemonController::RESULT_OK;

  done.Run(result);
}

void DaemonControllerDelegateLinux::UpdateConfig(
    scoped_ptr<base::DictionaryValue> config,
    const DaemonController::CompletionCallback& done) {
  JsonHostConfig config_file(GetConfigPath());
  if (!config_file.Read() ||
      !config_file.CopyFrom(config.get()) ||
      !config_file.Save()) {
    LOG(ERROR) << "Failed to update config file.";
    done.Run(DaemonController::RESULT_FAILED);
    return;
  }

  std::vector<std::string> args;
  args.push_back("--reload");
  int exit_code = 0;
  DaemonController::AsyncResult result = DaemonController::RESULT_FAILED;
  if (RunHostScript(args, &exit_code) && (exit_code == 0))
    result = DaemonController::RESULT_OK;

  done.Run(result);
}

void DaemonControllerDelegateLinux::Stop(
    const DaemonController::CompletionCallback& done) {
  std::vector<std::string> args;
  args.push_back("--stop");
  int exit_code = 0;
  DaemonController::AsyncResult result = DaemonController::RESULT_FAILED;
  if (RunHostScript(args, &exit_code) && (exit_code == 0))
    result = DaemonController::RESULT_OK;

  done.Run(result);
}

void DaemonControllerDelegateLinux::SetWindow(void* window_handle) {
  // noop
}

std::string DaemonControllerDelegateLinux::GetVersion() {
  base::FilePath script_path;
  if (!GetScriptPath(&script_path)) {
    return std::string();
  }
  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;
    return std::string();
  }

  base::TrimWhitespaceASCII(version, base::TRIM_ALL, &version);
  if (!base::ContainsOnlyChars(version, "0123456789.")) {
    LOG(ERROR) << "Received invalid host version number: " << version;
    return std::string();
  }

  return version;
}

DaemonController::UsageStatsConsent
DaemonControllerDelegateLinux::GetUsageStatsConsent() {
  // Crash dump collection is not implemented on Linux yet.
  // http://crbug.com/130678.
  DaemonController::UsageStatsConsent consent;
  consent.supported = false;
  consent.allowed = false;
  consent.set_by_policy = false;
  return consent;
}

scoped_refptr<DaemonController> DaemonController::Create() {
  scoped_ptr<DaemonController::Delegate> delegate(
      new DaemonControllerDelegateLinux());
  return new DaemonController(delegate.Pass());
}

}  // namespace remoting

/* [<][>][^][v][top][bottom][index][help] */