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
39 namespace buzz {
40
41 class XmppClient::Private :
42 public sigslot::has_slots<>,
43 public XmppSessionHandler,
44 public XmppOutputHandler {
45 public:
46
Private(XmppClient * client)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
~Private()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
ResetSocket()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
IsTestServer(const std::string & server_name,const std::string & test_server_domain)103 bool 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
Connect(const XmppClientSettings & settings,const std::string & lang,AsyncSocket * socket,PreXmppAuth * pre_auth)110 XmppReturnStatus 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
GetState() const170 XmppEngine::State XmppClient::GetState() const {
171 if (!d_->engine_)
172 return XmppEngine::STATE_NONE;
173 return d_->engine_->GetState();
174 }
175
GetError(int * subcode)176 XmppEngine::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
GetStreamError()191 const XmlElement* XmppClient::GetStreamError() {
192 if (!d_->engine_) {
193 return NULL;
194 }
195 return d_->engine_->GetStreamError();
196 }
197
GetCaptchaChallenge()198 CaptchaChallenge XmppClient::GetCaptchaChallenge() {
199 if (!d_->engine_)
200 return CaptchaChallenge();
201 return d_->captcha_challenge_;
202 }
203
GetAuthMechanism()204 std::string XmppClient::GetAuthMechanism() {
205 if (!d_->engine_)
206 return "";
207 return d_->auth_mechanism_;
208 }
209
GetAuthToken()210 std::string XmppClient::GetAuthToken() {
211 if (!d_->engine_)
212 return "";
213 return d_->auth_token_;
214 }
215
ProcessStart()216 int 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
OnAuthDone()239 void XmppClient::OnAuthDone() {
240 Wake();
241 }
242
ProcessTokenLogin()243 int 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
ProcessStartXmppLogin()287 int 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
ProcessResponse()303 int 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
Disconnect()311 XmppReturnStatus 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
XmppClient(TaskParent * parent)320 XmppClient::XmppClient(TaskParent* parent)
321 : XmppTaskParentInterface(parent),
322 delivering_signal_(false),
323 valid_(false) {
324 d_.reset(new Private(this));
325 valid_ = true;
326 }
327
~XmppClient()328 XmppClient::~XmppClient() {
329 valid_ = false;
330 }
331
jid() const332 const Jid& XmppClient::jid() const {
333 return d_->engine_->FullJid();
334 }
335
336
NextId()337 std::string XmppClient::NextId() {
338 return d_->engine_->NextId();
339 }
340
SendStanza(const XmlElement * stanza)341 XmppReturnStatus XmppClient::SendStanza(const XmlElement* stanza) {
342 return d_->engine_->SendStanza(stanza);
343 }
344
SendStanzaError(const XmlElement * old_stanza,XmppStanzaError xse,const std::string & message)345 XmppReturnStatus 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
SendRaw(const std::string & text)351 XmppReturnStatus XmppClient::SendRaw(const std::string& text) {
352 return d_->engine_->SendRaw(text);
353 }
354
engine()355 XmppEngine* XmppClient::engine() {
356 return d_->engine_.get();
357 }
358
OnSocketConnected()359 void XmppClient::Private::OnSocketConnected() {
360 engine_->Connect();
361 }
362
OnSocketRead()363 void 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
OnSocketClosed()389 void XmppClient::Private::OnSocketClosed() {
390 int code = socket_->GetError();
391 engine_->ConnectionClosed(code);
392 }
393
OnStateChange(int state)394 void 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
WriteOutput(const char * bytes,size_t len)404 void 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
StartTls(const std::string & domain)413 void XmppClient::Private::StartTls(const std::string& domain) {
414 #if defined(FEATURE_ENABLE_SSL)
415 socket_->StartTls(domain);
416 #endif
417 }
418
CloseConnection()419 void XmppClient::Private::CloseConnection() {
420 socket_->Close();
421 }
422
AddXmppTask(XmppTask * task,XmppEngine::HandlerLevel level)423 void XmppClient::AddXmppTask(XmppTask* task, XmppEngine::HandlerLevel level) {
424 d_->engine_->AddStanzaHandler(task, level);
425 }
426
RemoveXmppTask(XmppTask * task)427 void XmppClient::RemoveXmppTask(XmppTask* task) {
428 d_->engine_->RemoveStanzaHandler(task);
429 }
430
EnsureClosed()431 void 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