Index: webrtc/libjingle/xmpp/xmppclient.cc |
diff --git a/webrtc/libjingle/xmpp/xmppclient.cc b/webrtc/libjingle/xmpp/xmppclient.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..a87659824bbb5d472ab6e61c9c60ffee15c969f4 |
--- /dev/null |
+++ b/webrtc/libjingle/xmpp/xmppclient.cc |
@@ -0,0 +1,423 @@ |
+/* |
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved. |
+ * |
+ * Use of this source code is governed by a BSD-style license |
+ * that can be found in the LICENSE file in the root of the source |
+ * tree. An additional intellectual property rights grant can be found |
+ * in the file PATENTS. All contributing project authors may |
+ * be found in the AUTHORS file in the root of the source tree. |
+ */ |
+ |
+#include "webrtc/libjingle/xmpp/xmppclient.h" |
+ |
+#include "webrtc/libjingle/xmpp/constants.h" |
+#include "webrtc/libjingle/xmpp/plainsaslhandler.h" |
+#include "webrtc/libjingle/xmpp/prexmppauth.h" |
+#include "webrtc/libjingle/xmpp/saslplainmechanism.h" |
+#include "webrtc/base/logging.h" |
+#include "webrtc/base/sigslot.h" |
+#include "webrtc/base/stringutils.h" |
+#include "xmpptask.h" |
+ |
+namespace buzz { |
+ |
+class XmppClient::Private : |
+ public sigslot::has_slots<>, |
+ public XmppSessionHandler, |
+ public XmppOutputHandler { |
+public: |
+ |
+ explicit Private(XmppClient* client) : |
+ client_(client), |
+ socket_(), |
+ engine_(), |
+ proxy_port_(0), |
+ pre_engine_error_(XmppEngine::ERROR_NONE), |
+ pre_engine_subcode_(0), |
+ signal_closed_(false), |
+ allow_plain_(false) {} |
+ |
+ virtual ~Private() { |
+ // We need to disconnect from socket_ before engine_ is destructed (by |
+ // the auto-generated destructor code). |
+ ResetSocket(); |
+ } |
+ |
+ // the owner |
+ XmppClient* const client_; |
+ |
+ // the two main objects |
+ std::unique_ptr<AsyncSocket> socket_; |
+ std::unique_ptr<XmppEngine> engine_; |
+ std::unique_ptr<PreXmppAuth> pre_auth_; |
+ rtc::CryptString pass_; |
+ std::string auth_mechanism_; |
+ std::string auth_token_; |
+ rtc::SocketAddress server_; |
+ std::string proxy_host_; |
+ int proxy_port_; |
+ XmppEngine::Error pre_engine_error_; |
+ int pre_engine_subcode_; |
+ CaptchaChallenge captcha_challenge_; |
+ bool signal_closed_; |
+ bool allow_plain_; |
+ |
+ void ResetSocket() { |
+ if (socket_) { |
+ socket_->SignalConnected.disconnect(this); |
+ socket_->SignalRead.disconnect(this); |
+ socket_->SignalClosed.disconnect(this); |
+ socket_.reset(NULL); |
+ } |
+ } |
+ |
+ // implementations of interfaces |
+ void OnStateChange(int state); |
+ void WriteOutput(const char* bytes, size_t len); |
+ void StartTls(const std::string& domainname); |
+ void CloseConnection(); |
+ |
+ // slots for socket signals |
+ void OnSocketConnected(); |
+ void OnSocketRead(); |
+ void OnSocketClosed(); |
+}; |
+ |
+bool IsTestServer(const std::string& server_name, |
+ const std::string& test_server_domain) { |
+ return (!test_server_domain.empty() && |
+ rtc::ends_with(server_name.c_str(), |
+ test_server_domain.c_str())); |
+} |
+ |
+XmppReturnStatus XmppClient::Connect( |
+ const XmppClientSettings& settings, |
+ const std::string& lang, AsyncSocket* socket, PreXmppAuth* pre_auth) { |
+ if (socket == NULL) |
+ return XMPP_RETURN_BADARGUMENT; |
+ if (d_->socket_) |
+ return XMPP_RETURN_BADSTATE; |
+ |
+ d_->socket_.reset(socket); |
+ |
+ d_->socket_->SignalConnected.connect(d_.get(), &Private::OnSocketConnected); |
+ d_->socket_->SignalRead.connect(d_.get(), &Private::OnSocketRead); |
+ d_->socket_->SignalClosed.connect(d_.get(), &Private::OnSocketClosed); |
+ |
+ d_->engine_.reset(XmppEngine::Create()); |
+ d_->engine_->SetSessionHandler(d_.get()); |
+ d_->engine_->SetOutputHandler(d_.get()); |
+ if (!settings.resource().empty()) { |
+ d_->engine_->SetRequestedResource(settings.resource()); |
+ } |
+ d_->engine_->SetTls(settings.use_tls()); |
+ |
+ // The talk.google.com server returns a certificate with common-name: |
+ // CN="gmail.com" for @gmail.com accounts, |
+ // CN="googlemail.com" for @googlemail.com accounts, |
+ // CN="talk.google.com" for other accounts (such as @example.com), |
+ // so we tweak the tls server setting for those other accounts to match the |
+ // returned certificate CN of "talk.google.com". |
+ // For other servers, we leave the strings empty, which causes the jid's |
+ // domain to be used. We do the same for gmail.com and googlemail.com as the |
+ // returned CN matches the account domain in those cases. |
+ std::string server_name = settings.server().HostAsURIString(); |
+ if (server_name == buzz::STR_TALK_GOOGLE_COM || |
+ server_name == buzz::STR_TALKX_L_GOOGLE_COM || |
+ server_name == buzz::STR_XMPP_GOOGLE_COM || |
+ server_name == buzz::STR_XMPPX_L_GOOGLE_COM || |
+ IsTestServer(server_name, settings.test_server_domain())) { |
+ if (settings.host() != STR_GMAIL_COM && |
+ settings.host() != STR_GOOGLEMAIL_COM) { |
+ d_->engine_->SetTlsServer("", STR_TALK_GOOGLE_COM); |
+ } |
+ } |
+ |
+ // Set language |
+ d_->engine_->SetLanguage(lang); |
+ |
+ d_->engine_->SetUser(buzz::Jid(settings.user(), settings.host(), STR_EMPTY)); |
+ |
+ d_->pass_ = settings.pass(); |
+ d_->auth_mechanism_ = settings.auth_mechanism(); |
+ d_->auth_token_ = settings.auth_token(); |
+ d_->server_ = settings.server(); |
+ d_->proxy_host_ = settings.proxy_host(); |
+ d_->proxy_port_ = settings.proxy_port(); |
+ d_->allow_plain_ = settings.allow_plain(); |
+ d_->pre_auth_.reset(pre_auth); |
+ |
+ return XMPP_RETURN_OK; |
+} |
+ |
+XmppEngine::State XmppClient::GetState() const { |
+ if (!d_->engine_) |
+ return XmppEngine::STATE_NONE; |
+ return d_->engine_->GetState(); |
+} |
+ |
+XmppEngine::Error XmppClient::GetError(int* subcode) { |
+ if (subcode) { |
+ *subcode = 0; |
+ } |
+ if (!d_->engine_) |
+ return XmppEngine::ERROR_NONE; |
+ if (d_->pre_engine_error_ != XmppEngine::ERROR_NONE) { |
+ if (subcode) { |
+ *subcode = d_->pre_engine_subcode_; |
+ } |
+ return d_->pre_engine_error_; |
+ } |
+ return d_->engine_->GetError(subcode); |
+} |
+ |
+const XmlElement* XmppClient::GetStreamError() { |
+ if (!d_->engine_) { |
+ return NULL; |
+ } |
+ return d_->engine_->GetStreamError(); |
+} |
+ |
+CaptchaChallenge XmppClient::GetCaptchaChallenge() { |
+ if (!d_->engine_) |
+ return CaptchaChallenge(); |
+ return d_->captcha_challenge_; |
+} |
+ |
+std::string XmppClient::GetAuthMechanism() { |
+ if (!d_->engine_) |
+ return ""; |
+ return d_->auth_mechanism_; |
+} |
+ |
+std::string XmppClient::GetAuthToken() { |
+ if (!d_->engine_) |
+ return ""; |
+ return d_->auth_token_; |
+} |
+ |
+int XmppClient::ProcessStart() { |
+ // Should not happen, but was observed in crash reports |
+ if (!d_->socket_) { |
+ LOG(LS_ERROR) << "socket_ already reset"; |
+ return STATE_DONE; |
+ } |
+ |
+ if (d_->pre_auth_) { |
+ d_->pre_auth_->SignalAuthDone.connect(this, &XmppClient::OnAuthDone); |
+ d_->pre_auth_->StartPreXmppAuth( |
+ d_->engine_->GetUser(), d_->server_, d_->pass_, |
+ d_->auth_mechanism_, d_->auth_token_); |
+ d_->pass_.Clear(); // done with this; |
+ return STATE_PRE_XMPP_LOGIN; |
+ } |
+ else { |
+ d_->engine_->SetSaslHandler(new PlainSaslHandler( |
+ d_->engine_->GetUser(), d_->pass_, d_->allow_plain_)); |
+ d_->pass_.Clear(); // done with this; |
+ return STATE_START_XMPP_LOGIN; |
+ } |
+} |
+ |
+void XmppClient::OnAuthDone() { |
+ Wake(); |
+} |
+ |
+int XmppClient::ProcessTokenLogin() { |
+ // Should not happen, but was observed in crash reports |
+ if (!d_->socket_) { |
+ LOG(LS_ERROR) << "socket_ already reset"; |
+ return STATE_DONE; |
+ } |
+ |
+ // Don't know how this could happen, but crash reports show it as NULL |
+ if (!d_->pre_auth_) { |
+ d_->pre_engine_error_ = XmppEngine::ERROR_AUTH; |
+ EnsureClosed(); |
+ return STATE_ERROR; |
+ } |
+ |
+ // Wait until pre authentication is done is done |
+ if (!d_->pre_auth_->IsAuthDone()) |
+ return STATE_BLOCKED; |
+ |
+ if (!d_->pre_auth_->IsAuthorized()) { |
+ // maybe split out a case when gaia is down? |
+ if (d_->pre_auth_->HadError()) { |
+ d_->pre_engine_error_ = XmppEngine::ERROR_AUTH; |
+ d_->pre_engine_subcode_ = d_->pre_auth_->GetError(); |
+ } |
+ else { |
+ d_->pre_engine_error_ = XmppEngine::ERROR_UNAUTHORIZED; |
+ d_->pre_engine_subcode_ = 0; |
+ d_->captcha_challenge_ = d_->pre_auth_->GetCaptchaChallenge(); |
+ } |
+ d_->pre_auth_.reset(NULL); // done with this |
+ EnsureClosed(); |
+ return STATE_ERROR; |
+ } |
+ |
+ // Save auth token as a result |
+ |
+ d_->auth_mechanism_ = d_->pre_auth_->GetAuthMechanism(); |
+ d_->auth_token_ = d_->pre_auth_->GetAuthToken(); |
+ |
+ // transfer ownership of pre_auth_ to engine |
+ d_->engine_->SetSaslHandler(d_->pre_auth_.release()); |
+ return STATE_START_XMPP_LOGIN; |
+} |
+ |
+int XmppClient::ProcessStartXmppLogin() { |
+ // Should not happen, but was observed in crash reports |
+ if (!d_->socket_) { |
+ LOG(LS_ERROR) << "socket_ already reset"; |
+ return STATE_DONE; |
+ } |
+ |
+ // Done with pre-connect tasks - connect! |
+ if (!d_->socket_->Connect(d_->server_)) { |
+ EnsureClosed(); |
+ return STATE_ERROR; |
+ } |
+ |
+ return STATE_RESPONSE; |
+} |
+ |
+int XmppClient::ProcessResponse() { |
+ // Hang around while we are connected. |
+ if (!delivering_signal_ && |
+ (!d_->engine_ || d_->engine_->GetState() == XmppEngine::STATE_CLOSED)) |
+ return STATE_DONE; |
+ return STATE_BLOCKED; |
+} |
+ |
+XmppReturnStatus XmppClient::Disconnect() { |
+ if (!d_->socket_) |
+ return XMPP_RETURN_BADSTATE; |
+ Abort(); |
+ d_->engine_->Disconnect(); |
+ d_->ResetSocket(); |
+ return XMPP_RETURN_OK; |
+} |
+ |
+XmppClient::XmppClient(TaskParent* parent) |
+ : XmppTaskParentInterface(parent), |
+ delivering_signal_(false), |
+ valid_(false) { |
+ d_.reset(new Private(this)); |
+ valid_ = true; |
+} |
+ |
+XmppClient::~XmppClient() { |
+ valid_ = false; |
+} |
+ |
+const Jid& XmppClient::jid() const { |
+ return d_->engine_->FullJid(); |
+} |
+ |
+ |
+std::string XmppClient::NextId() { |
+ return d_->engine_->NextId(); |
+} |
+ |
+XmppReturnStatus XmppClient::SendStanza(const XmlElement* stanza) { |
+ return d_->engine_->SendStanza(stanza); |
+} |
+ |
+XmppReturnStatus XmppClient::SendStanzaError( |
+ const XmlElement* old_stanza, XmppStanzaError xse, |
+ const std::string& message) { |
+ return d_->engine_->SendStanzaError(old_stanza, xse, message); |
+} |
+ |
+XmppReturnStatus XmppClient::SendRaw(const std::string& text) { |
+ return d_->engine_->SendRaw(text); |
+} |
+ |
+XmppEngine* XmppClient::engine() { |
+ return d_->engine_.get(); |
+} |
+ |
+void XmppClient::Private::OnSocketConnected() { |
+ engine_->Connect(); |
+} |
+ |
+void XmppClient::Private::OnSocketRead() { |
+ char bytes[4096]; |
+ size_t bytes_read; |
+ for (;;) { |
+ // Should not happen, but was observed in crash reports |
+ if (!socket_) { |
+ LOG(LS_ERROR) << "socket_ already reset"; |
+ return; |
+ } |
+ |
+ if (!socket_->Read(bytes, sizeof(bytes), &bytes_read)) { |
+ // TODO: deal with error information |
+ return; |
+ } |
+ |
+ if (bytes_read == 0) |
+ return; |
+ |
+//#if !defined(NDEBUG) |
+ client_->SignalLogInput(bytes, static_cast<int>(bytes_read)); |
+//#endif |
+ |
+ engine_->HandleInput(bytes, bytes_read); |
+ } |
+} |
+ |
+void XmppClient::Private::OnSocketClosed() { |
+ int code = socket_->GetError(); |
+ engine_->ConnectionClosed(code); |
+} |
+ |
+void XmppClient::Private::OnStateChange(int state) { |
+ if (state == XmppEngine::STATE_CLOSED) { |
+ client_->EnsureClosed(); |
+ } |
+ else { |
+ client_->SignalStateChange((XmppEngine::State)state); |
+ } |
+ client_->Wake(); |
+} |
+ |
+void XmppClient::Private::WriteOutput(const char* bytes, size_t len) { |
+//#if !defined(NDEBUG) |
+ client_->SignalLogOutput(bytes, static_cast<int>(len)); |
+//#endif |
+ |
+ socket_->Write(bytes, len); |
+ // TODO: deal with error information |
+} |
+ |
+void XmppClient::Private::StartTls(const std::string& domain) { |
+#if defined(FEATURE_ENABLE_SSL) |
+ socket_->StartTls(domain); |
+#endif |
+} |
+ |
+void XmppClient::Private::CloseConnection() { |
+ socket_->Close(); |
+} |
+ |
+void XmppClient::AddXmppTask(XmppTask* task, XmppEngine::HandlerLevel level) { |
+ d_->engine_->AddStanzaHandler(task, level); |
+} |
+ |
+void XmppClient::RemoveXmppTask(XmppTask* task) { |
+ d_->engine_->RemoveStanzaHandler(task); |
+} |
+ |
+void XmppClient::EnsureClosed() { |
+ if (!d_->signal_closed_) { |
+ d_->signal_closed_ = true; |
+ delivering_signal_ = true; |
+ SignalStateChange(XmppEngine::STATE_CLOSED); |
+ delivering_signal_ = false; |
+ } |
+} |
+ |
+} // namespace buzz |