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

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

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

Powered by Google App Engine
This is Rietveld 408576698