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