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.
5 #include "remoting/host/curtain_mode.h"
7 #include <ApplicationServices/ApplicationServices.h>
8 #include <Carbon/Carbon.h>
9 #include <Security/Security.h>
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"
22 using remoting::ClientSessionControl
;
24 const char* kCGSessionPath
=
25 "/System/Library/CoreServices/Menu Extras/User.menu/Contents/Resources/"
28 // Used to detach the current session from the local console and disconnect
29 // the connnection if it gets re-attached.
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
> {
37 scoped_refptr
<base::SingleThreadTaskRunner
> caller_task_runner
,
38 scoped_refptr
<base::SingleThreadTaskRunner
> ui_task_runner
,
39 base::WeakPtr
<ClientSessionControl
> client_session_control
);
45 friend class base::RefCountedThreadSafe
<SessionWatcher
>;
46 virtual ~SessionWatcher();
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();
52 // Installs the switch-in handler.
53 bool InstallEventHandler();
55 // Removes the switch-in handler.
56 void RemoveEventHandler();
58 // Disconnects the client session.
59 void DisconnectSession();
61 // Handlers for the switch-in event.
62 static OSStatus
SessionActivateHandler(EventHandlerCallRef handler
,
66 // Task runner on which public methods of this class must be called.
67 scoped_refptr
<base::SingleThreadTaskRunner
> caller_task_runner_
;
69 // Task runner representing the thread receiving Carbon events.
70 scoped_refptr
<base::SingleThreadTaskRunner
> ui_task_runner_
;
72 // Used to disconnect the client session.
73 base::WeakPtr
<ClientSessionControl
> client_session_control_
;
75 EventHandlerRef event_handler_
;
77 DISALLOW_COPY_AND_ASSIGN(SessionWatcher
);
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_(nullptr) {
90 void SessionWatcher::Start() {
91 DCHECK(caller_task_runner_
->BelongsToCurrentThread());
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));
102 void SessionWatcher::Stop() {
103 DCHECK(caller_task_runner_
->BelongsToCurrentThread());
105 client_session_control_
.reset();
106 ui_task_runner_
->PostTask(
107 FROM_HERE
, base::Bind(&SessionWatcher::RemoveEventHandler
, this));
110 SessionWatcher::~SessionWatcher() {
111 DCHECK(!event_handler_
);
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
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.";
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.";
137 base::ScopedCFTypeRef
<CFDictionaryRef
> session(
138 CGSessionCopyCurrentDictionary());
140 // CGSessionCopyCurrentDictionary has been observed to return nullptr 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
!= nullptr);
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();
155 execl(kCGSessionPath
, kCGSessionPath
, "-suspend", nullptr);
157 } else if (child
> 0) {
159 waitpid(child
, &status
, 0);
161 LOG(ERROR
) << kCGSessionPath
<< " failed.";
166 LOG(ERROR
) << "fork() failed.";
173 bool SessionWatcher::InstallEventHandler() {
174 DCHECK(ui_task_runner_
->BelongsToCurrentThread());
175 DCHECK(!event_handler_
);
178 event
.eventClass
= kEventClassSystem
;
179 event
.eventKind
= kEventSystemUserSessionActivated
;
180 OSStatus result
= ::InstallApplicationEventHandler(
181 NewEventHandlerUPP(SessionActivateHandler
), 1, &event
, this,
183 if (result
!= noErr
) {
184 event_handler_
= nullptr;
192 void SessionWatcher::RemoveEventHandler() {
193 DCHECK(ui_task_runner_
->BelongsToCurrentThread());
195 if (event_handler_
) {
196 ::RemoveEventHandler(event_handler_
);
197 event_handler_
= nullptr;
201 void SessionWatcher::DisconnectSession() {
202 if (!caller_task_runner_
->BelongsToCurrentThread()) {
203 caller_task_runner_
->PostTask(
204 FROM_HERE
, base::Bind(&SessionWatcher::DisconnectSession
, this));
208 if (client_session_control_
)
209 client_session_control_
->DisconnectSession();
212 OSStatus
SessionWatcher::SessionActivateHandler(EventHandlerCallRef handler
,
215 static_cast<SessionWatcher
*>(user_data
)->DisconnectSession();
223 class CurtainModeMac
: public CurtainMode
{
226 scoped_refptr
<base::SingleThreadTaskRunner
> caller_task_runner
,
227 scoped_refptr
<base::SingleThreadTaskRunner
> ui_task_runner
,
228 base::WeakPtr
<ClientSessionControl
> client_session_control
);
229 ~CurtainModeMac() override
;
231 // Overriden from CurtainMode.
232 bool Activate() override
;
235 scoped_refptr
<SessionWatcher
> session_watcher_
;
237 DISALLOW_COPY_AND_ASSIGN(CurtainModeMac
);
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
,
246 client_session_control
)) {
249 CurtainModeMac::~CurtainModeMac() {
250 session_watcher_
->Stop();
253 bool CurtainModeMac::Activate() {
254 session_watcher_
->Start();
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 make_scoped_ptr(new CurtainModeMac(
264 caller_task_runner
, ui_task_runner
, client_session_control
));
267 } // namespace remoting