Home | History | Annotate | Download | only in setup
      1 // Copyright 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "remoting/host/setup/daemon_controller_delegate_linux.h"
      6 
      7 #include <unistd.h>
      8 
      9 #include "base/basictypes.h"
     10 #include "base/bind.h"
     11 #include "base/command_line.h"
     12 #include "base/compiler_specific.h"
     13 #include "base/environment.h"
     14 #include "base/file_util.h"
     15 #include "base/files/file_path.h"
     16 #include "base/json/json_writer.h"
     17 #include "base/logging.h"
     18 #include "base/md5.h"
     19 #include "base/process/kill.h"
     20 #include "base/process/launch.h"
     21 #include "base/process/process_handle.h"
     22 #include "base/strings/string_number_conversions.h"
     23 #include "base/strings/string_split.h"
     24 #include "base/strings/string_util.h"
     25 #include "base/thread_task_runner_handle.h"
     26 #include "base/values.h"
     27 #include "net/base/net_util.h"
     28 #include "remoting/host/host_config.h"
     29 #include "remoting/host/json_host_config.h"
     30 #include "remoting/host/usage_stats_consent.h"
     31 
     32 namespace remoting {
     33 
     34 namespace {
     35 
     36 const char kDaemonScript[] =
     37     "/opt/google/chrome-remote-desktop/chrome-remote-desktop";
     38 
     39 // Timeout for running daemon script. The script itself sets a timeout when
     40 // waiting for the host to come online, so the setting here should be at least
     41 // as long.
     42 const int64 kDaemonTimeoutMs = 60000;
     43 
     44 // Timeout for commands that require password prompt - 5 minutes.
     45 const int64 kSudoTimeoutSeconds = 5 * 60;
     46 
     47 std::string GetMd5(const std::string& value) {
     48   base::MD5Context ctx;
     49   base::MD5Init(&ctx);
     50   base::MD5Update(&ctx, value);
     51   base::MD5Digest digest;
     52   base::MD5Final(&digest, &ctx);
     53   return StringToLowerASCII(base::HexEncode(digest.a, sizeof(digest.a)));
     54 }
     55 
     56 base::FilePath GetConfigPath() {
     57   std::string filename = "host#" + GetMd5(net::GetHostName()) + ".json";
     58   return base::GetHomeDir().
     59       Append(".config/chrome-remote-desktop").Append(filename);
     60 }
     61 
     62 bool GetScriptPath(base::FilePath* result) {
     63   base::FilePath candidate_exe(kDaemonScript);
     64   if (access(candidate_exe.value().c_str(), X_OK) == 0) {
     65     *result = candidate_exe;
     66     return true;
     67   }
     68   return false;
     69 }
     70 
     71 bool RunHostScriptWithTimeout(
     72     const std::vector<std::string>& args,
     73     base::TimeDelta timeout,
     74     int* exit_code) {
     75   DCHECK(exit_code);
     76 
     77   // As long as we're relying on running an external binary from the
     78   // PATH, don't do it as root.
     79   if (getuid() == 0) {
     80     LOG(ERROR) << "Refusing to run script as root.";
     81     return false;
     82   }
     83   base::FilePath script_path;
     84   if (!GetScriptPath(&script_path)) {
     85     LOG(ERROR) << "GetScriptPath() failed.";
     86     return false;
     87   }
     88   CommandLine command_line(script_path);
     89   for (unsigned int i = 0; i < args.size(); ++i) {
     90     command_line.AppendArg(args[i]);
     91   }
     92   base::ProcessHandle process_handle;
     93 
     94   // Redirect the child's stdout to the parent's stderr. In the case where this
     95   // parent process is a Native Messaging host, its stdout is used to send
     96   // messages to the web-app.
     97   base::FileHandleMappingVector fds_to_remap;
     98   fds_to_remap.push_back(std::pair<int, int>(STDERR_FILENO, STDOUT_FILENO));
     99   base::LaunchOptions options;
    100   options.fds_to_remap = &fds_to_remap;
    101   if (!base::LaunchProcess(command_line, options, &process_handle)) {
    102     LOG(ERROR) << "Failed to run command: "
    103                << command_line.GetCommandLineString();
    104     return false;
    105   }
    106 
    107   if (!base::WaitForExitCodeWithTimeout(process_handle, exit_code, timeout)) {
    108     base::KillProcess(process_handle, 0, false);
    109     LOG(ERROR) << "Timeout exceeded for command: "
    110                << command_line.GetCommandLineString();
    111     return false;
    112   }
    113 
    114   return true;
    115 }
    116 
    117 bool RunHostScript(const std::vector<std::string>& args,
    118                           int* exit_code) {
    119   return RunHostScriptWithTimeout(
    120       args, base::TimeDelta::FromMilliseconds(kDaemonTimeoutMs), exit_code);
    121 }
    122 
    123 }  // namespace
    124 
    125 DaemonControllerDelegateLinux::DaemonControllerDelegateLinux() {
    126 }
    127 
    128 DaemonControllerDelegateLinux::~DaemonControllerDelegateLinux() {
    129 }
    130 
    131 DaemonController::State DaemonControllerDelegateLinux::GetState() {
    132   std::vector<std::string> args;
    133   args.push_back("--check-running");
    134   int exit_code = 0;
    135   if (!RunHostScript(args, &exit_code)) {
    136     // TODO(jamiewalch): When we have a good story for installing, return
    137     // NOT_INSTALLED rather than NOT_IMPLEMENTED (the former suppresses
    138     // the relevant UI in the web-app).
    139     return DaemonController::STATE_NOT_IMPLEMENTED;
    140   }
    141 
    142   if (exit_code == 0) {
    143     return DaemonController::STATE_STARTED;
    144   } else {
    145     return DaemonController::STATE_STOPPED;
    146   }
    147 }
    148 
    149 scoped_ptr<base::DictionaryValue> DaemonControllerDelegateLinux::GetConfig() {
    150   scoped_ptr<base::DictionaryValue> result(new base::DictionaryValue());
    151 
    152   if (GetState() != DaemonController::STATE_NOT_IMPLEMENTED) {
    153     JsonHostConfig config(GetConfigPath());
    154     if (config.Read()) {
    155       std::string value;
    156       if (config.GetString(kHostIdConfigPath, &value)) {
    157         result->SetString(kHostIdConfigPath, value);
    158       }
    159       if (config.GetString(kXmppLoginConfigPath, &value)) {
    160         result->SetString(kXmppLoginConfigPath, value);
    161       }
    162     } else {
    163       result.reset(); // Return NULL in case of error.
    164     }
    165   }
    166 
    167   return result.Pass();
    168 }
    169 
    170 void DaemonControllerDelegateLinux::SetConfigAndStart(
    171     scoped_ptr<base::DictionaryValue> config,
    172     bool consent,
    173     const DaemonController::CompletionCallback& done) {
    174   // Add the user to chrome-remote-desktop group first.
    175   std::vector<std::string> args;
    176   args.push_back("--add-user");
    177   int exit_code;
    178   if (!RunHostScriptWithTimeout(
    179           args, base::TimeDelta::FromSeconds(kSudoTimeoutSeconds),
    180           &exit_code) ||
    181       exit_code != 0) {
    182     LOG(ERROR) << "Failed to add user to chrome-remote-desktop group.";
    183     done.Run(DaemonController::RESULT_FAILED);
    184     return;
    185   }
    186 
    187   // Ensure the configuration directory exists.
    188   base::FilePath config_dir = GetConfigPath().DirName();
    189   if (!base::DirectoryExists(config_dir) &&
    190       !base::CreateDirectory(config_dir)) {
    191     LOG(ERROR) << "Failed to create config directory " << config_dir.value();
    192     done.Run(DaemonController::RESULT_FAILED);
    193     return;
    194   }
    195 
    196   // Write config.
    197   JsonHostConfig config_file(GetConfigPath());
    198   if (!config_file.CopyFrom(config.get()) ||
    199       !config_file.Save()) {
    200     LOG(ERROR) << "Failed to update config file.";
    201     done.Run(DaemonController::RESULT_FAILED);
    202     return;
    203   }
    204 
    205   // Finally start the host.
    206   args.clear();
    207   args.push_back("--start");
    208   DaemonController::AsyncResult result = DaemonController::RESULT_FAILED;
    209   if (RunHostScript(args, &exit_code) && (exit_code == 0))
    210     result = DaemonController::RESULT_OK;
    211 
    212   done.Run(result);
    213 }
    214 
    215 void DaemonControllerDelegateLinux::UpdateConfig(
    216     scoped_ptr<base::DictionaryValue> config,
    217     const DaemonController::CompletionCallback& done) {
    218   JsonHostConfig config_file(GetConfigPath());
    219   if (!config_file.Read() ||
    220       !config_file.CopyFrom(config.get()) ||
    221       !config_file.Save()) {
    222     LOG(ERROR) << "Failed to update config file.";
    223     done.Run(DaemonController::RESULT_FAILED);
    224     return;
    225   }
    226 
    227   std::vector<std::string> args;
    228   args.push_back("--reload");
    229   int exit_code = 0;
    230   DaemonController::AsyncResult result = DaemonController::RESULT_FAILED;
    231   if (RunHostScript(args, &exit_code) && (exit_code == 0))
    232     result = DaemonController::RESULT_OK;
    233 
    234   done.Run(result);
    235 }
    236 
    237 void DaemonControllerDelegateLinux::Stop(
    238     const DaemonController::CompletionCallback& done) {
    239   std::vector<std::string> args;
    240   args.push_back("--stop");
    241   int exit_code = 0;
    242   DaemonController::AsyncResult result = DaemonController::RESULT_FAILED;
    243   if (RunHostScript(args, &exit_code) && (exit_code == 0))
    244     result = DaemonController::RESULT_OK;
    245 
    246   done.Run(result);
    247 }
    248 
    249 void DaemonControllerDelegateLinux::SetWindow(void* window_handle) {
    250   // noop
    251 }
    252 
    253 std::string DaemonControllerDelegateLinux::GetVersion() {
    254   base::FilePath script_path;
    255   if (!GetScriptPath(&script_path)) {
    256     return std::string();
    257   }
    258   CommandLine command_line(script_path);
    259   command_line.AppendArg("--host-version");
    260 
    261   std::string version;
    262   int exit_code = 0;
    263   int result =
    264       base::GetAppOutputWithExitCode(command_line, &version, &exit_code);
    265   if (!result || exit_code != 0) {
    266     LOG(ERROR) << "Failed to run \"" << command_line.GetCommandLineString()
    267                << "\". Exit code: " << exit_code;
    268     return std::string();
    269   }
    270 
    271   TrimWhitespaceASCII(version, TRIM_ALL, &version);
    272   if (!ContainsOnlyChars(version, "0123456789.")) {
    273     LOG(ERROR) << "Received invalid host version number: " << version;
    274     return std::string();
    275   }
    276 
    277   return version;
    278 }
    279 
    280 DaemonController::UsageStatsConsent
    281 DaemonControllerDelegateLinux::GetUsageStatsConsent() {
    282   // Crash dump collection is not implemented on Linux yet.
    283   // http://crbug.com/130678.
    284   DaemonController::UsageStatsConsent consent;
    285   consent.supported = false;
    286   consent.allowed = false;
    287   consent.set_by_policy = false;
    288   return consent;
    289 }
    290 
    291 scoped_refptr<DaemonController> DaemonController::Create() {
    292   scoped_ptr<DaemonController::Delegate> delegate(
    293       new DaemonControllerDelegateLinux());
    294   return new DaemonController(delegate.Pass());
    295 }
    296 
    297 }  // namespace remoting
    298