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/" |
| 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 DaemonController::STATE_NOT_IMPLEMENTED; |
| 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_ ? DaemonController::STATE_START_FAILED : |
| 109 DaemonController::STATE_STOPPED; |
| 110 } else { |
| 111 return 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::Write(config.get(), &file_content); |
| 174 if (file_util::WriteFile(FilePath(kHostConfigFile), file_content.c_str(), |
| 175 file_content.size()) != |
| 176 static_cast<int>(file_content.size())) { |
| 177 LOG(ERROR) << "Failed to write config file: " << kHostConfigFile; |
| 178 base::AutoLock lock(start_failed_lock_); |
| 179 start_failed_ = true; |
| 180 return; |
| 181 } |
| 182 |
| 183 // Creating the trigger file causes launchd to start the service, so the |
| 184 // extra step performed in DoStop() is not necessary here. |
| 185 if (!RunToolScriptAsRoot("--enable")) { |
| 186 base::AutoLock lock(start_failed_lock_); |
| 187 start_failed_ = true; |
| 188 } |
| 189 } |
| 190 |
| 191 void DaemonControllerMac::DoStop() { |
| 192 if (!RunToolScriptAsRoot("--disable")) |
| 193 return; |
| 194 |
| 195 // Deleting the trigger file does not cause launchd to stop the service. |
| 196 // Since the service is running for the local user's desktop (not as root), |
| 197 // it has to be stopped for that user. This cannot easily be done in the |
| 198 // shell-script running as root, so it is done here instead. |
| 199 StopService(); |
| 200 } |
| 201 |
| 202 bool DaemonControllerMac::RunToolScriptAsRoot(const char* command) { |
| 203 // TODO(lambroslambrou): Supply a localized prompt string here. |
| 204 base::mac::ScopedAuthorizationRef authorization( |
| 205 base::mac::AuthorizationCreateToRunAsRoot(CFSTR(""))); |
| 206 if (!authorization) { |
| 207 LOG(ERROR) << "Failed to get root privileges."; |
| 208 return false; |
| 209 } |
| 210 |
| 211 // TODO(lambroslambrou): Check permissions and ownership of tool script, and |
| 212 // use sandbox-exec to minimize exposure - http://crbug.com/120903 |
| 213 const char* arguments[] = { command, NULL }; |
| 214 int exit_status; |
| 215 OSStatus status = base::mac::ExecuteWithPrivilegesAndWait( |
| 216 authorization.get(), |
| 217 kStartStopTool, |
| 218 kAuthorizationFlagDefaults, |
| 219 arguments, |
| 220 NULL, |
| 221 &exit_status); |
| 222 if (status != errAuthorizationSuccess) { |
| 223 OSSTATUS_LOG(ERROR, status) << "AuthorizationExecuteWithPrivileges"; |
| 224 return false; |
| 225 } |
| 226 if (exit_status != 0) { |
| 227 LOG(ERROR) << kStartStopTool << " failed with exit status " << exit_status; |
| 228 return false; |
| 229 } |
| 230 |
| 231 return true; |
| 232 } |
| 233 |
| 234 bool DaemonControllerMac::StopService() { |
| 235 base::mac::ScopedLaunchData response( |
| 236 base::mac::MessageForJob(kServiceName, LAUNCH_KEY_STOPJOB)); |
| 237 if (!response) { |
| 238 LOG(ERROR) << "Failed to send message to launchd"; |
| 239 return false; |
| 240 } |
| 241 |
| 242 // Got a response, so check if launchd sent a non-zero error code, otherwise |
| 243 // assume the command was successful. |
| 244 if (launch_data_get_type(response.get()) == LAUNCH_DATA_ERRNO) { |
| 245 int error = launch_data_get_errno(response.get()); |
| 246 if (error) { |
| 247 LOG(ERROR) << "launchd returned error " << error; |
| 248 return false; |
| 249 } |
| 250 } |
| 251 return true; |
52 } | 252 } |
53 | 253 |
54 } // namespace | 254 } // namespace |
55 | 255 |
56 DaemonController* remoting::DaemonController::Create() { | 256 DaemonController* remoting::DaemonController::Create() { |
57 return new DaemonControllerMac(); | 257 return new DaemonControllerMac(); |
58 } | 258 } |
59 | 259 |
60 } // namespace remoting | 260 } // namespace remoting |
OLD | NEW |