• 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 "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
XmppLoginTask(XmppEngineImpl * pctx)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_(),
70   fullJid_(STR_EMPTY),
71   streamId_(STR_EMPTY),
72   pvecQueuedStanzas_(new std::vector<XmlElement *>()),
73   sasl_mech_() {
74 }
75 
~XmppLoginTask()76 XmppLoginTask::~XmppLoginTask() {
77   for (size_t i = 0; i < pvecQueuedStanzas_->size(); i += 1)
78     delete (*pvecQueuedStanzas_)[i];
79 }
80 
81 void
IncomingStanza(const XmlElement * element,bool isStart)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 *
NextStanza()91 XmppLoginTask::NextStanza() {
92   const XmlElement * result = pelStanza_;
93   pelStanza_ = NULL;
94   return result;
95 }
96 
97 bool
Advance()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
HandleStartStream(const XmlElement * element)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
HandleFeatures(const XmlElement * element)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 *
GetFeature(const QName & name)371 XmppLoginTask::GetFeature(const QName & name) {
372   return pelFeatures_->FirstNamed(name);
373 }
374 
375 bool
Failure(XmppEngine::Error reason)376 XmppLoginTask::Failure(XmppEngine::Error reason) {
377   state_ = LOGINSTATE_DONE;
378   pctx_->SignalError(reason, 0);
379   return false;
380 }
381 
382 void
OutgoingStanza(const XmlElement * element)383 XmppLoginTask::OutgoingStanza(const XmlElement * element) {
384   XmlElement * pelCopy = new XmlElement(*element);
385   pvecQueuedStanzas_->push_back(pelCopy);
386 }
387 
388 void
FlushQueuedStanzas()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