Home | History | Annotate | Download | only in host
      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