Xavier Bouchoux made the SSL connection non-blocking for the multi interface

(when using OpenSSL).
This commit is contained in:
Daniel Stenberg
2006-03-21 21:54:44 +00:00
parent 15f2647d71
commit 83367f67de
10 changed files with 345 additions and 132 deletions

View File

@@ -7,6 +7,9 @@
Changelog Changelog
Daniel (21 March 2006) Daniel (21 March 2006)
- Xavier Bouchoux made the SSL connection non-blocking for the multi interface
(when using OpenSSL).
- Tor Arntsen fixed the AIX Toolbox RPM spec - Tor Arntsen fixed the AIX Toolbox RPM spec
Daniel (20 March 2006) Daniel (20 March 2006)

View File

@@ -11,7 +11,7 @@ Curl and libcurl 7.15.4
This release includes the following changes: This release includes the following changes:
o o less blocking for the multi interface during SSL connect negotiation
This release includes the following bugfixes: This release includes the following bugfixes:
@@ -27,6 +27,6 @@ Other curl-related news since the previous public release:
This release would not have looked like this without help, code, reports and This release would not have looked like this without help, code, reports and
advice from friends like these: advice from friends like these:
Dan Fandrich, Ilja van Sprundel, David McCreedy, Tor Arntsen Dan Fandrich, Ilja van Sprundel, David McCreedy, Tor Arntsen, Xavier Bouchoux
Thanks! (and sorry if I forgot to mention someone) Thanks! (and sorry if I forgot to mention someone)

View File

@@ -1371,13 +1371,6 @@ CURLcode Curl_http_connect(struct connectdata *conn, bool *done)
return result; return result;
} }
if(conn->protocol & PROT_HTTPS) {
/* perform SSL initialization for this socket */
result = Curl_ssl_connect(conn, FIRSTSOCKET);
if(result)
return result;
}
if(!data->state.this_is_a_follow) { if(!data->state.this_is_a_follow) {
/* this is not a followed location, get the original host name */ /* this is not a followed location, get the original host name */
if (data->state.first_host) if (data->state.first_host)
@@ -1387,11 +1380,68 @@ CURLcode Curl_http_connect(struct connectdata *conn, bool *done)
data->state.first_host = strdup(conn->host.name); data->state.first_host = strdup(conn->host.name);
} }
*done = TRUE; if(conn->protocol & PROT_HTTPS) {
/* perform SSL initialization */
if(data->state.used_interface == Curl_if_multi) {
result = Curl_https_connecting(conn, done);
if(result)
return result;
}
else {
/* BLOCKING */
result = Curl_ssl_connect(conn, FIRSTSOCKET);
if(result)
return result;
*done = TRUE;
}
}
else {
*done = TRUE;
}
return CURLE_OK; return CURLE_OK;
} }
CURLcode Curl_https_connecting(struct connectdata *conn, bool *done)
{
CURLcode result;
curlassert(conn->protocol & PROT_HTTPS);
/* perform SSL initialization for this socket */
result = Curl_ssl_connect_nonblocking(conn, FIRSTSOCKET, done);
if(result)
return result;
return CURLE_OK;
}
#ifdef USE_SSLEAY
CURLcode Curl_https_proto_fdset(struct connectdata *conn,
fd_set *read_fd_set,
fd_set *write_fd_set,
int *max_fdp)
{
if (conn->protocol & PROT_HTTPS) {
struct ssl_connect_data *connssl = &conn->ssl[FIRSTSOCKET];
curl_socket_t sockfd = conn->sock[FIRSTSOCKET];
if (connssl->connecting_state == ssl_connect_2_writing) {
/* write mode */
FD_SET(sockfd, write_fd_set);
if((int)sockfd > *max_fdp)
*max_fdp = (int)sockfd;
}
else if (connssl->connecting_state == ssl_connect_2_reading) {
/* read mode */
FD_SET(sockfd, read_fd_set);
if((int)sockfd > *max_fdp)
*max_fdp = (int)sockfd;
}
}
return CURLE_OK;
}
#endif
/* /*
* Curl_http_done() gets called from Curl_done() after a single HTTP request * Curl_http_done() gets called from Curl_done() after a single HTTP request
* has been performed. * has been performed.

View File

@@ -37,6 +37,11 @@ CURLcode Curl_proxyCONNECT(struct connectdata *conn,
CURLcode Curl_http(struct connectdata *conn, bool *done); CURLcode Curl_http(struct connectdata *conn, bool *done);
CURLcode Curl_http_done(struct connectdata *, CURLcode); CURLcode Curl_http_done(struct connectdata *, CURLcode);
CURLcode Curl_http_connect(struct connectdata *conn, bool *done); CURLcode Curl_http_connect(struct connectdata *conn, bool *done);
CURLcode Curl_https_connecting(struct connectdata *conn, bool *done);
CURLcode Curl_https_proto_fdset(struct connectdata *conn,
fd_set *read_fd_set,
fd_set *write_fd_set,
int *max_fdp);
/* The following functions are defined in http_chunks.c */ /* The following functions are defined in http_chunks.c */
void Curl_httpchunk_init(struct connectdata *conn); void Curl_httpchunk_init(struct connectdata *conn);

