// 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 #include "base/base64.h" #include "base/bind.h" #include "base/logging.h" #include "remoting/signaling/xmpp_stream_parser.h" #include "third_party/webrtc/libjingle/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"; buzz::StaticQName kXmppIqName = {"jabber:client", "iq"}; char kXmppBindNs[] = "urn:ietf:params:xml:ns:xmpp-bind"; buzz::StaticQName kXmppBindName = {kXmppBindNs, "bind"}; buzz::StaticQName kXmppJidName = {kXmppBindNs, "jid"}; buzz::StaticQName kJabberFeaturesName = {"http://etherx.jabber.org/streams", "features"}; char kXmppTlsNs[] = "urn:ietf:params:xml:ns:xmpp-tls"; buzz::StaticQName kStartTlsName = {kXmppTlsNs, "starttls"}; buzz::StaticQName kTlsProceedName = {kXmppTlsNs, "proceed"}; char kXmppSaslNs[] = "urn:ietf:params:xml:ns:xmpp-sasl"; buzz::StaticQName kSaslMechanismsName = {kXmppSaslNs, "mechanisms"}; buzz::StaticQName kSaslMechanismName = {kXmppSaslNs, "mechanism"}; buzz::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() { } 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(""); break; case TlsMode::WITHOUT_HANDSHAKE: // If 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(scoped_ptr 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: { buzz::XmlElement* mechanisms_element = stanza->FirstNamed(kSaslMechanismsName); bool oauth_supported = false; if (mechanisms_element) { for (buzz::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( "" "" "chromoting" "" "" "" "" ""); } 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: { buzz::XmlElement* bind = stanza->FirstNamed(kXmppBindName); buzz::XmlElement* jid = bind ? bind->FirstNamed(kXmppJidName) : nullptr; if (stanza->Attr(buzz::QName("", "id")) != "0" || stanza->Attr(buzz::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(buzz::QName("", "id")) != "1" || stanza->Attr(buzz::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( "" + cookie + ""); }; 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("" + first_message); } void XmppLoginHandler::OnError(SignalStrategy::Error error) { if (state_ != State::ERROR) { state_ = State::ERROR; delegate_->OnLoginHandlerError(error); } } } // namespace remoting