OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "chrome/browser/sync/notifier/communicator/xmpp_socket_adapter.h" |
| 6 |
| 7 #include <iomanip> |
| 8 #include <string> |
| 9 |
| 10 #include "chrome/browser/sync/notifier/communicator/product_info.h" |
| 11 #include "talk/base/byteorder.h" |
| 12 #include "talk/base/common.h" |
| 13 #include "talk/base/firewallsocketserver.h" |
| 14 #include "talk/base/logging.h" |
| 15 #include "talk/base/socketadapters.h" |
| 16 #include "talk/base/ssladapter.h" |
| 17 #include "talk/xmpp/xmppengine.h" |
| 18 |
| 19 namespace notifier { |
| 20 |
| 21 XmppSocketAdapter::XmppSocketAdapter(const buzz::XmppClientSettings& xcs, |
| 22 bool allow_unverified_certs) |
| 23 : state_(STATE_CLOSED), |
| 24 error_(ERROR_NONE), |
| 25 wsa_error_(0), |
| 26 socket_(NULL), |
| 27 protocol_(xcs.protocol()), |
| 28 firewall_(false), |
| 29 write_buffer_(NULL), |
| 30 write_buffer_length_(0), |
| 31 write_buffer_capacity_(0), |
| 32 allow_unverified_certs_(allow_unverified_certs) { |
| 33 proxy_.type = xcs.proxy(); |
| 34 proxy_.address.SetIP(xcs.proxy_host(), false); |
| 35 proxy_.address.SetPort(xcs.proxy_port()); |
| 36 proxy_.username = xcs.proxy_user(); |
| 37 proxy_.password = xcs.proxy_pass(); |
| 38 } |
| 39 |
| 40 XmppSocketAdapter::~XmppSocketAdapter() { |
| 41 FreeState(); |
| 42 |
| 43 // Clean up any previous socket - cannot delete socket on close because |
| 44 // close happens during the child socket's stack callback. |
| 45 if (socket_) { |
| 46 delete socket_; |
| 47 socket_ = NULL; |
| 48 } |
| 49 } |
| 50 |
| 51 bool XmppSocketAdapter::FreeState() { |
| 52 int code = 0; |
| 53 |
| 54 // Clean up the socket |
| 55 if (socket_ && !(state_ == STATE_CLOSED || state_ == STATE_CLOSING)) { |
| 56 code = socket_->Close(); |
| 57 } |
| 58 |
| 59 delete[] write_buffer_; |
| 60 write_buffer_ = NULL; |
| 61 write_buffer_length_ = 0; |
| 62 write_buffer_capacity_ = 0; |
| 63 |
| 64 if (code) { |
| 65 SetWSAError(code); |
| 66 return false; |
| 67 } |
| 68 return true; |
| 69 } |
| 70 |
| 71 bool XmppSocketAdapter::Connect(const talk_base::SocketAddress& addr) { |
| 72 if (state_ != STATE_CLOSED) { |
| 73 SetError(ERROR_WRONGSTATE); |
| 74 return false; |
| 75 } |
| 76 |
| 77 LOG(LS_INFO) << "XmppSocketAdapter::Connect(" << addr.ToString() << ")"; |
| 78 |
| 79 // Clean up any previous socket - cannot delete socket on close because |
| 80 // close happens during the child socket's stack callback. |
| 81 if (socket_) { |
| 82 delete socket_; |
| 83 socket_ = NULL; |
| 84 } |
| 85 |
| 86 talk_base::AsyncSocket* socket = |
| 87 talk_base::Thread::Current()->socketserver() |
| 88 ->CreateAsyncSocket(SOCK_STREAM); |
| 89 if (!socket) { |
| 90 SetWSAError(WSA_NOT_ENOUGH_MEMORY); |
| 91 return false; |
| 92 } |
| 93 |
| 94 if (firewall_) { |
| 95 // TODO(sync): Change this to make WSAAsyncSockets support current thread |
| 96 // socket server |
| 97 talk_base::FirewallSocketServer* fw = |
| 98 static_cast<talk_base::FirewallSocketServer*>( |
| 99 talk_base::Thread::Current()->socketserver()); |
| 100 socket = fw->WrapSocket(socket, SOCK_STREAM); |
| 101 } |
| 102 |
| 103 if (proxy_.type) { |
| 104 talk_base::AsyncSocket* proxy_socket = 0; |
| 105 if (proxy_.type == talk_base::PROXY_SOCKS5) { |
| 106 proxy_socket = new talk_base::AsyncSocksProxySocket( |
| 107 socket, proxy_.address, proxy_.username, proxy_.password); |
| 108 } else { |
| 109 // Note: we are trying unknown proxies as HTTPS currently |
| 110 proxy_socket = new talk_base::AsyncHttpsProxySocket(socket, |
| 111 GetUserAgentString(), proxy_.address, |
| 112 proxy_.username, proxy_.password); |
| 113 } |
| 114 if (!proxy_socket) { |
| 115 SetWSAError(WSA_NOT_ENOUGH_MEMORY); |
| 116 delete socket; |
| 117 return false; |
| 118 } |
| 119 socket = proxy_socket; // for our purposes the proxy is now the socket |
| 120 } |
| 121 |
| 122 // #if defined(PRODUCTION) |
| 123 if (protocol_ == cricket::PROTO_SSLTCP) { |
| 124 talk_base::AsyncSocket *fake_ssl_socket = |
| 125 new talk_base::AsyncSSLSocket(socket); |
| 126 if (!fake_ssl_socket) { |
| 127 SetWSAError(WSA_NOT_ENOUGH_MEMORY); |
| 128 delete socket; |
| 129 return false; |
| 130 } |
| 131 socket = fake_ssl_socket; // for our purposes the SSL socket is the socket |
| 132 } |
| 133 // #endif // PRODUCTION |
| 134 |
| 135 #if defined(FEATURE_ENABLE_SSL) |
| 136 talk_base::SSLAdapter* ssl = talk_base::SSLAdapter::Create(socket); |
| 137 socket = ssl; |
| 138 #endif |
| 139 |
| 140 // #if !defined(PRODUCTION) |
| 141 // if (protocol_ == cricket::PROTO_SSLTCP) { |
| 142 // ssl->set_ignore_bad_cert(true); |
| 143 // ssl->StartSSL(addr.hostname().c_str(), true); |
| 144 // } |
| 145 // #endif // PRODUCTION |
| 146 |
| 147 socket->SignalReadEvent.connect(this, &XmppSocketAdapter::OnReadEvent); |
| 148 socket->SignalWriteEvent.connect(this, &XmppSocketAdapter::OnWriteEvent); |
| 149 socket->SignalConnectEvent.connect(this, &XmppSocketAdapter::OnConnectEvent); |
| 150 socket->SignalCloseEvent.connect(this, &XmppSocketAdapter::OnCloseEvent); |
| 151 |
| 152 // The linux implementation of socket::Connect |
| 153 // returns an error when the connect didn't complete |
| 154 // yet. This can be distinguished from a failure |
| 155 // because socket::IsBlocking is true. Perhaps, |
| 156 // the linux implementation should be made to |
| 157 // behave like the windows version which doesn't do this, |
| 158 // but it seems to be a pattern with these methods |
| 159 // that they return an error if the operation didn't |
| 160 // complete in a sync fashion and one has to check IsBlocking |
| 161 // to tell if was a "real" error. |
| 162 if (socket->Connect(addr) == SOCKET_ERROR && !socket->IsBlocking()) { |
| 163 SetWSAError(socket->GetError()); |
| 164 delete socket; |
| 165 return false; |
| 166 } |
| 167 |
| 168 socket_ = socket; |
| 169 state_ = STATE_CONNECTING; |
| 170 return true; |
| 171 } |
| 172 |
| 173 bool XmppSocketAdapter::Read(char* data, size_t len, size_t* len_read) { |
| 174 if (len_read) |
| 175 *len_read = 0; |
| 176 |
| 177 if (state_ <= STATE_CLOSING) { |
| 178 SetError(ERROR_WRONGSTATE); |
| 179 return false; |
| 180 } |
| 181 |
| 182 ASSERT(socket_ != NULL); |
| 183 |
| 184 if (IsOpen()) { |
| 185 int result = socket_->Recv(data, len); |
| 186 if (result < 0) { |
| 187 if (!socket_->IsBlocking()) { |
| 188 SetWSAError(socket_->GetError()); |
| 189 return false; |
| 190 } |
| 191 |
| 192 result = 0; |
| 193 } |
| 194 |
| 195 if (len_read) |
| 196 *len_read = result; |
| 197 } |
| 198 |
| 199 return true; |
| 200 } |
| 201 |
| 202 bool XmppSocketAdapter::Write(const char* data, size_t len) { |
| 203 if (state_ <= STATE_CLOSING) { |
| 204 // There may be data in a buffer that gets lost. Too bad! |
| 205 SetError(ERROR_WRONGSTATE); |
| 206 return false; |
| 207 } |
| 208 |
| 209 ASSERT(socket_ != NULL); |
| 210 |
| 211 size_t sent = 0; |
| 212 |
| 213 // try an immediate write when there is no buffer |
| 214 // and we aren't in SSL mode or opening the connection |
| 215 if (write_buffer_length_ == 0 && IsOpen()) { |
| 216 int result = socket_->Send(data, len); |
| 217 if (result < 0) { |
| 218 if (!socket_->IsBlocking()) { |
| 219 SetWSAError(socket_->GetError()); |
| 220 return false; |
| 221 } |
| 222 result = 0; |
| 223 } |
| 224 |
| 225 sent = static_cast<size_t>(result); |
| 226 } |
| 227 |
| 228 // Buffer what we didn't send |
| 229 if (sent < len) { |
| 230 QueueWriteData(data + sent, len - sent); |
| 231 } |
| 232 |
| 233 // Service the socket right away to push the written data out in SSL mode |
| 234 return HandleWritable(); |
| 235 } |
| 236 |
| 237 bool XmppSocketAdapter::Close() { |
| 238 if (state_ == STATE_CLOSING) { |
| 239 return false; // avoid recursion, but not unexpected |
| 240 } |
| 241 if (state_ == STATE_CLOSED) { |
| 242 // in theory should not be trying to re-InternalClose. |
| 243 SetError(ERROR_WRONGSTATE); |
| 244 return false; |
| 245 } |
| 246 |
| 247 // todo: deal with flushing close |
| 248 // (flush, don't do reads, clean ssl) |
| 249 |
| 250 // If we've gotten to the point where we really do have a socket underneath |
| 251 // then close it. It should call us back to tell us it is closed, and |
| 252 // NotifyClose will be called. We indicate "closing" state so that we |
| 253 // do not recusively try to keep closing the socket. |
| 254 if (socket_) { |
| 255 state_ = STATE_CLOSING; |
| 256 socket_->Close(); |
| 257 } |
| 258 |
| 259 // If we didn't get the callback, then we better make sure we signal |
| 260 // closed. |
| 261 if (state_ != STATE_CLOSED) { |
| 262 // The socket was closed manually, not directly due to error. |
| 263 if (error_ != ERROR_NONE) { |
| 264 LOG(LS_INFO) << "XmppSocketAdapter::Close - previous Error: " << error_ |
| 265 << " WSAError: " << wsa_error_; |
| 266 error_ = ERROR_NONE; |
| 267 wsa_error_ = 0; |
| 268 } |
| 269 NotifyClose(); |
| 270 } |
| 271 return true; |
| 272 } |
| 273 |
| 274 void XmppSocketAdapter::NotifyClose() { |
| 275 if (state_ == STATE_CLOSED) { |
| 276 SetError(ERROR_WRONGSTATE); |
| 277 } else { |
| 278 LOG(LS_INFO) << "XmppSocketAdapter::NotifyClose - Error: " << error_ |
| 279 << " WSAError: " << wsa_error_; |
| 280 state_ = STATE_CLOSED; |
| 281 SignalClosed(); |
| 282 FreeState(); |
| 283 } |
| 284 } |
| 285 |
| 286 void XmppSocketAdapter::OnConnectEvent(talk_base::AsyncSocket *socket) { |
| 287 if (state_ == STATE_CONNECTING) { |
| 288 state_ = STATE_OPEN; |
| 289 LOG(LS_INFO) << "XmppSocketAdapter::OnConnectEvent - STATE_OPEN"; |
| 290 SignalConnected(); |
| 291 #if defined(FEATURE_ENABLE_SSL) |
| 292 } else if (state_ == STATE_TLS_CONNECTING) { |
| 293 state_ = STATE_TLS_OPEN; |
| 294 LOG(LS_INFO) << "XmppSocketAdapter::OnConnectEvent - STATE_TLS_OPEN"; |
| 295 SignalSSLConnected(); |
| 296 if (write_buffer_length_ > 0) { |
| 297 HandleWritable(); |
| 298 } |
| 299 #endif // defined(FEATURE_ENABLE_SSL) |
| 300 } else { |
| 301 LOG(LS_INFO) << "XmppSocketAdapter::OnConnectEvent - state is " << state_; |
| 302 ASSERT(false); |
| 303 } |
| 304 } |
| 305 |
| 306 void XmppSocketAdapter::OnReadEvent(talk_base::AsyncSocket *socket) { |
| 307 HandleReadable(); |
| 308 } |
| 309 |
| 310 void XmppSocketAdapter::OnWriteEvent(talk_base::AsyncSocket *socket) { |
| 311 HandleWritable(); |
| 312 } |
| 313 |
| 314 void XmppSocketAdapter::OnCloseEvent(talk_base::AsyncSocket *socket, |
| 315 int error) { |
| 316 LOG(LS_INFO) << "XmppSocketAdapter::OnCloseEvent(" << error << ")"; |
| 317 SetWSAError(error); |
| 318 if (error == SOCKET_EACCES) { |
| 319 SignalAuthenticationError(); // proxy needs authentication |
| 320 } |
| 321 NotifyClose(); |
| 322 } |
| 323 |
| 324 #if defined(FEATURE_ENABLE_SSL) |
| 325 bool XmppSocketAdapter::StartTls(const std::string& verify_host_name) { |
| 326 if (state_ != STATE_OPEN) { |
| 327 SetError(ERROR_WRONGSTATE); |
| 328 return false; |
| 329 } |
| 330 |
| 331 state_ = STATE_TLS_CONNECTING; |
| 332 |
| 333 ASSERT(write_buffer_length_ == 0); |
| 334 |
| 335 talk_base::SSLAdapter* ssl_adapter = |
| 336 static_cast<talk_base::SSLAdapter*>(socket_); |
| 337 |
| 338 if (allow_unverified_certs_) { |
| 339 ssl_adapter->set_ignore_bad_cert(true); |
| 340 } |
| 341 |
| 342 if (ssl_adapter->StartSSL(verify_host_name.c_str(), false) != 0) { |
| 343 state_ = STATE_OPEN; |
| 344 SetError(ERROR_SSL); |
| 345 return false; |
| 346 } |
| 347 |
| 348 return true; |
| 349 } |
| 350 #endif // defined(FEATURE_ENABLE_SSL) |
| 351 |
| 352 void XmppSocketAdapter::QueueWriteData(const char* data, size_t len) { |
| 353 // expand buffer if needed |
| 354 if (write_buffer_length_ + len > write_buffer_capacity_) { |
| 355 size_t new_capacity = 1024; |
| 356 while (new_capacity < write_buffer_length_ + len) { |
| 357 new_capacity = new_capacity * 2; |
| 358 } |
| 359 char* new_buffer = new char[new_capacity]; |
| 360 ASSERT(write_buffer_length_ <= 64000); |
| 361 memcpy(new_buffer, write_buffer_, write_buffer_length_); |
| 362 delete[] write_buffer_; |
| 363 write_buffer_ = new_buffer; |
| 364 write_buffer_capacity_ = new_capacity; |
| 365 } |
| 366 |
| 367 // copy data into the end of buffer |
| 368 memcpy(write_buffer_ + write_buffer_length_, data, len); |
| 369 write_buffer_length_ += len; |
| 370 } |
| 371 |
| 372 void XmppSocketAdapter::FlushWriteQueue(Error* error, int* wsa_error) { |
| 373 ASSERT(error && wsa_error); |
| 374 |
| 375 size_t flushed = 0; |
| 376 while (flushed < write_buffer_length_) { |
| 377 int sent = socket_->Send(write_buffer_ + flushed, |
| 378 static_cast<int>(write_buffer_length_ - flushed)); |
| 379 if (sent < 0) { |
| 380 if (!socket_->IsBlocking()) { |
| 381 *error = ERROR_WINSOCK; |
| 382 *wsa_error = socket_->GetError(); |
| 383 } |
| 384 break; |
| 385 } |
| 386 flushed += static_cast<size_t>(sent); |
| 387 } |
| 388 |
| 389 // remove flushed memory |
| 390 write_buffer_length_ -= flushed; |
| 391 memmove(write_buffer_, write_buffer_ + flushed, write_buffer_length_); |
| 392 |
| 393 // when everything is flushed, deallocate the buffer if it's gotten big |
| 394 if (write_buffer_length_ == 0) { |
| 395 if (write_buffer_capacity_ > 8192) { |
| 396 delete[] write_buffer_; |
| 397 write_buffer_ = NULL; |
| 398 write_buffer_capacity_ = 0; |
| 399 } |
| 400 } |
| 401 } |
| 402 |
| 403 void XmppSocketAdapter::SetError(Error error) { |
| 404 if (error_ == ERROR_NONE) { |
| 405 error_ = error; |
| 406 } |
| 407 } |
| 408 |
| 409 void XmppSocketAdapter::SetWSAError(int error) { |
| 410 if (error_ == ERROR_NONE && error != 0) { |
| 411 error_ = ERROR_WINSOCK; |
| 412 wsa_error_ = error; |
| 413 } |
| 414 } |
| 415 |
| 416 bool XmppSocketAdapter::HandleReadable() { |
| 417 if (!IsOpen()) |
| 418 return false; |
| 419 |
| 420 SignalRead(); |
| 421 return true; |
| 422 } |
| 423 |
| 424 bool XmppSocketAdapter::HandleWritable() { |
| 425 if (!IsOpen()) |
| 426 return false; |
| 427 |
| 428 Error error = ERROR_NONE; |
| 429 int wsa_error = 0; |
| 430 FlushWriteQueue(&error, &wsa_error); |
| 431 if (error != ERROR_NONE) { |
| 432 Close(); |
| 433 return false; |
| 434 } |
| 435 return true; |
| 436 } |
| 437 } // namespace notifier |
OLD | NEW |