OLD | NEW |
| (Empty) |
1 // Copyright 2013 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 #include "remoting/host/it2me/it2me_impl.h" | |
6 | |
7 #include "base/bind.h" | |
8 #include "base/strings/string_util.h" | |
9 #include "base/synchronization/waitable_event.h" | |
10 #include "base/threading/platform_thread.h" | |
11 #include "net/socket/client_socket_factory.h" | |
12 #include "remoting/base/auto_thread.h" | |
13 #include "remoting/base/rsa_key_pair.h" | |
14 #include "remoting/host/chromoting_host.h" | |
15 #include "remoting/host/chromoting_host_context.h" | |
16 #include "remoting/host/host_event_logger.h" | |
17 #include "remoting/host/host_secret.h" | |
18 #include "remoting/host/it2me_desktop_environment.h" | |
19 #include "remoting/host/policy_hack/policy_watcher.h" | |
20 #include "remoting/host/register_support_host_request.h" | |
21 #include "remoting/host/session_manager_factory.h" | |
22 #include "remoting/jingle_glue/network_settings.h" | |
23 #include "remoting/protocol/it2me_host_authenticator_factory.h" | |
24 | |
25 namespace remoting { | |
26 | |
27 namespace { | |
28 | |
29 // This is used for tagging system event logs. | |
30 const char kApplicationName[] = "chromoting"; | |
31 const int kMaxLoginAttempts = 5; | |
32 | |
33 } // namespace | |
34 | |
35 It2MeImpl::It2MeImpl( | |
36 scoped_ptr<ChromotingHostContext> host_context, | |
37 scoped_refptr<base::SingleThreadTaskRunner> plugin_task_runner, | |
38 base::WeakPtr<It2MeImpl::Observer> observer, | |
39 const XmppSignalStrategy::XmppServerConfig& xmpp_server_config, | |
40 const std::string& directory_bot_jid) | |
41 : host_context_(host_context.Pass()), | |
42 plugin_task_runner_(plugin_task_runner), | |
43 observer_(observer), | |
44 xmpp_server_config_(xmpp_server_config), | |
45 directory_bot_jid_(directory_bot_jid), | |
46 state_(kDisconnected), | |
47 failed_login_attempts_(0), | |
48 nat_traversal_enabled_(false), | |
49 policy_received_(false) { | |
50 DCHECK(plugin_task_runner_->BelongsToCurrentThread()); | |
51 } | |
52 | |
53 void It2MeImpl::Connect() { | |
54 if (!host_context_->ui_task_runner()->BelongsToCurrentThread()) { | |
55 DCHECK(plugin_task_runner_->BelongsToCurrentThread()); | |
56 host_context_->ui_task_runner()->PostTask( | |
57 FROM_HERE, base::Bind(&It2MeImpl::Connect, this)); | |
58 return; | |
59 } | |
60 | |
61 desktop_environment_factory_.reset(new It2MeDesktopEnvironmentFactory( | |
62 host_context_->network_task_runner(), | |
63 host_context_->input_task_runner(), | |
64 host_context_->ui_task_runner())); | |
65 | |
66 // Start monitoring configured policies. | |
67 policy_watcher_.reset( | |
68 policy_hack::PolicyWatcher::Create(host_context_->network_task_runner())); | |
69 policy_watcher_->StartWatching( | |
70 base::Bind(&It2MeImpl::OnPolicyUpdate, this)); | |
71 | |
72 // Switch to the network thread to start the actual connection. | |
73 host_context_->network_task_runner()->PostTask( | |
74 FROM_HERE, base::Bind(&It2MeImpl::ReadPolicyAndConnect, this)); | |
75 } | |
76 | |
77 void It2MeImpl::Disconnect() { | |
78 if (!host_context_->network_task_runner()->BelongsToCurrentThread()) { | |
79 DCHECK(plugin_task_runner_->BelongsToCurrentThread()); | |
80 host_context_->network_task_runner()->PostTask( | |
81 FROM_HERE, base::Bind(&It2MeImpl::Disconnect, this)); | |
82 return; | |
83 } | |
84 | |
85 switch (state_) { | |
86 case kDisconnected: | |
87 ShutdownOnNetworkThread(); | |
88 return; | |
89 | |
90 case kStarting: | |
91 SetState(kDisconnecting); | |
92 SetState(kDisconnected); | |
93 ShutdownOnNetworkThread(); | |
94 return; | |
95 | |
96 case kDisconnecting: | |
97 return; | |
98 | |
99 default: | |
100 SetState(kDisconnecting); | |
101 | |
102 if (!host_) { | |
103 SetState(kDisconnected); | |
104 ShutdownOnNetworkThread(); | |
105 return; | |
106 } | |
107 | |
108 // Deleting the host destroys SignalStrategy synchronously, but | |
109 // SignalStrategy::Listener handlers are not allowed to destroy | |
110 // SignalStrategy, so post task to destroy the host later. | |
111 host_context_->network_task_runner()->PostTask( | |
112 FROM_HERE, base::Bind(&It2MeImpl::ShutdownOnNetworkThread, this)); | |
113 return; | |
114 } | |
115 } | |
116 | |
117 void It2MeImpl::RequestNatPolicy() { | |
118 if (!host_context_->network_task_runner()->BelongsToCurrentThread()) { | |
119 DCHECK(plugin_task_runner_->BelongsToCurrentThread()); | |
120 host_context_->network_task_runner()->PostTask( | |
121 FROM_HERE, base::Bind(&It2MeImpl::RequestNatPolicy, this)); | |
122 return; | |
123 } | |
124 | |
125 if (policy_received_) | |
126 UpdateNatPolicy(nat_traversal_enabled_); | |
127 } | |
128 | |
129 void It2MeImpl::ReadPolicyAndConnect() { | |
130 DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread()); | |
131 | |
132 SetState(kStarting); | |
133 | |
134 // Only proceed to FinishConnect() if at least one policy update has been | |
135 // received. | |
136 if (policy_received_) { | |
137 FinishConnect(); | |
138 } else { | |
139 // Otherwise, create the policy watcher, and thunk the connect. | |
140 pending_connect_ = | |
141 base::Bind(&It2MeImpl::FinishConnect, this); | |
142 } | |
143 } | |
144 | |
145 void It2MeImpl::FinishConnect() { | |
146 DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread()); | |
147 | |
148 if (state_ != kStarting) { | |
149 // Host has been stopped while we were fetching policy. | |
150 return; | |
151 } | |
152 | |
153 // Check the host domain policy. | |
154 if (!required_host_domain_.empty() && | |
155 !EndsWith(xmpp_server_config_.username, | |
156 std::string("@") + required_host_domain_, false)) { | |
157 SetState(kInvalidDomainError); | |
158 return; | |
159 } | |
160 | |
161 // Generate a key pair for the Host to use. | |
162 // TODO(wez): Move this to the worker thread. | |
163 host_key_pair_ = RsaKeyPair::Generate(); | |
164 | |
165 // Create XMPP connection. | |
166 scoped_ptr<SignalStrategy> signal_strategy( | |
167 new XmppSignalStrategy(net::ClientSocketFactory::GetDefaultFactory(), | |
168 host_context_->url_request_context_getter(), | |
169 xmpp_server_config_)); | |
170 | |
171 // Request registration of the host for support. | |
172 scoped_ptr<RegisterSupportHostRequest> register_request( | |
173 new RegisterSupportHostRequest( | |
174 signal_strategy.get(), host_key_pair_, directory_bot_jid_, | |
175 base::Bind(&It2MeImpl::OnReceivedSupportID, | |
176 base::Unretained(this)))); | |
177 | |
178 // Beyond this point nothing can fail, so save the config and request. | |
179 signal_strategy_ = signal_strategy.Pass(); | |
180 register_request_ = register_request.Pass(); | |
181 | |
182 // If NAT traversal is off then limit port range to allow firewall pin-holing. | |
183 LOG(INFO) << "NAT state: " << nat_traversal_enabled_; | |
184 NetworkSettings network_settings( | |
185 nat_traversal_enabled_ ? | |
186 NetworkSettings::NAT_TRAVERSAL_ENABLED : | |
187 NetworkSettings::NAT_TRAVERSAL_DISABLED); | |
188 if (!nat_traversal_enabled_) { | |
189 network_settings.min_port = NetworkSettings::kDefaultMinPort; | |
190 network_settings.max_port = NetworkSettings::kDefaultMaxPort; | |
191 } | |
192 | |
193 // Create the host. | |
194 host_.reset(new ChromotingHost( | |
195 signal_strategy_.get(), | |
196 desktop_environment_factory_.get(), | |
197 CreateHostSessionManager(network_settings, | |
198 host_context_->url_request_context_getter()), | |
199 host_context_->audio_task_runner(), | |
200 host_context_->input_task_runner(), | |
201 host_context_->video_capture_task_runner(), | |
202 host_context_->video_encode_task_runner(), | |
203 host_context_->network_task_runner(), | |
204 host_context_->ui_task_runner())); | |
205 host_->AddStatusObserver(this); | |
206 log_to_server_.reset( | |
207 new LogToServer(host_->AsWeakPtr(), ServerLogEntry::IT2ME, | |
208 signal_strategy_.get(), directory_bot_jid_)); | |
209 | |
210 // Disable audio by default. | |
211 // TODO(sergeyu): Add UI to enable it. | |
212 scoped_ptr<protocol::CandidateSessionConfig> protocol_config = | |
213 protocol::CandidateSessionConfig::CreateDefault(); | |
214 protocol::CandidateSessionConfig::DisableAudioChannel(protocol_config.get()); | |
215 | |
216 // VP9 encode is not yet supported. | |
217 protocol::CandidateSessionConfig::DisableVideoCodec( | |
218 protocol_config.get(), protocol::ChannelConfig::CODEC_VP9); | |
219 | |
220 host_->set_protocol_config(protocol_config.Pass()); | |
221 | |
222 // Create event logger. | |
223 host_event_logger_ = | |
224 HostEventLogger::Create(host_->AsWeakPtr(), kApplicationName); | |
225 | |
226 // Connect signaling and start the host. | |
227 signal_strategy_->Connect(); | |
228 host_->Start(xmpp_server_config_.username); | |
229 | |
230 SetState(kRequestedAccessCode); | |
231 return; | |
232 } | |
233 | |
234 void It2MeImpl::ShutdownOnNetworkThread() { | |
235 DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread()); | |
236 DCHECK(state_ == kDisconnecting || state_ == kDisconnected); | |
237 | |
238 if (state_ == kDisconnecting) { | |
239 host_event_logger_.reset(); | |
240 host_->RemoveStatusObserver(this); | |
241 host_.reset(); | |
242 | |
243 register_request_.reset(); | |
244 log_to_server_.reset(); | |
245 signal_strategy_.reset(); | |
246 SetState(kDisconnected); | |
247 } | |
248 | |
249 host_context_->ui_task_runner()->PostTask( | |
250 FROM_HERE, base::Bind(&It2MeImpl::ShutdownOnUiThread, this)); | |
251 } | |
252 | |
253 void It2MeImpl::ShutdownOnUiThread() { | |
254 DCHECK(host_context_->ui_task_runner()->BelongsToCurrentThread()); | |
255 | |
256 // Destroy the DesktopEnvironmentFactory, to free thread references. | |
257 desktop_environment_factory_.reset(); | |
258 | |
259 // Stop listening for policy updates. | |
260 if (policy_watcher_.get()) { | |
261 base::WaitableEvent policy_watcher_stopped_(true, false); | |
262 policy_watcher_->StopWatching(&policy_watcher_stopped_); | |
263 policy_watcher_stopped_.Wait(); | |
264 policy_watcher_.reset(); | |
265 } | |
266 } | |
267 | |
268 void It2MeImpl::OnAccessDenied(const std::string& jid) { | |
269 DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread()); | |
270 | |
271 ++failed_login_attempts_; | |
272 if (failed_login_attempts_ == kMaxLoginAttempts) { | |
273 Disconnect(); | |
274 } | |
275 } | |
276 | |
277 void It2MeImpl::OnClientAuthenticated( | |
278 const std::string& jid) { | |
279 DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread()); | |
280 | |
281 if (state_ == kDisconnecting) { | |
282 // Ignore the new connection if we are disconnecting. | |
283 return; | |
284 } | |
285 if (state_ == kConnected) { | |
286 // If we already connected another client then one of the connections may be | |
287 // an attacker, so both are suspect and we have to reject the second | |
288 // connection and shutdown the host. | |
289 host_->RejectAuthenticatingClient(); | |
290 Disconnect(); | |
291 return; | |
292 } | |
293 | |
294 std::string client_username = jid; | |
295 size_t pos = client_username.find('/'); | |
296 if (pos != std::string::npos) | |
297 client_username.replace(pos, std::string::npos, ""); | |
298 | |
299 LOG(INFO) << "Client " << client_username << " connected."; | |
300 | |
301 // Pass the client user name to the script object before changing state. | |
302 plugin_task_runner_->PostTask( | |
303 FROM_HERE, base::Bind(&It2MeImpl::Observer::OnClientAuthenticated, | |
304 observer_, client_username)); | |
305 | |
306 SetState(kConnected); | |
307 } | |
308 | |
309 void It2MeImpl::OnClientDisconnected( | |
310 const std::string& jid) { | |
311 DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread()); | |
312 | |
313 Disconnect(); | |
314 } | |
315 | |
316 void It2MeImpl::OnPolicyUpdate( | |
317 scoped_ptr<base::DictionaryValue> policies) { | |
318 DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread()); | |
319 | |
320 bool nat_policy; | |
321 if (policies->GetBoolean(policy_hack::PolicyWatcher::kNatPolicyName, | |
322 &nat_policy)) { | |
323 UpdateNatPolicy(nat_policy); | |
324 } | |
325 std::string host_domain; | |
326 if (policies->GetString(policy_hack::PolicyWatcher::kHostDomainPolicyName, | |
327 &host_domain)) { | |
328 UpdateHostDomainPolicy(host_domain); | |
329 } | |
330 | |
331 policy_received_ = true; | |
332 | |
333 if (!pending_connect_.is_null()) { | |
334 pending_connect_.Run(); | |
335 pending_connect_.Reset(); | |
336 } | |
337 } | |
338 | |
339 void It2MeImpl::UpdateNatPolicy( | |
340 bool nat_traversal_enabled) { | |
341 DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread()); | |
342 | |
343 VLOG(2) << "UpdateNatPolicy: " << nat_traversal_enabled; | |
344 | |
345 // When transitioning from enabled to disabled, force disconnect any | |
346 // existing session. | |
347 if (nat_traversal_enabled_ && !nat_traversal_enabled && IsConnected()) { | |
348 Disconnect(); | |
349 } | |
350 | |
351 nat_traversal_enabled_ = nat_traversal_enabled; | |
352 | |
353 // Notify the web-app of the policy setting. | |
354 plugin_task_runner_->PostTask( | |
355 FROM_HERE, base::Bind(&It2MeImpl::Observer::OnNatPolicyChanged, | |
356 observer_, nat_traversal_enabled_)); | |
357 } | |
358 | |
359 void It2MeImpl::UpdateHostDomainPolicy( | |
360 const std::string& host_domain) { | |
361 DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread()); | |
362 | |
363 VLOG(2) << "UpdateHostDomainPolicy: " << host_domain; | |
364 | |
365 // When setting a host domain policy, force disconnect any existing session. | |
366 if (!host_domain.empty() && IsConnected()) { | |
367 Disconnect(); | |
368 } | |
369 | |
370 required_host_domain_ = host_domain; | |
371 } | |
372 | |
373 It2MeImpl::~It2MeImpl() { | |
374 // Check that resources that need to be torn down on the UI thread are gone. | |
375 DCHECK(!desktop_environment_factory_.get()); | |
376 DCHECK(!policy_watcher_.get()); | |
377 } | |
378 | |
379 void It2MeImpl::SetState(It2MeHostState state) { | |
380 DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread()); | |
381 | |
382 switch (state_) { | |
383 case kDisconnected: | |
384 DCHECK(state == kStarting || | |
385 state == kError) << state; | |
386 break; | |
387 case kStarting: | |
388 DCHECK(state == kRequestedAccessCode || | |
389 state == kDisconnecting || | |
390 state == kError || | |
391 state == kInvalidDomainError) << state; | |
392 break; | |
393 case kRequestedAccessCode: | |
394 DCHECK(state == kReceivedAccessCode || | |
395 state == kDisconnecting || | |
396 state == kError) << state; | |
397 break; | |
398 case kReceivedAccessCode: | |
399 DCHECK(state == kConnected || | |
400 state == kDisconnecting || | |
401 state == kError) << state; | |
402 break; | |
403 case kConnected: | |
404 DCHECK(state == kDisconnecting || | |
405 state == kDisconnected || | |
406 state == kError) << state; | |
407 break; | |
408 case kDisconnecting: | |
409 DCHECK(state == kDisconnected) << state; | |
410 break; | |
411 case kError: | |
412 DCHECK(state == kDisconnecting) << state; | |
413 break; | |
414 case kInvalidDomainError: | |
415 DCHECK(state == kDisconnecting) << state; | |
416 break; | |
417 }; | |
418 | |
419 state_ = state; | |
420 | |
421 // Post a state-change notification to the web-app. | |
422 plugin_task_runner_->PostTask( | |
423 FROM_HERE, base::Bind(&It2MeImpl::Observer::OnStateChanged, | |
424 observer_, state)); | |
425 } | |
426 | |
427 bool It2MeImpl::IsConnected() const { | |
428 return state_ == kRequestedAccessCode || state_ == kReceivedAccessCode || | |
429 state_ == kConnected; | |
430 } | |
431 | |
432 void It2MeImpl::OnReceivedSupportID( | |
433 bool success, | |
434 const std::string& support_id, | |
435 const base::TimeDelta& lifetime) { | |
436 DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread()); | |
437 | |
438 if (!success) { | |
439 SetState(kError); | |
440 Disconnect(); | |
441 return; | |
442 } | |
443 | |
444 std::string host_secret = GenerateSupportHostSecret(); | |
445 std::string access_code = support_id + host_secret; | |
446 | |
447 std::string local_certificate = host_key_pair_->GenerateCertificate(); | |
448 if (local_certificate.empty()) { | |
449 LOG(ERROR) << "Failed to generate host certificate."; | |
450 SetState(kError); | |
451 Disconnect(); | |
452 return; | |
453 } | |
454 | |
455 scoped_ptr<protocol::AuthenticatorFactory> factory( | |
456 new protocol::It2MeHostAuthenticatorFactory( | |
457 local_certificate, host_key_pair_, access_code)); | |
458 host_->SetAuthenticatorFactory(factory.Pass()); | |
459 | |
460 // Pass the Access Code to the script object before changing state. | |
461 plugin_task_runner_->PostTask( | |
462 FROM_HERE, base::Bind(&It2MeImpl::Observer::OnStoreAccessCode, | |
463 observer_, access_code, lifetime)); | |
464 | |
465 SetState(kReceivedAccessCode); | |
466 } | |
467 | |
468 } // namespace remoting | |
OLD | NEW |