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