Chromium Code Reviews| 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> | 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" |
|
Jamie
2012/04/27 18:21:04
Given your comment below, is this still needed?
Lambros
2012/04/28 01:54:19
It's needed for base::mac::GetLocalDirectory().
| |
| 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; | |
|
Jamie
2012/04/27 18:21:04
It might be worth investigating the discrepancy as
Lambros
2012/04/28 01:54:19
The definition in base/mac/foundation_util.h is in
| |
| 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 Loading... | |
| 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 Loading... | |
| 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 |
| OLD | NEW |