Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(447)

Side by Side Diff: remoting/host/plugin/daemon_controller_mac.cc

Issue 10171020: Preference Pane for chromoting on Mac (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Address comments. Created 8 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "remoting/host/plugin/daemon_controller.h" 5 #include "remoting/host/plugin/daemon_controller.h"
6 6
7 #include <launch.h> 7 #include <launch.h>
8 #include <sys/types.h> 8 #include <sys/types.h>
9 9
10 #include "base/basictypes.h" 10 #include "base/basictypes.h"
11 #include "base/bind.h" 11 #include "base/bind.h"
12 #include "base/compiler_specific.h" 12 #include "base/compiler_specific.h"
13 #include "base/eintr_wrapper.h"
14 #include "base/file_path.h" 13 #include "base/file_path.h"
15 #include "base/file_util.h" 14 #include "base/file_util.h"
16 #include "base/json/json_writer.h" 15 #include "base/json/json_writer.h"
17 #include "base/logging.h" 16 #include "base/logging.h"
18 #include "base/mac/authorization_util.h" 17 #include "base/mac/foundation_util.h"
19 #include "base/mac/launchd.h" 18 #include "base/mac/launchd.h"
20 #include "base/mac/mac_logging.h" 19 #include "base/mac/mac_logging.h"
21 #include "base/mac/scoped_authorizationref.h" 20 #include "base/mac/mac_util.h"
22 #include "base/mac/scoped_launch_data.h" 21 #include "base/mac/scoped_launch_data.h"
23 #include "base/threading/thread.h" 22 #include "base/threading/thread.h"
24 #include "base/time.h" 23 #include "base/time.h"
25 #include "base/values.h" 24 #include "base/values.h"
26 #include "remoting/host/json_host_config.h" 25 #include "remoting/host/json_host_config.h"
27 26
28 namespace remoting { 27 namespace remoting {
29 28
30 namespace { 29 namespace {
31 30
31 // The NSSystemDirectories.h header has a conflicting definition of
32 // NSSearchPathDirectory with the one in base/mac/foundation_util.h.
33 // Foundation.h would work, but it can only be included from Objective-C files.
34 // Therefore, we define the needed constants here.
35 const int NSLibraryDirectory = 5;
36
32 // The name of the Remoting Host service that is registered with launchd. 37 // The name of the Remoting Host service that is registered with launchd.
33 #define kServiceName "org.chromium.chromoting" 38 #define kServiceName "org.chromium.chromoting"
34 #define kConfigDir "/Library/PrivilegedHelperTools/" 39 #define kConfigDir "/Library/PrivilegedHelperTools/"
35 40
36 // This helper script is executed as root. It is passed a command-line option 41 // This helper script is executed as root. It is passed a command-line option
37 // (--enable or --disable), which causes it to create or remove a trigger file. 42 // (--enable or --disable), which causes it to create or remove a trigger file.
38 // The trigger file (defined in the service's plist file) informs launchd 43 // The trigger file (defined in the service's plist file) informs launchd
39 // whether the Host service should be running. Creating the trigger file causes 44 // whether the Host service should be running. Creating the trigger file causes
40 // launchd to immediately start the service. Deleting the trigger file has no 45 // launchd to immediately start the service. Deleting the trigger file has no
41 // immediate effect, but it prevents the service from being restarted if it 46 // immediate effect, but it prevents the service from being restarted if it
(...skipping 27 matching lines...) Expand all
69 private: 74 private:
70 void DoGetConfig(const GetConfigCallback& callback); 75 void DoGetConfig(const GetConfigCallback& callback);
71 void DoSetConfigAndStart(scoped_ptr<base::DictionaryValue> config, 76 void DoSetConfigAndStart(scoped_ptr<base::DictionaryValue> config,
72 const CompletionCallback& done_callback); 77 const CompletionCallback& done_callback);
73 void DoUpdateConfig(scoped_ptr<base::DictionaryValue> config, 78 void DoUpdateConfig(scoped_ptr<base::DictionaryValue> config,
74 const CompletionCallback& done_callback); 79 const CompletionCallback& done_callback);
75 void DoStop(const CompletionCallback& done_callback); 80 void DoStop(const CompletionCallback& done_callback);
76 void NotifyWhenStopped(const CompletionCallback& done_callback, 81 void NotifyWhenStopped(const CompletionCallback& done_callback,
77 int tries_remaining, 82 int tries_remaining,
78 const base::TimeDelta& sleep); 83 const base::TimeDelta& sleep);
84 bool ShowPreferencePane(const std::string& config_data);
79 85
80 bool RunToolScriptAsRoot(const char* command, const std::string& input_data);
81 bool StopService();
82
83 // The API for gaining root privileges is blocking (it prompts the user for
84 // a password). Since Start() and Stop() must not block the main thread, they
85 // need to post their tasks to a separate thread.
86 base::Thread auth_thread_; 86 base::Thread auth_thread_;
87 87
88 DISALLOW_COPY_AND_ASSIGN(DaemonControllerMac); 88 DISALLOW_COPY_AND_ASSIGN(DaemonControllerMac);
89 }; 89 };
90 90
91 DaemonControllerMac::DaemonControllerMac() 91 DaemonControllerMac::DaemonControllerMac()
92 : auth_thread_("Auth thread") { 92 : auth_thread_("Auth thread") {
93 auth_thread_.Start(); 93 auth_thread_.Start();
94 } 94 }
95 95
96 DaemonControllerMac::~DaemonControllerMac() { 96 DaemonControllerMac::~DaemonControllerMac() {
97 // This will block if the thread is waiting on a root password prompt. There
98 // doesn't seem to be an easy solution for this, other than to spawn a
99 // separate process to do the root elevation.
100
101 // TODO(lambroslambrou): Improve this, either by finding a way to terminate
102 // the thread, or by moving to a separate process.
103 auth_thread_.Stop(); 97 auth_thread_.Stop();
104 } 98 }
105 99
106 DaemonController::State DaemonControllerMac::GetState() { 100 DaemonController::State DaemonControllerMac::GetState() {
107 pid_t job_pid = base::mac::PIDForJob(kServiceName); 101 pid_t job_pid = base::mac::PIDForJob(kServiceName);
108 if (job_pid < 0) { 102 if (job_pid < 0) {
109 return DaemonController::STATE_NOT_INSTALLED; 103 return DaemonController::STATE_NOT_INSTALLED;
110 } else if (job_pid == 0) { 104 } else if (job_pid == 0) {
111 // Service is stopped, or a start attempt failed. 105 // Service is stopped, or a start attempt failed.
112 return DaemonController::STATE_STOPPED; 106 return DaemonController::STATE_STOPPED;
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after
164 if (host_config.GetString(kXmppLoginConfigPath, &value)) 158 if (host_config.GetString(kXmppLoginConfigPath, &value))
165 config.get()->SetString(kXmppLoginConfigPath, value); 159 config.get()->SetString(kXmppLoginConfigPath, value);
166 } 160 }
167 161
168 callback.Run(config.Pass()); 162 callback.Run(config.Pass());
169 } 163 }
170 164
171 void DaemonControllerMac::DoSetConfigAndStart( 165 void DaemonControllerMac::DoSetConfigAndStart(
172 scoped_ptr<base::DictionaryValue> config, 166 scoped_ptr<base::DictionaryValue> config,
173 const CompletionCallback& done_callback) { 167 const CompletionCallback& done_callback) {
174 std::string file_content; 168 std::string config_data;
175 base::JSONWriter::Write(config.get(), &file_content); 169 base::JSONWriter::Write(config.get(), &config_data);
176 170
177 // Creating the trigger file causes launchd to start the service, so the 171 bool result = ShowPreferencePane(config_data);
178 // extra step performed in DoStop() is not necessary here.
179 bool result = RunToolScriptAsRoot("--enable", file_content);
180 done_callback.Run(result ? RESULT_OK : RESULT_FAILED); 172 done_callback.Run(result ? RESULT_OK : RESULT_FAILED);
181 } 173 }
182 174
183 void DaemonControllerMac::DoUpdateConfig( 175 void DaemonControllerMac::DoUpdateConfig(
184 scoped_ptr<base::DictionaryValue> config, 176 scoped_ptr<base::DictionaryValue> config,
185 const CompletionCallback& done_callback) { 177 const CompletionCallback& done_callback) {
186 FilePath config_file_path(kHostConfigFile); 178 FilePath config_file_path(kHostConfigFile);
187 JsonHostConfig config_file(config_file_path); 179 JsonHostConfig config_file(config_file_path);
188 if (!config_file.Read()) { 180 if (!config_file.Read()) {
189 done_callback.Run(RESULT_FAILED); 181 done_callback.Run(RESULT_FAILED);
190 } 182 }
191 for (DictionaryValue::key_iterator key(config->begin_keys()); 183 for (DictionaryValue::key_iterator key(config->begin_keys());
192 key != config->end_keys(); ++key) { 184 key != config->end_keys(); ++key) {
193 std::string value; 185 std::string value;
194 if (!config->GetString(*key, &value)) { 186 if (!config->GetString(*key, &value)) {
195 LOG(ERROR) << *key << " is not a string."; 187 LOG(ERROR) << *key << " is not a string.";
196 done_callback.Run(RESULT_FAILED); 188 done_callback.Run(RESULT_FAILED);
197 } 189 }
198 config_file.SetString(*key, value); 190 config_file.SetString(*key, value);
199 } 191 }
200 192
201 std::string file_content = config_file.GetSerializedData(); 193 std::string config_data = config_file.GetSerializedData();
202 bool success = RunToolScriptAsRoot("--save-config", file_content); 194 bool success = ShowPreferencePane(config_data);
203 195
204 done_callback.Run(success ? RESULT_OK : RESULT_FAILED); 196 done_callback.Run(success ? RESULT_OK : RESULT_FAILED);
205 pid_t job_pid = base::mac::PIDForJob(kServiceName); 197 }
206 if (job_pid > 0) { 198
207 kill(job_pid, SIGHUP); 199 bool DaemonControllerMac::ShowPreferencePane(const std::string& config_data) {
200 if (!config_data.empty()) {
201 FilePath config_path;
202 if (!file_util::GetTempDir(&config_path)) {
203 LOG(ERROR) << "Failed to get filename for saving configuration data.";
204 return false;
205 }
206 config_path = config_path.Append(kServiceName ".json");
207
208 int written = file_util::WriteFile(config_path, config_data.data(),
209 config_data.size());
210 if (written != static_cast<int>(config_data.size())) {
211 LOG(ERROR) << "Failed to save configuration data to: "
212 << config_path.value();
213 return false;
214 }
208 } 215 }
216
217 FilePath pane_path;
218 // TODO(lambroslambrou): Use NSPreferencePanesDirectory once we start
219 // building against SDK 10.6.
220 if (!base::mac::GetLocalDirectory(NSLibraryDirectory, &pane_path)) {
221 LOG(ERROR) << "Failed to get directory for local preference panes.";
222 return false;
223 }
224 pane_path = pane_path.Append("PreferencePanes")
225 .Append(kServiceName ".prefPane");
226
227 FSRef pane_path_ref;
228 if (!base::mac::FSRefFromPath(pane_path.value(), &pane_path_ref)) {
229 LOG(ERROR) << "Failed to create FSRef";
230 return false;
231 }
232 OSStatus status = LSOpenFSRef(&pane_path_ref, NULL);
233 if (status != noErr) {
234 OSSTATUS_LOG(ERROR, status) << "LSOpenFSRef failed for path: "
235 << pane_path.value();
236 return false;
237 }
238
239 CFNotificationCenterRef center =
240 CFNotificationCenterGetDistributedCenter();
241 CFNotificationCenterPostNotification(center, CFSTR(kServiceName), NULL, NULL,
242 TRUE);
243 return true;
209 } 244 }
210 245
211 void DaemonControllerMac::DoStop(const CompletionCallback& done_callback) { 246 void DaemonControllerMac::DoStop(const CompletionCallback& done_callback) {
212 if (!RunToolScriptAsRoot("--disable", "")) { 247 if (!ShowPreferencePane("")) {
213 done_callback.Run(RESULT_FAILED); 248 done_callback.Run(RESULT_FAILED);
214 return; 249 return;
215 } 250 }
216 251
217 // Deleting the trigger file does not cause launchd to stop the service.
218 // Since the service is running for the local user's desktop (not as root),
219 // it has to be stopped for that user. This cannot easily be done in the
220 // shell-script running as root, so it is done here instead.
221 if (!StopService()) {
222 done_callback.Run(RESULT_FAILED);
223 return;
224 }
225
226 // StopService does not wait for the stop to take effect, so we can't return
227 // immediately. Instead, we wait up to 10s.
228 NotifyWhenStopped(done_callback, 252 NotifyWhenStopped(done_callback,
229 kStopWaitRetryLimit, 253 kStopWaitRetryLimit,
230 base::TimeDelta::FromMilliseconds(kStopWaitTimeout)); 254 base::TimeDelta::FromMilliseconds(kStopWaitTimeout));
231 } 255 }
232 256
233 void DaemonControllerMac::NotifyWhenStopped( 257 void DaemonControllerMac::NotifyWhenStopped(
234 const CompletionCallback& done_callback, 258 const CompletionCallback& done_callback,
235 int tries_remaining, 259 int tries_remaining,
236 const base::TimeDelta& sleep) { 260 const base::TimeDelta& sleep) {
237 if (GetState() == DaemonController::STATE_STOPPED) { 261 if (GetState() == DaemonController::STATE_STOPPED) {
238 done_callback.Run(RESULT_OK); 262 done_callback.Run(RESULT_OK);
239 } else if (tries_remaining == 0) { 263 } else if (tries_remaining == 0) {
240 done_callback.Run(RESULT_FAILED); 264 done_callback.Run(RESULT_FAILED);
241 } else { 265 } else {
242 auth_thread_.message_loop_proxy()->PostDelayedTask( 266 auth_thread_.message_loop_proxy()->PostDelayedTask(
243 FROM_HERE, 267 FROM_HERE,
244 base::Bind(&DaemonControllerMac::NotifyWhenStopped, 268 base::Bind(&DaemonControllerMac::NotifyWhenStopped,
245 base::Unretained(this), 269 base::Unretained(this),
246 done_callback, 270 done_callback,
247 tries_remaining - 1, 271 tries_remaining - 1,
248 sleep), 272 sleep),
249 sleep); 273 sleep);
250 } 274 }
251 } 275 }
252 276
253 bool DaemonControllerMac::RunToolScriptAsRoot(const char* command,
254 const std::string& input_data) {
255 // TODO(lambroslambrou): Supply a localized prompt string here.
256 base::mac::ScopedAuthorizationRef authorization(
257 base::mac::AuthorizationCreateToRunAsRoot(CFSTR("")));
258 if (!authorization) {
259 LOG(ERROR) << "Failed to get root privileges.";
260 return false;
261 }
262
263 if (!file_util::VerifyPathControlledByAdmin(FilePath(kStartStopTool))) {
264 LOG(ERROR) << "Security check failed for: " << kStartStopTool;
265 return false;
266 }
267
268 // TODO(lambroslambrou): Use sandbox-exec to minimize exposure -
269 // http://crbug.com/120903
270 const char* arguments[] = { command, NULL };
271 FILE* pipe = NULL;
272 pid_t pid;
273 OSStatus status = base::mac::ExecuteWithPrivilegesAndGetPID(
274 authorization.get(),
275 kStartStopTool,
276 kAuthorizationFlagDefaults,
277 arguments,
278 &pipe,
279 &pid);
280 if (status != errAuthorizationSuccess) {
281 OSSTATUS_LOG(ERROR, status) << "AuthorizationExecuteWithPrivileges";
282 return false;
283 }
284 if (pid == -1) {
285 LOG(ERROR) << "Failed to get child PID";
286 return false;
287 }
288
289 DCHECK(pipe);
290 if (!input_data.empty()) {
291 size_t bytes_written = fwrite(input_data.data(), sizeof(char),
292 input_data.size(), pipe);
293 // According to the fwrite manpage, a partial count is returned only if a
294 // write error has occurred.
295 if (bytes_written != input_data.size()) {
296 LOG(ERROR) << "Failed to write data to child process";
297 }
298 // Need to close, since the child waits for EOF on its stdin.
299 if (fclose(pipe) != 0) {
300 PLOG(ERROR) << "fclose";
301 }
302 }
303
304 int exit_status;
305 pid_t wait_result = HANDLE_EINTR(waitpid(pid, &exit_status, 0));
306 if (wait_result != pid) {
307 PLOG(ERROR) << "waitpid";
308 return false;
309 }
310 if (WIFEXITED(exit_status) && WEXITSTATUS(exit_status) == 0) {
311 return true;
312 } else {
313 LOG(ERROR) << kStartStopTool << " failed with exit status " << exit_status;
314 return false;
315 }
316 }
317
318 bool DaemonControllerMac::StopService() {
319 base::mac::ScopedLaunchData response(
320 base::mac::MessageForJob(kServiceName, LAUNCH_KEY_STOPJOB));
321 if (!response) {
322 LOG(ERROR) << "Failed to send message to launchd";
323 return false;
324 }
325
326 // Got a response, so check if launchd sent a non-zero error code, otherwise
327 // assume the command was successful.
328 if (launch_data_get_type(response.get()) == LAUNCH_DATA_ERRNO) {
329 int error = launch_data_get_errno(response.get());
330 if (error) {
331 LOG(ERROR) << "launchd returned error " << error;
332 return false;
333 }
334 }
335 return true;
336 }
337
338 } // namespace 277 } // namespace
339 278
340 scoped_ptr<DaemonController> remoting::DaemonController::Create() { 279 scoped_ptr<DaemonController> remoting::DaemonController::Create() {
341 return scoped_ptr<DaemonController>(new DaemonControllerMac()); 280 return scoped_ptr<DaemonController>(new DaemonControllerMac());
342 } 281 }
343 282
344 } // namespace remoting 283 } // namespace remoting
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698