1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "remoting/signaling/xmpp_login_handler.h"
7 #include "base/base64.h"
9 #include "base/logging.h"
10 #include "remoting/signaling/xmpp_stream_parser.h"
11 #include "third_party/webrtc/libjingle/xmllite/xmlelement.h"
13 // Undefine SendMessage and ERROR defined in Windows headers.
24 const char kOAuthMechanism
[] = "X-OAUTH2";
26 buzz::StaticQName kXmppIqName
= {"jabber:client", "iq"};
28 char kXmppBindNs
[] = "urn:ietf:params:xml:ns:xmpp-bind";
29 buzz::StaticQName kXmppBindName
= {kXmppBindNs
, "bind"};
30 buzz::StaticQName kXmppJidName
= {kXmppBindNs
, "jid"};
32 buzz::StaticQName kJabberFeaturesName
= {"http://etherx.jabber.org/streams",
35 char kXmppTlsNs
[] = "urn:ietf:params:xml:ns:xmpp-tls";
36 buzz::StaticQName kStartTlsName
= {kXmppTlsNs
, "starttls"};
37 buzz::StaticQName kTlsProceedName
= {kXmppTlsNs
, "proceed"};
39 char kXmppSaslNs
[] = "urn:ietf:params:xml:ns:xmpp-sasl";
40 buzz::StaticQName kSaslMechanismsName
= {kXmppSaslNs
, "mechanisms"};
41 buzz::StaticQName kSaslMechanismName
= {kXmppSaslNs
, "mechanism"};
42 buzz::StaticQName kSaslSuccessName
= {kXmppSaslNs
, "success"};
44 XmppLoginHandler::XmppLoginHandler(const std::string
& server
,
45 const std::string
& username
,
46 const std::string
& auth_token
,
51 auth_token_(auth_token
),
57 XmppLoginHandler::~XmppLoginHandler() {
60 void XmppLoginHandler::Start() {
63 state_
= State::WAIT_STREAM_HEADER_AFTER_TLS
;
66 case TlsMode::WITH_HANDSHAKE
:
67 state_
= State::WAIT_STREAM_HEADER
;
68 StartStream("<starttls xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\"/>");
70 case TlsMode::WITHOUT_HANDSHAKE
:
71 // If <starttls> handshake is not required then start TLS right away.
72 state_
= State::STARTING_TLS
;
73 delegate_
->StartTls();
78 void XmppLoginHandler::OnDataReceived(const std::string
& data
) {
79 DCHECK(state_
!= State::INIT
&& state_
!= State::DONE
&&
80 state_
!= State::ERROR
);
81 stream_parser_
->AppendData(data
);
84 void XmppLoginHandler::OnStanza(scoped_ptr
<buzz::XmlElement
> stanza
) {
86 case State::WAIT_STREAM_HEADER
: {
87 if (stanza
->Name() == kJabberFeaturesName
&&
88 stanza
->FirstNamed(kStartTlsName
) != nullptr) {
89 state_
= State::WAIT_STARTTLS_RESPONSE
;
91 LOG(ERROR
) << "Server doesn't support TLS.";
92 OnError(SignalStrategy::PROTOCOL_ERROR
);
97 case State::WAIT_STARTTLS_RESPONSE
: {
98 if (stanza
->Name() == kTlsProceedName
) {
99 state_
= State::STARTING_TLS
;
100 delegate_
->StartTls();
102 LOG(ERROR
) << "Failed to start TLS: " << stanza
->Str();
103 OnError(SignalStrategy::PROTOCOL_ERROR
);
108 case State::WAIT_STREAM_HEADER_AFTER_TLS
: {
109 buzz::XmlElement
* mechanisms_element
=
110 stanza
->FirstNamed(kSaslMechanismsName
);
111 bool oauth_supported
= false;
112 if (mechanisms_element
) {
113 for (buzz::XmlElement
* element
=
114 mechanisms_element
->FirstNamed(kSaslMechanismName
);
115 element
; element
= element
->NextNamed(kSaslMechanismName
)) {
116 if (element
->BodyText() == kOAuthMechanism
) {
117 oauth_supported
= true;
123 if (!oauth_supported
) {
124 LOG(ERROR
) << kOAuthMechanism
125 << " auth mechanism is not supported by the server.";
126 OnError(SignalStrategy::PROTOCOL_ERROR
);
130 state_
= State::WAIT_AUTH_RESULT
;
134 case State::WAIT_AUTH_RESULT
: {
135 if (stanza
->Name() == kSaslSuccessName
) {
136 state_
= State::WAIT_STREAM_HEADER_AFTER_AUTH
;
138 "<iq type=\"set\" id=\"0\">"
139 "<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\">"
140 "<resource>chromoting</resource>"
143 "<iq type=\"set\" id=\"1\">"
144 "<session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"/>"
147 OnError(SignalStrategy::AUTHENTICATION_FAILED
);
152 case State::WAIT_STREAM_HEADER_AFTER_AUTH
:
153 if (stanza
->Name() == kJabberFeaturesName
&&
154 stanza
->FirstNamed(kXmppBindName
) != nullptr) {
155 state_
= State::WAIT_BIND_RESULT
;
157 LOG(ERROR
) << "Server doesn't support bind after authentication.";
158 OnError(SignalStrategy::PROTOCOL_ERROR
);
162 case State::WAIT_BIND_RESULT
: {
163 buzz::XmlElement
* bind
= stanza
->FirstNamed(kXmppBindName
);
164 buzz::XmlElement
* jid
= bind
? bind
->FirstNamed(kXmppJidName
) : nullptr;
165 if (stanza
->Attr(buzz::QName("", "id")) != "0" ||
166 stanza
->Attr(buzz::QName("", "type")) != "result" || !jid
) {
167 LOG(ERROR
) << "Received unexpected response to bind: " << stanza
->Str();
168 OnError(SignalStrategy::PROTOCOL_ERROR
);
171 jid_
= jid
->BodyText();
172 state_
= State::WAIT_SESSION_IQ_RESULT
;
176 case State::WAIT_SESSION_IQ_RESULT
:
177 if (stanza
->Name() != kXmppIqName
||
178 stanza
->Attr(buzz::QName("", "id")) != "1" ||
179 stanza
->Attr(buzz::QName("", "type")) != "result") {
180 LOG(ERROR
) << "Failed to start session: " << stanza
->Str();
181 OnError(SignalStrategy::PROTOCOL_ERROR
);
184 state_
= State::DONE
;
185 delegate_
->OnHandshakeDone(jid_
, stream_parser_
.Pass());
194 void XmppLoginHandler::OnTlsStarted() {
195 DCHECK(state_
== State::STARTING_TLS
);
196 state_
= State::WAIT_STREAM_HEADER_AFTER_TLS
;
197 StartAuthHandshake();
200 void XmppLoginHandler::StartAuthHandshake() {
201 DCHECK(state_
== State::WAIT_STREAM_HEADER_AFTER_TLS
);
205 std::string("\0", 1) + username_
+ std::string("\0", 1) + auth_token_
,
208 "<auth xmlns=\"" + std::string(kXmppSaslNs
) + "\" "
209 "mechanism=\"" + "X-OAUTH2" + "\" "
210 "auth:service=\"oauth2\" "
211 "auth:allow-generated-jid=\"true\" "
212 "auth:client-uses-full-bind-result=\"true\" "
213 "auth:allow-non-google-login=\"true\" "
214 "xmlns:auth=\"http://www.google.com/talk/protocol/auth\">" +
219 void XmppLoginHandler::OnParserError() {
220 OnError(SignalStrategy::PROTOCOL_ERROR
);
223 void XmppLoginHandler::StartStream(const std::string
& first_message
) {
224 delegate_
->SendMessage("<stream:stream to=\"" + server_
+
225 "\" version=\"1.0\" xmlns=\"jabber:client\" "
226 "xmlns:stream=\"http://etherx.jabber.org/streams\">" +
228 stream_parser_
.reset(new XmppStreamParser());
229 stream_parser_
->SetCallbacks(
230 base::Bind(&XmppLoginHandler::OnStanza
, base::Unretained(this)),
231 base::Bind(&XmppLoginHandler::OnParserError
, base::Unretained(this)));
234 void XmppLoginHandler::OnError(SignalStrategy::Error error
) {
235 if (state_
!= State::ERROR
) {
236 state_
= State::ERROR
;
237 delegate_
->OnLoginHandlerError(error
);
241 } // namespace remoting