aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJerome Vouillon <vouillon@pps.jussieu.fr>2010-04-16 22:43:01 +0200
committerDaniel Stenberg <daniel@haxx.se>2010-04-16 22:43:01 +0200
commitc2888604d7ead19473b5621f8f2edab60fc418de (patch)
treec8ce3f5e8ad594f03c34d38f176b351d500fb503
parent6632d957e7d658566288406276dc98669e8228c7 (diff)
GnuTLS: make the connection phase non-blocking
When multi interface is used, the SSL handshake is no longer blocking when GnuTLS is used.
-rw-r--r--lib/gtls.c212
-rw-r--r--lib/gtls.h4
-rw-r--r--lib/http.c18
-rw-r--r--lib/urldata.h1
4 files changed, 160 insertions, 75 deletions
diff --git a/lib/gtls.c b/lib/gtls.c
index 92d780530..4f5edaf78 100644
--- a/lib/gtls.c
+++ b/lib/gtls.c
@@ -182,54 +182,76 @@ static void unload_file(gnutls_datum data) {
}
-/* this function does a BLOCKING SSL/TLS (re-)handshake */
+/* this function does a SSL/TLS (re-)handshake */
static CURLcode handshake(struct connectdata *conn,
- gnutls_session session,
int sockindex,
- bool duringconnect)
+ bool duringconnect,
+ bool nonblocking)
{
struct SessionHandle *data = conn->data;
+ struct ssl_connect_data *connssl = &conn->ssl[sockindex];
+ gnutls_session session = conn->ssl[sockindex].session;
+ curl_socket_t sockfd = conn->sock[sockindex];
+ long timeout_ms;
int rc;
- if(!gtls_inited)
- Curl_gtls_init();
- do {
- rc = gnutls_handshake(session);
+ int what;
- if((rc == GNUTLS_E_AGAIN) || (rc == GNUTLS_E_INTERRUPTED)) {
- long timeout_ms = Curl_timeleft(conn, NULL, duringconnect);
+ while(1) {
+ /* check allowed time left */
+ timeout_ms = Curl_timeleft(conn, NULL, duringconnect);
- if(timeout_ms < 0) {
- /* a precaution, no need to continue if time already is up */
- failf(data, "SSL connection timeout");
- return CURLE_OPERATION_TIMEDOUT;
- }
+ if(timeout_ms < 0) {
+ /* no need to continue if time already is up */
+ failf(data, "SSL connection timeout");
+ return CURLE_OPERATION_TIMEDOUT;
+ }
- rc = Curl_socket_ready(conn->sock[sockindex],
- conn->sock[sockindex], (int)timeout_ms);
- if(rc > 0)
- /* reabable or writable, go loop*/
- continue;
- else if(0 == rc) {
- /* timeout */
- failf(data, "SSL connection timeout");
- return CURLE_OPERATION_TIMEDOUT;
- }
- else {
- /* anything that gets here is fatally bad */
+ /* if ssl is expecting something, check if it's available. */
+ if(connssl->connecting_state == ssl_connect_2_reading
+ || connssl->connecting_state == ssl_connect_2_writing) {
+
+ curl_socket_t writefd = ssl_connect_2_writing==
+ connssl->connecting_state?sockfd:CURL_SOCKET_BAD;
+ curl_socket_t readfd = ssl_connect_2_reading==
+ connssl->connecting_state?sockfd:CURL_SOCKET_BAD;
+
+ what = Curl_socket_ready(readfd, writefd,
+ nonblocking?0:(int)timeout_ms);
+ if(what < 0) {
+ /* fatal error */
failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO);
return CURLE_SSL_CONNECT_ERROR;
}
+ else if(0 == what) {
+ if(nonblocking) {
+ return CURLE_OK;
+ }
+ else {
+ /* timeout */
+ failf(data, "SSL connection timeout");
+ return CURLE_OPERATION_TIMEDOUT;
+ }
+ }
+ /* socket is readable or writable */
}
- else
- break;
- } while(1);
- if(rc < 0) {
- failf(data, "gnutls_handshake() failed: %s", gnutls_strerror(rc));
- return CURLE_SSL_CONNECT_ERROR;
- }
+ rc = gnutls_handshake(session);
- return CURLE_OK;
+ if((rc == GNUTLS_E_AGAIN) || (rc == GNUTLS_E_INTERRUPTED)) {
+ connssl->connecting_state =
+ gnutls_record_get_direction(session)?
+ ssl_connect_2_writing:ssl_connect_2_reading;
+ if(nonblocking) {
+ return CURLE_OK;
+ }
+ } else if (rc < 0) {
+ failf(data, "gnutls_handshake() failed: %s", gnutls_strerror(rc));
+ } else {
+ /* Reset our connect state machine */
+ connssl->connecting_state = ssl_connect_1;
+ return CURLE_OK;
+ }
+ }
}
static gnutls_x509_crt_fmt do_file_type(const char *type)
@@ -243,31 +265,14 @@ static gnutls_x509_crt_fmt do_file_type(const char *type)
return -1;
}
-
-/*
- * This function is called after the TCP connect has completed. Setup the TLS
- * layer and do all necessary magic.
- */
-CURLcode
-Curl_gtls_connect(struct connectdata *conn,
- int sockindex)
-
+static CURLcode
+gtls_connect_step1(struct connectdata *conn,
+ int sockindex)
{
static const int cert_type_priority[] = { GNUTLS_CRT_X509, 0 };
struct SessionHandle *data = conn->data;
gnutls_session session;
int rc;
- unsigned int cert_list_size;
- const gnutls_datum *chainp;
- unsigned int verify_status;
- gnutls_x509_crt x509_cert,x509_issuer;
- gnutls_datum issuerp;
- char certbuf[256]; /* big enough? */
- size_t size;
- unsigned int algo;
- unsigned int bits;
- time_t certclock;
- const char *ptr;
void *ssl_sessionid;
size_t ssl_idsize;
bool sni = TRUE; /* default is SNI enabled */
@@ -411,10 +416,29 @@ Curl_gtls_connect(struct connectdata *conn,
infof (data, "SSL re-using session ID\n");
}
- rc = handshake(conn, session, sockindex, TRUE);
- if(rc)
- /* handshake() sets its own error message with failf() */
- return rc;
+ return CURLE_OK;
+}
+
+static CURLcode
+gtls_connect_step3(struct connectdata *conn,
+ int sockindex)
+{
+ unsigned int cert_list_size;
+ const gnutls_datum *chainp;
+ unsigned int verify_status;
+ gnutls_x509_crt x509_cert,x509_issuer;
+ gnutls_datum issuerp;
+ char certbuf[256]; /* big enough? */
+ size_t size;
+ unsigned int algo;
+ unsigned int bits;
+ time_t certclock;
+ const char *ptr;
+ struct SessionHandle *data = conn->data;
+ gnutls_session session = conn->ssl[sockindex].session;
+ int rc;
+ int incache;
+ void *ssl_sessionid;
/* This function will return the peer's raw certificate (chain) as sent by
the peer. These certificates are in raw format (DER encoded for
@@ -623,20 +647,88 @@ Curl_gtls_connect(struct connectdata *conn,
/* extract session ID to the allocated buffer */
gnutls_session_get_data(session, connect_sessionid, &connect_idsize);
- if(ssl_sessionid)
+ incache = !(Curl_ssl_getsessionid(conn, &ssl_sessionid, NULL));
+ if (incache) {
/* there was one before in the cache, so instead of risking that the
previous one was rejected, we just kill that and store the new */
Curl_ssl_delsessionid(conn, ssl_sessionid);
+ }
/* store this session id */
return Curl_ssl_addsessionid(conn, connect_sessionid, connect_idsize);
}
+ }
+
+ return CURLE_OK;
+}
+
+/*
+ * This function is called after the TCP connect has completed. Setup the TLS
+ * layer and do all necessary magic.
+ */
+/* We use connssl->connecting_state to keep track of the connection status;
+ there are three states: 'ssl_connect_1' (not started yet or complete),
+ 'ssl_connect_2_reading' (waiting for data from server), and
+ 'ssl_connect_2_writing' (waiting to be able to write).
+ */
+static CURLcode
+gtls_connect_common(struct connectdata *conn,
+ int sockindex,
+ bool nonblocking,
+ bool *done)
+{
+ int rc;
+ struct ssl_connect_data *connssl = &conn->ssl[sockindex];
+
+ /* Initiate the connection, if not already done */
+ if(ssl_connect_1==connssl->connecting_state) {
+ rc = gtls_connect_step1 (conn, sockindex);
+ if(rc)
+ return rc;
}
+ rc = handshake(conn, sockindex, TRUE, nonblocking);
+ if(rc)
+ /* handshake() sets its own error message with failf() */
+ return rc;
+
+ /* Finish connecting once the handshake is done */
+ if(ssl_connect_1==connssl->connecting_state) {
+ rc = gtls_connect_step3(conn, sockindex);
+ if(rc)
+ return rc;
+ }
+
+ *done = ssl_connect_1==connssl->connecting_state;
+
return CURLE_OK;
}
+CURLcode
+Curl_gtls_connect_nonblocking(struct connectdata *conn,
+ int sockindex,
+ bool *done)
+{
+ return gtls_connect_common(conn, sockindex, TRUE, done);
+}
+
+CURLcode
+Curl_gtls_connect(struct connectdata *conn,
+ int sockindex)
+
+{
+ CURLcode retcode;
+ bool done = FALSE;
+
+ retcode = gtls_connect_common(conn, sockindex, FALSE, &done);
+ if(retcode)
+ return retcode;
+
+ DEBUGASSERT(done);
+
+ return CURLE_OK;
+}
/* for documentation see Curl_ssl_send() in sslgen.h */
ssize_t Curl_gtls_send(struct connectdata *conn,
@@ -769,7 +861,7 @@ ssize_t Curl_gtls_recv(struct connectdata *conn, /* connection data */
if(ret == GNUTLS_E_REHANDSHAKE) {
/* BLOCKING call, this is bad but a work-around for now. Fixing this "the
proper way" takes a whole lot of work. */
- CURLcode rc = handshake(conn, conn->ssl[num].session, num, FALSE);
+ CURLcode rc = handshake(conn, num, FALSE, FALSE);
if(rc)
/* handshake() writes error message on its own */
*curlcode = rc;
diff --git a/lib/gtls.h b/lib/gtls.h
index 9fe618a32..da3489bc3 100644
--- a/lib/gtls.h
+++ b/lib/gtls.h
@@ -27,6 +27,9 @@
int Curl_gtls_init(void);
int Curl_gtls_cleanup(void);
CURLcode Curl_gtls_connect(struct connectdata *conn, int sockindex);
+CURLcode Curl_gtls_connect_nonblocking(struct connectdata *conn,
+ int sockindex,
+ bool *done);
/* tell GnuTLS to close down all open information regarding connections (and
thus session ID caching etc) */
@@ -52,6 +55,7 @@ int Curl_gtls_seed(struct SessionHandle *data);
#define curlssl_init Curl_gtls_init
#define curlssl_cleanup Curl_gtls_cleanup
#define curlssl_connect Curl_gtls_connect
+#define curlssl_connect_nonblocking Curl_gtls_connect_nonblocking
#define curlssl_session_free(x) Curl_gtls_session_free(x)
#define curlssl_close_all Curl_gtls_close_all
#define curlssl_close Curl_gtls_close
diff --git a/lib/http.c b/lib/http.c
index 7ffc34d49..99d34e661 100644
--- a/lib/http.c
+++ b/lib/http.c
@@ -1817,9 +1817,9 @@ static CURLcode https_connecting(struct connectdata *conn, bool *done)
}
#endif
-#ifdef USE_SSLEAY
-/* This function is OpenSSL-specific. It should be made to query the generic
- SSL layer instead. */
+#if defined(USE_SSLEAY) || defined(USE_GNUTLS)
+/* This function is for OpenSSL and GnuTLS only. It should be made to query
+ the generic SSL layer instead. */
static int https_getsock(struct connectdata *conn,
curl_socket_t *socks,
int numsocks)
@@ -1844,17 +1844,6 @@ static int https_getsock(struct connectdata *conn,
return CURLE_OK;
}
#else
-#ifdef USE_GNUTLS
-static int https_getsock(struct connectdata *conn,
- curl_socket_t *socks,
- int numsocks)
-{
- (void)conn;
- (void)socks;
- (void)numsocks;
- return GETSOCK_BLANK;
-}
-#else
#ifdef USE_NSS
static int https_getsock(struct connectdata *conn,
curl_socket_t *socks,
@@ -1879,7 +1868,6 @@ static int https_getsock(struct connectdata *conn,
#endif
#endif
#endif
-#endif
/*
* Curl_http_done() gets called from Curl_done() after a single HTTP request
diff --git a/lib/urldata.h b/lib/urldata.h
index ad172d66a..02233b685 100644
--- a/lib/urldata.h
+++ b/lib/urldata.h
@@ -231,6 +231,7 @@ struct ssl_connect_data {
#ifdef USE_GNUTLS
gnutls_session session;
gnutls_certificate_credentials cred;
+ ssl_connect_state connecting_state;
#endif /* USE_GNUTLS */
#ifdef USE_NSS
PRFileDesc *handle;