From c2888604d7ead19473b5621f8f2edab60fc418de Mon Sep 17 00:00:00 2001 From: Jerome Vouillon Date: Fri, 16 Apr 2010 22:43:01 +0200 Subject: GnuTLS: make the connection phase non-blocking When multi interface is used, the SSL handshake is no longer blocking when GnuTLS is used. --- lib/gtls.c | 212 ++++++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 152 insertions(+), 60 deletions(-) (limited to 'lib/gtls.c') 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; -- cgit v1.2.3