diff options
author | Jerome Vouillon <vouillon@pps.jussieu.fr> | 2010-04-16 22:43:01 +0200 |
---|---|---|
committer | Daniel Stenberg <daniel@haxx.se> | 2010-04-16 22:43:01 +0200 |
commit | c2888604d7ead19473b5621f8f2edab60fc418de (patch) | |
tree | c8ce3f5e8ad594f03c34d38f176b351d500fb503 | |
parent | 6632d957e7d658566288406276dc98669e8228c7 (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.c | 212 | ||||
-rw-r--r-- | lib/gtls.h | 4 | ||||
-rw-r--r-- | lib/http.c | 18 | ||||
-rw-r--r-- | lib/urldata.h | 1 |
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; |