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 "xmppclient.h" 29#include "xmpptask.h" 30#include "talk/base/logging.h" 31#include "talk/base/sigslot.h" 32#include "talk/base/scoped_ptr.h" 33#include "talk/base/stringutils.h" 34#include "talk/xmpp/constants.h" 35#include "talk/xmpp/saslplainmechanism.h" 36#include "talk/xmpp/prexmppauth.h" 37#include "talk/xmpp/plainsaslhandler.h" 38 39namespace buzz { 40 41class XmppClient::Private : 42 public sigslot::has_slots<>, 43 public XmppSessionHandler, 44 public XmppOutputHandler { 45public: 46 47 explicit Private(XmppClient* client) : 48 client_(client), 49 socket_(), 50 engine_(), 51 proxy_port_(0), 52 pre_engine_error_(XmppEngine::ERROR_NONE), 53 pre_engine_subcode_(0), 54 signal_closed_(false), 55 allow_plain_(false) {} 56 57 virtual ~Private() { 58 // We need to disconnect from socket_ before engine_ is destructed (by 59 // the auto-generated destructor code). 60 ResetSocket(); 61 } 62 63 // the owner 64 XmppClient* const client_; 65 66 // the two main objects 67 talk_base::scoped_ptr<AsyncSocket> socket_; 68 talk_base::scoped_ptr<XmppEngine> engine_; 69 talk_base::scoped_ptr<PreXmppAuth> pre_auth_; 70 talk_base::CryptString pass_; 71 std::string auth_mechanism_; 72 std::string auth_token_; 73 talk_base::SocketAddress server_; 74 std::string proxy_host_; 75 int proxy_port_; 76 XmppEngine::Error pre_engine_error_; 77 int pre_engine_subcode_; 78 CaptchaChallenge captcha_challenge_; 79 bool signal_closed_; 80 bool allow_plain_; 81 82 void ResetSocket() { 83 if (socket_) { 84 socket_->SignalConnected.disconnect(this); 85 socket_->SignalRead.disconnect(this); 86 socket_->SignalClosed.disconnect(this); 87 socket_.reset(NULL); 88 } 89 } 90 91 // implementations of interfaces 92 void OnStateChange(int state); 93 void WriteOutput(const char* bytes, size_t len); 94 void StartTls(const std::string& domainname); 95 void CloseConnection(); 96 97 // slots for socket signals 98 void OnSocketConnected(); 99 void OnSocketRead(); 100 void OnSocketClosed(); 101}; 102 103bool IsTestServer(const std::string& server_name, 104 const std::string& test_server_domain) { 105 return (!test_server_domain.empty() && 106 talk_base::ends_with(server_name.c_str(), 107 test_server_domain.c_str())); 108} 109 110XmppReturnStatus XmppClient::Connect( 111 const XmppClientSettings& settings, 112 const std::string& lang, AsyncSocket* socket, PreXmppAuth* pre_auth) { 113 if (socket == NULL) 114 return XMPP_RETURN_BADARGUMENT; 115 if (d_->socket_) 116 return XMPP_RETURN_BADSTATE; 117 118 d_->socket_.reset(socket); 119 120 d_->socket_->SignalConnected.connect(d_.get(), &Private::OnSocketConnected); 121 d_->socket_->SignalRead.connect(d_.get(), &Private::OnSocketRead); 122 d_->socket_->SignalClosed.connect(d_.get(), &Private::OnSocketClosed); 123 124 d_->engine_.reset(XmppEngine::Create()); 125 d_->engine_->SetSessionHandler(d_.get()); 126 d_->engine_->SetOutputHandler(d_.get()); 127 if (!settings.resource().empty()) { 128 d_->engine_->SetRequestedResource(settings.resource()); 129 } 130 d_->engine_->SetTls(settings.use_tls()); 131 132 // The talk.google.com server returns a certificate with common-name: 133 // CN="gmail.com" for @gmail.com accounts, 134 // CN="googlemail.com" for @googlemail.com accounts, 135 // CN="talk.google.com" for other accounts (such as @example.com), 136 // so we tweak the tls server setting for those other accounts to match the 137 // returned certificate CN of "talk.google.com". 138 // For other servers, we leave the strings empty, which causes the jid's 139 // domain to be used. We do the same for gmail.com and googlemail.com as the 140 // returned CN matches the account domain in those cases. 141 std::string server_name = settings.server().HostAsURIString(); 142 if (server_name == buzz::STR_TALK_GOOGLE_COM || 143 server_name == buzz::STR_TALKX_L_GOOGLE_COM || 144 server_name == buzz::STR_XMPP_GOOGLE_COM || 145 server_name == buzz::STR_XMPPX_L_GOOGLE_COM || 146 IsTestServer(server_name, settings.test_server_domain())) { 147 if (settings.host() != STR_GMAIL_COM && 148 settings.host() != STR_GOOGLEMAIL_COM) { 149 d_->engine_->SetTlsServer("", STR_TALK_GOOGLE_COM); 150 } 151 } 152 153 // Set language 154 d_->engine_->SetLanguage(lang); 155 156 d_->engine_->SetUser(buzz::Jid(settings.user(), settings.host(), STR_EMPTY)); 157 158 d_->pass_ = settings.pass(); 159 d_->auth_mechanism_ = settings.auth_mechanism(); 160 d_->auth_token_ = settings.auth_token(); 161 d_->server_ = settings.server(); 162 d_->proxy_host_ = settings.proxy_host(); 163 d_->proxy_port_ = settings.proxy_port(); 164 d_->allow_plain_ = settings.allow_plain(); 165 d_->pre_auth_.reset(pre_auth); 166 167 return XMPP_RETURN_OK; 168} 169 170XmppEngine::State XmppClient::GetState() const { 171 if (!d_->engine_) 172 return XmppEngine::STATE_NONE; 173 return d_->engine_->GetState(); 174} 175 176XmppEngine::Error XmppClient::GetError(int* subcode) { 177 if (subcode) { 178 *subcode = 0; 179 } 180 if (!d_->engine_) 181 return XmppEngine::ERROR_NONE; 182 if (d_->pre_engine_error_ != XmppEngine::ERROR_NONE) { 183 if (subcode) { 184 *subcode = d_->pre_engine_subcode_; 185 } 186 return d_->pre_engine_error_; 187 } 188 return d_->engine_->GetError(subcode); 189} 190 191const XmlElement* XmppClient::GetStreamError() { 192 if (!d_->engine_) { 193 return NULL; 194 } 195 return d_->engine_->GetStreamError(); 196} 197 198CaptchaChallenge XmppClient::GetCaptchaChallenge() { 199 if (!d_->engine_) 200 return CaptchaChallenge(); 201 return d_->captcha_challenge_; 202} 203 204std::string XmppClient::GetAuthMechanism() { 205 if (!d_->engine_) 206 return ""; 207 return d_->auth_mechanism_; 208} 209 210std::string XmppClient::GetAuthToken() { 211 if (!d_->engine_) 212 return ""; 213 return d_->auth_token_; 214} 215 216int XmppClient::ProcessStart() { 217 // Should not happen, but was observed in crash reports 218 if (!d_->socket_) { 219 LOG(LS_ERROR) << "socket_ already reset"; 220 return STATE_DONE; 221 } 222 223 if (d_->pre_auth_) { 224 d_->pre_auth_->SignalAuthDone.connect(this, &XmppClient::OnAuthDone); 225 d_->pre_auth_->StartPreXmppAuth( 226 d_->engine_->GetUser(), d_->server_, d_->pass_, 227 d_->auth_mechanism_, d_->auth_token_); 228 d_->pass_.Clear(); // done with this; 229 return STATE_PRE_XMPP_LOGIN; 230 } 231 else { 232 d_->engine_->SetSaslHandler(new PlainSaslHandler( 233 d_->engine_->GetUser(), d_->pass_, d_->allow_plain_)); 234 d_->pass_.Clear(); // done with this; 235 return STATE_START_XMPP_LOGIN; 236 } 237} 238 239void XmppClient::OnAuthDone() { 240 Wake(); 241} 242 243int XmppClient::ProcessTokenLogin() { 244 // Should not happen, but was observed in crash reports 245 if (!d_->socket_) { 246 LOG(LS_ERROR) << "socket_ already reset"; 247 return STATE_DONE; 248 } 249 250 // Don't know how this could happen, but crash reports show it as NULL 251 if (!d_->pre_auth_) { 252 d_->pre_engine_error_ = XmppEngine::ERROR_AUTH; 253 EnsureClosed(); 254 return STATE_ERROR; 255 } 256 257 // Wait until pre authentication is done is done 258 if (!d_->pre_auth_->IsAuthDone()) 259 return STATE_BLOCKED; 260 261 if (!d_->pre_auth_->IsAuthorized()) { 262 // maybe split out a case when gaia is down? 263 if (d_->pre_auth_->HadError()) { 264 d_->pre_engine_error_ = XmppEngine::ERROR_AUTH; 265 d_->pre_engine_subcode_ = d_->pre_auth_->GetError(); 266 } 267 else { 268 d_->pre_engine_error_ = XmppEngine::ERROR_UNAUTHORIZED; 269 d_->pre_engine_subcode_ = 0; 270 d_->captcha_challenge_ = d_->pre_auth_->GetCaptchaChallenge(); 271 } 272 d_->pre_auth_.reset(NULL); // done with this 273 EnsureClosed(); 274 return STATE_ERROR; 275 } 276 277 // Save auth token as a result 278 279 d_->auth_mechanism_ = d_->pre_auth_->GetAuthMechanism(); 280 d_->auth_token_ = d_->pre_auth_->GetAuthToken(); 281 282 // transfer ownership of pre_auth_ to engine 283 d_->engine_->SetSaslHandler(d_->pre_auth_.release()); 284 return STATE_START_XMPP_LOGIN; 285} 286 287int XmppClient::ProcessStartXmppLogin() { 288 // Should not happen, but was observed in crash reports 289 if (!d_->socket_) { 290 LOG(LS_ERROR) << "socket_ already reset"; 291 return STATE_DONE; 292 } 293 294 // Done with pre-connect tasks - connect! 295 if (!d_->socket_->Connect(d_->server_)) { 296 EnsureClosed(); 297 return STATE_ERROR; 298 } 299 300 return STATE_RESPONSE; 301} 302 303int XmppClient::ProcessResponse() { 304 // Hang around while we are connected. 305 if (!delivering_signal_ && 306 (!d_->engine_ || d_->engine_->GetState() == XmppEngine::STATE_CLOSED)) 307 return STATE_DONE; 308 return STATE_BLOCKED; 309} 310 311XmppReturnStatus XmppClient::Disconnect() { 312 if (!d_->socket_) 313 return XMPP_RETURN_BADSTATE; 314 Abort(); 315 d_->engine_->Disconnect(); 316 d_->ResetSocket(); 317 return XMPP_RETURN_OK; 318} 319 320XmppClient::XmppClient(TaskParent* parent) 321 : XmppTaskParentInterface(parent), 322 delivering_signal_(false), 323 valid_(false) { 324 d_.reset(new Private(this)); 325 valid_ = true; 326} 327 328XmppClient::~XmppClient() { 329 valid_ = false; 330} 331 332const Jid& XmppClient::jid() const { 333 return d_->engine_->FullJid(); 334} 335 336 337std::string XmppClient::NextId() { 338 return d_->engine_->NextId(); 339} 340 341XmppReturnStatus XmppClient::SendStanza(const XmlElement* stanza) { 342 return d_->engine_->SendStanza(stanza); 343} 344 345XmppReturnStatus XmppClient::SendStanzaError( 346 const XmlElement* old_stanza, XmppStanzaError xse, 347 const std::string& message) { 348 return d_->engine_->SendStanzaError(old_stanza, xse, message); 349} 350 351XmppReturnStatus XmppClient::SendRaw(const std::string& text) { 352 return d_->engine_->SendRaw(text); 353} 354 355XmppEngine* XmppClient::engine() { 356 return d_->engine_.get(); 357} 358 359void XmppClient::Private::OnSocketConnected() { 360 engine_->Connect(); 361} 362 363void XmppClient::Private::OnSocketRead() { 364 char bytes[4096]; 365 size_t bytes_read; 366 for (;;) { 367 // Should not happen, but was observed in crash reports 368 if (!socket_) { 369 LOG(LS_ERROR) << "socket_ already reset"; 370 return; 371 } 372 373 if (!socket_->Read(bytes, sizeof(bytes), &bytes_read)) { 374 // TODO: deal with error information 375 return; 376 } 377 378 if (bytes_read == 0) 379 return; 380 381//#ifdef _DEBUG 382 client_->SignalLogInput(bytes, static_cast<int>(bytes_read)); 383//#endif 384 385 engine_->HandleInput(bytes, bytes_read); 386 } 387} 388 389void XmppClient::Private::OnSocketClosed() { 390 int code = socket_->GetError(); 391 engine_->ConnectionClosed(code); 392} 393 394void XmppClient::Private::OnStateChange(int state) { 395 if (state == XmppEngine::STATE_CLOSED) { 396 client_->EnsureClosed(); 397 } 398 else { 399 client_->SignalStateChange((XmppEngine::State)state); 400 } 401 client_->Wake(); 402} 403 404void XmppClient::Private::WriteOutput(const char* bytes, size_t len) { 405//#ifdef _DEBUG 406 client_->SignalLogOutput(bytes, static_cast<int>(len)); 407//#endif 408 409 socket_->Write(bytes, len); 410 // TODO: deal with error information 411} 412 413void XmppClient::Private::StartTls(const std::string& domain) { 414#if defined(FEATURE_ENABLE_SSL) 415 socket_->StartTls(domain); 416#endif 417} 418 419void XmppClient::Private::CloseConnection() { 420 socket_->Close(); 421} 422 423void XmppClient::AddXmppTask(XmppTask* task, XmppEngine::HandlerLevel level) { 424 d_->engine_->AddStanzaHandler(task, level); 425} 426 427void XmppClient::RemoveXmppTask(XmppTask* task) { 428 d_->engine_->RemoveStanzaHandler(task); 429} 430 431void XmppClient::EnsureClosed() { 432 if (!d_->signal_closed_) { 433 d_->signal_closed_ = true; 434 delivering_signal_ = true; 435 SignalStateChange(XmppEngine::STATE_CLOSED); 436 delivering_signal_ = false; 437 } 438} 439 440} // namespace buzz 441