Home | History | Annotate | Download | only in xmpp
      1 /*
      2  *  Copyright 2004 The WebRTC Project Authors. All rights reserved.
      3  *
      4  *  Use of this source code is governed by a BSD-style license
      5  *  that can be found in the LICENSE file in the root of the source
      6  *  tree. An additional intellectual property rights grant can be found
      7  *  in the file PATENTS.  All contributing project authors may
      8  *  be found in the AUTHORS file in the root of the source tree.
      9  */
     10 
     11 #include "webrtc/libjingle/xmpp/xmpplogintask.h"
     12 
     13 #include <string>
     14 #include <vector>
     15 
     16 #include "webrtc/libjingle/xmllite/xmlelement.h"
     17 #include "webrtc/libjingle/xmpp/constants.h"
     18 #include "webrtc/libjingle/xmpp/jid.h"
     19 #include "webrtc/libjingle/xmpp/saslmechanism.h"
     20 #include "webrtc/libjingle/xmpp/xmppengineimpl.h"
     21 #include "webrtc/base/base64.h"
     22 #include "webrtc/base/common.h"
     23 
     24 using rtc::ConstantLabel;
     25 
     26 namespace buzz {
     27 
     28 #if !defined(NDEBUG)
     29 const ConstantLabel XmppLoginTask::LOGINTASK_STATES[] = {
     30   KLABEL(LOGINSTATE_INIT),
     31   KLABEL(LOGINSTATE_STREAMSTART_SENT),
     32   KLABEL(LOGINSTATE_STARTED_XMPP),
     33   KLABEL(LOGINSTATE_TLS_INIT),
     34   KLABEL(LOGINSTATE_AUTH_INIT),
     35   KLABEL(LOGINSTATE_BIND_INIT),
     36   KLABEL(LOGINSTATE_TLS_REQUESTED),
     37   KLABEL(LOGINSTATE_SASL_RUNNING),
     38   KLABEL(LOGINSTATE_BIND_REQUESTED),
     39   KLABEL(LOGINSTATE_SESSION_REQUESTED),
     40   KLABEL(LOGINSTATE_DONE),
     41   LASTLABEL
     42 };
     43 #endif
     44 XmppLoginTask::XmppLoginTask(XmppEngineImpl * pctx) :
     45   pctx_(pctx),
     46   authNeeded_(true),
     47   allowNonGoogleLogin_(true),
     48   state_(LOGINSTATE_INIT),
     49   pelStanza_(NULL),
     50   isStart_(false),
     51   iqId_(STR_EMPTY),
     52   pelFeatures_(),
     53   fullJid_(STR_EMPTY),
     54   streamId_(STR_EMPTY),
     55   pvecQueuedStanzas_(new std::vector<XmlElement *>()),
     56   sasl_mech_() {
     57 }
     58 
     59 XmppLoginTask::~XmppLoginTask() {
     60   for (size_t i = 0; i < pvecQueuedStanzas_->size(); i += 1)
     61     delete (*pvecQueuedStanzas_)[i];
     62 }
     63 
     64 void
     65 XmppLoginTask::IncomingStanza(const XmlElement *element, bool isStart) {
     66   pelStanza_ = element;
     67   isStart_ = isStart;
     68   Advance();
     69   pelStanza_ = NULL;
     70   isStart_ = false;
     71 }
     72 
     73 const XmlElement *
     74 XmppLoginTask::NextStanza() {
     75   const XmlElement * result = pelStanza_;
     76   pelStanza_ = NULL;
     77   return result;
     78 }
     79 
     80 bool
     81 XmppLoginTask::Advance() {
     82 
     83   for (;;) {
     84 
     85     const XmlElement * element = NULL;
     86 
     87 #if !defined(NDEBUG)
     88     LOG(LS_VERBOSE) << "XmppLoginTask::Advance - "
     89       << rtc::ErrorName(state_, LOGINTASK_STATES);
     90 #endif
     91 
     92     switch (state_) {
     93 
     94       case LOGINSTATE_INIT: {
     95         pctx_->RaiseReset();
     96         pelFeatures_.reset(NULL);
     97 
     98         // The proper domain to verify against is the real underlying
     99         // domain - i.e., the domain that owns the JID.  Our XmppEngineImpl
    100         // also allows matching against a proxy domain instead, if it is told
    101         // to do so - see the implementation of XmppEngineImpl::StartTls and
    102         // XmppEngine::SetTlsServerDomain to see how you can use that feature
    103         pctx_->InternalSendStart(pctx_->user_jid_.domain());
    104         state_ = LOGINSTATE_STREAMSTART_SENT;
    105         break;
    106       }
    107 
    108       case LOGINSTATE_STREAMSTART_SENT: {
    109         if (NULL == (element = NextStanza()))
    110           return true;
    111 
    112         if (!isStart_ || !HandleStartStream(element))
    113           return Failure(XmppEngine::ERROR_VERSION);
    114 
    115         state_ = LOGINSTATE_STARTED_XMPP;
    116         return true;
    117       }
    118 
    119       case LOGINSTATE_STARTED_XMPP: {
    120         if (NULL == (element = NextStanza()))
    121           return true;
    122 
    123         if (!HandleFeatures(element))
    124           return Failure(XmppEngine::ERROR_VERSION);
    125 
    126         bool tls_present = (GetFeature(QN_TLS_STARTTLS) != NULL);
    127         // Error if TLS required but not present.
    128         if (pctx_->tls_option_ == buzz::TLS_REQUIRED && !tls_present) {
    129           return Failure(XmppEngine::ERROR_TLS);
    130         }
    131         // Use TLS if required or enabled, and also available
    132         if ((pctx_->tls_option_ == buzz::TLS_REQUIRED ||
    133             pctx_->tls_option_ == buzz::TLS_ENABLED) && tls_present) {
    134           state_ = LOGINSTATE_TLS_INIT;
    135           continue;
    136         }
    137 
    138         if (authNeeded_) {
    139           state_ = LOGINSTATE_AUTH_INIT;
    140           continue;
    141         }
    142 
    143         state_ = LOGINSTATE_BIND_INIT;
    144         continue;
    145       }
    146 
    147       case LOGINSTATE_TLS_INIT: {
    148         const XmlElement * pelTls = GetFeature(QN_TLS_STARTTLS);
    149         if (!pelTls)
    150           return Failure(XmppEngine::ERROR_TLS);
    151 
    152         XmlElement el(QN_TLS_STARTTLS, true);
    153         pctx_->InternalSendStanza(&el);
    154         state_ = LOGINSTATE_TLS_REQUESTED;
    155         continue;
    156       }
    157 
    158       case LOGINSTATE_TLS_REQUESTED: {
    159         if (NULL == (element = NextStanza()))
    160           return true;
    161         if (element->Name() != QN_TLS_PROCEED)
    162           return Failure(XmppEngine::ERROR_TLS);
    163 
    164         // The proper domain to verify against is the real underlying
    165         // domain - i.e., the domain that owns the JID.  Our XmppEngineImpl
    166         // also allows matching against a proxy domain instead, if it is told
    167         // to do so - see the implementation of XmppEngineImpl::StartTls and
    168         // XmppEngine::SetTlsServerDomain to see how you can use that feature
    169         pctx_->StartTls(pctx_->user_jid_.domain());
    170         pctx_->tls_option_ = buzz::TLS_ENABLED;
    171         state_ = LOGINSTATE_INIT;
    172         continue;
    173       }
    174 
    175       case LOGINSTATE_AUTH_INIT: {
    176         const XmlElement * pelSaslAuth = GetFeature(QN_SASL_MECHANISMS);
    177         if (!pelSaslAuth) {
    178           return Failure(XmppEngine::ERROR_AUTH);
    179         }
    180 
    181         // Collect together the SASL auth mechanisms presented by the server
    182         std::vector<std::string> mechanisms;
    183         for (const XmlElement * pelMech =
    184              pelSaslAuth->FirstNamed(QN_SASL_MECHANISM);
    185              pelMech;
    186              pelMech = pelMech->NextNamed(QN_SASL_MECHANISM)) {
    187 
    188           mechanisms.push_back(pelMech->BodyText());
    189         }
    190 
    191         // Given all the mechanisms, choose the best
    192         std::string choice(pctx_->ChooseBestSaslMechanism(mechanisms, pctx_->IsEncrypted()));
    193         if (choice.empty()) {
    194           return Failure(XmppEngine::ERROR_AUTH);
    195         }
    196 
    197         // No recognized auth mechanism - that's an error
    198         sasl_mech_.reset(pctx_->GetSaslMechanism(choice));
    199         if (!sasl_mech_) {
    200           return Failure(XmppEngine::ERROR_AUTH);
    201         }
    202 
    203         // OK, let's start it.
    204         XmlElement * auth = sasl_mech_->StartSaslAuth();
    205         if (auth == NULL) {
    206           return Failure(XmppEngine::ERROR_AUTH);
    207         }
    208         if (allowNonGoogleLogin_) {
    209           // Setting the following two attributes is required to support
    210           // non-google ids.
    211 
    212           // Allow login with non-google id accounts.
    213           auth->SetAttr(QN_GOOGLE_ALLOW_NON_GOOGLE_ID_XMPP_LOGIN, "true");
    214 
    215           // Allow login with either the non-google id or the friendly email.
    216           auth->SetAttr(QN_GOOGLE_AUTH_CLIENT_USES_FULL_BIND_RESULT, "true");
    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