| OLD | NEW |
| (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 | |
| OLD | NEW |