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 "talk/xmpp/xmppclient.h"
29
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"
38#include "xmpptask.h"
39
40namespace buzz {
41
42class XmppClient::Private :
43    public sigslot::has_slots<>,
44    public XmppSessionHandler,
45    public XmppOutputHandler {
46public:
47
48  explicit Private(XmppClient* client) :
49    client_(client),
50    socket_(),
51    engine_(),
52    proxy_port_(0),
53    pre_engine_error_(XmppEngine::ERROR_NONE),
54    pre_engine_subcode_(0),
55    signal_closed_(false),
56    allow_plain_(false) {}
57
58  virtual ~Private() {
59    // We need to disconnect from socket_ before engine_ is destructed (by
60    // the auto-generated destructor code).
61    ResetSocket();
62  }
63
64  // the owner
65  XmppClient* const client_;
66
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_;
76  int proxy_port_;
77  XmppEngine::Error pre_engine_error_;
78  int pre_engine_subcode_;
79  CaptchaChallenge captcha_challenge_;
80  bool signal_closed_;
81  bool allow_plain_;
82
83  void ResetSocket() {
84    if (socket_) {
85      socket_->SignalConnected.disconnect(this);
86      socket_->SignalRead.disconnect(this);
87      socket_->SignalClosed.disconnect(this);
88      socket_.reset(NULL);
89    }
90  }
91
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();
97
98  // slots for socket signals
99  void OnSocketConnected();
100  void OnSocketRead();
101  void OnSocketClosed();
102};
103
104bool 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()));
109}
110
111XmppReturnStatus XmppClient::Connect(
112    const XmppClientSettings& settings,
113    const std::string& lang, AsyncSocket* socket, PreXmppAuth* pre_auth) {
114  if (socket == NULL)
115    return XMPP_RETURN_BADARGUMENT;
116  if (d_->socket_)
117    return XMPP_RETURN_BADSTATE;
118
119  d_->socket_.reset(socket);
120
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);
124
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());
130  }
131  d_->engine_->SetTls(settings.use_tls());
132
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);
151    }
152  }
153
154  // Set language
155  d_->engine_->SetLanguage(lang);
156
157  d_->engine_->SetUser(buzz::Jid(settings.user(), settings.host(), STR_EMPTY));
158
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);
167
168  return XMPP_RETURN_OK;
169}
170
171XmppEngine::State XmppClient::GetState() const {
172  if (!d_->engine_)
173    return XmppEngine::STATE_NONE;
174  return d_->engine_->GetState();
175}
176
177XmppEngine::Error XmppClient::GetError(int* subcode) {
178  if (subcode) {
179    *subcode = 0;
180  }
181  if (!d_->engine_)
182    return XmppEngine::ERROR_NONE;
183  if (d_->pre_engine_error_ != XmppEngine::ERROR_NONE) {
184    if (subcode) {
185      *subcode = d_->pre_engine_subcode_;
186    }
187    return d_->pre_engine_error_;
188  }
189  return d_->engine_->GetError(subcode);
190}
191
192const XmlElement* XmppClient::GetStreamError() {
193  if (!d_->engine_) {
194    return NULL;
195  }
196  return d_->engine_->GetStreamError();
197}
198
199CaptchaChallenge XmppClient::GetCaptchaChallenge() {
200  if (!d_->engine_)
201    return CaptchaChallenge();
202  return d_->captcha_challenge_;
203}
204
205std::string XmppClient::GetAuthMechanism() {
206  if (!d_->engine_)
207    return "";
208  return d_->auth_mechanism_;
209}
210
211std::string XmppClient::GetAuthToken() {
212  if (!d_->engine_)
213    return "";
214  return d_->auth_token_;
215}
216
217int XmppClient::ProcessStart() {
218  // Should not happen, but was observed in crash reports
219  if (!d_->socket_) {
220    LOG(LS_ERROR) << "socket_ already reset";
221    return STATE_DONE;
222  }
223
224  if (d_->pre_auth_) {
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;
231  }
232  else {
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;
237  }
238}
239
240void XmppClient::OnAuthDone() {
241  Wake();
242}
243
244int XmppClient::ProcessTokenLogin() {
245  // Should not happen, but was observed in crash reports
246  if (!d_->socket_) {
247    LOG(LS_ERROR) << "socket_ already reset";
248    return STATE_DONE;
249  }
250
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;
254    EnsureClosed();
255    return STATE_ERROR;
256  }
257
258  // Wait until pre authentication is done is done
259  if (!d_->pre_auth_->IsAuthDone())
260    return STATE_BLOCKED;
261
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();
267    }
268    else {
269      d_->pre_engine_error_ = XmppEngine::ERROR_UNAUTHORIZED;
270      d_->pre_engine_subcode_ = 0;
271      d_->captcha_challenge_ = d_->pre_auth_->GetCaptchaChallenge();
272    }
273    d_->pre_auth_.reset(NULL); // done with this
274    EnsureClosed();
275    return STATE_ERROR;
276  }
277
278  // Save auth token as a result
279
280  d_->auth_mechanism_ = d_->pre_auth_->GetAuthMechanism();
281  d_->auth_token_ = d_->pre_auth_->GetAuthToken();
282
283  // transfer ownership of pre_auth_ to engine
284  d_->engine_->SetSaslHandler(d_->pre_auth_.release());
285  return STATE_START_XMPP_LOGIN;
286}
287
288int XmppClient::ProcessStartXmppLogin() {
289  // Should not happen, but was observed in crash reports
290  if (!d_->socket_) {
291    LOG(LS_ERROR) << "socket_ already reset";
292    return STATE_DONE;
293  }
294
295  // Done with pre-connect tasks - connect!
296  if (!d_->socket_->Connect(d_->server_)) {
297    EnsureClosed();
298    return STATE_ERROR;
299  }
300
301  return STATE_RESPONSE;
302}
303
304int XmppClient::ProcessResponse() {
305  // Hang around while we are connected.
306  if (!delivering_signal_ &&
307      (!d_->engine_ || d_->engine_->GetState() == XmppEngine::STATE_CLOSED))
308    return STATE_DONE;
309  return STATE_BLOCKED;
310}
311
312XmppReturnStatus XmppClient::Disconnect() {
313  if (!d_->socket_)
314    return XMPP_RETURN_BADSTATE;
315  Abort();
316  d_->engine_->Disconnect();
317  d_->ResetSocket();
318  return XMPP_RETURN_OK;
319}
320
321XmppClient::XmppClient(TaskParent* parent)
322    : XmppTaskParentInterface(parent),
323      delivering_signal_(false),
324      valid_(false) {
325  d_.reset(new Private(this));
326  valid_ = true;
327}
328
329XmppClient::~XmppClient() {
330  valid_ = false;
331}
332
333const Jid& XmppClient::jid() const {
334  return d_->engine_->FullJid();
335}
336
337
338std::string XmppClient::NextId() {
339  return d_->engine_->NextId();
340}
341
342XmppReturnStatus XmppClient::SendStanza(const XmlElement* stanza) {
343  return d_->engine_->SendStanza(stanza);
344}
345
346XmppReturnStatus XmppClient::SendStanzaError(
347    const XmlElement* old_stanza, XmppStanzaError xse,
348    const std::string& message) {
349  return d_->engine_->SendStanzaError(old_stanza, xse, message);
350}
351
352XmppReturnStatus XmppClient::SendRaw(const std::string& text) {
353  return d_->engine_->SendRaw(text);
354}
355
356XmppEngine* XmppClient::engine() {
357  return d_->engine_.get();
358}
359
360void XmppClient::Private::OnSocketConnected() {
361  engine_->Connect();
362}
363
364void XmppClient::Private::OnSocketRead() {
365  char bytes[4096];
366  size_t bytes_read;
367  for (;;) {
368    // Should not happen, but was observed in crash reports
369    if (!socket_) {
370      LOG(LS_ERROR) << "socket_ already reset";
371      return;
372    }
373
374    if (!socket_->Read(bytes, sizeof(bytes), &bytes_read)) {
375      // TODO: deal with error information
376      return;
377    }
378
379    if (bytes_read == 0)
380      return;
381
382//#ifdef _DEBUG
383    client_->SignalLogInput(bytes, static_cast<int>(bytes_read));
384//#endif
385
386    engine_->HandleInput(bytes, bytes_read);
387  }
388}
389
390void XmppClient::Private::OnSocketClosed() {
391  int code = socket_->GetError();
392  engine_->ConnectionClosed(code);
393}
394
395void XmppClient::Private::OnStateChange(int state) {
396  if (state == XmppEngine::STATE_CLOSED) {
397    client_->EnsureClosed();
398  }
399  else {
400    client_->SignalStateChange((XmppEngine::State)state);
401  }
402  client_->Wake();
403}
404
405void XmppClient::Private::WriteOutput(const char* bytes, size_t len) {
406//#ifdef _DEBUG
407  client_->SignalLogOutput(bytes, static_cast<int>(len));
408//#endif
409
410  socket_->Write(bytes, len);
411  // TODO: deal with error information
412}
413
414void XmppClient::Private::StartTls(const std::string& domain) {
415#if defined(FEATURE_ENABLE_SSL)
416  socket_->StartTls(domain);
417#endif
418}
419
420void XmppClient::Private::CloseConnection() {
421  socket_->Close();
422}
423
424void XmppClient::AddXmppTask(XmppTask* task, XmppEngine::HandlerLevel level) {
425  d_->engine_->AddStanzaHandler(task, level);
426}
427
428void XmppClient::RemoveXmppTask(XmppTask* task) {
429  d_->engine_->RemoveStanzaHandler(task);
430}
431
432void 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;
438  }
439}
440
441}  // namespace buzz
442