/* * libjingle * Copyright 2004--2005, Google Inc. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "talk/xmpp/xmpplogintask.h" #include #include #include "talk/base/base64.h" #include "talk/base/common.h" #include "talk/xmllite/xmlelement.h" #include "talk/xmpp/constants.h" #include "talk/xmpp/jid.h" #include "talk/xmpp/saslmechanism.h" #include "talk/xmpp/xmppengineimpl.h" using talk_base::ConstantLabel; namespace buzz { #ifdef _DEBUG 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 // _DEBUG 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()), 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 _DEBUG LOG(LS_VERBOSE) << "XmppLoginTask::Advance - " << talk_base::ErrorName(state_, LOGINTASK_STATES); #endif // _DEBUG 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 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(); } }