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 // This file implements the Windows service controlling Me2Me host processes
6 // running within user sessions.
8 #include "remoting/host/win/host_service.h"
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"
40 const char kIoThreadName
[] = "I/O thread";
42 // Command line switches:
44 // "--console" runs the service interactively for debugging purposes.
45 const char kConsoleSwitchName
[] = "console";
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
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
);
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
[] =
60 SDDL_ACE(SDDL_MANDATORY_LABEL
, SDDL_NO_EXECUTE_UP
, SDDL_ML_MEDIUM
);
64 HostService
* HostService::GetInstance() {
65 return base::Singleton
<HostService
>::get();
68 bool HostService::InitWithCommandLine(const base::CommandLine
* command_line
) {
69 base::CommandLine::StringVector args
= command_line
->GetArgs();
71 LOG(ERROR
) << "No positional parameters expected.";
75 // Run interactively if needed.
76 if (run_routine_
== &HostService::RunAsService
&&
77 command_line
->HasSwitch(kConsoleSwitchName
)) {
78 run_routine_
= &HostService::RunInConsole
;
84 int HostService::Run() {
85 return (this->*run_routine_
)();
88 bool HostService::AddWtsTerminalObserver(const std::string
& terminal_id
,
89 WtsTerminalObserver
* observer
) {
90 DCHECK(main_task_runner_
->BelongsToCurrentThread());
92 RegisteredObserver registered_observer
;
93 registered_observer
.terminal_id
= terminal_id
;
94 registered_observer
.session_id
= kInvalidSessionId
;
95 registered_observer
.observer
= observer
;
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
102 if (i
->terminal_id
== terminal_id
) {
103 registered_observer
.session_id
= i
->session_id
;
104 session_id_found
= true;
107 // Check that |observer| hasn't been registered already.
108 if (i
->observer
== observer
)
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
);
117 observers_
.push_back(registered_observer
);
119 if (registered_observer
.session_id
!= kInvalidSessionId
) {
120 observer
->OnSessionAttached(registered_observer
.session_id
);
126 void HostService::RemoveWtsTerminalObserver(WtsTerminalObserver
* observer
) {
127 DCHECK(main_task_runner_
->BelongsToCurrentThread());
129 std::list
<RegisteredObserver
>::const_iterator i
;
130 for (i
= observers_
.begin(); i
!= observers_
.end(); ++i
) {
131 if (i
->observer
== observer
) {
138 HostService::HostService() :
139 run_routine_(&HostService::RunAsService
),
140 service_status_handle_(0),
141 stopped_event_(true, false),
142 weak_factory_(this) {
145 HostService::~HostService() {
148 void HostService::OnSessionChange(uint32 event
, uint32 session_id
) {
149 DCHECK(main_task_runner_
->BelongsToCurrentThread());
150 DCHECK_NE(session_id
, kInvalidSessionId
);
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
) {
158 // Assuming that notification can arrive later query the current state of
160 std::string terminal_id
;
161 bool attached
= LookupTerminalId(session_id
, &terminal_id
);
163 std::list
<RegisteredObserver
>::iterator i
= observers_
.begin();
164 while (i
!= observers_
.end()) {
165 std::list
<RegisteredObserver
>::iterator next
= i
;
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();
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
;
184 if (i
->session_id
!= kInvalidSessionId
) {
185 i
->session_id
= kInvalidSessionId
;
186 i
->observer
->OnSessionDetached();
189 // Verify that OnSessionDetached() above didn't remove |observer|
191 std::list
<RegisteredObserver
>::iterator j
= next
;
193 if (j
->observer
== observer
) {
194 j
->session_id
= session_id
;
195 observer
->OnSessionAttached(session_id
);
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
.get()) {
210 LOG(FATAL
) << "Failed to start the I/O thread";
214 daemon_process_
= DaemonProcess::Create(
217 base::Bind(&HostService::StopDaemonProcess
, weak_ptr_
));
220 int HostService::RunAsService() {
221 SERVICE_TABLE_ENTRYW dispatch_table
[] = {
222 { const_cast<LPWSTR
>(kWindowsServiceName
), &HostService::ServiceMain
},
226 if (!StartServiceCtrlDispatcherW(dispatch_table
)) {
227 PLOG(ERROR
) << "Failed to connect to the service control manager";
228 return kInitializationFailed
;
231 // Wait until the service thread completely exited to avoid concurrent
232 // teardown of objects registered with base::AtExitManager and object
233 // destoyed by the service thread.
234 stopped_event_
.Wait();
236 return kSuccessExitCode
;
239 void HostService::RunAsServiceImpl() {
240 base::MessageLoopForUI message_loop
;
241 base::RunLoop run_loop
;
242 main_task_runner_
= message_loop
.task_runner();
243 weak_ptr_
= weak_factory_
.GetWeakPtr();
245 // Register the service control handler.
246 service_status_handle_
= RegisterServiceCtrlHandlerExW(
247 kWindowsServiceName
, &HostService::ServiceControlHandler
, this);
248 if (service_status_handle_
== 0) {
249 PLOG(ERROR
) << "Failed to register the service control handler";
253 // Report running status of the service.
254 SERVICE_STATUS service_status
;
255 ZeroMemory(&service_status
, sizeof(service_status
));
256 service_status
.dwServiceType
= SERVICE_WIN32_OWN_PROCESS
;
257 service_status
.dwCurrentState
= SERVICE_RUNNING
;
258 service_status
.dwControlsAccepted
= SERVICE_ACCEPT_SHUTDOWN
|
259 SERVICE_ACCEPT_STOP
|
260 SERVICE_ACCEPT_SESSIONCHANGE
;
261 service_status
.dwWin32ExitCode
= kSuccessExitCode
;
262 if (!SetServiceStatus(service_status_handle_
, &service_status
)) {
264 << "Failed to report service status to the service control manager";
269 base::win::ScopedCOMInitializer com_initializer
;
270 if (!com_initializer
.succeeded())
273 if (!InitializeComSecurity(base::WideToUTF8(kComProcessSd
),
274 base::WideToUTF8(kComProcessMandatoryLabel
),
279 CreateLauncher(scoped_refptr
<AutoThreadTaskRunner
>(
280 new AutoThreadTaskRunner(main_task_runner_
,
281 run_loop
.QuitClosure())));
285 weak_factory_
.InvalidateWeakPtrs();
287 // Tell SCM that the service is stopped.
288 service_status
.dwCurrentState
= SERVICE_STOPPED
;
289 service_status
.dwControlsAccepted
= 0;
290 if (!SetServiceStatus(service_status_handle_
, &service_status
)) {
292 << "Failed to report service status to the service control manager";
297 int HostService::RunInConsole() {
298 base::MessageLoopForUI message_loop
;
299 base::RunLoop run_loop
;
300 main_task_runner_
= message_loop
.task_runner();
301 weak_ptr_
= weak_factory_
.GetWeakPtr();
303 int result
= kInitializationFailed
;
306 base::win::ScopedCOMInitializer com_initializer
;
307 if (!com_initializer
.succeeded())
310 if (!InitializeComSecurity(base::WideToUTF8(kComProcessSd
),
311 base::WideToUTF8(kComProcessMandatoryLabel
),
316 // Subscribe to Ctrl-C and other console events.
317 if (!SetConsoleCtrlHandler(&HostService::ConsoleControlHandler
, TRUE
)) {
318 PLOG(ERROR
) << "Failed to set console control handler";
322 // Create a window for receiving session change notifications.
323 base::win::MessageWindow window
;
324 if (!window
.Create(base::Bind(&HostService::HandleMessage
,
325 base::Unretained(this)))) {
326 PLOG(ERROR
) << "Failed to create the session notification window";
330 // Subscribe to session change notifications.
331 if (WTSRegisterSessionNotification(window
.hwnd(),
332 NOTIFY_FOR_ALL_SESSIONS
) != FALSE
) {
333 CreateLauncher(scoped_refptr
<AutoThreadTaskRunner
>(
334 new AutoThreadTaskRunner(main_task_runner_
,
335 run_loop
.QuitClosure())));
340 // Release the control handler.
341 stopped_event_
.Signal();
343 WTSUnRegisterSessionNotification(window
.hwnd());
344 result
= kSuccessExitCode
;
348 weak_factory_
.InvalidateWeakPtrs();
350 // Unsubscribe from console events. Ignore the exit code. There is nothing
351 // we can do about it now and the program is about to exit anyway. Even if
352 // it crashes nothing is going to be broken because of it.
353 SetConsoleCtrlHandler(&HostService::ConsoleControlHandler
, FALSE
);
358 void HostService::StopDaemonProcess() {
359 DCHECK(main_task_runner_
->BelongsToCurrentThread());
361 daemon_process_
.reset();
364 bool HostService::HandleMessage(
365 UINT message
, WPARAM wparam
, LPARAM lparam
, LRESULT
* result
) {
366 if (message
== WM_WTSSESSION_CHANGE
) {
367 OnSessionChange(wparam
, lparam
);
376 BOOL WINAPI
HostService::ConsoleControlHandler(DWORD event
) {
377 HostService
* self
= HostService::GetInstance();
380 case CTRL_BREAK_EVENT
:
381 case CTRL_CLOSE_EVENT
:
382 case CTRL_LOGOFF_EVENT
:
383 case CTRL_SHUTDOWN_EVENT
:
384 self
->main_task_runner_
->PostTask(
385 FROM_HERE
, base::Bind(&HostService::StopDaemonProcess
,
395 DWORD WINAPI
HostService::ServiceControlHandler(DWORD control
,
399 HostService
* self
= reinterpret_cast<HostService
*>(context
);
401 case SERVICE_CONTROL_INTERROGATE
:
404 case SERVICE_CONTROL_SHUTDOWN
:
405 case SERVICE_CONTROL_STOP
:
406 self
->main_task_runner_
->PostTask(
407 FROM_HERE
, base::Bind(&HostService::StopDaemonProcess
,
411 case SERVICE_CONTROL_SESSIONCHANGE
:
412 self
->main_task_runner_
->PostTask(FROM_HERE
, base::Bind(
413 &HostService::OnSessionChange
, self
->weak_ptr_
, event_type
,
414 reinterpret_cast<WTSSESSION_NOTIFICATION
*>(event_data
)->dwSessionId
));
418 return ERROR_CALL_NOT_IMPLEMENTED
;
423 VOID WINAPI
HostService::ServiceMain(DWORD argc
, WCHAR
* argv
[]) {
424 HostService
* self
= HostService::GetInstance();
427 self
->RunAsServiceImpl();
429 // Release the control handler and notify the main thread that it can exit
431 self
->stopped_event_
.Signal();
434 int DaemonProcessMain() {
435 HostService
* service
= HostService::GetInstance();
436 if (!service
->InitWithCommandLine(base::CommandLine::ForCurrentProcess())) {
437 return kUsageExitCode
;
440 return service
->Run();
443 } // namespace remoting