View File

@@ -5,7 +5,7 @@
* | (__| |_| | _ <| |___ * | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____| * \___|\___/|_| \_\_____|
* *
* Copyright (C) 1998 - 2005, Daniel Stenberg, <daniel@haxx.se>, et al. * Copyright (C) 1998 - 2006, Daniel Stenberg, <daniel@haxx.se>, et al.
* *
* This software is licensed as described in the file COPYING, which * This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms * you should have received as part of this distribution. The terms
@@ -215,6 +215,23 @@ Curl_ssl_connect(struct connectdata *conn, int sockindex)
#endif /* USE_SSL */ #endif /* USE_SSL */
} }
CURLcode
Curl_ssl_connect_nonblocking(struct connectdata *conn, int sockindex,
bool *done)
{
#if defined(USE_SSL) && defined(USE_SSLEAY)
/* mark this is being ssl enabled from here on. */
conn->ssl[sockindex].use = TRUE;
return Curl_ossl_connect_nonblocking(conn, sockindex, done);
#else
/* not implemented!
fallback to BLOCKING call. */
*done = TRUE;
return Curl_ssl_connect(conn, sockindex);
#endif
}
#ifdef USE_SSL #ifdef USE_SSL
/* /*

View File

@@ -32,6 +32,9 @@ void Curl_free_ssl_config(struct ssl_config_data* sslc);
int Curl_ssl_init(void); int Curl_ssl_init(void);
void Curl_ssl_cleanup(void); void Curl_ssl_cleanup(void);
CURLcode Curl_ssl_connect(struct connectdata *conn, int sockindex); CURLcode Curl_ssl_connect(struct connectdata *conn, int sockindex);
CURLcode Curl_ssl_connect_nonblocking(struct connectdata *conn,
int sockindex,
bool *done);
void Curl_ssl_close(struct connectdata *conn); void Curl_ssl_close(struct connectdata *conn);
/* tell the SSL stuff to close down all open information regarding /* tell the SSL stuff to close down all open information regarding
connections (and thus session ID caching etc) */ connections (and thus session ID caching etc) */

View File

