OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 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.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/threading/thread.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 class DaemonControllerLinux : public remoting::DaemonController { | |
57 public: | |
58 DaemonControllerLinux(); | |
59 | |
60 virtual State GetState() OVERRIDE; | |
61 virtual void GetConfig(const GetConfigCallback& callback) OVERRIDE; | |
62 virtual void SetConfigAndStart( | |
63 scoped_ptr<base::DictionaryValue> config, | |
64 bool consent, | |
65 const CompletionCallback& done) OVERRIDE; | |
66 virtual void UpdateConfig(scoped_ptr<base::DictionaryValue> config, | |
67 const CompletionCallback& done_callback) OVERRIDE; | |
68 virtual void Stop(const CompletionCallback& done_callback) OVERRIDE; | |
69 virtual void SetWindow(void* window_handle) OVERRIDE; | |
70 virtual void GetVersion(const GetVersionCallback& done_callback) OVERRIDE; | |
71 virtual void GetUsageStatsConsent( | |
72 const GetUsageStatsConsentCallback& done) OVERRIDE; | |
73 | |
74 private: | |
75 base::FilePath GetConfigPath(); | |
76 | |
77 void DoGetConfig(const GetConfigCallback& callback); | |
78 void DoSetConfigAndStart(scoped_ptr<base::DictionaryValue> config, | |
79 const CompletionCallback& done); | |
80 void DoUpdateConfig(scoped_ptr<base::DictionaryValue> config, | |
81 const CompletionCallback& done_callback); | |
82 void DoStop(const CompletionCallback& done_callback); | |
83 void DoGetVersion(const GetVersionCallback& done_callback); | |
84 | |
85 base::Thread file_io_thread_; | |
86 | |
87 DISALLOW_COPY_AND_ASSIGN(DaemonControllerLinux); | |
88 }; | |
89 | |
90 DaemonControllerLinux::DaemonControllerLinux() | |
91 : file_io_thread_("DaemonControllerFileIO") { | |
92 file_io_thread_.Start(); | |
93 } | |
94 | |
95 static bool GetScriptPath(base::FilePath* result) { | |
96 base::FilePath candidate_exe(kDaemonScript); | |
97 if (access(candidate_exe.value().c_str(), X_OK) == 0) { | |
98 *result = candidate_exe; | |
99 return true; | |
100 } | |
101 return false; | |
102 } | |
103 | |
104 static bool RunHostScriptWithTimeout( | |
105 const std::vector<std::string>& args, | |
106 base::TimeDelta timeout, | |
107 int* exit_code) { | |
108 DCHECK(exit_code); | |
109 | |
110 // As long as we're relying on running an external binary from the | |
111 // PATH, don't do it as root. | |
112 if (getuid() == 0) { | |
113 LOG(ERROR) << "Refusing to run script as root."; | |
114 return false; | |
115 } | |
116 base::FilePath script_path; | |
117 if (!GetScriptPath(&script_path)) { | |
118 LOG(ERROR) << "GetScriptPath() failed."; | |
119 return false; | |
120 } | |
121 CommandLine command_line(script_path); | |
122 for (unsigned int i = 0; i < args.size(); ++i) { | |
123 command_line.AppendArg(args[i]); | |
124 } | |
125 base::ProcessHandle process_handle; | |
126 | |
127 // Redirect the child's stdout to the parent's stderr. In the case where this | |
128 // parent process is a Native Messaging host, its stdout is used to send | |
129 // messages to the web-app. | |
130 base::FileHandleMappingVector fds_to_remap; | |
131 fds_to_remap.push_back(std::pair<int, int>(STDERR_FILENO, STDOUT_FILENO)); | |
132 base::LaunchOptions options; | |
133 options.fds_to_remap = &fds_to_remap; | |
134 if (!base::LaunchProcess(command_line, options, &process_handle)) { | |
135 LOG(ERROR) << "Failed to run command: " | |
136 << command_line.GetCommandLineString(); | |
137 return false; | |
138 } | |
139 | |
140 if (!base::WaitForExitCodeWithTimeout(process_handle, exit_code, timeout)) { | |
141 base::KillProcess(process_handle, 0, false); | |
142 LOG(ERROR) << "Timeout exceeded for command: " | |
143 << command_line.GetCommandLineString(); | |
144 return false; | |
145 } | |
146 | |
147 return true; | |
148 } | |
149 | |
150 static bool RunHostScript(const std::vector<std::string>& args, | |
151 int* exit_code) { | |
152 return RunHostScriptWithTimeout( | |
153 args, base::TimeDelta::FromMilliseconds(kDaemonTimeoutMs), exit_code); | |
154 } | |
155 | |
156 remoting::DaemonController::State DaemonControllerLinux::GetState() { | |
157 std::vector<std::string> args; | |
158 args.push_back("--check-running"); | |
159 int exit_code = 0; | |
160 if (!RunHostScript(args, &exit_code)) { | |
161 // TODO(jamiewalch): When we have a good story for installing, return | |
162 // NOT_INSTALLED rather than NOT_IMPLEMENTED (the former suppresses | |
163 // the relevant UI in the web-app). | |
164 return remoting::DaemonController::STATE_NOT_IMPLEMENTED; | |
165 } | |
166 | |
167 if (exit_code == 0) { | |
168 return remoting::DaemonController::STATE_STARTED; | |
169 } else { | |
170 return remoting::DaemonController::STATE_STOPPED; | |
171 } | |
172 } | |
173 | |
174 void DaemonControllerLinux::GetConfig(const GetConfigCallback& callback) { | |
175 // base::Unretained() is safe because we control lifetime of the thread. | |
176 file_io_thread_.message_loop()->PostTask(FROM_HERE, base::Bind( | |
177 &DaemonControllerLinux::DoGetConfig, base::Unretained(this), callback)); | |
178 } | |
179 | |
180 void DaemonControllerLinux::GetUsageStatsConsent( | |
181 const GetUsageStatsConsentCallback& done) { | |
182 // Crash dump collection is not implemented on Linux yet. | |
183 // http://crbug.com/130678. | |
184 done.Run(false, false, false); | |
185 } | |
186 | |
187 void DaemonControllerLinux::SetConfigAndStart( | |
188 scoped_ptr<base::DictionaryValue> config, | |
189 bool /* consent */, | |
190 const CompletionCallback& done) { | |
191 // base::Unretained() is safe because we control lifetime of the thread. | |
192 file_io_thread_.message_loop()->PostTask(FROM_HERE, base::Bind( | |
193 &DaemonControllerLinux::DoSetConfigAndStart, base::Unretained(this), | |
194 base::Passed(&config), done)); | |
195 } | |
196 | |
197 void DaemonControllerLinux::UpdateConfig( | |
198 scoped_ptr<base::DictionaryValue> config, | |
199 const CompletionCallback& done_callback) { | |
200 file_io_thread_.message_loop()->PostTask(FROM_HERE, base::Bind( | |
201 &DaemonControllerLinux::DoUpdateConfig, base::Unretained(this), | |
202 base::Passed(&config), done_callback)); | |
203 } | |
204 | |
205 void DaemonControllerLinux::Stop(const CompletionCallback& done_callback) { | |
206 file_io_thread_.message_loop()->PostTask(FROM_HERE, base::Bind( | |
207 &DaemonControllerLinux::DoStop, base::Unretained(this), | |
208 done_callback)); | |
209 } | |
210 | |
211 void DaemonControllerLinux::SetWindow(void* window_handle) { | |
212 // noop | |
213 } | |
214 | |
215 void DaemonControllerLinux::GetVersion( | |
216 const GetVersionCallback& done_callback) { | |
217 file_io_thread_.message_loop()->PostTask(FROM_HERE, base::Bind( | |
218 &DaemonControllerLinux::DoGetVersion, base::Unretained(this), | |
219 done_callback)); | |
220 } | |
221 | |
222 base::FilePath DaemonControllerLinux::GetConfigPath() { | |
223 std::string filename = "host#" + GetMd5(net::GetHostName()) + ".json"; | |
224 return file_util::GetHomeDir(). | |
225 Append(".config/chrome-remote-desktop").Append(filename); | |
226 } | |
227 | |
228 void DaemonControllerLinux::DoGetConfig(const GetConfigCallback& callback) { | |
229 scoped_ptr<base::DictionaryValue> result(new base::DictionaryValue()); | |
230 | |
231 if (GetState() != remoting::DaemonController::STATE_NOT_IMPLEMENTED) { | |
232 JsonHostConfig config(GetConfigPath()); | |
233 if (config.Read()) { | |
234 std::string value; | |
235 if (config.GetString(kHostIdConfigPath, &value)) { | |
236 result->SetString(kHostIdConfigPath, value); | |
237 } | |
238 if (config.GetString(kXmppLoginConfigPath, &value)) { | |
239 result->SetString(kXmppLoginConfigPath, value); | |
240 } | |
241 } else { | |
242 result.reset(); // Return NULL in case of error. | |
243 } | |
244 } | |
245 | |
246 callback.Run(result.Pass()); | |
247 } | |
248 | |
249 void DaemonControllerLinux::DoSetConfigAndStart( | |
250 scoped_ptr<base::DictionaryValue> config, | |
251 const CompletionCallback& done_callback) { | |
252 | |
253 // Add the user to chrome-remote-desktop group first. | |
254 std::vector<std::string> args; | |
255 args.push_back("--add-user"); | |
256 int exit_code; | |
257 if (!RunHostScriptWithTimeout( | |
258 args, base::TimeDelta::FromSeconds(kSudoTimeoutSeconds), | |
259 &exit_code) || | |
260 exit_code != 0) { | |
261 LOG(ERROR) << "Failed to add user to chrome-remote-desktop group."; | |
262 done_callback.Run(RESULT_FAILED); | |
263 return; | |
264 } | |
265 | |
266 // Ensure the configuration directory exists. | |
267 base::FilePath config_dir = GetConfigPath().DirName(); | |
268 if (!base::DirectoryExists(config_dir) && | |
269 !file_util::CreateDirectory(config_dir)) { | |
270 LOG(ERROR) << "Failed to create config directory " << config_dir.value(); | |
271 done_callback.Run(RESULT_FAILED); | |
272 return; | |
273 } | |
274 | |
275 // Write config. | |
276 JsonHostConfig config_file(GetConfigPath()); | |
277 if (!config_file.CopyFrom(config.get()) || | |
278 !config_file.Save()) { | |
279 LOG(ERROR) << "Failed to update config file."; | |
280 done_callback.Run(RESULT_FAILED); | |
281 return; | |
282 } | |
283 | |
284 // Finally start the host. | |
285 args.clear(); | |
286 args.push_back("--start"); | |
287 AsyncResult result; | |
288 if (RunHostScript(args, &exit_code)) { | |
289 result = (exit_code == 0) ? RESULT_OK : RESULT_FAILED; | |
290 } else { | |
291 result = RESULT_FAILED; | |
292 } | |
293 done_callback.Run(result); | |
294 } | |
295 | |
296 void DaemonControllerLinux::DoUpdateConfig( | |
297 scoped_ptr<base::DictionaryValue> config, | |
298 const CompletionCallback& done_callback) { | |
299 JsonHostConfig config_file(GetConfigPath()); | |
300 if (!config_file.Read() || | |
301 !config_file.CopyFrom(config.get()) || | |
302 !config_file.Save()) { | |
303 LOG(ERROR) << "Failed to update config file."; | |
304 done_callback.Run(RESULT_FAILED); | |
305 return; | |
306 } | |
307 | |
308 std::vector<std::string> args; | |
309 args.push_back("--reload"); | |
310 AsyncResult result; | |
311 int exit_code; | |
312 if (RunHostScript(args, &exit_code)) { | |
313 result = (exit_code == 0) ? RESULT_OK : RESULT_FAILED; | |
314 } else { | |
315 result = RESULT_FAILED; | |
316 } | |
317 | |
318 done_callback.Run(result); | |
319 } | |
320 | |
321 void DaemonControllerLinux::DoStop(const CompletionCallback& done_callback) { | |
322 std::vector<std::string> args; | |
323 args.push_back("--stop"); | |
324 int exit_code = 0; | |
325 AsyncResult result; | |
326 if (RunHostScript(args, &exit_code)) { | |
327 result = (exit_code == 0) ? RESULT_OK : RESULT_FAILED; | |
328 } else { | |
329 result = RESULT_FAILED; | |
330 } | |
331 done_callback.Run(result); | |
332 } | |
333 | |
334 void DaemonControllerLinux::DoGetVersion( | |
335 const GetVersionCallback& done_callback) { | |
336 base::FilePath script_path; | |
337 if (!GetScriptPath(&script_path)) { | |
338 done_callback.Run(std::string()); | |
339 return; | |
340 } | |
341 CommandLine command_line(script_path); | |
342 command_line.AppendArg("--host-version"); | |
343 | |
344 std::string version; | |
345 int exit_code = 0; | |
346 int result = | |
347 base::GetAppOutputWithExitCode(command_line, &version, &exit_code); | |
348 if (!result || exit_code != 0) { | |
349 LOG(ERROR) << "Failed to run \"" << command_line.GetCommandLineString() | |
350 << "\". Exit code: " << exit_code; | |
351 done_callback.Run(std::string()); | |
352 return; | |
353 } | |
354 | |
355 TrimWhitespaceASCII(version, TRIM_ALL, &version); | |
356 if (!ContainsOnlyChars(version, "0123456789.")) { | |
357 LOG(ERROR) << "Received invalid host version number: " << version; | |
358 done_callback.Run(std::string()); | |
359 return; | |
360 } | |
361 | |
362 done_callback.Run(version); | |
363 } | |
364 | |
365 } // namespace | |
366 | |
367 scoped_ptr<DaemonController> remoting::DaemonController::Create() { | |
368 return scoped_ptr<DaemonController>(new DaemonControllerLinux()); | |
369 } | |
370 | |
371 } // namespace remoting | |
OLD | NEW |