OLD | NEW |
| (Empty) |
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/sigslot.h" | |
19 #include "webrtc/base/stringutils.h" | |
20 #include "xmpptask.h" | |
21 | |
22 namespace buzz { | |
23 | |
24 class XmppClient::Private : | |
25 public sigslot::has_slots<>, | |
26 public XmppSessionHandler, | |
27 public XmppOutputHandler { | |
28 public: | |
29 | |
30 explicit Private(XmppClient* client) : | |
31 client_(client), | |
32 socket_(), | |
33 engine_(), | |
34 proxy_port_(0), | |
35 pre_engine_error_(XmppEngine::ERROR_NONE), | |
36 pre_engine_subcode_(0), | |
37 signal_closed_(false), | |
38 allow_plain_(false) {} | |
39 | |
40 virtual ~Private() { | |
41 // We need to disconnect from socket_ before engine_ is destructed (by | |
42 // the auto-generated destructor code). | |
43 ResetSocket(); | |
44 } | |
45 | |
46 // the owner | |
47 XmppClient* const client_; | |
48 | |
49 // the two main objects | |
50 std::unique_ptr<AsyncSocket> socket_; | |
51 std::unique_ptr<XmppEngine> engine_; | |
52 std::unique_ptr<PreXmppAuth> pre_auth_; | |
53 rtc::CryptString pass_; | |
54 std::string auth_mechanism_; | |
55 std::string auth_token_; | |
56 rtc::SocketAddress server_; | |
57 std::string proxy_host_; | |
58 int proxy_port_; | |
59 XmppEngine::Error pre_engine_error_; | |
60 int pre_engine_subcode_; | |
61 CaptchaChallenge captcha_challenge_; | |
62 bool signal_closed_; | |
63 bool allow_plain_; | |
64 | |
65 void ResetSocket() { | |
66 if (socket_) { | |
67 socket_->SignalConnected.disconnect(this); | |
68 socket_->SignalRead.disconnect(this); | |
69 socket_->SignalClosed.disconnect(this); | |
70 socket_.reset(NULL); | |
71 } | |
72 } | |
73 | |
74 // implementations of interfaces | |
75 void OnStateChange(int state); | |
76 void WriteOutput(const char* bytes, size_t len); | |
77 void StartTls(const std::string& domainname); | |
78 void CloseConnection(); | |
79 | |
80 // slots for socket signals | |
81 void OnSocketConnected(); | |
82 void OnSocketRead(); | |
83 void OnSocketClosed(); | |
84 }; | |
85 | |
86 bool IsTestServer(const std::string& server_name, | |
87 const std::string& test_server_domain) { | |
88 return (!test_server_domain.empty() && | |
89 rtc::ends_with(server_name.c_str(), | |
90 test_server_domain.c_str())); | |
91 } | |
92 | |
93 XmppReturnStatus XmppClient::Connect( | |
94 const XmppClientSettings& settings, | |
95 const std::string& lang, AsyncSocket* socket, PreXmppAuth* pre_auth) { | |
96 if (socket == NULL) | |
97 return XMPP_RETURN_BADARGUMENT; | |
98 if (d_->socket_) | |
99 return XMPP_RETURN_BADSTATE; | |
100 | |
101 d_->socket_.reset(socket); | |
102 | |
103 d_->socket_->SignalConnected.connect(d_.get(), &Private::OnSocketConnected); | |
104 d_->socket_->SignalRead.connect(d_.get(), &Private::OnSocketRead); | |
105 d_->socket_->SignalClosed.connect(d_.get(), &Private::OnSocketClosed); | |
106 | |
107 d_->engine_.reset(XmppEngine::Create()); | |
108 d_->engine_->SetSessionHandler(d_.get()); | |
109 d_->engine_->SetOutputHandler(d_.get()); | |
110 if (!settings.resource().empty()) { | |
111 d_->engine_->SetRequestedResource(settings.resource()); | |
112 } | |
113 d_->engine_->SetTls(settings.use_tls()); | |
114 | |
115 // The talk.google.com server returns a certificate with common-name: | |
116 // CN="gmail.com" for @gmail.com accounts, | |
117 // CN="googlemail.com" for @googlemail.com accounts, | |
118 // CN="talk.google.com" for other accounts (such as @example.com), | |
119 // so we tweak the tls server setting for those other accounts to match the | |
120 // returned certificate CN of "talk.google.com". | |
121 // For other servers, we leave the strings empty, which causes the jid's | |
122 // domain to be used. We do the same for gmail.com and googlemail.com as the | |
123 // returned CN matches the account domain in those cases. | |
124 std::string server_name = settings.server().HostAsURIString(); | |
125 if (server_name == buzz::STR_TALK_GOOGLE_COM || | |
126 server_name == buzz::STR_TALKX_L_GOOGLE_COM || | |
127 server_name == buzz::STR_XMPP_GOOGLE_COM || | |
128 server_name == buzz::STR_XMPPX_L_GOOGLE_COM || | |
129 IsTestServer(server_name, settings.test_server_domain())) { | |
130 if (settings.host() != STR_GMAIL_COM && | |
131 settings.host() != STR_GOOGLEMAIL_COM) { | |
132 d_->engine_->SetTlsServer("", STR_TALK_GOOGLE_COM); | |
133 } | |
134 } | |
135 | |
136 // Set language | |
137 d_->engine_->SetLanguage(lang); | |
138 | |
139 d_->engine_->SetUser(buzz::Jid(settings.user(), settings.host(), STR_EMPTY)); | |
140 | |
141 d_->pass_ = settings.pass(); | |
142 d_->auth_mechanism_ = settings.auth_mechanism(); | |
143 d_->auth_token_ = settings.auth_token(); | |
144 d_->server_ = settings.server(); | |
145 d_->proxy_host_ = settings.proxy_host(); | |
146 d_->proxy_port_ = settings.proxy_port(); | |
147 d_->allow_plain_ = settings.allow_plain(); | |
148 d_->pre_auth_.reset(pre_auth); | |
149 | |
150 return XMPP_RETURN_OK; | |
151 } | |
152 | |
153 XmppEngine::State XmppClient::GetState() const { | |
154 if (!d_->engine_) | |
155 return XmppEngine::STATE_NONE; | |
156 return d_->engine_->GetState(); | |
157 } | |
158 | |
159 XmppEngine::Error XmppClient::GetError(int* subcode) { | |
160 if (subcode) { | |
161 *subcode = 0; | |
162 } | |
163 if (!d_->engine_) | |
164 return XmppEngine::ERROR_NONE; | |
165 if (d_->pre_engine_error_ != XmppEngine::ERROR_NONE) { | |
166 if (subcode) { | |
167 *subcode = d_->pre_engine_subcode_; | |
168 } | |
169 return d_->pre_engine_error_; | |
170 } | |
171 return d_->engine_->GetError(subcode); | |
172 } | |
173 | |
174 const XmlElement* XmppClient::GetStreamError() { | |
175 if (!d_->engine_) { | |
176 return NULL; | |
177 } | |
178 return d_->engine_->GetStreamError(); | |
179 } | |
180 | |
181 CaptchaChallenge XmppClient::GetCaptchaChallenge() { | |
182 if (!d_->engine_) | |
183 return CaptchaChallenge(); | |
184 return d_->captcha_challenge_; | |
185 } | |
186 | |
187 std::string XmppClient::GetAuthMechanism() { | |
188 if (!d_->engine_) | |
189 return ""; | |
190 return d_->auth_mechanism_; | |
191 } | |
192 | |
193 std::string XmppClient::GetAuthToken() { | |
194 if (!d_->engine_) | |
195 return ""; | |
196 return d_->auth_token_; | |
197 } | |
198 | |
199 int XmppClient::ProcessStart() { | |
200 // Should not happen, but was observed in crash reports | |
201 if (!d_->socket_) { | |
202 LOG(LS_ERROR) << "socket_ already reset"; | |
203 return STATE_DONE; | |
204 } | |
205 | |
206 if (d_->pre_auth_) { | |
207 d_->pre_auth_->SignalAuthDone.connect(this, &XmppClient::OnAuthDone); | |
208 d_->pre_auth_->StartPreXmppAuth( | |
209 d_->engine_->GetUser(), d_->server_, d_->pass_, | |
210 d_->auth_mechanism_, d_->auth_token_); | |
211 d_->pass_.Clear(); // done with this; | |
212 return STATE_PRE_XMPP_LOGIN; | |
213 } | |
214 else { | |
215 d_->engine_->SetSaslHandler(new PlainSaslHandler( | |
216 d_->engine_->GetUser(), d_->pass_, d_->allow_plain_)); | |
217 d_->pass_.Clear(); // done with this; | |
218 return STATE_START_XMPP_LOGIN; | |
219 } | |
220 } | |
221 | |
222 void XmppClient::OnAuthDone() { | |
223 Wake(); | |
224 } | |
225 | |
226 int XmppClient::ProcessTokenLogin() { | |
227 // Should not happen, but was observed in crash reports | |
228 if (!d_->socket_) { | |
229 LOG(LS_ERROR) << "socket_ already reset"; | |
230 return STATE_DONE; | |
231 } | |
232 | |
233 // Don't know how this could happen, but crash reports show it as NULL | |
234 if (!d_->pre_auth_) { | |
235 d_->pre_engine_error_ = XmppEngine::ERROR_AUTH; | |
236 EnsureClosed(); | |
237 return STATE_ERROR; | |
238 } | |
239 | |
240 // Wait until pre authentication is done is done | |
241 if (!d_->pre_auth_->IsAuthDone()) | |
242 return STATE_BLOCKED; | |
243 | |
244 if (!d_->pre_auth_->IsAuthorized()) { | |
245 // maybe split out a case when gaia is down? | |
246 if (d_->pre_auth_->HadError()) { | |
247 d_->pre_engine_error_ = XmppEngine::ERROR_AUTH; | |
248 d_->pre_engine_subcode_ = d_->pre_auth_->GetError(); | |
249 } | |
250 else { | |
251 d_->pre_engine_error_ = XmppEngine::ERROR_UNAUTHORIZED; | |
252 d_->pre_engine_subcode_ = 0; | |
253 d_->captcha_challenge_ = d_->pre_auth_->GetCaptchaChallenge(); | |
254 } | |
255 d_->pre_auth_.reset(NULL); // done with this | |
256 EnsureClosed(); | |
257 return STATE_ERROR; | |
258 } | |
259 | |
260 // Save auth token as a result | |
261 | |
262 d_->auth_mechanism_ = d_->pre_auth_->GetAuthMechanism(); | |
263 d_->auth_token_ = d_->pre_auth_->GetAuthToken(); | |
264 | |
265 // transfer ownership of pre_auth_ to engine | |
266 d_->engine_->SetSaslHandler(d_->pre_auth_.release()); | |
267 return STATE_START_XMPP_LOGIN; | |
268 } | |
269 | |
270 int XmppClient::ProcessStartXmppLogin() { | |
271 // Should not happen, but was observed in crash reports | |
272 if (!d_->socket_) { | |
273 LOG(LS_ERROR) << "socket_ already reset"; | |
274 return STATE_DONE; | |
275 } | |
276 | |
277 // Done with pre-connect tasks - connect! | |
278 if (!d_->socket_->Connect(d_->server_)) { | |
279 EnsureClosed(); | |
280 return STATE_ERROR; | |
281 } | |
282 | |
283 return STATE_RESPONSE; | |
284 } | |
285 | |
286 int XmppClient::ProcessResponse() { | |
287 // Hang around while we are connected. | |
288 if (!delivering_signal_ && | |
289 (!d_->engine_ || d_->engine_->GetState() == XmppEngine::STATE_CLOSED)) | |
290 return STATE_DONE; | |
291 return STATE_BLOCKED; | |
292 } | |
293 | |
294 XmppReturnStatus XmppClient::Disconnect() { | |
295 if (!d_->socket_) | |
296 return XMPP_RETURN_BADSTATE; | |
297 Abort(); | |
298 d_->engine_->Disconnect(); | |
299 d_->ResetSocket(); | |
300 return XMPP_RETURN_OK; | |
301 } | |
302 | |
303 XmppClient::XmppClient(TaskParent* parent) | |
304 : XmppTaskParentInterface(parent), | |
305 delivering_signal_(false), | |
306 valid_(false) { | |
307 d_.reset(new Private(this)); | |
308 valid_ = true; | |
309 } | |
310 | |
311 XmppClient::~XmppClient() { | |
312 valid_ = false; | |
313 } | |
314 | |
315 const Jid& XmppClient::jid() const { | |
316 return d_->engine_->FullJid(); | |
317 } | |
318 | |
319 | |
320 std::string XmppClient::NextId() { | |
321 return d_->engine_->NextId(); | |
322 } | |
323 | |
324 XmppReturnStatus XmppClient::SendStanza(const XmlElement* stanza) { | |
325 return d_->engine_->SendStanza(stanza); | |
326 } | |
327 | |
328 XmppReturnStatus XmppClient::SendStanzaError( | |
329 const XmlElement* old_stanza, XmppStanzaError xse, | |
330 const std::string& message) { | |
331 return d_->engine_->SendStanzaError(old_stanza, xse, message); | |
332 } | |
333 | |
334 XmppReturnStatus XmppClient::SendRaw(const std::string& text) { | |
335 return d_->engine_->SendRaw(text); | |
336 } | |
337 | |
338 XmppEngine* XmppClient::engine() { | |
339 return d_->engine_.get(); | |
340 } | |
341 | |
342 void XmppClient::Private::OnSocketConnected() { | |
343 engine_->Connect(); | |
344 } | |
345 | |
346 void XmppClient::Private::OnSocketRead() { | |
347 char bytes[4096]; | |
348 size_t bytes_read; | |
349 for (;;) { | |
350 // Should not happen, but was observed in crash reports | |
351 if (!socket_) { | |
352 LOG(LS_ERROR) << "socket_ already reset"; | |
353 return; | |
354 } | |
355 | |
356 if (!socket_->Read(bytes, sizeof(bytes), &bytes_read)) { | |
357 // TODO: deal with error information | |
358 return; | |
359 } | |
360 | |
361 if (bytes_read == 0) | |
362 return; | |
363 | |
364 //#if !defined(NDEBUG) | |
365 client_->SignalLogInput(bytes, static_cast<int>(bytes_read)); | |
366 //#endif | |
367 | |
368 engine_->HandleInput(bytes, bytes_read); | |
369 } | |
370 } | |
371 | |
372 void XmppClient::Private::OnSocketClosed() { | |
373 int code = socket_->GetError(); | |
374 engine_->ConnectionClosed(code); | |
375 } | |
376 | |
377 void XmppClient::Private::OnStateChange(int state) { | |
378 if (state == XmppEngine::STATE_CLOSED) { | |
379 client_->EnsureClosed(); | |
380 } | |
381 else { | |
382 client_->SignalStateChange((XmppEngine::State)state); | |
383 } | |
384 client_->Wake(); | |
385 } | |
386 | |
387 void XmppClient::Private::WriteOutput(const char* bytes, size_t len) { | |
388 //#if !defined(NDEBUG) | |
389 client_->SignalLogOutput(bytes, static_cast<int>(len)); | |
390 //#endif | |
391 | |
392 socket_->Write(bytes, len); | |
393 // TODO: deal with error information | |
394 } | |
395 | |
396 void XmppClient::Private::StartTls(const std::string& domain) { | |
397 #if defined(FEATURE_ENABLE_SSL) | |
398 socket_->StartTls(domain); | |
399 #endif | |
400 } | |
401 | |
402 void XmppClient::Private::CloseConnection() { | |
403 socket_->Close(); | |
404 } | |
405 | |
406 void XmppClient::AddXmppTask(XmppTask* task, XmppEngine::HandlerLevel level) { | |
407 d_->engine_->AddStanzaHandler(task, level); | |
408 } | |
409 | |
410 void XmppClient::RemoveXmppTask(XmppTask* task) { | |
411 d_->engine_->RemoveStanzaHandler(task); | |
412 } | |
413 | |
414 void XmppClient::EnsureClosed() { | |
415 if (!d_->signal_closed_) { | |
416 d_->signal_closed_ = true; | |
417 delivering_signal_ = true; | |
418 SignalStateChange(XmppEngine::STATE_CLOSED); | |
419 delivering_signal_ = false; | |
420 } | |
421 } | |
422 | |
423 } // namespace buzz | |
OLD | NEW |