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 <iostream>
     29 #include <string>
     30 #include <vector>
     31 #include "talk/base/base64.h"
     32 #include "talk/base/common.h"
     33 #include "talk/xmllite/xmlelement.h"
     34 #include "talk/xmpp/constants.h"
     35 #include "talk/xmpp/jid.h"
     36 #include "talk/xmpp/saslmechanism.h"
     37 #include "talk/xmpp/xmppengineimpl.h"
     38 #include "talk/xmpp/xmpplogintask.h"
     39 
     40 using talk_base::ConstantLabel;
     41 
     42 namespace buzz {
     43 
     44 #ifdef _DEBUG
     45 const ConstantLabel XmppLoginTask::LOGINTASK_STATES[] = {
     46   KLABEL(LOGINSTATE_INIT),
     47   KLABEL(LOGINSTATE_STREAMSTART_SENT),
     48   KLABEL(LOGINSTATE_STARTED_XMPP),
     49   KLABEL(LOGINSTATE_TLS_INIT),
     50   KLABEL(LOGINSTATE_AUTH_INIT),
     51   KLABEL(LOGINSTATE_BIND_INIT),
     52   KLABEL(LOGINSTATE_TLS_REQUESTED),
     53   KLABEL(LOGINSTATE_SASL_RUNNING),
     54   KLABEL(LOGINSTATE_BIND_REQUESTED),
     55   KLABEL(LOGINSTATE_SESSION_REQUESTED),
     56   KLABEL(LOGINSTATE_DONE),
     57   LASTLABEL
     58 };
     59 #endif  // _DEBUG
     60 
     61 XmppLoginTask::XmppLoginTask(XmppEngineImpl * pctx) :
     62   pctx_(pctx),
     63   authNeeded_(true),
     64   state_(LOGINSTATE_INIT),
     65   pelStanza_(NULL),
     66   isStart_(false),
     67   iqId_(STR_EMPTY),
     68   pelFeatures_(NULL),
     69   fullJid_(STR_EMPTY),
     70   streamId_(STR_EMPTY),
     71   pvecQueuedStanzas_(new std::vector<XmlElement *>()),
     72   sasl_mech_(NULL) {
     73 }
     74 
     75 XmppLoginTask::~XmppLoginTask() {
     76   for (size_t i = 0; i < pvecQueuedStanzas_->size(); i += 1)
     77     delete (*pvecQueuedStanzas_)[i];
     78 }
     79 
     80 void
     81 XmppLoginTask::IncomingStanza(const XmlElement *element, bool isStart) {
     82   pelStanza_ = element;
     83   isStart_ = isStart;
     84   Advance();
     85   pelStanza_ = NULL;
     86   isStart_ = false;
     87 }
     88 
     89 const XmlElement *
     90 XmppLoginTask::NextStanza() {
     91   const XmlElement * result = pelStanza_;
     92   pelStanza_ = NULL;
     93   return result;
     94 }
     95 
     96 bool
     97 XmppLoginTask::Advance() {
     98 
     99   for (;;) {
    100 
    101     const XmlElement * element = NULL;
    102 
    103 #if _DEBUG
    104     LOG(LS_VERBOSE) << "XmppLoginTask::Advance - "
    105       << talk_base::ErrorName(state_, LOGINTASK_STATES);
    106 #endif  // _DEBUG
    107 
    108     switch (state_) {
    109 
    110       case LOGINSTATE_INIT: {
    111         pctx_->RaiseReset();
    112         pelFeatures_.reset(NULL);
    113 
    114         // The proper domain to verify against is the real underlying
    115         // domain - i.e., the domain that owns the JID.  Our XmppEngineImpl
    116         // also allows matching against a proxy domain instead, if it is told
    117         // to do so - see the implementation of XmppEngineImpl::StartTls and
    118         // XmppEngine::SetTlsServerDomain to see how you can use that feature
    119         pctx_->InternalSendStart(pctx_->user_jid_.domain());
    120         state_ = LOGINSTATE_STREAMSTART_SENT;
    121         break;
    122       }
    123 
    124       case LOGINSTATE_STREAMSTART_SENT: {
    125         if (NULL == (element = NextStanza()))
    126           return true;
    127 
    128         if (!isStart_ || !HandleStartStream(element))
    129           return Failure(XmppEngine::ERROR_VERSION);
    130 
    131         state_ = LOGINSTATE_STARTED_XMPP;
    132         return true;
    133       }
    134 
    135       case LOGINSTATE_STARTED_XMPP: {
    136         if (NULL == (element = NextStanza()))
    137           return true;
    138 
    139         if (!HandleFeatures(element))
    140           return Failure(XmppEngine::ERROR_VERSION);
    141 
    142         // Use TLS if forced, or if available
    143         if (pctx_->tls_needed_ || GetFeature(QN_TLS_STARTTLS) != NULL) {
    144           state_ = LOGINSTATE_TLS_INIT;
    145           continue;
    146         }
    147 
    148         if (authNeeded_) {
    149           state_ = LOGINSTATE_AUTH_INIT;
    150           continue;
    151         }
    152 
    153         state_ = LOGINSTATE_BIND_INIT;
    154         continue;
    155       }
    156 
    157       case LOGINSTATE_TLS_INIT: {
    158         const XmlElement * pelTls = GetFeature(QN_TLS_STARTTLS);
    159         if (!pelTls)
    160           return Failure(XmppEngine::ERROR_TLS);
    161 
    162         XmlElement el(QN_TLS_STARTTLS, true);
    163         pctx_->InternalSendStanza(&el);
    164         state_ = LOGINSTATE_TLS_REQUESTED;
    165         continue;
    166       }
    167 
    168       case LOGINSTATE_TLS_REQUESTED: {
    169         if (NULL == (element = NextStanza()))
    170           return true;
    171         if (element->Name() != QN_TLS_PROCEED)
    172           return Failure(XmppEngine::ERROR_TLS);
    173 
    174         // The proper domain to verify against is the real underlying
    175         // domain - i.e., the domain that owns the JID.  Our XmppEngineImpl
    176         // also allows matching against a proxy domain instead, if it is told
    177         // to do so - see the implementation of XmppEngineImpl::StartTls and
    178         // XmppEngine::SetTlsServerDomain to see how you can use that feature
    179         pctx_->StartTls(pctx_->user_jid_.domain());
    180         pctx_->tls_needed_ = false;
    181         state_ = LOGINSTATE_INIT;
    182         continue;
    183       }
    184 
    185       case LOGINSTATE_AUTH_INIT: {
    186         const XmlElement * pelSaslAuth = GetFeature(QN_SASL_MECHANISMS);
    187         if (!pelSaslAuth) {
    188           return Failure(XmppEngine::ERROR_AUTH);
    189         }
    190 
    191         // Collect together the SASL auth mechanisms presented by the server
    192         std::vector<std::string> mechanisms;
    193         for (const XmlElement * pelMech =
    194              pelSaslAuth->FirstNamed(QN_SASL_MECHANISM);
    195              pelMech;
    196              pelMech = pelMech->NextNamed(QN_SASL_MECHANISM)) {
    197 
    198           mechanisms.push_back(pelMech->BodyText());
    199         }
    200 
    201         // Given all the mechanisms, choose the best
    202         std::string choice(pctx_->ChooseBestSaslMechanism(mechanisms, pctx_->IsEncrypted()));
    203         if (choice.empty()) {
    204           return Failure(XmppEngine::ERROR_AUTH);
    205         }
    206 
    207         // No recognized auth mechanism - that's an error
    208         sasl_mech_.reset(pctx_->GetSaslMechanism(choice));
    209         if (sasl_mech_.get() == NULL) {
    210           return Failure(XmppEngine::ERROR_AUTH);
    211         }
    212 
    213         // OK, let's start it.
    214         XmlElement * auth = sasl_mech_->StartSaslAuth();
    215         if (auth == NULL) {
    216           return Failure(XmppEngine::ERROR_AUTH);
    217         }
    218 
    219         pctx_->InternalSendStanza(auth);
    220         delete auth;
    221         state_ = LOGINSTATE_SASL_RUNNING;
    222         continue;
    223       }
    224 
    225       case LOGINSTATE_SASL_RUNNING: {
    226         if (NULL == (element = NextStanza()))
    227           return true;
    228         if (element->Name().Namespace() != NS_SASL)
    229           return Failure(XmppEngine::ERROR_AUTH);
    230         if (element->Name() == QN_SASL_CHALLENGE) {
    231           XmlElement * response = sasl_mech_->HandleSaslChallenge(element);
    232           if (response == NULL) {
    233             return Failure(XmppEngine::ERROR_AUTH);
    234           }
    235           pctx_->InternalSendStanza(response);
    236           delete response;
    237           state_ = LOGINSTATE_SASL_RUNNING;
    238           continue;
    239         }
    240         if (element->Name() != QN_SASL_SUCCESS) {
    241           return Failure(XmppEngine::ERROR_UNAUTHORIZED);
    242         }
    243 
    244         // Authenticated!
    245         authNeeded_ = false;
    246         state_ = LOGINSTATE_INIT;
    247         continue;
    248       }
    249 
    250       case LOGINSTATE_BIND_INIT: {
    251         const XmlElement * pelBindFeature = GetFeature(QN_BIND_BIND);
    252         const XmlElement * pelSessionFeature = GetFeature(QN_SESSION_SESSION);
    253         if (!pelBindFeature || !pelSessionFeature)
    254           return Failure(XmppEngine::ERROR_BIND);
    255 
    256         XmlElement iq(QN_IQ);
    257         iq.AddAttr(QN_TYPE, "set");
    258 
    259         iqId_ = pctx_->NextId();
    260         iq.AddAttr(QN_ID, iqId_);
    261         iq.AddElement(new XmlElement(QN_BIND_BIND, true));
    262 
    263         if (pctx_->requested_resource_ != STR_EMPTY) {
    264           iq.AddElement(new XmlElement(QN_BIND_RESOURCE), 1);
    265           iq.AddText(pctx_->requested_resource_, 2);
    266         }
    267         pctx_->InternalSendStanza(&iq);
    268         state_ = LOGINSTATE_BIND_REQUESTED;
    269         continue;
    270       }
    271 
    272       case LOGINSTATE_BIND_REQUESTED: {
    273         if (NULL == (element = NextStanza()))
    274           return true;
    275 
    276         if (element->Name() != QN_IQ || element->Attr(QN_ID) != iqId_ ||
    277             element->Attr(QN_TYPE) == "get" || element->Attr(QN_TYPE) == "set")
    278           return true;
    279 
    280         if (element->Attr(QN_TYPE) != "result" || element->FirstElement() == NULL ||
    281             element->FirstElement()->Name() != QN_BIND_BIND)
    282           return Failure(XmppEngine::ERROR_BIND);
    283 
    284         fullJid_ = Jid(element->FirstElement()->TextNamed(QN_BIND_JID));
    285         if (!fullJid_.IsFull()) {
    286           return Failure(XmppEngine::ERROR_BIND);
    287         }
    288 
    289         // now request session
    290         XmlElement iq(QN_IQ);
    291         iq.AddAttr(QN_TYPE, "set");
    292 
    293         iqId_ = pctx_->NextId();
    294         iq.AddAttr(QN_ID, iqId_);
    295         iq.AddElement(new XmlElement(QN_SESSION_SESSION, true));
    296         pctx_->InternalSendStanza(&iq);
    297 
    298         state_ = LOGINSTATE_SESSION_REQUESTED;
    299         continue;
    300       }
    301 
    302       case LOGINSTATE_SESSION_REQUESTED: {
    303         if (NULL == (element = NextStanza()))
    304           return true;
    305         if (element->Name() != QN_IQ || element->Attr(QN_ID) != iqId_ ||
    306             element->Attr(QN_TYPE) == "get" || element->Attr(QN_TYPE) == "set")
    307           return false;
    308 
    309         if (element->Attr(QN_TYPE) != "result")
    310           return Failure(XmppEngine::ERROR_BIND);
    311 
    312         pctx_->SignalBound(fullJid_);
    313         FlushQueuedStanzas();
    314         state_ = LOGINSTATE_DONE;
    315         return true;
    316       }
    317 
    318       case LOGINSTATE_DONE:
    319         return false;
    320     }
    321   }
    322 }
    323 
    324 bool
    325 XmppLoginTask::HandleStartStream(const XmlElement *element) {
    326 
    327   if (element->Name() != QN_STREAM_STREAM)
    328     return false;
    329 
    330   if (element->Attr(QN_XMLNS) != "jabber:client")
    331     return false;
    332 
    333   if (element->Attr(QN_VERSION) != "1.0")
    334     return false;
    335 
    336   if (!element->HasAttr(QN_ID))
    337     return false;
    338 
    339   streamId_ = element->Attr(QN_ID);
    340 
    341   return true;
    342 }
    343 
    344 bool
    345 XmppLoginTask::HandleFeatures(const XmlElement *element) {
    346   if (element->Name() != QN_STREAM_FEATURES)
    347     return false;
    348 
    349   pelFeatures_.reset(new XmlElement(*element));
    350   return true;
    351 }
    352 
    353 const XmlElement *
    354 XmppLoginTask::GetFeature(const QName & name) {
    355   return pelFeatures_->FirstNamed(name);
    356 }
    357 
    358 bool
    359 XmppLoginTask::Failure(XmppEngine::Error reason) {
    360   state_ = LOGINSTATE_DONE;
    361   pctx_->SignalError(reason, 0);
    362   return false;
    363 }
    364 
    365 void
    366 XmppLoginTask::OutgoingStanza(const XmlElement * element) {
    367   XmlElement * pelCopy = new XmlElement(*element);
    368   pvecQueuedStanzas_->push_back(pelCopy);
    369 }
    370 
    371 void
    372 XmppLoginTask::FlushQueuedStanzas() {
    373   for (size_t i = 0; i < pvecQueuedStanzas_->size(); i += 1) {
    374     pctx_->InternalSendStanza((*pvecQueuedStanzas_)[i]);
    375     delete (*pvecQueuedStanzas_)[i];
    376   }
    377   pvecQueuedStanzas_->clear();
    378 }
    379 
    380 }
    381