Home | History | Annotate | Download | only in xmpp
      1 /*
      2  * libjingle
      3  * Copyright 2004--2005, Google Inc.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions are met:
      7  *
      8  *  1. Redistributions of source code must retain the above copyright notice,
      9  *     this list of conditions and the following disclaimer.
     10  *  2. Redistributions in binary form must reproduce the above copyright notice,
     11  *     this list of conditions and the following disclaimer in the documentation
     12  *     and/or other materials provided with the distribution.
     13  *  3. The name of the author may not be used to endorse or promote products
     14  *     derived from this software without specific prior written permission.
     15  *
     16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
     17  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
     18  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
     19  * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     20  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
     22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
     23  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
     24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
     25  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     26  */
     27 
     28 #include "talk/xmpp/xmpplogintask.h"
     29 
     30 #include <string>
     31 #include <vector>
     32 
     33 #include "talk/base/base64.h"
     34 #include "talk/base/common.h"
     35 #include "talk/xmllite/xmlelement.h"
     36 #include "talk/xmpp/constants.h"
     37 #include "talk/xmpp/jid.h"
     38 #include "talk/xmpp/saslmechanism.h"
     39 #include "talk/xmpp/xmppengineimpl.h"
     40 
     41 using talk_base::ConstantLabel;
     42 
     43 namespace buzz {
     44 
     45 #ifdef _DEBUG
     46 const ConstantLabel XmppLoginTask::LOGINTASK_STATES[] = {
     47   KLABEL(LOGINSTATE_INIT),
     48   KLABEL(LOGINSTATE_STREAMSTART_SENT),
     49   KLABEL(LOGINSTATE_STARTED_XMPP),
     50   KLABEL(LOGINSTATE_TLS_INIT),
     51   KLABEL(LOGINSTATE_AUTH_INIT),
     52   KLABEL(LOGINSTATE_BIND_INIT),
     53   KLABEL(LOGINSTATE_TLS_REQUESTED),
     54   KLABEL(LOGINSTATE_SASL_RUNNING),
     55   KLABEL(LOGINSTATE_BIND_REQUESTED),
     56   KLABEL(LOGINSTATE_SESSION_REQUESTED),
     57   KLABEL(LOGINSTATE_DONE),
     58   LASTLABEL
     59 };
     60 #endif  // _DEBUG
     61 XmppLoginTask::XmppLoginTask(XmppEngineImpl * pctx) :
     62   pctx_(pctx),
     63   authNeeded_(true),
     64   allowNonGoogleLogin_(true),
     65   state_(LOGINSTATE_INIT),
     66   pelStanza_(NULL),
     67   isStart_(false),
     68   iqId_(STR_EMPTY),
     69   pelFeatures_(NULL),
     70   fullJid_(STR_EMPTY),
     71   streamId_(STR_EMPTY),
     72   pvecQueuedStanzas_(new std::vector<XmlElement *>()),
     73   sasl_mech_(NULL) {
     74 }
     75 
     76 XmppLoginTask::~XmppLoginTask() {
     77   for (size_t i = 0; i < pvecQueuedStanzas_->size(); i += 1)
     78     delete (*pvecQueuedStanzas_)[i];
     79 }
     80 
     81 void
     82 XmppLoginTask::IncomingStanza(const XmlElement *element, bool isStart) {
     83   pelStanza_ = element;
     84   isStart_ = isStart;
     85   Advance();
     86   pelStanza_ = NULL;
     87   isStart_ = false;
     88 }
     89 
     90 const XmlElement *
     91 XmppLoginTask::NextStanza() {
     92   const XmlElement * result = pelStanza_;
     93   pelStanza_ = NULL;
     94   return result;
     95 }
     96 
     97 bool
     98 XmppLoginTask::Advance() {
     99 
    100   for (;;) {
    101 
    102     const XmlElement * element = NULL;
    103 
    104 #if _DEBUG
    105     LOG(LS_VERBOSE) << "XmppLoginTask::Advance - "
    106       << talk_base::ErrorName(state_, LOGINTASK_STATES);
    107 #endif  // _DEBUG
    108 
    109     switch (state_) {
    110 
    111       case LOGINSTATE_INIT: {
    112         pctx_->RaiseReset();
    113         pelFeatures_.reset(NULL);
    114 
    115         // The proper domain to verify against is the real underlying
    116         // domain - i.e., the domain that owns the JID.  Our XmppEngineImpl
    117         // also allows matching against a proxy domain instead, if it is told
    118         // to do so - see the implementation of XmppEngineImpl::StartTls and
    119         // XmppEngine::SetTlsServerDomain to see how you can use that feature
    120         pctx_->InternalSendStart(pctx_->user_jid_.domain());
    121         state_ = LOGINSTATE_STREAMSTART_SENT;
    122         break;
    123       }
    124 
    125       case LOGINSTATE_STREAMSTART_SENT: {
    126         if (NULL == (element = NextStanza()))
    127           return true;
    128 
    129         if (!isStart_ || !HandleStartStream(element))
    130           return Failure(XmppEngine::ERROR_VERSION);
    131 
    132         state_ = LOGINSTATE_STARTED_XMPP;
    133         return true;
    134       }
    135 
    136       case LOGINSTATE_STARTED_XMPP: {
    137         if (NULL == (element = NextStanza()))
    138           return true;
    139 
    140         if (!HandleFeatures(element))
    141           return Failure(XmppEngine::ERROR_VERSION);
    142 
    143         bool tls_present = (GetFeature(QN_TLS_STARTTLS) != NULL);
    144         // Error if TLS required but not present.
    145         if (pctx_->tls_option_ == buzz::TLS_REQUIRED && !tls_present) {
    146           return Failure(XmppEngine::ERROR_TLS);
    147         }
    148         // Use TLS if required or enabled, and also available
    149         if ((pctx_->tls_option_ == buzz::TLS_REQUIRED ||
    150             pctx_->tls_option_ == buzz::TLS_ENABLED) && tls_present) {
    151           state_ = LOGINSTATE_TLS_INIT;
    152           continue;
    153         }
    154 
    155         if (authNeeded_) {
    156           state_ = LOGINSTATE_AUTH_INIT;
    157           continue;
    158         }
    159 
    160         state_ = LOGINSTATE_BIND_INIT;
    161         continue;
    162       }
    163 
    164       case LOGINSTATE_TLS_INIT: {
    165         const XmlElement * pelTls = GetFeature(QN_TLS_STARTTLS);
    166         if (!pelTls)
    167           return Failure(XmppEngine::ERROR_TLS);
    168 
    169         XmlElement el(QN_TLS_STARTTLS, true);
    170         pctx_->InternalSendStanza(&el);
    171         state_ = LOGINSTATE_TLS_REQUESTED;
    172         continue;
    173       }
    174 
    175       case LOGINSTATE_TLS_REQUESTED: {
    176         if (NULL == (element = NextStanza()))
    177           return true;
    178         if (element->Name() != QN_TLS_PROCEED)
    179           return Failure(XmppEngine::ERROR_TLS);
    180 
    181         // The proper domain to verify against is the real underlying
    182         // domain - i.e., the domain that owns the JID.  Our XmppEngineImpl
    183         // also allows matching against a proxy domain instead, if it is told
    184         // to do so - see the implementation of XmppEngineImpl::StartTls and
    185         // XmppEngine::SetTlsServerDomain to see how you can use that feature
    186         pctx_->StartTls(pctx_->user_jid_.domain());
    187         pctx_->tls_option_ = buzz::TLS_ENABLED;
    188         state_ = LOGINSTATE_INIT;
    189         continue;
    190       }
    191 
    192       case LOGINSTATE_AUTH_INIT: {
    193         const XmlElement * pelSaslAuth = GetFeature(QN_SASL_MECHANISMS);
    194         if (!pelSaslAuth) {
    195           return Failure(XmppEngine::ERROR_AUTH);
    196         }
    197 
    198         // Collect together the SASL auth mechanisms presented by the server
    199         std::vector<std::string> mechanisms;
    200         for (const XmlElement * pelMech =
    201              pelSaslAuth->FirstNamed(QN_SASL_MECHANISM);
    202              pelMech;
    203              pelMech = pelMech->NextNamed(QN_SASL_MECHANISM)) {
    204 
    205           mechanisms.push_back(pelMech->BodyText());
    206         }
    207 
    208         // Given all the mechanisms, choose the best
    209         std::string choice(pctx_->ChooseBestSaslMechanism(mechanisms, pctx_->IsEncrypted()));
    210         if (choice.empty()) {
    211           return Failure(XmppEngine::ERROR_AUTH);
    212         }
    213 
    214         // No recognized auth mechanism - that's an error
    215         sasl_mech_.reset(pctx_->GetSaslMechanism(choice));
    216         if (!sasl_mech_) {
    217           return Failure(XmppEngine::ERROR_AUTH);
    218         }
    219 
    220         // OK, let's start it.
    221         XmlElement * auth = sasl_mech_->StartSaslAuth();
    222         if (auth == NULL) {
    223           return Failure(XmppEngine::ERROR_AUTH);
    224         }
    225         if (allowNonGoogleLogin_) {
    226           // Setting the following two attributes is required to support
    227           // non-google ids.
    228 
    229           // Allow login with non-google id accounts.
    230           auth->SetAttr(QN_GOOGLE_ALLOW_NON_GOOGLE_ID_XMPP_LOGIN, "true");
    231 
    232           // Allow login with either the non-google id or the friendly email.
    233           auth->SetAttr(QN_GOOGLE_AUTH_CLIENT_USES_FULL_BIND_RESULT, "true");
    234         }
    235 
    236         pctx_->InternalSendStanza(auth);
    237         delete auth;
    238         state_ = LOGINSTATE_SASL_RUNNING;
    239         continue;
    240       }
    241 
    242       case LOGINSTATE_SASL_RUNNING: {
    243         if (NULL == (element = NextStanza()))
    244           return true;
    245         if (element->Name().Namespace() != NS_SASL)
    246           return Failure(XmppEngine::ERROR_AUTH);
    247         if (element->Name() == QN_SASL_CHALLENGE) {
    248           XmlElement * response = sasl_mech_->HandleSaslChallenge(element);
    249           if (response == NULL) {
    250             return Failure(XmppEngine::ERROR_AUTH);
    251           }
    252           pctx_->InternalSendStanza(response);
    253           delete response;
    254           state_ = LOGINSTATE_SASL_RUNNING;
    255           continue;
    256         }
    257         if (element->Name() != QN_SASL_SUCCESS) {
    258           return Failure(XmppEngine::ERROR_UNAUTHORIZED);
    259         }
    260 
    261         // Authenticated!
    262         authNeeded_ = false;
    263         state_ = LOGINSTATE_INIT;
    264         continue;
    265       }
    266 
    267       case LOGINSTATE_BIND_INIT: {
    268         const XmlElement * pelBindFeature = GetFeature(QN_BIND_BIND);
    269         const XmlElement * pelSessionFeature = GetFeature(QN_SESSION_SESSION);
    270         if (!pelBindFeature || !pelSessionFeature)
    271           return Failure(XmppEngine::ERROR_BIND);
    272 
    273         XmlElement iq(QN_IQ);
    274         iq.AddAttr(QN_TYPE, "set");
    275 
    276         iqId_ = pctx_->NextId();
    277         iq.AddAttr(QN_ID, iqId_);
    278         iq.AddElement(new XmlElement(QN_BIND_BIND, true));
    279 
    280         if (pctx_->requested_resource_ != STR_EMPTY) {
    281           iq.AddElement(new XmlElement(QN_BIND_RESOURCE), 1);
    282           iq.AddText(pctx_->requested_resource_, 2);
    283         }
    284         pctx_->InternalSendStanza(&iq);
    285         state_ = LOGINSTATE_BIND_REQUESTED;
    286         continue;
    287       }
    288 
    289       case LOGINSTATE_BIND_REQUESTED: {
    290         if (NULL == (element = NextStanza()))
    291           return true;
    292 
    293         if (element->Name() != QN_IQ || element->Attr(QN_ID) != iqId_ ||
    294             element->Attr(QN_TYPE) == "get" || element->Attr(QN_TYPE) == "set")
    295           return true;
    296 
    297         if (element->Attr(QN_TYPE) != "result" || element->FirstElement() == NULL ||
    298             element->FirstElement()->Name() != QN_BIND_BIND)
    299           return Failure(XmppEngine::ERROR_BIND);
    300 
    301         fullJid_ = Jid(element->FirstElement()->TextNamed(QN_BIND_JID));
    302         if (!fullJid_.IsFull()) {
    303           return Failure(XmppEngine::ERROR_BIND);
    304         }
    305 
    306         // now request session
    307         XmlElement iq(QN_IQ);
    308         iq.AddAttr(QN_TYPE, "set");
    309 
    310         iqId_ = pctx_->NextId();
    311         iq.AddAttr(QN_ID, iqId_);
    312         iq.AddElement(new XmlElement(QN_SESSION_SESSION, true));
    313         pctx_->InternalSendStanza(&iq);
    314 
    315         state_ = LOGINSTATE_SESSION_REQUESTED;
    316         continue;
    317       }
    318 
    319       case LOGINSTATE_SESSION_REQUESTED: {
    320         if (NULL == (element = NextStanza()))
    321           return true;
    322         if (element->Name() != QN_IQ || element->Attr(QN_ID) != iqId_ ||
    323             element->Attr(QN_TYPE) == "get" || element->Attr(QN_TYPE) == "set")
    324           return false;
    325 
    326         if (element->Attr(QN_TYPE) != "result")
    327           return Failure(XmppEngine::ERROR_BIND);
    328 
    329         pctx_->SignalBound(fullJid_);
    330         FlushQueuedStanzas();
    331         state_ = LOGINSTATE_DONE;
    332         return true;
    333       }
    334 
    335       case LOGINSTATE_DONE:
    336         return false;
    337     }
    338   }
    339 }
    340 
    341 bool
    342 XmppLoginTask::HandleStartStream(const XmlElement *element) {
    343 
    344   if (element->Name() != QN_STREAM_STREAM)
    345     return false;
    346 
    347   if (element->Attr(QN_XMLNS) != "jabber:client")
    348     return false;
    349 
    350   if (element->Attr(QN_VERSION) != "1.0")
    351     return false;
    352 
    353   if (!element->HasAttr(QN_ID))
    354     return false;
    355 
    356   streamId_ = element->Attr(QN_ID);
    357 
    358   return true;
    359 }
    360 
    361 bool
    362 XmppLoginTask::HandleFeatures(const XmlElement *element) {
    363   if (element->Name() != QN_STREAM_FEATURES)
    364     return false;
    365 
    366   pelFeatures_.reset(new XmlElement(*element));
    367   return true;
    368 }
    369 
    370 const XmlElement *
    371 XmppLoginTask::GetFeature(const QName & name) {
    372   return pelFeatures_->FirstNamed(name);
    373 }
    374 
    375 bool
    376 XmppLoginTask::Failure(XmppEngine::Error reason) {
    377   state_ = LOGINSTATE_DONE;
    378   pctx_->SignalError(reason, 0);
    379   return false;
    380 }
    381 
    382 void
    383 XmppLoginTask::OutgoingStanza(const XmlElement * element) {
    384   XmlElement * pelCopy = new XmlElement(*element);
    385   pvecQueuedStanzas_->push_back(pelCopy);
    386 }
    387 
    388 void
    389 XmppLoginTask::FlushQueuedStanzas() {
    390   for (size_t i = 0; i < pvecQueuedStanzas_->size(); i += 1) {
    391     pctx_->InternalSendStanza((*pvecQueuedStanzas_)[i]);
    392     delete (*pvecQueuedStanzas_)[i];
    393   }
    394   pvecQueuedStanzas_->clear();
    395 }
    396 
    397 }
    398