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
24using rtc::ConstantLabel;
25
26namespace buzz {
27
28#if !defined(NDEBUG)
29const 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
44XmppLoginTask::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
59XmppLoginTask::~XmppLoginTask() {
60  for (size_t i = 0; i < pvecQueuedStanzas_->size(); i += 1)
61    delete (*pvecQueuedStanzas_)[i];
62}
63
64void
65XmppLoginTask::IncomingStanza(const XmlElement *element, bool isStart) {
66  pelStanza_ = element;
67  isStart_ = isStart;
68  Advance();
69  pelStanza_ = NULL;
70  isStart_ = false;
71}
72
73const XmlElement *
74XmppLoginTask::NextStanza() {
75  const XmlElement * result = pelStanza_;
76  pelStanza_ = NULL;
77  return result;
78}
79
80bool
81XmppLoginTask::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
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