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
40using talk_base::ConstantLabel;
41
42namespace buzz {
43
44#ifdef _DEBUG
45const 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
61XmppLoginTask::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
75XmppLoginTask::~XmppLoginTask() {
76  for (size_t i = 0; i < pvecQueuedStanzas_->size(); i += 1)
77    delete (*pvecQueuedStanzas_)[i];
78}
79
80void
81XmppLoginTask::IncomingStanza(const XmlElement *element, bool isStart) {
82  pelStanza_ = element;
83  isStart_ = isStart;
84  Advance();
85  pelStanza_ = NULL;
86  isStart_ = false;
87}
88
89const XmlElement *
90XmppLoginTask::NextStanza() {
91  const XmlElement * result = pelStanza_;
92  pelStanza_ = NULL;
93  return result;
94}
95
96bool
97XmppLoginTask::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
324bool
325XmppLoginTask::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
344bool
345XmppLoginTask::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
353const XmlElement *
354XmppLoginTask::GetFeature(const QName & name) {
355  return pelFeatures_->FirstNamed(name);
356}
357
358bool
359XmppLoginTask::Failure(XmppEngine::Error reason) {
360  state_ = LOGINSTATE_DONE;
361  pctx_->SignalError(reason, 0);
362  return false;
363}
364
365void
366XmppLoginTask::OutgoingStanza(const XmlElement * element) {
367  XmlElement * pelCopy = new XmlElement(*element);
368  pvecQueuedStanzas_->push_back(pelCopy);
369}
370
371void
372XmppLoginTask::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