[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 1 | // Copyright (c) 2008 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 "net/base/ssl_client_socket_mac.h" |
| 6 | |
| 7 | #include "base/singleton.h" |
| 8 | #include "base/string_util.h" |
| 9 | #include "net/base/net_errors.h" |
| 10 | #include "net/base/ssl_info.h" |
| 11 | |
| 12 | // Welcome to Mac SSL. We've been waiting for you. |
| 13 | // |
| 14 | // The Mac SSL implementation is, like the Windows and NSS implementations, a |
| 15 | // giant state machine. This design constraint is due to the asynchronous nature |
| 16 | // of our underlying transport mechanism. We can call down to read/write on the |
| 17 | // network, but what happens is that either it completes immediately or returns |
| 18 | // saying that we'll get a callback sometime in the future. In that case, we |
| 19 | // have to return to our caller but pick up where we left off when we |
| 20 | // resume. Thus the fun. |
| 21 | // |
| 22 | // On Windows, we use Security Contexts, which are driven by us. We fetch data |
| 23 | // from the network, we call the context to decrypt the data, and so on. On the |
| 24 | // Mac, however, we provide Secure Transport with callbacks to get data from the |
| 25 | // network, and it calls us back to fetch the data from the network for |
| 26 | // it. Therefore, there are different sets of states in our respective state |
| 27 | // machines, fewer on the Mac because Secure Transport keeps a lot of its own |
| 28 | // state. The discussion about what each of the states means lives in comments |
| 29 | // in the DoLoop() function. |
| 30 | // |
| 31 | // Secure Transport is designed for use by either blocking or non-blocking |
| 32 | // network I/O. If, for example, you called SSLRead() to fetch data, Secure |
| 33 | // Transport will, unless it has some cached data, issue a read to your network |
| 34 | // callback read function to fetch it some more encrypted data. It's expecting |
| 35 | // one of two things. If your function is hooked up to a blocking source, then |
| 36 | // it'll block pending receipt of the data from the other end. That's fine, as |
| 37 | // when you return with the data, Secure Transport will do its thing. On the |
| 38 | // other hand, suppose that your socket is non-blocking and tells your function |
| 39 | // that it would block. Then you let Secure Transport know, and it'll tell the |
| 40 | // original caller that it would have blocked and that they need to call it |
| 41 | // "later." |
| 42 | // |
| 43 | // When's "later," though? We have fully-asynchronous networking, so we get a |
| 44 | // callback when our data's ready. But Secure Transport has no way for us to |
| 45 | // tell it that data has arrived, so we must re-execute the call that triggered |
| 46 | // the I/O (we rely on our state machine to do this). When we do so Secure |
| 47 | // Transport will ask once again for the data. Chances are that it'll be the |
| 48 | // same request as the previous time, but that's not actually guaranteed. But as |
| 49 | // long as we buffer what we have and keep track of where we were, it works |
| 50 | // quite well. |
| 51 | // |
| 52 | // Except for network writes. They shoot this plan straight to hell. |
| 53 | // |
| 54 | // Faking a blocking connection with an asynchronous connection (theoretically |
| 55 | // more powerful) simply doesn't work for writing. Suppose that Secure Transport |
| 56 | // requests a write of data to the network. With blocking I/O, we'd just block |
| 57 | // until the write completed, and with non-blocking I/O we'd know how many bytes |
| 58 | // we wrote before we would have blocked. But with the asynchronous I/O, the |
| 59 | // transport underneath us can tell us that it'll let us know sometime "later" |
| 60 | // whether or not things succeeded, and how many bytes were written. What do we |
| 61 | // return to Secure Transport? We can't return a byte count, but we can't return |
| 62 | // "later" as we're not guaranteed to be called in the future with the same data |
| 63 | // to write. |
| 64 | // |
| 65 | // So, like in any good relationship, we're forced to lie. Whenever Secure |
| 66 | // Transport asks for data to be written, we take it all and lie about it always |
| 67 | // being written. We spin in a loop (see SSLWriteCallback() and |
| 68 | // OnWriteComplete()) independent of the main state machine writing the data to |
| 69 | // the network, and get the data out. The main consequence of this independence |
| 70 | // from the state machine is that we require a full-duplex transport underneath |
| 71 | // us since we can't use it to keep our reading and writing |
| 72 | // straight. Fortunately, the NSS implementation also has this issue to deal |
| 73 | // with, so we share the same Libevent-based full-duplex TCP socket. |
| 74 | // |
| 75 | // A side comment on return values might be in order. Those who haven't taken |
| 76 | // the time to read the documentation (ahem, header comments) in our various |
| 77 | // files might be a bit surprised to see result values being treated as both |
| 78 | // lengths and errors. Like Shimmer, they are both. In both the case of |
| 79 | // immediate results as well as results returned in callbacks, a negative return |
| 80 | // value indicates an error, a zero return value indicates end-of-stream (for |
| 81 | // reads), and a positive return value indicates the number of bytes read or |
| 82 | // written. Thus, many functions start off with |if (result < 0) return |
| 83 | // result;|. That gets the error condition out of the way, and from that point |
| 84 | // forward the result can be treated as a length. |
| 85 | |
| 86 | namespace net { |
| 87 | |
| 88 | namespace { |
| 89 | |
| 90 | int NetErrorFromOSStatus(OSStatus status) { |
| 91 | switch (status) { |
| 92 | case errSSLWouldBlock: |
| 93 | return ERR_IO_PENDING; |
| 94 | case errSSLIllegalParam: |
| 95 | case errSSLBadCipherSuite: |
| 96 | case errSSLBadConfiguration: |
| 97 | return ERR_INVALID_ARGUMENT; |
| 98 | case errSSLClosedNoNotify: |
| 99 | return ERR_CONNECTION_RESET; |
| 100 | case errSSLConnectionRefused: |
| 101 | return ERR_CONNECTION_REFUSED; |
| 102 | case errSSLClosedAbort: |
| 103 | return ERR_CONNECTION_ABORTED; |
| 104 | case errSSLInternal: |
| 105 | case errSSLCrypto: |
| 106 | case errSSLFatalAlert: |
| 107 | case errSSLProtocol: |
| 108 | return ERR_SSL_PROTOCOL_ERROR; |
| 109 | case errSSLHostNameMismatch: |
| 110 | return ERR_CERT_COMMON_NAME_INVALID; |
| 111 | case errSSLCertExpired: |
| 112 | case errSSLCertNotYetValid: |
| 113 | return ERR_CERT_DATE_INVALID; |
| 114 | case errSSLNoRootCert: |
| 115 | case errSSLUnknownRootCert: |
| 116 | return ERR_CERT_AUTHORITY_INVALID; |
| 117 | case errSSLXCertChainInvalid: |
| 118 | case errSSLBadCert: |
| 119 | return ERR_CERT_INVALID; |
| 120 | case errSSLPeerCertRevoked: |
| 121 | return ERR_CERT_REVOKED; |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 122 | |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 123 | case errSSLClosedGraceful: |
| 124 | case noErr: |
| 125 | return OK; |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 126 | |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 127 | case errSSLBadRecordMac: |
| 128 | case errSSLBufferOverflow: |
| 129 | case errSSLDecryptionFail: |
| 130 | case errSSLModuleAttach: |
| 131 | case errSSLNegotiation: |
| 132 | case errSSLRecordOverflow: |
| 133 | case errSSLSessionNotFound: |
| 134 | default: |
| 135 | LOG(WARNING) << "Unknown error " << status << |
| 136 | " mapped to net::ERR_FAILED"; |
| 137 | return ERR_FAILED; |
| 138 | } |
| 139 | } |
| 140 | |
| 141 | OSStatus OSStatusFromNetError(int net_error) { |
| 142 | switch (net_error) { |
| 143 | case ERR_IO_PENDING: |
| 144 | return errSSLWouldBlock; |
| 145 | case ERR_INTERNET_DISCONNECTED: |
| 146 | case ERR_TIMED_OUT: |
| 147 | case ERR_CONNECTION_ABORTED: |
| 148 | case ERR_CONNECTION_RESET: |
| 149 | case ERR_CONNECTION_REFUSED: |
| 150 | case ERR_ADDRESS_UNREACHABLE: |
| 151 | case ERR_ADDRESS_INVALID: |
| 152 | return errSSLClosedAbort; |
| 153 | case OK: |
| 154 | return noErr; |
| 155 | default: |
| 156 | LOG(WARNING) << "Unknown error " << net_error << |
| 157 | " mapped to errSSLIllegalParam"; |
| 158 | return errSSLIllegalParam; |
| 159 | } |
| 160 | } |
| 161 | |
[email protected] | e159339 | 2008-10-20 19:46:33 | [diff] [blame] | 162 | // Converts from a cipher suite to its key size. If the suite is marked with a |
| 163 | // **, it's not actually implemented in Secure Transport and won't be returned |
| 164 | // (but we'll code for it anyway). The reference here is |
| 165 | // http://www.opensource.apple.com/darwinsource/10.5.5/libsecurity_ssl-32463/lib/cipherSpecs.c |
| 166 | // Seriously, though, there has to be an API for this, but I can't find one. |
| 167 | // Anybody? |
| 168 | int KeySizeOfCipherSuite(SSLCipherSuite suite) { |
| 169 | switch (suite) { |
| 170 | // SSL 2 only |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 171 | |
[email protected] | e159339 | 2008-10-20 19:46:33 | [diff] [blame] | 172 | case SSL_RSA_WITH_DES_CBC_MD5: |
| 173 | return 56; |
| 174 | case SSL_RSA_WITH_3DES_EDE_CBC_MD5: |
| 175 | return 112; |
| 176 | case SSL_RSA_WITH_RC2_CBC_MD5: |
| 177 | case SSL_RSA_WITH_IDEA_CBC_MD5: // ** |
| 178 | return 128; |
| 179 | case SSL_NO_SUCH_CIPHERSUITE: // ** |
| 180 | return 0; |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 181 | |
[email protected] | e159339 | 2008-10-20 19:46:33 | [diff] [blame] | 182 | // SSL 2, 3, TLS |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 183 | |
[email protected] | e159339 | 2008-10-20 19:46:33 | [diff] [blame] | 184 | case SSL_NULL_WITH_NULL_NULL: |
| 185 | case SSL_RSA_WITH_NULL_MD5: |
| 186 | case SSL_RSA_WITH_NULL_SHA: // ** |
| 187 | case SSL_FORTEZZA_DMS_WITH_NULL_SHA: // ** |
| 188 | return 0; |
| 189 | case SSL_RSA_EXPORT_WITH_RC4_40_MD5: |
| 190 | case SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5: |
| 191 | case SSL_RSA_EXPORT_WITH_DES40_CBC_SHA: |
| 192 | case SSL_DH_DSS_EXPORT_WITH_DES40_CBC_SHA: // ** |
| 193 | case SSL_DH_RSA_EXPORT_WITH_DES40_CBC_SHA: // ** |
| 194 | case SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA: |
| 195 | case SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA: |
| 196 | case SSL_DH_anon_EXPORT_WITH_RC4_40_MD5: |
| 197 | case SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA: |
| 198 | return 40; |
| 199 | case SSL_RSA_WITH_DES_CBC_SHA: |
| 200 | case SSL_DH_DSS_WITH_DES_CBC_SHA: // ** |
| 201 | case SSL_DH_RSA_WITH_DES_CBC_SHA: // ** |
| 202 | case SSL_DHE_DSS_WITH_DES_CBC_SHA: |
| 203 | case SSL_DHE_RSA_WITH_DES_CBC_SHA: |
| 204 | case SSL_DH_anon_WITH_DES_CBC_SHA: |
| 205 | return 56; |
| 206 | case SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA: // ** |
| 207 | return 80; |
| 208 | case SSL_RSA_WITH_3DES_EDE_CBC_SHA: |
| 209 | case SSL_DH_DSS_WITH_3DES_EDE_CBC_SHA: // ** |
| 210 | case SSL_DH_RSA_WITH_3DES_EDE_CBC_SHA: // ** |
| 211 | case SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA: |
| 212 | case SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA: |
| 213 | case SSL_DH_anon_WITH_3DES_EDE_CBC_SHA: |
| 214 | return 112; |
| 215 | case SSL_RSA_WITH_RC4_128_MD5: |
| 216 | case SSL_RSA_WITH_RC4_128_SHA: |
| 217 | case SSL_RSA_WITH_IDEA_CBC_SHA: // ** |
| 218 | case SSL_DH_anon_WITH_RC4_128_MD5: |
| 219 | return 128; |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 220 | |
[email protected] | e159339 | 2008-10-20 19:46:33 | [diff] [blame] | 221 | // TLS AES options (see RFC 3268) |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 222 | |
[email protected] | e159339 | 2008-10-20 19:46:33 | [diff] [blame] | 223 | case TLS_RSA_WITH_AES_128_CBC_SHA: |
| 224 | case TLS_DH_DSS_WITH_AES_128_CBC_SHA: // ** |
| 225 | case TLS_DH_RSA_WITH_AES_128_CBC_SHA: // ** |
| 226 | case TLS_DHE_DSS_WITH_AES_128_CBC_SHA: |
| 227 | case TLS_DHE_RSA_WITH_AES_128_CBC_SHA: |
| 228 | case TLS_DH_anon_WITH_AES_128_CBC_SHA: |
| 229 | return 128; |
| 230 | case TLS_RSA_WITH_AES_256_CBC_SHA: |
| 231 | case TLS_DH_DSS_WITH_AES_256_CBC_SHA: // ** |
| 232 | case TLS_DH_RSA_WITH_AES_256_CBC_SHA: // ** |
| 233 | case TLS_DHE_DSS_WITH_AES_256_CBC_SHA: |
| 234 | case TLS_DHE_RSA_WITH_AES_256_CBC_SHA: |
| 235 | case TLS_DH_anon_WITH_AES_256_CBC_SHA: |
| 236 | return 256; |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 237 | |
[email protected] | e159339 | 2008-10-20 19:46:33 | [diff] [blame] | 238 | default: |
| 239 | return -1; |
| 240 | } |
| 241 | } |
| 242 | |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 243 | } // namespace |
| 244 | |
| 245 | //----------------------------------------------------------------------------- |
| 246 | |
| 247 | SSLClientSocketMac::SSLClientSocketMac(ClientSocket* transport_socket, |
| 248 | const std::string& hostname, |
| 249 | const SSLConfig& ssl_config) |
| 250 | : io_callback_(this, &SSLClientSocketMac::OnIOComplete), |
| 251 | write_callback_(this, &SSLClientSocketMac::OnWriteComplete), |
| 252 | transport_(transport_socket), |
| 253 | hostname_(hostname), |
| 254 | ssl_config_(ssl_config), |
| 255 | user_callback_(NULL), |
| 256 | next_state_(STATE_NONE), |
| 257 | next_io_state_(STATE_NONE), |
[email protected] | e159339 | 2008-10-20 19:46:33 | [diff] [blame] | 258 | server_cert_status_(0), |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 259 | completed_handshake_(false), |
| 260 | ssl_context_(NULL), |
| 261 | pending_send_error_(OK), |
| 262 | recv_buffer_head_slop_(0), |
| 263 | recv_buffer_tail_slop_(0) { |
| 264 | } |
| 265 | |
| 266 | SSLClientSocketMac::~SSLClientSocketMac() { |
| 267 | Disconnect(); |
| 268 | } |
| 269 | |
| 270 | int SSLClientSocketMac::Connect(CompletionCallback* callback) { |
| 271 | DCHECK(transport_.get()); |
| 272 | DCHECK(next_state_ == STATE_NONE); |
| 273 | DCHECK(!user_callback_); |
| 274 | |
| 275 | next_state_ = STATE_CONNECT; |
| 276 | int rv = DoLoop(OK); |
| 277 | if (rv == ERR_IO_PENDING) |
| 278 | user_callback_ = callback; |
| 279 | return rv; |
| 280 | } |
| 281 | |
| 282 | int SSLClientSocketMac::ReconnectIgnoringLastError( |
| 283 | CompletionCallback* callback) { |
| 284 | // TODO(darin): implement me! |
| 285 | return ERR_FAILED; |
| 286 | } |
| 287 | |
| 288 | void SSLClientSocketMac::Disconnect() { |
| 289 | completed_handshake_ = false; |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 290 | |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 291 | if (ssl_context_) { |
| 292 | SSLClose(ssl_context_); |
| 293 | SSLDisposeContext(ssl_context_); |
| 294 | ssl_context_ = NULL; |
| 295 | } |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 296 | |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 297 | transport_->Disconnect(); |
| 298 | } |
| 299 | |
| 300 | bool SSLClientSocketMac::IsConnected() const { |
| 301 | // Ideally, we should also check if we have received the close_notify alert |
| 302 | // message from the server, and return false in that case. We're not doing |
| 303 | // that, so this function may return a false positive. Since the upper |
| 304 | // layer (HttpNetworkTransaction) needs to handle a persistent connection |
| 305 | // closed by the server when we send a request anyway, a false positive in |
| 306 | // exchange for simpler code is a good trade-off. |
| 307 | return completed_handshake_ && transport_->IsConnected(); |
| 308 | } |
| 309 | |
[email protected] | b219785 | 2009-02-19 23:27:33 | [diff] [blame] | 310 | bool SSLClientSocketMac::IsConnectedAndIdle() const { |
| 311 | // Unlike IsConnected, this method doesn't return a false positive. |
| 312 | // |
| 313 | // Strictly speaking, we should check if we have received the close_notify |
| 314 | // alert message from the server, and return false in that case. Although |
| 315 | // the close_notify alert message means EOF in the SSL layer, it is just |
| 316 | // bytes to the transport layer below, so transport_->IsConnectedAndIdle() |
| 317 | // returns the desired false when we receive close_notify. |
| 318 | return completed_handshake_ && transport_->IsConnectedAndIdle(); |
| 319 | } |
| 320 | |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 321 | int SSLClientSocketMac::Read(char* buf, int buf_len, |
| 322 | CompletionCallback* callback) { |
| 323 | DCHECK(completed_handshake_); |
| 324 | DCHECK(next_state_ == STATE_NONE); |
| 325 | DCHECK(!user_callback_); |
| 326 | |
| 327 | user_buf_ = buf; |
| 328 | user_buf_len_ = buf_len; |
| 329 | |
| 330 | next_state_ = STATE_PAYLOAD_READ; |
| 331 | int rv = DoLoop(OK); |
| 332 | if (rv == ERR_IO_PENDING) |
| 333 | user_callback_ = callback; |
| 334 | return rv; |
| 335 | } |
| 336 | |
| 337 | int SSLClientSocketMac::Write(const char* buf, int buf_len, |
| 338 | CompletionCallback* callback) { |
| 339 | DCHECK(completed_handshake_); |
| 340 | DCHECK(next_state_ == STATE_NONE); |
| 341 | DCHECK(!user_callback_); |
| 342 | |
| 343 | user_buf_ = const_cast<char*>(buf); |
| 344 | user_buf_len_ = buf_len; |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 345 | |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 346 | next_state_ = STATE_PAYLOAD_WRITE; |
| 347 | int rv = DoLoop(OK); |
| 348 | if (rv == ERR_IO_PENDING) |
| 349 | user_callback_ = callback; |
| 350 | return rv; |
| 351 | } |
| 352 | |
| 353 | void SSLClientSocketMac::GetSSLInfo(SSLInfo* ssl_info) { |
[email protected] | e159339 | 2008-10-20 19:46:33 | [diff] [blame] | 354 | DCHECK(completed_handshake_); |
| 355 | OSStatus status; |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 356 | |
[email protected] | e159339 | 2008-10-20 19:46:33 | [diff] [blame] | 357 | ssl_info->Reset(); |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 358 | |
[email protected] | e159339 | 2008-10-20 19:46:33 | [diff] [blame] | 359 | // set cert |
| 360 | CFArrayRef certs; |
| 361 | status = SSLCopyPeerCertificates(ssl_context_, &certs); |
| 362 | if (!status) { |
| 363 | DCHECK(CFArrayGetCount(certs) > 0); |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 364 | |
[email protected] | e159339 | 2008-10-20 19:46:33 | [diff] [blame] | 365 | SecCertificateRef client_cert = |
| 366 | static_cast<SecCertificateRef>( |
| 367 | const_cast<void*>(CFArrayGetValueAtIndex(certs, 0))); |
| 368 | CFRetain(client_cert); |
[email protected] | 9283116 | 2009-01-29 08:32:11 | [diff] [blame] | 369 | ssl_info->cert = X509Certificate::CreateFromHandle( |
| 370 | client_cert, X509Certificate::SOURCE_FROM_NETWORK); |
[email protected] | e159339 | 2008-10-20 19:46:33 | [diff] [blame] | 371 | CFRelease(certs); |
| 372 | } |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 373 | |
[email protected] | e159339 | 2008-10-20 19:46:33 | [diff] [blame] | 374 | // update status |
| 375 | ssl_info->cert_status = server_cert_status_; |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 376 | |
[email protected] | e159339 | 2008-10-20 19:46:33 | [diff] [blame] | 377 | // security info |
| 378 | SSLCipherSuite suite; |
| 379 | status = SSLGetNegotiatedCipher(ssl_context_, &suite); |
| 380 | if (!status) |
| 381 | ssl_info->security_bits = KeySizeOfCipherSuite(suite); |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 382 | } |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 383 | |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 384 | void SSLClientSocketMac::DoCallback(int rv) { |
| 385 | DCHECK(rv != ERR_IO_PENDING); |
| 386 | DCHECK(user_callback_); |
| 387 | |
| 388 | // since Run may result in Read being called, clear user_callback_ up front. |
| 389 | CompletionCallback* c = user_callback_; |
| 390 | user_callback_ = NULL; |
| 391 | c->Run(rv); |
| 392 | } |
| 393 | |
| 394 | void SSLClientSocketMac::OnIOComplete(int result) { |
| 395 | if (next_io_state_ != STATE_NONE) { |
| 396 | State next_state = next_state_; |
| 397 | next_state_ = next_io_state_; |
| 398 | next_io_state_ = STATE_NONE; |
| 399 | result = DoLoop(result); |
| 400 | next_state_ = next_state; |
| 401 | } |
| 402 | if (next_state_ != STATE_NONE) { |
| 403 | int rv = DoLoop(result); |
| 404 | if (rv != ERR_IO_PENDING) |
| 405 | DoCallback(rv); |
| 406 | } |
| 407 | } |
| 408 | |
| 409 | // This is the main loop driving the state machine. Most calls coming from the |
| 410 | // outside just set up a few variables and jump into here. |
| 411 | int SSLClientSocketMac::DoLoop(int last_io_result) { |
| 412 | DCHECK(next_state_ != STATE_NONE); |
| 413 | int rv = last_io_result; |
| 414 | do { |
| 415 | State state = next_state_; |
| 416 | next_state_ = STATE_NONE; |
| 417 | switch (state) { |
| 418 | case STATE_CONNECT: |
| 419 | // We must establish a connection to the other side using our |
| 420 | // lower-level transport. |
| 421 | rv = DoConnect(); |
| 422 | break; |
| 423 | case STATE_CONNECT_COMPLETE: |
| 424 | // We have a connection to the other side; initialize our SSL engine. |
| 425 | rv = DoConnectComplete(rv); |
| 426 | break; |
| 427 | case STATE_HANDSHAKE: |
| 428 | // Do the SSL/TLS handshake. |
| 429 | rv = DoHandshake(); |
| 430 | break; |
| 431 | case STATE_READ_COMPLETE: |
| 432 | // A read off the network is complete; do the paperwork. |
| 433 | rv = DoReadComplete(rv); |
| 434 | break; |
| 435 | case STATE_PAYLOAD_READ: |
| 436 | // Do a read of data from the network. |
| 437 | rv = DoPayloadRead(); |
| 438 | break; |
| 439 | case STATE_PAYLOAD_WRITE: |
| 440 | // Do a write of data to the network. |
| 441 | rv = DoPayloadWrite(); |
| 442 | break; |
| 443 | default: |
| 444 | rv = ERR_UNEXPECTED; |
| 445 | NOTREACHED() << "unexpected state"; |
| 446 | break; |
| 447 | } |
| 448 | } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE); |
| 449 | return rv; |
| 450 | } |
| 451 | |
| 452 | int SSLClientSocketMac::DoConnect() { |
| 453 | next_state_ = STATE_CONNECT_COMPLETE; |
| 454 | return transport_->Connect(&io_callback_); |
| 455 | } |
| 456 | |
| 457 | int SSLClientSocketMac::DoConnectComplete(int result) { |
| 458 | if (result < 0) |
| 459 | return result; |
| 460 | |
| 461 | OSStatus status = noErr; |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 462 | |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 463 | status = SSLNewContext(false, &ssl_context_); |
| 464 | if (status) |
| 465 | return NetErrorFromOSStatus(status); |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 466 | |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 467 | status = SSLSetProtocolVersionEnabled(ssl_context_, |
| 468 | kSSLProtocol2, |
| 469 | ssl_config_.ssl2_enabled); |
| 470 | if (status) |
| 471 | return NetErrorFromOSStatus(status); |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 472 | |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 473 | status = SSLSetProtocolVersionEnabled(ssl_context_, |
| 474 | kSSLProtocol3, |
| 475 | ssl_config_.ssl3_enabled); |
| 476 | if (status) |
| 477 | return NetErrorFromOSStatus(status); |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 478 | |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 479 | status = SSLSetProtocolVersionEnabled(ssl_context_, |
| 480 | kTLSProtocol1, |
| 481 | ssl_config_.tls1_enabled); |
| 482 | if (status) |
| 483 | return NetErrorFromOSStatus(status); |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 484 | |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 485 | status = SSLSetIOFuncs(ssl_context_, SSLReadCallback, SSLWriteCallback); |
| 486 | if (status) |
| 487 | return NetErrorFromOSStatus(status); |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 488 | |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 489 | status = SSLSetConnection(ssl_context_, this); |
| 490 | if (status) |
| 491 | return NetErrorFromOSStatus(status); |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 492 | |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 493 | status = SSLSetPeerDomainName(ssl_context_, hostname_.c_str(), |
| 494 | hostname_.length()); |
| 495 | if (status) |
| 496 | return NetErrorFromOSStatus(status); |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 497 | |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 498 | next_state_ = STATE_HANDSHAKE; |
| 499 | return OK; |
| 500 | } |
| 501 | |
| 502 | int SSLClientSocketMac::DoHandshake() { |
| 503 | OSStatus status = SSLHandshake(ssl_context_); |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 504 | |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 505 | if (status == errSSLWouldBlock) |
| 506 | next_state_ = STATE_HANDSHAKE; |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 507 | |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 508 | if (status == noErr) |
| 509 | completed_handshake_ = true; |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 510 | |
[email protected] | e159339 | 2008-10-20 19:46:33 | [diff] [blame] | 511 | int net_error = NetErrorFromOSStatus(status); |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 512 | |
[email protected] | e159339 | 2008-10-20 19:46:33 | [diff] [blame] | 513 | // At this point we have a connection. For now, we're going to use the default |
| 514 | // certificate verification that the system does, and accept its answer for |
| 515 | // the cert status. In the future, we'll need to call SSLSetEnableCertVerify |
| 516 | // to disable cert verification and do the verification ourselves. This allows |
| 517 | // very fine-grained control over what we'll accept for certification. |
| 518 | // TODO(avi): ditto |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 519 | |
[email protected] | e159339 | 2008-10-20 19:46:33 | [diff] [blame] | 520 | // TODO(wtc): for now, always check revocation. |
| 521 | server_cert_status_ = CERT_STATUS_REV_CHECKING_ENABLED; |
| 522 | if (net_error) |
[email protected] | dedb5943 | 2009-02-03 16:51:15 | [diff] [blame] | 523 | server_cert_status_ |= MapNetErrorToCertStatus(net_error); |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 524 | |
[email protected] | e159339 | 2008-10-20 19:46:33 | [diff] [blame] | 525 | return net_error; |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 526 | } |
| 527 | |
| 528 | int SSLClientSocketMac::DoReadComplete(int result) { |
| 529 | if (result < 0) |
| 530 | return result; |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 531 | |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 532 | recv_buffer_tail_slop_ -= result; |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 533 | |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 534 | return result; |
| 535 | } |
| 536 | |
| 537 | void SSLClientSocketMac::OnWriteComplete(int result) { |
| 538 | if (result < 0) { |
| 539 | pending_send_error_ = result; |
| 540 | return; |
| 541 | } |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 542 | |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 543 | send_buffer_.erase(send_buffer_.begin(), |
| 544 | send_buffer_.begin() + result); |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 545 | |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 546 | if (!send_buffer_.empty()) |
| 547 | SSLWriteCallback(this, NULL, NULL); |
| 548 | } |
| 549 | |
| 550 | int SSLClientSocketMac::DoPayloadRead() { |
| 551 | size_t processed; |
| 552 | OSStatus status = SSLRead(ssl_context_, |
| 553 | user_buf_, |
| 554 | user_buf_len_, |
| 555 | &processed); |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 556 | |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 557 | // There's a subtle difference here in semantics of the "would block" errors. |
| 558 | // In our code, ERR_IO_PENDING means the whole operation is async, while |
| 559 | // errSSLWouldBlock means that the stream isn't ending (and is often returned |
| 560 | // along with partial data). So even though "would block" is returned, if we |
| 561 | // have data, let's just return it. |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 562 | |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 563 | if (processed > 0) { |
| 564 | next_state_ = STATE_NONE; |
| 565 | return processed; |
| 566 | } |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 567 | |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 568 | if (status == errSSLWouldBlock) { |
| 569 | next_state_ = STATE_PAYLOAD_READ; |
| 570 | } |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 571 | |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 572 | return NetErrorFromOSStatus(status); |
| 573 | } |
| 574 | |
| 575 | int SSLClientSocketMac::DoPayloadWrite() { |
| 576 | size_t processed; |
| 577 | OSStatus status = SSLWrite(ssl_context_, |
| 578 | user_buf_, |
| 579 | user_buf_len_, |
| 580 | &processed); |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 581 | |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 582 | if (processed > 0) |
| 583 | return processed; |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 584 | |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 585 | return NetErrorFromOSStatus(status); |
| 586 | } |
| 587 | |
| 588 | // Handling the reading from the network is one of those things that should be |
| 589 | // simpler than it is. Ideally, we'd have some kind of ring buffer. For now, a |
| 590 | // std::vector<char> will have to do. |
| 591 | // |
| 592 | // The need for a buffer at all comes from the difference between an |
| 593 | // asynchronous connection (which is what we have) and a non-blocking connection |
| 594 | // (which is what we fake for Secure Transport). When Secure Transport calls us |
| 595 | // to read data, we call our underlying transport, which will likely tell us |
| 596 | // that it'll do a callback. When that happens, we need to tell Secure Transport |
| 597 | // that we've "blocked". When the callback happens, we have a chunk of data that |
| 598 | // we need to feed to Secure Transport, but it's not interested. It'll ask for |
| 599 | // it again when we call it again, so we need to hold on to the data. |
| 600 | // |
| 601 | // Why keep our own buffer? Well, when we execute a read and the underlying |
| 602 | // transport says that it'll do a callback, it keeps the pointer to the |
| 603 | // buffer. We can't pass it the buffer that Secure Transport gave us to fill, as |
| 604 | // we can't guarantee its lifetime. |
| 605 | // |
| 606 | // The basic idea, then, is this: we have a buffer filled with the data that |
| 607 | // we've read from the network but haven't given to Secure Transport |
| 608 | // yet. Whenever we read from the network the first thing we do is ensure we |
| 609 | // have enough room in the buffer for the read. We enlarge the buffer to be big |
| 610 | // enough to hold both our existing data and the new data, and then we mark the |
| 611 | // extra space at the end as "tail slop." Slop is just space at the ends of the |
| 612 | // buffer that's going to be used for data but isn't (yet). A diagram: |
| 613 | // |
| 614 | // +--------------------------------------+--------------------------------+ |
| 615 | // | existing good data ~~~~~~~~~~~~~~~~~ | tail slop area ~~~~~~~~~~~~~~~ | |
| 616 | // +--------------------------------------+--------------------------------+ |
| 617 | // |
| 618 | // When executing a read, we pass a pointer to the beginning of the tail slop |
| 619 | // area (guaranteed to be contiguous space because it's a vector, unlike, say, a |
| 620 | // deque (sigh)) and the size of the tail slop. When we get data (either here in |
| 621 | // SSLReadCallback() or above in DoReadComplete()) we subtract the number of |
| 622 | // bytes received from the tail slop value. That moves those bytes |
| 623 | // (conceptually, not physically) from the tail slop area to the area containing |
| 624 | // real data. |
| 625 | // |
| 626 | // The idea is still pretty simple. We enlarge the tail slop, call our |
| 627 | // underlying network, get data, shrink the slop area to match, copy requested |
| 628 | // data back into our caller's buffer, and delete the data from the head of the |
| 629 | // vector. |
| 630 | // |
| 631 | // Except for a nasty little problem. Asynchronous I/O calls keep the buffer |
| 632 | // pointer. |
| 633 | // |
| 634 | // This leads to the following scenario: we have a few bytes of good data in our |
| 635 | // buffer. But our caller requests more than that. We oblige by enlarging the |
| 636 | // tail slop, and calling our underlying provider, but the provider says that |
| 637 | // it'll call us back later. So we shrug our shoulders, copy what we do have |
| 638 | // into our caller's buffer and... |
| 639 | // |
| 640 | // Wait. We can't delete the data from the head of our vector. That would |
| 641 | // invalidate the pointer that we just gave to our provider. So instead, in that |
| 642 | // case we keep track of where the good data starts by keeping a "head slop" |
| 643 | // value, which just notes what data we've already sent and that is useless to |
| 644 | // us but that we can't delete because we have I/O in flight depending on us |
| 645 | // leaving the buffer alone. |
| 646 | // |
| 647 | // I hear what you're saying. "We need to use a ring buffer!" You write it, |
| 648 | // then, and I'll use it. Here are the features it needs. First, it needs to be |
| 649 | // able to have contiguous segments of arbitrary length attached to it to create |
| 650 | // read buffers. Second, each of those segments must have a "used" length |
| 651 | // indicator, so if it was half-filled by a previous data read, but the next |
| 652 | // data read is for more than there's space left, a new segment can be created |
| 653 | // for the new read without leaving an internal gap. |
| 654 | // |
| 655 | // Get to it. |
| 656 | // |
| 657 | // (sigh) Who am I kidding? TODO(avi): write the aforementioned ring buffer |
| 658 | |
| 659 | // static |
| 660 | OSStatus SSLClientSocketMac::SSLReadCallback(SSLConnectionRef connection, |
| 661 | void* data, |
| 662 | size_t* data_length) { |
| 663 | DCHECK(data); |
| 664 | DCHECK(data_length); |
| 665 | SSLClientSocketMac* us = |
| 666 | const_cast<SSLClientSocketMac*>( |
| 667 | static_cast<const SSLClientSocketMac*>(connection)); |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 668 | |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 669 | // If we have I/O in flight, promise we'll get back to them and use the |
| 670 | // existing callback to do so |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 671 | |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 672 | if (us->next_io_state_ == STATE_READ_COMPLETE) { |
| 673 | *data_length = 0; |
| 674 | return errSSLWouldBlock; |
| 675 | } |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 676 | |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 677 | // Start with what's in the buffer |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 678 | |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 679 | size_t total_read = us->recv_buffer_.size() - us->recv_buffer_head_slop_ - |
| 680 | us->recv_buffer_tail_slop_; |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 681 | |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 682 | // Resize the buffer if needed |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 683 | |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 684 | if (us->recv_buffer_.size() - us->recv_buffer_head_slop_ < *data_length) { |
| 685 | us->recv_buffer_.resize(us->recv_buffer_head_slop_ + *data_length); |
| 686 | us->recv_buffer_tail_slop_ = *data_length - total_read; |
| 687 | } |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 688 | |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 689 | int rv = 1; // any old value to spin the loop below |
| 690 | while (rv > 0 && total_read < *data_length) { |
| 691 | rv = us->transport_->Read(&us->recv_buffer_[us->recv_buffer_head_slop_ + |
| 692 | total_read], |
| 693 | us->recv_buffer_tail_slop_, |
| 694 | &us->io_callback_); |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 695 | |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 696 | if (rv > 0) { |
| 697 | total_read += rv; |
| 698 | us->recv_buffer_tail_slop_ -= rv; |
| 699 | } |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 700 | } |
| 701 | |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 702 | *data_length = total_read; |
| 703 | if (total_read) { |
| 704 | memcpy(data, &us->recv_buffer_[us->recv_buffer_head_slop_], total_read); |
| 705 | if (rv == ERR_IO_PENDING) { |
| 706 | // We have I/O in flight which is going to land in our buffer. We can't |
| 707 | // shuffle things around, so we need to just fiddle with pointers. |
| 708 | us->recv_buffer_head_slop_ += total_read; |
| 709 | } else { |
| 710 | us->recv_buffer_.erase(us->recv_buffer_.begin(), |
| 711 | us->recv_buffer_.begin() + |
| 712 | total_read + |
| 713 | us->recv_buffer_head_slop_); |
| 714 | us->recv_buffer_head_slop_ = 0; |
| 715 | } |
| 716 | } |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 717 | |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 718 | if (rv == ERR_IO_PENDING) { |
| 719 | us->next_io_state_ = STATE_READ_COMPLETE; |
| 720 | } |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 721 | |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 722 | if (rv < 0) |
| 723 | return OSStatusFromNetError(rv); |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 724 | |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 725 | return noErr; |
| 726 | } |
| 727 | |
| 728 | // static |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 729 | OSStatus SSLClientSocketMac::SSLWriteCallback(SSLConnectionRef connection, |
| 730 | const void* data, |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 731 | size_t* data_length) { |
| 732 | SSLClientSocketMac* us = |
| 733 | const_cast<SSLClientSocketMac*>( |
| 734 | static_cast<const SSLClientSocketMac*>(connection)); |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 735 | |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 736 | if (us->pending_send_error_ != OK) { |
| 737 | OSStatus status = OSStatusFromNetError(us->pending_send_error_); |
| 738 | us->pending_send_error_ = OK; |
| 739 | return status; |
| 740 | } |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 741 | |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 742 | if (data) |
| 743 | us->send_buffer_.insert(us->send_buffer_.end(), |
| 744 | static_cast<const char*>(data), |
| 745 | static_cast<const char*>(data) + *data_length); |
| 746 | int rv; |
| 747 | do { |
| 748 | rv = us->transport_->Write(&us->send_buffer_[0], |
| 749 | us->send_buffer_.size(), |
| 750 | &us->write_callback_); |
| 751 | if (rv > 0) { |
| 752 | us->send_buffer_.erase(us->send_buffer_.begin(), |
| 753 | us->send_buffer_.begin() + rv); |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 754 | |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 755 | } |
| 756 | } while (rv > 0 && !us->send_buffer_.empty()); |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 757 | |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 758 | if (rv < 0 && rv != ERR_IO_PENDING) { |
| 759 | return OSStatusFromNetError(rv); |
| 760 | } |
[email protected] | f0a51fb5 | 2009-03-05 12:46:38 | [diff] [blame^] | 761 | |
[email protected] | b75523f | 2008-10-17 14:49:07 | [diff] [blame] | 762 | // always lie to our caller |
| 763 | return noErr; |
| 764 | } |
| 765 | |
| 766 | } // namespace net |
| 767 | |