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