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/xmpp/constants.h" 31 #include "talk/base/sigslot.h" 32 #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 37 namespace buzz { 38 39 talk_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 46 class XmppClient::Private : 47 public sigslot::has_slots<>, 48 public XmppSessionHandler, 49 public XmppOutputHandler { 50 public: 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 92 XmppReturnStatus 93 XmppClient::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 (at) 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 145 XmppEngine::State 146 XmppClient::GetState() { 147 if (d_->engine_.get() == NULL) 148 return XmppEngine::STATE_NONE; 149 return d_->engine_->GetState(); 150 } 151 152 XmppEngine::Error 153 XmppClient::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 168 const XmlElement * 169 XmppClient::GetStreamError() { 170 if (d_->engine_.get() == NULL) { 171 return NULL; 172 } 173 return d_->engine_->GetStreamError(); 174 } 175 176 CaptchaChallenge XmppClient::GetCaptchaChallenge() { 177 if (d_->engine_.get() == NULL) 178 return CaptchaChallenge(); 179 return d_->captcha_challenge_; 180 } 181 182 std::string 183 XmppClient::GetAuthCookie() { 184 if (d_->engine_.get() == NULL) 185 return ""; 186 return d_->auth_cookie_; 187 } 188 189 int 190 XmppClient::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 206 void 207 XmppClient::OnAuthDone() { 208 Wake(); 209 } 210 211 int 212 XmppClient::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 248 int 249 XmppClient::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 259 int 260 XmppClient::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 268 XmppReturnStatus 269 XmppClient::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 277 XmppClient::XmppClient(TaskParent * parent) 278 : Task(parent), 279 delivering_signal_(false), 280 valid_(false) { 281 d_.reset(new Private(this)); 282 valid_ = true; 283 } 284 285 XmppClient::~XmppClient() { 286 valid_ = false; 287 } 288 289 const Jid & 290 XmppClient::jid() { 291 return d_->engine_->FullJid(); 292 } 293 294 295 std::string 296 XmppClient::NextId() { 297 return d_->engine_->NextId(); 298 } 299 300 XmppReturnStatus 301 XmppClient::SendStanza(const XmlElement * stanza) { 302 return d_->engine_->SendStanza(stanza); 303 } 304 305 XmppReturnStatus 306 XmppClient::SendStanzaError(const XmlElement * old_stanza, XmppStanzaError xse, const std::string & message) { 307 return d_->engine_->SendStanzaError(old_stanza, xse, message); 308 } 309 310 XmppReturnStatus 311 XmppClient::SendRaw(const std::string & text) { 312 return d_->engine_->SendRaw(text); 313 } 314 315 XmppEngine* 316 XmppClient::engine() { 317 return d_->engine_.get(); 318 } 319 320 void 321 XmppClient::Private::OnSocketConnected() { 322 engine_->Connect(); 323 } 324 325 void 326 XmppClient::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 346 void 347 XmppClient::Private::OnSocketClosed() { 348 int code = socket_->GetError(); 349 engine_->ConnectionClosed(code); 350 } 351 352 void 353 XmppClient::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 363 void 364 XmppClient::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 374 void 375 XmppClient::Private::StartTls(const std::string & domain) { 376 #if defined(FEATURE_ENABLE_SSL) 377 socket_->StartTls(domain); 378 #endif 379 } 380 381 void 382 XmppClient::Private::CloseConnection() { 383 socket_->Close(); 384 } 385 386 void 387 XmppClient::AddXmppTask(XmppTask * task, XmppEngine::HandlerLevel level) { 388 d_->engine_->AddStanzaHandler(task, level); 389 } 390 391 void 392 XmppClient::RemoveXmppTask(XmppTask * task) { 393 d_->engine_->RemoveStanzaHandler(task); 394 } 395 396 void 397 XmppClient::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