• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
GetInstance()64 HostService* HostService::GetInstance() {
65   return Singleton<HostService>::get();
66 }
67 
InitWithCommandLine(const base::CommandLine * command_line)68 bool HostService::InitWithCommandLine(const base::CommandLine* command_line) {
69   base::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 
Run()84 int HostService::Run() {
85   return (this->*run_routine_)();
86 }
87 
AddWtsTerminalObserver(const std::string & terminal_id,WtsTerminalObserver * observer)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 
RemoveWtsTerminalObserver(WtsTerminalObserver * observer)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 
HostService()138 HostService::HostService() :
139   run_routine_(&HostService::RunAsService),
140   service_status_handle_(0),
141   stopped_event_(true, false),
142   weak_factory_(this) {
143 }
144 
~HostService()145 HostService::~HostService() {
146 }
147 
OnSessionChange(uint32 event,uint32 session_id)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 
CreateLauncher(scoped_refptr<AutoThreadTaskRunner> task_runner)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 
RunAsService()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     PLOG(ERROR) << "Failed to connect to the service control manager";
228     return kInitializationFailed;
229   }
230 
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();
235 
236   return kSuccessExitCode;
237 }
238 
RunAsServiceImpl()239 void HostService::RunAsServiceImpl() {
240   base::MessageLoopForUI message_loop;
241   base::RunLoop run_loop;
242   main_task_runner_ = message_loop.message_loop_proxy();
243   weak_ptr_ = weak_factory_.GetWeakPtr();
244 
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";
250     return;
251   }
252 
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)) {
263     PLOG(ERROR)
264         << "Failed to report service status to the service control manager";
265     return;
266   }
267 
268   // Initialize COM.
269   base::win::ScopedCOMInitializer com_initializer;
270   if (!com_initializer.succeeded())
271     return;
272 
273   if (!InitializeComSecurity(base::WideToUTF8(kComProcessSd),
274                              base::WideToUTF8(kComProcessMandatoryLabel),
275                              false)) {
276     return;
277   }
278 
279   CreateLauncher(scoped_refptr<AutoThreadTaskRunner>(
280       new AutoThreadTaskRunner(main_task_runner_,
281                                run_loop.QuitClosure())));
282 
283   // Run the service.
284   run_loop.Run();
285   weak_factory_.InvalidateWeakPtrs();
286 
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)) {
291     PLOG(ERROR)
292         << "Failed to report service status to the service control manager";
293     return;
294   }
295 }
296 
RunInConsole()297 int HostService::RunInConsole() {
298   base::MessageLoopForUI message_loop;
299   base::RunLoop run_loop;
300   main_task_runner_ = message_loop.message_loop_proxy();
301   weak_ptr_ = weak_factory_.GetWeakPtr();
302 
303   int result = kInitializationFailed;
304 
305   // Initialize COM.
306   base::win::ScopedCOMInitializer com_initializer;
307   if (!com_initializer.succeeded())
308     return result;
309 
310   if (!InitializeComSecurity(base::WideToUTF8(kComProcessSd),
311                              base::WideToUTF8(kComProcessMandatoryLabel),
312                              false)) {
313     return result;
314   }
315 
316   // Subscribe to Ctrl-C and other console events.
317   if (!SetConsoleCtrlHandler(&HostService::ConsoleControlHandler, TRUE)) {
318     PLOG(ERROR) << "Failed to set console control handler";
319     return result;
320   }
321 
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";
327     goto cleanup;
328   }
329 
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())));
336 
337     // Run the service.
338     run_loop.Run();
339 
340     // Release the control handler.
341     stopped_event_.Signal();
342 
343     WTSUnRegisterSessionNotification(window.hwnd());
344     result = kSuccessExitCode;
345   }
346 
347 cleanup:
348   weak_factory_.InvalidateWeakPtrs();
349 
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);
354 
355   return result;
356 }
357 
StopDaemonProcess()358 void HostService::StopDaemonProcess() {
359   DCHECK(main_task_runner_->BelongsToCurrentThread());
360 
361   daemon_process_.reset();
362 }
363 
HandleMessage(UINT message,WPARAM wparam,LPARAM lparam,LRESULT * result)364 bool HostService::HandleMessage(
365     UINT message, WPARAM wparam, LPARAM lparam, LRESULT* result) {
366   if (message == WM_WTSSESSION_CHANGE) {
367     OnSessionChange(wparam, lparam);
368     *result = 0;
369     return true;
370   }
371 
372   return false;
373 }
374 
375 // static
ConsoleControlHandler(DWORD event)376 BOOL WINAPI HostService::ConsoleControlHandler(DWORD event) {
377   HostService* self = HostService::GetInstance();
378   switch (event) {
379     case CTRL_C_EVENT:
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,
386                                 self->weak_ptr_));
387       return TRUE;
388 
389     default:
390       return FALSE;
391   }
392 }
393 
394 // static
ServiceControlHandler(DWORD control,DWORD event_type,LPVOID event_data,LPVOID context)395 DWORD WINAPI HostService::ServiceControlHandler(DWORD control,
396                                                 DWORD event_type,
397                                                 LPVOID event_data,
398                                                 LPVOID context) {
399   HostService* self = reinterpret_cast<HostService*>(context);
400   switch (control) {
401     case SERVICE_CONTROL_INTERROGATE:
402       return NO_ERROR;
403 
404     case SERVICE_CONTROL_SHUTDOWN:
405     case SERVICE_CONTROL_STOP:
406       self->main_task_runner_->PostTask(
407           FROM_HERE, base::Bind(&HostService::StopDaemonProcess,
408                                 self->weak_ptr_));
409       return NO_ERROR;
410 
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));
415       return NO_ERROR;
416 
417     default:
418       return ERROR_CALL_NOT_IMPLEMENTED;
419   }
420 }
421 
422 // static
ServiceMain(DWORD argc,WCHAR * argv[])423 VOID WINAPI HostService::ServiceMain(DWORD argc, WCHAR* argv[]) {
424   HostService* self = HostService::GetInstance();
425 
426   // Run the service.
427   self->RunAsServiceImpl();
428 
429   // Release the control handler and notify the main thread that it can exit
430   // now.
431   self->stopped_event_.Signal();
432 }
433 
DaemonProcessMain()434 int DaemonProcessMain() {
435   HostService* service = HostService::GetInstance();
436   if (!service->InitWithCommandLine(base::CommandLine::ForCurrentProcess())) {
437     return kUsageExitCode;
438   }
439 
440   return service->Run();
441 }
442 
443 }  // namespace remoting
444