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.
5 #include "remoting/host/setup/daemon_controller_delegate_linux.h"
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/file_util.h"
16 #include "base/files/file_path.h"
17 #include "base/json/json_writer.h"
18 #include "base/logging.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"
39 const char kDaemonScript
[] =
40 "/opt/google/chrome-remote-desktop/chrome-remote-desktop";
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
45 const int64 kDaemonTimeoutMs
= 60000;
47 // Timeout for commands that require password prompt - 5 minutes.
48 const int64 kSudoTimeoutSeconds
= 5 * 60;
50 std::string
GetMd5(const std::string
& value
) {
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
)));
59 base::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
);
66 bool 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
;
75 bool RunHostScriptWithTimeout(
76 const std::vector
<std::string
>& args
,
77 base::TimeDelta timeout
,
81 // As long as we're relying on running an external binary from the
82 // PATH, don't do it as root.
84 LOG(ERROR
) << "Refusing to run script as root.";
87 base::FilePath script_path
;
88 if (!GetScriptPath(&script_path
)) {
89 LOG(ERROR
) << "GetScriptPath() failed.";
92 base::CommandLine
command_line(script_path
);
93 for (unsigned int i
= 0; i
< args
.size(); ++i
) {
94 command_line
.AppendArg(args
[i
]);
96 base::ProcessHandle process_handle
;
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
;
106 #if !defined(OS_CHROMEOS)
107 options
.allow_new_privs
= true;
110 if (!base::LaunchProcess(command_line
, options
, &process_handle
)) {
111 LOG(ERROR
) << "Failed to run command: "
112 << command_line
.GetCommandLineString();
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();
126 bool RunHostScript(const std::vector
<std::string
>& args
, int* exit_code
) {
127 return RunHostScriptWithTimeout(
128 args
, base::TimeDelta::FromMilliseconds(kDaemonTimeoutMs
), exit_code
);
133 DaemonControllerDelegateLinux::DaemonControllerDelegateLinux() {
136 DaemonControllerDelegateLinux::~DaemonControllerDelegateLinux() {
139 DaemonController::State
DaemonControllerDelegateLinux::GetState() {
140 base::FilePath script_path
;
141 if (!GetScriptPath(&script_path
)) {
142 return DaemonController::STATE_NOT_IMPLEMENTED
;
144 base::CommandLine
command_line(script_path
);
145 command_line
.AppendArg("--get-status");
150 base::GetAppOutputWithExitCode(command_line
, &status
, &exit_code
);
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
;
158 if (exit_code
!= 0) {
159 LOG(ERROR
) << "Failed to run \"" << command_line
.GetCommandLineString()
160 << "\". Exit code: " << exit_code
;
161 return DaemonController::STATE_UNKNOWN
;
164 base::TrimWhitespaceASCII(status
, base::TRIM_ALL
, &status
);
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
;
173 LOG(ERROR
) << "Unknown status string returned from \""
174 << command_line
.GetCommandLineString()
176 return DaemonController::STATE_UNKNOWN
;
180 scoped_ptr
<base::DictionaryValue
> DaemonControllerDelegateLinux::GetConfig() {
181 scoped_ptr
<base::DictionaryValue
> result(new base::DictionaryValue());
183 if (GetState() != DaemonController::STATE_NOT_IMPLEMENTED
) {
184 JsonHostConfig
config(GetConfigPath());
187 if (config
.GetString(kHostIdConfigPath
, &value
)) {
188 result
->SetString(kHostIdConfigPath
, value
);
190 if (config
.GetString(kXmppLoginConfigPath
, &value
)) {
191 result
->SetString(kXmppLoginConfigPath
, value
);
194 result
.reset(); // Return NULL in case of error.
198 return result
.Pass();
201 void DaemonControllerDelegateLinux::InstallHost(
202 const DaemonController::CompletionCallback
& done
) {
206 void DaemonControllerDelegateLinux::SetConfigAndStart(
207 scoped_ptr
<base::DictionaryValue
> config
,
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");
214 if (!RunHostScriptWithTimeout(
215 args
, base::TimeDelta::FromSeconds(kSudoTimeoutSeconds
),
218 LOG(ERROR
) << "Failed to add user to chrome-remote-desktop group.";
219 done
.Run(DaemonController::RESULT_FAILED
);
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
);
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
);
241 // Finally start the host.
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
;
251 void 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
);
263 std::vector
<std::string
> args
;
264 args
.push_back("--reload");
266 DaemonController::AsyncResult result
= DaemonController::RESULT_FAILED
;
267 if (RunHostScript(args
, &exit_code
) && (exit_code
== 0))
268 result
= DaemonController::RESULT_OK
;
273 void DaemonControllerDelegateLinux::Stop(
274 const DaemonController::CompletionCallback
& done
) {
275 std::vector
<std::string
> args
;
276 args
.push_back("--stop");
278 DaemonController::AsyncResult result
= DaemonController::RESULT_FAILED
;
279 if (RunHostScript(args
, &exit_code
) && (exit_code
== 0))
280 result
= DaemonController::RESULT_OK
;
285 void DaemonControllerDelegateLinux::SetWindow(void* window_handle
) {
289 std::string
DaemonControllerDelegateLinux::GetVersion() {
290 base::FilePath script_path
;
291 if (!GetScriptPath(&script_path
)) {
292 return std::string();
294 base::CommandLine
command_line(script_path
);
295 command_line
.AppendArg("--host-version");
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();
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();
316 DaemonController::UsageStatsConsent
317 DaemonControllerDelegateLinux::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;
327 scoped_refptr
<DaemonController
> DaemonController::Create() {
328 scoped_ptr
<DaemonController::Delegate
> delegate(
329 new DaemonControllerDelegateLinux());
330 return new DaemonController(delegate
.Pass());
333 } // namespace remoting