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