blob: cc3ae94a4e0ab63405f6c571236e2114b5c2253a [file] [log] [blame]
// 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