3
* Copyright 2004--2005, Google Inc.
5
* Redistribution and use in source and binary forms, with or without
6
* modification, are permitted provided that the following conditions are met:
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.
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.
28
#include "talk/xmpp/xmpplogintask.h"
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"
41
using talk_base::ConstantLabel;
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),
61
XmppLoginTask::XmppLoginTask(XmppEngineImpl * pctx) :
64
allowNonGoogleLogin_(true),
65
state_(LOGINSTATE_INIT),
72
pvecQueuedStanzas_(new std::vector<XmlElement *>()),
76
XmppLoginTask::~XmppLoginTask() {
77
for (size_t i = 0; i < pvecQueuedStanzas_->size(); i += 1)
78
delete (*pvecQueuedStanzas_)[i];
82
XmppLoginTask::IncomingStanza(const XmlElement *element, bool isStart) {
91
XmppLoginTask::NextStanza() {
92
const XmlElement * result = pelStanza_;
98
XmppLoginTask::Advance() {
102
const XmlElement * element = NULL;
105
LOG(LS_VERBOSE) << "XmppLoginTask::Advance - "
106
<< talk_base::ErrorName(state_, LOGINTASK_STATES);
111
case LOGINSTATE_INIT: {
113
pelFeatures_.reset(NULL);
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;
125
case LOGINSTATE_STREAMSTART_SENT: {
126
if (NULL == (element = NextStanza()))
129
if (!isStart_ || !HandleStartStream(element))
130
return Failure(XmppEngine::ERROR_VERSION);
132
state_ = LOGINSTATE_STARTED_XMPP;
136
case LOGINSTATE_STARTED_XMPP: {
137
if (NULL == (element = NextStanza()))
140
if (!HandleFeatures(element))
141
return Failure(XmppEngine::ERROR_VERSION);
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);
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;
156
state_ = LOGINSTATE_AUTH_INIT;
160
state_ = LOGINSTATE_BIND_INIT;
164
case LOGINSTATE_TLS_INIT: {
165
const XmlElement * pelTls = GetFeature(QN_TLS_STARTTLS);
167
return Failure(XmppEngine::ERROR_TLS);
169
XmlElement el(QN_TLS_STARTTLS, true);
170
pctx_->InternalSendStanza(&el);
171
state_ = LOGINSTATE_TLS_REQUESTED;
175
case LOGINSTATE_TLS_REQUESTED: {
176
if (NULL == (element = NextStanza()))
178
if (element->Name() != QN_TLS_PROCEED)
179
return Failure(XmppEngine::ERROR_TLS);
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;
192
case LOGINSTATE_AUTH_INIT: {
193
const XmlElement * pelSaslAuth = GetFeature(QN_SASL_MECHANISMS);
195
return Failure(XmppEngine::ERROR_AUTH);
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);
203
pelMech = pelMech->NextNamed(QN_SASL_MECHANISM)) {
205
mechanisms.push_back(pelMech->BodyText());
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);
214
// No recognized auth mechanism - that's an error
215
sasl_mech_.reset(pctx_->GetSaslMechanism(choice));
216
if (sasl_mech_.get() == NULL) {
217
return Failure(XmppEngine::ERROR_AUTH);
220
// OK, let's start it.
221
XmlElement * auth = sasl_mech_->StartSaslAuth();
223
return Failure(XmppEngine::ERROR_AUTH);
225
if (allowNonGoogleLogin_) {
226
// Setting the following two attributes is required to support
229
// Allow login with non-google id accounts.
230
auth->SetAttr(QN_GOOGLE_ALLOW_NON_GOOGLE_ID_XMPP_LOGIN, "true");
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");
236
pctx_->InternalSendStanza(auth);
238
state_ = LOGINSTATE_SASL_RUNNING;
242
case LOGINSTATE_SASL_RUNNING: {
243
if (NULL == (element = NextStanza()))
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);
252
pctx_->InternalSendStanza(response);
254
state_ = LOGINSTATE_SASL_RUNNING;
257
if (element->Name() != QN_SASL_SUCCESS) {
258
return Failure(XmppEngine::ERROR_UNAUTHORIZED);
263
state_ = LOGINSTATE_INIT;
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);
273
XmlElement iq(QN_IQ);
274
iq.AddAttr(QN_TYPE, "set");
276
iqId_ = pctx_->NextId();
277
iq.AddAttr(QN_ID, iqId_);
278
iq.AddElement(new XmlElement(QN_BIND_BIND, true));
280
if (pctx_->requested_resource_ != STR_EMPTY) {
281
iq.AddElement(new XmlElement(QN_BIND_RESOURCE), 1);
282
iq.AddText(pctx_->requested_resource_, 2);
284
pctx_->InternalSendStanza(&iq);
285
state_ = LOGINSTATE_BIND_REQUESTED;
289
case LOGINSTATE_BIND_REQUESTED: {
290
if (NULL == (element = NextStanza()))
293
if (element->Name() != QN_IQ || element->Attr(QN_ID) != iqId_ ||
294
element->Attr(QN_TYPE) == "get" || element->Attr(QN_TYPE) == "set")
297
if (element->Attr(QN_TYPE) != "result" || element->FirstElement() == NULL ||
298
element->FirstElement()->Name() != QN_BIND_BIND)
299
return Failure(XmppEngine::ERROR_BIND);
301
fullJid_ = Jid(element->FirstElement()->TextNamed(QN_BIND_JID));
302
if (!fullJid_.IsFull()) {
303
return Failure(XmppEngine::ERROR_BIND);
306
// now request session
307
XmlElement iq(QN_IQ);
308
iq.AddAttr(QN_TYPE, "set");
310
iqId_ = pctx_->NextId();
311
iq.AddAttr(QN_ID, iqId_);
312
iq.AddElement(new XmlElement(QN_SESSION_SESSION, true));
313
pctx_->InternalSendStanza(&iq);
315
state_ = LOGINSTATE_SESSION_REQUESTED;
319
case LOGINSTATE_SESSION_REQUESTED: {
320
if (NULL == (element = NextStanza()))
322
if (element->Name() != QN_IQ || element->Attr(QN_ID) != iqId_ ||
323
element->Attr(QN_TYPE) == "get" || element->Attr(QN_TYPE) == "set")
326
if (element->Attr(QN_TYPE) != "result")
327
return Failure(XmppEngine::ERROR_BIND);
329
pctx_->SignalBound(fullJid_);
330
FlushQueuedStanzas();
331
state_ = LOGINSTATE_DONE;
335
case LOGINSTATE_DONE:
342
XmppLoginTask::HandleStartStream(const XmlElement *element) {
344
if (element->Name() != QN_STREAM_STREAM)
347
if (element->Attr(QN_XMLNS) != "jabber:client")
350
if (element->Attr(QN_VERSION) != "1.0")
353
if (!element->HasAttr(QN_ID))
356
streamId_ = element->Attr(QN_ID);
362
XmppLoginTask::HandleFeatures(const XmlElement *element) {
363
if (element->Name() != QN_STREAM_FEATURES)
366
pelFeatures_.reset(new XmlElement(*element));
371
XmppLoginTask::GetFeature(const QName & name) {
372
return pelFeatures_->FirstNamed(name);
376
XmppLoginTask::Failure(XmppEngine::Error reason) {
377
state_ = LOGINSTATE_DONE;
378
pctx_->SignalError(reason, 0);
383
XmppLoginTask::OutgoingStanza(const XmlElement * element) {
384
XmlElement * pelCopy = new XmlElement(*element);
385
pvecQueuedStanzas_->push_back(pelCopy);
389
XmppLoginTask::FlushQueuedStanzas() {
390
for (size_t i = 0; i < pvecQueuedStanzas_->size(); i += 1) {
391
pctx_->InternalSendStanza((*pvecQueuedStanzas_)[i]);
392
delete (*pvecQueuedStanzas_)[i];
394
pvecQueuedStanzas_->clear();