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