| /* |
| * 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 |