GnuTLS: make the connection phase non-blocking

When multi interface is used, the SSL handshake is no longer
blocking when GnuTLS is used.
This commit is contained in:
Jerome Vouillon 2010-04-16 22:43:01 +02:00 committed by Daniel Stenberg
parent 6632d957e7
commit c2888604d7
4 changed files with 161 additions and 76 deletions

View File

@ -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);
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;
}
}
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,21 +647,89 @@ 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,
int sockindex,
@ -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;

View File

@ -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

View File

@ -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

View File

@ -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;