diff --git a/CHANGES b/CHANGES index c139ade72..9fa1ff566 100644 --- a/CHANGES +++ b/CHANGES @@ -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) diff --git a/RELEASE-NOTES b/RELEASE-NOTES index 7ef611926..780643ec3 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -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) diff --git a/lib/http.c b/lib/http.c index e15054f57..a7db903fd 100644 --- a/lib/http.c +++ b/lib/http.c @@ -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. diff --git a/lib/http.h b/lib/http.h index 599d067c0..bc459f5d3 100644 --- a/lib/http.h +++ b/lib/http.h @@ -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); diff --git a/lib/sslgen.c b/lib/sslgen.c index d7d1259f3..a4c941050 100644 --- a/lib/sslgen.c +++ b/lib/sslgen.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2005, Daniel Stenberg, , et al. + * Copyright (C) 1998 - 2006, Daniel Stenberg, , 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 /* diff --git a/lib/sslgen.h b/lib/sslgen.h index 3e1dfa0a6..6b63c3688 100644 --- a/lib/sslgen.h +++ b/lib/sslgen.h @@ -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) */ diff --git a/lib/ssluse.c b/lib/ssluse.c index 0ec216a3f..7be5a7cad 100644 --- a/lib/ssluse.c +++ b/lib/ssluse.c @@ -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, diff --git a/lib/ssluse.h b/lib/ssluse.h index 00345317d..1bbc2cc1c 100644 --- a/lib/ssluse.h +++ b/lib/ssluse.h @@ -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) */ diff --git a/lib/url.c b/lib/url.c index 9a715c9f0..38a2a27da 100644 --- a/lib/url.c +++ b/lib/url.c @@ -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 diff --git a/lib/urldata.h b/lib/urldata.h index 6cb3729b9..1418f3e92 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -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;