| Index: remoting/signaling/xmpp_login_handler.cc
|
| diff --git a/remoting/signaling/xmpp_login_handler.cc b/remoting/signaling/xmpp_login_handler.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..cc3ae94a4e0ab63405f6c571236e2114b5c2253a
|
| --- /dev/null
|
| +++ b/remoting/signaling/xmpp_login_handler.cc
|
| @@ -0,0 +1,241 @@
|
| +// 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 "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("<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(scoped_ptr<buzz::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: {
|
| + 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(
|
| + "<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: {
|
| + 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_, stream_parser_.Pass());
|
| + 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) {
|
| + delegate_->SendMessage("<stream:stream to=\"" + server_ +
|
| + "\" version=\"1.0\" xmlns=\"jabber:client\" "
|
| + "xmlns:stream=\"http://etherx.jabber.org/streams\">" +
|
| + first_message);
|
| + stream_parser_.reset(new XmppStreamParser());
|
| + stream_parser_->SetCallbacks(
|
| + base::Bind(&XmppLoginHandler::OnStanza, base::Unretained(this)),
|
| + base::Bind(&XmppLoginHandler::OnParserError, base::Unretained(this)));
|
| +}
|
| +
|
| +void XmppLoginHandler::OnError(SignalStrategy::Error error) {
|
| + if (state_ != State::ERROR) {
|
| + state_ = State::ERROR;
|
| + delegate_->OnLoginHandlerError(error);
|
| + }
|
| +}
|
| +
|
| +} // namespace remoting
|
|
|