- add sources.
[platform/framework/web/crosswalk.git] / src / remoting / host / setup / daemon_controller_delegate_linux.cc
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
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 base::FilePath GetConfigPath() {
57   std::string filename = "host#" + GetMd5(net::GetHostName()) + ".json";
58   return file_util::GetHomeDir().
59       Append(".config/chrome-remote-desktop").Append(filename);
60 }
61
62 bool 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
71 bool 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
117 bool 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
125 DaemonControllerDelegateLinux::DaemonControllerDelegateLinux() {
126 }
127
128 DaemonControllerDelegateLinux::~DaemonControllerDelegateLinux() {
129 }
130
131 DaemonController::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
149 scoped_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
170 void 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       !file_util::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
215 void 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
237 void 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
249 void DaemonControllerDelegateLinux::SetWindow(void* window_handle) {
250   // noop
251 }
252
253 std::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
280 DaemonController::UsageStatsConsent
281 DaemonControllerDelegateLinux::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
291 scoped_refptr<DaemonController> DaemonController::Create() {
292   scoped_ptr<DaemonController::Delegate> delegate(
293       new DaemonControllerDelegateLinux());
294   return new DaemonController(delegate.Pass());
295 }
296
297 }  // namespace remoting