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
23 namespace buzz {
24
25 class XmppClient::Private :
26 public sigslot::has_slots<>,
27 public XmppSessionHandler,
28 public XmppOutputHandler {
29 public:
30
Private(XmppClient * client)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
~Private()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
ResetSocket()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
IsTestServer(const std::string & server_name,const std::string & test_server_domain)87 bool 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
Connect(const XmppClientSettings & settings,const std::string & lang,AsyncSocket * socket,PreXmppAuth * pre_auth)94 XmppReturnStatus 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
GetState() const154 XmppEngine::State XmppClient::GetState() const {
155 if (!d_->engine_)
156 return XmppEngine::STATE_NONE;
157 return d_->engine_->GetState();
158 }
159
GetError(int * subcode)160 XmppEngine::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
GetStreamError()175 const XmlElement* XmppClient::GetStreamError() {
176 if (!d_->engine_) {
177 return NULL;
178 }
179 return d_->engine_->GetStreamError();
180 }
181
GetCaptchaChallenge()182 CaptchaChallenge XmppClient::GetCaptchaChallenge() {
183 if (!d_->engine_)
184 return CaptchaChallenge();
185 return d_->captcha_challenge_;
186 }
187
GetAuthMechanism()188 std::string XmppClient::GetAuthMechanism() {
189 if (!d_->engine_)
190 return "";
191 return d_->auth_mechanism_;
192 }
193
GetAuthToken()194 std::string XmppClient::GetAuthToken() {
195 if (!d_->engine_)
196 return "";
197 return d_->auth_token_;
198 }
199
ProcessStart()200 int 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
OnAuthDone()223 void XmppClient::OnAuthDone() {
224 Wake();
225 }
226
ProcessTokenLogin()227 int 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
ProcessStartXmppLogin()271 int 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
ProcessResponse()287 int 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
Disconnect()295 XmppReturnStatus 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
XmppClient(TaskParent * parent)304 XmppClient::XmppClient(TaskParent* parent)
305 : XmppTaskParentInterface(parent),
306 delivering_signal_(false),
307 valid_(false) {
308 d_.reset(new Private(this));
309 valid_ = true;
310 }
311
~XmppClient()312 XmppClient::~XmppClient() {
313 valid_ = false;
314 }
315
jid() const316 const Jid& XmppClient::jid() const {
317 return d_->engine_->FullJid();
318 }
319
320
NextId()321 std::string XmppClient::NextId() {
322 return d_->engine_->NextId();
323 }
324
SendStanza(const XmlElement * stanza)325 XmppReturnStatus XmppClient::SendStanza(const XmlElement* stanza) {
326 return d_->engine_->SendStanza(stanza);
327 }
328
SendStanzaError(const XmlElement * old_stanza,XmppStanzaError xse,const std::string & message)329 XmppReturnStatus 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
SendRaw(const std::string & text)335 XmppReturnStatus XmppClient::SendRaw(const std::string& text) {
336 return d_->engine_->SendRaw(text);
337 }
338
engine()339 XmppEngine* XmppClient::engine() {
340 return d_->engine_.get();
341 }
342
OnSocketConnected()343 void XmppClient::Private::OnSocketConnected() {
344 engine_->Connect();
345 }
346
OnSocketRead()347 void 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
OnSocketClosed()373 void XmppClient::Private::OnSocketClosed() {
374 int code = socket_->GetError();
375 engine_->ConnectionClosed(code);
376 }
377
OnStateChange(int state)378 void 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
WriteOutput(const char * bytes,size_t len)388 void 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
StartTls(const std::string & domain)397 void XmppClient::Private::StartTls(const std::string& domain) {
398 #if defined(FEATURE_ENABLE_SSL)
399 socket_->StartTls(domain);
400 #endif
401 }
402
CloseConnection()403 void XmppClient::Private::CloseConnection() {
404 socket_->Close();
405 }
406
AddXmppTask(XmppTask * task,XmppEngine::HandlerLevel level)407 void XmppClient::AddXmppTask(XmppTask* task, XmppEngine::HandlerLevel level) {
408 d_->engine_->AddStanzaHandler(task, level);
409 }
410
RemoveXmppTask(XmppTask * task)411 void XmppClient::RemoveXmppTask(XmppTask* task) {
412 d_->engine_->RemoveStanzaHandler(task);
413 }
414
EnsureClosed()415 void 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