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 "webrtc/libjingle/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 "webrtc/base/base64.h"
39 #include "webrtc/base/common.h"
40
41 using rtc::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 << rtc::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