• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
XmppLoginTask(XmppEngineImpl * pctx)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 
~XmppLoginTask()59 XmppLoginTask::~XmppLoginTask() {
60   for (size_t i = 0; i < pvecQueuedStanzas_->size(); i += 1)
61     delete (*pvecQueuedStanzas_)[i];
62 }
63 
64 void
IncomingStanza(const XmlElement * element,bool isStart)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 *
NextStanza()74 XmppLoginTask::NextStanza() {
75   const XmlElement * result = pelStanza_;
76   pelStanza_ = NULL;
77   return result;
78 }
79 
80 bool
Advance()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
HandleStartStream(const XmlElement * element)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
HandleFeatures(const XmlElement * element)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 *
GetFeature(const QName & name)354 XmppLoginTask::GetFeature(const QName & name) {
355   return pelFeatures_->FirstNamed(name);
356 }
357 
358 bool
Failure(XmppEngine::Error reason)359 XmppLoginTask::Failure(XmppEngine::Error reason) {
360   state_ = LOGINSTATE_DONE;
361   pctx_->SignalError(reason, 0);
362   return false;
363 }
364 
365 void
OutgoingStanza(const XmlElement * element)366 XmppLoginTask::OutgoingStanza(const XmlElement * element) {
367   XmlElement * pelCopy = new XmlElement(*element);
368   pvecQueuedStanzas_->push_back(pelCopy);
369 }
370 
371 void
FlushQueuedStanzas()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