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 a standalone host process for Me2Me.
6
7 #include <string>
8
9 #include "base/at_exit.h"
10 #include "base/bind.h"
11 #include "base/callback.h"
12 #include "base/command_line.h"
13 #include "base/debug/alias.h"
14 #include "base/file_util.h"
15 #include "base/files/file_path.h"
16 #include "base/memory/scoped_ptr.h"
17 #include "base/message_loop/message_loop.h"
18 #include "base/single_thread_task_runner.h"
19 #include "base/strings/string_number_conversions.h"
20 #include "base/strings/string_util.h"
21 #include "base/strings/utf_string_conversions.h"
22 #include "base/synchronization/waitable_event.h"
23 #include "base/threading/thread.h"
24 #include "build/build_config.h"
25 #include "crypto/nss_util.h"
26 #include "ipc/ipc_channel.h"
27 #include "ipc/ipc_channel_proxy.h"
28 #include "ipc/ipc_listener.h"
29 #include "media/base/media.h"
30 #include "net/base/network_change_notifier.h"
31 #include "net/socket/client_socket_factory.h"
32 #include "net/socket/ssl_server_socket.h"
33 #include "net/url_request/url_fetcher.h"
34 #include "remoting/base/auto_thread_task_runner.h"
35 #include "remoting/base/breakpad.h"
36 #include "remoting/base/constants.h"
37 #include "remoting/base/logging.h"
38 #include "remoting/base/rsa_key_pair.h"
39 #include "remoting/host/branding.h"
40 #include "remoting/host/chromoting_host.h"
41 #include "remoting/host/chromoting_host_context.h"
42 #include "remoting/host/chromoting_messages.h"
43 #include "remoting/host/config_file_watcher.h"
44 #include "remoting/host/config_watcher.h"
45 #include "remoting/host/desktop_environment.h"
46 #include "remoting/host/desktop_session_connector.h"
47 #include "remoting/host/dns_blackhole_checker.h"
48 #include "remoting/host/heartbeat_sender.h"
49 #include "remoting/host/host_change_notification_listener.h"
50 #include "remoting/host/host_config.h"
51 #include "remoting/host/host_event_logger.h"
52 #include "remoting/host/host_exit_codes.h"
53 #include "remoting/host/host_main.h"
54 #include "remoting/host/host_status_sender.h"
55 #include "remoting/host/ipc_constants.h"
56 #include "remoting/host/ipc_desktop_environment.h"
57 #include "remoting/host/ipc_host_event_logger.h"
58 #include "remoting/host/json_host_config.h"
59 #include "remoting/host/log_to_server.h"
60 #include "remoting/host/logging.h"
61 #include "remoting/host/me2me_desktop_environment.h"
62 #include "remoting/host/pairing_registry_delegate.h"
63 #include "remoting/host/policy_hack/policy_watcher.h"
64 #include "remoting/host/service_urls.h"
65 #include "remoting/host/session_manager_factory.h"
66 #include "remoting/host/signaling_connector.h"
67 #include "remoting/host/token_validator_factory_impl.h"
68 #include "remoting/host/usage_stats_consent.h"
69 #include "remoting/host/username.h"
70 #include "remoting/jingle_glue/network_settings.h"
71 #include "remoting/jingle_glue/xmpp_signal_strategy.h"
72 #include "remoting/protocol/me2me_host_authenticator_factory.h"
73 #include "remoting/protocol/pairing_registry.h"
74
75 #if defined(OS_POSIX)
76 #include <signal.h>
77 #include <sys/types.h>
78 #include <unistd.h>
79 #include "base/file_descriptor_posix.h"
80 #include "remoting/host/pam_authorization_factory_posix.h"
81 #include "remoting/host/posix/signal_handler.h"
82 #endif // defined(OS_POSIX)
83
84 #if defined(OS_MACOSX)
85 #include "base/mac/scoped_cftyperef.h"
86 #endif // defined(OS_MACOSX)
87
88 #if defined(OS_LINUX)
89 #include "remoting/host/audio_capturer_linux.h"
90 #endif // defined(OS_LINUX)
91
92 #if defined(OS_WIN)
93 #include <commctrl.h>
94 #include "base/win/scoped_handle.h"
95 #include "remoting/host/win/session_desktop_environment.h"
96 #endif // defined(OS_WIN)
97
98 #if defined(TOOLKIT_GTK)
99 #include "ui/gfx/gtk_util.h"
100 #endif // defined(TOOLKIT_GTK)
101
102 // This is used for tagging system event logs.
103 const char kApplicationName[] = "chromoting";
104
105 #if defined(OS_LINUX)
106 // The command line switch used to pass name of the pipe to capture audio on
107 // linux.
108 const char kAudioPipeSwitchName[] = "audio-pipe-name";
109 #endif // defined(OS_LINUX)
110
111 // The command line switch used by the parent to request the host to signal it
112 // when it is successfully started.
113 const char kSignalParentSwitchName[] = "signal-parent";
114
115 // Value used for --host-config option to indicate that the path must be read
116 // from stdin.
117 const char kStdinConfigPath[] = "-";
118
119 namespace remoting {
120
121 class HostProcess
122 : public ConfigWatcher::Delegate,
123 public HeartbeatSender::Listener,
124 public HostChangeNotificationListener::Listener,
125 public IPC::Listener,
126 public base::RefCountedThreadSafe<HostProcess> {
127 public:
128 HostProcess(scoped_ptr<ChromotingHostContext> context,
129 int* exit_code_out);
130
131 // ConfigWatcher::Delegate interface.
132 virtual void OnConfigUpdated(const std::string& serialized_config) OVERRIDE;
133 virtual void OnConfigWatcherError() OVERRIDE;
134
135 // IPC::Listener implementation.
136 virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
137 virtual void OnChannelError() OVERRIDE;
138
139 // HeartbeatSender::Listener overrides.
140 virtual void OnHeartbeatSuccessful() OVERRIDE;
141 virtual void OnUnknownHostIdError() OVERRIDE;
142
143 // HostChangeNotificationListener::Listener overrides.
144 virtual void OnHostDeleted() OVERRIDE;
145
146 private:
147 enum HostState {
148 // Host process has just been started. Waiting for config and policies to be
149 // read from the disk.
150 HOST_INITIALIZING,
151
152 // Host is started and running.
153 HOST_STARTED,
154
155 // Host is being stopped and will need to be started again.
156 HOST_STOPPING_TO_RESTART,
157
158 // Host is being stopped.
159 HOST_STOPPING,
160
161 // Host has been stopped.
162 HOST_STOPPED,
163
164 // Allowed state transitions:
165 // INITIALIZING->STARTED
166 // INITIALIZING->STOPPED
167 // STARTED->STOPPING_TO_RESTART
168 // STARTED->STOPPING
169 // STOPPING_TO_RESTART->STARTED
170 // STOPPING_TO_RESTART->STOPPING
171 // STOPPING->STOPPED
172 // STOPPED->STARTED
173 //
174 // |host_| must be NULL in INITIALIZING and STOPPED states and not-NULL in
175 // all other states.
176 };
177
178 friend class base::RefCountedThreadSafe<HostProcess>;
179 virtual ~HostProcess();
180
181 void StartOnNetworkThread();
182
183 #if defined(OS_POSIX)
184 // Callback passed to RegisterSignalHandler() to handle SIGTERM events.
185 void SigTermHandler(int signal_number);
186 #endif
187
188 // Called to initialize resources on the UI thread.
189 void StartOnUiThread();
190
191 // Initializes IPC control channel and config file path from |cmd_line|.
192 // Called on the UI thread.
193 bool InitWithCommandLine(const CommandLine* cmd_line);
194
195 // Called on the UI thread to start monitoring the configuration file.
196 void StartWatchingConfigChanges();
197
198 // Called on the network thread to set the host's Authenticator factory.
199 void CreateAuthenticatorFactory();
200
201 // Tear down resources that run on the UI thread.
202 void ShutdownOnUiThread();
203
204 // Applies the host config, returning true if successful.
205 bool ApplyConfig(scoped_ptr<JsonHostConfig> config);
206
207 void OnPolicyUpdate(scoped_ptr<base::DictionaryValue> policies);
208 bool OnHostDomainPolicyUpdate(const std::string& host_domain);
209 bool OnUsernamePolicyUpdate(bool curtain_required,
210 bool username_match_required);
211 bool OnNatPolicyUpdate(bool nat_traversal_enabled);
212 void OnCurtainPolicyUpdate(bool curtain_required);
213 bool OnHostTalkGadgetPrefixPolicyUpdate(const std::string& talkgadget_prefix);
214 bool OnHostTokenUrlPolicyUpdate(const GURL& token_url,
215 const GURL& token_validation_url);
216 bool OnPairingPolicyUpdate(bool pairing_enabled);
217
218 void StartHost();
219
220 void OnAuthFailed();
221
222 void RestartHost();
223
224 // Stops the host and shuts down the process with the specified |exit_code|.
225 void ShutdownHost(HostExitCodes exit_code);
226
227 void ScheduleHostShutdown();
228
229 void ShutdownOnNetworkThread();
230
231 // Crashes the process in response to a daemon's request. The daemon passes
232 // the location of the code that detected the fatal error resulted in this
233 // request.
234 void OnCrash(const std::string& function_name,
235 const std::string& file_name,
236 const int& line_number);
237
238 scoped_ptr<ChromotingHostContext> context_;
239
240 // Created on the UI thread but used from the network thread.
241 scoped_ptr<net::NetworkChangeNotifier> network_change_notifier_;
242
243 // Accessed on the UI thread.
244 scoped_ptr<IPC::ChannelProxy> daemon_channel_;
245
246 // XMPP server/remoting bot configuration (initialized from the command line).
247 XmppSignalStrategy::XmppServerConfig xmpp_server_config_;
248 std::string directory_bot_jid_;
249
250 // Created on the UI thread but used from the network thread.
251 base::FilePath host_config_path_;
252 std::string host_config_;
253 scoped_ptr<DesktopEnvironmentFactory> desktop_environment_factory_;
254
255 // Accessed on the network thread.
256 HostState state_;
257
258 scoped_ptr<ConfigWatcher> config_watcher_;
259
260 std::string host_id_;
261 protocol::SharedSecretHash host_secret_hash_;
262 scoped_refptr<RsaKeyPair> key_pair_;
263 std::string oauth_refresh_token_;
264 std::string serialized_config_;
265 std::string host_owner_;
266 bool use_service_account_;
267 scoped_ptr<policy_hack::PolicyWatcher> policy_watcher_;
268 bool allow_nat_traversal_;
269 std::string talkgadget_prefix_;
270 bool allow_pairing_;
271
272 bool curtain_required_;
273 GURL token_url_;
274 GURL token_validation_url_;
275
276 scoped_ptr<XmppSignalStrategy> signal_strategy_;
277 scoped_ptr<SignalingConnector> signaling_connector_;
278 scoped_ptr<HeartbeatSender> heartbeat_sender_;
279 scoped_ptr<HostStatusSender> host_status_sender_;
280 scoped_ptr<HostChangeNotificationListener> host_change_notification_listener_;
281 scoped_ptr<LogToServer> log_to_server_;
282 scoped_ptr<HostEventLogger> host_event_logger_;
283
284 scoped_ptr<ChromotingHost> host_;
285
286 // Used to keep this HostProcess alive until it is shutdown.
287 scoped_refptr<HostProcess> self_;
288
289 #if defined(REMOTING_MULTI_PROCESS)
290 DesktopSessionConnector* desktop_session_connector_;
291 #endif // defined(REMOTING_MULTI_PROCESS)
292
293 int* exit_code_out_;
294 bool signal_parent_;
295 };
296
HostProcess(scoped_ptr<ChromotingHostContext> context,int * exit_code_out)297 HostProcess::HostProcess(scoped_ptr<ChromotingHostContext> context,
298 int* exit_code_out)
299 : context_(context.Pass()),
300 state_(HOST_INITIALIZING),
301 use_service_account_(false),
302 allow_nat_traversal_(true),
303 allow_pairing_(true),
304 curtain_required_(false),
305 #if defined(REMOTING_MULTI_PROCESS)
306 desktop_session_connector_(NULL),
307 #endif // defined(REMOTING_MULTI_PROCESS)
308 self_(this),
309 exit_code_out_(exit_code_out),
310 signal_parent_(false) {
311 StartOnUiThread();
312 }
313
~HostProcess()314 HostProcess::~HostProcess() {
315 // Verify that UI components have been torn down.
316 DCHECK(!config_watcher_);
317 DCHECK(!daemon_channel_);
318 DCHECK(!desktop_environment_factory_);
319
320 // We might be getting deleted on one of the threads the |host_context| owns,
321 // so we need to post it back to the caller thread to safely join & delete the
322 // threads it contains. This will go away when we move to AutoThread.
323 // |context_release()| will null |context_| before the method is invoked, so
324 // we need to pull out the task-runner on which to call DeleteSoon first.
325 scoped_refptr<base::SingleThreadTaskRunner> task_runner =
326 context_->ui_task_runner();
327 task_runner->DeleteSoon(FROM_HERE, context_.release());
328 }
329
InitWithCommandLine(const CommandLine * cmd_line)330 bool HostProcess::InitWithCommandLine(const CommandLine* cmd_line) {
331 #if defined(REMOTING_MULTI_PROCESS)
332 // Parse the handle value and convert it to a handle/file descriptor.
333 std::string channel_name =
334 cmd_line->GetSwitchValueASCII(kDaemonPipeSwitchName);
335
336 int pipe_handle = 0;
337 if (channel_name.empty() ||
338 !base::StringToInt(channel_name, &pipe_handle)) {
339 LOG(ERROR) << "Invalid '" << kDaemonPipeSwitchName
340 << "' value: " << channel_name;
341 return false;
342 }
343
344 #if defined(OS_WIN)
345 base::win::ScopedHandle pipe(reinterpret_cast<HANDLE>(pipe_handle));
346 IPC::ChannelHandle channel_handle(pipe);
347 #elif defined(OS_POSIX)
348 base::FileDescriptor pipe(pipe_handle, true);
349 IPC::ChannelHandle channel_handle(channel_name, pipe);
350 #endif // defined(OS_POSIX)
351
352 // Connect to the daemon process.
353 daemon_channel_.reset(new IPC::ChannelProxy(
354 channel_handle,
355 IPC::Channel::MODE_CLIENT,
356 this,
357 context_->network_task_runner()));
358 #else // !defined(REMOTING_MULTI_PROCESS)
359 // Connect to the daemon process.
360 std::string channel_name =
361 cmd_line->GetSwitchValueASCII(kDaemonPipeSwitchName);
362 if (!channel_name.empty()) {
363 daemon_channel_.reset(
364 new IPC::ChannelProxy(channel_name,
365 IPC::Channel::MODE_CLIENT,
366 this,
367 context_->network_task_runner().get()));
368 }
369
370 if (cmd_line->HasSwitch(kHostConfigSwitchName)) {
371 host_config_path_ = cmd_line->GetSwitchValuePath(kHostConfigSwitchName);
372
373 // Read config from stdin if necessary.
374 if (host_config_path_ == base::FilePath(kStdinConfigPath)) {
375 char buf[4096];
376 size_t len;
377 while ((len = fread(buf, 1, sizeof(buf), stdin)) > 0) {
378 host_config_.append(buf, len);
379 }
380 }
381 } else {
382 base::FilePath default_config_dir = remoting::GetConfigDir();
383 host_config_path_ = default_config_dir.Append(kDefaultHostConfigFile);
384 }
385
386 if (host_config_path_ != base::FilePath(kStdinConfigPath) &&
387 !base::PathExists(host_config_path_)) {
388 LOG(ERROR) << "Can't find host config at " << host_config_path_.value();
389 return false;
390 }
391 #endif // !defined(REMOTING_MULTI_PROCESS)
392
393 // Ignore certificate requests - the host currently has no client certificate
394 // support, so ignoring certificate requests allows connecting to servers that
395 // request, but don't require, a certificate (optional client authentication).
396 net::URLFetcher::SetIgnoreCertificateRequests(true);
397
398 ServiceUrls* service_urls = ServiceUrls::GetInstance();
399 bool xmpp_server_valid = net::ParseHostAndPort(
400 service_urls->xmpp_server_address(),
401 &xmpp_server_config_.host, &xmpp_server_config_.port);
402 if (!xmpp_server_valid) {
403 LOG(ERROR) << "Invalid XMPP server: " <<
404 service_urls->xmpp_server_address();
405 return false;
406 }
407 xmpp_server_config_.use_tls = service_urls->xmpp_server_use_tls();
408 directory_bot_jid_ = service_urls->directory_bot_jid();
409
410 signal_parent_ = cmd_line->HasSwitch(kSignalParentSwitchName);
411
412 return true;
413 }
414
OnConfigUpdated(const std::string & serialized_config)415 void HostProcess::OnConfigUpdated(
416 const std::string& serialized_config) {
417 if (!context_->network_task_runner()->BelongsToCurrentThread()) {
418 context_->network_task_runner()->PostTask(FROM_HERE,
419 base::Bind(&HostProcess::OnConfigUpdated, this, serialized_config));
420 return;
421 }
422
423 // Filter out duplicates.
424 if (serialized_config_ == serialized_config)
425 return;
426
427 HOST_LOG << "Processing new host configuration.";
428
429 serialized_config_ = serialized_config;
430 scoped_ptr<JsonHostConfig> config(new JsonHostConfig(base::FilePath()));
431 if (!config->SetSerializedData(serialized_config)) {
432 LOG(ERROR) << "Invalid configuration.";
433 ShutdownHost(kInvalidHostConfigurationExitCode);
434 return;
435 }
436
437 if (!ApplyConfig(config.Pass())) {
438 LOG(ERROR) << "Failed to apply the configuration.";
439 ShutdownHost(kInvalidHostConfigurationExitCode);
440 return;
441 }
442
443 if (state_ == HOST_INITIALIZING) {
444 // TODO(sergeyu): Currently OnPolicyUpdate() assumes that host config is
445 // already loaded so PolicyWatcher has to be started here. Separate policy
446 // loading from policy verifications and move |policy_watcher_|
447 // initialization to StartOnNetworkThread().
448 policy_watcher_.reset(
449 policy_hack::PolicyWatcher::Create(context_->file_task_runner()));
450 policy_watcher_->StartWatching(
451 base::Bind(&HostProcess::OnPolicyUpdate, base::Unretained(this)));
452 } else if (state_ == HOST_STARTED) {
453 // TODO(sergeyu): Here we assume that PIN is the only part of the config
454 // that may change while the service is running. Change ApplyConfig() to
455 // detect other changes in the config and restart host if necessary here.
456 CreateAuthenticatorFactory();
457 }
458 }
459
OnConfigWatcherError()460 void HostProcess::OnConfigWatcherError() {
461 DCHECK(context_->network_task_runner()->BelongsToCurrentThread());
462 ShutdownHost(kInvalidHostConfigurationExitCode);
463 }
464
StartOnNetworkThread()465 void HostProcess::StartOnNetworkThread() {
466 DCHECK(context_->network_task_runner()->BelongsToCurrentThread());
467
468 #if !defined(REMOTING_MULTI_PROCESS)
469 if (host_config_path_ == base::FilePath(kStdinConfigPath)) {
470 // Process config we've read from stdin.
471 OnConfigUpdated(host_config_);
472 } else {
473 // Start watching the host configuration file.
474 config_watcher_.reset(new ConfigFileWatcher(context_->network_task_runner(),
475 context_->file_task_runner(),
476 host_config_path_));
477 config_watcher_->Watch(this);
478 }
479 #endif // !defined(REMOTING_MULTI_PROCESS)
480
481 #if defined(OS_POSIX)
482 remoting::RegisterSignalHandler(
483 SIGTERM,
484 base::Bind(&HostProcess::SigTermHandler, base::Unretained(this)));
485 #endif // defined(OS_POSIX)
486 }
487
488 #if defined(OS_POSIX)
SigTermHandler(int signal_number)489 void HostProcess::SigTermHandler(int signal_number) {
490 DCHECK(signal_number == SIGTERM);
491 DCHECK(context_->network_task_runner()->BelongsToCurrentThread());
492 HOST_LOG << "Caught SIGTERM: Shutting down...";
493 ShutdownHost(kSuccessExitCode);
494 }
495 #endif // OS_POSIX
496
CreateAuthenticatorFactory()497 void HostProcess::CreateAuthenticatorFactory() {
498 DCHECK(context_->network_task_runner()->BelongsToCurrentThread());
499
500 if (state_ != HOST_STARTED)
501 return;
502
503 std::string local_certificate = key_pair_->GenerateCertificate();
504 if (local_certificate.empty()) {
505 LOG(ERROR) << "Failed to generate host certificate.";
506 ShutdownHost(kInitializationFailed);
507 return;
508 }
509
510 scoped_refptr<protocol::PairingRegistry> pairing_registry = NULL;
511 if (allow_pairing_) {
512 pairing_registry = CreatePairingRegistry(context_->file_task_runner());
513 }
514
515 scoped_ptr<protocol::AuthenticatorFactory> factory;
516
517 if (token_url_.is_empty() && token_validation_url_.is_empty()) {
518 factory = protocol::Me2MeHostAuthenticatorFactory::CreateWithSharedSecret(
519 use_service_account_, host_owner_, local_certificate, key_pair_,
520 host_secret_hash_, pairing_registry);
521
522 } else if (token_url_.is_valid() && token_validation_url_.is_valid()) {
523 scoped_ptr<protocol::ThirdPartyHostAuthenticator::TokenValidatorFactory>
524 token_validator_factory(new TokenValidatorFactoryImpl(
525 token_url_, token_validation_url_, key_pair_,
526 context_->url_request_context_getter()));
527 factory = protocol::Me2MeHostAuthenticatorFactory::CreateWithThirdPartyAuth(
528 use_service_account_, host_owner_, local_certificate, key_pair_,
529 token_validator_factory.Pass());
530
531 } else {
532 // TODO(rmsousa): If the policy is bad the host should not go online. It
533 // should keep running, but not connected, until the policies are fixed.
534 // Having it show up as online and then reject all clients is misleading.
535 LOG(ERROR) << "One of the third-party token URLs is empty or invalid. "
536 << "Host will reject all clients until policies are corrected. "
537 << "TokenUrl: " << token_url_ << ", "
538 << "TokenValidationUrl: " << token_validation_url_;
539 factory = protocol::Me2MeHostAuthenticatorFactory::CreateRejecting();
540 }
541
542 #if defined(OS_POSIX)
543 // On Linux and Mac, perform a PAM authorization step after authentication.
544 factory.reset(new PamAuthorizationFactory(factory.Pass()));
545 #endif
546 host_->SetAuthenticatorFactory(factory.Pass());
547
548 host_->set_pairing_registry(pairing_registry);
549 }
550
551 // IPC::Listener implementation.
OnMessageReceived(const IPC::Message & message)552 bool HostProcess::OnMessageReceived(const IPC::Message& message) {
553 DCHECK(context_->ui_task_runner()->BelongsToCurrentThread());
554
555 #if defined(REMOTING_MULTI_PROCESS)
556 bool handled = true;
557 IPC_BEGIN_MESSAGE_MAP(HostProcess, message)
558 IPC_MESSAGE_HANDLER(ChromotingDaemonMsg_Crash, OnCrash)
559 IPC_MESSAGE_HANDLER(ChromotingDaemonNetworkMsg_Configuration,
560 OnConfigUpdated)
561 IPC_MESSAGE_FORWARD(
562 ChromotingDaemonNetworkMsg_DesktopAttached,
563 desktop_session_connector_,
564 DesktopSessionConnector::OnDesktopSessionAgentAttached)
565 IPC_MESSAGE_FORWARD(ChromotingDaemonNetworkMsg_TerminalDisconnected,
566 desktop_session_connector_,
567 DesktopSessionConnector::OnTerminalDisconnected)
568 IPC_MESSAGE_UNHANDLED(handled = false)
569 IPC_END_MESSAGE_MAP()
570
571 CHECK(handled) << "Received unexpected IPC type: " << message.type();
572 return handled;
573
574 #else // !defined(REMOTING_MULTI_PROCESS)
575 return false;
576 #endif // !defined(REMOTING_MULTI_PROCESS)
577 }
578
OnChannelError()579 void HostProcess::OnChannelError() {
580 DCHECK(context_->ui_task_runner()->BelongsToCurrentThread());
581
582 // Shutdown the host if the daemon process disconnects the IPC channel.
583 context_->network_task_runner()->PostTask(
584 FROM_HERE,
585 base::Bind(&HostProcess::ShutdownHost, this, kSuccessExitCode));
586 }
587
StartOnUiThread()588 void HostProcess::StartOnUiThread() {
589 DCHECK(context_->ui_task_runner()->BelongsToCurrentThread());
590
591 if (!InitWithCommandLine(CommandLine::ForCurrentProcess())) {
592 // Shutdown the host if the command line is invalid.
593 context_->network_task_runner()->PostTask(
594 FROM_HERE, base::Bind(&HostProcess::ShutdownHost, this,
595 kUsageExitCode));
596 return;
597 }
598
599 #if defined(OS_LINUX)
600 // If an audio pipe is specific on the command-line then initialize
601 // AudioCapturerLinux to capture from it.
602 base::FilePath audio_pipe_name = CommandLine::ForCurrentProcess()->
603 GetSwitchValuePath(kAudioPipeSwitchName);
604 if (!audio_pipe_name.empty()) {
605 remoting::AudioCapturerLinux::InitializePipeReader(
606 context_->audio_task_runner(), audio_pipe_name);
607 }
608 #endif // defined(OS_LINUX)
609
610 // Create a desktop environment factory appropriate to the build type &
611 // platform.
612 #if defined(OS_WIN)
613 IpcDesktopEnvironmentFactory* desktop_environment_factory =
614 new IpcDesktopEnvironmentFactory(
615 context_->audio_task_runner(),
616 context_->network_task_runner(),
617 context_->video_capture_task_runner(),
618 context_->network_task_runner(),
619 daemon_channel_.get());
620 desktop_session_connector_ = desktop_environment_factory;
621 #else // !defined(OS_WIN)
622 DesktopEnvironmentFactory* desktop_environment_factory =
623 new Me2MeDesktopEnvironmentFactory(
624 context_->network_task_runner(),
625 context_->input_task_runner(),
626 context_->ui_task_runner());
627 #endif // !defined(OS_WIN)
628
629 desktop_environment_factory_.reset(desktop_environment_factory);
630
631 context_->network_task_runner()->PostTask(
632 FROM_HERE,
633 base::Bind(&HostProcess::StartOnNetworkThread, this));
634 }
635
ShutdownOnUiThread()636 void HostProcess::ShutdownOnUiThread() {
637 DCHECK(context_->ui_task_runner()->BelongsToCurrentThread());
638
639 // Tear down resources that need to be torn down on the UI thread.
640 network_change_notifier_.reset();
641 daemon_channel_.reset();
642 desktop_environment_factory_.reset();
643
644 // It is now safe for the HostProcess to be deleted.
645 self_ = NULL;
646
647 #if defined(OS_LINUX)
648 // Cause the global AudioPipeReader to be freed, otherwise the audio
649 // thread will remain in-use and prevent the process from exiting.
650 // TODO(wez): DesktopEnvironmentFactory should own the pipe reader.
651 // See crbug.com/161373 and crbug.com/104544.
652 AudioCapturerLinux::InitializePipeReader(NULL, base::FilePath());
653 #endif
654 }
655
656 // Overridden from HeartbeatSender::Listener
OnUnknownHostIdError()657 void HostProcess::OnUnknownHostIdError() {
658 LOG(ERROR) << "Host ID not found.";
659 ShutdownHost(kInvalidHostIdExitCode);
660 }
661
OnHeartbeatSuccessful()662 void HostProcess::OnHeartbeatSuccessful() {
663 HOST_LOG << "Host ready to receive connections.";
664 #if defined(OS_POSIX)
665 if (signal_parent_) {
666 kill(getppid(), SIGUSR1);
667 signal_parent_ = false;
668 }
669 #endif
670 }
671
OnHostDeleted()672 void HostProcess::OnHostDeleted() {
673 LOG(ERROR) << "Host was deleted from the directory.";
674 ShutdownHost(kInvalidHostIdExitCode);
675 }
676
677 // Applies the host config, returning true if successful.
ApplyConfig(scoped_ptr<JsonHostConfig> config)678 bool HostProcess::ApplyConfig(scoped_ptr<JsonHostConfig> config) {
679 DCHECK(context_->network_task_runner()->BelongsToCurrentThread());
680
681 if (!config->GetString(kHostIdConfigPath, &host_id_)) {
682 LOG(ERROR) << "host_id is not defined in the config.";
683 return false;
684 }
685
686 std::string key_base64;
687 if (!config->GetString(kPrivateKeyConfigPath, &key_base64)) {
688 LOG(ERROR) << "Private key couldn't be read from the config file.";
689 return false;
690 }
691
692 key_pair_ = RsaKeyPair::FromString(key_base64);
693 if (!key_pair_.get()) {
694 LOG(ERROR) << "Invalid private key in the config file.";
695 return false;
696 }
697
698 std::string host_secret_hash_string;
699 if (!config->GetString(kHostSecretHashConfigPath,
700 &host_secret_hash_string)) {
701 host_secret_hash_string = "plain:";
702 }
703
704 if (!host_secret_hash_.Parse(host_secret_hash_string)) {
705 LOG(ERROR) << "Invalid host_secret_hash.";
706 return false;
707 }
708
709 // Use an XMPP connection to the Talk network for session signalling.
710 if (!config->GetString(kXmppLoginConfigPath, &xmpp_server_config_.username) ||
711 !(config->GetString(kXmppAuthTokenConfigPath,
712 &xmpp_server_config_.auth_token) ||
713 config->GetString(kOAuthRefreshTokenConfigPath,
714 &oauth_refresh_token_))) {
715 LOG(ERROR) << "XMPP credentials are not defined in the config.";
716 return false;
717 }
718
719 if (!oauth_refresh_token_.empty()) {
720 // SignalingConnector is responsible for getting OAuth token.
721 xmpp_server_config_.auth_token = "";
722 xmpp_server_config_.auth_service = "oauth2";
723 } else if (!config->GetString(kXmppAuthServiceConfigPath,
724 &xmpp_server_config_.auth_service)) {
725 // For the me2me host, we default to ClientLogin token for chromiumsync
726 // because earlier versions of the host had no HTTP stack with which to
727 // request an OAuth2 access token.
728 xmpp_server_config_.auth_service = kChromotingTokenDefaultServiceName;
729 }
730
731 if (config->GetString(kHostOwnerConfigPath, &host_owner_)) {
732 // Service account configs have a host_owner, different from the xmpp_login.
733 use_service_account_ = true;
734 } else {
735 // User credential configs only have an xmpp_login, which is also the owner.
736 host_owner_ = xmpp_server_config_.username;
737 use_service_account_ = false;
738 }
739 return true;
740 }
741
OnPolicyUpdate(scoped_ptr<base::DictionaryValue> policies)742 void HostProcess::OnPolicyUpdate(scoped_ptr<base::DictionaryValue> policies) {
743 // TODO(rmsousa): Consolidate all On*PolicyUpdate methods into this one.
744 // TODO(sergeyu): Currently polices are verified only when they are loaded.
745 // Separate policy loading from policy verifications - this will allow to
746 // check policies again later, e.g. when host config changes.
747
748 if (!context_->network_task_runner()->BelongsToCurrentThread()) {
749 context_->network_task_runner()->PostTask(FROM_HERE, base::Bind(
750 &HostProcess::OnPolicyUpdate, this, base::Passed(&policies)));
751 return;
752 }
753
754 bool restart_required = false;
755 bool bool_value;
756 std::string string_value;
757 if (policies->GetString(policy_hack::PolicyWatcher::kHostDomainPolicyName,
758 &string_value)) {
759 restart_required |= OnHostDomainPolicyUpdate(string_value);
760 }
761 bool curtain_required = false;
762 if (policies->GetBoolean(
763 policy_hack::PolicyWatcher::kHostRequireCurtainPolicyName,
764 &curtain_required)) {
765 OnCurtainPolicyUpdate(curtain_required);
766 }
767 if (policies->GetBoolean(
768 policy_hack::PolicyWatcher::kHostMatchUsernamePolicyName,
769 &bool_value)) {
770 restart_required |= OnUsernamePolicyUpdate(curtain_required, bool_value);
771 }
772 if (policies->GetBoolean(policy_hack::PolicyWatcher::kNatPolicyName,
773 &bool_value)) {
774 restart_required |= OnNatPolicyUpdate(bool_value);
775 }
776 if (policies->GetString(
777 policy_hack::PolicyWatcher::kHostTalkGadgetPrefixPolicyName,
778 &string_value)) {
779 restart_required |= OnHostTalkGadgetPrefixPolicyUpdate(string_value);
780 }
781 std::string token_url_string, token_validation_url_string;
782 if (policies->GetString(
783 policy_hack::PolicyWatcher::kHostTokenUrlPolicyName,
784 &token_url_string) &&
785 policies->GetString(
786 policy_hack::PolicyWatcher::kHostTokenValidationUrlPolicyName,
787 &token_validation_url_string)) {
788 restart_required |= OnHostTokenUrlPolicyUpdate(
789 GURL(token_url_string), GURL(token_validation_url_string));
790 }
791 if (policies->GetBoolean(
792 policy_hack::PolicyWatcher::kHostAllowClientPairing,
793 &bool_value)) {
794 restart_required |= OnPairingPolicyUpdate(bool_value);
795 }
796
797 if (state_ == HOST_INITIALIZING) {
798 StartHost();
799 } else if (state_ == HOST_STARTED && restart_required) {
800 RestartHost();
801 }
802 }
803
OnHostDomainPolicyUpdate(const std::string & host_domain)804 bool HostProcess::OnHostDomainPolicyUpdate(const std::string& host_domain) {
805 // Returns true if the host has to be restarted after this policy update.
806 DCHECK(context_->network_task_runner()->BelongsToCurrentThread());
807
808 HOST_LOG << "Policy sets host domain: " << host_domain;
809
810 if (!host_domain.empty() &&
811 !EndsWith(host_owner_, std::string("@") + host_domain, false)) {
812 ShutdownHost(kInvalidHostDomainExitCode);
813 }
814 return false;
815 }
816
OnUsernamePolicyUpdate(bool curtain_required,bool host_username_match_required)817 bool HostProcess::OnUsernamePolicyUpdate(bool curtain_required,
818 bool host_username_match_required) {
819 // Returns false: never restart the host after this policy update.
820 DCHECK(context_->network_task_runner()->BelongsToCurrentThread());
821
822 if (host_username_match_required) {
823 HOST_LOG << "Policy requires host username match.";
824 std::string username = GetUsername();
825 bool shutdown = username.empty() ||
826 !StartsWithASCII(host_owner_, username + std::string("@"),
827 false);
828
829 #if defined(OS_MACOSX)
830 // On Mac, we run as root at the login screen, so the username won't match.
831 // However, there's no need to enforce the policy at the login screen, as
832 // the client will have to reconnect if a login occurs.
833 if (shutdown && getuid() == 0) {
834 shutdown = false;
835 }
836 #endif
837
838 // Curtain-mode on Windows presents the standard OS login prompt to the user
839 // for each connection, removing the need for an explicit user-name matching
840 // check.
841 #if defined(OS_WIN) && defined(REMOTING_RDP_SESSION)
842 if (curtain_required)
843 return false;
844 #endif // defined(OS_WIN) && defined(REMOTING_RDP_SESSION)
845
846 // Shutdown the host if the username does not match.
847 if (shutdown) {
848 LOG(ERROR) << "The host username does not match.";
849 ShutdownHost(kUsernameMismatchExitCode);
850 }
851 } else {
852 HOST_LOG << "Policy does not require host username match.";
853 }
854
855 return false;
856 }
857
OnNatPolicyUpdate(bool nat_traversal_enabled)858 bool HostProcess::OnNatPolicyUpdate(bool nat_traversal_enabled) {
859 // Returns true if the host has to be restarted after this policy update.
860 DCHECK(context_->network_task_runner()->BelongsToCurrentThread());
861
862 if (allow_nat_traversal_ != nat_traversal_enabled) {
863 if (nat_traversal_enabled)
864 HOST_LOG << "Policy enables NAT traversal.";
865 else
866 HOST_LOG << "Policy disables NAT traversal.";
867 allow_nat_traversal_ = nat_traversal_enabled;
868 return true;
869 }
870 return false;
871 }
872
OnCurtainPolicyUpdate(bool curtain_required)873 void HostProcess::OnCurtainPolicyUpdate(bool curtain_required) {
874 // Returns true if the host has to be restarted after this policy update.
875 DCHECK(context_->network_task_runner()->BelongsToCurrentThread());
876
877 #if defined(OS_MACOSX)
878 if (curtain_required) {
879 // When curtain mode is in effect on Mac, the host process runs in the
880 // user's switched-out session, but launchd will also run an instance at
881 // the console login screen. Even if no user is currently logged-on, we
882 // can't support remote-access to the login screen because the current host
883 // process model disconnects the client during login, which would leave
884 // the logged in session un-curtained on the console until they reconnect.
885 //
886 // TODO(jamiewalch): Fix this once we have implemented the multi-process
887 // daemon architecture (crbug.com/134894)
888 if (getuid() == 0) {
889 LOG(ERROR) << "Running the host in the console login session is yet not "
890 "supported.";
891 ShutdownHost(kLoginScreenNotSupportedExitCode);
892 return;
893 }
894 }
895 #endif
896
897 if (curtain_required_ != curtain_required) {
898 if (curtain_required)
899 HOST_LOG << "Policy requires curtain-mode.";
900 else
901 HOST_LOG << "Policy does not require curtain-mode.";
902 curtain_required_ = curtain_required;
903 if (host_)
904 host_->SetEnableCurtaining(curtain_required_);
905 }
906 }
907
OnHostTalkGadgetPrefixPolicyUpdate(const std::string & talkgadget_prefix)908 bool HostProcess::OnHostTalkGadgetPrefixPolicyUpdate(
909 const std::string& talkgadget_prefix) {
910 // Returns true if the host has to be restarted after this policy update.
911 DCHECK(context_->network_task_runner()->BelongsToCurrentThread());
912
913 if (talkgadget_prefix != talkgadget_prefix_) {
914 HOST_LOG << "Policy sets talkgadget prefix: " << talkgadget_prefix;
915 talkgadget_prefix_ = talkgadget_prefix;
916 return true;
917 }
918 return false;
919 }
920
OnHostTokenUrlPolicyUpdate(const GURL & token_url,const GURL & token_validation_url)921 bool HostProcess::OnHostTokenUrlPolicyUpdate(
922 const GURL& token_url,
923 const GURL& token_validation_url) {
924 // Returns true if the host has to be restarted after this policy update.
925 DCHECK(context_->network_task_runner()->BelongsToCurrentThread());
926
927 if (token_url_ != token_url ||
928 token_validation_url_ != token_validation_url) {
929 HOST_LOG << "Policy sets third-party token URLs: "
930 << "TokenUrl: " << token_url << ", "
931 << "TokenValidationUrl: " << token_validation_url;
932
933 token_url_ = token_url;
934 token_validation_url_ = token_validation_url;
935 return true;
936 }
937
938 return false;
939 }
940
OnPairingPolicyUpdate(bool allow_pairing)941 bool HostProcess::OnPairingPolicyUpdate(bool allow_pairing) {
942 DCHECK(context_->network_task_runner()->BelongsToCurrentThread());
943
944 if (allow_pairing_ == allow_pairing)
945 return false;
946
947 if (allow_pairing)
948 HOST_LOG << "Policy enables client pairing.";
949 else
950 HOST_LOG << "Policy disables client pairing.";
951 allow_pairing_ = allow_pairing;
952 return true;
953 }
954
StartHost()955 void HostProcess::StartHost() {
956 DCHECK(context_->network_task_runner()->BelongsToCurrentThread());
957 DCHECK(!host_);
958 DCHECK(!signal_strategy_.get());
959 DCHECK(state_ == HOST_INITIALIZING || state_ == HOST_STOPPING_TO_RESTART ||
960 state_ == HOST_STOPPED) << state_;
961 state_ = HOST_STARTED;
962
963 signal_strategy_.reset(
964 new XmppSignalStrategy(net::ClientSocketFactory::GetDefaultFactory(),
965 context_->url_request_context_getter(),
966 xmpp_server_config_));
967
968 scoped_ptr<DnsBlackholeChecker> dns_blackhole_checker(
969 new DnsBlackholeChecker(context_->url_request_context_getter(),
970 talkgadget_prefix_));
971
972 // Create a NetworkChangeNotifier for use by the signaling connector.
973 network_change_notifier_.reset(net::NetworkChangeNotifier::Create());
974
975 signaling_connector_.reset(new SignalingConnector(
976 signal_strategy_.get(),
977 context_->url_request_context_getter(),
978 dns_blackhole_checker.Pass(),
979 base::Bind(&HostProcess::OnAuthFailed, this)));
980
981 if (!oauth_refresh_token_.empty()) {
982 scoped_ptr<SignalingConnector::OAuthCredentials> oauth_credentials(
983 new SignalingConnector::OAuthCredentials(
984 xmpp_server_config_.username, oauth_refresh_token_,
985 use_service_account_));
986 signaling_connector_->EnableOAuth(oauth_credentials.Pass());
987 }
988
989 NetworkSettings network_settings(
990 allow_nat_traversal_ ?
991 NetworkSettings::NAT_TRAVERSAL_ENABLED :
992 NetworkSettings::NAT_TRAVERSAL_DISABLED);
993 if (!allow_nat_traversal_) {
994 network_settings.min_port = NetworkSettings::kDefaultMinPort;
995 network_settings.max_port = NetworkSettings::kDefaultMaxPort;
996 }
997
998 host_.reset(new ChromotingHost(
999 signal_strategy_.get(),
1000 desktop_environment_factory_.get(),
1001 CreateHostSessionManager(signal_strategy_.get(), network_settings,
1002 context_->url_request_context_getter()),
1003 context_->audio_task_runner(),
1004 context_->input_task_runner(),
1005 context_->video_capture_task_runner(),
1006 context_->video_encode_task_runner(),
1007 context_->network_task_runner(),
1008 context_->ui_task_runner()));
1009
1010 // TODO(simonmorris): Get the maximum session duration from a policy.
1011 #if defined(OS_LINUX)
1012 host_->SetMaximumSessionDuration(base::TimeDelta::FromHours(20));
1013 #endif
1014
1015 heartbeat_sender_.reset(new HeartbeatSender(
1016 this, host_id_, signal_strategy_.get(), key_pair_,
1017 directory_bot_jid_));
1018
1019 host_status_sender_.reset(new HostStatusSender(
1020 host_id_, signal_strategy_.get(), key_pair_, directory_bot_jid_));
1021
1022 host_change_notification_listener_.reset(new HostChangeNotificationListener(
1023 this, host_id_, signal_strategy_.get(), directory_bot_jid_));
1024
1025 log_to_server_.reset(
1026 new LogToServer(host_->AsWeakPtr(), ServerLogEntry::ME2ME,
1027 signal_strategy_.get(), directory_bot_jid_));
1028
1029 // Set up repoting the host status notifications.
1030 #if defined(REMOTING_MULTI_PROCESS)
1031 host_event_logger_.reset(
1032 new IpcHostEventLogger(host_->AsWeakPtr(), daemon_channel_.get()));
1033 #else // !defined(REMOTING_MULTI_PROCESS)
1034 host_event_logger_ =
1035 HostEventLogger::Create(host_->AsWeakPtr(), kApplicationName);
1036 #endif // !defined(REMOTING_MULTI_PROCESS)
1037
1038 host_->SetEnableCurtaining(curtain_required_);
1039 host_->Start(host_owner_);
1040
1041 CreateAuthenticatorFactory();
1042 }
1043
OnAuthFailed()1044 void HostProcess::OnAuthFailed() {
1045 ShutdownHost(kInvalidOauthCredentialsExitCode);
1046 }
1047
RestartHost()1048 void HostProcess::RestartHost() {
1049 DCHECK(context_->network_task_runner()->BelongsToCurrentThread());
1050 DCHECK_EQ(state_, HOST_STARTED);
1051
1052 state_ = HOST_STOPPING_TO_RESTART;
1053 ShutdownOnNetworkThread();
1054 }
1055
ShutdownHost(HostExitCodes exit_code)1056 void HostProcess::ShutdownHost(HostExitCodes exit_code) {
1057 DCHECK(context_->network_task_runner()->BelongsToCurrentThread());
1058
1059 *exit_code_out_ = exit_code;
1060
1061 switch (state_) {
1062 case HOST_INITIALIZING:
1063 state_ = HOST_STOPPING;
1064 ShutdownOnNetworkThread();
1065 break;
1066
1067 case HOST_STARTED:
1068 state_ = HOST_STOPPING;
1069 host_status_sender_->SendOfflineStatus(exit_code);
1070 ScheduleHostShutdown();
1071 break;
1072
1073 case HOST_STOPPING_TO_RESTART:
1074 state_ = HOST_STOPPING;
1075 break;
1076
1077 case HOST_STOPPING:
1078 case HOST_STOPPED:
1079 // Host is already stopped or being stopped. No action is required.
1080 break;
1081 }
1082 }
1083
1084 // TODO(weitaosu): shut down the host once we get an ACK for the offline status
1085 // XMPP message.
ScheduleHostShutdown()1086 void HostProcess::ScheduleHostShutdown() {
1087 // Delay the shutdown by 2 second to allow SendOfflineStatus to complete.
1088 context_->network_task_runner()->PostDelayedTask(
1089 FROM_HERE,
1090 base::Bind(&HostProcess::ShutdownOnNetworkThread, base::Unretained(this)),
1091 base::TimeDelta::FromSeconds(2));
1092 }
1093
ShutdownOnNetworkThread()1094 void HostProcess::ShutdownOnNetworkThread() {
1095 DCHECK(context_->network_task_runner()->BelongsToCurrentThread());
1096
1097 host_.reset();
1098 host_event_logger_.reset();
1099 log_to_server_.reset();
1100 heartbeat_sender_.reset();
1101 host_status_sender_.reset();
1102 host_change_notification_listener_.reset();
1103 signaling_connector_.reset();
1104 signal_strategy_.reset();
1105 network_change_notifier_.reset();
1106
1107 if (state_ == HOST_STOPPING_TO_RESTART) {
1108 StartHost();
1109 } else if (state_ == HOST_STOPPING) {
1110 state_ = HOST_STOPPED;
1111
1112 if (policy_watcher_.get()) {
1113 base::WaitableEvent done_event(true, false);
1114 policy_watcher_->StopWatching(&done_event);
1115 done_event.Wait();
1116 policy_watcher_.reset();
1117 }
1118
1119 config_watcher_.reset();
1120
1121 // Complete the rest of shutdown on the main thread.
1122 context_->ui_task_runner()->PostTask(
1123 FROM_HERE,
1124 base::Bind(&HostProcess::ShutdownOnUiThread, this));
1125 } else {
1126 // This method is only called in STOPPING_TO_RESTART and STOPPING states.
1127 NOTREACHED();
1128 }
1129 }
1130
OnCrash(const std::string & function_name,const std::string & file_name,const int & line_number)1131 void HostProcess::OnCrash(const std::string& function_name,
1132 const std::string& file_name,
1133 const int& line_number) {
1134 char message[1024];
1135 base::snprintf(message, sizeof(message),
1136 "Requested by %s at %s, line %d.",
1137 function_name.c_str(), file_name.c_str(), line_number);
1138 base::debug::Alias(message);
1139
1140 // The daemon requested us to crash the process.
1141 CHECK(false) << message;
1142 }
1143
HostProcessMain()1144 int HostProcessMain() {
1145 #if defined(TOOLKIT_GTK)
1146 // Required for any calls into GTK functions, such as the Disconnect and
1147 // Continue windows, though these should not be used for the Me2Me case
1148 // (crbug.com/104377).
1149 gfx::GtkInitFromCommandLine(*CommandLine::ForCurrentProcess());
1150 #endif // TOOLKIT_GTK
1151
1152 // Enable support for SSL server sockets, which must be done while still
1153 // single-threaded.
1154 net::EnableSSLServerSockets();
1155
1156 // Ensures runtime specific CPU features are initialized.
1157 media::InitializeCPUSpecificMediaFeatures();
1158
1159 // Create the main message loop and start helper threads.
1160 base::MessageLoop message_loop(base::MessageLoop::TYPE_UI);
1161 scoped_ptr<ChromotingHostContext> context =
1162 ChromotingHostContext::Create(new AutoThreadTaskRunner(
1163 message_loop.message_loop_proxy(), base::MessageLoop::QuitClosure()));
1164 if (!context)
1165 return kInitializationFailed;
1166
1167 // Create & start the HostProcess using these threads.
1168 // TODO(wez): The HostProcess holds a reference to itself until Shutdown().
1169 // Remove this hack as part of the multi-process refactoring.
1170 int exit_code = kSuccessExitCode;
1171 new HostProcess(context.Pass(), &exit_code);
1172
1173 // Run the main (also UI) message loop until the host no longer needs it.
1174 message_loop.Run();
1175
1176 return exit_code;
1177 }
1178
1179 } // namespace remoting
1180
1181 #if !defined(OS_WIN)
main(int argc,char ** argv)1182 int main(int argc, char** argv) {
1183 return remoting::HostMain(argc, argv);
1184 }
1185 #endif // !defined(OS_WIN)
1186