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 "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" 40 41 using talk_base::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 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 76 XmppLoginTask::~XmppLoginTask() { 77 for (size_t i = 0; i < pvecQueuedStanzas_->size(); i += 1) 78 delete (*pvecQueuedStanzas_)[i]; 79 } 80 81 void 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 * 91 XmppLoginTask::NextStanza() { 92 const XmlElement * result = pelStanza_; 93 pelStanza_ = NULL; 94 return result; 95 } 96 97 bool 98 XmppLoginTask::Advance() { 99 100 for (;;) { 101 102 const XmlElement * element = NULL; 103 104 #if _DEBUG 105 LOG(LS_VERBOSE) << "XmppLoginTask::Advance - " 106 << talk_base::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 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 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 * 371 XmppLoginTask::GetFeature(const QName & name) { 372 return pelFeatures_->FirstNamed(name); 373 } 374 375 bool 376 XmppLoginTask::Failure(XmppEngine::Error reason) { 377 state_ = LOGINSTATE_DONE; 378 pctx_->SignalError(reason, 0); 379 return false; 380 } 381 382 void 383 XmppLoginTask::OutgoingStanza(const XmlElement * element) { 384 XmlElement * pelCopy = new XmlElement(*element); 385 pvecQueuedStanzas_->push_back(pelCopy); 386 } 387 388 void 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