@@ -1116,23 +1116,21 @@ static void ssl_tls_trace(int direction, int ssl_ver, int content_type,
#ifdef USE_SSLEAY #ifdef USE_SSLEAY
/* ====================================================== */ /* ====================================================== */
CURLcode
Curl_ossl_connect(struct connectdata *conn, static CURLcode
Curl_ossl_connect_step1(struct connectdata *conn,
int sockindex) int sockindex)
{ {
CURLcode retcode = CURLE_OK; CURLcode retcode = CURLE_OK;
struct SessionHandle *data = conn->data; struct SessionHandle *data = conn->data;
int err;
long lerr;
int what;
char * str;
SSL_METHOD_QUAL SSL_METHOD *req_method=NULL; SSL_METHOD_QUAL SSL_METHOD *req_method=NULL;
void *ssl_sessionid=NULL; void *ssl_sessionid=NULL;
ASN1_TIME *certdate;
curl_socket_t sockfd = conn->sock[sockindex]; curl_socket_t sockfd = conn->sock[sockindex];
struct ssl_connect_data *connssl = &conn->ssl[sockindex]; struct ssl_connect_data *connssl = &conn->ssl[sockindex];
curlassert(ssl_connect_1 == connssl->connecting_state);
if(!ssl_seeded || data->set.ssl.random_file || data->set.ssl.egdsocket) { if(!ssl_seeded || data->set.ssl.random_file || data->set.ssl.egdsocket) {
/* Make funny stuff to get random input */ /* Make funny stuff to get random input */
random_the_seed(data); random_the_seed(data);
@@ -1297,134 +1295,150 @@ Curl_ossl_connect(struct connectdata *conn,
return CURLE_SSL_CONNECT_ERROR; return CURLE_SSL_CONNECT_ERROR;
} }
while(1) { connssl->connecting_state = ssl_connect_2;
int writefd; return CURLE_OK;
int readfd; }
long timeout_ms;
long has_passed;
/* Find out if any timeout is set. If not, use 300 seconds. static CURLcode
Otherwise, figure out the most strict timeout of the two possible one Curl_ossl_connect_step2(struct connectdata *conn,
and then how much time that has elapsed to know how much time we int sockindex, long* timeout_ms)
allow for the connect call */ {
if(data->set.timeout || data->set.connecttimeout) { struct SessionHandle *data = conn->data;
int err;
long has_passed;
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
/* get the most strict timeout of the ones converted to milliseconds */ curlassert(ssl_connect_2 == connssl->connecting_state
if(data->set.timeout && || ssl_connect_2_reading == connssl->connecting_state
(data->set.timeout>data->set.connecttimeout)) || ssl_connect_2_writing == connssl->connecting_state);
timeout_ms = data->set.timeout*1000;
else /* Find out if any timeout is set. If not, use 300 seconds.
timeout_ms = data->set.connecttimeout*1000; Otherwise, figure out the most strict timeout of the two possible one
} and then how much time that has elapsed to know how much time we
allow for the connect call */
if(data->set.timeout || data->set.connecttimeout) {
/* get the most strict timeout of the ones converted to milliseconds */
if(data->set.timeout &&
(data->set.timeout>data->set.connecttimeout))
*timeout_ms = data->set.timeout*1000;
else else
/* no particular time-out has been set */ *timeout_ms = data->set.connecttimeout*1000;
timeout_ms= DEFAULT_CONNECT_TIMEOUT; }
else
/* no particular time-out has been set */
*timeout_ms= DEFAULT_CONNECT_TIMEOUT;
/* Evaluate in milliseconds how much time that has passed */ /* Evaluate in milliseconds how much time that has passed */
has_passed = Curl_tvdiff(Curl_tvnow(), data->progress.t_startsingle); has_passed = Curl_tvdiff(Curl_tvnow(), data->progress.t_startsingle);
/* subtract the passed time */ /* subtract the passed time */
timeout_ms -= has_passed; *timeout_ms -= has_passed;
if(timeout_ms < 0) { if(*timeout_ms < 0) {
/* a precaution, no need to continue if time already is up */ /* a precaution, no need to continue if time already is up */
failf(data, "SSL connection timeout"); failf(data, "SSL connection timeout");
return CURLE_OPERATION_TIMEOUTED; return CURLE_OPERATION_TIMEOUTED;
}
err = SSL_connect(connssl->handle);
/* 1 is fine
0 is "not successful but was shut down controlled"
<0 is "handshake was not successful, because a fatal error occurred" */
if(1 != err) {
int detail = SSL_get_error(connssl->handle, err);
if(SSL_ERROR_WANT_READ == detail) {
connssl->connecting_state = ssl_connect_2_reading;
return CURLE_OK;
} }
else if(SSL_ERROR_WANT_WRITE == detail) {
connssl->connecting_state = ssl_connect_2_writing;
return CURLE_OK;
}
else {
/* untreated error */
unsigned long errdetail;
char error_buffer[120]; /* OpenSSL documents that this must be at least
120 bytes long. */
CURLcode rc;
const char *cert_problem = NULL;
readfd = CURL_SOCKET_BAD; connssl->connecting_state = ssl_connect_2; /* the connection failed,
writefd = CURL_SOCKET_BAD; we're not waiting for
anything else. */
err = SSL_connect(connssl->handle); errdetail = ERR_get_error(); /* Gets the earliest error code from the
thread's error queue and removes the
entry. */
/* 1 is fine switch(errdetail) {
0 is "not successful but was shut down controlled" case 0x1407E086:
<0 is "handshake was not successful, because a fatal error occurred" */ /* 1407E086:
if(1 != err) { SSL routines:
int detail = SSL_get_error(connssl->handle, err); SSL2_SET_CERTIFICATE:
certificate verify failed */
/* fall-through */
case 0x14090086:
/* 14090086:
SSL routines:
SSL3_GET_SERVER_CERTIFICATE:
certificate verify failed */
cert_problem = "SSL certificate problem, verify that the CA cert is"
" OK. Details:\n";
rc = CURLE_SSL_CACERT;
break;
default:
rc = CURLE_SSL_CONNECT_ERROR;
break;
}
if(SSL_ERROR_WANT_READ == detail) /* detail is already set to the SSL error above */
readfd = sockfd;
else if(SSL_ERROR_WANT_WRITE == detail)
writefd = sockfd;
else {
/* untreated error */
unsigned long errdetail;
char error_buffer[120]; /* OpenSSL documents that this must be at least
120 bytes long. */
CURLcode rc;
const char *cert_problem = NULL;
errdetail = ERR_get_error(); /* Gets the earliest error code from the /* If we e.g. use SSLv2 request-method and the server doesn't like us
thread's error queue and removes the * (RST connection etc.), OpenSSL gives no explanation whatsoever and
entry. */ * the SO_ERROR is also lost.
*/
switch(errdetail) { if (CURLE_SSL_CONNECT_ERROR == rc && errdetail == 0) {
case 0x1407E086: failf(data, "Unknown SSL protocol error in connection to %s:%d ",
/* 1407E086: conn->host.name, conn->port);
SSL routines:
SSL2_SET_CERTIFICATE:
certificate verify failed */
/* fall-through */
case 0x14090086:
/* 14090086:
SSL routines:
SSL3_GET_SERVER_CERTIFICATE:
certificate verify failed */
cert_problem = "SSL certificate problem, verify that the CA cert is"
" OK. Details:\n";
rc = CURLE_SSL_CACERT;
break;
default:
rc = CURLE_SSL_CONNECT_ERROR;
break;
}
/* detail is already set to the SSL error above */
/* If we e.g. use SSLv2 request-method and the server doesn't like us
* (RST connection etc.), OpenSSL gives no explanation whatsoever and
* the SO_ERROR is also lost.
*/
if (CURLE_SSL_CONNECT_ERROR == rc && errdetail == 0) {
failf(data, "Unknown SSL protocol error in connection to %s:%d ",
conn->host.name, conn->port);
return rc;
}
/* Could be a CERT problem */
SSL_strerror(errdetail, error_buffer, sizeof(error_buffer));
failf(data, "%s%s", cert_problem ? cert_problem : "", error_buffer);
return rc; return rc;
} }
/* Could be a CERT problem */
SSL_strerror(errdetail, error_buffer, sizeof(error_buffer));
failf(data, "%s%s", cert_problem ? cert_problem : "", error_buffer);
return rc;
} }
else }
/* we have been connected fine, get out of the connect loop */ else {
break; /* we have been connected fine, we're not waiting for anything else. */
connssl->connecting_state = ssl_connect_3;
while(1) { /* Informational message */
what = Curl_select(readfd, writefd, (int)timeout_ms); infof (data, "SSL connection using %s\n",
if(what > 0) SSL_get_cipher(connssl->handle));
/* reabable or writable, go loop in the outer loop */
break;
else if(0 == what) {
/* timeout */
failf(data, "SSL connection timeout");
return CURLE_OPERATION_TIMEDOUT;
}
else {
/* anything that gets here is fatally bad */
failf(data, "select on SSL socket, errno: %d", Curl_ourerrno());
return CURLE_SSL_CONNECT_ERROR;
}
} /* while()-loop for the select() */
} /* while()-loop for the SSL_connect() */
/* Informational message */ return CURLE_OK;
infof (data, "SSL connection using %s\n", }
SSL_get_cipher(connssl->handle)); }
if(!ssl_sessionid) { static CURLcode
Curl_ossl_connect_step3(struct connectdata *conn,
int sockindex)
{
CURLcode retcode = CURLE_OK;
char * str;
long lerr;
ASN1_TIME *certdate;
void *ssl_sessionid=NULL;
struct SessionHandle *data = conn->data;
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
curlassert(ssl_connect_3 == connssl->connecting_state);
if(Curl_ssl_getsessionid(conn, &ssl_sessionid, NULL)) {
/* Since this is not a cached session ID, then we want to stach this one /* Since this is not a cached session ID, then we want to stach this one
in the cache! */ in the cache! */
SSL_SESSION *ssl_sessionid; SSL_SESSION *ssl_sessionid;
@@ -1529,9 +1543,114 @@ Curl_ossl_connect(struct connectdata *conn,
X509_free(connssl->server_cert); X509_free(connssl->server_cert);
connssl->server_cert = NULL; connssl->server_cert = NULL;
connssl->connecting_state = ssl_connect_done;
return retcode; return retcode;
} }
static CURLcode
Curl_ossl_connect_common(struct connectdata *conn,
int sockindex,
bool nonblocking,
bool *done)
{
CURLcode retcode;
struct SessionHandle *data = conn->data;
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
curl_socket_t sockfd = conn->sock[sockindex];
long timeout_ms;
if (ssl_connect_1==connssl->connecting_state) {
retcode = Curl_ossl_connect_step1(conn, sockindex);
if (retcode)
return retcode;
}
timeout_ms = 0;
while (ssl_connect_2 == connssl->connecting_state ||
ssl_connect_2_reading == connssl->connecting_state ||
ssl_connect_2_writing == connssl->connecting_state) {
/* 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) {
int writefd = ssl_connect_2_writing==
connssl->connecting_state?sockfd:CURL_SOCKET_BAD;
int readfd = ssl_connect_2_reading==
connssl->connecting_state?sockfd:CURL_SOCKET_BAD;
while(1) {
int what = Curl_select(readfd, writefd, nonblocking?0:(int)timeout_ms);
if(what > 0)
/* reabable or writable, go loop in the outer loop */
break;
else if(0 == what) {
if (nonblocking) {
*done = FALSE;
return CURLE_OK;
}
else {
/* timeout */
failf(data, "SSL connection timeout");
return CURLE_OPERATION_TIMEDOUT;
}
}
else {
/* anything that gets here is fatally bad */
failf(data, "select on SSL socket, errno: %d", Curl_ourerrno());
return CURLE_SSL_CONNECT_ERROR;
}
} /* while()-loop for the select() */
}
/* get the timeout from step2 to avoid computing it twice. */
retcode = Curl_ossl_connect_step2(conn, sockindex, &timeout_ms);
if (retcode)
return retcode;
} /* repeat step2 until all transactions are done. */
if (ssl_connect_3==connssl->connecting_state) {
retcode = Curl_ossl_connect_step3(conn, sockindex);
if (retcode)
return retcode;
}
if (ssl_connect_done==connssl->connecting_state) {
*done = TRUE;
}
else {
*done = FALSE;
}
return CURLE_OK;
}
CURLcode
Curl_ossl_connect_nonblocking(struct connectdata *conn,
int sockindex,
bool *done)
{
return Curl_ossl_connect_common(conn, sockindex, TRUE, done);
}
CURLcode
Curl_ossl_connect(struct connectdata *conn,
int sockindex)
{
CURLcode retcode;
bool done = FALSE;
retcode = Curl_ossl_connect_common(conn, sockindex, FALSE, &done);
if (retcode)
return retcode;
curlassert(done);
return CURLE_OK;
}
/* return number of sent (non-SSL) bytes */ /* return number of sent (non-SSL) bytes */
int Curl_ossl_send(struct connectdata *conn, int Curl_ossl_send(struct connectdata *conn,
int sockindex, int sockindex,

View File

@@ -29,6 +29,9 @@
#include "urldata.h" #include "urldata.h"
CURLcode Curl_ossl_connect(struct connectdata *conn, int sockindex); CURLcode Curl_ossl_connect(struct connectdata *conn, int sockindex);
CURLcode Curl_ossl_connect_nonblocking(struct connectdata *conn,
int sockindex,
bool *done);
void Curl_ossl_close(struct connectdata *conn); /* close a SSL connection */ void Curl_ossl_close(struct connectdata *conn); /* close a SSL connection */
/* tell OpenSSL to close down all open information regarding connections (and /* tell OpenSSL to close down all open information regarding connections (and
thus session ID caching etc) */ thus session ID caching etc) */

View File

@@ -2990,6 +2990,8 @@ static CURLcode CreateConnection(struct SessionHandle *data,
conn->curl_do_more = NULL; conn->curl_do_more = NULL;
conn->curl_done = Curl_http_done; conn->curl_done = Curl_http_done;
conn->curl_connect = Curl_http_connect; conn->curl_connect = Curl_http_connect;
conn->curl_connecting = Curl_https_connecting;
conn->curl_proto_fdset = Curl_https_proto_fdset;
#else /* USE_SS */ #else /* USE_SS */
failf(data, LIBCURL_NAME failf(data, LIBCURL_NAME

View File

@@ -128,13 +128,23 @@ struct krb4buffer {
int eof_flag; int eof_flag;
}; };
enum protection_level { enum protection_level {
prot_clear, prot_clear,
prot_safe, prot_safe,
prot_confidential, prot_confidential,
prot_private prot_private
}; };
#endif #endif
/* enum for the nonblocking SSL connection state machine */
typedef enum {
ssl_connect_1,
ssl_connect_2,
ssl_connect_2_reading,
ssl_connect_2_writing,
ssl_connect_3,
ssl_connect_done
} ssl_connect_state;
/* struct for data related to each SSL connection */ /* struct for data related to each SSL connection */
struct ssl_connect_data { struct ssl_connect_data {
bool use; /* use ssl encrypted communications TRUE/FALSE */ bool use; /* use ssl encrypted communications TRUE/FALSE */
@@ -143,6 +153,7 @@ struct ssl_connect_data {
SSL_CTX* ctx; SSL_CTX* ctx;
SSL* handle; SSL* handle;
X509* server_cert; X509* server_cert;
ssl_connect_state connecting_state;
#endif /* USE_SSLEAY */ #endif /* USE_SSLEAY */
#ifdef USE_GNUTLS #ifdef USE_GNUTLS
gnutls_session session; gnutls_session session;