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