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 3 // found in the LICENSE file. 4 5 #include "remoting/host/curtain_mode.h" 6 7 #include <ApplicationServices/ApplicationServices.h> 8 #include <Carbon/Carbon.h> 9 #include <Security/Security.h> 10 #include <unistd.h> 11 12 #include "base/bind.h" 13 #include "base/location.h" 14 #include "base/logging.h" 15 #include "base/mac/mac_util.h" 16 #include "base/mac/scoped_cftyperef.h" 17 #include "base/single_thread_task_runner.h" 18 #include "remoting/host/client_session_control.h" 19 20 namespace { 21 22 using remoting::ClientSessionControl; 23 24 const char* kCGSessionPath = 25 "/System/Library/CoreServices/Menu Extras/User.menu/Contents/Resources/" 26 "CGSession"; 27 28 // Used to detach the current session from the local console and disconnect 29 // the connnection if it gets re-attached. 30 // 31 // Because the switch-in handler can only called on the main (UI) thread, this 32 // class installs the handler and detaches the current session from the console 33 // on the UI thread as well. 34 class SessionWatcher : public base::RefCountedThreadSafe<SessionWatcher> { 35 public: 36 SessionWatcher( 37 scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner, 38 scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner, 39 base::WeakPtr<ClientSessionControl> client_session_control); 40 41 void Start(); 42 void Stop(); 43 44 private: 45 friend class base::RefCountedThreadSafe<SessionWatcher>; 46 virtual ~SessionWatcher(); 47 48 // Detaches the session from the console and install the switch-in handler to 49 // detect when the session re-attaches back. 50 void ActivateCurtain(); 51 52 // Installs the switch-in handler. 53 bool InstallEventHandler(); 54 55 // Removes the switch-in handler. 56 void RemoveEventHandler(); 57 58 // Disconnects the client session. 59 void DisconnectSession(); 60 61 // Handlers for the switch-in event. 62 static OSStatus SessionActivateHandler(EventHandlerCallRef handler, 63 EventRef event, 64 void* user_data); 65 66 // Task runner on which public methods of this class must be called. 67 scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner_; 68 69 // Task runner representing the thread receiving Carbon events. 70 scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner_; 71 72 // Used to disconnect the client session. 73 base::WeakPtr<ClientSessionControl> client_session_control_; 74 75 EventHandlerRef event_handler_; 76 77 DISALLOW_COPY_AND_ASSIGN(SessionWatcher); 78 }; 79 80 SessionWatcher::SessionWatcher( 81 scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner, 82 scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner, 83 base::WeakPtr<ClientSessionControl> client_session_control) 84 : caller_task_runner_(caller_task_runner), 85 ui_task_runner_(ui_task_runner), 86 client_session_control_(client_session_control), 87 event_handler_(NULL) { 88 } 89 90 void SessionWatcher::Start() { 91 DCHECK(caller_task_runner_->BelongsToCurrentThread()); 92 93 // Activate curtain asynchronously since it has to be done on the UI thread. 94 // Because the curtain activation is asynchronous, it is possible that 95 // the connection will not be curtained for a brief moment. This seems to be 96 // unaviodable as long as the curtain enforcement depends on processing of 97 // the switch-in notifications. 98 ui_task_runner_->PostTask( 99 FROM_HERE, base::Bind(&SessionWatcher::ActivateCurtain, this)); 100 } 101 102 void SessionWatcher::Stop() { 103 DCHECK(caller_task_runner_->BelongsToCurrentThread()); 104 105 client_session_control_.reset(); 106 ui_task_runner_->PostTask( 107 FROM_HERE, base::Bind(&SessionWatcher::RemoveEventHandler, this)); 108 } 109 110 SessionWatcher::~SessionWatcher() { 111 DCHECK(!event_handler_); 112 } 113 114 void SessionWatcher::ActivateCurtain() { 115 // Curtain mode causes problems with the login screen on Lion only (starting 116 // with 10.7.3), so disable it on that platform. There is a work-around, but 117 // it involves modifying a system Plist pertaining to power-management, so 118 // it's not something that should be done automatically. For more details, 119 // see https://discussions.apple.com/thread/3209415?start=690&tstart=0 120 // 121 // TODO(jamiewalch): If the underlying OS bug is ever fixed, we should support 122 // curtain mode on suitable versions of Lion. 123 if (base::mac::IsOSLion()) { 124 LOG(ERROR) << "Host curtaining is not supported on Mac OS X 10.7."; 125 DisconnectSession(); 126 return; 127 } 128 129 // Try to install the switch-in handler. Do this before switching out the 130 // current session so that the console session is not affected if it fails. 131 if (!InstallEventHandler()) { 132 LOG(ERROR) << "Failed to install the switch-in handler."; 133 DisconnectSession(); 134 return; 135 } 136 137 base::ScopedCFTypeRef<CFDictionaryRef> session( 138 CGSessionCopyCurrentDictionary()); 139 140 // CGSessionCopyCurrentDictionary has been observed to return NULL in some 141 // cases. Once the system is in this state, curtain mode will fail as the 142 // CGSession command thinks the session is not attached to the console. The 143 // only known remedy is logout or reboot. Since we're not sure what causes 144 // this, or how common it is, a crash report is useful in this case (note 145 // that the connection would have to be refused in any case, so this is no 146 // loss of functionality). 147 CHECK(session != NULL); 148 149 const void* on_console = CFDictionaryGetValue(session, 150 kCGSessionOnConsoleKey); 151 const void* logged_in = CFDictionaryGetValue(session, kCGSessionLoginDoneKey); 152 if (logged_in == kCFBooleanTrue && on_console == kCFBooleanTrue) { 153 pid_t child = fork(); 154 if (child == 0) { 155 execl(kCGSessionPath, kCGSessionPath, "-suspend", NULL); 156 _exit(1); 157 } else if (child > 0) { 158 int status = 0; 159 waitpid(child, &status, 0); 160 if (status != 0) { 161 LOG(ERROR) << kCGSessionPath << " failed."; 162 DisconnectSession(); 163 return; 164 } 165 } else { 166 LOG(ERROR) << "fork() failed."; 167 DisconnectSession(); 168 return; 169 } 170 } 171 } 172 173 bool SessionWatcher::InstallEventHandler() { 174 DCHECK(ui_task_runner_->BelongsToCurrentThread()); 175 DCHECK(!event_handler_); 176 177 EventTypeSpec event; 178 event.eventClass = kEventClassSystem; 179 event.eventKind = kEventSystemUserSessionActivated; 180 OSStatus result = ::InstallApplicationEventHandler( 181 NewEventHandlerUPP(SessionActivateHandler), 1, &event, this, 182 &event_handler_); 183 if (result != noErr) { 184 event_handler_ = NULL; 185 DisconnectSession(); 186 return false; 187 } 188 189 return true; 190 } 191 192 void SessionWatcher::RemoveEventHandler() { 193 DCHECK(ui_task_runner_->BelongsToCurrentThread()); 194 195 if (event_handler_) { 196 ::RemoveEventHandler(event_handler_); 197 event_handler_ = NULL; 198 } 199 } 200 201 void SessionWatcher::DisconnectSession() { 202 if (!caller_task_runner_->BelongsToCurrentThread()) { 203 caller_task_runner_->PostTask( 204 FROM_HERE, base::Bind(&SessionWatcher::DisconnectSession, this)); 205 return; 206 } 207 208 if (client_session_control_) 209 client_session_control_->DisconnectSession(); 210 } 211 212 OSStatus SessionWatcher::SessionActivateHandler(EventHandlerCallRef handler, 213 EventRef event, 214 void* user_data) { 215 static_cast<SessionWatcher*>(user_data)->DisconnectSession(); 216 return noErr; 217 } 218 219 } // namespace 220 221 namespace remoting { 222 223 class CurtainModeMac : public CurtainMode { 224 public: 225 CurtainModeMac( 226 scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner, 227 scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner, 228 base::WeakPtr<ClientSessionControl> client_session_control); 229 virtual ~CurtainModeMac(); 230 231 // Overriden from CurtainMode. 232 virtual bool Activate() OVERRIDE; 233 234 private: 235 scoped_refptr<SessionWatcher> session_watcher_; 236 237 DISALLOW_COPY_AND_ASSIGN(CurtainModeMac); 238 }; 239 240 CurtainModeMac::CurtainModeMac( 241 scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner, 242 scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner, 243 base::WeakPtr<ClientSessionControl> client_session_control) 244 : session_watcher_(new SessionWatcher(caller_task_runner, 245 ui_task_runner, 246 client_session_control)) { 247 } 248 249 CurtainModeMac::~CurtainModeMac() { 250 session_watcher_->Stop(); 251 } 252 253 bool CurtainModeMac::Activate() { 254 session_watcher_->Start(); 255 return true; 256 } 257 258 // static 259 scoped_ptr<CurtainMode> CurtainMode::Create( 260 scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner, 261 scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner, 262 base::WeakPtr<ClientSessionControl> client_session_control) { 263 return scoped_ptr<CurtainMode>(new CurtainModeMac(caller_task_runner, 264 ui_task_runner, 265 client_session_control)); 266 } 267 268 } // namespace remoting 269