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