| OLD | NEW |
| (Empty) | |
| 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. |
| 4 |
| 5 #include "remoting/signaling/xmpp_login_handler.h" |
| 6 |
| 7 #include "base/base64.h" |
| 8 #include "base/bind.h" |
| 9 #include "base/logging.h" |
| 10 #include "remoting/signaling/xmpp_stream_parser.h" |
| 11 #include "third_party/webrtc/libjingle/xmllite/xmlelement.h" |
| 12 |
| 13 // Undefine SendMessage and ERROR defined in Windows headers. |
| 14 #ifdef SendMessage |
| 15 #undef SendMessage |
| 16 #endif |
| 17 |
| 18 #ifdef ERROR |
| 19 #undef ERROR |
| 20 #endif |
| 21 |
| 22 namespace remoting { |
| 23 |
| 24 const char kOAuthMechanism[] = "X-OAUTH2"; |
| 25 |
| 26 buzz::StaticQName kXmppIqName = {"jabber:client", "iq"}; |
| 27 |
| 28 char kXmppBindNs[] = "urn:ietf:params:xml:ns:xmpp-bind"; |
| 29 buzz::StaticQName kXmppBindName = {kXmppBindNs, "bind"}; |
| 30 buzz::StaticQName kXmppJidName = {kXmppBindNs, "jid"}; |
| 31 |
| 32 buzz::StaticQName kJabberFeaturesName = {"http://etherx.jabber.org/streams", |
| 33 "features"}; |
| 34 |
| 35 char kXmppTlsNs[] = "urn:ietf:params:xml:ns:xmpp-tls"; |
| 36 buzz::StaticQName kStartTlsName = {kXmppTlsNs, "starttls"}; |
| 37 buzz::StaticQName kTlsProceedName = {kXmppTlsNs, "proceed"}; |
| 38 |
| 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"}; |
| 43 |
| 44 XmppLoginHandler::XmppLoginHandler(const std::string& server, |
| 45 const std::string& username, |
| 46 const std::string& auth_token, |
| 47 TlsMode tls_mode, |
| 48 Delegate* delegate) |
| 49 : server_(server), |
| 50 username_(username), |
| 51 auth_token_(auth_token), |
| 52 tls_mode_(tls_mode), |
| 53 delegate_(delegate), |
| 54 state_(State::INIT) { |
| 55 } |
| 56 |
| 57 XmppLoginHandler::~XmppLoginHandler() { |
| 58 } |
| 59 |
| 60 void XmppLoginHandler::Start() { |
| 61 switch (tls_mode_) { |
| 62 case TlsMode::NO_TLS: |
| 63 state_ = State::WAIT_STREAM_HEADER_AFTER_TLS; |
| 64 StartAuthHandshake(); |
| 65 break; |
| 66 case TlsMode::WITH_HANDSHAKE: |
| 67 state_ = State::WAIT_STREAM_HEADER; |
| 68 StartStream("<starttls xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\"/>"); |
| 69 break; |
| 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(); |
| 74 break; |
| 75 } |
| 76 } |
| 77 |
| 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); |
| 82 } |
| 83 |
| 84 void XmppLoginHandler::OnStanza(scoped_ptr<buzz::XmlElement> stanza) { |
| 85 switch (state_) { |
| 86 case State::WAIT_STREAM_HEADER: { |
| 87 if (stanza->Name() == kJabberFeaturesName && |
| 88 stanza->FirstNamed(kStartTlsName) != nullptr) { |
| 89 state_ = State::WAIT_STARTTLS_RESPONSE; |
| 90 } else { |
| 91 LOG(ERROR) << "Server doesn't support TLS."; |
| 92 OnError(SignalStrategy::PROTOCOL_ERROR); |
| 93 } |
| 94 break; |
| 95 } |
| 96 |
| 97 case State::WAIT_STARTTLS_RESPONSE: { |
| 98 if (stanza->Name() == kTlsProceedName) { |
| 99 state_ = State::STARTING_TLS; |
| 100 delegate_->StartTls(); |
| 101 } else { |
| 102 LOG(ERROR) << "Failed to start TLS: " << stanza->Str(); |
| 103 OnError(SignalStrategy::PROTOCOL_ERROR); |
| 104 } |
| 105 break; |
| 106 } |
| 107 |
| 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; |
| 118 break; |
| 119 } |
| 120 } |
| 121 } |
| 122 |
| 123 if (!oauth_supported) { |
| 124 LOG(ERROR) << kOAuthMechanism |
| 125 << " auth mechanism is not supported by the server."; |
| 126 OnError(SignalStrategy::PROTOCOL_ERROR); |
| 127 return; |
| 128 } |
| 129 |
| 130 state_ = State::WAIT_AUTH_RESULT; |
| 131 break; |
| 132 } |
| 133 |
| 134 case State::WAIT_AUTH_RESULT: { |
| 135 if (stanza->Name() == kSaslSuccessName) { |
| 136 state_ = State::WAIT_STREAM_HEADER_AFTER_AUTH; |
| 137 StartStream( |
| 138 "<iq type=\"set\" id=\"0\">" |
| 139 "<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\">" |
| 140 "<resource>chromoting</resource>" |
| 141 "</bind>" |
| 142 "</iq>" |
| 143 "<iq type=\"set\" id=\"1\">" |
| 144 "<session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"/>" |
| 145 "</iq>"); |
| 146 } else { |
| 147 OnError(SignalStrategy::AUTHENTICATION_FAILED); |
| 148 } |
| 149 break; |
| 150 } |
| 151 |
| 152 case State::WAIT_STREAM_HEADER_AFTER_AUTH: |
| 153 if (stanza->Name() == kJabberFeaturesName && |
| 154 stanza->FirstNamed(kXmppBindName) != nullptr) { |
| 155 state_ = State::WAIT_BIND_RESULT; |
| 156 } else { |
| 157 LOG(ERROR) << "Server doesn't support bind after authentication."; |
| 158 OnError(SignalStrategy::PROTOCOL_ERROR); |
| 159 } |
| 160 break; |
| 161 |
| 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); |
| 169 return; |
| 170 } |
| 171 jid_ = jid->BodyText(); |
| 172 state_ = State::WAIT_SESSION_IQ_RESULT; |
| 173 break; |
| 174 } |
| 175 |
| 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); |
| 182 return; |
| 183 } |
| 184 state_ = State::DONE; |
| 185 delegate_->OnHandshakeDone(jid_, stream_parser_.Pass()); |
| 186 break; |
| 187 |
| 188 default: |
| 189 NOTREACHED(); |
| 190 break; |
| 191 } |
| 192 } |
| 193 |
| 194 void XmppLoginHandler::OnTlsStarted() { |
| 195 DCHECK(state_ == State::STARTING_TLS); |
| 196 state_ = State::WAIT_STREAM_HEADER_AFTER_TLS; |
| 197 StartAuthHandshake(); |
| 198 } |
| 199 |
| 200 void XmppLoginHandler::StartAuthHandshake() { |
| 201 DCHECK(state_ == State::WAIT_STREAM_HEADER_AFTER_TLS); |
| 202 |
| 203 std::string cookie; |
| 204 base::Base64Encode( |
| 205 std::string("\0", 1) + username_ + std::string("\0", 1) + auth_token_, |
| 206 &cookie); |
| 207 StartStream( |
| 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\">" + |
| 215 cookie + |
| 216 "</auth>"); |
| 217 }; |
| 218 |
| 219 void XmppLoginHandler::OnParserError() { |
| 220 OnError(SignalStrategy::PROTOCOL_ERROR); |
| 221 } |
| 222 |
| 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\">" + |
| 227 first_message); |
| 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))); |
| 232 } |
| 233 |
| 234 void XmppLoginHandler::OnError(SignalStrategy::Error error) { |
| 235 if (state_ != State::ERROR) { |
| 236 state_ = State::ERROR; |
| 237 delegate_->OnLoginHandlerError(error); |
| 238 } |
| 239 } |
| 240 |
| 241 } // namespace remoting |
| OLD | NEW |