11e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)/* 21e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) * libjingle 31e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) * Copyright 2004--2005, Google Inc. 41e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) * 51e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) * Redistribution and use in source and binary forms, with or without 61e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) * modification, are permitted provided that the following conditions are met: 7d57369da7c6519fef57db42085f7b42d4c8845c1Torne (Richard Coles) * 81e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) * 1. Redistributions of source code must retain the above copyright notice, 91e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) * this list of conditions and the following disclaimer. 101e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) * 2. Redistributions in binary form must reproduce the above copyright notice, 111e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) * this list of conditions and the following disclaimer in the documentation 121e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) * and/or other materials provided with the distribution. 131e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) * 3. The name of the author may not be used to endorse or promote products 141e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) * derived from this software without specific prior written permission. 151e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) * 161e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED 171e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 181e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 19f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 201e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 211e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 221e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 23d57369da7c6519fef57db42085f7b42d4c8845c1Torne (Richard Coles) * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 24f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 251e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 261e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) */ 271e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 28f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "xmppclient.h" 291e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)#include "xmpptask.h" 301e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)#include "talk/xmpp/constants.h" 311e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)#include "talk/base/sigslot.h" 321e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)#include "talk/xmpp/saslplainmechanism.h" 33#include "talk/xmpp/prexmppauth.h" 34#include "talk/base/scoped_ptr.h" 35#include "talk/xmpp/plainsaslhandler.h" 36 37namespace buzz { 38 39talk_base::TaskParent* XmppClient::GetParent(int code) { 40 if (code == XMPP_CLIENT_TASK_CODE) 41 return this; 42 else 43 return talk_base::Task::GetParent(code); 44} 45 46class XmppClient::Private : 47 public sigslot::has_slots<>, 48 public XmppSessionHandler, 49 public XmppOutputHandler { 50public: 51 52 Private(XmppClient * client) : 53 client_(client), 54 socket_(NULL), 55 engine_(NULL), 56 proxy_port_(0), 57 pre_engine_error_(XmppEngine::ERROR_NONE), 58 pre_engine_subcode_(0), 59 signal_closed_(false), 60 allow_plain_(false) {} 61 62 // the owner 63 XmppClient * const client_; 64 65 // the two main objects 66 talk_base::scoped_ptr<AsyncSocket> socket_; 67 talk_base::scoped_ptr<XmppEngine> engine_; 68 talk_base::scoped_ptr<PreXmppAuth> pre_auth_; 69 talk_base::CryptString pass_; 70 std::string auth_cookie_; 71 talk_base::SocketAddress server_; 72 std::string proxy_host_; 73 int proxy_port_; 74 XmppEngine::Error pre_engine_error_; 75 int pre_engine_subcode_; 76 CaptchaChallenge captcha_challenge_; 77 bool signal_closed_; 78 bool allow_plain_; 79 80 // implementations of interfaces 81 void OnStateChange(int state); 82 void WriteOutput(const char * bytes, size_t len); 83 void StartTls(const std::string & domainname); 84 void CloseConnection(); 85 86 // slots for socket signals 87 void OnSocketConnected(); 88 void OnSocketRead(); 89 void OnSocketClosed(); 90}; 91 92XmppReturnStatus 93XmppClient::Connect(const XmppClientSettings & settings, const std::string & lang, AsyncSocket * socket, PreXmppAuth * pre_auth) { 94 if (socket == NULL) 95 return XMPP_RETURN_BADARGUMENT; 96 if (d_->socket_.get() != NULL) 97 return XMPP_RETURN_BADSTATE; 98 99 d_->socket_.reset(socket); 100 101 d_->socket_->SignalConnected.connect(d_.get(), &Private::OnSocketConnected); 102 d_->socket_->SignalRead.connect(d_.get(), &Private::OnSocketRead); 103 d_->socket_->SignalClosed.connect(d_.get(), &Private::OnSocketClosed); 104 105 d_->engine_.reset(XmppEngine::Create()); 106 d_->engine_->SetSessionHandler(d_.get()); 107 d_->engine_->SetOutputHandler(d_.get()); 108 if (!settings.resource().empty()) { 109 d_->engine_->SetRequestedResource(settings.resource()); 110 } 111 d_->engine_->SetUseTls(settings.use_tls()); 112 113 // 114 // The talk.google.com server expects you to use "gmail.com" in the 115 // stream, and expects the domain certificate to be "gmail.com" as well. 116 // For all other servers, we leave the strings empty, which causes 117 // the jid's domain to be used. "foo@example.com" -> stream to="example.com" 118 // tls certificate for "example.com" 119 // 120 // This is only true when using Gaia auth, so let's say if there's no preauth, 121 // we should use the actual server name 122 std::string server_name = settings.server().IPAsString(); 123 if ((server_name == buzz::STR_TALK_GOOGLE_COM || 124 server_name == buzz::STR_TALKX_L_GOOGLE_COM) && 125 pre_auth != NULL) { 126 d_->engine_->SetTlsServer(buzz::STR_GMAIL_COM, buzz::STR_GMAIL_COM); 127 } 128 129 // Set language 130 d_->engine_->SetLanguage(lang); 131 132 d_->engine_->SetUser(buzz::Jid(settings.user(), settings.host(), STR_EMPTY)); 133 134 d_->pass_ = settings.pass(); 135 d_->auth_cookie_ = settings.auth_cookie(); 136 d_->server_ = settings.server(); 137 d_->proxy_host_ = settings.proxy_host(); 138 d_->proxy_port_ = settings.proxy_port(); 139 d_->allow_plain_ = settings.allow_plain(); 140 d_->pre_auth_.reset(pre_auth); 141 142 return XMPP_RETURN_OK; 143} 144 145XmppEngine::State 146XmppClient::GetState() { 147 if (d_->engine_.get() == NULL) 148 return XmppEngine::STATE_NONE; 149 return d_->engine_->GetState(); 150} 151 152XmppEngine::Error 153XmppClient::GetError(int *subcode) { 154 if (subcode) { 155 *subcode = 0; 156 } 157 if (d_->engine_.get() == NULL) 158 return XmppEngine::ERROR_NONE; 159 if (d_->pre_engine_error_ != XmppEngine::ERROR_NONE) { 160 if (subcode) { 161 *subcode = d_->pre_engine_subcode_; 162 } 163 return d_->pre_engine_error_; 164 } 165 return d_->engine_->GetError(subcode); 166} 167 168const XmlElement * 169XmppClient::GetStreamError() { 170 if (d_->engine_.get() == NULL) { 171 return NULL; 172 } 173 return d_->engine_->GetStreamError(); 174} 175 176CaptchaChallenge XmppClient::GetCaptchaChallenge() { 177 if (d_->engine_.get() == NULL) 178 return CaptchaChallenge(); 179 return d_->captcha_challenge_; 180} 181 182std::string 183XmppClient::GetAuthCookie() { 184 if (d_->engine_.get() == NULL) 185 return ""; 186 return d_->auth_cookie_; 187} 188 189int 190XmppClient::ProcessStart() { 191 if (d_->pre_auth_.get()) { 192 d_->pre_auth_->SignalAuthDone.connect(this, &XmppClient::OnAuthDone); 193 d_->pre_auth_->StartPreXmppAuth( 194 d_->engine_->GetUser(), d_->server_, d_->pass_, d_->auth_cookie_); 195 d_->pass_.Clear(); // done with this; 196 return STATE_PRE_XMPP_LOGIN; 197 } 198 else { 199 d_->engine_->SetSaslHandler(new PlainSaslHandler( 200 d_->engine_->GetUser(), d_->pass_, d_->allow_plain_)); 201 d_->pass_.Clear(); // done with this; 202 return STATE_START_XMPP_LOGIN; 203 } 204} 205 206void 207XmppClient::OnAuthDone() { 208 Wake(); 209} 210 211int 212XmppClient::ProcessCookieLogin() { 213 // Don't know how this could happen, but crash reports show it as NULL 214 if (!d_->pre_auth_.get()) { 215 d_->pre_engine_error_ = XmppEngine::ERROR_AUTH; 216 EnsureClosed(); 217 return STATE_ERROR; 218 } 219 220 // Wait until pre authentication is done is done 221 if (!d_->pre_auth_->IsAuthDone()) 222 return STATE_BLOCKED; 223 224 if (!d_->pre_auth_->IsAuthorized()) { 225 // maybe split out a case when gaia is down? 226 if (d_->pre_auth_->HadError()) { 227 d_->pre_engine_error_ = XmppEngine::ERROR_AUTH; 228 d_->pre_engine_subcode_ = d_->pre_auth_->GetError(); 229 } 230 else { 231 d_->pre_engine_error_ = XmppEngine::ERROR_UNAUTHORIZED; 232 d_->pre_engine_subcode_ = 0; 233 d_->captcha_challenge_ = d_->pre_auth_->GetCaptchaChallenge(); 234 } 235 d_->pre_auth_.reset(NULL); // done with this 236 EnsureClosed(); 237 return STATE_ERROR; 238 } 239 240 // Save auth cookie as a result 241 d_->auth_cookie_ = d_->pre_auth_->GetAuthCookie(); 242 243 // transfer ownership of pre_auth_ to engine 244 d_->engine_->SetSaslHandler(d_->pre_auth_.release()); 245 return STATE_START_XMPP_LOGIN; 246} 247 248int 249XmppClient::ProcessStartXmppLogin() { 250 // Done with pre-connect tasks - connect! 251 if (!d_->socket_->Connect(d_->server_)) { 252 EnsureClosed(); 253 return STATE_ERROR; 254 } 255 256 return STATE_RESPONSE; 257} 258 259int 260XmppClient::ProcessResponse() { 261 // Hang around while we are connected. 262 if (!delivering_signal_ && (d_->engine_.get() == NULL || 263 d_->engine_->GetState() == XmppEngine::STATE_CLOSED)) 264 return STATE_DONE; 265 return STATE_BLOCKED; 266} 267 268XmppReturnStatus 269XmppClient::Disconnect() { 270 if (d_->socket_.get() == NULL) 271 return XMPP_RETURN_BADSTATE; 272 d_->engine_->Disconnect(); 273 d_->socket_.reset(NULL); 274 return XMPP_RETURN_OK; 275} 276 277XmppClient::XmppClient(TaskParent * parent) 278 : Task(parent), 279 delivering_signal_(false), 280 valid_(false) { 281 d_.reset(new Private(this)); 282 valid_ = true; 283} 284 285XmppClient::~XmppClient() { 286 valid_ = false; 287} 288 289const Jid & 290XmppClient::jid() { 291 return d_->engine_->FullJid(); 292} 293 294 295std::string 296XmppClient::NextId() { 297 return d_->engine_->NextId(); 298} 299 300XmppReturnStatus 301XmppClient::SendStanza(const XmlElement * stanza) { 302 return d_->engine_->SendStanza(stanza); 303} 304 305XmppReturnStatus 306XmppClient::SendStanzaError(const XmlElement * old_stanza, XmppStanzaError xse, const std::string & message) { 307 return d_->engine_->SendStanzaError(old_stanza, xse, message); 308} 309 310XmppReturnStatus 311XmppClient::SendRaw(const std::string & text) { 312 return d_->engine_->SendRaw(text); 313} 314 315XmppEngine* 316XmppClient::engine() { 317 return d_->engine_.get(); 318} 319 320void 321XmppClient::Private::OnSocketConnected() { 322 engine_->Connect(); 323} 324 325void 326XmppClient::Private::OnSocketRead() { 327 char bytes[4096]; 328 size_t bytes_read; 329 for (;;) { 330 if (!socket_->Read(bytes, sizeof(bytes), &bytes_read)) { 331 // TODO: deal with error information 332 return; 333 } 334 335 if (bytes_read == 0) 336 return; 337 338//#ifdef _DEBUG 339 client_->SignalLogInput(bytes, bytes_read); 340//#endif 341 342 engine_->HandleInput(bytes, bytes_read); 343 } 344} 345 346void 347XmppClient::Private::OnSocketClosed() { 348 int code = socket_->GetError(); 349 engine_->ConnectionClosed(code); 350} 351 352void 353XmppClient::Private::OnStateChange(int state) { 354 if (state == XmppEngine::STATE_CLOSED) { 355 client_->EnsureClosed(); 356 } 357 else { 358 client_->SignalStateChange((XmppEngine::State)state); 359 } 360 client_->Wake(); 361} 362 363void 364XmppClient::Private::WriteOutput(const char * bytes, size_t len) { 365 366//#ifdef _DEBUG 367 client_->SignalLogOutput(bytes, len); 368//#endif 369 370 socket_->Write(bytes, len); 371 // TODO: deal with error information 372} 373 374void 375XmppClient::Private::StartTls(const std::string & domain) { 376#if defined(FEATURE_ENABLE_SSL) 377 socket_->StartTls(domain); 378#endif 379} 380 381void 382XmppClient::Private::CloseConnection() { 383 socket_->Close(); 384} 385 386void 387XmppClient::AddXmppTask(XmppTask * task, XmppEngine::HandlerLevel level) { 388 d_->engine_->AddStanzaHandler(task, level); 389} 390 391void 392XmppClient::RemoveXmppTask(XmppTask * task) { 393 d_->engine_->RemoveStanzaHandler(task); 394} 395 396void 397XmppClient::EnsureClosed() { 398 if (!d_->signal_closed_) { 399 d_->signal_closed_ = true; 400 delivering_signal_ = true; 401 SignalStateChange(XmppEngine::STATE_CLOSED); 402 delivering_signal_ = false; 403 } 404} 405 406 407} 408