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
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
Daniel (20 March 2006)

View File

@ -11,7 +11,7 @@ Curl and libcurl 7.15.4
This release includes the following changes:
o
o less blocking for the multi interface during SSL connect negotiation
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
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)

View File

@ -1371,13 +1371,6 @@ CURLcode Curl_http_connect(struct connectdata *conn, bool *done)
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) {
/* this is not a followed location, get the original host name */
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);
}
*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;
}
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
* 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_done(struct connectdata *, CURLcode);
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 */
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
* 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 */
}
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
/*

View File

@ -32,6 +32,9 @@ void Curl_free_ssl_config(struct ssl_config_data* sslc);
int Curl_ssl_init(void);
void Curl_ssl_cleanup(void);
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);
/* tell the SSL stuff to close down all open information regarding
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
/* ====================================================== */
CURLcode
Curl_ossl_connect(struct connectdata *conn,
static CURLcode
Curl_ossl_connect_step1(struct connectdata *conn,
int sockindex)
{
CURLcode retcode = CURLE_OK;
struct SessionHandle *data = conn->data;
int err;
long lerr;
int what;
char * str;
SSL_METHOD_QUAL SSL_METHOD *req_method=NULL;
void *ssl_sessionid=NULL;
ASN1_TIME *certdate;
curl_socket_t sockfd = conn->sock[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) {
/* Make funny stuff to get random input */
random_the_seed(data);
@ -1297,134 +1295,150 @@ Curl_ossl_connect(struct connectdata *conn,
return CURLE_SSL_CONNECT_ERROR;
}
while(1) {
int writefd;
int readfd;
long timeout_ms;
long has_passed;
connssl->connecting_state = ssl_connect_2;
return CURLE_OK;
}
/* Find out if any timeout is set. If not, use 300 seconds.
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) {
static CURLcode
Curl_ossl_connect_step2(struct connectdata *conn,
int sockindex, long* timeout_ms)
{
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 */
if(data->set.timeout &&
(data->set.timeout>data->set.connecttimeout))
timeout_ms = data->set.timeout*1000;
else
timeout_ms = data->set.connecttimeout*1000;
}
curlassert(ssl_connect_2 == connssl->connecting_state
|| ssl_connect_2_reading == connssl->connecting_state
|| ssl_connect_2_writing == connssl->connecting_state);
/* Find out if any timeout is set. If not, use 300 seconds.
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
/* no particular time-out has been set */
timeout_ms= DEFAULT_CONNECT_TIMEOUT;
*timeout_ms = data->set.connecttimeout*1000;
}
else
/* no particular time-out has been set */
*timeout_ms= DEFAULT_CONNECT_TIMEOUT;
/* Evaluate in milliseconds how much time that has passed */
has_passed = Curl_tvdiff(Curl_tvnow(), data->progress.t_startsingle);
/* Evaluate in milliseconds how much time that has passed */
has_passed = Curl_tvdiff(Curl_tvnow(), data->progress.t_startsingle);
/* subtract the passed time */
timeout_ms -= has_passed;
/* subtract the passed time */
*timeout_ms -= has_passed;
if(timeout_ms < 0) {
/* a precaution, no need to continue if time already is up */
failf(data, "SSL connection timeout");
return CURLE_OPERATION_TIMEOUTED;
if(*timeout_ms < 0) {
/* a precaution, no need to continue if time already is up */
failf(data, "SSL connection timeout");
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;
writefd = CURL_SOCKET_BAD;
connssl->connecting_state = ssl_connect_2; /* the connection failed,
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
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);
switch(errdetail) {
case 0x1407E086:
/* 1407E086:
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;
}
if(SSL_ERROR_WANT_READ == detail)
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;
/* detail is already set to the SSL error above */
errdetail = ERR_get_error(); /* Gets the earliest error code from the
thread's error queue and removes the
entry. */
switch(errdetail) {
case 0x1407E086:
/* 1407E086:
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);
/* 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;
}
else
/* we have been connected fine, get out of the connect loop */
break;
}
else {
/* we have been connected fine, we're not waiting for anything else. */
connssl->connecting_state = ssl_connect_3;
while(1) {
what = Curl_select(readfd, writefd, (int)timeout_ms);
if(what > 0)
/* 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 */
infof (data, "SSL connection using %s\n",
SSL_get_cipher(connssl->handle));
/* Informational message */
infof (data, "SSL connection using %s\n",
SSL_get_cipher(connssl->handle));
return CURLE_OK;
}
}
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
in the cache! */
SSL_SESSION *ssl_sessionid;
@ -1529,9 +1543,114 @@ Curl_ossl_connect(struct connectdata *conn,
X509_free(connssl->server_cert);
connssl->server_cert = NULL;
connssl->connecting_state = ssl_connect_done;
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 */
int Curl_ossl_send(struct connectdata *conn,
int sockindex,

View File

@ -29,6 +29,9 @@
#include "urldata.h"
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 */
/* tell OpenSSL to close down all open information regarding connections (and
thus session ID caching etc) */

View File

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

View File

@ -128,13 +128,23 @@ struct krb4buffer {
int eof_flag;
};
enum protection_level {
prot_clear,
prot_safe,
prot_confidential,
prot_private
prot_clear,
prot_safe,
prot_confidential,
prot_private
};
#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 ssl_connect_data {
bool use; /* use ssl encrypted communications TRUE/FALSE */
@ -143,6 +153,7 @@ struct ssl_connect_data {
SSL_CTX* ctx;
SSL* handle;
X509* server_cert;
ssl_connect_state connecting_state;
#endif /* USE_SSLEAY */
#ifdef USE_GNUTLS
gnutls_session session;