Index: chrome/browser/sync/notifier/communicator/single_login_attempt.cc |
=================================================================== |
--- chrome/browser/sync/notifier/communicator/single_login_attempt.cc (revision 0) |
+++ chrome/browser/sync/notifier/communicator/single_login_attempt.cc (revision 0) |
@@ -0,0 +1,562 @@ |
+// Copyright (c) 2009 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include <string> |
+ |
+#include "chrome/browser/sync/notifier/communicator/single_login_attempt.h" |
+ |
+#include "chrome/browser/sync/notifier/communicator/connection_options.h" |
+#include "chrome/browser/sync/notifier/communicator/connection_settings.h" |
+#include "chrome/browser/sync/notifier/communicator/const_communicator.h" |
+#include "chrome/browser/sync/notifier/communicator/login_failure.h" |
+#include "chrome/browser/sync/notifier/communicator/login_settings.h" |
+#include "chrome/browser/sync/notifier/communicator/product_info.h" |
+#include "chrome/browser/sync/notifier/communicator/xmpp_connection_generator.h" |
+#include "chrome/browser/sync/notifier/communicator/xmpp_socket_adapter.h" |
+#include "chrome/browser/sync/notifier/gaia_auth/gaiaauth.h" |
+#include "talk/base/asynchttprequest.h" |
+#include "talk/base/firewallsocketserver.h" |
+#include "talk/base/signalthread.h" |
+#include "talk/base/taskrunner.h" |
+#include "talk/xmllite/xmlelement.h" |
+#include "talk/xmpp/constants.h" |
+#include "talk/xmpp/prexmppauth.h" |
+#include "talk/xmpp/xmppclient.h" |
+#include "talk/xmpp/xmppclientsettings.h" |
+ |
+namespace notifier { |
+static void FillProxyInfo(const buzz::XmppClientSettings& xcs, |
+ talk_base::ProxyInfo* proxy) { |
+ ASSERT(proxy != NULL); |
+ proxy->type = xcs.proxy(); |
+ proxy->address.SetIP(xcs.proxy_host()); |
+ proxy->address.SetPort(xcs.proxy_port()); |
+ if (xcs.use_proxy_auth()) { |
+ proxy->username = xcs.proxy_user(); |
+ proxy->password = xcs.proxy_pass(); |
+ } |
+} |
+ |
+static void GetClientErrorInformation( |
+ buzz::XmppClient* client, |
+ buzz::XmppEngine::Error* error, |
+ int* subcode, |
+ buzz::XmlElement** stream_error, |
+ buzz::CaptchaChallenge* captcha_challenge) { |
+ ASSERT(client != NULL); |
+ ASSERT(error && subcode && stream_error && captcha_challenge); |
+ |
+ *error = client->GetError(subcode); |
+ *captcha_challenge = client->GetCaptchaChallenge(); |
+ |
+ *stream_error = NULL; |
+ if (*error == buzz::XmppEngine::ERROR_STREAM) { |
+ const buzz::XmlElement* error_element = client->GetStreamError(); |
+ if (error_element) { |
+ *stream_error = new buzz::XmlElement(*error_element); |
+ } |
+ } |
+} |
+ |
+SingleLoginAttempt::SingleLoginAttempt(talk_base::Task* parent, |
+ LoginSettings* login_settings, |
+ bool successful_connection) |
+ : talk_base::Task(parent), |
+ state_(buzz::XmppEngine::STATE_NONE), |
+ code_(buzz::XmppEngine::ERROR_NONE), |
+ subcode_(0), |
+ need_authentication_(false), |
+ certificate_expired_(false), |
+ cookie_refreshed_(false), |
+ successful_connection_(successful_connection), |
+ login_settings_(login_settings), |
+ client_(NULL) { |
+ connection_generator_.reset(new XmppConnectionGenerator( |
+ this, |
+ &login_settings_->connection_options(), |
+ login_settings_->proxy_only(), |
+ login_settings_->server_list(), |
+ login_settings_->server_count())); |
+ |
+ connection_generator_->SignalExhaustedSettings.connect( |
+ this, |
+ &SingleLoginAttempt::OnAttemptedAllConnections); |
+ connection_generator_->SignalNewSettings.connect( |
+ this, |
+ &SingleLoginAttempt::DoLogin); |
+} |
+ |
+SingleLoginAttempt::~SingleLoginAttempt() { |
+ // If this assertion goes off, it means that "Stop()" didn't get |
+ // called like it should have been. |
+ ASSERT(client_ == NULL); |
+} |
+ |
+bool SingleLoginAttempt::auto_reconnect() const { |
+ return login_settings_->connection_options().auto_reconnect(); |
+} |
+ |
+const talk_base::ProxyInfo& SingleLoginAttempt::proxy() const { |
+ ASSERT(connection_generator_.get()); |
+ return connection_generator_->proxy(); |
+} |
+ |
+int SingleLoginAttempt::ProcessStart() { |
+ ASSERT(GetState() == talk_base::Task::STATE_START); |
+ connection_generator_->StartGenerating(); |
+ |
+ // After being started, this class is callback driven and does |
+ // signaling from those callbacks (with checks to see if it is |
+ // done if it may be called back from something that isn't a child task). |
+ return talk_base::Task::STATE_BLOCKED; |
+} |
+ |
+void SingleLoginAttempt::Stop() { |
+ ClearClient(); |
+ talk_base::Task::Stop(); |
+ |
+ // No more signals should happen after being stopped. |
+ // (This is needed because some of these signals |
+ // happen due to other components doing signaling which |
+ // may continue running even though this task is stopped.) |
+ SignalUnexpectedDisconnect.disconnect_all(); |
+ SignalRedirect.disconnect_all(); |
+ SignalLoginFailure.disconnect_all(); |
+ SignalNeedAutoReconnect.disconnect_all(); |
+ SignalClientStateChange.disconnect_all(); |
+} |
+ |
+void SingleLoginAttempt::OnAttemptedAllConnections( |
+ bool successfully_resolved_dns, |
+ int first_dns_error) { |
+ |
+ // Maybe we needed proxy authentication? |
+ if (need_authentication_) { |
+ LoginFailure failure(LoginFailure::PROXY_AUTHENTICATION_ERROR); |
+ SignalLoginFailure(failure); |
+ return; |
+ } |
+ |
+ if (certificate_expired_) { |
+ LoginFailure failure(LoginFailure::CERTIFICATE_EXPIRED_ERROR); |
+ SignalLoginFailure(failure); |
+ return; |
+ } |
+ |
+ if (!successfully_resolved_dns) { |
+ code_ = buzz::XmppEngine::ERROR_SOCKET; |
+ subcode_ = first_dns_error; |
+ } |
+ |
+ LOG(INFO) << "Connection failed with error " << code_; |
+ |
+ // We were connected and we had a problem |
+ if (successful_connection_ && auto_reconnect()) { |
+ SignalNeedAutoReconnect(); |
+ // expect to be deleted at this point |
+ return; |
+ } |
+ |
+ DiagnoseConnectionError(); |
+} |
+ |
+void SingleLoginAttempt::UseNextConnection() { |
+ ASSERT(connection_generator_.get() != NULL); |
+ ClearClient(); |
+ connection_generator_->UseNextConnection(); |
+} |
+ |
+void SingleLoginAttempt::UseCurrentConnection() { |
+ ASSERT(connection_generator_.get() != NULL); |
+ ClearClient(); |
+ connection_generator_->UseCurrentConnection(); |
+} |
+ |
+void SingleLoginAttempt::DoLogin( |
+ const ConnectionSettings& connection_settings) { |
+ if (client_) { |
+ return; |
+ } |
+ |
+ buzz::XmppClientSettings client_settings; |
+ // set the user settings portion |
+ *static_cast<buzz::XmppClientSettings*>(&client_settings) = |
+ login_settings_->user_settings(); |
+ // fill in the rest of the client settings |
+ connection_settings.FillXmppClientSettings(&client_settings); |
+ |
+ client_ = new buzz::XmppClient(this); |
+ SignalLogInput.repeat(client_->SignalLogInput); |
+ SignalLogOutput.repeat(client_->SignalLogOutput); |
+ |
+ // listen for connection progress |
+ client_->SignalStateChange.connect(this, |
+ &SingleLoginAttempt::OnClientStateChange); |
+ |
+ // transition to "start" |
+ OnClientStateChange(buzz::XmppEngine::STATE_START); |
+ // start connecting |
+ client_->Connect(client_settings, login_settings_->lang(), |
+ CreateSocket(client_settings), |
+ CreatePreXmppAuth(client_settings)); |
+ client_->Start(); |
+} |
+ |
+void SingleLoginAttempt::OnAuthenticationError() { |
+ // We can check this flag later if all connection options fail |
+ need_authentication_ = true; |
+} |
+ |
+void SingleLoginAttempt::OnCertificateExpired() { |
+ // We can check this flag later if all connection options fail |
+ certificate_expired_ = true; |
+} |
+ |
+ |
+buzz::AsyncSocket* SingleLoginAttempt::CreateSocket( |
+ const buzz::XmppClientSettings& xcs) { |
+ bool allow_unverified_certs = |
+ login_settings_->connection_options().allow_unverified_certs(); |
+ XmppSocketAdapter* adapter = new XmppSocketAdapter(xcs, |
+ allow_unverified_certs); |
+ adapter->SignalAuthenticationError.connect( |
+ this, |
+ &SingleLoginAttempt::OnAuthenticationError); |
+ if (login_settings_->firewall()) { |
+ adapter->set_firewall(true); |
+ } |
+ return adapter; |
+} |
+ |
+buzz::PreXmppAuth* SingleLoginAttempt::CreatePreXmppAuth( |
+ const buzz::XmppClientSettings& xcs) { |
+ if (login_settings_->no_gaia_auth()) |
+ return NULL; |
+ |
+ // For GMail, use Gaia preauthentication over HTTP |
+ buzz::GaiaAuth* auth = new buzz::GaiaAuth(GetUserAgentString(), |
+ GetProductSignature()); |
+ auth->SignalAuthenticationError.connect( |
+ this, |
+ &SingleLoginAttempt::OnAuthenticationError); |
+ auth->SignalCertificateExpired.connect( |
+ this, |
+ &SingleLoginAttempt::OnCertificateExpired); |
+ auth->SignalFreshAuthCookie.connect( |
+ this, |
+ &SingleLoginAttempt::OnFreshAuthCookie); |
+ auth->set_token_service(xcs.token_service()); |
+ |
+ talk_base::ProxyInfo proxy; |
+ FillProxyInfo(xcs, &proxy); |
+ auth->set_proxy(proxy); |
+ auth->set_firewall(login_settings_->firewall()); |
+ return auth; |
+} |
+ |
+void SingleLoginAttempt::OnFreshAuthCookie(const std::string& auth_cookie) { |
+ // Remember this is a fresh cookie |
+ cookie_refreshed_ = true; |
+ |
+ // TODO(sync): do the cookie logic (part of which is in the #if 0 below) |
+ |
+ // The following code is what PhoneWindow does for the equivalent method |
+#if 0 |
+ // Save cookie |
+ AccountInfo current(account_history_.current()); |
+ current.set_auth_cookie(auth_cookie); |
+ account_history_.set_current(current); |
+ |
+ // Calc next time to refresh cookie, between 5 and 10 days |
+ // The cookie has 14 days of life; this gives at least 4 days of retries |
+ // before the current cookie expires, maximizing the chance of |
+ // having a valid cookie next time the connection servers go down. |
+ FTULL now; |
+ |
+ // NOTE: The following line is win32. Address this when implementing this |
+ // code (doing "the cookie logic") |
+ GetSystemTimeAsFileTime(&(now.ft)); |
+ ULONGLONG five_days = (ULONGLONG)10000 * 1000 * 60 * 60 * 24 * 5; // 5 days |
+ ULONGLONG random = (ULONGLONG)10000 * // get to 100 ns units |
+ ((rand() % (5 * 24 * 60)) * (60 * 1000) + // random min. in 5 day period |
+ (rand() % 1000) * 60); // random 1/1000th of a minute |
+ next_cookie_refresh_ = now.ull + five_days + random; // 5-10 days |
+#endif |
+} |
+ |
+void SingleLoginAttempt::DiagnoseConnectionError() { |
+ switch (code_) { |
+ case buzz::XmppEngine::ERROR_MISSING_USERNAME: |
+ case buzz::XmppEngine::ERROR_NETWORK_TIMEOUT: |
+ case buzz::XmppEngine::ERROR_DOCUMENT_CLOSED: |
+ case buzz::XmppEngine::ERROR_BIND: |
+ case buzz::XmppEngine::ERROR_AUTH: |
+ case buzz::XmppEngine::ERROR_TLS: |
+ case buzz::XmppEngine::ERROR_UNAUTHORIZED: |
+ case buzz::XmppEngine::ERROR_VERSION: |
+ case buzz::XmppEngine::ERROR_STREAM: |
+ case buzz::XmppEngine::ERROR_XML: |
+ case buzz::XmppEngine::ERROR_NONE: |
+ default: { |
+ LoginFailure failure(LoginFailure::XMPP_ERROR, code_, subcode_); |
+ SignalLoginFailure(failure); |
+ return; |
+ } |
+ |
+ // The following errors require diagnosistics: |
+ // * spurious close of connection |
+ // * socket errors after auth |
+ case buzz::XmppEngine::ERROR_CONNECTION_CLOSED: |
+ case buzz::XmppEngine::ERROR_SOCKET: |
+ break; |
+ } |
+ |
+ talk_base::AsyncHttpRequest *http_request = |
+ new talk_base::AsyncHttpRequest(GetUserAgentString()); |
+ http_request->set_host("www.google.com"); |
+ http_request->set_port(80); |
+ http_request->set_secure(false); |
+ http_request->request().path = "/"; |
+ http_request->request().verb = talk_base::HV_GET; |
+ |
+ talk_base::ProxyInfo proxy; |
+ ASSERT(connection_generator_.get() != NULL); |
+ if (connection_generator_.get()) { |
+ proxy = connection_generator_->proxy(); |
+ } |
+ http_request->set_proxy(proxy); |
+ http_request->set_firewall(login_settings_->firewall()); |
+ |
+ http_request->SignalWorkDone.connect(this, |
+ &SingleLoginAttempt::OnHttpTestDone); |
+ http_request->Start(); |
+ http_request->Release(); |
+} |
+ |
+void SingleLoginAttempt::OnHttpTestDone(talk_base::SignalThread* thread) { |
+ ASSERT(thread != NULL); |
+ |
+ talk_base::AsyncHttpRequest* request = |
+ static_cast<talk_base::AsyncHttpRequest*>(thread); |
+ |
+ if (request->response().scode == 200) { |
+ // We were able to do an HTTP GET of www.google.com:80 |
+ |
+ // |
+ // The original error should be reported |
+ // |
+ LoginFailure failure(LoginFailure::XMPP_ERROR, code_, subcode_); |
+ SignalLoginFailure(failure); |
+ return; |
+ } |
+ |
+ // Otherwise lets transmute the error into ERROR_SOCKET, and put |
+ // the subcode as an indicator of what we think the problem |
+ // might be. |
+ |
+#if 0 |
+ // TODO(sync): determine if notifier has an analogous situation |
+ |
+ // |
+ // We weren't able to do an HTTP GET of www.google.com:80 |
+ // |
+ GAutoupdater::Version version_logged_in(g_options.version_logged_in()); |
+ GAutoupdater::Version version_installed(GetProductVersion().c_str()); |
+ if (version_logged_in < version_installed) { |
+ // |
+ // Google Talk has been updated and can no longer connect |
+ // to the Google Talk Service. Your firewall is probably |
+ // not allowing the new version of Google Talk to connect |
+ // to the internet. Please adjust your firewall settings |
+ // to allow the new version of Google Talk to connect to |
+ // the internet. |
+ // |
+ // We'll use the "error=1" to help figure this out for now |
+ // |
+ LoginFailure failure(LoginFailure::XMPP_ERROR, |
+ buzz::XmppEngine::ERROR_SOCKET, |
+ 1); |
+ SignalLoginFailure(failure); |
+ return; |
+ } |
+#endif |
+ |
+ // |
+ // Any other checking we can add here? |
+ // |
+ |
+ // |
+ // Google Talk is unable to use your internet connection. Either your |
+ // network isn't configured or Google Talk is being blocked by |
+ // a local firewall. |
+ // |
+ // We'll use the "error=0" to help figure this out for now |
+ // |
+ LoginFailure failure(LoginFailure::XMPP_ERROR, |
+ buzz::XmppEngine::ERROR_SOCKET, |
+ 0); |
+ SignalLoginFailure(failure); |
+} |
+ |
+void SingleLoginAttempt::OnClientStateChange(buzz::XmppEngine::State state) { |
+ if (state_ == state) |
+ return; |
+ |
+ buzz::XmppEngine::State previous_state = state_; |
+ state_ = state; |
+ |
+ switch (state) { |
+ case buzz::XmppEngine::STATE_NONE: |
+ case buzz::XmppEngine::STATE_START: |
+ case buzz::XmppEngine::STATE_OPENING: |
+ // do nothing |
+ break; |
+ case buzz::XmppEngine::STATE_OPEN: |
+ successful_connection_ = true; |
+ break; |
+ case buzz::XmppEngine::STATE_CLOSED: |
+ OnClientStateChangeClosed(previous_state); |
+ break; |
+ } |
+ SignalClientStateChange(state); |
+ if (state_ == buzz::XmppEngine::STATE_CLOSED) { |
+ OnClientStateChange(buzz::XmppEngine::STATE_NONE); |
+ } |
+} |
+ |
+void SingleLoginAttempt::ClearClient() { |
+ if (client_ != NULL) { |
+ client_->Disconnect(); |
+ |
+ // If this assertion goes off, it means that the disconnect didn't occur |
+ // properly. See SingleLoginAttempt::OnClientStateChange, |
+ // case XmppEngine::STATE_CLOSED |
+ ASSERT(client_ == NULL); |
+ } |
+} |
+ |
+void SingleLoginAttempt::OnClientStateChangeClosed( |
+ buzz::XmppEngine::State previous_state) { |
+ buzz::XmppEngine::Error error = buzz::XmppEngine::ERROR_NONE; |
+ int error_subcode = 0; |
+ buzz::CaptchaChallenge captcha_challenge; |
+ buzz::XmlElement* stream_error_ptr; |
+ GetClientErrorInformation(client_, |
+ &error, |
+ &error_subcode, |
+ &stream_error_ptr, |
+ &captcha_challenge); |
+ scoped_ptr<buzz::XmlElement> stream_error(stream_error_ptr); |
+ |
+ client_->SignalStateChange.disconnect(this); |
+ client_ = NULL; |
+ |
+ if (error == buzz::XmppEngine::ERROR_NONE) { |
+ SignalLogoff(); |
+ return; |
+ } else if (previous_state == buzz::XmppEngine::STATE_OPEN) { |
+ // Handler should attempt reconnect |
+ SignalUnexpectedDisconnect(); |
+ return; |
+ } else { |
+ HandleConnectionError(error, error_subcode, stream_error.get(), |
+ captcha_challenge); |
+ } |
+} |
+ |
+void SingleLoginAttempt::HandleConnectionPasswordError( |
+ const buzz::CaptchaChallenge& captcha_challenge) { |
+ LOG(LS_VERBOSE) << "SingleLoginAttempt::HandleConnectionPasswordError"; |
+ |
+ // Clear the auth cookie |
+ std::string current_auth_cookie = |
+ login_settings_->user_settings().auth_cookie(); |
+ login_settings_->modifiable_user_settings()->set_auth_cookie(""); |
+ // If there was an auth cookie and it was the same as the last |
+ // auth cookie, then it is a stale cookie. Retry login. |
+ if (!current_auth_cookie.empty() && !cookie_refreshed_) { |
+ UseCurrentConnection(); |
+ return; |
+ } |
+ |
+ LoginFailure failure(LoginFailure::XMPP_ERROR, code_, subcode_, |
+ captcha_challenge); |
+ SignalLoginFailure(failure); |
+} |
+ |
+void SingleLoginAttempt::HandleConnectionError( |
+ buzz::XmppEngine::Error code, |
+ int subcode, |
+ const buzz::XmlElement* stream_error, |
+ const buzz::CaptchaChallenge& captcha_challenge) { |
+ LOG_F(LS_VERBOSE) << "(" << code << ", " << subcode << ")"; |
+ |
+ // Save off the error code information, so we can use it |
+ // to tell the user what went wrong if all else fails |
+ code_ = code; |
+ subcode_ = subcode; |
+ if ((code_ == buzz::XmppEngine::ERROR_UNAUTHORIZED) || |
+ (code_ == buzz::XmppEngine::ERROR_MISSING_USERNAME)) { |
+ // There was a problem with credentials (username/password) |
+ HandleConnectionPasswordError(captcha_challenge); |
+ return; |
+ } |
+ |
+ // Unexpected disconnect, |
+ // Unreachable host, |
+ // Or internal server binding error - |
+ // All these are temporary problems, so continue reconnecting |
+ |
+ // GaiaAuth signals this directly via SignalCertificateExpired, but |
+ // SChannelAdapter propagates the error through SocketWindow as a socket |
+ // error. |
+ if (code_ == buzz::XmppEngine::ERROR_SOCKET && |
+ subcode_ == SEC_E_CERT_EXPIRED) { |
+ certificate_expired_ = true; |
+ } |
+ |
+ login_settings_->modifiable_user_settings()->set_resource(""); |
+ |
+ // Look for stream::error server redirection stanza "see-other-host" |
+ if (stream_error) { |
+ const buzz::XmlElement* other = |
+ stream_error->FirstNamed(buzz::QN_XSTREAM_SEE_OTHER_HOST); |
+ if (other) { |
+ const buzz::XmlElement* text = |
+ stream_error->FirstNamed(buzz::QN_XSTREAM_TEXT); |
+ if (text) { |
+ // Yep, its a "stream:error" with "see-other-host" text, let's |
+ // parse out the server:port, and then reconnect with that. |
+ const std::string& redirect = text->BodyText(); |
+ unsigned int colon = redirect.find(":"); |
+ int redirect_port = kDefaultXmppPort; |
+ std::string redirect_server; |
+ if (colon == std::string::npos) { |
+ redirect_server = redirect; |
+ } else { |
+ redirect_server = redirect.substr(0, colon); |
+ const std::string& port_text = redirect.substr(colon + 1); |
+ std::istringstream ist(port_text); |
+ ist >> redirect_port; |
+ } |
+ // we never allow a redirect to port 0 |
+ if (redirect_port == 0) { |
+ redirect_port = kDefaultXmppPort; |
+ } |
+ SignalRedirect(redirect_server, redirect_port); |
+ // may be deleted at this point |
+ return; |
+ } |
+ } |
+ } |
+ |
+ ASSERT(connection_generator_.get() != NULL); |
+ if (!connection_generator_.get()) { |
+ return; |
+ } |
+ |
+ // Iterate to the next possible connection (still trying to connect) |
+ UseNextConnection(); |
+} |
+} // namespace notifier |
Property changes on: chrome\browser\sync\notifier\communicator\single_login_attempt.cc |
___________________________________________________________________ |
Added: svn:eol-style |
+ LF |