• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
35 namespace remoting {
36 
37 namespace {
38 
39 const 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.
45 const int64 kDaemonTimeoutMs = 60000;
46 
47 // Timeout for commands that require password prompt - 5 minutes.
48 const int64 kSudoTimeoutSeconds = 5 * 60;
49 
GetMd5(const std::string & value)50 std::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 
GetConfigPath()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);
64 }
65 
GetScriptPath(base::FilePath * result)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;
70     return true;
71   }
72   return false;
73 }
74 
RunHostScriptWithTimeout(const std::vector<std::string> & args,base::TimeDelta timeout,int * exit_code)75 bool 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 
RunHostScript(const std::vector<std::string> & args,int * exit_code)126 bool 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 
DaemonControllerDelegateLinux()133 DaemonControllerDelegateLinux::DaemonControllerDelegateLinux() {
134 }
135 
~DaemonControllerDelegateLinux()136 DaemonControllerDelegateLinux::~DaemonControllerDelegateLinux() {
137 }
138 
GetState()139 DaemonController::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 
GetConfig()180 scoped_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 
InstallHost(const DaemonController::CompletionCallback & done)201 void DaemonControllerDelegateLinux::InstallHost(
202     const DaemonController::CompletionCallback& done) {
203   NOTREACHED();
204 }
205 
SetConfigAndStart(scoped_ptr<base::DictionaryValue> config,bool consent,const DaemonController::CompletionCallback & done)206 void 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 
UpdateConfig(scoped_ptr<base::DictionaryValue> config,const DaemonController::CompletionCallback & done)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);
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 
Stop(const DaemonController::CompletionCallback & done)273 void 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 
SetWindow(void * window_handle)285 void DaemonControllerDelegateLinux::SetWindow(void* window_handle) {
286   // noop
287 }
288 
GetVersion()289 std::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 
316 DaemonController::UsageStatsConsent
GetUsageStatsConsent()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;
324   return consent;
325 }
326 
Create()327 scoped_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