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

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: Save xib in IBuilder 3.2.6 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
« no previous file with comments | « remoting/host/me2me_preference_pane-Info.plist ('k') | remoting/remoting.gyp » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 file that 42 // (--enable or --disable), which causes it to create or remove a file that
38 // informs the host's launch script of whether the host is enabled or disabled. 43 // informs the host's launch script of whether the host is enabled or disabled.
39 const char kStartStopTool[] = kConfigDir kServiceName ".me2me.sh"; 44 const char kStartStopTool[] = kConfigDir kServiceName ".me2me.sh";
40 45
41 // Use a single configuration file, instead of separate "auth" and "host" files. 46 // Use a single configuration file, instead of separate "auth" and "host" files.
(...skipping 23 matching lines...) Expand all
65 private: 70 private:
66 void DoGetConfig(const GetConfigCallback& callback); 71 void DoGetConfig(const GetConfigCallback& callback);
67 void DoSetConfigAndStart(scoped_ptr<base::DictionaryValue> config, 72 void DoSetConfigAndStart(scoped_ptr<base::DictionaryValue> config,
68 const CompletionCallback& done_callback); 73 const CompletionCallback& done_callback);
69 void DoUpdateConfig(scoped_ptr<base::DictionaryValue> config, 74 void DoUpdateConfig(scoped_ptr<base::DictionaryValue> config,
70 const CompletionCallback& done_callback); 75 const CompletionCallback& done_callback);
71 void DoStop(const CompletionCallback& done_callback); 76 void DoStop(const CompletionCallback& done_callback);
72 void NotifyWhenStopped(const CompletionCallback& done_callback, 77 void NotifyWhenStopped(const CompletionCallback& done_callback,
73 int tries_remaining, 78 int tries_remaining,
74 const base::TimeDelta& sleep); 79 const base::TimeDelta& sleep);
80 bool ShowPreferencePane(const std::string& config_data);
75 81
76 bool RunToolScriptAsRoot(const char* command, const std::string& input_data);
77 bool SendJobControlMessage(const char* launchKey);
78
79 // The API for gaining root privileges is blocking (it prompts the user for
80 // a password). Since Start() and Stop() must not block the main thread, they
81 // need to post their tasks to a separate thread.
82 base::Thread auth_thread_; 82 base::Thread auth_thread_;
83 83
84 DISALLOW_COPY_AND_ASSIGN(DaemonControllerMac); 84 DISALLOW_COPY_AND_ASSIGN(DaemonControllerMac);
85 }; 85 };
86 86
87 DaemonControllerMac::DaemonControllerMac() 87 DaemonControllerMac::DaemonControllerMac()
88 : auth_thread_("Auth thread") { 88 : auth_thread_("Auth thread") {
89 auth_thread_.Start(); 89 auth_thread_.Start();
90 } 90 }
91 91
92 DaemonControllerMac::~DaemonControllerMac() { 92 DaemonControllerMac::~DaemonControllerMac() {
93 // This will block if the thread is waiting on a root password prompt. There
94 // doesn't seem to be an easy solution for this, other than to spawn a
95 // separate process to do the root elevation.
96
97 // TODO(lambroslambrou): Improve this, either by finding a way to terminate
98 // the thread, or by moving to a separate process.
99 auth_thread_.Stop(); 93 auth_thread_.Stop();
100 } 94 }
101 95
102 DaemonController::State DaemonControllerMac::GetState() { 96 DaemonController::State DaemonControllerMac::GetState() {
103 pid_t job_pid = base::mac::PIDForJob(kServiceName); 97 pid_t job_pid = base::mac::PIDForJob(kServiceName);
104 if (job_pid < 0) { 98 if (job_pid < 0) {
105 return DaemonController::STATE_NOT_INSTALLED; 99 return DaemonController::STATE_NOT_INSTALLED;
106 } else if (job_pid == 0) { 100 } else if (job_pid == 0) {
107 // Service is stopped, or a start attempt failed. 101 // Service is stopped, or a start attempt failed.
108 return DaemonController::STATE_STOPPED; 102 return DaemonController::STATE_STOPPED;
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after
160 if (host_config.GetString(kXmppLoginConfigPath, &value)) 154 if (host_config.GetString(kXmppLoginConfigPath, &value))
161 config.get()->SetString(kXmppLoginConfigPath, value); 155 config.get()->SetString(kXmppLoginConfigPath, value);
162 } 156 }
163 157
164 callback.Run(config.Pass()); 158 callback.Run(config.Pass());
165 } 159 }
166 160
167 void DaemonControllerMac::DoSetConfigAndStart( 161 void DaemonControllerMac::DoSetConfigAndStart(
168 scoped_ptr<base::DictionaryValue> config, 162 scoped_ptr<base::DictionaryValue> config,
169 const CompletionCallback& done_callback) { 163 const CompletionCallback& done_callback) {
170 std::string file_content; 164 std::string config_data;
171 base::JSONWriter::Write(config.get(), &file_content); 165 base::JSONWriter::Write(config.get(), &config_data);
172 if (!RunToolScriptAsRoot("--enable", file_content)) { 166
173 done_callback.Run(RESULT_FAILED); 167 bool result = ShowPreferencePane(config_data);
174 return; 168
175 }
176 bool result = SendJobControlMessage(LAUNCH_KEY_STARTJOB);
177 done_callback.Run(result ? RESULT_OK : RESULT_FAILED); 169 done_callback.Run(result ? RESULT_OK : RESULT_FAILED);
178 } 170 }
179 171
180 void DaemonControllerMac::DoUpdateConfig( 172 void DaemonControllerMac::DoUpdateConfig(
181 scoped_ptr<base::DictionaryValue> config, 173 scoped_ptr<base::DictionaryValue> config,
182 const CompletionCallback& done_callback) { 174 const CompletionCallback& done_callback) {
183 FilePath config_file_path(kHostConfigFile); 175 FilePath config_file_path(kHostConfigFile);
184 JsonHostConfig config_file(config_file_path); 176 JsonHostConfig config_file(config_file_path);
185 if (!config_file.Read()) { 177 if (!config_file.Read()) {
186 done_callback.Run(RESULT_FAILED); 178 done_callback.Run(RESULT_FAILED);
187 return; 179 return;
188 } 180 }
189 for (DictionaryValue::key_iterator key(config->begin_keys()); 181 for (DictionaryValue::key_iterator key(config->begin_keys());
190 key != config->end_keys(); ++key) { 182 key != config->end_keys(); ++key) {
191 std::string value; 183 std::string value;
192 if (!config->GetString(*key, &value)) { 184 if (!config->GetString(*key, &value)) {
193 LOG(ERROR) << *key << " is not a string."; 185 LOG(ERROR) << *key << " is not a string.";
194 done_callback.Run(RESULT_FAILED); 186 done_callback.Run(RESULT_FAILED);
195 return; 187 return;
196 } 188 }
197 config_file.SetString(*key, value); 189 config_file.SetString(*key, value);
198 } 190 }
199 191
200 std::string file_content = config_file.GetSerializedData(); 192 std::string config_data = config_file.GetSerializedData();
201 if (!RunToolScriptAsRoot("--save-config", file_content)) { 193 bool success = ShowPreferencePane(config_data);
202 done_callback.Run(RESULT_FAILED); 194
203 return; 195 done_callback.Run(success ? RESULT_OK : RESULT_FAILED);
196 }
197
198 bool DaemonControllerMac::ShowPreferencePane(const std::string& config_data) {
199 if (!config_data.empty()) {
200 FilePath config_path;
201 if (!file_util::GetTempDir(&config_path)) {
202 LOG(ERROR) << "Failed to get filename for saving configuration data.";
203 return false;
204 }
205 config_path = config_path.Append(kServiceName ".json");
206
207 int written = file_util::WriteFile(config_path, config_data.data(),
208 config_data.size());
209 if (written != static_cast<int>(config_data.size())) {
210 LOG(ERROR) << "Failed to save configuration data to: "
211 << config_path.value();
212 return false;
213 }
204 } 214 }
205 215
206 done_callback.Run(RESULT_OK); 216 FilePath pane_path;
207 pid_t job_pid = base::mac::PIDForJob(kServiceName); 217 // TODO(lambroslambrou): Use NSPreferencePanesDirectory once we start
208 if (job_pid > 0) { 218 // building against SDK 10.6.
209 kill(job_pid, SIGHUP); 219 if (!base::mac::GetLocalDirectory(NSLibraryDirectory, &pane_path)) {
220 LOG(ERROR) << "Failed to get directory for local preference panes.";
221 return false;
210 } 222 }
223 pane_path = pane_path.Append("PreferencePanes")
224 .Append(kServiceName ".prefPane");
225
226 FSRef pane_path_ref;
227 if (!base::mac::FSRefFromPath(pane_path.value(), &pane_path_ref)) {
228 LOG(ERROR) << "Failed to create FSRef";
229 return false;
230 }
231 OSStatus status = LSOpenFSRef(&pane_path_ref, NULL);
232 if (status != noErr) {
233 OSSTATUS_LOG(ERROR, status) << "LSOpenFSRef failed for path: "
234 << pane_path.value();
235 return false;
236 }
237
238 CFNotificationCenterRef center =
239 CFNotificationCenterGetDistributedCenter();
240 CFNotificationCenterPostNotification(center, CFSTR(kServiceName), NULL, NULL,
241 TRUE);
242 return true;
211 } 243 }
212 244
213 void DaemonControllerMac::DoStop(const CompletionCallback& done_callback) { 245 void DaemonControllerMac::DoStop(const CompletionCallback& done_callback) {
214 if (!RunToolScriptAsRoot("--disable", "")) { 246 if (!ShowPreferencePane("")) {
215 done_callback.Run(RESULT_FAILED); 247 done_callback.Run(RESULT_FAILED);
216 return; 248 return;
217 } 249 }
218 250
219 // Deleting the trigger file does not cause launchd to stop the service.
220 // Since the service is running for the local user's desktop (not as root),
221 // it has to be stopped for that user. This cannot easily be done in the
222 // shell-script running as root, so it is done here instead.
223 if (!SendJobControlMessage(LAUNCH_KEY_STOPJOB)) {
224 done_callback.Run(RESULT_FAILED);
225 return;
226 }
227
228 // SendJobControlMessage does not wait for the stop to take effect, so we
229 // can't return immediately. Instead, we wait up to 10s.
230 NotifyWhenStopped(done_callback, 251 NotifyWhenStopped(done_callback,
231 kStopWaitRetryLimit, 252 kStopWaitRetryLimit,
232 base::TimeDelta::FromMilliseconds(kStopWaitTimeout)); 253 base::TimeDelta::FromMilliseconds(kStopWaitTimeout));
233 } 254 }
234 255
235 void DaemonControllerMac::NotifyWhenStopped( 256 void DaemonControllerMac::NotifyWhenStopped(
236 const CompletionCallback& done_callback, 257 const CompletionCallback& done_callback,
237 int tries_remaining, 258 int tries_remaining,
238 const base::TimeDelta& sleep) { 259 const base::TimeDelta& sleep) {
239 if (GetState() == DaemonController::STATE_STOPPED) { 260 if (GetState() == DaemonController::STATE_STOPPED) {
240 done_callback.Run(RESULT_OK); 261 done_callback.Run(RESULT_OK);
241 } else if (tries_remaining == 0) { 262 } else if (tries_remaining == 0) {
242 done_callback.Run(RESULT_FAILED); 263 done_callback.Run(RESULT_FAILED);
243 } else { 264 } else {
244 auth_thread_.message_loop_proxy()->PostDelayedTask( 265 auth_thread_.message_loop_proxy()->PostDelayedTask(
245 FROM_HERE, 266 FROM_HERE,
246 base::Bind(&DaemonControllerMac::NotifyWhenStopped, 267 base::Bind(&DaemonControllerMac::NotifyWhenStopped,
247 base::Unretained(this), 268 base::Unretained(this),
248 done_callback, 269 done_callback,
249 tries_remaining - 1, 270 tries_remaining - 1,
250 sleep), 271 sleep),
251 sleep); 272 sleep);
252 } 273 }
253 } 274 }
254 275
255 bool DaemonControllerMac::RunToolScriptAsRoot(const char* command,
256 const std::string& input_data) {
257 // TODO(lambroslambrou): Supply a localized prompt string here.
258 base::mac::ScopedAuthorizationRef authorization(
259 base::mac::AuthorizationCreateToRunAsRoot(CFSTR("")));
260 if (!authorization) {
261 LOG(ERROR) << "Failed to get root privileges.";
262 return false;
263 }
264
265 if (!file_util::VerifyPathControlledByAdmin(FilePath(kStartStopTool))) {
266 LOG(ERROR) << "Security check failed for: " << kStartStopTool;
267 return false;
268 }
269
270 // TODO(lambroslambrou): Use sandbox-exec to minimize exposure -
271 // http://crbug.com/120903
272 const char* arguments[] = { command, NULL };
273 FILE* pipe = NULL;
274 pid_t pid;
275 OSStatus status = base::mac::ExecuteWithPrivilegesAndGetPID(
276 authorization.get(),
277 kStartStopTool,
278 kAuthorizationFlagDefaults,
279 arguments,
280 &pipe,
281 &pid);
282 if (status != errAuthorizationSuccess) {
283 OSSTATUS_LOG(ERROR, status) << "AuthorizationExecuteWithPrivileges";
284 return false;
285 }
286 if (pid == -1) {
287 LOG(ERROR) << "Failed to get child PID";
288 return false;
289 }
290
291 DCHECK(pipe);
292 if (!input_data.empty()) {
293 size_t bytes_written = fwrite(input_data.data(), sizeof(char),
294 input_data.size(), pipe);
295 // According to the fwrite manpage, a partial count is returned only if a
296 // write error has occurred.
297 if (bytes_written != input_data.size()) {
298 LOG(ERROR) << "Failed to write data to child process";
299 }
300 // Need to close, since the child waits for EOF on its stdin.
301 if (fclose(pipe) != 0) {
302 PLOG(ERROR) << "fclose";
303 }
304 }
305
306 int exit_status;
307 pid_t wait_result = HANDLE_EINTR(waitpid(pid, &exit_status, 0));
308 if (wait_result != pid) {
309 PLOG(ERROR) << "waitpid";
310 return false;
311 }
312 if (WIFEXITED(exit_status) && WEXITSTATUS(exit_status) == 0) {
313 return true;
314 } else {
315 LOG(ERROR) << kStartStopTool << " failed with exit status " << exit_status;
316 return false;
317 }
318 }
319
320 bool DaemonControllerMac::SendJobControlMessage(const char* launchKey) {
321 base::mac::ScopedLaunchData response(
322 base::mac::MessageForJob(kServiceName, launchKey));
323 if (!response) {
324 LOG(ERROR) << "Failed to send message to launchd";
325 return false;
326 }
327
328 // Got a response, so check if launchd sent a non-zero error code, otherwise
329 // assume the command was successful.
330 if (launch_data_get_type(response.get()) == LAUNCH_DATA_ERRNO) {
331 int error = launch_data_get_errno(response.get());
332 if (error) {
333 LOG(ERROR) << "launchd returned error " << error;
334 return false;
335 }
336 }
337 return true;
338 }
339
340 } // namespace 276 } // namespace
341 277
342 scoped_ptr<DaemonController> remoting::DaemonController::Create() { 278 scoped_ptr<DaemonController> remoting::DaemonController::Create() {
343 return scoped_ptr<DaemonController>(new DaemonControllerMac()); 279 return scoped_ptr<DaemonController>(new DaemonControllerMac());
344 } 280 }
345 281
346 } // namespace remoting 282 } // namespace remoting
OLDNEW
« no previous file with comments | « remoting/host/me2me_preference_pane-Info.plist ('k') | remoting/remoting.gyp » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698