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