[curl] nss: implement non-blocking SSL handshake

Kamil Dudka kdudka at fedoraproject.org
Fri Apr 25 15:41:30 UTC 2014


commit 0f6b1efb1469b7801a08b8fd1172808227804b49
Author: Kamil Dudka <kdudka at redhat.com>
Date:   Fri Apr 25 17:36:51 2014 +0200

    nss: implement non-blocking SSL handshake

 0003-curl-7.36.0-8868a226.patch |  526 +++++++++++++++++++++++++++++++++++++++
 curl.spec                       |    9 +-
 2 files changed, 534 insertions(+), 1 deletions(-)
---
diff --git a/0003-curl-7.36.0-8868a226.patch b/0003-curl-7.36.0-8868a226.patch
new file mode 100644
index 0000000..954776c
--- /dev/null
+++ b/0003-curl-7.36.0-8868a226.patch
@@ -0,0 +1,526 @@
+From 79dd8298f45b9f5dd97c06c397d40e45f905d5d3 Mon Sep 17 00:00:00 2001
+From: Kamil Dudka <kdudka at redhat.com>
+Date: Thu, 17 Apr 2014 13:12:59 +0200
+Subject: [PATCH 1/3] nss: split Curl_nss_connect() into 4 functions
+
+[upstream commit a43bba3a34ed8912c4ca10f213590d1998ba0d29]
+
+Signed-off-by: Kamil Dudka <kdudka at redhat.com>
+---
+ lib/vtls/nss.c |  134 +++++++++++++++++++++++++++++++++++++++-----------------
+ 1 files changed, 94 insertions(+), 40 deletions(-)
+
+diff --git a/lib/vtls/nss.c b/lib/vtls/nss.c
+index 80e26e2..4f4e6c8 100644
+--- a/lib/vtls/nss.c
++++ b/lib/vtls/nss.c
+@@ -1296,9 +1296,62 @@ static CURLcode nss_init_sslver(SSLVersionRange *sslver,
+   return CURLE_SSL_CONNECT_ERROR;
+ }
+ 
+-CURLcode Curl_nss_connect(struct connectdata *conn, int sockindex)
++static CURLcode nss_fail_connect(struct ssl_connect_data *connssl,
++                                 struct SessionHandle *data,
++                                 CURLcode curlerr)
+ {
++  SSLVersionRange sslver;
+   PRErrorCode err = 0;
++
++  /* reset the flag to avoid an infinite loop */
++  data->state.ssl_connect_retry = FALSE;
++
++  if(is_nss_error(curlerr)) {
++    /* read NSPR error code */
++    err = PR_GetError();
++    if(is_cc_error(err))
++      curlerr = CURLE_SSL_CERTPROBLEM;
++
++    /* print the error number and error string */
++    infof(data, "NSS error %d (%s)\n", err, nss_error_to_name(err));
++
++    /* print a human-readable message describing the error if available */
++    nss_print_error_message(data, err);
++  }
++
++  /* cleanup on connection failure */
++  Curl_llist_destroy(connssl->obj_list, NULL);
++  connssl->obj_list = NULL;
++
++  if((SSL_VersionRangeGet(connssl->handle, &sslver) == SECSuccess)
++      && (sslver.min == SSL_LIBRARY_VERSION_3_0)
++      && (sslver.max == SSL_LIBRARY_VERSION_TLS_1_0)
++      && isTLSIntoleranceError(err)) {
++    /* schedule reconnect through Curl_retry_request() */
++    data->state.ssl_connect_retry = TRUE;
++    infof(data, "Error in TLS handshake, trying SSLv3...\n");
++    return CURLE_OK;
++  }
++
++  return curlerr;
++}
++
++/* Switch the SSL socket into non-blocking mode. */
++static CURLcode nss_set_nonblock(struct ssl_connect_data *connssl,
++                                 struct SessionHandle *data)
++{
++  static PRSocketOptionData sock_opt;
++  sock_opt.option = PR_SockOpt_Nonblocking;
++  sock_opt.value.non_blocking = PR_TRUE;
++
++  if(PR_SetSocketOption(connssl->handle, &sock_opt) != PR_SUCCESS)
++    return nss_fail_connect(connssl, data, CURLE_SSL_CONNECT_ERROR);
++
++  return CURLE_OK;
++}
++
++static CURLcode nss_setup_connect(struct connectdata *conn, int sockindex)
++{
+   PRFileDesc *model = NULL;
+   PRBool ssl_no_cache;
+   PRBool ssl_cbc_random_iv;
+@@ -1306,9 +1359,6 @@ CURLcode Curl_nss_connect(struct connectdata *conn, int sockindex)
+   curl_socket_t sockfd = conn->sock[sockindex];
+   struct ssl_connect_data *connssl = &conn->ssl[sockindex];
+   CURLcode curlerr;
+-  PRSocketOptionData sock_opt;
+-  long time_left;
+-  PRUint32 timeout;
+ 
+   SSLVersionRange sslver = {
+     SSL_LIBRARY_VERSION_3_0,      /* min */
+@@ -1534,16 +1584,32 @@ CURLcode Curl_nss_connect(struct connectdata *conn, int sockindex)
+ 
+   SSL_SetURL(connssl->handle, conn->host.name);
+ 
++  return CURLE_OK;
++
++error:
++  if(model)
++    PR_Close(model);
++
++  return nss_fail_connect(connssl, data, curlerr);
++}
++
++static CURLcode nss_do_connect(struct connectdata *conn, int sockindex)
++{
++  struct ssl_connect_data *connssl = &conn->ssl[sockindex];
++  struct SessionHandle *data = conn->data;
++  CURLcode curlerr = CURLE_SSL_CONNECT_ERROR;
++  PRUint32 timeout;
++
+   /* check timeout situation */
+-  time_left = Curl_timeleft(data, NULL, TRUE);
++  const long time_left = Curl_timeleft(data, NULL, TRUE);
+   if(time_left < 0L) {
+     failf(data, "timed out before SSL handshake");
+     curlerr = CURLE_OPERATION_TIMEDOUT;
+     goto error;
+   }
+-  timeout = PR_MillisecondsToInterval((PRUint32) time_left);
+ 
+   /* Force the handshake now */
++  timeout = PR_MillisecondsToInterval((PRUint32) time_left);
+   if(SSL_ForceHandshakeWithTimeout(connssl->handle, timeout) != SECSuccess) {
+     if(conn->data->set.ssl.certverifyresult == SSL_ERROR_BAD_CERT_DOMAIN)
+       curlerr = CURLE_PEER_FAILED_VERIFICATION;
+@@ -1552,12 +1618,6 @@ CURLcode Curl_nss_connect(struct connectdata *conn, int sockindex)
+     goto error;
+   }
+ 
+-  /* switch the SSL socket into non-blocking mode */
+-  sock_opt.option = PR_SockOpt_Nonblocking;
+-  sock_opt.value.non_blocking = PR_TRUE;
+-  if(PR_SetSocketOption(connssl->handle, &sock_opt) != PR_SUCCESS)
+-    goto error;
+-
+   connssl->state = ssl_connection_complete;
+   conn->recv[sockindex] = nss_recv;
+   conn->send[sockindex] = nss_send;
+@@ -1585,40 +1645,34 @@ CURLcode Curl_nss_connect(struct connectdata *conn, int sockindex)
+ 
+   return CURLE_OK;
+ 
+-  error:
+-  /* reset the flag to avoid an infinite loop */
+-  data->state.ssl_connect_retry = FALSE;
++error:
++  return nss_fail_connect(connssl, data, curlerr);
++}
+ 
+-  if(is_nss_error(curlerr)) {
+-    /* read NSPR error code */
+-    err = PR_GetError();
+-    if(is_cc_error(err))
+-      curlerr = CURLE_SSL_CERTPROBLEM;
++CURLcode Curl_nss_connect(struct connectdata *conn, int sockindex)
++{
++  struct ssl_connect_data *connssl = &conn->ssl[sockindex];
++  struct SessionHandle *data = conn->data;
++  CURLcode rv;
+ 
+-    /* print the error number and error string */
+-    infof(data, "NSS error %d (%s)\n", err, nss_error_to_name(err));
++  rv = nss_setup_connect(conn, sockindex);
++  if(rv)
++    return rv;
+ 
+-    /* print a human-readable message describing the error if available */
+-    nss_print_error_message(data, err);
++  rv = nss_do_connect(conn, sockindex);
++  switch(rv) {
++  case CURLE_OK:
++    break;
++  default:
++    return rv;
+   }
+ 
+-  if(model)
+-    PR_Close(model);
+-
+-  /* cleanup on connection failure */
+-  Curl_llist_destroy(connssl->obj_list, NULL);
+-  connssl->obj_list = NULL;
+-
+-  if((sslver.min == SSL_LIBRARY_VERSION_3_0)
+-      && (sslver.max == SSL_LIBRARY_VERSION_TLS_1_0)
+-      && isTLSIntoleranceError(err)) {
+-    /* schedule reconnect through Curl_retry_request() */
+-    data->state.ssl_connect_retry = TRUE;
+-    infof(data, "Error in TLS handshake, trying SSLv3...\n");
+-    return CURLE_OK;
+-  }
++  /* switch the SSL socket into non-blocking mode */
++  rv = nss_set_nonblock(connssl, data);
++  if(rv)
++    return rv;
+ 
+-  return curlerr;
++  return CURLE_OK;
+ }
+ 
+ static ssize_t nss_send(struct connectdata *conn,  /* connection data */
+-- 
+1.7.1
+
+
+From f6c04350401c111f92f1428f80a28b66f6609cac Mon Sep 17 00:00:00 2001
+From: Kamil Dudka <kdudka at redhat.com>
+Date: Thu, 17 Apr 2014 13:27:39 +0200
+Subject: [PATCH 2/3] nss: implement non-blocking SSL handshake
+
+[upstream commit 8868a226cdad66a9a07d6e3f168884817592a1df]
+
+Signed-off-by: Kamil Dudka <kdudka at redhat.com>
+---
+ lib/urldata.h   |    1 +
+ lib/vtls/nss.c  |   57 ++++++++++++++++++++++++++++++++++++++++++++++--------
+ lib/vtls/nssg.h |    1 +
+ 3 files changed, 50 insertions(+), 9 deletions(-)
+
+diff --git a/lib/urldata.h b/lib/urldata.h
+index 25f9676..d3bb350 100644
+--- a/lib/urldata.h
++++ b/lib/urldata.h
+@@ -318,6 +318,7 @@ struct ssl_connect_data {
+   struct SessionHandle *data;
+   struct curl_llist *obj_list;
+   PK11GenericObject *obj_clicert;
++  ssl_connect_state connecting_state;
+ #endif /* USE_NSS */
+ #ifdef USE_QSOSSL
+   SSLHandle *handle;
+diff --git a/lib/vtls/nss.c b/lib/vtls/nss.c
+index 4f4e6c8..e076e54 100644
+--- a/lib/vtls/nss.c
++++ b/lib/vtls/nss.c
+@@ -1611,7 +1611,10 @@ static CURLcode nss_do_connect(struct connectdata *conn, int sockindex)
+   /* Force the handshake now */
+   timeout = PR_MillisecondsToInterval((PRUint32) time_left);
+   if(SSL_ForceHandshakeWithTimeout(connssl->handle, timeout) != SECSuccess) {
+-    if(conn->data->set.ssl.certverifyresult == SSL_ERROR_BAD_CERT_DOMAIN)
++    if(PR_GetError() == PR_WOULD_BLOCK_ERROR)
++      /* TODO: propagate the blocking direction from the NSPR layer */
++      return CURLE_AGAIN;
++    else if(conn->data->set.ssl.certverifyresult == SSL_ERROR_BAD_CERT_DOMAIN)
+       curlerr = CURLE_PEER_FAILED_VERIFICATION;
+     else if(conn->data->set.ssl.certverifyresult!=0)
+       curlerr = CURLE_SSL_CACERT;
+@@ -1649,32 +1652,68 @@ error:
+   return nss_fail_connect(connssl, data, curlerr);
+ }
+ 
+-CURLcode Curl_nss_connect(struct connectdata *conn, int sockindex)
++static CURLcode nss_connect_common(struct connectdata *conn, int sockindex,
++                                   bool *done)
+ {
+   struct ssl_connect_data *connssl = &conn->ssl[sockindex];
+   struct SessionHandle *data = conn->data;
++  const bool blocking = (done == NULL);
+   CURLcode rv;
+ 
+-  rv = nss_setup_connect(conn, sockindex);
+-  if(rv)
+-    return rv;
++  if(connssl->connecting_state == ssl_connect_1) {
++    rv = nss_setup_connect(conn, sockindex);
++    if(rv)
++      /* we do not expect CURLE_AGAIN from nss_setup_connect() */
++      return rv;
++
++    if(!blocking) {
++      /* in non-blocking mode, set NSS non-blocking mode before handshake */
++      rv = nss_set_nonblock(connssl, data);
++      if(rv)
++        return rv;
++    }
++
++    connssl->connecting_state = ssl_connect_2;
++  }
+ 
+   rv = nss_do_connect(conn, sockindex);
+   switch(rv) {
+   case CURLE_OK:
+     break;
++  case CURLE_AGAIN:
++    if(!blocking)
++      /* CURLE_AGAIN in non-blocking mode is not an error */
++      return CURLE_OK;
++    /* fall through */
+   default:
+     return rv;
+   }
+ 
+-  /* switch the SSL socket into non-blocking mode */
+-  rv = nss_set_nonblock(connssl, data);
+-  if(rv)
+-    return rv;
++  if(blocking) {
++    /* in blocking mode, set NSS non-blocking mode _after_ SSL handshake */
++    rv = nss_set_nonblock(connssl, data);
++    if(rv)
++      return rv;
++  }
++  else
++    /* signal completed SSL handshake */
++    *done = TRUE;
+ 
++  connssl->connecting_state = ssl_connect_done;
+   return CURLE_OK;
+ }
+ 
++CURLcode Curl_nss_connect(struct connectdata *conn, int sockindex)
++{
++  return nss_connect_common(conn, sockindex, /* blocking */ NULL);
++}
++
++CURLcode Curl_nss_connect_nonblocking(struct connectdata *conn,
++                                      int sockindex, bool *done)
++{
++  return nss_connect_common(conn, sockindex, done);
++}
++
+ static ssize_t nss_send(struct connectdata *conn,  /* connection data */
+                         int sockindex,             /* socketindex */
+                         const void *mem,           /* send this data */
+diff --git a/lib/vtls/nssg.h b/lib/vtls/nssg.h
+index 38181a9..21e96ce 100644
+--- a/lib/vtls/nssg.h
++++ b/lib/vtls/nssg.h
+@@ -68,6 +68,7 @@ void Curl_nss_md5sum(unsigned char *tmp, /* input */
+ #define curlssl_init Curl_nss_init
+ #define curlssl_cleanup Curl_nss_cleanup
+ #define curlssl_connect Curl_nss_connect
++#define curlssl_connect_nonblocking Curl_nss_connect_nonblocking
+ 
+ /* NSS has its own session ID cache */
+ #define curlssl_session_free(x) Curl_nop_stmt
+-- 
+1.7.1
+
+
+From 9fb78efb737ea8c2a9f7c27ea501b1fcf6a90599 Mon Sep 17 00:00:00 2001
+From: Kamil Dudka <kdudka at redhat.com>
+Date: Wed, 23 Apr 2014 15:37:26 +0200
+Subject: [PATCH 3/3] nss: propagate blocking direction from NSPR I/O
+
+... during the non-blocking SSL handshake
+
+[upstream commit 9c941e92c4bd3d2a5dbe243f7517b6a6029afc6e]
+
+Signed-off-by: Kamil Dudka <kdudka at redhat.com>
+---
+ lib/http.c     |    2 +-
+ lib/vtls/nss.c |  108 +++++++++++++++++++++++++++++++++++++++++++++++++++++---
+ 2 files changed, 104 insertions(+), 6 deletions(-)
+
+diff --git a/lib/http.c b/lib/http.c
+index 4a29058..3f8a4c0 100644
+--- a/lib/http.c
++++ b/lib/http.c
+@@ -1361,7 +1361,7 @@ static CURLcode https_connecting(struct connectdata *conn, bool *done)
+ #endif
+ 
+ #if defined(USE_SSLEAY) || defined(USE_GNUTLS) || defined(USE_SCHANNEL) || \
+-    defined(USE_DARWINSSL) || defined(USE_POLARSSL)
++    defined(USE_DARWINSSL) || defined(USE_POLARSSL) || defined(USE_NSS)
+ /* This function is for OpenSSL, GnuTLS, darwinssl, schannel and polarssl only.
+    It should be made to query the generic SSL layer instead. */
+ static int https_getsock(struct connectdata *conn,
+diff --git a/lib/vtls/nss.c b/lib/vtls/nss.c
+index e076e54..3447f97 100644
+--- a/lib/vtls/nss.c
++++ b/lib/vtls/nss.c
+@@ -180,6 +180,10 @@ static const cipher_s cipherlist[] = {
+ static const char* pem_library = "libnsspem.so";
+ SECMODModule* mod = NULL;
+ 
++/* NSPR I/O layer we use to detect blocking direction during SSL handshake */
++static PRDescIdentity nspr_io_identity = PR_INVALID_IO_LAYER;
++static PRIOMethods nspr_io_methods;
++
+ static const char* nss_error_to_name(PRErrorCode code)
+ {
+   const char *name = PR_ErrorToName(code);
+@@ -940,6 +944,60 @@ isTLSIntoleranceError(PRInt32 err)
+   }
+ }
+ 
++/* update blocking direction in case of PR_WOULD_BLOCK_ERROR */
++static void nss_update_connecting_state(ssl_connect_state state, void *secret)
++{
++  struct ssl_connect_data *connssl = (struct ssl_connect_data *)secret;
++  if(PR_GetError() != PR_WOULD_BLOCK_ERROR)
++    /* an unrelated error is passing by */
++    return;
++
++  switch(connssl->connecting_state) {
++  case ssl_connect_2:
++  case ssl_connect_2_reading:
++  case ssl_connect_2_writing:
++    break;
++  default:
++    /* we are not called from an SSL handshake */
++    return;
++  }
++
++  /* update the state accordingly */
++  connssl->connecting_state = state;
++}
++
++/* recv() wrapper we use to detect blocking direction during SSL handshake */
++static PRInt32 nspr_io_recv(PRFileDesc *fd, void *buf, PRInt32 amount,
++                            PRIntn flags, PRIntervalTime timeout)
++{
++  const PRRecvFN recv_fn = fd->lower->methods->recv;
++  const PRInt32 rv = recv_fn(fd->lower, buf, amount, flags, timeout);
++  if(rv < 0)
++    /* check for PR_WOULD_BLOCK_ERROR and update blocking direction */
++    nss_update_connecting_state(ssl_connect_2_reading, fd->secret);
++  return rv;
++}
++
++/* send() wrapper we use to detect blocking direction during SSL handshake */
++static PRInt32 nspr_io_send(PRFileDesc *fd, const void *buf, PRInt32 amount,
++                            PRIntn flags, PRIntervalTime timeout)
++{
++  const PRSendFN send_fn = fd->lower->methods->send;
++  const PRInt32 rv = send_fn(fd->lower, buf, amount, flags, timeout);
++  if(rv < 0)
++    /* check for PR_WOULD_BLOCK_ERROR and update blocking direction */
++    nss_update_connecting_state(ssl_connect_2_writing, fd->secret);
++  return rv;
++}
++
++/* close() wrapper to avoid assertion failure due to fd->secret != NULL */
++static PRStatus nspr_io_close(PRFileDesc *fd)
++{
++  const PRCloseFN close_fn = PR_GetDefaultIOMethods()->close;
++  fd->secret = NULL;
++  return close_fn(fd);
++}
++
+ static CURLcode nss_init_core(struct SessionHandle *data, const char *cert_dir)
+ {
+   NSSInitParameters initparams;
+@@ -1004,6 +1062,21 @@ static CURLcode nss_init(struct SessionHandle *data)
+     }
+   }
+ 
++  if(nspr_io_identity == PR_INVALID_IO_LAYER) {
++    /* allocate an identity for our own NSPR I/O layer */
++    nspr_io_identity = PR_GetUniqueIdentity("libcurl");
++    if(nspr_io_identity == PR_INVALID_IO_LAYER)
++      return CURLE_OUT_OF_MEMORY;
++
++    /* the default methods just call down to the lower I/O layer */
++    memcpy(&nspr_io_methods, PR_GetDefaultIOMethods(), sizeof nspr_io_methods);
++
++    /* override certain methods in the table by our wrappers */
++    nspr_io_methods.recv  = nspr_io_recv;
++    nspr_io_methods.send  = nspr_io_send;
++    nspr_io_methods.close = nspr_io_close;
++  }
++
+   rv = nss_init_core(data, cert_dir);
+   if(rv)
+     return rv;
+@@ -1353,6 +1426,8 @@ static CURLcode nss_set_nonblock(struct ssl_connect_data *connssl,
+ static CURLcode nss_setup_connect(struct connectdata *conn, int sockindex)
+ {
+   PRFileDesc *model = NULL;
++  PRFileDesc *nspr_io = NULL;
++  PRFileDesc *nspr_io_stub = NULL;
+   PRBool ssl_no_cache;
+   PRBool ssl_cbc_random_iv;
+   struct SessionHandle *data = conn->data;
+@@ -1525,11 +1600,34 @@ static CURLcode nss_setup_connect(struct connectdata *conn, int sockindex)
+     goto error;
+   }
+ 
+-  /* Import our model socket  onto the existing file descriptor */
+-  connssl->handle = PR_ImportTCPSocket(sockfd);
+-  connssl->handle = SSL_ImportFD(model, connssl->handle);
+-  if(!connssl->handle)
++  /* wrap OS file descriptor by NSPR's file descriptor abstraction */
++  nspr_io = PR_ImportTCPSocket(sockfd);
++  if(!nspr_io)
++    goto error;
++
++  /* create our own NSPR I/O layer */
++  nspr_io_stub = PR_CreateIOLayerStub(nspr_io_identity, &nspr_io_methods);
++  if(!nspr_io_stub) {
++    PR_Close(nspr_io);
++    goto error;
++  }
++
++  /* make the per-connection data accessible from NSPR I/O callbacks */
++  nspr_io_stub->secret = (void *)connssl;
++
++  /* push our new layer to the NSPR I/O stack */
++  if(PR_PushIOLayer(nspr_io, PR_TOP_IO_LAYER, nspr_io_stub) != PR_SUCCESS) {
++    PR_Close(nspr_io);
++    PR_Close(nspr_io_stub);
+     goto error;
++  }
++
++  /* import our model socket onto the current I/O stack */
++  connssl->handle = SSL_ImportFD(model, nspr_io);
++  if(!connssl->handle) {
++    PR_Close(nspr_io);
++    goto error;
++  }
+ 
+   PR_Close(model); /* We don't need this any more */
+   model = NULL;
+@@ -1612,7 +1710,7 @@ static CURLcode nss_do_connect(struct connectdata *conn, int sockindex)
+   timeout = PR_MillisecondsToInterval((PRUint32) time_left);
+   if(SSL_ForceHandshakeWithTimeout(connssl->handle, timeout) != SECSuccess) {
+     if(PR_GetError() == PR_WOULD_BLOCK_ERROR)
+-      /* TODO: propagate the blocking direction from the NSPR layer */
++      /* blocking direction is updated by nss_update_connecting_state() */
+       return CURLE_AGAIN;
+     else if(conn->data->set.ssl.certverifyresult == SSL_ERROR_BAD_CERT_DOMAIN)
+       curlerr = CURLE_PEER_FAILED_VERIFICATION;
+-- 
+1.7.1
+
diff --git a/curl.spec b/curl.spec
index 53fa9c8..08b5dc1 100644
--- a/curl.spec
+++ b/curl.spec
@@ -1,7 +1,7 @@
 Summary: A utility for getting files from remote servers (FTP, HTTP, and others)
 Name: curl
 Version: 7.36.0
-Release: 2%{?dist}
+Release: 3%{?dist}
 License: MIT
 Group: Applications/Internet
 Source: http://curl.haxx.se/download/%{name}-%{version}.tar.lzma
@@ -13,6 +13,9 @@ Patch1: 0001-curl-7.36.0-f82e0edc.patch
 # extend URL parser to support IPv6 zone identifiers (#680996)
 Patch2: 0002-curl-7.36.0-9317eced.patch
 
+# nss: implement non-blocking SSL handshake
+Patch3: 0003-curl-7.36.0-8868a226.patch
+
 # patch making libcurl multilib ready
 Patch101: 0101-curl-7.32.0-multilib.patch
 
@@ -127,6 +130,7 @@ documentation of the library, too.
 # upstream patches
 %patch1 -p1
 %patch2 -p1
+%patch3 -p1
 
 # Fedora patches
 %patch101 -p1
@@ -248,6 +252,9 @@ rm -rf $RPM_BUILD_ROOT
 %{_datadir}/aclocal/libcurl.m4
 
 %changelog
+* Fri Apr 25 2014 Kamil Dudka <kdudka at redhat.com> 7.36.0-3
+- nss: implement non-blocking SSL handshake
+
 * Wed Apr 02 2014 Kamil Dudka <kdudka at redhat.com> 7.36.0-2
 - extend URL parser to support IPv6 zone identifiers (#680996)
 


More information about the scm-commits mailing list