diff options
author | willchan@chromium.org <willchan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-09-10 16:53:42 +0000 |
---|---|---|
committer | willchan@chromium.org <willchan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-09-10 16:53:42 +0000 |
commit | ba62fbd428232eee1815d4b4d63fc65cb3ead566 (patch) | |
tree | 56d05f663fe81afc7c7f6c232d1f650994bbe8ed /jingle | |
parent | a538b1cdbd69c307ef672b5176c8ac9039981c41 (diff) | |
download | chromium_src-ba62fbd428232eee1815d4b4d63fc65cb3ead566.zip chromium_src-ba62fbd428232eee1815d4b4d63fc65cb3ead566.tar.gz chromium_src-ba62fbd428232eee1815d4b4d63fc65cb3ead566.tar.bz2 |
Revert r59012 which started using Chrome sockets for sync.
This caused us to write to ChromeNetLog from the sync thread. ChromeNetLog is not thread safe, so this causes problems.
BUG=55116,54146
TEST=Start up chrome. Does not repeatedly hit DCHECKs on ChromeNetLog::AddEntry().
Review URL: http://codereview.chromium.org/3358028
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@59104 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'jingle')
19 files changed, 1558 insertions, 26 deletions
diff --git a/jingle/jingle.gyp b/jingle/jingle.gyp index 79ee350..2f01fff 100644 --- a/jingle/jingle.gyp +++ b/jingle/jingle.gyp @@ -17,11 +17,15 @@ 'sources': [ 'notifier/base/chrome_async_socket.cc', 'notifier/base/chrome_async_socket.h', + 'notifier/base/signal_thread_task.h', + 'notifier/base/ssl_adapter.h', + 'notifier/base/ssl_adapter.cc', 'notifier/base/fake_ssl_client_socket.cc', 'notifier/base/fake_ssl_client_socket.h', 'notifier/base/notification_method.h', 'notifier/base/notification_method.cc', 'notifier/base/notifier_options.h', + 'notifier/base/static_assert.h', 'notifier/base/task_pump.cc', 'notifier/base/task_pump.h', 'notifier/base/xmpp_client_socket_factory.cc', @@ -40,10 +44,16 @@ 'notifier/communicator/login_failure.h', 'notifier/communicator/login_settings.cc', 'notifier/communicator/login_settings.h', + 'notifier/communicator/product_info.cc', + 'notifier/communicator/product_info.h', 'notifier/communicator/single_login_attempt.cc', 'notifier/communicator/single_login_attempt.h', + 'notifier/communicator/ssl_socket_adapter.cc', + 'notifier/communicator/ssl_socket_adapter.h', 'notifier/communicator/xmpp_connection_generator.cc', 'notifier/communicator/xmpp_connection_generator.h', + 'notifier/communicator/xmpp_socket_adapter.cc', + 'notifier/communicator/xmpp_socket_adapter.h', 'notifier/listener/listen_task.cc', 'notifier/listener/listen_task.h', 'notifier/listener/mediator_thread.h', @@ -67,6 +77,7 @@ 'defines' : [ '_CRT_SECURE_NO_WARNINGS', '_USE_32BIT_TIME_T', + 'kXmppProductName="chromium-sync"', ], 'dependencies': [ '../base/base.gyp:base', diff --git a/jingle/notifier/base/notifier_options.h b/jingle/notifier/base/notifier_options.h index 289b7d8..32374cf 100644 --- a/jingle/notifier/base/notifier_options.h +++ b/jingle/notifier/base/notifier_options.h @@ -12,16 +12,23 @@ namespace notifier { struct NotifierOptions { NotifierOptions() - : try_ssltcp_first(false), + : use_chrome_async_socket(true), + try_ssltcp_first(false), notification_method(kDefaultNotificationMethod) {} - NotifierOptions(const bool try_ssltcp_first, + NotifierOptions(const bool use_chrome_async_socket, + const bool try_ssltcp_first, const net::HostPortPair& xmpp_host_port, NotificationMethod notification_method) - : try_ssltcp_first(try_ssltcp_first), + : use_chrome_async_socket(use_chrome_async_socket), + try_ssltcp_first(try_ssltcp_first), xmpp_host_port(xmpp_host_port), notification_method(notification_method) {} + // Indicates whether to use the chrome-socket-based buzz::AsyncSocket + // implementation for notifications. + bool use_chrome_async_socket; + // Indicates that the SSLTCP port (443) is to be tried before the the XMPP // port (5222) during login. bool try_ssltcp_first; diff --git a/jingle/notifier/base/signal_thread_task.h b/jingle/notifier/base/signal_thread_task.h new file mode 100644 index 0000000..8a6ff27 --- /dev/null +++ b/jingle/notifier/base/signal_thread_task.h @@ -0,0 +1,96 @@ +// 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. + +#ifndef JINGLE_NOTIFIER_BASE_SIGNAL_THREAD_TASK_H_ +#define JINGLE_NOTIFIER_BASE_SIGNAL_THREAD_TASK_H_ + +#include "base/logging.h" +#include "talk/base/common.h" +#include "talk/base/signalthread.h" +#include "talk/base/sigslot.h" +#include "talk/base/task.h" + +namespace notifier { + +template<class T> +class SignalThreadTask : public talk_base::Task, + public sigslot::has_slots<> { + public: + // Takes ownership of signal_thread. + SignalThreadTask(talk_base::Task* task_parent, T** signal_thread) + : talk_base::Task(task_parent), + signal_thread_(NULL), + finished_(false) { + SetSignalThread(signal_thread); + } + + virtual ~SignalThreadTask() { + ClearSignalThread(); + } + + virtual void Stop() { + Task::Stop(); + ClearSignalThread(); + } + + virtual int ProcessStart() { + DCHECK_EQ(GetState(), talk_base::Task::STATE_START); + signal_thread_->SignalWorkDone.connect( + this, + &SignalThreadTask<T>::OnWorkDone); + signal_thread_->Start(); + return talk_base::Task::STATE_RESPONSE; + } + + int ProcessResponse() { + if (!finished_) { + return talk_base::Task::STATE_BLOCKED; + } + SignalWorkDone(signal_thread_); + ClearSignalThread(); + return talk_base::Task::STATE_DONE; + } + + sigslot::signal1<T*> SignalWorkDone; + + private: + // Takes ownership of signal_thread. + void SetSignalThread(T** signal_thread) { + DCHECK(!signal_thread_); + DCHECK(signal_thread); + DCHECK(*signal_thread); + // No one should be listening to the signal thread for work done. + // They should be using this class instead. Unfortunately, we + // can't verify this. + + signal_thread_ = *signal_thread; + + // Helps callers not to use signal thread after this point since this class + // has taken ownership (and avoid the error of doing + // signal_thread->Start()). + *signal_thread = NULL; + } + + void OnWorkDone(talk_base::SignalThread* signal_thread) { + DCHECK_EQ(signal_thread, signal_thread_); + finished_ = true; + Wake(); + } + + void ClearSignalThread() { + if (signal_thread_) { + // Don't wait on the thread destruction, or we may deadlock. + signal_thread_->Destroy(false); + signal_thread_ = NULL; + } + } + + T* signal_thread_; + bool finished_; + DISALLOW_COPY_AND_ASSIGN(SignalThreadTask); +}; + +} // namespace notifier + +#endif // JINGLE_NOTIFIER_BASE_SIGNAL_THREAD_TASK_H_ diff --git a/jingle/notifier/base/ssl_adapter.cc b/jingle/notifier/base/ssl_adapter.cc new file mode 100644 index 0000000..2111244 --- /dev/null +++ b/jingle/notifier/base/ssl_adapter.cc @@ -0,0 +1,27 @@ +// 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 "jingle/notifier/base/ssl_adapter.h" + +#if defined(OS_WIN) +#include "talk/base/ssladapter.h" +#else +#include "jingle/notifier/communicator/ssl_socket_adapter.h" +#endif + +namespace notifier { + +talk_base::SSLAdapter* CreateSSLAdapter(talk_base::AsyncSocket* socket) { + talk_base::SSLAdapter* ssl_adapter = +#if defined(OS_WIN) + talk_base::SSLAdapter::Create(socket); +#else + notifier::SSLSocketAdapter::Create(socket); +#endif + DCHECK(ssl_adapter); + return ssl_adapter; +} + +} // namespace notifier + diff --git a/jingle/notifier/base/ssl_adapter.h b/jingle/notifier/base/ssl_adapter.h new file mode 100644 index 0000000..32517cd --- /dev/null +++ b/jingle/notifier/base/ssl_adapter.h @@ -0,0 +1,33 @@ +// 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. + +#ifndef JINGLE_NOTIFIER_BASE_SSL_ADAPTER_H_ +#define JINGLE_NOTIFIER_BASE_SSL_ADAPTER_H_ + +namespace talk_base { +class AsyncSocket; +class SSLAdapter; +} // namespace talk_base + +namespace notifier { + +// Wraps the given socket in a platform-dependent SSLAdapter +// implementation. +talk_base::SSLAdapter* CreateSSLAdapter(talk_base::AsyncSocket* socket); + +// Utility template class that overrides CreateSSLAdapter() to use the +// above function. +template <class SocketFactory> +class SSLAdapterSocketFactory : public SocketFactory { + public: + virtual talk_base::SSLAdapter* CreateSSLAdapter( + talk_base::AsyncSocket* socket) { + return ::notifier::CreateSSLAdapter(socket); + } +}; + +} // namespace notifier + +#endif // JINGLE_NOTIFIER_BASE_SSL_ADAPTER_H_ + diff --git a/jingle/notifier/base/static_assert.h b/jingle/notifier/base/static_assert.h new file mode 100644 index 0000000..58a8fe4 --- /dev/null +++ b/jingle/notifier/base/static_assert.h @@ -0,0 +1,21 @@ +// 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. + +#ifndef JINGLE_NOTIFIER_BASE_STATIC_ASSERT_H_ +#define JINGLE_NOTIFIER_BASE_STATIC_ASSERT_H_ + +template<bool> struct STATIC_ASSERTION_FAILURE; + +template<> struct STATIC_ASSERTION_FAILURE<true> { + enum { value = 1 }; +}; + +template<int> struct static_assert_test{}; + +#define STATIC_ASSERT(B) \ +typedef static_assert_test<\ + sizeof(STATIC_ASSERTION_FAILURE< (bool)( B ) >)>\ + static_assert_typedef_ ## __LINE__ + +#endif // JINGLE_NOTIFIER_BASE_STATIC_ASSERT_H_ diff --git a/jingle/notifier/communicator/login.cc b/jingle/notifier/communicator/login.cc index cd0cbbf..041bc67 100644 --- a/jingle/notifier/communicator/login.cc +++ b/jingle/notifier/communicator/login.cc @@ -11,6 +11,7 @@ #include "base/time.h" #include "jingle/notifier/communicator/connection_options.h" #include "jingle/notifier/communicator/login_settings.h" +#include "jingle/notifier/communicator/product_info.h" #include "jingle/notifier/communicator/single_login_attempt.h" #include "net/base/host_port_pair.h" #include "talk/base/common.h" @@ -31,6 +32,7 @@ namespace notifier { static const int kRedirectTimeoutMinutes = 5; Login::Login(talk_base::TaskParent* parent, + bool use_chrome_async_socket, const buzz::XmppClientSettings& user_settings, const ConnectionOptions& options, std::string lang, @@ -41,6 +43,7 @@ Login::Login(talk_base::TaskParent* parent, bool try_ssltcp_first, bool proxy_only) : parent_(parent), + use_chrome_async_socket_(use_chrome_async_socket), login_settings_(new LoginSettings(user_settings, options, lang, @@ -80,7 +83,9 @@ void Login::StartConnection() { LOG(INFO) << "Starting connection..."; single_attempt_ = new SingleLoginAttempt(parent_, - login_settings_.get()); + login_settings_.get(), + use_chrome_async_socket_, + true); // Do the signaling hook-ups. single_attempt_->SignalUnexpectedDisconnect.connect( diff --git a/jingle/notifier/communicator/login.h b/jingle/notifier/communicator/login.h index 27b2543..84e89a8 100644 --- a/jingle/notifier/communicator/login.h +++ b/jingle/notifier/communicator/login.h @@ -49,6 +49,7 @@ class Login : public net::NetworkChangeNotifier::Observer, public: // firewall may be NULL. Login(talk_base::TaskParent* parent, + bool use_chrome_async_socket, const buzz::XmppClientSettings& user_settings, const ConnectionOptions& options, std::string lang, @@ -97,6 +98,7 @@ class Login : public net::NetworkChangeNotifier::Observer, void DoReconnect(); talk_base::TaskParent* parent_; + bool use_chrome_async_socket_; scoped_ptr<LoginSettings> login_settings_; LoginConnectionState state_; SingleLoginAttempt* single_attempt_; diff --git a/jingle/notifier/communicator/product_info.cc b/jingle/notifier/communicator/product_info.cc new file mode 100644 index 0000000..c1deafb --- /dev/null +++ b/jingle/notifier/communicator/product_info.cc @@ -0,0 +1,15 @@ +// 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> + +namespace notifier { +std::string GetUserAgentString() { + return kXmppProductName; +} + +std::string GetProductSignature() { + return kXmppProductName; +} +} // namespace notifier diff --git a/jingle/notifier/communicator/product_info.h b/jingle/notifier/communicator/product_info.h new file mode 100644 index 0000000..9e8e5d0 --- /dev/null +++ b/jingle/notifier/communicator/product_info.h @@ -0,0 +1,15 @@ +// 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. + +#ifndef JINGLE_NOTIFIER_COMMUNICATOR_PRODUCT_INFO_H_ +#define JINGLE_NOTIFIER_COMMUNICATOR_PRODUCT_INFO_H_ + +#include <string> + +namespace notifier { +std::string GetUserAgentString(); +std::string GetProductSignature(); +} // namespace notifier + +#endif // JINGLE_NOTIFIER_COMMUNICATOR_PRODUCT_INFO_H_ diff --git a/jingle/notifier/communicator/single_login_attempt.cc b/jingle/notifier/communicator/single_login_attempt.cc index 4db7758..ccdf0ce 100644 --- a/jingle/notifier/communicator/single_login_attempt.cc +++ b/jingle/notifier/communicator/single_login_attempt.cc @@ -18,9 +18,16 @@ #include "jingle/notifier/communicator/gaia_token_pre_xmpp_auth.h" #include "jingle/notifier/communicator/login_failure.h" #include "jingle/notifier/communicator/login_settings.h" +#include "jingle/notifier/communicator/product_info.h" #include "jingle/notifier/communicator/xmpp_connection_generator.h" +#include "jingle/notifier/communicator/xmpp_socket_adapter.h" #include "net/base/ssl_config_service.h" #include "net/socket/client_socket_factory.h" +#include "talk/base/asynchttprequest.h" +#include "talk/base/firewallsocketserver.h" +#include "talk/base/signalthread.h" +#include "talk/base/taskrunner.h" +#include "talk/base/win32socketinit.h" #include "talk/xmllite/xmlelement.h" #include "talk/xmpp/xmppclient.h" #include "talk/xmpp/xmppclientsettings.h" @@ -54,15 +61,23 @@ static void GetClientErrorInformation( } SingleLoginAttempt::SingleLoginAttempt(talk_base::TaskParent* parent, - LoginSettings* login_settings) + LoginSettings* login_settings, + bool use_chrome_async_socket, + bool successful_connection) : talk_base::Task(parent), + use_chrome_async_socket_(use_chrome_async_socket), 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) { +#if defined(OS_WIN) + talk_base::EnsureWinsockInit(); +#endif connection_generator_.reset(new XmppConnectionGenerator( this, login_settings_->host_resolver(), @@ -139,7 +154,14 @@ void SingleLoginAttempt::OnAttemptedAllConnections( LOG(INFO) << "Connection failed with error " << code_; - SignalNeedAutoReconnect(); + // We were connected and we had a problem. + if (successful_connection_) { + SignalNeedAutoReconnect(); + // Expect to be deleted at this point. + return; + } + + DiagnoseConnectionError(); } void SingleLoginAttempt::UseNextConnection() { @@ -203,24 +225,39 @@ void SingleLoginAttempt::OnCertificateExpired() { buzz::AsyncSocket* SingleLoginAttempt::CreateSocket( const buzz::XmppClientSettings& xcs) { - bool use_fake_ssl_client_socket = - (xcs.protocol() == cricket::PROTO_SSLTCP); - net::ClientSocketFactory* const client_socket_factory = - new XmppClientSocketFactory( - net::ClientSocketFactory::GetDefaultFactory(), - use_fake_ssl_client_socket); - // The default SSLConfig is good enough for us for now. - const net::SSLConfig ssl_config; - // A read buffer of 64k ought to be sufficient. - const size_t kReadBufSize = 64U * 1024U; - // This number was taken from a similar number in - // XmppSocketAdapter. - const size_t kWriteBufSize = 64U * 1024U; - // TODO(akalin): Use a real NetLog. - net::NetLog* const net_log = NULL; - return new ChromeAsyncSocket( - client_socket_factory, ssl_config, - kReadBufSize, kWriteBufSize, net_log); + if (use_chrome_async_socket_) { + bool use_fake_ssl_client_socket = + (xcs.protocol() == cricket::PROTO_SSLTCP); + net::ClientSocketFactory* const client_socket_factory = + new XmppClientSocketFactory( + net::ClientSocketFactory::GetDefaultFactory(), + use_fake_ssl_client_socket); + // The default SSLConfig is good enough for us for now. + const net::SSLConfig ssl_config; + // A read buffer of 64k ought to be sufficient. + const size_t kReadBufSize = 64U * 1024U; + // This number was taken from a similar number in + // XmppSocketAdapter. + const size_t kWriteBufSize = 64U * 1024U; + // TODO(akalin): Use a real NetLog. + net::NetLog* const net_log = NULL; + return new ChromeAsyncSocket( + client_socket_factory, ssl_config, + kReadBufSize, kWriteBufSize, net_log); + } + // TODO(akalin): Always use ChromeAsyncSocket and get rid of this + // code. + 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( @@ -230,6 +267,147 @@ buzz::PreXmppAuth* SingleLoginAttempt::CreatePreXmppAuth( jid.Str(), xcs.auth_cookie(), xcs.token_service()); } +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; + DCHECK(connection_generator_.get()); + 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) { + DCHECK(thread); + + 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; @@ -241,9 +419,11 @@ void SingleLoginAttempt::OnClientStateChange(buzz::XmppEngine::State state) { case buzz::XmppEngine::STATE_NONE: case buzz::XmppEngine::STATE_START: case buzz::XmppEngine::STATE_OPENING: - case buzz::XmppEngine::STATE_OPEN: // Do nothing. break; + case buzz::XmppEngine::STATE_OPEN: + successful_connection_ = true; + break; case buzz::XmppEngine::STATE_CLOSED: OnClientStateChangeClosed(previous_state); break; @@ -319,6 +499,14 @@ void SingleLoginAttempt::HandleConnectionError( // 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". diff --git a/jingle/notifier/communicator/single_login_attempt.h b/jingle/notifier/communicator/single_login_attempt.h index 1b9900c..8b4f93b 100644 --- a/jingle/notifier/communicator/single_login_attempt.h +++ b/jingle/notifier/communicator/single_login_attempt.h @@ -47,7 +47,9 @@ class XmppConnectionGenerator; class SingleLoginAttempt : public talk_base::Task, public sigslot::has_slots<> { public: SingleLoginAttempt(talk_base::TaskParent* parent, - LoginSettings* login_settings); + LoginSettings* login_settings, + bool use_chrome_async_socket, + bool successful_connection); ~SingleLoginAttempt(); virtual int ProcessStart(); void UseNextConnection(); @@ -105,18 +107,25 @@ class SingleLoginAttempt : public talk_base::Task, public sigslot::has_slots<> { const buzz::XmlElement* stream_error); void HandleConnectionPasswordError(); + void DiagnoseConnectionError(); + void OnHttpTestDone(talk_base::SignalThread* thread); + void OnAuthenticationError(); void OnCertificateExpired(); + void OnFreshAuthCookie(const std::string& auth_cookie); void OnClientStateChange(buzz::XmppEngine::State state); void OnClientStateChangeClosed(buzz::XmppEngine::State previous_state); void OnAttemptedAllConnections(bool successfully_resolved_dns, int first_dns_error); + bool use_chrome_async_socket_; buzz::XmppEngine::State state_; buzz::XmppEngine::Error code_; int subcode_; bool need_authentication_; bool certificate_expired_; + bool cookie_refreshed_; + bool successful_connection_; LoginSettings* login_settings_; buzz::XmppClient* client_; scoped_ptr<XmppConnectionGenerator> connection_generator_; diff --git a/jingle/notifier/communicator/ssl_socket_adapter.cc b/jingle/notifier/communicator/ssl_socket_adapter.cc new file mode 100644 index 0000000..cac3041 --- /dev/null +++ b/jingle/notifier/communicator/ssl_socket_adapter.cc @@ -0,0 +1,386 @@ +// Copyright (c) 2010 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 "jingle/notifier/communicator/ssl_socket_adapter.h" + +#include "base/compiler_specific.h" +#include "base/message_loop.h" +#include "net/base/address_list.h" +#include "net/base/net_errors.h" +#include "net/base/ssl_config_service.h" +#include "net/base/sys_addrinfo.h" +#include "net/socket/client_socket_factory.h" +#include "net/url_request/url_request_context.h" + +namespace notifier { + +namespace { + +// Convert values from <errno.h> to values from "net/base/net_errors.h" +int MapPosixError(int err) { + // There are numerous posix error codes, but these are the ones we thus far + // find interesting. + switch (err) { + case EAGAIN: +#if EWOULDBLOCK != EAGAIN + case EWOULDBLOCK: +#endif + return net::ERR_IO_PENDING; + case ENETDOWN: + return net::ERR_INTERNET_DISCONNECTED; + case ETIMEDOUT: + return net::ERR_TIMED_OUT; + case ECONNRESET: + case ENETRESET: // Related to keep-alive + return net::ERR_CONNECTION_RESET; + case ECONNABORTED: + return net::ERR_CONNECTION_ABORTED; + case ECONNREFUSED: + return net::ERR_CONNECTION_REFUSED; + case EHOSTUNREACH: + case ENETUNREACH: + return net::ERR_ADDRESS_UNREACHABLE; + case EADDRNOTAVAIL: + return net::ERR_ADDRESS_INVALID; + case 0: + return net::OK; + default: + LOG(WARNING) << "Unknown error " << err << " mapped to net::ERR_FAILED"; + return net::ERR_FAILED; + } +} + +} // namespace + +SSLSocketAdapter* SSLSocketAdapter::Create(AsyncSocket* socket) { + return new SSLSocketAdapter(socket); +} + +SSLSocketAdapter::SSLSocketAdapter(AsyncSocket* socket) + : SSLAdapter(socket), + ignore_bad_cert_(false), + ALLOW_THIS_IN_INITIALIZER_LIST( + connected_callback_(this, &SSLSocketAdapter::OnConnected)), + ALLOW_THIS_IN_INITIALIZER_LIST( + read_callback_(this, &SSLSocketAdapter::OnRead)), + ALLOW_THIS_IN_INITIALIZER_LIST( + write_callback_(this, &SSLSocketAdapter::OnWrite)), + ssl_state_(SSLSTATE_NONE), + read_state_(IOSTATE_NONE), + write_state_(IOSTATE_NONE) { + transport_socket_ = new TransportSocket(socket, this); +} + +SSLSocketAdapter::~SSLSocketAdapter() { +} + +int SSLSocketAdapter::StartSSL(const char* hostname, bool restartable) { + DCHECK(!restartable); + hostname_ = hostname; + + if (socket_->GetState() != Socket::CS_CONNECTED) { + ssl_state_ = SSLSTATE_WAIT; + return 0; + } else { + return BeginSSL(); + } +} + +int SSLSocketAdapter::BeginSSL() { + if (!MessageLoop::current()) { + // Certificate verification is done via the Chrome message loop. + // Without this check, if we don't have a chrome message loop the + // SSL connection just hangs silently. + LOG(DFATAL) << "Chrome message loop (needed by SSL certificate " + << "verification) does not exist"; + return net::ERR_UNEXPECTED; + } + + // SSLConfigService is not thread-safe, and the default values for SSLConfig + // are correct for us, so we don't use the config service to initialize this + // object. + net::SSLConfig ssl_config; + transport_socket_->set_addr(talk_base::SocketAddress(hostname_, 0)); + ssl_socket_.reset( + net::ClientSocketFactory::GetDefaultFactory()->CreateSSLClientSocket( + transport_socket_, hostname_.c_str(), ssl_config)); + + int result = ssl_socket_->Connect(&connected_callback_); + + if (result == net::ERR_IO_PENDING || result == net::OK) { + return 0; + } else { + LOG(ERROR) << "Could not start SSL: " << net::ErrorToString(result); + return result; + } +} + +int SSLSocketAdapter::Send(const void* buf, size_t len) { + if (ssl_state_ != SSLSTATE_CONNECTED) { + return AsyncSocketAdapter::Send(buf, len); + } else { + scoped_refptr<net::IOBuffer> transport_buf = new net::IOBuffer(len); + memcpy(transport_buf->data(), buf, len); + + int result = ssl_socket_->Write(transport_buf, len, NULL); + if (result == net::ERR_IO_PENDING) { + SetError(EWOULDBLOCK); + } + transport_buf = NULL; + return result; + } +} + +int SSLSocketAdapter::Recv(void* buf, size_t len) { + switch (ssl_state_) { + case SSLSTATE_NONE: + return AsyncSocketAdapter::Recv(buf, len); + + case SSLSTATE_WAIT: + SetError(EWOULDBLOCK); + return -1; + + case SSLSTATE_CONNECTED: + switch (read_state_) { + case IOSTATE_NONE: { + transport_buf_ = new net::IOBuffer(len); + int result = ssl_socket_->Read(transport_buf_, len, &read_callback_); + if (result >= 0) { + memcpy(buf, transport_buf_->data(), len); + } + + if (result == net::ERR_IO_PENDING) { + read_state_ = IOSTATE_PENDING; + SetError(EWOULDBLOCK); + } else { + if (result < 0) { + SetError(result); + LOG(INFO) << "Socket error " << result; + } + transport_buf_ = NULL; + } + return result; + } + case IOSTATE_PENDING: + SetError(EWOULDBLOCK); + return -1; + + case IOSTATE_COMPLETE: + memcpy(buf, transport_buf_->data(), len); + transport_buf_ = NULL; + read_state_ = IOSTATE_NONE; + return data_transferred_; + } + } + + NOTREACHED(); + return -1; +} + +void SSLSocketAdapter::OnConnected(int result) { + if (result == net::OK) { + ssl_state_ = SSLSTATE_CONNECTED; + OnConnectEvent(this); + } else { + LOG(WARNING) << "OnConnected failed with error " << result; + } +} + +void SSLSocketAdapter::OnRead(int result) { + DCHECK(read_state_ == IOSTATE_PENDING); + read_state_ = IOSTATE_COMPLETE; + data_transferred_ = result; + AsyncSocketAdapter::OnReadEvent(this); +} + +void SSLSocketAdapter::OnWrite(int result) { + DCHECK(write_state_ == IOSTATE_PENDING); + write_state_ = IOSTATE_COMPLETE; + data_transferred_ = result; + AsyncSocketAdapter::OnWriteEvent(this); +} + +void SSLSocketAdapter::OnConnectEvent(talk_base::AsyncSocket* socket) { + if (ssl_state_ != SSLSTATE_WAIT) { + AsyncSocketAdapter::OnConnectEvent(socket); + } else { + ssl_state_ = SSLSTATE_NONE; + int result = BeginSSL(); + if (0 != result) { + // TODO(zork): Handle this case gracefully. + LOG(WARNING) << "BeginSSL() failed with " << result; + } + } +} + +TransportSocket::TransportSocket(talk_base::AsyncSocket* socket, + SSLSocketAdapter *ssl_adapter) + : read_callback_(NULL), + write_callback_(NULL), + read_buffer_len_(0), + write_buffer_len_(0), + socket_(socket), + was_used_to_convey_data_(false) { + socket_->SignalReadEvent.connect(this, &TransportSocket::OnReadEvent); + socket_->SignalWriteEvent.connect(this, &TransportSocket::OnWriteEvent); +} + +TransportSocket::~TransportSocket() { +} + +int TransportSocket::Connect(net::CompletionCallback* callback) { + // Connect is never called by SSLClientSocket, instead SSLSocketAdapter + // calls Connect() on socket_ directly. + NOTREACHED(); + return false; +} + +void TransportSocket::Disconnect() { + socket_->Close(); +} + +bool TransportSocket::IsConnected() const { + return (socket_->GetState() == talk_base::Socket::CS_CONNECTED); +} + +bool TransportSocket::IsConnectedAndIdle() const { + // Not implemented. + NOTREACHED(); + return false; +} + +int TransportSocket::GetPeerAddress(net::AddressList* address) const { + talk_base::SocketAddress socket_address = socket_->GetRemoteAddress(); + + // libjingle supports only IPv4 addresses. + sockaddr_in ipv4addr; + socket_address.ToSockAddr(&ipv4addr); + + struct addrinfo ai; + memset(&ai, 0, sizeof(ai)); + ai.ai_family = ipv4addr.sin_family; + ai.ai_socktype = SOCK_STREAM; + ai.ai_protocol = IPPROTO_TCP; + ai.ai_addr = reinterpret_cast<struct sockaddr*>(&ipv4addr); + ai.ai_addrlen = sizeof(ipv4addr); + + address->Copy(&ai, false); + return net::OK; +} + +void TransportSocket::SetSubresourceSpeculation() { + NOTREACHED(); +} + +void TransportSocket::SetOmniboxSpeculation() { + NOTREACHED(); +} + +bool TransportSocket::WasEverUsed() const { + // We don't use this in ClientSocketPools, so this should never be used. + NOTREACHED(); + return was_used_to_convey_data_; +} + +int TransportSocket::Read(net::IOBuffer* buf, int buf_len, + net::CompletionCallback* callback) { + DCHECK(buf); + DCHECK(!read_callback_); + DCHECK(!read_buffer_.get()); + int result = socket_->Recv(buf->data(), buf_len); + if (result < 0) { + result = MapPosixError(socket_->GetError()); + if (result == net::ERR_IO_PENDING) { + read_callback_ = callback; + read_buffer_ = buf; + read_buffer_len_ = buf_len; + } + } + if (result != net::ERR_IO_PENDING) + was_used_to_convey_data_ = true; + return result; +} + +int TransportSocket::Write(net::IOBuffer* buf, int buf_len, + net::CompletionCallback* callback) { + DCHECK(buf); + DCHECK(!write_callback_); + DCHECK(!write_buffer_.get()); + int result = socket_->Send(buf->data(), buf_len); + if (result < 0) { + result = MapPosixError(socket_->GetError()); + if (result == net::ERR_IO_PENDING) { + write_callback_ = callback; + write_buffer_ = buf; + write_buffer_len_ = buf_len; + } + } + if (result != net::ERR_IO_PENDING) + was_used_to_convey_data_ = true; + return result; +} + +bool TransportSocket::SetReceiveBufferSize(int32 size) { + // Not implemented. + return false; +} + +bool TransportSocket::SetSendBufferSize(int32 size) { + // Not implemented. + return false; +} + +void TransportSocket::OnReadEvent(talk_base::AsyncSocket* socket) { + if (read_callback_) { + DCHECK(read_buffer_.get()); + net::CompletionCallback* callback = read_callback_; + scoped_refptr<net::IOBuffer> buffer = read_buffer_; + int buffer_len = read_buffer_len_; + + read_callback_ = NULL; + read_buffer_ = NULL; + read_buffer_len_ = 0; + + int result = socket_->Recv(buffer->data(), buffer_len); + if (result < 0) { + result = MapPosixError(socket_->GetError()); + if (result == net::ERR_IO_PENDING) { + read_callback_ = callback; + read_buffer_ = buffer; + read_buffer_len_ = buffer_len; + return; + } + } + was_used_to_convey_data_ = true; + callback->RunWithParams(Tuple1<int>(result)); + } +} + +void TransportSocket::OnWriteEvent(talk_base::AsyncSocket* socket) { + if (write_callback_) { + DCHECK(write_buffer_.get()); + net::CompletionCallback* callback = write_callback_; + scoped_refptr<net::IOBuffer> buffer = write_buffer_; + int buffer_len = write_buffer_len_; + + write_callback_ = NULL; + write_buffer_ = NULL; + write_buffer_len_ = 0; + + int result = socket_->Send(buffer->data(), buffer_len); + if (result < 0) { + result = MapPosixError(socket_->GetError()); + if (result == net::ERR_IO_PENDING) { + write_callback_ = callback; + write_buffer_ = buffer; + write_buffer_len_ = buffer_len; + return; + } + } + was_used_to_convey_data_ = true; + callback->RunWithParams(Tuple1<int>(result)); + } +} + +} // namespace notifier diff --git a/jingle/notifier/communicator/ssl_socket_adapter.h b/jingle/notifier/communicator/ssl_socket_adapter.h new file mode 100644 index 0000000..cdbc94e --- /dev/null +++ b/jingle/notifier/communicator/ssl_socket_adapter.h @@ -0,0 +1,145 @@ +// Copyright (c) 2010 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. + +#ifndef JINGLE_NOTIFIER_COMMUNICATOR_SSL_SOCKET_ADAPTER_H_ +#define JINGLE_NOTIFIER_COMMUNICATOR_SSL_SOCKET_ADAPTER_H_ + +#include "base/scoped_ptr.h" +#include "net/base/completion_callback.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" +#include "net/base/net_log.h" +#include "net/socket/client_socket.h" +#include "net/socket/ssl_client_socket.h" +#include "talk/base/asyncsocket.h" +#include "talk/base/ssladapter.h" + +namespace notifier { + +class SSLSocketAdapter; + +// TODO(sergeyu): Write unittests for this code! + +// This class provides a wrapper to libjingle's talk_base::AsyncSocket that +// implements Chromium's net::ClientSocket interface. It's used by +// SSLSocketAdapter to enable Chromium's SSL implementation to work over +// libjingle's socket class. +class TransportSocket : public net::ClientSocket, public sigslot::has_slots<> { + public: + TransportSocket(talk_base::AsyncSocket* socket, + SSLSocketAdapter *ssl_adapter); + ~TransportSocket(); + + void set_addr(const talk_base::SocketAddress& addr) { + addr_ = addr; + } + + // net::ClientSocket implementation + + virtual int Connect(net::CompletionCallback* callback); + virtual void Disconnect(); + virtual bool IsConnected() const; + virtual bool IsConnectedAndIdle() const; + virtual int GetPeerAddress(net::AddressList* address) const; + virtual const net::BoundNetLog& NetLog() const { return net_log_; } + virtual void SetSubresourceSpeculation(); + virtual void SetOmniboxSpeculation(); + virtual bool WasEverUsed() const; + + // net::Socket implementation + + virtual int Read(net::IOBuffer* buf, int buf_len, + net::CompletionCallback* callback); + virtual int Write(net::IOBuffer* buf, int buf_len, + net::CompletionCallback* callback); + virtual bool SetReceiveBufferSize(int32 size); + virtual bool SetSendBufferSize(int32 size); + + private: + friend class SSLSocketAdapter; + + void OnReadEvent(talk_base::AsyncSocket* socket); + void OnWriteEvent(talk_base::AsyncSocket* socket); + + net::CompletionCallback* read_callback_; + net::CompletionCallback* write_callback_; + + scoped_refptr<net::IOBuffer> read_buffer_; + int read_buffer_len_; + scoped_refptr<net::IOBuffer> write_buffer_; + int write_buffer_len_; + + net::BoundNetLog net_log_; + + talk_base::AsyncSocket *socket_; + talk_base::SocketAddress addr_; + + bool was_used_to_convey_data_; + + DISALLOW_COPY_AND_ASSIGN(TransportSocket); +}; + +// This provides a talk_base::AsyncSocketAdapter interface around Chromium's +// net::SSLClientSocket class. This allows notifier to use Chromium's SSL +// implementation instead of OpenSSL. +class SSLSocketAdapter : public talk_base::SSLAdapter { + public: + explicit SSLSocketAdapter(talk_base::AsyncSocket* socket); + ~SSLSocketAdapter(); + + // StartSSL returns 0 if successful, or non-zero on failure. + // If StartSSL is called while the socket is closed or connecting, the SSL + // negotiation will begin as soon as the socket connects. + // + // restartable is not implemented, and must be set to false. + virtual int StartSSL(const char* hostname, bool restartable); + + // Create the default SSL adapter for this platform. + static SSLSocketAdapter* Create(AsyncSocket* socket); + + virtual int Send(const void* pv, size_t cb); + virtual int Recv(void* pv, size_t cb); + + private: + friend class TransportSocket; + + enum SSLState { + SSLSTATE_NONE, + SSLSTATE_WAIT, + SSLSTATE_CONNECTED, + }; + + enum IOState { + IOSTATE_NONE, + IOSTATE_PENDING, + IOSTATE_COMPLETE, + }; + + void OnConnected(int result); + void OnRead(int result); + void OnWrite(int result); + + virtual void OnConnectEvent(talk_base::AsyncSocket* socket); + + int BeginSSL(); + + bool ignore_bad_cert_; + std::string hostname_; + TransportSocket* transport_socket_; + scoped_ptr<net::SSLClientSocket> ssl_socket_; + net::CompletionCallbackImpl<SSLSocketAdapter> connected_callback_; + net::CompletionCallbackImpl<SSLSocketAdapter> read_callback_; + net::CompletionCallbackImpl<SSLSocketAdapter> write_callback_; + SSLState ssl_state_; + IOState read_state_; + IOState write_state_; + scoped_refptr<net::IOBuffer> transport_buf_; + int data_transferred_; + + DISALLOW_COPY_AND_ASSIGN(SSLSocketAdapter); +}; + +} // namespace notifier + +#endif // JINGLE_NOTIFIER_COMMUNICATOR_SSL_SOCKET_ADAPTER_H_ diff --git a/jingle/notifier/communicator/xmpp_connection_generator.cc b/jingle/notifier/communicator/xmpp_connection_generator.cc index 25b131c..ad551d2 100644 --- a/jingle/notifier/communicator/xmpp_connection_generator.cc +++ b/jingle/notifier/communicator/xmpp_connection_generator.cc @@ -23,8 +23,10 @@ #include "base/callback.h" #include "base/compiler_specific.h" #include "base/logging.h" +#include "jingle/notifier/base/signal_thread_task.h" #include "jingle/notifier/communicator/connection_options.h" #include "jingle/notifier/communicator/connection_settings.h" +#include "jingle/notifier/communicator/product_info.h" #include "net/base/net_errors.h" #include "net/base/sys_addrinfo.h" #include "talk/base/httpcommon-inl.h" diff --git a/jingle/notifier/communicator/xmpp_socket_adapter.cc b/jingle/notifier/communicator/xmpp_socket_adapter.cc new file mode 100644 index 0000000..7d7a2e9 --- /dev/null +++ b/jingle/notifier/communicator/xmpp_socket_adapter.cc @@ -0,0 +1,429 @@ +// 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 "jingle/notifier/communicator/xmpp_socket_adapter.h" + +#include <iomanip> +#include <string> + +#include "base/logging.h" +#include "jingle/notifier/base/ssl_adapter.h" +#include "jingle/notifier/communicator/product_info.h" +#include "talk/base/byteorder.h" +#include "talk/base/common.h" +#include "talk/base/firewallsocketserver.h" +#include "talk/base/logging.h" +#include "talk/base/socketadapters.h" +#include "talk/base/ssladapter.h" +#include "talk/base/thread.h" +#include "talk/xmpp/xmppengine.h" + +namespace notifier { + +XmppSocketAdapter::XmppSocketAdapter(const buzz::XmppClientSettings& xcs, + bool allow_unverified_certs) + : state_(STATE_CLOSED), + error_(ERROR_NONE), + wsa_error_(0), + socket_(NULL), + protocol_(xcs.protocol()), + firewall_(false), + write_buffer_(NULL), + write_buffer_length_(0), + write_buffer_capacity_(0), + allow_unverified_certs_(allow_unverified_certs) { + proxy_.type = xcs.proxy(); + proxy_.address.SetIP(xcs.proxy_host()); + proxy_.address.SetPort(xcs.proxy_port()); + proxy_.username = xcs.proxy_user(); + proxy_.password = xcs.proxy_pass(); +} + +XmppSocketAdapter::~XmppSocketAdapter() { + FreeState(); + + // Clean up any previous socket - cannot delete socket on close because close + // happens during the child socket's stack callback. + if (socket_) { + delete socket_; + socket_ = NULL; + } +} + +bool XmppSocketAdapter::FreeState() { + int code = 0; + + // Clean up the socket. + if (socket_ && !(state_ == STATE_CLOSED || state_ == STATE_CLOSING)) { + code = socket_->Close(); + } + + delete[] write_buffer_; + write_buffer_ = NULL; + write_buffer_length_ = 0; + write_buffer_capacity_ = 0; + + if (code) { + SetWSAError(code); + return false; + } + return true; +} + +bool XmppSocketAdapter::Connect(const talk_base::SocketAddress& addr) { + if (state_ != STATE_CLOSED) { + SetError(ERROR_WRONGSTATE); + return false; + } + + LOG(INFO) << "XmppSocketAdapter::Connect(" << addr.ToString() << ")"; + + // Clean up any previous socket - cannot delete socket on close because close + // happens during the child socket's stack callback. + if (socket_) { + delete socket_; + socket_ = NULL; + } + + talk_base::AsyncSocket* socket = + talk_base::Thread::Current()->socketserver()->CreateAsyncSocket( + SOCK_STREAM); + if (!socket) { + SetWSAError(WSA_NOT_ENOUGH_MEMORY); + return false; + } + + if (firewall_) { + // TODO(sync): Change this to make WSAAsyncSockets support current thread + // socket server. + talk_base::FirewallSocketServer* fw = + static_cast<talk_base::FirewallSocketServer*>( + talk_base::Thread::Current()->socketserver()); + socket = fw->WrapSocket(socket, SOCK_STREAM); + } + + if (proxy_.type) { + talk_base::AsyncSocket* proxy_socket = 0; + if (proxy_.type == talk_base::PROXY_SOCKS5) { + proxy_socket = new talk_base::AsyncSocksProxySocket( + socket, proxy_.address, proxy_.username, proxy_.password); + } else { + // Note: we are trying unknown proxies as HTTPS currently. + proxy_socket = new talk_base::AsyncHttpsProxySocket(socket, + GetUserAgentString(), proxy_.address, proxy_.username, + proxy_.password); + } + if (!proxy_socket) { + SetWSAError(WSA_NOT_ENOUGH_MEMORY); + delete socket; + return false; + } + socket = proxy_socket; // For our purposes the proxy is now the socket. + } + + if (protocol_ == cricket::PROTO_SSLTCP) { + talk_base::AsyncSocket *fake_ssl_socket = + new talk_base::AsyncSSLSocket(socket); + if (!fake_ssl_socket) { + SetWSAError(WSA_NOT_ENOUGH_MEMORY); + delete socket; + return false; + } + socket = fake_ssl_socket; // For our purposes the SSL socket is the socket. + } + +#if defined(FEATURE_ENABLE_SSL) + talk_base::SSLAdapter* ssl_adapter = notifier::CreateSSLAdapter(socket); + socket = ssl_adapter; // For our purposes the SSL adapter is the socket. +#endif + + socket->SignalReadEvent.connect(this, &XmppSocketAdapter::OnReadEvent); + socket->SignalWriteEvent.connect(this, &XmppSocketAdapter::OnWriteEvent); + socket->SignalConnectEvent.connect(this, &XmppSocketAdapter::OnConnectEvent); + socket->SignalCloseEvent.connect(this, &XmppSocketAdapter::OnCloseEvent); + + // The linux implementation of socket::Connect returns an error when the + // connect didn't complete yet. This can be distinguished from a failure + // because socket::IsBlocking is true. Perhaps, the linux implementation + // should be made to behave like the windows version which doesn't do this, + // but it seems to be a pattern with these methods that they return an error + // if the operation didn't complete in a sync fashion and one has to check + // IsBlocking to tell if was a "real" error. + if (socket->Connect(addr) == SOCKET_ERROR && !socket->IsBlocking()) { + SetWSAError(socket->GetError()); + delete socket; + return false; + } + + socket_ = socket; + state_ = STATE_CONNECTING; + return true; +} + +bool XmppSocketAdapter::Read(char* data, size_t len, size_t* len_read) { + if (len_read) + *len_read = 0; + + if (state_ <= STATE_CLOSING) { + SetError(ERROR_WRONGSTATE); + return false; + } + + DCHECK(socket_); + + if (IsOpen()) { + int result = socket_->Recv(data, len); + if (result < 0) { + if (!socket_->IsBlocking()) { + SetWSAError(socket_->GetError()); + return false; + } + + result = 0; + } + + if (len_read) + *len_read = result; + } + + return true; +} + +bool XmppSocketAdapter::Write(const char* data, size_t len) { + if (state_ <= STATE_CLOSING) { + // There may be data in a buffer that gets lost. Too bad! + SetError(ERROR_WRONGSTATE); + return false; + } + + DCHECK(socket_); + + size_t sent = 0; + + // Try an immediate write when there is no buffer and we aren't in SSL mode + // or opening the connection. + if (write_buffer_length_ == 0 && IsOpen()) { + int result = socket_->Send(data, len); + if (result < 0) { + if (!socket_->IsBlocking()) { + SetWSAError(socket_->GetError()); + return false; + } + result = 0; + } + + sent = static_cast<size_t>(result); + } + + // Buffer what we didn't send. + if (sent < len) { + QueueWriteData(data + sent, len - sent); + } + + // Service the socket right away to push the written data out in SSL mode. + return HandleWritable(); +} + +bool XmppSocketAdapter::Close() { + if (state_ == STATE_CLOSING) { + return false; // Avoid recursion, but not unexpected. + } + if (state_ == STATE_CLOSED) { + // In theory should not be trying to re-InternalClose. + SetError(ERROR_WRONGSTATE); + return false; + } + + // TODO(sync): deal with flushing close (flush, don't do reads, clean ssl). + + // If we've gotten to the point where we really do have a socket underneath + // then close it. It should call us back to tell us it is closed, and + // NotifyClose will be called. We indicate "closing" state so that we + // do not recusively try to keep closing the socket. + if (socket_) { + state_ = STATE_CLOSING; + socket_->Close(); + } + + // If we didn't get the callback, then we better make sure we signal + // closed. + if (state_ != STATE_CLOSED) { + // The socket was closed manually, not directly due to error. + if (error_ != ERROR_NONE) { + LOG(INFO) << "XmppSocketAdapter::Close - previous Error: " << error_ + << " WSAError: " << wsa_error_; + error_ = ERROR_NONE; + wsa_error_ = 0; + } + NotifyClose(); + } + return true; +} + +void XmppSocketAdapter::NotifyClose() { + if (state_ == STATE_CLOSED) { + SetError(ERROR_WRONGSTATE); + } else { + LOG(INFO) << "XmppSocketAdapter::NotifyClose - Error: " << error_ + << " WSAError: " << wsa_error_; + state_ = STATE_CLOSED; + SignalClosed(); + FreeState(); + } +} + +void XmppSocketAdapter::OnConnectEvent(talk_base::AsyncSocket *socket) { + if (state_ == STATE_CONNECTING) { + state_ = STATE_OPEN; + LOG(INFO) << "XmppSocketAdapter::OnConnectEvent - STATE_OPEN"; + SignalConnected(); +#if defined(FEATURE_ENABLE_SSL) + } else if (state_ == STATE_TLS_CONNECTING) { + state_ = STATE_TLS_OPEN; + LOG(INFO) << "XmppSocketAdapter::OnConnectEvent - STATE_TLS_OPEN"; + SignalSSLConnected(); + if (write_buffer_length_ > 0) { + HandleWritable(); + } +#endif // defined(FEATURE_ENABLE_SSL) + } else { + LOG(DFATAL) << "unexpected XmppSocketAdapter::OnConnectEvent state: " + << state_; + } +} + +void XmppSocketAdapter::OnReadEvent(talk_base::AsyncSocket *socket) { + HandleReadable(); +} + +void XmppSocketAdapter::OnWriteEvent(talk_base::AsyncSocket *socket) { + HandleWritable(); +} + +void XmppSocketAdapter::OnCloseEvent(talk_base::AsyncSocket *socket, + int error) { + LOG(INFO) << "XmppSocketAdapter::OnCloseEvent(" << error << ")"; + SetWSAError(error); + if (error == SOCKET_EACCES) { + SignalAuthenticationError(); // Proxy needs authentication. + } + NotifyClose(); +} + +#if defined(FEATURE_ENABLE_SSL) +bool XmppSocketAdapter::StartTls(const std::string& verify_host_name) { + if (state_ != STATE_OPEN) { + SetError(ERROR_WRONGSTATE); + return false; + } + + state_ = STATE_TLS_CONNECTING; + + DCHECK_EQ(write_buffer_length_, 0U); + + talk_base::SSLAdapter* ssl_adapter = + static_cast<talk_base::SSLAdapter*>(socket_); + + if (allow_unverified_certs_) { + ssl_adapter->set_ignore_bad_cert(true); + } + + if (ssl_adapter->StartSSL(verify_host_name.c_str(), false) != 0) { + state_ = STATE_OPEN; + SetError(ERROR_SSL); + return false; + } + + return true; +} +#endif // defined(FEATURE_ENABLE_SSL) + +void XmppSocketAdapter::QueueWriteData(const char* data, size_t len) { + // Expand buffer if needed. + if (write_buffer_length_ + len > write_buffer_capacity_) { + size_t new_capacity = 1024; + while (new_capacity < write_buffer_length_ + len) { + new_capacity = new_capacity * 2; + } + char* new_buffer = new char[new_capacity]; + DCHECK_LE(write_buffer_length_, 64000U); + memcpy(new_buffer, write_buffer_, write_buffer_length_); + delete[] write_buffer_; + write_buffer_ = new_buffer; + write_buffer_capacity_ = new_capacity; + } + + // Copy data into the end of buffer. + memcpy(write_buffer_ + write_buffer_length_, data, len); + write_buffer_length_ += len; +} + +void XmppSocketAdapter::FlushWriteQueue(Error* error, int* wsa_error) { + DCHECK(error); + DCHECK(wsa_error); + + size_t flushed = 0; + while (flushed < write_buffer_length_) { + int sent = socket_->Send(write_buffer_ + flushed, + static_cast<int>(write_buffer_length_ - flushed)); + if (sent < 0) { + if (!socket_->IsBlocking()) { + *error = ERROR_WINSOCK; + *wsa_error = socket_->GetError(); + } + break; + } + flushed += static_cast<size_t>(sent); + } + + // Remove flushed memory. + write_buffer_length_ -= flushed; + memmove(write_buffer_, write_buffer_ + flushed, write_buffer_length_); + + // When everything is flushed, deallocate the buffer if it's gotten big. + if (write_buffer_length_ == 0) { + if (write_buffer_capacity_ > 8192) { + delete[] write_buffer_; + write_buffer_ = NULL; + write_buffer_capacity_ = 0; + } + } +} + +void XmppSocketAdapter::SetError(Error error) { + if (error_ == ERROR_NONE) { + error_ = error; + } +} + +void XmppSocketAdapter::SetWSAError(int error) { + if (error_ == ERROR_NONE && error != 0) { + error_ = ERROR_WINSOCK; + wsa_error_ = error; + } +} + +bool XmppSocketAdapter::HandleReadable() { + if (!IsOpen()) + return false; + + SignalRead(); + return true; +} + +bool XmppSocketAdapter::HandleWritable() { + if (!IsOpen()) + return false; + + Error error = ERROR_NONE; + int wsa_error = 0; + FlushWriteQueue(&error, &wsa_error); + if (error != ERROR_NONE) { + Close(); + return false; + } + return true; +} + +} // namespace notifier diff --git a/jingle/notifier/communicator/xmpp_socket_adapter.h b/jingle/notifier/communicator/xmpp_socket_adapter.h new file mode 100644 index 0000000..ff22a9c --- /dev/null +++ b/jingle/notifier/communicator/xmpp_socket_adapter.h @@ -0,0 +1,87 @@ +// 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. + +#ifndef JINGLE_NOTIFIER_COMMUNICATOR_XMPP_SOCKET_ADAPTER_H_ +#define JINGLE_NOTIFIER_COMMUNICATOR_XMPP_SOCKET_ADAPTER_H_ + +#include <string> + +#include "talk/base/asyncsocket.h" +#include "talk/xmpp/asyncsocket.h" +#include "talk/xmpp/xmppclientsettings.h" +#include "talk/xmpp/xmppengine.h" + +#ifndef _WIN32 +// Additional errors used by us from Win32 headers. +#define SEC_E_CERT_EXPIRED static_cast<int>(0x80090328L) +#define WSA_NOT_ENOUGH_MEMORY ENOMEM +#endif + +namespace notifier { + +class XmppSocketAdapter : public buzz::AsyncSocket, + public sigslot::has_slots<> { + public: + XmppSocketAdapter(const buzz::XmppClientSettings& xcs, + bool allow_unverified_certs); + virtual ~XmppSocketAdapter(); + + virtual State state() { return state_; } + virtual Error error() { return error_; } + virtual int GetError() { return wsa_error_; } + + void set_firewall(bool firewall) { firewall_ = firewall; } + + virtual bool Connect(const talk_base::SocketAddress& addr); + virtual bool Read(char* data, size_t len, size_t* len_read); + virtual bool Write(const char* data, size_t len); + virtual bool Close(); + +#if defined(FEATURE_ENABLE_SSL) + bool StartTls(const std::string& domainname); + bool IsOpen() const { return state_ == STATE_OPEN + || state_ == STATE_TLS_OPEN; } +#else + bool IsOpen() const { return state_ == STATE_OPEN; } +#endif + + sigslot::signal0<> SignalAuthenticationError; + + private: + // Return false if the socket is closed. + bool HandleReadable(); + bool HandleWritable(); + + State state_; + Error error_; + int wsa_error_; + + talk_base::AsyncSocket* socket_; + cricket::ProtocolType protocol_; + talk_base::ProxyInfo proxy_; + bool firewall_; + char* write_buffer_; + size_t write_buffer_length_; + size_t write_buffer_capacity_; + bool allow_unverified_certs_; + + bool FreeState(); + void NotifyClose(); + + void OnReadEvent(talk_base::AsyncSocket* socket); + void OnWriteEvent(talk_base::AsyncSocket* socket); + void OnConnectEvent(talk_base::AsyncSocket* socket); + void OnCloseEvent(talk_base::AsyncSocket* socket, int error); + + void QueueWriteData(const char* data, size_t len); + void FlushWriteQueue(Error* error, int* wsa_error); + + void SetError(Error error); + void SetWSAError(int error); + DISALLOW_COPY_AND_ASSIGN(XmppSocketAdapter); +}; + +} // namespace notifier + +#endif // JINGLE_NOTIFIER_COMMUNICATOR_XMPP_SOCKET_ADAPTER_H_ diff --git a/jingle/notifier/listener/mediator_thread_impl.cc b/jingle/notifier/listener/mediator_thread_impl.cc index 918b49b..02d48fb1 100644 --- a/jingle/notifier/listener/mediator_thread_impl.cc +++ b/jingle/notifier/listener/mediator_thread_impl.cc @@ -16,6 +16,8 @@ #include "jingle/notifier/listener/subscribe_task.h" #include "net/base/host_port_pair.h" #include "net/base/host_resolver.h" +#include "talk/base/physicalsocketserver.h" +#include "talk/base/thread.h" #include "talk/xmpp/xmppclient.h" #include "talk/xmpp/xmppclientsettings.h" @@ -49,6 +51,44 @@ void MediatorThreadImpl::Start() { // TODO(akalin): Make this function return a bool and remove this // CHECK(). CHECK(worker_thread_.StartWithOptions(options)); + if (!notifier_options_.use_chrome_async_socket) { + worker_message_loop()->PostTask( + FROM_HERE, + NewRunnableMethod(this, &MediatorThreadImpl::StartLibjingleThread)); + } +} + +void MediatorThreadImpl::StartLibjingleThread() { + DCHECK_EQ(MessageLoop::current(), worker_message_loop()); + DCHECK(!notifier_options_.use_chrome_async_socket); + socket_server_.reset(new talk_base::PhysicalSocketServer()); + libjingle_thread_.reset(new talk_base::Thread()); + talk_base::ThreadManager::SetCurrent(libjingle_thread_.get()); + worker_message_loop()->PostTask( + FROM_HERE, + NewRunnableMethod(this, &MediatorThreadImpl::PumpLibjingleLoop)); +} + +void MediatorThreadImpl::StopLibjingleThread() { + DCHECK_EQ(MessageLoop::current(), worker_message_loop()); + DCHECK(!notifier_options_.use_chrome_async_socket); + talk_base::ThreadManager::SetCurrent(NULL); + libjingle_thread_.reset(); + socket_server_.reset(); +} + +void MediatorThreadImpl::PumpLibjingleLoop() { + DCHECK_EQ(MessageLoop::current(), worker_message_loop()); + DCHECK(!notifier_options_.use_chrome_async_socket); + // Pump the libjingle message loop 100ms at a time. + if (!libjingle_thread_.get()) { + // StopLibjingleThread() was called. + return; + } + libjingle_thread_->ProcessMessages(100); + worker_message_loop()->PostTask( + FROM_HERE, + NewRunnableMethod(this, &MediatorThreadImpl::PumpLibjingleLoop)); } void MediatorThreadImpl::Login(const buzz::XmppClientSettings& settings) { @@ -63,6 +103,11 @@ void MediatorThreadImpl::Logout() { worker_message_loop()->PostTask( FROM_HERE, NewRunnableMethod(this, &MediatorThreadImpl::DoDisconnect)); + if (!notifier_options_.use_chrome_async_socket) { + worker_message_loop()->PostTask( + FROM_HERE, + NewRunnableMethod(this, &MediatorThreadImpl::StopLibjingleThread)); + } // TODO(akalin): Decomp this into a separate stop method. worker_thread_.Stop(); // Process any messages the worker thread may be posted on our @@ -160,6 +205,7 @@ void MediatorThreadImpl::DoLogin( // Language is not used in the stanza so we default to |en|. std::string lang = "en"; login_.reset(new notifier::Login(pump_.get(), + notifier_options_.use_chrome_async_socket, settings, options, lang, diff --git a/jingle/notifier/listener/mediator_thread_impl.h b/jingle/notifier/listener/mediator_thread_impl.h index 342079f..755731e 100644 --- a/jingle/notifier/listener/mediator_thread_impl.h +++ b/jingle/notifier/listener/mediator_thread_impl.h @@ -93,6 +93,10 @@ class MediatorThreadImpl MessageLoop* parent_message_loop_; private: + void StartLibjingleThread(); + void PumpLibjingleLoop(); + void StopLibjingleThread(); + // Called from within the thread on internal events. void DoLogin(const buzz::XmppClientSettings& settings); void DoDisconnect(); @@ -133,6 +137,10 @@ class MediatorThreadImpl scoped_ptr<notifier::TaskPump> pump_; scoped_ptr<notifier::Login> login_; + // Used only when |use_chrome_async_socket_| is false. + scoped_ptr<talk_base::SocketServer> socket_server_; + scoped_ptr<talk_base::Thread> libjingle_thread_; + DISALLOW_COPY_AND_ASSIGN(MediatorThreadImpl); }; |