3 * Copyright 2004--2005, Google Inc.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
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.
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.
28 #include "talk/xmpp/xmppclient.h"
30 #include "talk/xmpp/constants.h"
31 #include "talk/xmpp/plainsaslhandler.h"
32 #include "talk/xmpp/prexmppauth.h"
33 #include "talk/xmpp/saslplainmechanism.h"
34 #include "webrtc/base/logging.h"
35 #include "webrtc/base/scoped_ptr.h"
36 #include "webrtc/base/sigslot.h"
37 #include "webrtc/base/stringutils.h"
42 class XmppClient::Private :
43 public sigslot::has_slots<>,
44 public XmppSessionHandler,
45 public XmppOutputHandler {
48 explicit Private(XmppClient* client) :
53 pre_engine_error_(XmppEngine::ERROR_NONE),
54 pre_engine_subcode_(0),
55 signal_closed_(false),
56 allow_plain_(false) {}
59 // We need to disconnect from socket_ before engine_ is destructed (by
60 // the auto-generated destructor code).
65 XmppClient* const client_;
67 // the two main objects
68 rtc::scoped_ptr<AsyncSocket> socket_;
69 rtc::scoped_ptr<XmppEngine> engine_;
70 rtc::scoped_ptr<PreXmppAuth> pre_auth_;
71 rtc::CryptString pass_;
72 std::string auth_mechanism_;
73 std::string auth_token_;
74 rtc::SocketAddress server_;
75 std::string proxy_host_;
77 XmppEngine::Error pre_engine_error_;
78 int pre_engine_subcode_;
79 CaptchaChallenge captcha_challenge_;
85 socket_->SignalConnected.disconnect(this);
86 socket_->SignalRead.disconnect(this);
87 socket_->SignalClosed.disconnect(this);
92 // implementations of interfaces
93 void OnStateChange(int state);
94 void WriteOutput(const char* bytes, size_t len);
95 void StartTls(const std::string& domainname);
96 void CloseConnection();
98 // slots for socket signals
99 void OnSocketConnected();
101 void OnSocketClosed();
104 bool IsTestServer(const std::string& server_name,
105 const std::string& test_server_domain) {
106 return (!test_server_domain.empty() &&
107 rtc::ends_with(server_name.c_str(),
108 test_server_domain.c_str()));
111 XmppReturnStatus XmppClient::Connect(
112 const XmppClientSettings& settings,
113 const std::string& lang, AsyncSocket* socket, PreXmppAuth* pre_auth) {
115 return XMPP_RETURN_BADARGUMENT;
117 return XMPP_RETURN_BADSTATE;
119 d_->socket_.reset(socket);
121 d_->socket_->SignalConnected.connect(d_.get(), &Private::OnSocketConnected);
122 d_->socket_->SignalRead.connect(d_.get(), &Private::OnSocketRead);
123 d_->socket_->SignalClosed.connect(d_.get(), &Private::OnSocketClosed);
125 d_->engine_.reset(XmppEngine::Create());
126 d_->engine_->SetSessionHandler(d_.get());
127 d_->engine_->SetOutputHandler(d_.get());
128 if (!settings.resource().empty()) {
129 d_->engine_->SetRequestedResource(settings.resource());
131 d_->engine_->SetTls(settings.use_tls());
133 // The talk.google.com server returns a certificate with common-name:
134 // CN="gmail.com" for @gmail.com accounts,
135 // CN="googlemail.com" for @googlemail.com accounts,
136 // CN="talk.google.com" for other accounts (such as @example.com),
137 // so we tweak the tls server setting for those other accounts to match the
138 // returned certificate CN of "talk.google.com".
139 // For other servers, we leave the strings empty, which causes the jid's
140 // domain to be used. We do the same for gmail.com and googlemail.com as the
141 // returned CN matches the account domain in those cases.
142 std::string server_name = settings.server().HostAsURIString();
143 if (server_name == buzz::STR_TALK_GOOGLE_COM ||
144 server_name == buzz::STR_TALKX_L_GOOGLE_COM ||
145 server_name == buzz::STR_XMPP_GOOGLE_COM ||
146 server_name == buzz::STR_XMPPX_L_GOOGLE_COM ||
147 IsTestServer(server_name, settings.test_server_domain())) {
148 if (settings.host() != STR_GMAIL_COM &&
149 settings.host() != STR_GOOGLEMAIL_COM) {
150 d_->engine_->SetTlsServer("", STR_TALK_GOOGLE_COM);
155 d_->engine_->SetLanguage(lang);
157 d_->engine_->SetUser(buzz::Jid(settings.user(), settings.host(), STR_EMPTY));
159 d_->pass_ = settings.pass();
160 d_->auth_mechanism_ = settings.auth_mechanism();
161 d_->auth_token_ = settings.auth_token();
162 d_->server_ = settings.server();
163 d_->proxy_host_ = settings.proxy_host();
164 d_->proxy_port_ = settings.proxy_port();
165 d_->allow_plain_ = settings.allow_plain();
166 d_->pre_auth_.reset(pre_auth);
168 return XMPP_RETURN_OK;
171 XmppEngine::State XmppClient::GetState() const {
173 return XmppEngine::STATE_NONE;
174 return d_->engine_->GetState();
177 XmppEngine::Error XmppClient::GetError(int* subcode) {
182 return XmppEngine::ERROR_NONE;
183 if (d_->pre_engine_error_ != XmppEngine::ERROR_NONE) {
185 *subcode = d_->pre_engine_subcode_;
187 return d_->pre_engine_error_;
189 return d_->engine_->GetError(subcode);
192 const XmlElement* XmppClient::GetStreamError() {
196 return d_->engine_->GetStreamError();
199 CaptchaChallenge XmppClient::GetCaptchaChallenge() {
201 return CaptchaChallenge();
202 return d_->captcha_challenge_;
205 std::string XmppClient::GetAuthMechanism() {
208 return d_->auth_mechanism_;
211 std::string XmppClient::GetAuthToken() {
214 return d_->auth_token_;
217 int XmppClient::ProcessStart() {
218 // Should not happen, but was observed in crash reports
220 LOG(LS_ERROR) << "socket_ already reset";
225 d_->pre_auth_->SignalAuthDone.connect(this, &XmppClient::OnAuthDone);
226 d_->pre_auth_->StartPreXmppAuth(
227 d_->engine_->GetUser(), d_->server_, d_->pass_,
228 d_->auth_mechanism_, d_->auth_token_);
229 d_->pass_.Clear(); // done with this;
230 return STATE_PRE_XMPP_LOGIN;
233 d_->engine_->SetSaslHandler(new PlainSaslHandler(
234 d_->engine_->GetUser(), d_->pass_, d_->allow_plain_));
235 d_->pass_.Clear(); // done with this;
236 return STATE_START_XMPP_LOGIN;
240 void XmppClient::OnAuthDone() {
244 int XmppClient::ProcessTokenLogin() {
245 // Should not happen, but was observed in crash reports
247 LOG(LS_ERROR) << "socket_ already reset";
251 // Don't know how this could happen, but crash reports show it as NULL
252 if (!d_->pre_auth_) {
253 d_->pre_engine_error_ = XmppEngine::ERROR_AUTH;
258 // Wait until pre authentication is done is done
259 if (!d_->pre_auth_->IsAuthDone())
260 return STATE_BLOCKED;
262 if (!d_->pre_auth_->IsAuthorized()) {
263 // maybe split out a case when gaia is down?
264 if (d_->pre_auth_->HadError()) {
265 d_->pre_engine_error_ = XmppEngine::ERROR_AUTH;
266 d_->pre_engine_subcode_ = d_->pre_auth_->GetError();
269 d_->pre_engine_error_ = XmppEngine::ERROR_UNAUTHORIZED;
270 d_->pre_engine_subcode_ = 0;
271 d_->captcha_challenge_ = d_->pre_auth_->GetCaptchaChallenge();
273 d_->pre_auth_.reset(NULL); // done with this
278 // Save auth token as a result
280 d_->auth_mechanism_ = d_->pre_auth_->GetAuthMechanism();
281 d_->auth_token_ = d_->pre_auth_->GetAuthToken();
283 // transfer ownership of pre_auth_ to engine
284 d_->engine_->SetSaslHandler(d_->pre_auth_.release());
285 return STATE_START_XMPP_LOGIN;
288 int XmppClient::ProcessStartXmppLogin() {
289 // Should not happen, but was observed in crash reports
291 LOG(LS_ERROR) << "socket_ already reset";
295 // Done with pre-connect tasks - connect!
296 if (!d_->socket_->Connect(d_->server_)) {
301 return STATE_RESPONSE;
304 int XmppClient::ProcessResponse() {
305 // Hang around while we are connected.
306 if (!delivering_signal_ &&
307 (!d_->engine_ || d_->engine_->GetState() == XmppEngine::STATE_CLOSED))
309 return STATE_BLOCKED;
312 XmppReturnStatus XmppClient::Disconnect() {
314 return XMPP_RETURN_BADSTATE;
316 d_->engine_->Disconnect();
318 return XMPP_RETURN_OK;
321 XmppClient::XmppClient(TaskParent* parent)
322 : XmppTaskParentInterface(parent),
323 delivering_signal_(false),
325 d_.reset(new Private(this));
329 XmppClient::~XmppClient() {
333 const Jid& XmppClient::jid() const {
334 return d_->engine_->FullJid();
338 std::string XmppClient::NextId() {
339 return d_->engine_->NextId();
342 XmppReturnStatus XmppClient::SendStanza(const XmlElement* stanza) {
343 return d_->engine_->SendStanza(stanza);
346 XmppReturnStatus XmppClient::SendStanzaError(
347 const XmlElement* old_stanza, XmppStanzaError xse,
348 const std::string& message) {
349 return d_->engine_->SendStanzaError(old_stanza, xse, message);
352 XmppReturnStatus XmppClient::SendRaw(const std::string& text) {
353 return d_->engine_->SendRaw(text);
356 XmppEngine* XmppClient::engine() {
357 return d_->engine_.get();
360 void XmppClient::Private::OnSocketConnected() {
364 void XmppClient::Private::OnSocketRead() {
368 // Should not happen, but was observed in crash reports
370 LOG(LS_ERROR) << "socket_ already reset";
374 if (!socket_->Read(bytes, sizeof(bytes), &bytes_read)) {
375 // TODO: deal with error information
383 client_->SignalLogInput(bytes, static_cast<int>(bytes_read));
386 engine_->HandleInput(bytes, bytes_read);
390 void XmppClient::Private::OnSocketClosed() {
391 int code = socket_->GetError();
392 engine_->ConnectionClosed(code);
395 void XmppClient::Private::OnStateChange(int state) {
396 if (state == XmppEngine::STATE_CLOSED) {
397 client_->EnsureClosed();
400 client_->SignalStateChange((XmppEngine::State)state);
405 void XmppClient::Private::WriteOutput(const char* bytes, size_t len) {
407 client_->SignalLogOutput(bytes, static_cast<int>(len));
410 socket_->Write(bytes, len);
411 // TODO: deal with error information
414 void XmppClient::Private::StartTls(const std::string& domain) {
415 #if defined(FEATURE_ENABLE_SSL)
416 socket_->StartTls(domain);
420 void XmppClient::Private::CloseConnection() {
424 void XmppClient::AddXmppTask(XmppTask* task, XmppEngine::HandlerLevel level) {
425 d_->engine_->AddStanzaHandler(task, level);
428 void XmppClient::RemoveXmppTask(XmppTask* task) {
429 d_->engine_->RemoveStanzaHandler(task);
432 void XmppClient::EnsureClosed() {
433 if (!d_->signal_closed_) {
434 d_->signal_closed_ = true;
435 delivering_signal_ = true;
436 SignalStateChange(XmppEngine::STATE_CLOSED);
437 delivering_signal_ = false;