Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(263)

Side by Side Diff: chrome/browser/sync/notifier/communicator/single_login_attempt.cc

Issue 1956001: Moved XMPP notifier library from chrome/browser/sync to chrome/common.... (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: '' Created 10 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2009 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 <algorithm>
6 #include <string>
7 #include <vector>
8
9 #include "chrome/browser/sync/notifier/communicator/single_login_attempt.h"
10
11 #include "base/logging.h"
12 #include "chrome/browser/sync/notifier/communicator/connection_options.h"
13 #include "chrome/browser/sync/notifier/communicator/connection_settings.h"
14 #include "chrome/browser/sync/notifier/communicator/const_communicator.h"
15 #include "chrome/browser/sync/notifier/communicator/login_failure.h"
16 #include "chrome/browser/sync/notifier/communicator/login_settings.h"
17 #include "chrome/browser/sync/notifier/communicator/product_info.h"
18 #include "chrome/browser/sync/notifier/communicator/xmpp_connection_generator.h"
19 #include "chrome/browser/sync/notifier/communicator/xmpp_socket_adapter.h"
20 #include "talk/base/asynchttprequest.h"
21 #include "talk/base/firewallsocketserver.h"
22 #include "talk/base/signalthread.h"
23 #include "talk/base/taskrunner.h"
24 #include "talk/base/winsock_initializer.h"
25 #include "talk/xmllite/xmlelement.h"
26 #include "talk/xmpp/saslcookiemechanism.h"
27 #include "talk/xmpp/saslhandler.h"
28 #include "talk/xmpp/xmppclient.h"
29 #include "talk/xmpp/xmppclientsettings.h"
30 #include "talk/xmpp/xmppconstants.h"
31
32 namespace notifier {
33
34 static void GetClientErrorInformation(
35 buzz::XmppClient* client,
36 buzz::XmppEngine::Error* error,
37 int* subcode,
38 buzz::XmlElement** stream_error) {
39 ASSERT(client != NULL);
40 ASSERT(error && subcode && stream_error);
41
42 *error = client->GetError(subcode);
43
44 *stream_error = NULL;
45 if (*error == buzz::XmppEngine::ERROR_STREAM) {
46 const buzz::XmlElement* error_element = client->GetStreamError();
47 if (error_element) {
48 *stream_error = new buzz::XmlElement(*error_element);
49 }
50 }
51 }
52
53 namespace {
54
55 const char kGaiaAuthMechanism[] = "X-GOOGLE-TOKEN";
56
57 // This class looks for the X-GOOGLE-TOKEN auth mechanism and uses
58 // that instead of the default auth mechanism (PLAIN).
59 class GaiaOnlySaslHandler : public buzz::SaslHandler {
60 public:
61 GaiaOnlySaslHandler(
62 const std::string& username,
63 const std::string& token,
64 const std::string& token_service)
65 : username_(username),
66 token_(token),
67 token_service_(token_service) {}
68
69 virtual std::string ChooseBestSaslMechanism(
70 const std::vector<std::string> & mechanisms, bool encrypted) {
71 return (std::find(mechanisms.begin(),
72 mechanisms.end(), kGaiaAuthMechanism) !=
73 mechanisms.end()) ? kGaiaAuthMechanism : "";
74 }
75
76 virtual buzz::SaslMechanism* CreateSaslMechanism(
77 const std::string& mechanism) {
78 return
79 (mechanism == kGaiaAuthMechanism) ?
80 new buzz::SaslCookieMechanism(
81 kGaiaAuthMechanism, username_, token_, token_service_)
82 : NULL;
83 }
84
85 virtual bool GetTlsServerInfo(const talk_base::SocketAddress& server,
86 std::string* tls_server_hostname,
87 std::string* tls_server_domain) {
88 std::string server_ip = server.IPAsString();
89 if ((server_ip == buzz::STR_TALK_GOOGLE_COM) ||
90 (server_ip == buzz::STR_TALKX_L_GOOGLE_COM)) {
91 // For Gaia auth, the talk.google.com server expects you to use
92 // "gmail.com" in the stream, and expects the domain certificate
93 // to be "gmail.com" as well.
94 *tls_server_hostname = buzz::STR_GMAIL_COM;
95 *tls_server_domain = buzz::STR_GMAIL_COM;
96 return true;
97 }
98 return false;
99 }
100
101 private:
102 std::string username_, token_, token_service_;
103 };
104
105 } // namespace
106
107 SingleLoginAttempt::SingleLoginAttempt(talk_base::Task* parent,
108 LoginSettings* login_settings,
109 bool successful_connection)
110 : talk_base::Task(parent),
111 state_(buzz::XmppEngine::STATE_NONE),
112 code_(buzz::XmppEngine::ERROR_NONE),
113 subcode_(0),
114 need_authentication_(false),
115 certificate_expired_(false),
116 cookie_refreshed_(false),
117 successful_connection_(successful_connection),
118 login_settings_(login_settings),
119 client_(NULL) {
120 #if defined(OS_WIN)
121 talk_base::EnsureWinsockInit();
122 #endif
123 connection_generator_.reset(new XmppConnectionGenerator(
124 this,
125 &login_settings_->connection_options(),
126 login_settings_->proxy_only(),
127 login_settings_->server_list(),
128 login_settings_->server_count()));
129
130 connection_generator_->SignalExhaustedSettings.connect(
131 this,
132 &SingleLoginAttempt::OnAttemptedAllConnections);
133 connection_generator_->SignalNewSettings.connect(
134 this,
135 &SingleLoginAttempt::DoLogin);
136 }
137
138 SingleLoginAttempt::~SingleLoginAttempt() {
139 // If this assertion goes off, it means that "Stop()" didn't get called like
140 // it should have been.
141 ASSERT(client_ == NULL);
142 }
143
144 bool SingleLoginAttempt::auto_reconnect() const {
145 return login_settings_->connection_options().auto_reconnect();
146 }
147
148 const talk_base::ProxyInfo& SingleLoginAttempt::proxy() const {
149 ASSERT(connection_generator_.get());
150 return connection_generator_->proxy();
151 }
152
153 int SingleLoginAttempt::ProcessStart() {
154 ASSERT(GetState() == talk_base::Task::STATE_START);
155 connection_generator_->StartGenerating();
156
157 // After being started, this class is callback driven and does signaling from
158 // those callbacks (with checks to see if it is done if it may be called back
159 // from something that isn't a child task).
160 return talk_base::Task::STATE_BLOCKED;
161 }
162
163 void SingleLoginAttempt::Stop() {
164 ClearClient();
165 talk_base::Task::Stop();
166
167 // No more signals should happen after being stopped. This is needed because
168 // some of these signals happen due to other components doing signaling which
169 // may continue running even though this task is stopped.
170 SignalUnexpectedDisconnect.disconnect_all();
171 SignalRedirect.disconnect_all();
172 SignalLoginFailure.disconnect_all();
173 SignalNeedAutoReconnect.disconnect_all();
174 SignalClientStateChange.disconnect_all();
175 }
176
177 void SingleLoginAttempt::OnAttemptedAllConnections(
178 bool successfully_resolved_dns,
179 int first_dns_error) {
180
181 // Maybe we needed proxy authentication?
182 if (need_authentication_) {
183 LoginFailure failure(LoginFailure::PROXY_AUTHENTICATION_ERROR);
184 SignalLoginFailure(failure);
185 return;
186 }
187
188 if (certificate_expired_) {
189 LoginFailure failure(LoginFailure::CERTIFICATE_EXPIRED_ERROR);
190 SignalLoginFailure(failure);
191 return;
192 }
193
194 if (!successfully_resolved_dns) {
195 code_ = buzz::XmppEngine::ERROR_SOCKET;
196 subcode_ = first_dns_error;
197 }
198
199 LOG(INFO) << "Connection failed with error " << code_;
200
201 // We were connected and we had a problem.
202 if (successful_connection_ && auto_reconnect()) {
203 SignalNeedAutoReconnect();
204 // Expect to be deleted at this point.
205 return;
206 }
207
208 DiagnoseConnectionError();
209 }
210
211 void SingleLoginAttempt::UseNextConnection() {
212 ASSERT(connection_generator_.get() != NULL);
213 ClearClient();
214 connection_generator_->UseNextConnection();
215 }
216
217 void SingleLoginAttempt::UseCurrentConnection() {
218 ASSERT(connection_generator_.get() != NULL);
219 ClearClient();
220 connection_generator_->UseCurrentConnection();
221 }
222
223 void SingleLoginAttempt::DoLogin(
224 const ConnectionSettings& connection_settings) {
225 if (client_) {
226 return;
227 }
228
229 buzz::XmppClientSettings client_settings;
230 // Set the user settings portion.
231 *static_cast<buzz::XmppClientSettings*>(&client_settings) =
232 login_settings_->user_settings();
233 // Fill in the rest of the client settings.
234 connection_settings.FillXmppClientSettings(&client_settings);
235
236 client_ = new buzz::XmppClient(this);
237 SignalLogInput.repeat(client_->SignalLogInput);
238 SignalLogOutput.repeat(client_->SignalLogOutput);
239
240 // Listen for connection progress.
241 client_->SignalStateChange.connect(this,
242 &SingleLoginAttempt::OnClientStateChange);
243
244 // Transition to "start".
245 OnClientStateChange(buzz::XmppEngine::STATE_START);
246 // Start connecting.
247 client_->Connect(client_settings, login_settings_->lang(),
248 CreateSocket(client_settings),
249 NULL,
250 CreateSaslHandler(client_settings));
251 client_->Start();
252 }
253
254 void SingleLoginAttempt::OnAuthenticationError() {
255 // We can check this flag later if all connection options fail.
256 need_authentication_ = true;
257 }
258
259 void SingleLoginAttempt::OnCertificateExpired() {
260 // We can check this flag later if all connection options fail.
261 certificate_expired_ = true;
262 }
263
264 buzz::AsyncSocket* SingleLoginAttempt::CreateSocket(
265 const buzz::XmppClientSettings& xcs) {
266 bool allow_unverified_certs =
267 login_settings_->connection_options().allow_unverified_certs();
268 XmppSocketAdapter* adapter = new XmppSocketAdapter(xcs,
269 allow_unverified_certs);
270 adapter->SignalAuthenticationError.connect(
271 this,
272 &SingleLoginAttempt::OnAuthenticationError);
273 if (login_settings_->firewall()) {
274 adapter->set_firewall(true);
275 }
276 return adapter;
277 }
278
279 buzz::SaslHandler* SingleLoginAttempt::CreateSaslHandler(
280 const buzz::XmppClientSettings& xcs) {
281 buzz::Jid jid(xcs.user(), xcs.host(), buzz::STR_EMPTY);
282 return new GaiaOnlySaslHandler(
283 jid.Str(), xcs.auth_cookie(), xcs.token_service());
284 }
285
286 void SingleLoginAttempt::OnFreshAuthCookie(const std::string& auth_cookie) {
287 // Remember this is a fresh cookie.
288 cookie_refreshed_ = true;
289
290 // TODO(sync): do the cookie logic (part of which is in the #if 0 below).
291
292 // The following code is what PhoneWindow does for the equivalent method.
293 #if 0
294 // Save cookie
295 AccountInfo current(account_history_.current());
296 current.set_auth_cookie(auth_cookie);
297 account_history_.set_current(current);
298
299 // Calc next time to refresh cookie, between 5 and 10 days. The cookie has
300 // 14 days of life; this gives at least 4 days of retries before the current
301 // cookie expires, maximizing the chance of having a valid cookie next time
302 // the connection servers go down.
303 FTULL now;
304
305 // NOTE: The following line is win32. Address this when implementing this
306 // code (doing "the cookie logic").
307 GetSystemTimeAsFileTime(&(now.ft));
308 ULONGLONG five_days = (ULONGLONG)10000 * 1000 * 60 * 60 * 24 * 5; // 5 days
309 ULONGLONG random = (ULONGLONG)10000 * // get to 100 ns units
310 ((rand() % (5 * 24 * 60)) * (60 * 1000) + // random min. in 5 day period
311 (rand() % 1000) * 60); // random 1/1000th of a minute
312 next_cookie_refresh_ = now.ull + five_days + random; // 5-10 days
313 #endif
314 }
315
316 void SingleLoginAttempt::DiagnoseConnectionError() {
317 switch (code_) {
318 case buzz::XmppEngine::ERROR_MISSING_USERNAME:
319 case buzz::XmppEngine::ERROR_NETWORK_TIMEOUT:
320 case buzz::XmppEngine::ERROR_DOCUMENT_CLOSED:
321 case buzz::XmppEngine::ERROR_BIND:
322 case buzz::XmppEngine::ERROR_AUTH:
323 case buzz::XmppEngine::ERROR_TLS:
324 case buzz::XmppEngine::ERROR_UNAUTHORIZED:
325 case buzz::XmppEngine::ERROR_VERSION:
326 case buzz::XmppEngine::ERROR_STREAM:
327 case buzz::XmppEngine::ERROR_XML:
328 case buzz::XmppEngine::ERROR_NONE:
329 default: {
330 LoginFailure failure(LoginFailure::XMPP_ERROR, code_, subcode_);
331 SignalLoginFailure(failure);
332 return;
333 }
334
335 // The following errors require diagnosistics:
336 // * spurious close of connection
337 // * socket errors after auth
338 case buzz::XmppEngine::ERROR_CONNECTION_CLOSED:
339 case buzz::XmppEngine::ERROR_SOCKET:
340 break;
341 }
342
343 talk_base::AsyncHttpRequest *http_request =
344 new talk_base::AsyncHttpRequest(GetUserAgentString());
345 http_request->set_host("www.google.com");
346 http_request->set_port(80);
347 http_request->set_secure(false);
348 http_request->request().path = "/";
349 http_request->request().verb = talk_base::HV_GET;
350
351 talk_base::ProxyInfo proxy;
352 ASSERT(connection_generator_.get() != NULL);
353 if (connection_generator_.get()) {
354 proxy = connection_generator_->proxy();
355 }
356 http_request->set_proxy(proxy);
357 http_request->set_firewall(login_settings_->firewall());
358
359 http_request->SignalWorkDone.connect(this,
360 &SingleLoginAttempt::OnHttpTestDone);
361 http_request->Start();
362 http_request->Release();
363 }
364
365 void SingleLoginAttempt::OnHttpTestDone(talk_base::SignalThread* thread) {
366 ASSERT(thread != NULL);
367
368 talk_base::AsyncHttpRequest* request =
369 static_cast<talk_base::AsyncHttpRequest*>(thread);
370
371 if (request->response().scode == 200) {
372 // We were able to do an HTTP GET of www.google.com:80
373
374 //
375 // The original error should be reported
376 //
377 LoginFailure failure(LoginFailure::XMPP_ERROR, code_, subcode_);
378 SignalLoginFailure(failure);
379 return;
380 }
381
382 // Otherwise lets transmute the error into ERROR_SOCKET, and put the subcode
383 // as an indicator of what we think the problem might be.
384
385 #if 0
386 // TODO(sync): determine if notifier has an analogous situation.
387
388 //
389 // We weren't able to do an HTTP GET of www.google.com:80
390 //
391 GAutoupdater::Version version_logged_in(g_options.version_logged_in());
392 GAutoupdater::Version version_installed(GetProductVersion().c_str());
393 if (version_logged_in < version_installed) {
394 //
395 // Google Talk has been updated and can no longer connect to the Google
396 // Talk Service. Your firewall is probably not allowing the new version of
397 // Google Talk to connect to the internet. Please adjust your firewall
398 // settings to allow the new version of Google Talk to connect to the
399 // internet.
400 //
401 // We'll use the "error=1" to help figure this out for now.
402 //
403 LoginFailure failure(LoginFailure::XMPP_ERROR,
404 buzz::XmppEngine::ERROR_SOCKET,
405 1);
406 SignalLoginFailure(failure);
407 return;
408 }
409 #endif
410
411 //
412 // Any other checking we can add here?
413 //
414
415 //
416 // Google Talk is unable to use your internet connection. Either your network
417 // isn't configured or Google Talk is being blocked by a local firewall.
418 //
419 // We'll use the "error=0" to help figure this out for now
420 //
421 LoginFailure failure(LoginFailure::XMPP_ERROR,
422 buzz::XmppEngine::ERROR_SOCKET,
423 0);
424 SignalLoginFailure(failure);
425 }
426
427 void SingleLoginAttempt::OnClientStateChange(buzz::XmppEngine::State state) {
428 if (state_ == state)
429 return;
430
431 buzz::XmppEngine::State previous_state = state_;
432 state_ = state;
433
434 switch (state) {
435 case buzz::XmppEngine::STATE_NONE:
436 case buzz::XmppEngine::STATE_START:
437 case buzz::XmppEngine::STATE_OPENING:
438 // Do nothing.
439 break;
440 case buzz::XmppEngine::STATE_OPEN:
441 successful_connection_ = true;
442 break;
443 case buzz::XmppEngine::STATE_CLOSED:
444 OnClientStateChangeClosed(previous_state);
445 break;
446 }
447 SignalClientStateChange(state);
448 if (state_ == buzz::XmppEngine::STATE_CLOSED) {
449 OnClientStateChange(buzz::XmppEngine::STATE_NONE);
450 }
451 }
452
453 void SingleLoginAttempt::ClearClient() {
454 if (client_ != NULL) {
455 client_->Disconnect();
456
457 // If this assertion goes off, it means that the disconnect didn't occur
458 // properly. See SingleLoginAttempt::OnClientStateChange,
459 // case XmppEngine::STATE_CLOSED
460 ASSERT(client_ == NULL);
461 }
462 }
463
464 void SingleLoginAttempt::OnClientStateChangeClosed(
465 buzz::XmppEngine::State previous_state) {
466 buzz::XmppEngine::Error error = buzz::XmppEngine::ERROR_NONE;
467 int error_subcode = 0;
468 buzz::XmlElement* stream_error_ptr;
469 GetClientErrorInformation(client_,
470 &error,
471 &error_subcode,
472 &stream_error_ptr);
473 scoped_ptr<buzz::XmlElement> stream_error(stream_error_ptr);
474
475 client_->SignalStateChange.disconnect(this);
476 client_ = NULL;
477
478 if (error == buzz::XmppEngine::ERROR_NONE) {
479 SignalLogoff();
480 return;
481 } else if (previous_state == buzz::XmppEngine::STATE_OPEN) {
482 // Handler should attempt reconnect.
483 SignalUnexpectedDisconnect();
484 return;
485 } else {
486 HandleConnectionError(error, error_subcode, stream_error.get());
487 }
488 }
489
490 void SingleLoginAttempt::HandleConnectionPasswordError() {
491 LOG(INFO) << "SingleLoginAttempt::HandleConnectionPasswordError";
492 LoginFailure failure(LoginFailure::XMPP_ERROR, code_, subcode_);
493 SignalLoginFailure(failure);
494 }
495
496 void SingleLoginAttempt::HandleConnectionError(
497 buzz::XmppEngine::Error code,
498 int subcode,
499 const buzz::XmlElement* stream_error) {
500 LOG(INFO) << "(" << code << ", " << subcode << ")";
501
502 // Save off the error code information, so we can use it to tell the user
503 // what went wrong if all else fails.
504 code_ = code;
505 subcode_ = subcode;
506 if ((code_ == buzz::XmppEngine::ERROR_UNAUTHORIZED) ||
507 (code_ == buzz::XmppEngine::ERROR_MISSING_USERNAME)) {
508 // There was a problem with credentials (username/password).
509 HandleConnectionPasswordError();
510 return;
511 }
512
513 // Unexpected disconnect,
514 // Unreachable host,
515 // Or internal server binding error -
516 // All these are temporary problems, so continue reconnecting.
517
518 // GaiaAuth signals this directly via SignalCertificateExpired, but
519 // SChannelAdapter propagates the error through SocketWindow as a socket
520 // error.
521 if (code_ == buzz::XmppEngine::ERROR_SOCKET &&
522 subcode_ == SEC_E_CERT_EXPIRED) {
523 certificate_expired_ = true;
524 }
525
526 login_settings_->modifiable_user_settings()->set_resource("");
527
528 // Look for stream::error server redirection stanza "see-other-host".
529 if (stream_error) {
530 const buzz::XmlElement* other =
531 stream_error->FirstNamed(buzz::QN_XSTREAM_SEE_OTHER_HOST);
532 if (other) {
533 const buzz::XmlElement* text =
534 stream_error->FirstNamed(buzz::QN_XSTREAM_TEXT);
535 if (text) {
536 // Yep, its a "stream:error" with "see-other-host" text, let's parse
537 // out the server:port, and then reconnect with that.
538 const std::string& redirect = text->BodyText();
539 size_t colon = redirect.find(":");
540 int redirect_port = kDefaultXmppPort;
541 std::string redirect_server;
542 if (colon == std::string::npos) {
543 redirect_server = redirect;
544 } else {
545 redirect_server = redirect.substr(0, colon);
546 const std::string& port_text = redirect.substr(colon + 1);
547 std::istringstream ist(port_text);
548 ist >> redirect_port;
549 }
550 // We never allow a redirect to port 0.
551 if (redirect_port == 0) {
552 redirect_port = kDefaultXmppPort;
553 }
554 SignalRedirect(redirect_server, redirect_port);
555 // May be deleted at this point.
556 return;
557 }
558 }
559 }
560
561 ASSERT(connection_generator_.get() != NULL);
562 if (!connection_generator_.get()) {
563 return;
564 }
565
566 // Iterate to the next possible connection (still trying to connect).
567 UseNextConnection();
568 }
569
570 } // namespace notifier
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698