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