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:
parent
6632d957e7
commit
c2888604d7
214
lib/gtls.c
214
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);
|
||||
|
||||
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;
|
||||
|
@ -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
|
||||
|
18
lib/http.c
18
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
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user