| Index: webrtc/libjingle/xmpp/xmpplogintask.cc
|
| diff --git a/webrtc/libjingle/xmpp/xmpplogintask.cc b/webrtc/libjingle/xmpp/xmpplogintask.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..e39713d463b2ed3f5a4b3239bc36463c93921995
|
| --- /dev/null
|
| +++ b/webrtc/libjingle/xmpp/xmpplogintask.cc
|
| @@ -0,0 +1,380 @@
|
| +/*
|
| + * Copyright 2004 The WebRTC Project Authors. All rights reserved.
|
| + *
|
| + * Use of this source code is governed by a BSD-style license
|
| + * that can be found in the LICENSE file in the root of the source
|
| + * tree. An additional intellectual property rights grant can be found
|
| + * in the file PATENTS. All contributing project authors may
|
| + * be found in the AUTHORS file in the root of the source tree.
|
| + */
|
| +
|
| +#include "webrtc/libjingle/xmpp/xmpplogintask.h"
|
| +
|
| +#include <string>
|
| +#include <vector>
|
| +
|
| +#include "webrtc/libjingle/xmllite/xmlelement.h"
|
| +#include "webrtc/libjingle/xmpp/constants.h"
|
| +#include "webrtc/libjingle/xmpp/jid.h"
|
| +#include "webrtc/libjingle/xmpp/saslmechanism.h"
|
| +#include "webrtc/libjingle/xmpp/xmppengineimpl.h"
|
| +#include "webrtc/base/base64.h"
|
| +#include "webrtc/base/common.h"
|
| +
|
| +using rtc::ConstantLabel;
|
| +
|
| +namespace buzz {
|
| +
|
| +#if !defined(NDEBUG)
|
| +const ConstantLabel XmppLoginTask::LOGINTASK_STATES[] = {
|
| + KLABEL(LOGINSTATE_INIT),
|
| + KLABEL(LOGINSTATE_STREAMSTART_SENT),
|
| + KLABEL(LOGINSTATE_STARTED_XMPP),
|
| + KLABEL(LOGINSTATE_TLS_INIT),
|
| + KLABEL(LOGINSTATE_AUTH_INIT),
|
| + KLABEL(LOGINSTATE_BIND_INIT),
|
| + KLABEL(LOGINSTATE_TLS_REQUESTED),
|
| + KLABEL(LOGINSTATE_SASL_RUNNING),
|
| + KLABEL(LOGINSTATE_BIND_REQUESTED),
|
| + KLABEL(LOGINSTATE_SESSION_REQUESTED),
|
| + KLABEL(LOGINSTATE_DONE),
|
| + LASTLABEL
|
| +};
|
| +#endif
|
| +XmppLoginTask::XmppLoginTask(XmppEngineImpl * pctx) :
|
| + pctx_(pctx),
|
| + authNeeded_(true),
|
| + allowNonGoogleLogin_(true),
|
| + state_(LOGINSTATE_INIT),
|
| + pelStanza_(NULL),
|
| + isStart_(false),
|
| + iqId_(STR_EMPTY),
|
| + pelFeatures_(),
|
| + fullJid_(STR_EMPTY),
|
| + streamId_(STR_EMPTY),
|
| + pvecQueuedStanzas_(new std::vector<XmlElement *>()),
|
| + sasl_mech_() {
|
| +}
|
| +
|
| +XmppLoginTask::~XmppLoginTask() {
|
| + for (size_t i = 0; i < pvecQueuedStanzas_->size(); i += 1)
|
| + delete (*pvecQueuedStanzas_)[i];
|
| +}
|
| +
|
| +void
|
| +XmppLoginTask::IncomingStanza(const XmlElement *element, bool isStart) {
|
| + pelStanza_ = element;
|
| + isStart_ = isStart;
|
| + Advance();
|
| + pelStanza_ = NULL;
|
| + isStart_ = false;
|
| +}
|
| +
|
| +const XmlElement *
|
| +XmppLoginTask::NextStanza() {
|
| + const XmlElement * result = pelStanza_;
|
| + pelStanza_ = NULL;
|
| + return result;
|
| +}
|
| +
|
| +bool
|
| +XmppLoginTask::Advance() {
|
| +
|
| + for (;;) {
|
| +
|
| + const XmlElement * element = NULL;
|
| +
|
| +#if !defined(NDEBUG)
|
| + LOG(LS_VERBOSE) << "XmppLoginTask::Advance - "
|
| + << rtc::ErrorName(state_, LOGINTASK_STATES);
|
| +#endif
|
| +
|
| + switch (state_) {
|
| +
|
| + case LOGINSTATE_INIT: {
|
| + pctx_->RaiseReset();
|
| + pelFeatures_.reset(NULL);
|
| +
|
| + // The proper domain to verify against is the real underlying
|
| + // domain - i.e., the domain that owns the JID. Our XmppEngineImpl
|
| + // also allows matching against a proxy domain instead, if it is told
|
| + // to do so - see the implementation of XmppEngineImpl::StartTls and
|
| + // XmppEngine::SetTlsServerDomain to see how you can use that feature
|
| + pctx_->InternalSendStart(pctx_->user_jid_.domain());
|
| + state_ = LOGINSTATE_STREAMSTART_SENT;
|
| + break;
|
| + }
|
| +
|
| + case LOGINSTATE_STREAMSTART_SENT: {
|
| + if (NULL == (element = NextStanza()))
|
| + return true;
|
| +
|
| + if (!isStart_ || !HandleStartStream(element))
|
| + return Failure(XmppEngine::ERROR_VERSION);
|
| +
|
| + state_ = LOGINSTATE_STARTED_XMPP;
|
| + return true;
|
| + }
|
| +
|
| + case LOGINSTATE_STARTED_XMPP: {
|
| + if (NULL == (element = NextStanza()))
|
| + return true;
|
| +
|
| + if (!HandleFeatures(element))
|
| + return Failure(XmppEngine::ERROR_VERSION);
|
| +
|
| + bool tls_present = (GetFeature(QN_TLS_STARTTLS) != NULL);
|
| + // Error if TLS required but not present.
|
| + if (pctx_->tls_option_ == buzz::TLS_REQUIRED && !tls_present) {
|
| + return Failure(XmppEngine::ERROR_TLS);
|
| + }
|
| + // Use TLS if required or enabled, and also available
|
| + if ((pctx_->tls_option_ == buzz::TLS_REQUIRED ||
|
| + pctx_->tls_option_ == buzz::TLS_ENABLED) && tls_present) {
|
| + state_ = LOGINSTATE_TLS_INIT;
|
| + continue;
|
| + }
|
| +
|
| + if (authNeeded_) {
|
| + state_ = LOGINSTATE_AUTH_INIT;
|
| + continue;
|
| + }
|
| +
|
| + state_ = LOGINSTATE_BIND_INIT;
|
| + continue;
|
| + }
|
| +
|
| + case LOGINSTATE_TLS_INIT: {
|
| + const XmlElement * pelTls = GetFeature(QN_TLS_STARTTLS);
|
| + if (!pelTls)
|
| + return Failure(XmppEngine::ERROR_TLS);
|
| +
|
| + XmlElement el(QN_TLS_STARTTLS, true);
|
| + pctx_->InternalSendStanza(&el);
|
| + state_ = LOGINSTATE_TLS_REQUESTED;
|
| + continue;
|
| + }
|
| +
|
| + case LOGINSTATE_TLS_REQUESTED: {
|
| + if (NULL == (element = NextStanza()))
|
| + return true;
|
| + if (element->Name() != QN_TLS_PROCEED)
|
| + return Failure(XmppEngine::ERROR_TLS);
|
| +
|
| + // The proper domain to verify against is the real underlying
|
| + // domain - i.e., the domain that owns the JID. Our XmppEngineImpl
|
| + // also allows matching against a proxy domain instead, if it is told
|
| + // to do so - see the implementation of XmppEngineImpl::StartTls and
|
| + // XmppEngine::SetTlsServerDomain to see how you can use that feature
|
| + pctx_->StartTls(pctx_->user_jid_.domain());
|
| + pctx_->tls_option_ = buzz::TLS_ENABLED;
|
| + state_ = LOGINSTATE_INIT;
|
| + continue;
|
| + }
|
| +
|
| + case LOGINSTATE_AUTH_INIT: {
|
| + const XmlElement * pelSaslAuth = GetFeature(QN_SASL_MECHANISMS);
|
| + if (!pelSaslAuth) {
|
| + return Failure(XmppEngine::ERROR_AUTH);
|
| + }
|
| +
|
| + // Collect together the SASL auth mechanisms presented by the server
|
| + std::vector<std::string> mechanisms;
|
| + for (const XmlElement * pelMech =
|
| + pelSaslAuth->FirstNamed(QN_SASL_MECHANISM);
|
| + pelMech;
|
| + pelMech = pelMech->NextNamed(QN_SASL_MECHANISM)) {
|
| +
|
| + mechanisms.push_back(pelMech->BodyText());
|
| + }
|
| +
|
| + // Given all the mechanisms, choose the best
|
| + std::string choice(pctx_->ChooseBestSaslMechanism(mechanisms, pctx_->IsEncrypted()));
|
| + if (choice.empty()) {
|
| + return Failure(XmppEngine::ERROR_AUTH);
|
| + }
|
| +
|
| + // No recognized auth mechanism - that's an error
|
| + sasl_mech_.reset(pctx_->GetSaslMechanism(choice));
|
| + if (!sasl_mech_) {
|
| + return Failure(XmppEngine::ERROR_AUTH);
|
| + }
|
| +
|
| + // OK, let's start it.
|
| + XmlElement * auth = sasl_mech_->StartSaslAuth();
|
| + if (auth == NULL) {
|
| + return Failure(XmppEngine::ERROR_AUTH);
|
| + }
|
| + if (allowNonGoogleLogin_) {
|
| + // Setting the following two attributes is required to support
|
| + // non-google ids.
|
| +
|
| + // Allow login with non-google id accounts.
|
| + auth->SetAttr(QN_GOOGLE_ALLOW_NON_GOOGLE_ID_XMPP_LOGIN, "true");
|
| +
|
| + // Allow login with either the non-google id or the friendly email.
|
| + auth->SetAttr(QN_GOOGLE_AUTH_CLIENT_USES_FULL_BIND_RESULT, "true");
|
| + }
|
| +
|
| + pctx_->InternalSendStanza(auth);
|
| + delete auth;
|
| + state_ = LOGINSTATE_SASL_RUNNING;
|
| + continue;
|
| + }
|
| +
|
| + case LOGINSTATE_SASL_RUNNING: {
|
| + if (NULL == (element = NextStanza()))
|
| + return true;
|
| + if (element->Name().Namespace() != NS_SASL)
|
| + return Failure(XmppEngine::ERROR_AUTH);
|
| + if (element->Name() == QN_SASL_CHALLENGE) {
|
| + XmlElement * response = sasl_mech_->HandleSaslChallenge(element);
|
| + if (response == NULL) {
|
| + return Failure(XmppEngine::ERROR_AUTH);
|
| + }
|
| + pctx_->InternalSendStanza(response);
|
| + delete response;
|
| + state_ = LOGINSTATE_SASL_RUNNING;
|
| + continue;
|
| + }
|
| + if (element->Name() != QN_SASL_SUCCESS) {
|
| + return Failure(XmppEngine::ERROR_UNAUTHORIZED);
|
| + }
|
| +
|
| + // Authenticated!
|
| + authNeeded_ = false;
|
| + state_ = LOGINSTATE_INIT;
|
| + continue;
|
| + }
|
| +
|
| + case LOGINSTATE_BIND_INIT: {
|
| + const XmlElement * pelBindFeature = GetFeature(QN_BIND_BIND);
|
| + const XmlElement * pelSessionFeature = GetFeature(QN_SESSION_SESSION);
|
| + if (!pelBindFeature || !pelSessionFeature)
|
| + return Failure(XmppEngine::ERROR_BIND);
|
| +
|
| + XmlElement iq(QN_IQ);
|
| + iq.AddAttr(QN_TYPE, "set");
|
| +
|
| + iqId_ = pctx_->NextId();
|
| + iq.AddAttr(QN_ID, iqId_);
|
| + iq.AddElement(new XmlElement(QN_BIND_BIND, true));
|
| +
|
| + if (pctx_->requested_resource_ != STR_EMPTY) {
|
| + iq.AddElement(new XmlElement(QN_BIND_RESOURCE), 1);
|
| + iq.AddText(pctx_->requested_resource_, 2);
|
| + }
|
| + pctx_->InternalSendStanza(&iq);
|
| + state_ = LOGINSTATE_BIND_REQUESTED;
|
| + continue;
|
| + }
|
| +
|
| + case LOGINSTATE_BIND_REQUESTED: {
|
| + if (NULL == (element = NextStanza()))
|
| + return true;
|
| +
|
| + if (element->Name() != QN_IQ || element->Attr(QN_ID) != iqId_ ||
|
| + element->Attr(QN_TYPE) == "get" || element->Attr(QN_TYPE) == "set")
|
| + return true;
|
| +
|
| + if (element->Attr(QN_TYPE) != "result" || element->FirstElement() == NULL ||
|
| + element->FirstElement()->Name() != QN_BIND_BIND)
|
| + return Failure(XmppEngine::ERROR_BIND);
|
| +
|
| + fullJid_ = Jid(element->FirstElement()->TextNamed(QN_BIND_JID));
|
| + if (!fullJid_.IsFull()) {
|
| + return Failure(XmppEngine::ERROR_BIND);
|
| + }
|
| +
|
| + // now request session
|
| + XmlElement iq(QN_IQ);
|
| + iq.AddAttr(QN_TYPE, "set");
|
| +
|
| + iqId_ = pctx_->NextId();
|
| + iq.AddAttr(QN_ID, iqId_);
|
| + iq.AddElement(new XmlElement(QN_SESSION_SESSION, true));
|
| + pctx_->InternalSendStanza(&iq);
|
| +
|
| + state_ = LOGINSTATE_SESSION_REQUESTED;
|
| + continue;
|
| + }
|
| +
|
| + case LOGINSTATE_SESSION_REQUESTED: {
|
| + if (NULL == (element = NextStanza()))
|
| + return true;
|
| + if (element->Name() != QN_IQ || element->Attr(QN_ID) != iqId_ ||
|
| + element->Attr(QN_TYPE) == "get" || element->Attr(QN_TYPE) == "set")
|
| + return false;
|
| +
|
| + if (element->Attr(QN_TYPE) != "result")
|
| + return Failure(XmppEngine::ERROR_BIND);
|
| +
|
| + pctx_->SignalBound(fullJid_);
|
| + FlushQueuedStanzas();
|
| + state_ = LOGINSTATE_DONE;
|
| + return true;
|
| + }
|
| +
|
| + case LOGINSTATE_DONE:
|
| + return false;
|
| + }
|
| + }
|
| +}
|
| +
|
| +bool
|
| +XmppLoginTask::HandleStartStream(const XmlElement *element) {
|
| +
|
| + if (element->Name() != QN_STREAM_STREAM)
|
| + return false;
|
| +
|
| + if (element->Attr(QN_XMLNS) != "jabber:client")
|
| + return false;
|
| +
|
| + if (element->Attr(QN_VERSION) != "1.0")
|
| + return false;
|
| +
|
| + if (!element->HasAttr(QN_ID))
|
| + return false;
|
| +
|
| + streamId_ = element->Attr(QN_ID);
|
| +
|
| + return true;
|
| +}
|
| +
|
| +bool
|
| +XmppLoginTask::HandleFeatures(const XmlElement *element) {
|
| + if (element->Name() != QN_STREAM_FEATURES)
|
| + return false;
|
| +
|
| + pelFeatures_.reset(new XmlElement(*element));
|
| + return true;
|
| +}
|
| +
|
| +const XmlElement *
|
| +XmppLoginTask::GetFeature(const QName & name) {
|
| + return pelFeatures_->FirstNamed(name);
|
| +}
|
| +
|
| +bool
|
| +XmppLoginTask::Failure(XmppEngine::Error reason) {
|
| + state_ = LOGINSTATE_DONE;
|
| + pctx_->SignalError(reason, 0);
|
| + return false;
|
| +}
|
| +
|
| +void
|
| +XmppLoginTask::OutgoingStanza(const XmlElement * element) {
|
| + XmlElement * pelCopy = new XmlElement(*element);
|
| + pvecQueuedStanzas_->push_back(pelCopy);
|
| +}
|
| +
|
| +void
|
| +XmppLoginTask::FlushQueuedStanzas() {
|
| + for (size_t i = 0; i < pvecQueuedStanzas_->size(); i += 1) {
|
| + pctx_->InternalSendStanza((*pvecQueuedStanzas_)[i]);
|
| + delete (*pvecQueuedStanzas_)[i];
|
| + }
|
| + pvecQueuedStanzas_->clear();
|
| +}
|
| +
|
| +}
|
|
|