OLD | NEW |
---|---|
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> | |
8 #include <sys/types.h> | |
9 | |
7 #include "base/basictypes.h" | 10 #include "base/basictypes.h" |
11 #include "base/bind.h" | |
8 #include "base/compiler_specific.h" | 12 #include "base/compiler_specific.h" |
13 #include "base/file_path.h" | |
14 #include "base/file_util.h" | |
15 #include "base/json/json_writer.h" | |
9 #include "base/logging.h" | 16 #include "base/logging.h" |
17 #include "base/mac/authorization_util.h" | |
18 #include "base/mac/launchd.h" | |
19 #include "base/mac/mac_logging.h" | |
20 #include "base/mac/scoped_authorizationref.h" | |
21 #include "base/mac/scoped_launch_data.h" | |
22 #include "base/synchronization/lock.h" | |
23 #include "base/threading/thread.h" | |
24 #include "base/values.h" | |
25 #include "remoting/host/json_host_config.h" | |
10 | 26 |
11 namespace remoting { | 27 namespace remoting { |
12 | 28 |
13 namespace { | 29 namespace { |
14 | 30 |
31 // The name of the Remoting Host service that is registered with launchd. | |
32 #define kServiceName "org.chromium.chromoting" | |
33 #define kConfigDir "/Library/PrivilegedHelperTools/" | |
dmac
2012/03/29 14:52:41
normally we wouldn't hardcode a path like this. Th
| |
34 | |
35 // This helper script is executed as root. It is passed a command-line option | |
36 // (--enable or --disable), which causes it to create or remove a trigger file. | |
37 // The trigger file (defined in the service's plist file) informs launchd | |
38 // whether the Host service should be running. Creating the trigger file causes | |
39 // launchd to immediately start the service. Deleting the trigger file has no | |
40 // immediate effect, but it prevents the service from being restarted if it | |
41 // becomes stopped. | |
42 const char kStartStopTool[] = kConfigDir kServiceName ".me2me.sh"; | |
43 | |
44 // Use a single configuration file, instead of separate "auth" and "host" files. | |
45 // This is because the SetConfigAndStart() API only provides a single | |
46 // dictionary, and splitting this into two dictionaries would require | |
47 // knowledge of which keys belong in which files. | |
48 const char kHostConfigFile[] = kConfigDir kServiceName ".json"; | |
49 | |
15 class DaemonControllerMac : public remoting::DaemonController { | 50 class DaemonControllerMac : public remoting::DaemonController { |
16 public: | 51 public: |
17 DaemonControllerMac(); | 52 DaemonControllerMac(); |
53 virtual ~DaemonControllerMac(); | |
18 | 54 |
19 virtual State GetState() OVERRIDE; | 55 virtual State GetState() OVERRIDE; |
20 virtual void GetConfig(const GetConfigCallback& callback) OVERRIDE; | 56 virtual void GetConfig(const GetConfigCallback& callback) OVERRIDE; |
21 virtual void SetConfigAndStart( | 57 virtual void SetConfigAndStart( |
22 scoped_ptr<base::DictionaryValue> config) OVERRIDE; | 58 scoped_ptr<base::DictionaryValue> config) OVERRIDE; |
23 virtual void SetPin(const std::string& pin) OVERRIDE; | 59 virtual void SetPin(const std::string& pin) OVERRIDE; |
24 virtual void Stop() OVERRIDE; | 60 virtual void Stop() OVERRIDE; |
25 | 61 |
26 private: | 62 private: |
63 void DoGetConfig(const GetConfigCallback& callback); | |
64 void DoSetConfigAndStart(scoped_ptr<base::DictionaryValue> config); | |
65 void DoStop(); | |
66 | |
67 bool RunToolScriptAsRoot(const char* command); | |
68 bool StopService(); | |
69 | |
70 // The API for gaining root privileges is blocking (it prompts the user for | |
71 // a password). Since Start() and Stop() must not block the main thread, they | |
72 // need to post their tasks to a separate thread. | |
73 base::Thread auth_thread_; | |
74 | |
75 // This distinguishes between STATE_START_FAILED and STATE_STOPPED in | |
76 // GetState(). | |
77 bool start_failed_; | |
78 base::Lock start_failed_lock_; | |
79 | |
27 DISALLOW_COPY_AND_ASSIGN(DaemonControllerMac); | 80 DISALLOW_COPY_AND_ASSIGN(DaemonControllerMac); |
28 }; | 81 }; |
29 | 82 |
30 DaemonControllerMac::DaemonControllerMac() { | 83 DaemonControllerMac::DaemonControllerMac() |
84 : auth_thread_("Auth thread"), | |
85 start_failed_(false) { | |
86 auth_thread_.Start(); | |
87 } | |
88 | |
89 DaemonControllerMac::~DaemonControllerMac() { | |
90 // This will block if the thread is waiting on a root password prompt. There | |
91 // doesn't seem to be an easy solution for this, other than to spawn a | |
92 // separate process to do the root elevation. | |
93 | |
94 // TODO(lambroslambrou): Improve this, either by finding a way to terminate | |
95 // the thread, or by moving to a separate process. | |
96 auth_thread_.Stop(); | |
31 } | 97 } |
32 | 98 |
33 DaemonController::State DaemonControllerMac::GetState() { | 99 DaemonController::State DaemonControllerMac::GetState() { |
34 return DaemonController::STATE_NOT_IMPLEMENTED; | 100 pid_t job_pid = base::mac::PIDForJob(kServiceName); |
101 if (job_pid < 0) { | |
102 // TODO(lambroslambrou): Change this to STATE_NOT_INSTALLED when the | |
103 // installation process is implemented. | |
104 return remoting::DaemonController::STATE_NOT_IMPLEMENTED; | |
Jamie
2012/03/29 05:20:29
Nit: You're already in namespace remoting. No need
Lambros
2012/03/29 18:20:44
Done.
| |
105 } else if (job_pid == 0) { | |
106 // Service is stopped, or a start attempt failed. | |
107 base::AutoLock lock(start_failed_lock_); | |
108 return start_failed_ ? remoting::DaemonController::STATE_START_FAILED : | |
109 remoting::DaemonController::STATE_STOPPED; | |
110 } else { | |
111 return remoting::DaemonController::STATE_STARTED; | |
112 } | |
35 } | 113 } |
36 | 114 |
37 void DaemonControllerMac::GetConfig(const GetConfigCallback& callback) { | 115 void DaemonControllerMac::GetConfig(const GetConfigCallback& callback) { |
38 NOTIMPLEMENTED(); | 116 // base::Unretained() is safe, since this object owns the thread and therefore |
117 // outlives it. | |
118 auth_thread_.message_loop_proxy()->PostTask( | |
119 FROM_HERE, | |
120 base::Bind(&DaemonControllerMac::DoGetConfig, base::Unretained(this), | |
121 callback)); | |
39 } | 122 } |
40 | 123 |
41 void DaemonControllerMac::SetConfigAndStart( | 124 void DaemonControllerMac::SetConfigAndStart( |
42 scoped_ptr<base::DictionaryValue> config) { | 125 scoped_ptr<base::DictionaryValue> config) { |
43 NOTIMPLEMENTED(); | 126 { |
127 base::AutoLock lock(start_failed_lock_); | |
128 start_failed_ = false; | |
129 } | |
130 | |
131 auth_thread_.message_loop_proxy()->PostTask( | |
132 FROM_HERE, | |
133 base::Bind(&DaemonControllerMac::DoSetConfigAndStart, | |
134 base::Unretained(this), base::Passed(&config))); | |
44 } | 135 } |
45 | 136 |
46 void DaemonControllerMac::SetPin(const std::string& pin) { | 137 void DaemonControllerMac::SetPin(const std::string& pin) { |
47 NOTIMPLEMENTED(); | 138 NOTIMPLEMENTED(); |
48 } | 139 } |
49 | 140 |
50 void DaemonControllerMac::Stop() { | 141 void DaemonControllerMac::Stop() { |
51 NOTIMPLEMENTED(); | 142 auth_thread_.message_loop_proxy()->PostTask( |
143 FROM_HERE, | |
144 base::Bind(&DaemonControllerMac::DoStop, base::Unretained(this))); | |
145 } | |
146 | |
147 void DaemonControllerMac::DoGetConfig(const GetConfigCallback& callback) { | |
148 JsonHostConfig host_config(FilePath(kHostConfigFile), | |
149 base::MessageLoopProxy::current()); | |
150 host_config.Read(); | |
151 | |
152 scoped_ptr<base::DictionaryValue> config(new base::DictionaryValue()); | |
153 | |
154 const char* key = "host_id"; | |
155 std::string value; | |
156 if (host_config.GetString(key, &value)) | |
157 config.get()->SetString(key, value); | |
158 key = "xmpp_login"; | |
159 if (host_config.GetString(key, &value)) | |
160 config.get()->SetString(key, value); | |
161 | |
162 callback.Run(config.Pass()); | |
163 } | |
164 | |
165 void DaemonControllerMac::DoSetConfigAndStart( | |
166 scoped_ptr<base::DictionaryValue> config) { | |
167 // JsonHostConfig doesn't provide a way to save on the current thread, wait | |
168 // for completion, and know whether the save succeeded. Instead, use | |
169 // base::JSONWriter directly. | |
170 | |
171 // TODO(lambroslambrou): Improve the JsonHostConfig interface. | |
172 std::string file_content; | |
173 base::JSONWriter::WriteWithOptions(config.get(), | |
174 base::JSONWriter::OPTIONS_PRETTY_PRINT, | |
Jamie
2012/03/29 05:20:29
Optional: Pretty-printing doesn't seem necessary.
Lambros
2012/03/29 18:20:44
Done.
| |
175 &file_content); | |
176 if (file_util::WriteFile(FilePath(kHostConfigFile), file_content.c_str(), | |
177 file_content.size()) != | |
178 static_cast<int>(file_content.size())) { | |
179 LOG(ERROR) << "Failed to write config file: " << kHostConfigFile; | |
180 base::AutoLock lock(start_failed_lock_); | |
181 start_failed_ = true; | |
182 return; | |
183 } | |
184 | |
185 // Creating the trigger file causes launchd to start the service, so the | |
186 // extra step performed in DoStop() is not necessary here. | |
187 if (!RunToolScriptAsRoot("--enable")) { | |
188 base::AutoLock lock(start_failed_lock_); | |
189 start_failed_ = true; | |
190 } | |
191 } | |
192 | |
193 void DaemonControllerMac::DoStop() { | |
194 if (!RunToolScriptAsRoot("--disable")) | |
195 return; | |
196 | |
197 // Deleting the trigger file does not cause launchd to stop the service. | |
198 // Since the service is running for the local user's desktop (not as root), | |
199 // it has to be stopped for that user. This cannot easily be done in the | |
200 // shell-script running as root, so it is done here instead. | |
201 StopService(); | |
202 } | |
203 | |
204 bool DaemonControllerMac::RunToolScriptAsRoot(const char* command) { | |
205 base::mac::ScopedAuthorizationRef authorization( | |
206 base::mac::AuthorizationCreateToRunAsRoot(CFSTR(""))); | |
dmac
2012/03/29 14:52:41
No prompt?
Lambros
2012/03/29 18:20:44
Added TODO.
| |
207 if (!authorization) { | |
208 LOG(ERROR) << "Failed to get root privileges."; | |
209 return false; | |
210 } | |
211 | |
212 const char* arguments[] = { command, NULL }; | |
213 int exit_status; | |
214 OSStatus status = base::mac::ExecuteWithPrivilegesAndWait( | |
dmac
2012/03/29 14:52:41
Doing it this way is somewhat deprecated on Lion.
Lambros
2012/03/29 18:20:44
This is non-trivial, unfortunately. The SM framew
| |
215 authorization.get(), | |
216 kStartStopTool, | |
dmac
2012/03/29 14:52:41
instead of running kStartStopTool directly with fu
Lambros
2012/03/29 18:20:44
Raised bug-report and added TODO for this.
On 201
dmac
2012/03/29 23:46:25
OK... I would consider this to be of high priority
| |
217 kAuthorizationFlagDefaults, | |
218 arguments, | |
219 NULL, | |
220 &exit_status); | |
221 if (status != errAuthorizationSuccess) { | |
222 OSSTATUS_LOG(ERROR, status) << "AuthorizationExecuteWithPrivileges"; | |
223 return false; | |
224 } | |
225 if (exit_status != 0) { | |
226 LOG(ERROR) << kStartStopTool << " failed with exit status " << exit_status; | |
227 return false; | |
228 } | |
229 | |
230 return true; | |
231 } | |
232 | |
233 bool DaemonControllerMac::StopService() { | |
234 base::mac::ScopedLaunchData response( | |
235 base::mac::MessageForJob(kServiceName, LAUNCH_KEY_STOPJOB)); | |
236 if (!response) { | |
237 LOG(ERROR) << "Failed to send message to launchd"; | |
238 return false; | |
239 } | |
240 | |
241 // Got a response, so check if launchd sent a non-zero error code, otherwise | |
242 // assume the command was successful. | |
243 if (launch_data_get_type(response.get()) == LAUNCH_DATA_ERRNO) { | |
244 int error = launch_data_get_errno(response.get()); | |
245 if (error) { | |
246 LOG(ERROR) << "launchd returned error " << error; | |
247 return false; | |
248 } | |
249 } | |
250 return true; | |
52 } | 251 } |
53 | 252 |
54 } // namespace | 253 } // namespace |
55 | 254 |
56 DaemonController* remoting::DaemonController::Create() { | 255 DaemonController* remoting::DaemonController::Create() { |
57 return new DaemonControllerMac(); | 256 return new DaemonControllerMac(); |
58 } | 257 } |
59 | 258 |
60 } // namespace remoting | 259 } // namespace remoting |
OLD | NEW |