Home | History | Annotate | Download | only in win
      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 // This file implements the Windows service controlling Me2Me host processes
      6 // running within user sessions.
      7 
      8 #include "remoting/host/win/host_service.h"
      9 
     10 #include <sddl.h>
     11 #include <windows.h>
     12 #include <wtsapi32.h>
     13 
     14 #include "base/base_paths.h"
     15 #include "base/base_switches.h"
     16 #include "base/bind.h"
     17 #include "base/command_line.h"
     18 #include "base/files/file_path.h"
     19 #include "base/message_loop/message_loop.h"
     20 #include "base/run_loop.h"
     21 #include "base/single_thread_task_runner.h"
     22 #include "base/strings/utf_string_conversions.h"
     23 #include "base/threading/thread.h"
     24 #include "base/win/message_window.h"
     25 #include "base/win/scoped_com_initializer.h"
     26 #include "remoting/base/auto_thread.h"
     27 #include "remoting/base/scoped_sc_handle_win.h"
     28 #include "remoting/host/branding.h"
     29 #include "remoting/host/daemon_process.h"
     30 #include "remoting/host/host_exit_codes.h"
     31 #include "remoting/host/logging.h"
     32 #include "remoting/host/win/com_security.h"
     33 #include "remoting/host/win/core_resource.h"
     34 #include "remoting/host/win/wts_terminal_observer.h"
     35 
     36 namespace remoting {
     37 
     38 namespace {
     39 
     40 const char kIoThreadName[] = "I/O thread";
     41 
     42 // Command line switches:
     43 
     44 // "--console" runs the service interactively for debugging purposes.
     45 const char kConsoleSwitchName[] = "console";
     46 
     47 // Security descriptor allowing local processes running under SYSTEM or
     48 // LocalService accounts to call COM methods exposed by the daemon.
     49 const wchar_t kComProcessSd[] =
     50     SDDL_OWNER L":" SDDL_LOCAL_SYSTEM
     51     SDDL_GROUP L":" SDDL_LOCAL_SYSTEM
     52     SDDL_DACL L":"
     53     SDDL_ACE(SDDL_ACCESS_ALLOWED, SDDL_COM_EXECUTE_LOCAL, SDDL_LOCAL_SYSTEM)
     54     SDDL_ACE(SDDL_ACCESS_ALLOWED, SDDL_COM_EXECUTE_LOCAL, SDDL_LOCAL_SERVICE);
     55 
     56 // Appended to |kComProcessSd| to specify that only callers running at medium or
     57 // higher integrity level are allowed to call COM methods exposed by the daemon.
     58 const wchar_t kComProcessMandatoryLabel[] =
     59     SDDL_SACL L":"
     60     SDDL_ACE(SDDL_MANDATORY_LABEL, SDDL_NO_EXECUTE_UP, SDDL_ML_MEDIUM);
     61 
     62 }  // namespace
     63 
     64 HostService* HostService::GetInstance() {
     65   return Singleton<HostService>::get();
     66 }
     67 
     68 bool HostService::InitWithCommandLine(const CommandLine* command_line) {
     69   CommandLine::StringVector args = command_line->GetArgs();
     70   if (!args.empty()) {
     71     LOG(ERROR) << "No positional parameters expected.";
     72     return false;
     73   }
     74 
     75   // Run interactively if needed.
     76   if (run_routine_ == &HostService::RunAsService &&
     77       command_line->HasSwitch(kConsoleSwitchName)) {
     78     run_routine_ = &HostService::RunInConsole;
     79   }
     80 
     81   return true;
     82 }
     83 
     84 int HostService::Run() {
     85   return (this->*run_routine_)();
     86 }
     87 
     88 bool HostService::AddWtsTerminalObserver(const std::string& terminal_id,
     89                                          WtsTerminalObserver* observer) {
     90   DCHECK(main_task_runner_->BelongsToCurrentThread());
     91 
     92   RegisteredObserver registered_observer;
     93   registered_observer.terminal_id = terminal_id;
     94   registered_observer.session_id = kInvalidSessionId;
     95   registered_observer.observer = observer;
     96 
     97   bool session_id_found = false;
     98   std::list<RegisteredObserver>::const_iterator i;
     99   for (i = observers_.begin(); i != observers_.end(); ++i) {
    100     // Get the attached session ID from another observer watching the same WTS
    101     // console if any.
    102     if (i->terminal_id == terminal_id) {
    103       registered_observer.session_id = i->session_id;
    104       session_id_found = true;
    105     }
    106 
    107     // Check that |observer| hasn't been registered already.
    108     if (i->observer == observer)
    109       return false;
    110   }
    111 
    112   // If |terminal_id| is new, enumerate all sessions to see if there is one
    113   // attached to |terminal_id|.
    114   if (!session_id_found)
    115     registered_observer.session_id = LookupSessionId(terminal_id);
    116 
    117   observers_.push_back(registered_observer);
    118 
    119   if (registered_observer.session_id != kInvalidSessionId) {
    120     observer->OnSessionAttached(registered_observer.session_id);
    121   }
    122 
    123   return true;
    124 }
    125 
    126 void HostService::RemoveWtsTerminalObserver(WtsTerminalObserver* observer) {
    127   DCHECK(main_task_runner_->BelongsToCurrentThread());
    128 
    129   std::list<RegisteredObserver>::const_iterator i;
    130   for (i = observers_.begin(); i != observers_.end(); ++i) {
    131     if (i->observer == observer) {
    132       observers_.erase(i);
    133       return;
    134     }
    135   }
    136 }
    137 
    138 HostService::HostService() :
    139   run_routine_(&HostService::RunAsService),
    140   service_status_handle_(0),
    141   stopped_event_(true, false),
    142   weak_factory_(this) {
    143 }
    144 
    145 HostService::~HostService() {
    146 }
    147 
    148 void HostService::OnSessionChange(uint32 event, uint32 session_id) {
    149   DCHECK(main_task_runner_->BelongsToCurrentThread());
    150   DCHECK_NE(session_id, kInvalidSessionId);
    151 
    152   // Process only attach/detach notifications.
    153   if (event != WTS_CONSOLE_CONNECT && event != WTS_CONSOLE_DISCONNECT &&
    154       event != WTS_REMOTE_CONNECT && event != WTS_REMOTE_DISCONNECT) {
    155     return;
    156   }
    157 
    158   // Assuming that notification can arrive later query the current state of
    159   // |session_id|.
    160   std::string terminal_id;
    161   bool attached = LookupTerminalId(session_id, &terminal_id);
    162 
    163   std::list<RegisteredObserver>::iterator i = observers_.begin();
    164   while (i != observers_.end()) {
    165     std::list<RegisteredObserver>::iterator next = i;
    166     ++next;
    167 
    168     // Issue a detach notification if the session was detached from a client or
    169     // if it is now attached to a different client.
    170     if (i->session_id == session_id &&
    171         (!attached || !(i->terminal_id == terminal_id))) {
    172       i->session_id = kInvalidSessionId;
    173       i->observer->OnSessionDetached();
    174       i = next;
    175       continue;
    176     }
    177 
    178     // The client currently attached to |session_id| was attached to a different
    179     // session before. Reconnect it to |session_id|.
    180     if (attached && i->terminal_id == terminal_id &&
    181         i->session_id != session_id) {
    182       WtsTerminalObserver* observer = i->observer;
    183 
    184       if (i->session_id != kInvalidSessionId) {
    185         i->session_id = kInvalidSessionId;
    186         i->observer->OnSessionDetached();
    187       }
    188 
    189       // Verify that OnSessionDetached() above didn't remove |observer|
    190       // from the list.
    191       std::list<RegisteredObserver>::iterator j = next;
    192       --j;
    193       if (j->observer == observer) {
    194         j->session_id = session_id;
    195         observer->OnSessionAttached(session_id);
    196       }
    197     }
    198 
    199     i = next;
    200   }
    201 }
    202 
    203 void HostService::CreateLauncher(
    204     scoped_refptr<AutoThreadTaskRunner> task_runner) {
    205   // Launch the I/O thread.
    206   scoped_refptr<AutoThreadTaskRunner> io_task_runner =
    207       AutoThread::CreateWithType(
    208           kIoThreadName, task_runner, base::MessageLoop::TYPE_IO);
    209   if (!io_task_runner) {
    210     LOG(FATAL) << "Failed to start the I/O thread";
    211     return;
    212   }
    213 
    214   daemon_process_ = DaemonProcess::Create(
    215       task_runner,
    216       io_task_runner,
    217       base::Bind(&HostService::StopDaemonProcess, weak_ptr_));
    218 }
    219 
    220 int HostService::RunAsService() {
    221   SERVICE_TABLE_ENTRYW dispatch_table[] = {
    222     { const_cast<LPWSTR>(kWindowsServiceName), &HostService::ServiceMain },
    223     { NULL, NULL }
    224   };
    225 
    226   if (!StartServiceCtrlDispatcherW(dispatch_table)) {
    227     LOG_GETLASTERROR(ERROR)
    228         << "Failed to connect to the service control manager";
    229     return kInitializationFailed;
    230   }
    231 
    232   // Wait until the service thread completely exited to avoid concurrent
    233   // teardown of objects registered with base::AtExitManager and object
    234   // destoyed by the service thread.
    235   stopped_event_.Wait();
    236 
    237   return kSuccessExitCode;
    238 }
    239 
    240 void HostService::RunAsServiceImpl() {
    241   base::MessageLoop message_loop(base::MessageLoop::TYPE_UI);
    242   base::RunLoop run_loop;
    243   main_task_runner_ = message_loop.message_loop_proxy();
    244   weak_ptr_ = weak_factory_.GetWeakPtr();
    245 
    246   // Register the service control handler.
    247   service_status_handle_ = RegisterServiceCtrlHandlerExW(
    248       kWindowsServiceName, &HostService::ServiceControlHandler, this);
    249   if (service_status_handle_ == 0) {
    250     LOG_GETLASTERROR(ERROR)
    251         << "Failed to register the service control handler";
    252     return;
    253   }
    254 
    255   // Report running status of the service.
    256   SERVICE_STATUS service_status;
    257   ZeroMemory(&service_status, sizeof(service_status));
    258   service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
    259   service_status.dwCurrentState = SERVICE_RUNNING;
    260   service_status.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN |
    261                                       SERVICE_ACCEPT_STOP |
    262                                       SERVICE_ACCEPT_SESSIONCHANGE;
    263   service_status.dwWin32ExitCode = kSuccessExitCode;
    264   if (!SetServiceStatus(service_status_handle_, &service_status)) {
    265     LOG_GETLASTERROR(ERROR)
    266         << "Failed to report service status to the service control manager";
    267     return;
    268   }
    269 
    270   // Initialize COM.
    271   base::win::ScopedCOMInitializer com_initializer;
    272   if (!com_initializer.succeeded())
    273     return;
    274 
    275   if (!InitializeComSecurity(WideToUTF8(kComProcessSd),
    276                              WideToUTF8(kComProcessMandatoryLabel), false)) {
    277     return;
    278   }
    279 
    280   CreateLauncher(scoped_refptr<AutoThreadTaskRunner>(
    281       new AutoThreadTaskRunner(main_task_runner_,
    282                                run_loop.QuitClosure())));
    283 
    284   // Run the service.
    285   run_loop.Run();
    286   weak_factory_.InvalidateWeakPtrs();
    287 
    288   // Tell SCM that the service is stopped.
    289   service_status.dwCurrentState = SERVICE_STOPPED;
    290   service_status.dwControlsAccepted = 0;
    291   if (!SetServiceStatus(service_status_handle_, &service_status)) {
    292     LOG_GETLASTERROR(ERROR)
    293         << "Failed to report service status to the service control manager";
    294     return;
    295   }
    296 }
    297 
    298 int HostService::RunInConsole() {
    299   base::MessageLoop message_loop(base::MessageLoop::TYPE_UI);
    300   base::RunLoop run_loop;
    301   main_task_runner_ = message_loop.message_loop_proxy();
    302   weak_ptr_ = weak_factory_.GetWeakPtr();
    303 
    304   int result = kInitializationFailed;
    305 
    306   // Initialize COM.
    307   base::win::ScopedCOMInitializer com_initializer;
    308   if (!com_initializer.succeeded())
    309     return result;
    310 
    311   if (!InitializeComSecurity(WideToUTF8(kComProcessSd),
    312                              WideToUTF8(kComProcessMandatoryLabel), false)) {
    313     return result;
    314   }
    315 
    316   // Subscribe to Ctrl-C and other console events.
    317   if (!SetConsoleCtrlHandler(&HostService::ConsoleControlHandler, TRUE)) {
    318     LOG_GETLASTERROR(ERROR)
    319         << "Failed to set console control handler";
    320     return result;
    321   }
    322 
    323   // Create a window for receiving session change notifications.
    324   base::win::MessageWindow window;
    325   if (!window.Create(base::Bind(&HostService::HandleMessage,
    326                                 base::Unretained(this)))) {
    327     LOG_GETLASTERROR(ERROR)
    328         << "Failed to create the session notification window";
    329     goto cleanup;
    330   }
    331 
    332   // Subscribe to session change notifications.
    333   if (WTSRegisterSessionNotification(window.hwnd(),
    334                                      NOTIFY_FOR_ALL_SESSIONS) != FALSE) {
    335     CreateLauncher(scoped_refptr<AutoThreadTaskRunner>(
    336         new AutoThreadTaskRunner(main_task_runner_,
    337                                  run_loop.QuitClosure())));
    338 
    339     // Run the service.
    340     run_loop.Run();
    341 
    342     // Release the control handler.
    343     stopped_event_.Signal();
    344 
    345     WTSUnRegisterSessionNotification(window.hwnd());
    346     result = kSuccessExitCode;
    347   }
    348 
    349 cleanup:
    350   weak_factory_.InvalidateWeakPtrs();
    351 
    352   // Unsubscribe from console events. Ignore the exit code. There is nothing
    353   // we can do about it now and the program is about to exit anyway. Even if
    354   // it crashes nothing is going to be broken because of it.
    355   SetConsoleCtrlHandler(&HostService::ConsoleControlHandler, FALSE);
    356 
    357   return result;
    358 }
    359 
    360 void HostService::StopDaemonProcess() {
    361   DCHECK(main_task_runner_->BelongsToCurrentThread());
    362 
    363   daemon_process_.reset();
    364 }
    365 
    366 bool HostService::HandleMessage(
    367     UINT message, WPARAM wparam, LPARAM lparam, LRESULT* result) {
    368   if (message == WM_WTSSESSION_CHANGE) {
    369     OnSessionChange(wparam, lparam);
    370     *result = 0;
    371     return true;
    372   }
    373 
    374   return false;
    375 }
    376 
    377 // static
    378 BOOL WINAPI HostService::ConsoleControlHandler(DWORD event) {
    379   HostService* self = HostService::GetInstance();
    380   switch (event) {
    381     case CTRL_C_EVENT:
    382     case CTRL_BREAK_EVENT:
    383     case CTRL_CLOSE_EVENT:
    384     case CTRL_LOGOFF_EVENT:
    385     case CTRL_SHUTDOWN_EVENT:
    386       self->main_task_runner_->PostTask(
    387           FROM_HERE, base::Bind(&HostService::StopDaemonProcess,
    388                                 self->weak_ptr_));
    389       return TRUE;
    390 
    391     default:
    392       return FALSE;
    393   }
    394 }
    395 
    396 // static
    397 DWORD WINAPI HostService::ServiceControlHandler(DWORD control,
    398                                                 DWORD event_type,
    399                                                 LPVOID event_data,
    400                                                 LPVOID context) {
    401   HostService* self = reinterpret_cast<HostService*>(context);
    402   switch (control) {
    403     case SERVICE_CONTROL_INTERROGATE:
    404       return NO_ERROR;
    405 
    406     case SERVICE_CONTROL_SHUTDOWN:
    407     case SERVICE_CONTROL_STOP:
    408       self->main_task_runner_->PostTask(
    409           FROM_HERE, base::Bind(&HostService::StopDaemonProcess,
    410                                 self->weak_ptr_));
    411       return NO_ERROR;
    412 
    413     case SERVICE_CONTROL_SESSIONCHANGE:
    414       self->main_task_runner_->PostTask(FROM_HERE, base::Bind(
    415           &HostService::OnSessionChange, self->weak_ptr_, event_type,
    416           reinterpret_cast<WTSSESSION_NOTIFICATION*>(event_data)->dwSessionId));
    417       return NO_ERROR;
    418 
    419     default:
    420       return ERROR_CALL_NOT_IMPLEMENTED;
    421   }
    422 }
    423 
    424 // static
    425 VOID WINAPI HostService::ServiceMain(DWORD argc, WCHAR* argv[]) {
    426   HostService* self = HostService::GetInstance();
    427 
    428   // Run the service.
    429   self->RunAsServiceImpl();
    430 
    431   // Release the control handler and notify the main thread that it can exit
    432   // now.
    433   self->stopped_event_.Signal();
    434 }
    435 
    436 int DaemonProcessMain() {
    437   HostService* service = HostService::GetInstance();
    438   if (!service->InitWithCommandLine(CommandLine::ForCurrentProcess())) {
    439     return kUsageExitCode;
    440   }
    441 
    442   return service->Run();
    443 }
    444 
    445 } // namespace remoting
    446