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