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 32namespace remoting { 33 34namespace { 35 36const 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. 42const int64 kDaemonTimeoutMs = 60000; 43 44// Timeout for commands that require password prompt - 5 minutes. 45const int64 kSudoTimeoutSeconds = 5 * 60; 46 47std::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 56base::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 62bool 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 71bool 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 117bool 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 125DaemonControllerDelegateLinux::DaemonControllerDelegateLinux() { 126} 127 128DaemonControllerDelegateLinux::~DaemonControllerDelegateLinux() { 129} 130 131DaemonController::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 149scoped_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 170void 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 215void 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 237void 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 249void DaemonControllerDelegateLinux::SetWindow(void* window_handle) { 250 // noop 251} 252 253std::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 280DaemonController::UsageStatsConsent 281DaemonControllerDelegateLinux::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 291scoped_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