| // Copyright 2015 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 "remoting/signaling/xmpp_login_handler.h" |
| |
| #include <utility> |
| |
| #include "base/base64.h" |
| #include "base/bind.h" |
| #include "base/logging.h" |
| #include "remoting/signaling/xmpp_stream_parser.h" |
| #include "third_party/libjingle_xmpp/xmllite/xmlelement.h" |
| |
| // Undefine SendMessage and ERROR defined in Windows headers. |
| #ifdef SendMessage |
| #undef SendMessage |
| #endif |
| |
| #ifdef ERROR |
| #undef ERROR |
| #endif |
| |
| namespace remoting { |
| |
| const char kOAuthMechanism[] = "X-OAUTH2"; |
| |
| jingle_xmpp::StaticQName kXmppIqName = {"jabber:client", "iq"}; |
| |
| char kXmppBindNs[] = "urn:ietf:params:xml:ns:xmpp-bind"; |
| jingle_xmpp::StaticQName kXmppBindName = {kXmppBindNs, "bind"}; |
| jingle_xmpp::StaticQName kXmppJidName = {kXmppBindNs, "jid"}; |
| |
| jingle_xmpp::StaticQName kJabberFeaturesName = {"http://etherx.jabber.org/streams", |
| "features"}; |
| |
| char kXmppTlsNs[] = "urn:ietf:params:xml:ns:xmpp-tls"; |
| jingle_xmpp::StaticQName kStartTlsName = {kXmppTlsNs, "starttls"}; |
| jingle_xmpp::StaticQName kTlsProceedName = {kXmppTlsNs, "proceed"}; |
| |
| char kXmppSaslNs[] = "urn:ietf:params:xml:ns:xmpp-sasl"; |
| jingle_xmpp::StaticQName kSaslMechanismsName = {kXmppSaslNs, "mechanisms"}; |
| jingle_xmpp::StaticQName kSaslMechanismName = {kXmppSaslNs, "mechanism"}; |
| jingle_xmpp::StaticQName kSaslSuccessName = {kXmppSaslNs, "success"}; |
| |
| XmppLoginHandler::XmppLoginHandler(const std::string& server, |
| const std::string& username, |
| const std::string& auth_token, |
| TlsMode tls_mode, |
| Delegate* delegate) |
| : server_(server), |
| username_(username), |
| auth_token_(auth_token), |
| tls_mode_(tls_mode), |
| delegate_(delegate), |
| state_(State::INIT) { |
| } |
| |
| XmppLoginHandler::~XmppLoginHandler() = default; |
| |
| void XmppLoginHandler::Start() { |
| switch (tls_mode_) { |
| case TlsMode::NO_TLS: |
| state_ = State::WAIT_STREAM_HEADER_AFTER_TLS; |
| StartAuthHandshake(); |
| break; |
| case TlsMode::WITH_HANDSHAKE: |
| state_ = State::WAIT_STREAM_HEADER; |
| StartStream("<starttls xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\"/>"); |
| break; |
| case TlsMode::WITHOUT_HANDSHAKE: |
| // If <starttls> handshake is not required then start TLS right away. |
| state_ = State::STARTING_TLS; |
| delegate_->StartTls(); |
| break; |
| } |
| } |
| |
| void XmppLoginHandler::OnDataReceived(const std::string& data) { |
| DCHECK(state_ != State::INIT && state_ != State::DONE && |
| state_ != State::ERROR); |
| stream_parser_->AppendData(data); |
| } |
| |
| void XmppLoginHandler::OnStanza(std::unique_ptr<jingle_xmpp::XmlElement> stanza) { |
| switch (state_) { |
| case State::WAIT_STREAM_HEADER: { |
| if (stanza->Name() == kJabberFeaturesName && |
| stanza->FirstNamed(kStartTlsName) != nullptr) { |
| state_ = State::WAIT_STARTTLS_RESPONSE; |
| } else { |
| LOG(ERROR) << "Server doesn't support TLS."; |
| OnError(SignalStrategy::PROTOCOL_ERROR); |
| } |
| break; |
| } |
| |
| case State::WAIT_STARTTLS_RESPONSE: { |
| if (stanza->Name() == kTlsProceedName) { |
| state_ = State::STARTING_TLS; |
| delegate_->StartTls(); |
| } else { |
| LOG(ERROR) << "Failed to start TLS: " << stanza->Str(); |
| OnError(SignalStrategy::PROTOCOL_ERROR); |
| } |
| break; |
| } |
| |
| case State::WAIT_STREAM_HEADER_AFTER_TLS: { |
| jingle_xmpp::XmlElement* mechanisms_element = |
| stanza->FirstNamed(kSaslMechanismsName); |
| bool oauth_supported = false; |
| if (mechanisms_element) { |
| for (jingle_xmpp::XmlElement* element = |
| mechanisms_element->FirstNamed(kSaslMechanismName); |
| element; element = element->NextNamed(kSaslMechanismName)) { |
| if (element->BodyText() == kOAuthMechanism) { |
| oauth_supported = true; |
| break; |
| } |
| } |
| } |
| |
| if (!oauth_supported) { |
| LOG(ERROR) << kOAuthMechanism |
| << " auth mechanism is not supported by the server."; |
| OnError(SignalStrategy::PROTOCOL_ERROR); |
| return; |
| } |
| |
| state_ = State::WAIT_AUTH_RESULT; |
| break; |
| } |
| |
| case State::WAIT_AUTH_RESULT: { |
| if (stanza->Name() == kSaslSuccessName) { |
| state_ = State::WAIT_STREAM_HEADER_AFTER_AUTH; |
| StartStream( |
| "<iq type=\"set\" id=\"0\">" |
| "<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\">" |
| "<resource>chromoting</resource>" |
| "</bind>" |
| "</iq>" |
| "<iq type=\"set\" id=\"1\">" |
| "<session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"/>" |
| "</iq>"); |
| } else { |
| OnError(SignalStrategy::AUTHENTICATION_FAILED); |
| } |
| break; |
| } |
| |
| case State::WAIT_STREAM_HEADER_AFTER_AUTH: |
| if (stanza->Name() == kJabberFeaturesName && |
| stanza->FirstNamed(kXmppBindName) != nullptr) { |
| state_ = State::WAIT_BIND_RESULT; |
| } else { |
| LOG(ERROR) << "Server doesn't support bind after authentication."; |
| OnError(SignalStrategy::PROTOCOL_ERROR); |
| } |
| break; |
| |
| case State::WAIT_BIND_RESULT: { |
| jingle_xmpp::XmlElement* bind = stanza->FirstNamed(kXmppBindName); |
| jingle_xmpp::XmlElement* jid = bind ? bind->FirstNamed(kXmppJidName) : nullptr; |
| if (stanza->Attr(jingle_xmpp::QName("", "id")) != "0" || |
| stanza->Attr(jingle_xmpp::QName("", "type")) != "result" || !jid) { |
| LOG(ERROR) << "Received unexpected response to bind: " << stanza->Str(); |
| OnError(SignalStrategy::PROTOCOL_ERROR); |
| return; |
| } |
| jid_ = jid->BodyText(); |
| state_ = State::WAIT_SESSION_IQ_RESULT; |
| break; |
| } |
| |
| case State::WAIT_SESSION_IQ_RESULT: |
| if (stanza->Name() != kXmppIqName || |
| stanza->Attr(jingle_xmpp::QName("", "id")) != "1" || |
| stanza->Attr(jingle_xmpp::QName("", "type")) != "result") { |
| LOG(ERROR) << "Failed to start session: " << stanza->Str(); |
| OnError(SignalStrategy::PROTOCOL_ERROR); |
| return; |
| } |
| state_ = State::DONE; |
| delegate_->OnHandshakeDone(jid_, std::move(stream_parser_)); |
| break; |
| |
| default: |
| NOTREACHED(); |
| break; |
| } |
| } |
| |
| void XmppLoginHandler::OnTlsStarted() { |
| DCHECK(state_ == State::STARTING_TLS); |
| state_ = State::WAIT_STREAM_HEADER_AFTER_TLS; |
| StartAuthHandshake(); |
| } |
| |
| void XmppLoginHandler::StartAuthHandshake() { |
| DCHECK(state_ == State::WAIT_STREAM_HEADER_AFTER_TLS); |
| |
| std::string cookie; |
| base::Base64Encode( |
| std::string("\0", 1) + username_ + std::string("\0", 1) + auth_token_, |
| &cookie); |
| StartStream( |
| "<auth xmlns=\"" + std::string(kXmppSaslNs) + "\" " |
| "mechanism=\"" + "X-OAUTH2" + "\" " |
| "auth:service=\"oauth2\" " |
| "auth:allow-generated-jid=\"true\" " |
| "auth:client-uses-full-bind-result=\"true\" " |
| "auth:allow-non-google-login=\"true\" " |
| "xmlns:auth=\"http://www.google.com/talk/protocol/auth\">" + |
| cookie + |
| "</auth>"); |
| } |
| |
| void XmppLoginHandler::OnParserError() { |
| OnError(SignalStrategy::PROTOCOL_ERROR); |
| } |
| |
| void XmppLoginHandler::StartStream(const std::string& first_message) { |
| stream_parser_.reset(new XmppStreamParser()); |
| stream_parser_->SetCallbacks( |
| base::Bind(&XmppLoginHandler::OnStanza, base::Unretained(this)), |
| base::Bind(&XmppLoginHandler::OnParserError, base::Unretained(this))); |
| delegate_->SendMessage("<stream:stream to=\"" + server_ + |
| "\" version=\"1.0\" xmlns=\"jabber:client\" " |
| "xmlns:stream=\"http://etherx.jabber.org/streams\">" + |
| first_message); |
| } |
| |
| void XmppLoginHandler::OnError(SignalStrategy::Error error) { |
| if (state_ != State::ERROR) { |
| state_ = State::ERROR; |
| delegate_->OnLoginHandlerError(error); |
| } |
| } |
| |
| } // namespace remoting |