• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
XmppLoginTask(XmppEngineImpl * pctx)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 
~XmppLoginTask()75 XmppLoginTask::~XmppLoginTask() {
76   for (size_t i = 0; i < pvecQueuedStanzas_->size(); i += 1)
77     delete (*pvecQueuedStanzas_)[i];
78 }
79 
80 void
IncomingStanza(const XmlElement * element,bool isStart)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 *
NextStanza()90 XmppLoginTask::NextStanza() {
91   const XmlElement * result = pelStanza_;
92   pelStanza_ = NULL;
93   return result;
94 }
95 
96 bool
Advance()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
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