mirror of
https://github.com/curl/curl.git
synced 2025-01-30 14:22:33 +08:00
connection: shutdown TLS (for FTP) better
This adds connection shutdown infrastructure and first use for FTP. FTP data connections, when not encountering an error, are now shut down in a blocking way with a 2sec timeout. - add cfilter `Curl_cft_shutdown` callback - keep a shutdown start timestamp and timeout at connectdata - provide shutdown timeout default and member in `data->set.shutdowntimeout`. - provide methods for starting, interrogating and clearing shutdown timers - provide `Curl_conn_shutdown_blocking()` to shutdown the `sockindex` filter chain in a blocking way. Use that in FTP. - add `Curl_conn_cf_poll()` to wait for socket events during shutdown of a connection filter chain. This gets the monitoring sockets and events via the filters "adjust_pollset()" methods. This gives correct behaviour when shutting down a TLS connection through a HTTP/2 proxy. - Implement shutdown for all socket filters - for HTTP/2 and h2 proxying to send GOAWAY - for TLS backends to the best of their capabilities - for tcp socket filter to make a final, nonblocking receive to avoid unwanted RST states - add shutdown forwarding to happy eyeballers and https connect ballers when applicable. Closes #13904
This commit is contained in:
parent
7d934267ab
commit
c31041b17e
@ -65,8 +65,6 @@ problems may have been fixed or changed somewhat since this was written.
|
||||
7.2 Implicit FTPS upload timeout
|
||||
7.3 FTP with NOBODY and FAILONERROR
|
||||
7.4 FTP with ACCT
|
||||
7.5 FTPS upload, FileZilla, GnuTLS and close_notify
|
||||
7.11 FTPS upload data loss with TLS 1.3
|
||||
7.12 FTPS directory listing hangs on Windows with Schannel
|
||||
|
||||
9. SFTP and SCP
|
||||
@ -410,30 +408,6 @@ problems may have been fixed or changed somewhat since this was written.
|
||||
thus fails to issue the correct command:
|
||||
https://curl.se/bug/view.cgi?id=635
|
||||
|
||||
7.5 FTPS upload, FileZilla, GnuTLS and close_notify
|
||||
|
||||
An issue where curl does not send the TLS alert close_notify, which triggers
|
||||
the wrath of GnuTLS in FileZilla server, and a FTP reply 426 ECONNABORTED.
|
||||
|
||||
https://github.com/curl/curl/issues/11383
|
||||
|
||||
7.11 FTPS upload data loss with TLS 1.3
|
||||
|
||||
During FTPS upload curl does not attempt to read TLS handshake messages sent
|
||||
after the initial handshake. OpenSSL servers running TLS 1.3 may send such a
|
||||
message. When curl closes the upload connection if unread data has been
|
||||
received (such as a TLS handshake message) then the TCP protocol sends an
|
||||
RST to the server, which may cause the server to discard or truncate the
|
||||
upload if it has not read all sent data yet, and then return an error to curl
|
||||
on the control channel connection.
|
||||
|
||||
Since 7.78.0 this is mostly fixed. curl will do a single read before closing
|
||||
TLS connections (which causes the TLS library to read handshake messages),
|
||||
however there is still possibility of an RST if more messages need to be read
|
||||
or a message arrives after the read but before close (network race condition).
|
||||
|
||||
https://github.com/curl/curl/issues/6149
|
||||
|
||||
7.12 FTPS server compatibility on Windows with Schannel
|
||||
|
||||
FTPS is not widely used with the Schannel TLS backend and so there may be more
|
||||
|
@ -1077,6 +1077,7 @@ struct Curl_cftype Curl_cft_h1_proxy = {
|
||||
cf_h1_proxy_destroy,
|
||||
cf_h1_proxy_connect,
|
||||
cf_h1_proxy_close,
|
||||
Curl_cf_def_shutdown,
|
||||
Curl_cf_http_proxy_get_host,
|
||||
cf_h1_proxy_adjust_pollset,
|
||||
Curl_cf_def_data_pending,
|
||||
|
@ -181,7 +181,9 @@ struct cf_h2_proxy_ctx {
|
||||
int32_t goaway_error;
|
||||
int32_t last_stream_id;
|
||||
BIT(conn_closed);
|
||||
BIT(goaway);
|
||||
BIT(rcvd_goaway);
|
||||
BIT(sent_goaway);
|
||||
BIT(shutdown);
|
||||
BIT(nw_out_blocked);
|
||||
};
|
||||
|
||||
@ -694,7 +696,7 @@ static int proxy_h2_on_frame_recv(nghttp2_session *session,
|
||||
}
|
||||
break;
|
||||
case NGHTTP2_GOAWAY:
|
||||
ctx->goaway = TRUE;
|
||||
ctx->rcvd_goaway = TRUE;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@ -1166,6 +1168,42 @@ static void cf_h2_proxy_destroy(struct Curl_cfilter *cf,
|
||||
}
|
||||
}
|
||||
|
||||
static CURLcode cf_h2_proxy_shutdown(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data, bool *done)
|
||||
{
|
||||
struct cf_h2_proxy_ctx *ctx = cf->ctx;
|
||||
CURLcode result;
|
||||
int rv;
|
||||
|
||||
if(!cf->connected || !ctx->h2 || ctx->shutdown) {
|
||||
*done = TRUE;
|
||||
return CURLE_OK;
|
||||
}
|
||||
|
||||
if(!ctx->sent_goaway) {
|
||||
rv = nghttp2_submit_goaway(ctx->h2, NGHTTP2_FLAG_NONE,
|
||||
0, 0,
|
||||
(const uint8_t *)"shutown", sizeof("shutown"));
|
||||
if(rv) {
|
||||
failf(data, "nghttp2_submit_goaway() failed: %s(%d)",
|
||||
nghttp2_strerror(rv), rv);
|
||||
return CURLE_SEND_ERROR;
|
||||
}
|
||||
ctx->sent_goaway = TRUE;
|
||||
}
|
||||
/* GOAWAY submitted, process egress and ingress until nghttp2 is done. */
|
||||
result = CURLE_OK;
|
||||
if(nghttp2_session_want_write(ctx->h2))
|
||||
result = proxy_h2_progress_egress(cf, data);
|
||||
if(!result && nghttp2_session_want_read(ctx->h2))
|
||||
result = proxy_h2_progress_ingress(cf, data);
|
||||
|
||||
*done = !result && !nghttp2_session_want_write(ctx->h2) &&
|
||||
!nghttp2_session_want_read(ctx->h2);
|
||||
ctx->shutdown = (result || *done);
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool cf_h2_proxy_data_pending(struct Curl_cfilter *cf,
|
||||
const struct Curl_easy *data)
|
||||
{
|
||||
@ -1182,12 +1220,12 @@ static void cf_h2_proxy_adjust_pollset(struct Curl_cfilter *cf,
|
||||
struct easy_pollset *ps)
|
||||
{
|
||||
struct cf_h2_proxy_ctx *ctx = cf->ctx;
|
||||
struct cf_call_data save;
|
||||
curl_socket_t sock = Curl_conn_cf_get_socket(cf, data);
|
||||
bool want_recv, want_send;
|
||||
|
||||
Curl_pollset_check(data, ps, sock, &want_recv, &want_send);
|
||||
if(ctx->h2 && (want_recv || want_send)) {
|
||||
struct cf_call_data save;
|
||||
bool c_exhaust, s_exhaust;
|
||||
|
||||
CF_DATA_SAVE(save, cf, data);
|
||||
@ -1202,6 +1240,14 @@ static void cf_h2_proxy_adjust_pollset(struct Curl_cfilter *cf,
|
||||
Curl_pollset_set(data, ps, sock, want_recv, want_send);
|
||||
CF_DATA_RESTORE(cf, save);
|
||||
}
|
||||
else if(ctx->sent_goaway && !ctx->shutdown) {
|
||||
/* shutdown in progress */
|
||||
CF_DATA_SAVE(save, cf, data);
|
||||
want_send = nghttp2_session_want_write(ctx->h2);
|
||||
want_recv = nghttp2_session_want_read(ctx->h2);
|
||||
Curl_pollset_set(data, ps, sock, want_recv, want_send);
|
||||
CF_DATA_RESTORE(cf, save);
|
||||
}
|
||||
}
|
||||
|
||||
static ssize_t h2_handle_tunnel_close(struct Curl_cfilter *cf,
|
||||
@ -1259,7 +1305,8 @@ static ssize_t tunnel_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
|
||||
}
|
||||
else if(ctx->tunnel.reset ||
|
||||
(ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) ||
|
||||
(ctx->goaway && ctx->last_stream_id < ctx->tunnel.stream_id)) {
|
||||
(ctx->rcvd_goaway &&
|
||||
ctx->last_stream_id < ctx->tunnel.stream_id)) {
|
||||
*err = CURLE_RECV_ERROR;
|
||||
nread = -1;
|
||||
}
|
||||
@ -1537,6 +1584,7 @@ struct Curl_cftype Curl_cft_h2_proxy = {
|
||||
cf_h2_proxy_destroy,
|
||||
cf_h2_proxy_connect,
|
||||
cf_h2_proxy_close,
|
||||
cf_h2_proxy_shutdown,
|
||||
Curl_cf_http_proxy_get_host,
|
||||
cf_h2_proxy_adjust_pollset,
|
||||
cf_h2_proxy_data_pending,
|
||||
|
@ -194,6 +194,7 @@ struct Curl_cftype Curl_cft_haproxy = {
|
||||
cf_haproxy_destroy,
|
||||
cf_haproxy_connect,
|
||||
cf_haproxy_close,
|
||||
Curl_cf_def_shutdown,
|
||||
Curl_cf_def_get_host,
|
||||
cf_haproxy_adjust_pollset,
|
||||
Curl_cf_def_data_pending,
|
||||
|
@ -55,7 +55,8 @@ struct cf_hc_baller {
|
||||
CURLcode result;
|
||||
struct curltime started;
|
||||
int reply_ms;
|
||||
bool enabled;
|
||||
BIT(enabled);
|
||||
BIT(shutdown);
|
||||
};
|
||||
|
||||
static void cf_hc_baller_reset(struct cf_hc_baller *b,
|
||||
@ -322,6 +323,49 @@ out:
|
||||
return result;
|
||||
}
|
||||
|
||||
static CURLcode cf_hc_shutdown(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data, bool *done)
|
||||
{
|
||||
struct cf_hc_ctx *ctx = cf->ctx;
|
||||
struct cf_hc_baller *ballers[2];
|
||||
size_t i;
|
||||
CURLcode result = CURLE_OK;
|
||||
|
||||
DEBUGASSERT(data);
|
||||
if(cf->connected) {
|
||||
*done = TRUE;
|
||||
return CURLE_OK;
|
||||
}
|
||||
|
||||
/* shutdown all ballers that have not done so already. If one fails,
|
||||
* continue shutting down others until all are shutdown. */
|
||||
ballers[0] = &ctx->h3_baller;
|
||||
ballers[1] = &ctx->h21_baller;
|
||||
for(i = 0; i < sizeof(ballers)/sizeof(ballers[0]); i++) {
|
||||
struct cf_hc_baller *b = ballers[i];
|
||||
bool bdone = FALSE;
|
||||
if(!cf_hc_baller_is_active(b) || b->shutdown)
|
||||
continue;
|
||||
b->result = b->cf->cft->do_shutdown(b->cf, data, &bdone);
|
||||
if(b->result || bdone)
|
||||
b->shutdown = TRUE; /* treat a failed shutdown as done */
|
||||
}
|
||||
|
||||
*done = TRUE;
|
||||
for(i = 0; i < sizeof(ballers)/sizeof(ballers[0]); i++) {
|
||||
if(ballers[i] && !ballers[i]->shutdown)
|
||||
*done = FALSE;
|
||||
}
|
||||
if(*done) {
|
||||
for(i = 0; i < sizeof(ballers)/sizeof(ballers[0]); i++) {
|
||||
if(ballers[i] && ballers[i]->result)
|
||||
result = ballers[i]->result;
|
||||
}
|
||||
}
|
||||
CURL_TRC_CF(data, cf, "shutdown -> %d, done=%d", result, *done);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void cf_hc_adjust_pollset(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
struct easy_pollset *ps)
|
||||
@ -434,6 +478,7 @@ struct Curl_cftype Curl_cft_http_connect = {
|
||||
cf_hc_destroy,
|
||||
cf_hc_connect,
|
||||
cf_hc_close,
|
||||
cf_hc_shutdown,
|
||||
Curl_cf_def_get_host,
|
||||
cf_hc_adjust_pollset,
|
||||
cf_hc_data_pending,
|
||||
|
@ -1010,6 +1010,30 @@ static void cf_socket_close(struct Curl_cfilter *cf, struct Curl_easy *data)
|
||||
cf->connected = FALSE;
|
||||
}
|
||||
|
||||
static CURLcode cf_socket_shutdown(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
bool *done)
|
||||
{
|
||||
if(cf->connected) {
|
||||
struct cf_socket_ctx *ctx = cf->ctx;
|
||||
|
||||
CURL_TRC_CF(data, cf, "cf_socket_shutdown(%" CURL_FORMAT_SOCKET_T
|
||||
")", ctx->sock);
|
||||
/* On TCP, and when the socket looks well and non-blocking mode
|
||||
* can be enabled, receive dangling bytes before close to avoid
|
||||
* entering RST states unnecessarily. */
|
||||
if(ctx->sock != CURL_SOCKET_BAD &&
|
||||
ctx->transport == TRNSPRT_TCP &&
|
||||
(curlx_nonblock(ctx->sock, TRUE) >= 0)) {
|
||||
unsigned char buf[1024];
|
||||
(void)sread(ctx->sock, buf, sizeof(buf));
|
||||
}
|
||||
cf_socket_close(cf, data);
|
||||
}
|
||||
*done = TRUE;
|
||||
return CURLE_OK;
|
||||
}
|
||||
|
||||
static void cf_socket_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
|
||||
{
|
||||
struct cf_socket_ctx *ctx = cf->ctx;
|
||||
@ -1729,6 +1753,7 @@ struct Curl_cftype Curl_cft_tcp = {
|
||||
cf_socket_destroy,
|
||||
cf_tcp_connect,
|
||||
cf_socket_close,
|
||||
cf_socket_shutdown,
|
||||
cf_socket_get_host,
|
||||
cf_socket_adjust_pollset,
|
||||
cf_socket_data_pending,
|
||||
@ -1872,6 +1897,7 @@ struct Curl_cftype Curl_cft_udp = {
|
||||
cf_socket_destroy,
|
||||
cf_udp_connect,
|
||||
cf_socket_close,
|
||||
cf_socket_shutdown,
|
||||
cf_socket_get_host,
|
||||
cf_socket_adjust_pollset,
|
||||
cf_socket_data_pending,
|
||||
@ -1923,6 +1949,7 @@ struct Curl_cftype Curl_cft_unix = {
|
||||
cf_socket_destroy,
|
||||
cf_tcp_connect,
|
||||
cf_socket_close,
|
||||
cf_socket_shutdown,
|
||||
cf_socket_get_host,
|
||||
cf_socket_adjust_pollset,
|
||||
cf_socket_data_pending,
|
||||
@ -1987,6 +2014,7 @@ struct Curl_cftype Curl_cft_tcp_accept = {
|
||||
cf_socket_destroy,
|
||||
cf_tcp_accept_connect,
|
||||
cf_socket_close,
|
||||
cf_socket_shutdown,
|
||||
cf_socket_get_host, /* TODO: not accurate */
|
||||
cf_socket_adjust_pollset,
|
||||
cf_socket_data_pending,
|
||||
|
100
lib/cfilters.c
100
lib/cfilters.c
@ -55,6 +55,15 @@ void Curl_cf_def_close(struct Curl_cfilter *cf, struct Curl_easy *data)
|
||||
}
|
||||
#endif
|
||||
|
||||
CURLcode Curl_cf_def_shutdown(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data, bool *done)
|
||||
{
|
||||
(void)cf;
|
||||
(void)data;
|
||||
*done = TRUE;
|
||||
return CURLE_OK;
|
||||
}
|
||||
|
||||
static void conn_report_connect_stats(struct Curl_easy *data,
|
||||
struct connectdata *conn);
|
||||
|
||||
@ -168,6 +177,61 @@ void Curl_conn_close(struct Curl_easy *data, int index)
|
||||
}
|
||||
}
|
||||
|
||||
CURLcode Curl_conn_shutdown_blocking(struct Curl_easy *data, int sockindex)
|
||||
{
|
||||
struct Curl_cfilter *cf;
|
||||
CURLcode result = CURLE_OK;
|
||||
|
||||
DEBUGASSERT(data->conn);
|
||||
/* it is valid to call that without filters being present */
|
||||
cf = data->conn->cfilter[sockindex];
|
||||
if(cf) {
|
||||
timediff_t timeout_ms;
|
||||
bool done = FALSE;
|
||||
int what;
|
||||
|
||||
DEBUGF(infof(data, "shutdown start on%s connection",
|
||||
sockindex? " secondary" : ""));
|
||||
Curl_shutdown_start(data, sockindex, NULL);
|
||||
while(cf) {
|
||||
while(!done && !result) {
|
||||
result = cf->cft->do_shutdown(cf, data, &done);
|
||||
if(!result && !done) {
|
||||
timeout_ms = Curl_shutdown_timeleft(data->conn, sockindex, NULL);
|
||||
if(timeout_ms < 0) {
|
||||
failf(data, "SSL shutdown timeout");
|
||||
result = CURLE_OPERATION_TIMEDOUT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
what = Curl_conn_cf_poll(cf, data, timeout_ms);
|
||||
if(what < 0) {
|
||||
/* fatal error */
|
||||
failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO);
|
||||
result = CURLE_RECV_ERROR;
|
||||
goto out;
|
||||
}
|
||||
else if(0 == what) {
|
||||
failf(data, "SSL shutdown timeout");
|
||||
result = CURLE_OPERATION_TIMEDOUT;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(result)
|
||||
break;
|
||||
CURL_TRC_CF(data, cf, "shut down successfully");
|
||||
cf = cf->next;
|
||||
done = FALSE;
|
||||
}
|
||||
Curl_shutdown_clear(data, sockindex);
|
||||
DEBUGF(infof(data, "shutdown done on%s connection -> %d",
|
||||
sockindex? " secondary" : "", result));
|
||||
}
|
||||
out:
|
||||
return result;
|
||||
}
|
||||
|
||||
ssize_t Curl_cf_recv(struct Curl_easy *data, int num, char *buf,
|
||||
size_t len, CURLcode *code)
|
||||
{
|
||||
@ -464,6 +528,42 @@ void Curl_conn_adjust_pollset(struct Curl_easy *data,
|
||||
}
|
||||
}
|
||||
|
||||
int Curl_conn_cf_poll(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
timediff_t timeout_ms)
|
||||
{
|
||||
struct easy_pollset ps;
|
||||
struct pollfd pfds[MAX_SOCKSPEREASYHANDLE];
|
||||
unsigned int i, npfds = 0;
|
||||
|
||||
DEBUGASSERT(cf);
|
||||
DEBUGASSERT(data);
|
||||
DEBUGASSERT(data->conn);
|
||||
memset(&ps, 0, sizeof(ps));
|
||||
memset(pfds, 0, sizeof(pfds));
|
||||
|
||||
Curl_conn_cf_adjust_pollset(cf, data, &ps);
|
||||
DEBUGASSERT(ps.num <= MAX_SOCKSPEREASYHANDLE);
|
||||
for(i = 0; i < ps.num; ++i) {
|
||||
short events = 0;
|
||||
if(ps.actions[i] & CURL_POLL_IN) {
|
||||
events |= POLLIN;
|
||||
}
|
||||
if(ps.actions[i] & CURL_POLL_OUT) {
|
||||
events |= POLLOUT;
|
||||
}
|
||||
if(events) {
|
||||
pfds[npfds].fd = ps.sockets[i];
|
||||
pfds[npfds].events = events;
|
||||
++npfds;
|
||||
}
|
||||
}
|
||||
|
||||
if(!npfds)
|
||||
DEBUGF(infof(data, "no sockets to poll!"));
|
||||
return Curl_poll(pfds, npfds, timeout_ms);
|
||||
}
|
||||
|
||||
void Curl_conn_get_host(struct Curl_easy *data, int sockindex,
|
||||
const char **phost, const char **pdisplay_host,
|
||||
int *pport)
|
||||
|
@ -24,6 +24,7 @@
|
||||
*
|
||||
***************************************************************************/
|
||||
|
||||
#include "timediff.h"
|
||||
|
||||
struct Curl_cfilter;
|
||||
struct Curl_easy;
|
||||
@ -36,9 +37,17 @@ struct connectdata;
|
||||
typedef void Curl_cft_destroy_this(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data);
|
||||
|
||||
/* Callback to close the connection immediately. */
|
||||
typedef void Curl_cft_close(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data);
|
||||
|
||||
/* Callback to close the connection filter gracefully, non-blocking.
|
||||
* Implementations MUST NOT chain calls to cf->next.
|
||||
*/
|
||||
typedef CURLcode Curl_cft_shutdown(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
bool *done);
|
||||
|
||||
typedef CURLcode Curl_cft_connect(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
bool blocking, bool *done);
|
||||
@ -194,6 +203,7 @@ struct Curl_cftype {
|
||||
Curl_cft_destroy_this *destroy; /* destroy resources of this cf */
|
||||
Curl_cft_connect *do_connect; /* establish connection */
|
||||
Curl_cft_close *do_close; /* close conn */
|
||||
Curl_cft_shutdown *do_shutdown; /* shutdown conn */
|
||||
Curl_cft_get_host *get_host; /* host filter talks to */
|
||||
Curl_cft_adjust_pollset *adjust_pollset; /* adjust transfer poll set */
|
||||
Curl_cft_data_pending *has_data_pending;/* conn has data pending */
|
||||
@ -244,6 +254,8 @@ CURLcode Curl_cf_def_conn_keep_alive(struct Curl_cfilter *cf,
|
||||
CURLcode Curl_cf_def_query(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
int query, int *pres1, void *pres2);
|
||||
CURLcode Curl_cf_def_shutdown(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data, bool *done);
|
||||
|
||||
/**
|
||||
* Create a new filter instance, unattached to the filter chain.
|
||||
@ -371,6 +383,12 @@ bool Curl_conn_is_multiplex(struct connectdata *conn, int sockindex);
|
||||
*/
|
||||
void Curl_conn_close(struct Curl_easy *data, int sockindex);
|
||||
|
||||
/**
|
||||
* Shutdown the connection at `sockindex` blocking with timeout
|
||||
* from `data->set.shutdowntimeout`, default DEFAULT_SHUTDOWN_TIMEOUT_MS
|
||||
*/
|
||||
CURLcode Curl_conn_shutdown_blocking(struct Curl_easy *data, int sockindex);
|
||||
|
||||
/**
|
||||
* Return if data is pending in some connection filter at chain
|
||||
* `sockindex` for connection `data->conn`.
|
||||
@ -402,6 +420,15 @@ void Curl_conn_cf_adjust_pollset(struct Curl_cfilter *cf,
|
||||
void Curl_conn_adjust_pollset(struct Curl_easy *data,
|
||||
struct easy_pollset *ps);
|
||||
|
||||
/**
|
||||
* Curl_poll() the filter chain at `cf` with timeout `timeout_ms`.
|
||||
* Returns 0 on timeout, negative on error or number of sockets
|
||||
* with requested poll events.
|
||||
*/
|
||||
int Curl_conn_cf_poll(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
timediff_t timeout_ms);
|
||||
|
||||
/**
|
||||
* Receive data through the filter chain at `sockindex` for connection
|
||||
* `data->conn`. Copy at most `len` bytes into `buf`. Return the
|
||||
|
@ -142,6 +142,43 @@ timediff_t Curl_timeleft(struct Curl_easy *data,
|
||||
return (ctimeleft_ms < timeleft_ms)? ctimeleft_ms : timeleft_ms;
|
||||
}
|
||||
|
||||
void Curl_shutdown_start(struct Curl_easy *data, int sockindex,
|
||||
struct curltime *nowp)
|
||||
{
|
||||
struct curltime now;
|
||||
|
||||
DEBUGASSERT(data->conn);
|
||||
if(!nowp) {
|
||||
now = Curl_now();
|
||||
nowp = &now;
|
||||
}
|
||||
data->conn->shutdown.start[sockindex] = *nowp;
|
||||
data->conn->shutdown.timeout_ms = (data->set.shutdowntimeout > 0) ?
|
||||
data->set.shutdowntimeout : DEFAULT_SHUTDOWN_TIMEOUT_MS;
|
||||
}
|
||||
|
||||
timediff_t Curl_shutdown_timeleft(struct connectdata *conn, int sockindex,
|
||||
struct curltime *nowp)
|
||||
{
|
||||
struct curltime now;
|
||||
|
||||
if(!conn->shutdown.start[sockindex].tv_sec || !conn->shutdown.timeout_ms)
|
||||
return 0; /* not started or no limits */
|
||||
|
||||
if(!nowp) {
|
||||
now = Curl_now();
|
||||
nowp = &now;
|
||||
}
|
||||
return conn->shutdown.timeout_ms -
|
||||
Curl_timediff(*nowp, conn->shutdown.start[sockindex]);
|
||||
}
|
||||
|
||||
void Curl_shutdown_clear(struct Curl_easy *data, int sockindex)
|
||||
{
|
||||
struct curltime *pt = &data->conn->shutdown.start[sockindex];
|
||||
memset(pt, 0, sizeof(*pt));
|
||||
}
|
||||
|
||||
/* Copies connection info into the transfer handle to make it available when
|
||||
the transfer handle is no longer associated with the connection. */
|
||||
void Curl_persistconninfo(struct Curl_easy *data, struct connectdata *conn,
|
||||
@ -358,6 +395,7 @@ struct eyeballer {
|
||||
BIT(has_started); /* attempts have started */
|
||||
BIT(is_done); /* out of addresses/time */
|
||||
BIT(connected); /* cf has connected */
|
||||
BIT(shutdown); /* cf has shutdown */
|
||||
BIT(inconclusive); /* connect was not a hard failure, we
|
||||
* might talk to a restarting server */
|
||||
};
|
||||
@ -857,6 +895,46 @@ static void cf_he_ctx_clear(struct Curl_cfilter *cf, struct Curl_easy *data)
|
||||
ctx->winner = NULL;
|
||||
}
|
||||
|
||||
static CURLcode cf_he_shutdown(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data, bool *done)
|
||||
{
|
||||
struct cf_he_ctx *ctx = cf->ctx;
|
||||
size_t i;
|
||||
CURLcode result = CURLE_OK;
|
||||
|
||||
DEBUGASSERT(data);
|
||||
if(cf->connected) {
|
||||
*done = TRUE;
|
||||
return CURLE_OK;
|
||||
}
|
||||
|
||||
/* shutdown all ballers that have not done so already. If one fails,
|
||||
* continue shutting down others until all are shutdown. */
|
||||
for(i = 0; i < ARRAYSIZE(ctx->baller); i++) {
|
||||
struct eyeballer *baller = ctx->baller[i];
|
||||
bool bdone = FALSE;
|
||||
if(!baller || !baller->cf || baller->shutdown)
|
||||
continue;
|
||||
baller->result = baller->cf->cft->do_shutdown(baller->cf, data, &bdone);
|
||||
if(baller->result || bdone)
|
||||
baller->shutdown = TRUE; /* treat a failed shutdown as done */
|
||||
}
|
||||
|
||||
*done = TRUE;
|
||||
for(i = 0; i < ARRAYSIZE(ctx->baller); i++) {
|
||||
if(ctx->baller[i] && !ctx->baller[i]->shutdown)
|
||||
*done = FALSE;
|
||||
}
|
||||
if(*done) {
|
||||
for(i = 0; i < ARRAYSIZE(ctx->baller); i++) {
|
||||
if(ctx->baller[i] && ctx->baller[i]->result)
|
||||
result = ctx->baller[i]->result;
|
||||
}
|
||||
}
|
||||
CURL_TRC_CF(data, cf, "shutdown -> %d, done=%d", result, *done);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void cf_he_adjust_pollset(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
struct easy_pollset *ps)
|
||||
@ -1052,6 +1130,7 @@ struct Curl_cftype Curl_cft_happy_eyeballs = {
|
||||
cf_he_destroy,
|
||||
cf_he_connect,
|
||||
cf_he_close,
|
||||
cf_he_shutdown,
|
||||
Curl_cf_def_get_host,
|
||||
cf_he_adjust_pollset,
|
||||
cf_he_data_pending,
|
||||
@ -1316,6 +1395,7 @@ struct Curl_cftype Curl_cft_setup = {
|
||||
cf_setup_destroy,
|
||||
cf_setup_connect,
|
||||
cf_setup_close,
|
||||
Curl_cf_def_shutdown,
|
||||
Curl_cf_def_get_host,
|
||||
Curl_cf_def_adjust_pollset,
|
||||
Curl_cf_def_data_pending,
|
||||
|
@ -40,6 +40,18 @@ timediff_t Curl_timeleft(struct Curl_easy *data,
|
||||
|
||||
#define DEFAULT_CONNECT_TIMEOUT 300000 /* milliseconds == five minutes */
|
||||
|
||||
#define DEFAULT_SHUTDOWN_TIMEOUT_MS (2 * 1000)
|
||||
|
||||
void Curl_shutdown_start(struct Curl_easy *data, int sockindex,
|
||||
struct curltime *nowp);
|
||||
|
||||
/* return how much time there's left to shutdown the connection at
|
||||
* sockindex. */
|
||||
timediff_t Curl_shutdown_timeleft(struct connectdata *conn, int sockindex,
|
||||
struct curltime *nowp);
|
||||
|
||||
void Curl_shutdown_clear(struct Curl_easy *data, int sockindex);
|
||||
|
||||
/*
|
||||
* Used to extract socket and connectdata struct for the most recent
|
||||
* transfer on the given Curl_easy.
|
||||
|
23
lib/ftp.c
23
lib/ftp.c
@ -290,12 +290,15 @@ const struct Curl_handler Curl_handler_ftps = {
|
||||
};
|
||||
#endif
|
||||
|
||||
static void close_secondarysocket(struct Curl_easy *data,
|
||||
struct connectdata *conn)
|
||||
static void close_secondarysocket(struct Curl_easy *data, bool premature)
|
||||
{
|
||||
if(!premature) {
|
||||
CURL_TRC_FTP(data, "[%s] shutting down DATA connection", FTP_DSTATE(data));
|
||||
Curl_conn_shutdown_blocking(data, SECONDARYSOCKET);
|
||||
}
|
||||
CURL_TRC_FTP(data, "[%s] closing DATA connection", FTP_DSTATE(data));
|
||||
Curl_conn_close(data, SECONDARYSOCKET);
|
||||
Curl_conn_cf_discard_all(data, conn, SECONDARYSOCKET);
|
||||
Curl_conn_cf_discard_all(data, data->conn, SECONDARYSOCKET);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -475,7 +478,7 @@ static CURLcode AcceptServerConnect(struct Curl_easy *data)
|
||||
Curl_set_in_callback(data, false);
|
||||
|
||||
if(error) {
|
||||
close_secondarysocket(data, conn);
|
||||
close_secondarysocket(data, TRUE);
|
||||
return CURLE_ABORTED_BY_CALLBACK;
|
||||
}
|
||||
}
|
||||
@ -2980,7 +2983,13 @@ static CURLcode ftp_statemachine(struct Curl_easy *data,
|
||||
case FTP_CCC:
|
||||
if(ftpcode < 500) {
|
||||
/* First shut down the SSL layer (note: this call will block) */
|
||||
result = Curl_ssl_cfilter_remove(data, FIRSTSOCKET);
|
||||
/* This has only been tested on the proftpd server, and the mod_tls
|
||||
* code sends a close notify alert without waiting for a close notify
|
||||
* alert in response. Thus we wait for a close notify alert from the
|
||||
* server, but we do not send one. Let's hope other servers do
|
||||
* the same... */
|
||||
result = Curl_ssl_cfilter_remove(data, FIRSTSOCKET,
|
||||
(data->set.ftp_ccc == CURLFTPSSL_CCC_ACTIVE));
|
||||
|
||||
if(result)
|
||||
failf(data, "Failed to clear the command channel (CCC)");
|
||||
@ -3457,7 +3466,7 @@ static CURLcode ftp_done(struct Curl_easy *data, CURLcode status,
|
||||
}
|
||||
}
|
||||
|
||||
close_secondarysocket(data, conn);
|
||||
close_secondarysocket(data, result != CURLE_OK);
|
||||
}
|
||||
|
||||
if(!result && (ftp->transfer == PPTRANSFER_BODY) && ftpc->ctl_valid &&
|
||||
@ -4425,7 +4434,7 @@ static CURLcode ftp_dophase_done(struct Curl_easy *data, bool connected)
|
||||
CURLcode result = ftp_do_more(data, &completed);
|
||||
|
||||
if(result) {
|
||||
close_secondarysocket(data, conn);
|
||||
close_secondarysocket(data, TRUE);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
71
lib/http2.c
71
lib/http2.c
@ -133,11 +133,14 @@ struct cf_h2_ctx {
|
||||
struct Curl_hash streams; /* hash of `data->id` to `h2_stream_ctx` */
|
||||
size_t drain_total; /* sum of all stream's UrlState drain */
|
||||
uint32_t max_concurrent_streams;
|
||||
uint32_t goaway_error;
|
||||
int32_t last_stream_id;
|
||||
uint32_t goaway_error; /* goaway error code from server */
|
||||
int32_t remote_max_sid; /* max id processed by server */
|
||||
int32_t local_max_sid; /* max id processed by us */
|
||||
BIT(conn_closed);
|
||||
BIT(goaway);
|
||||
BIT(rcvd_goaway);
|
||||
BIT(sent_goaway);
|
||||
BIT(enable_push);
|
||||
BIT(shutdown);
|
||||
BIT(nw_out_blocked);
|
||||
};
|
||||
|
||||
@ -437,7 +440,7 @@ static CURLcode cf_h2_ctx_init(struct Curl_cfilter *cf,
|
||||
Curl_bufq_initp(&ctx->outbufq, &ctx->stream_bufcp, H2_NW_SEND_CHUNKS, 0);
|
||||
Curl_dyn_init(&ctx->scratch, CURL_MAX_HTTP_HEADER);
|
||||
Curl_hash_offt_init(&ctx->streams, 63, h2_stream_hash_free);
|
||||
ctx->last_stream_id = 2147483647;
|
||||
ctx->remote_max_sid = 2147483647;
|
||||
|
||||
rc = nghttp2_session_callbacks_new(&cbs);
|
||||
if(rc) {
|
||||
@ -967,6 +970,10 @@ static int push_promise(struct Curl_cfilter *cf,
|
||||
rv = CURL_PUSH_DENY;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* success, remember max stream id processed */
|
||||
if(newstream->id > ctx->local_max_sid)
|
||||
ctx->local_max_sid = newstream->id;
|
||||
}
|
||||
else {
|
||||
CURL_TRC_CF(data, cf, "Got PUSH_PROMISE, ignore it");
|
||||
@ -1252,12 +1259,12 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
|
||||
break;
|
||||
}
|
||||
case NGHTTP2_GOAWAY:
|
||||
ctx->goaway = TRUE;
|
||||
ctx->rcvd_goaway = TRUE;
|
||||
ctx->goaway_error = frame->goaway.error_code;
|
||||
ctx->last_stream_id = frame->goaway.last_stream_id;
|
||||
ctx->remote_max_sid = frame->goaway.last_stream_id;
|
||||
if(data) {
|
||||
infof(data, "received GOAWAY, error=%u, last_stream=%u",
|
||||
ctx->goaway_error, ctx->last_stream_id);
|
||||
ctx->goaway_error, ctx->remote_max_sid);
|
||||
Curl_multi_connchanged(data->multi);
|
||||
}
|
||||
break;
|
||||
@ -1878,7 +1885,7 @@ static ssize_t stream_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
|
||||
}
|
||||
else if(stream->reset ||
|
||||
(ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) ||
|
||||
(ctx->goaway && ctx->last_stream_id < stream->id)) {
|
||||
(ctx->rcvd_goaway && ctx->remote_max_sid < stream->id)) {
|
||||
CURL_TRC_CF(data, cf, "[%d] returning ERR", stream->id);
|
||||
*err = data->req.bytecount? CURLE_PARTIAL_FILE : CURLE_HTTP2;
|
||||
nread = -1;
|
||||
@ -2358,6 +2365,7 @@ static void cf_h2_adjust_pollset(struct Curl_cfilter *cf,
|
||||
struct easy_pollset *ps)
|
||||
{
|
||||
struct cf_h2_ctx *ctx = cf->ctx;
|
||||
struct cf_call_data save;
|
||||
curl_socket_t sock;
|
||||
bool want_recv, want_send;
|
||||
|
||||
@ -2368,7 +2376,6 @@ static void cf_h2_adjust_pollset(struct Curl_cfilter *cf,
|
||||
Curl_pollset_check(data, ps, sock, &want_recv, &want_send);
|
||||
if(want_recv || want_send) {
|
||||
struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data);
|
||||
struct cf_call_data save;
|
||||
bool c_exhaust, s_exhaust;
|
||||
|
||||
CF_DATA_SAVE(save, cf, data);
|
||||
@ -2383,6 +2390,14 @@ static void cf_h2_adjust_pollset(struct Curl_cfilter *cf,
|
||||
Curl_pollset_set(data, ps, sock, want_recv, want_send);
|
||||
CF_DATA_RESTORE(cf, save);
|
||||
}
|
||||
else if(ctx->sent_goaway && !ctx->shutdown) {
|
||||
/* shutdown in progress */
|
||||
CF_DATA_SAVE(save, cf, data);
|
||||
want_send = nghttp2_session_want_write(ctx->h2);
|
||||
want_recv = nghttp2_session_want_read(ctx->h2);
|
||||
Curl_pollset_set(data, ps, sock, want_recv, want_send);
|
||||
CF_DATA_RESTORE(cf, save);
|
||||
}
|
||||
}
|
||||
|
||||
static CURLcode cf_h2_connect(struct Curl_cfilter *cf,
|
||||
@ -2446,6 +2461,7 @@ static void cf_h2_close(struct Curl_cfilter *cf, struct Curl_easy *data)
|
||||
CF_DATA_SAVE(save, cf, data);
|
||||
cf_h2_ctx_clear(ctx);
|
||||
CF_DATA_RESTORE(cf, save);
|
||||
cf->connected = FALSE;
|
||||
}
|
||||
if(cf->next)
|
||||
cf->next->cft->do_close(cf->next, data);
|
||||
@ -2462,6 +2478,42 @@ static void cf_h2_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
|
||||
}
|
||||
}
|
||||
|
||||
static CURLcode cf_h2_shutdown(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data, bool *done)
|
||||
{
|
||||
struct cf_h2_ctx *ctx = cf->ctx;
|
||||
CURLcode result;
|
||||
int rv;
|
||||
|
||||
if(!cf->connected || !ctx->h2 || ctx->shutdown) {
|
||||
*done = TRUE;
|
||||
return CURLE_OK;
|
||||
}
|
||||
|
||||
if(!ctx->sent_goaway) {
|
||||
rv = nghttp2_submit_goaway(ctx->h2, NGHTTP2_FLAG_NONE,
|
||||
ctx->local_max_sid, 0,
|
||||
(const uint8_t *)"shutown", sizeof("shutown"));
|
||||
if(rv) {
|
||||
failf(data, "nghttp2_submit_goaway() failed: %s(%d)",
|
||||
nghttp2_strerror(rv), rv);
|
||||
return CURLE_SEND_ERROR;
|
||||
}
|
||||
ctx->sent_goaway = TRUE;
|
||||
}
|
||||
/* GOAWAY submitted, process egress and ingress until nghttp2 is done. */
|
||||
result = CURLE_OK;
|
||||
if(nghttp2_session_want_write(ctx->h2))
|
||||
result = h2_progress_egress(cf, data);
|
||||
if(!result && nghttp2_session_want_read(ctx->h2))
|
||||
result = h2_progress_ingress(cf, data, 0);
|
||||
|
||||
*done = !result && !nghttp2_session_want_write(ctx->h2) &&
|
||||
!nghttp2_session_want_read(ctx->h2);
|
||||
ctx->shutdown = (result || *done);
|
||||
return result;
|
||||
}
|
||||
|
||||
static CURLcode http2_data_pause(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
bool pause)
|
||||
@ -2632,6 +2684,7 @@ struct Curl_cftype Curl_cft_nghttp2 = {
|
||||
cf_h2_destroy,
|
||||
cf_h2_connect,
|
||||
cf_h2_close,
|
||||
cf_h2_shutdown,
|
||||
Curl_cf_def_get_host,
|
||||
cf_h2_adjust_pollset,
|
||||
cf_h2_data_pending,
|
||||
|
@ -298,6 +298,7 @@ struct Curl_cftype Curl_cft_http_proxy = {
|
||||
http_proxy_cf_destroy,
|
||||
http_proxy_cf_connect,
|
||||
http_proxy_cf_close,
|
||||
Curl_cf_def_shutdown,
|
||||
Curl_cf_http_proxy_get_host,
|
||||
Curl_cf_def_adjust_pollset,
|
||||
Curl_cf_def_data_pending,
|
||||
|
@ -1249,6 +1249,7 @@ struct Curl_cftype Curl_cft_socks_proxy = {
|
||||
socks_proxy_cf_destroy,
|
||||
socks_proxy_cf_connect,
|
||||
socks_proxy_cf_close,
|
||||
Curl_cf_def_shutdown,
|
||||
socks_cf_get_host,
|
||||
socks_cf_adjust_pollset,
|
||||
Curl_cf_def_data_pending,
|
||||
|
@ -847,6 +847,10 @@ struct connectdata {
|
||||
Curl_recv *recv[2];
|
||||
Curl_send *send[2];
|
||||
struct Curl_cfilter *cfilter[2]; /* connection filters */
|
||||
struct {
|
||||
struct curltime start[2]; /* when filter shutdown started */
|
||||
unsigned int timeout_ms; /* 0 means no timeout */
|
||||
} shutdown;
|
||||
|
||||
struct ssl_primary_config ssl_config;
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
@ -1614,9 +1618,10 @@ struct UserDefined {
|
||||
void *progress_client; /* pointer to pass to the progress callback */
|
||||
void *ioctl_client; /* pointer to pass to the ioctl callback */
|
||||
unsigned int timeout; /* ms, 0 means no timeout */
|
||||
unsigned int connecttimeout; /* ms, 0 means no timeout */
|
||||
unsigned int connecttimeout; /* ms, 0 means default timeout */
|
||||
unsigned int happy_eyeballs_timeout; /* ms, 0 is a valid value */
|
||||
unsigned int server_response_timeout; /* ms, 0 means no timeout */
|
||||
unsigned int shutdowntimeout; /* ms, 0 means default timeout */
|
||||
long maxage_conn; /* in seconds, max idle time to allow a connection that
|
||||
is to be reused */
|
||||
long maxlifetime_conn; /* in seconds, max time since creation to allow a
|
||||
|
@ -1038,6 +1038,7 @@ struct Curl_cftype Curl_cft_http3 = {
|
||||
cf_msh3_destroy,
|
||||
cf_msh3_connect,
|
||||
cf_msh3_close,
|
||||
Curl_cf_def_shutdown,
|
||||
Curl_cf_def_get_host,
|
||||
cf_msh3_adjust_pollset,
|
||||
cf_msh3_data_pending,
|
||||
|
@ -2438,6 +2438,7 @@ struct Curl_cftype Curl_cft_http3 = {
|
||||
cf_ngtcp2_destroy,
|
||||
cf_ngtcp2_connect,
|
||||
cf_ngtcp2_close,
|
||||
Curl_cf_def_shutdown,
|
||||
Curl_cf_def_get_host,
|
||||
cf_ngtcp2_adjust_pollset,
|
||||
cf_ngtcp2_data_pending,
|
||||
|
@ -2252,6 +2252,7 @@ struct Curl_cftype Curl_cft_http3 = {
|
||||
cf_osslq_destroy,
|
||||
cf_osslq_connect,
|
||||
cf_osslq_close,
|
||||
Curl_cf_def_shutdown,
|
||||
Curl_cf_def_get_host,
|
||||
cf_osslq_adjust_pollset,
|
||||
cf_osslq_data_pending,
|
||||
|
@ -1580,6 +1580,7 @@ struct Curl_cftype Curl_cft_http3 = {
|
||||
cf_quiche_destroy,
|
||||
cf_quiche_connect,
|
||||
cf_quiche_close,
|
||||
Curl_cf_def_shutdown,
|
||||
Curl_cf_def_get_host,
|
||||
cf_quiche_adjust_pollset,
|
||||
cf_quiche_data_pending,
|
||||
|
@ -63,6 +63,7 @@ struct bearssl_ssl_backend_data {
|
||||
bool active;
|
||||
/* size of pending write, yet to be flushed */
|
||||
size_t pending_write;
|
||||
BIT(sent_shutdown);
|
||||
};
|
||||
|
||||
struct cafile_parser {
|
||||
@ -1069,6 +1070,41 @@ static void *bearssl_get_internals(struct ssl_connect_data *connssl,
|
||||
return &backend->ctx;
|
||||
}
|
||||
|
||||
static CURLcode bearssl_shutdown(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
bool send_shutdown, bool *done)
|
||||
{
|
||||
struct ssl_connect_data *connssl = cf->ctx;
|
||||
struct bearssl_ssl_backend_data *backend =
|
||||
(struct bearssl_ssl_backend_data *)connssl->backend;
|
||||
CURLcode result;
|
||||
|
||||
DEBUGASSERT(backend);
|
||||
if(!backend->active || connssl->shutdown) {
|
||||
*done = TRUE;
|
||||
return CURLE_OK;
|
||||
}
|
||||
|
||||
*done = FALSE;
|
||||
if(!backend->sent_shutdown) {
|
||||
(void)send_shutdown; /* unknown how to suppress our close notify */
|
||||
br_ssl_engine_close(&backend->ctx.eng);
|
||||
backend->sent_shutdown = TRUE;
|
||||
}
|
||||
|
||||
result = bearssl_run_until(cf, data, BR_SSL_CLOSED);
|
||||
if(result == CURLE_OK) {
|
||||
*done = TRUE;
|
||||
}
|
||||
else if(result == CURLE_AGAIN)
|
||||
result = CURLE_OK;
|
||||
else
|
||||
CURL_TRC_CF(data, cf, "shutdown error: %d", result);
|
||||
|
||||
connssl->shutdown = (result || *done);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void bearssl_close(struct Curl_cfilter *cf, struct Curl_easy *data)
|
||||
{
|
||||
struct ssl_connect_data *connssl = cf->ctx;
|
||||
@ -1079,9 +1115,11 @@ static void bearssl_close(struct Curl_cfilter *cf, struct Curl_easy *data)
|
||||
DEBUGASSERT(backend);
|
||||
|
||||
if(backend->active) {
|
||||
if(!connssl->shutdown) {
|
||||
bool done;
|
||||
bearssl_shutdown(cf, data, TRUE, &done);
|
||||
}
|
||||
backend->active = FALSE;
|
||||
br_ssl_engine_close(&backend->ctx.eng);
|
||||
(void)bearssl_run_until(cf, data, BR_SSL_CLOSED);
|
||||
}
|
||||
if(backend->anchors) {
|
||||
for(i = 0; i < backend->anchors_len; ++i)
|
||||
@ -1112,7 +1150,7 @@ const struct Curl_ssl Curl_ssl_bearssl = {
|
||||
Curl_none_cleanup, /* cleanup */
|
||||
bearssl_version, /* version */
|
||||
Curl_none_check_cxn, /* check_cxn */
|
||||
Curl_none_shutdown, /* shutdown */
|
||||
bearssl_shutdown, /* shutdown */
|
||||
bearssl_data_pending, /* data_pending */
|
||||
bearssl_random, /* random */
|
||||
Curl_none_cert_status_request, /* cert_status_request */
|
||||
|
164
lib/vtls/gtls.c
164
lib/vtls/gtls.c
@ -1799,6 +1799,74 @@ static ssize_t gtls_send(struct Curl_cfilter *cf,
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* This function is called to shut down the SSL layer but keep the
|
||||
* socket open (CCC - Clear Command Channel)
|
||||
*/
|
||||
static CURLcode gtls_shutdown(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
bool send_shutdown, bool *done)
|
||||
{
|
||||
struct ssl_connect_data *connssl = cf->ctx;
|
||||
struct gtls_ssl_backend_data *backend =
|
||||
(struct gtls_ssl_backend_data *)connssl->backend;
|
||||
char buf[1024];
|
||||
CURLcode result = CURLE_OK;
|
||||
ssize_t nread;
|
||||
size_t i;
|
||||
|
||||
DEBUGASSERT(backend);
|
||||
if(!backend->gtls.session || connssl->shutdown) {
|
||||
*done = TRUE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
connssl->io_need = CURL_SSL_IO_NEED_NONE;
|
||||
*done = FALSE;
|
||||
|
||||
if(!backend->gtls.sent_shutdown) {
|
||||
/* do this only once */
|
||||
backend->gtls.sent_shutdown = TRUE;
|
||||
if(send_shutdown) {
|
||||
int ret = gnutls_bye(backend->gtls.session, GNUTLS_SHUT_RDWR);
|
||||
if(ret != GNUTLS_E_SUCCESS) {
|
||||
CURL_TRC_CF(data, cf, "SSL shutdown, gnutls_bye error: '%s'(%d)",
|
||||
gnutls_strerror((int)ret), (int)ret);
|
||||
result = CURLE_RECV_ERROR;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* SSL should now have started the shutdown from our side. Since it
|
||||
* was not complete, we are lacking the close notify from the server. */
|
||||
for(i = 0; i < 10; ++i) {
|
||||
nread = gnutls_record_recv(backend->gtls.session, buf, sizeof(buf));
|
||||
if(nread <= 0)
|
||||
break;
|
||||
}
|
||||
if(nread > 0) {
|
||||
/* still data coming in? */
|
||||
}
|
||||
else if(nread == 0) {
|
||||
/* We got the close notify alert and are done. */
|
||||
*done = TRUE;
|
||||
}
|
||||
else if((nread == GNUTLS_E_AGAIN) || (nread == GNUTLS_E_INTERRUPTED)) {
|
||||
connssl->io_need = gnutls_record_get_direction(backend->gtls.session)?
|
||||
CURL_SSL_IO_NEED_SEND : CURL_SSL_IO_NEED_RECV;
|
||||
}
|
||||
else {
|
||||
CURL_TRC_CF(data, cf, "SSL shutdown, error: '%s'(%d)",
|
||||
gnutls_strerror((int)nread), (int)nread);
|
||||
result = CURLE_RECV_ERROR;
|
||||
}
|
||||
|
||||
out:
|
||||
connssl->shutdown = (result || *done);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void gtls_close(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data)
|
||||
{
|
||||
@ -1810,16 +1878,10 @@ static void gtls_close(struct Curl_cfilter *cf,
|
||||
DEBUGASSERT(backend);
|
||||
CURL_TRC_CF(data, cf, "close");
|
||||
if(backend->gtls.session) {
|
||||
char buf[32];
|
||||
/* Maybe the server has already sent a close notify alert.
|
||||
Read it to avoid an RST on the TCP connection. */
|
||||
CURL_TRC_CF(data, cf, "close, try receive before bye");
|
||||
(void)gnutls_record_recv(backend->gtls.session, buf, sizeof(buf));
|
||||
CURL_TRC_CF(data, cf, "close, send bye");
|
||||
gnutls_bye(backend->gtls.session, GNUTLS_SHUT_RDWR);
|
||||
/* try one last receive */
|
||||
CURL_TRC_CF(data, cf, "close, receive after bye");
|
||||
(void)gnutls_record_recv(backend->gtls.session, buf, sizeof(buf));
|
||||
if(!connssl->shutdown) {
|
||||
bool done;
|
||||
gtls_shutdown(cf, data, TRUE, &done);
|
||||
}
|
||||
gnutls_deinit(backend->gtls.session);
|
||||
backend->gtls.session = NULL;
|
||||
}
|
||||
@ -1834,88 +1896,6 @@ static void gtls_close(struct Curl_cfilter *cf,
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* This function is called to shut down the SSL layer but keep the
|
||||
* socket open (CCC - Clear Command Channel)
|
||||
*/
|
||||
static int gtls_shutdown(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data)
|
||||
{
|
||||
struct ssl_connect_data *connssl = cf->ctx;
|
||||
struct gtls_ssl_backend_data *backend =
|
||||
(struct gtls_ssl_backend_data *)connssl->backend;
|
||||
int retval = 0;
|
||||
|
||||
DEBUGASSERT(backend);
|
||||
|
||||
#ifndef CURL_DISABLE_FTP
|
||||
/* This has only been tested on the proftpd server, and the mod_tls code
|
||||
sends a close notify alert without waiting for a close notify alert in
|
||||
response. Thus we wait for a close notify alert from the server, but
|
||||
we do not send one. Let's hope other servers do the same... */
|
||||
|
||||
if(data->set.ftp_ccc == CURLFTPSSL_CCC_ACTIVE)
|
||||
gnutls_bye(backend->gtls.session, GNUTLS_SHUT_WR);
|
||||
#endif
|
||||
|
||||
if(backend->gtls.session) {
|
||||
ssize_t result;
|
||||
bool done = FALSE;
|
||||
char buf[120];
|
||||
|
||||
while(!done && !connssl->peer_closed) {
|
||||
int what = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data),
|
||||
SSL_SHUTDOWN_TIMEOUT);
|
||||
if(what > 0) {
|
||||
/* Something to read, let's do it and hope that it is the close
|
||||
notify alert from the server */
|
||||
result = gnutls_record_recv(backend->gtls.session,
|
||||
buf, sizeof(buf));
|
||||
switch(result) {
|
||||
case 0:
|
||||
/* This is the expected response. There was no data but only
|
||||
the close notify alert */
|
||||
done = TRUE;
|
||||
break;
|
||||
case GNUTLS_E_AGAIN:
|
||||
case GNUTLS_E_INTERRUPTED:
|
||||
infof(data, "GNUTLS_E_AGAIN || GNUTLS_E_INTERRUPTED");
|
||||
break;
|
||||
default:
|
||||
retval = -1;
|
||||
done = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if(0 == what) {
|
||||
/* timeout */
|
||||
failf(data, "SSL shutdown timeout");
|
||||
done = TRUE;
|
||||
}
|
||||
else {
|
||||
/* anything that gets here is fatally bad */
|
||||
failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO);
|
||||
retval = -1;
|
||||
done = TRUE;
|
||||
}
|
||||
}
|
||||
gnutls_deinit(backend->gtls.session);
|
||||
}
|
||||
|
||||
#ifdef USE_GNUTLS_SRP
|
||||
{
|
||||
struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
|
||||
if(ssl_config->primary.username)
|
||||
gnutls_srp_free_client_credentials(backend->gtls.srp_client_cred);
|
||||
}
|
||||
#endif
|
||||
|
||||
backend->gtls.session = NULL;
|
||||
Curl_gtls_shared_creds_free(&backend->gtls.shared_creds);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static ssize_t gtls_recv(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
char *buf,
|
||||
|
@ -66,6 +66,7 @@ struct gtls_ctx {
|
||||
gnutls_srp_client_credentials_t srp_client_cred;
|
||||
#endif
|
||||
CURLcode io_result; /* result of last IO cfilter operation */
|
||||
BIT(sent_shutdown);
|
||||
};
|
||||
|
||||
typedef CURLcode Curl_gtls_ctx_setup_cb(struct Curl_cfilter *cf,
|
||||
|
@ -111,6 +111,7 @@ struct mbed_ssl_backend_data {
|
||||
#endif
|
||||
int *ciphersuites;
|
||||
BIT(initialized); /* mbedtls_ssl_context is initialized */
|
||||
BIT(sent_shutdown);
|
||||
};
|
||||
|
||||
/* apply threading? */
|
||||
@ -1198,23 +1199,108 @@ static void mbedtls_close_all(struct Curl_easy *data)
|
||||
(void)data;
|
||||
}
|
||||
|
||||
static CURLcode mbedtls_shutdown(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
bool send_shutdown, bool *done)
|
||||
{
|
||||
struct ssl_connect_data *connssl = cf->ctx;
|
||||
struct mbed_ssl_backend_data *backend =
|
||||
(struct mbed_ssl_backend_data *)connssl->backend;
|
||||
unsigned char buf[1024];
|
||||
CURLcode result = CURLE_OK;
|
||||
int ret;
|
||||
size_t i;
|
||||
|
||||
DEBUGASSERT(backend);
|
||||
|
||||
if(!backend->initialized || connssl->shutdown) {
|
||||
*done = TRUE;
|
||||
return CURLE_OK;
|
||||
}
|
||||
|
||||
connssl->io_need = CURL_SSL_IO_NEED_NONE;
|
||||
*done = FALSE;
|
||||
|
||||
if(!backend->sent_shutdown) {
|
||||
/* do this only once */
|
||||
backend->sent_shutdown = TRUE;
|
||||
if(send_shutdown) {
|
||||
ret = mbedtls_ssl_close_notify(&backend->ssl);
|
||||
switch(ret) {
|
||||
case 0: /* we sent it, receive from the server */
|
||||
break;
|
||||
case MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY: /* server also closed */
|
||||
*done = TRUE;
|
||||
goto out;
|
||||
case MBEDTLS_ERR_SSL_WANT_READ:
|
||||
connssl->io_need = CURL_SSL_IO_NEED_RECV;
|
||||
goto out;
|
||||
case MBEDTLS_ERR_SSL_WANT_WRITE:
|
||||
connssl->io_need = CURL_SSL_IO_NEED_SEND;
|
||||
goto out;
|
||||
default:
|
||||
CURL_TRC_CF(data, cf, "mbedtls_shutdown error -0x%04X", -ret);
|
||||
result = CURLE_RECV_ERROR;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* SSL should now have started the shutdown from our side. Since it
|
||||
* was not complete, we are lacking the close notify from the server. */
|
||||
for(i = 0; i < 10; ++i) {
|
||||
ret = mbedtls_ssl_read(&backend->ssl, buf, sizeof(buf));
|
||||
/* This seems to be a bug in mbedTLS TLSv1.3 where it reports
|
||||
* WANT_READ, but has not encountered an EAGAIN. */
|
||||
if(ret == MBEDTLS_ERR_SSL_WANT_READ)
|
||||
ret = mbedtls_ssl_read(&backend->ssl, buf, sizeof(buf));
|
||||
#ifdef TLS13_SUPPORT
|
||||
if(ret == MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET)
|
||||
continue;
|
||||
#endif
|
||||
if(ret <= 0)
|
||||
break;
|
||||
}
|
||||
|
||||
if(ret > 0) {
|
||||
/* still data coming in? */
|
||||
CURL_TRC_CF(data, cf, "mbedtls_shutdown, still getting data");
|
||||
}
|
||||
else if(ret == 0 || (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY)) {
|
||||
/* We got the close notify alert and are done. */
|
||||
CURL_TRC_CF(data, cf, "mbedtls_shutdown done");
|
||||
*done = TRUE;
|
||||
}
|
||||
else if(ret == MBEDTLS_ERR_SSL_WANT_READ) {
|
||||
CURL_TRC_CF(data, cf, "mbedtls_shutdown, need RECV");
|
||||
connssl->io_need = CURL_SSL_IO_NEED_RECV;
|
||||
}
|
||||
else if(ret == MBEDTLS_ERR_SSL_WANT_WRITE) {
|
||||
CURL_TRC_CF(data, cf, "mbedtls_shutdown, need SEND");
|
||||
connssl->io_need = CURL_SSL_IO_NEED_SEND;
|
||||
}
|
||||
else {
|
||||
CURL_TRC_CF(data, cf, "mbedtls_shutdown error -0x%04X", -ret);
|
||||
result = CURLE_RECV_ERROR;
|
||||
}
|
||||
|
||||
out:
|
||||
connssl->shutdown = (result || *done);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void mbedtls_close(struct Curl_cfilter *cf, struct Curl_easy *data)
|
||||
{
|
||||
struct ssl_connect_data *connssl = cf->ctx;
|
||||
struct mbed_ssl_backend_data *backend =
|
||||
(struct mbed_ssl_backend_data *)connssl->backend;
|
||||
|
||||
(void)data;
|
||||
DEBUGASSERT(backend);
|
||||
if(backend->initialized) {
|
||||
char buf[32];
|
||||
int ret;
|
||||
|
||||
/* Maybe the server has already sent a close notify alert.
|
||||
Read it to avoid an RST on the TCP connection. */
|
||||
(void)mbedtls_ssl_read(&backend->ssl, (unsigned char *)buf, sizeof(buf));
|
||||
ret = mbedtls_ssl_close_notify(&backend->ssl);
|
||||
CURL_TRC_CF(data, cf, "close_notify() -> %04x", -ret);
|
||||
if(!connssl->shutdown) {
|
||||
bool done;
|
||||
mbedtls_shutdown(cf, data, TRUE, &done);
|
||||
}
|
||||
|
||||
mbedtls_pk_free(&backend->pk);
|
||||
mbedtls_x509_crt_free(&backend->clicert);
|
||||
@ -1535,7 +1621,7 @@ const struct Curl_ssl Curl_ssl_mbedtls = {
|
||||
mbedtls_cleanup, /* cleanup */
|
||||
mbedtls_version, /* version */
|
||||
Curl_none_check_cxn, /* check_cxn */
|
||||
Curl_none_shutdown, /* shutdown */
|
||||
mbedtls_shutdown, /* shutdown */
|
||||
mbedtls_data_pending, /* data_pending */
|
||||
mbedtls_random, /* random */
|
||||
Curl_none_cert_status_request, /* cert_status_request */
|
||||
|
@ -1870,6 +1870,100 @@ static struct curl_slist *ossl_engines_list(struct Curl_easy *data)
|
||||
return list;
|
||||
}
|
||||
|
||||
static CURLcode ossl_shutdown(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
bool send_shutdown, bool *done)
|
||||
{
|
||||
struct ssl_connect_data *connssl = cf->ctx;
|
||||
struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend;
|
||||
CURLcode result = CURLE_OK;
|
||||
char buf[1024];
|
||||
int nread, err;
|
||||
unsigned long sslerr;
|
||||
|
||||
DEBUGASSERT(octx);
|
||||
if(!octx->ssl || connssl->shutdown) {
|
||||
*done = TRUE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
connssl->io_need = CURL_SSL_IO_NEED_NONE;
|
||||
*done = FALSE;
|
||||
if(!(SSL_get_shutdown(octx->ssl) & SSL_SENT_SHUTDOWN)) {
|
||||
/* We have not started the shutdown from our side yet. Check
|
||||
* if the server already sent us one. */
|
||||
ERR_clear_error();
|
||||
nread = SSL_read(octx->ssl, buf, (int)sizeof(buf));
|
||||
err = SSL_get_error(octx->ssl, nread);
|
||||
if(!nread && err == SSL_ERROR_ZERO_RETURN) {
|
||||
bool input_pending;
|
||||
/* Yes, it did. */
|
||||
if(!send_shutdown) {
|
||||
connssl->shutdown = TRUE;
|
||||
CURL_TRC_CF(data, cf, "SSL shutdown received, not sending");
|
||||
goto out;
|
||||
}
|
||||
else if(!cf->next->cft->is_alive(cf->next, data, &input_pending)) {
|
||||
/* Server closed the connection after its closy notify. It
|
||||
* seems not interested to see our close notify, so do not
|
||||
* send it. We are done. */
|
||||
connssl->peer_closed = TRUE;
|
||||
connssl->shutdown = TRUE;
|
||||
CURL_TRC_CF(data, cf, "peer closed connection");
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(send_shutdown && SSL_shutdown(octx->ssl) == 1) {
|
||||
CURL_TRC_CF(data, cf, "SSL shutdown finished");
|
||||
*done = TRUE;
|
||||
goto out;
|
||||
}
|
||||
else {
|
||||
size_t i;
|
||||
/* SSL should now have started the shutdown from our side. Since it
|
||||
* was not complete, we are lacking the close notify from the server. */
|
||||
for(i = 0; i < 10; ++i) {
|
||||
ERR_clear_error();
|
||||
nread = SSL_read(octx->ssl, buf, (int)sizeof(buf));
|
||||
if(nread <= 0)
|
||||
break;
|
||||
}
|
||||
err = SSL_get_error(octx->ssl, nread);
|
||||
switch(err) {
|
||||
case SSL_ERROR_ZERO_RETURN: /* no more data */
|
||||
CURL_TRC_CF(data, cf, "SSL shutdown received");
|
||||
*done = TRUE;
|
||||
break;
|
||||
case SSL_ERROR_NONE: /* just did not get anything */
|
||||
case SSL_ERROR_WANT_READ:
|
||||
/* SSL has send its notify and now wants to read the reply
|
||||
* from the server. We are not really interested in that. */
|
||||
CURL_TRC_CF(data, cf, "SSL shutdown sent, want receive");
|
||||
connssl->io_need = CURL_SSL_IO_NEED_RECV;
|
||||
break;
|
||||
case SSL_ERROR_WANT_WRITE:
|
||||
CURL_TRC_CF(data, cf, "SSL shutdown send blocked");
|
||||
connssl->io_need = CURL_SSL_IO_NEED_SEND;
|
||||
break;
|
||||
default:
|
||||
sslerr = ERR_get_error();
|
||||
CURL_TRC_CF(data, cf, "SSL shutdown, error: '%s', errno %d",
|
||||
(sslerr ?
|
||||
ossl_strerror(sslerr, buf, sizeof(buf)) :
|
||||
SSL_ERROR_to_str(err)),
|
||||
SOCKERRNO);
|
||||
result = CURLE_RECV_ERROR;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
connssl->shutdown = (result || *done);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void ossl_close(struct Curl_cfilter *cf, struct Curl_easy *data)
|
||||
{
|
||||
struct ssl_connect_data *connssl = cf->ctx;
|
||||
@ -1879,72 +1973,12 @@ static void ossl_close(struct Curl_cfilter *cf, struct Curl_easy *data)
|
||||
DEBUGASSERT(octx);
|
||||
|
||||
if(octx->ssl) {
|
||||
/* Send the TLS shutdown if we are still connected *and* if
|
||||
* the peer did not already close the connection. */
|
||||
if(cf->next && cf->next->connected && !connssl->peer_closed) {
|
||||
char buf[1024];
|
||||
int nread, err;
|
||||
unsigned long sslerr;
|
||||
|
||||
/* Maybe the server has already sent a close notify alert.
|
||||
Read it to avoid an RST on the TCP connection. */
|
||||
ERR_clear_error();
|
||||
nread = SSL_read(octx->ssl, buf, (int)sizeof(buf));
|
||||
err = SSL_get_error(octx->ssl, nread);
|
||||
if(!nread && err == SSL_ERROR_ZERO_RETURN) {
|
||||
CURLcode result;
|
||||
ssize_t n;
|
||||
size_t blen = sizeof(buf);
|
||||
CURL_TRC_CF(data, cf, "peer has shutdown TLS");
|
||||
/* SSL_read() will not longer touch the socket, let's receive
|
||||
* directly from the next filter to see if the underlying
|
||||
* connection has also been closed. */
|
||||
n = Curl_conn_cf_recv(cf->next, data, buf, blen, &result);
|
||||
if(!n) {
|
||||
connssl->peer_closed = TRUE;
|
||||
CURL_TRC_CF(data, cf, "peer closed connection");
|
||||
}
|
||||
}
|
||||
ERR_clear_error();
|
||||
if(connssl->peer_closed) {
|
||||
/* As the peer closed, we do not expect it to read anything more we
|
||||
* may send. It may be harmful, leading to TCP RST and delaying
|
||||
* a lingering close. Just leave. */
|
||||
CURL_TRC_CF(data, cf, "not from sending TLS shutdown on "
|
||||
"connection closed by peer");
|
||||
}
|
||||
else if(SSL_shutdown(octx->ssl) == 1) {
|
||||
CURL_TRC_CF(data, cf, "SSL shutdown finished");
|
||||
}
|
||||
else {
|
||||
nread = SSL_read(octx->ssl, buf, (int)sizeof(buf));
|
||||
err = SSL_get_error(octx->ssl, nread);
|
||||
switch(err) {
|
||||
case SSL_ERROR_NONE: /* this is not an error */
|
||||
case SSL_ERROR_ZERO_RETURN: /* no more data */
|
||||
CURL_TRC_CF(data, cf, "SSL shutdown, EOF from server");
|
||||
break;
|
||||
case SSL_ERROR_WANT_READ:
|
||||
/* SSL has send its notify and now wants to read the reply
|
||||
* from the server. We are not really interested in that. */
|
||||
CURL_TRC_CF(data, cf, "SSL shutdown sent");
|
||||
break;
|
||||
case SSL_ERROR_WANT_WRITE:
|
||||
CURL_TRC_CF(data, cf, "SSL shutdown send blocked");
|
||||
break;
|
||||
default:
|
||||
sslerr = ERR_get_error();
|
||||
CURL_TRC_CF(data, cf, "SSL shutdown, error: '%s', errno %d",
|
||||
(sslerr ?
|
||||
ossl_strerror(sslerr, buf, sizeof(buf)) :
|
||||
SSL_ERROR_to_str(err)),
|
||||
SOCKERRNO);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ERR_clear_error();
|
||||
SSL_set_connect_state(octx->ssl);
|
||||
/* Send the TLS shutdown if have not done so already and are still
|
||||
* connected *and* if the peer did not already close the connection. */
|
||||
if(cf->connected && !connssl->shutdown &&
|
||||
cf->next && cf->next->connected && !connssl->peer_closed) {
|
||||
bool done;
|
||||
(void)ossl_shutdown(cf, data, TRUE, &done);
|
||||
}
|
||||
|
||||
SSL_free(octx->ssl);
|
||||
@ -1961,114 +1995,6 @@ static void ossl_close(struct Curl_cfilter *cf, struct Curl_easy *data)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This function is called to shut down the SSL layer but keep the
|
||||
* socket open (CCC - Clear Command Channel)
|
||||
*/
|
||||
static int ossl_shutdown(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data)
|
||||
{
|
||||
int retval = 0;
|
||||
struct ssl_connect_data *connssl = cf->ctx;
|
||||
char buf[256]; /* We will use this for the OpenSSL error buffer, so it has
|
||||
to be at least 256 bytes long. */
|
||||
unsigned long sslerror;
|
||||
int nread;
|
||||
int buffsize;
|
||||
int err;
|
||||
bool done = FALSE;
|
||||
struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend;
|
||||
int loop = 10;
|
||||
|
||||
DEBUGASSERT(octx);
|
||||
|
||||
#ifndef CURL_DISABLE_FTP
|
||||
/* This has only been tested on the proftpd server, and the mod_tls code
|
||||
sends a close notify alert without waiting for a close notify alert in
|
||||
response. Thus we wait for a close notify alert from the server, but
|
||||
we do not send one. Let's hope other servers do the same... */
|
||||
|
||||
if(data->set.ftp_ccc == CURLFTPSSL_CCC_ACTIVE)
|
||||
(void)SSL_shutdown(octx->ssl);
|
||||
#endif
|
||||
|
||||
if(octx->ssl) {
|
||||
buffsize = (int)sizeof(buf);
|
||||
while(!done && loop--) {
|
||||
int what = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data),
|
||||
SSL_SHUTDOWN_TIMEOUT);
|
||||
if(what > 0) {
|
||||
ERR_clear_error();
|
||||
|
||||
/* Something to read, let's do it and hope that it is the close
|
||||
notify alert from the server */
|
||||
nread = SSL_read(octx->ssl, buf, buffsize);
|
||||
err = SSL_get_error(octx->ssl, nread);
|
||||
|
||||
switch(err) {
|
||||
case SSL_ERROR_NONE: /* this is not an error */
|
||||
case SSL_ERROR_ZERO_RETURN: /* no more data */
|
||||
/* This is the expected response. There was no data but only
|
||||
the close notify alert */
|
||||
done = TRUE;
|
||||
break;
|
||||
case SSL_ERROR_WANT_READ:
|
||||
/* there's data pending, re-invoke SSL_read() */
|
||||
infof(data, "SSL_ERROR_WANT_READ");
|
||||
break;
|
||||
case SSL_ERROR_WANT_WRITE:
|
||||
/* SSL wants a write. Really odd. Let's bail out. */
|
||||
infof(data, "SSL_ERROR_WANT_WRITE");
|
||||
done = TRUE;
|
||||
break;
|
||||
default:
|
||||
/* openssl/ssl.h says "look at error stack/return value/errno" */
|
||||
sslerror = ERR_get_error();
|
||||
failf(data, OSSL_PACKAGE " SSL_read on shutdown: %s, errno %d",
|
||||
(sslerror ?
|
||||
ossl_strerror(sslerror, buf, sizeof(buf)) :
|
||||
SSL_ERROR_to_str(err)),
|
||||
SOCKERRNO);
|
||||
done = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if(0 == what) {
|
||||
/* timeout */
|
||||
failf(data, "SSL shutdown timeout");
|
||||
done = TRUE;
|
||||
}
|
||||
else {
|
||||
/* anything that gets here is fatally bad */
|
||||
failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO);
|
||||
retval = -1;
|
||||
done = TRUE;
|
||||
}
|
||||
} /* while()-loop for the select() */
|
||||
|
||||
if(data->set.verbose) {
|
||||
#ifdef HAVE_SSL_GET_SHUTDOWN
|
||||
switch(SSL_get_shutdown(octx->ssl)) {
|
||||
case SSL_SENT_SHUTDOWN:
|
||||
infof(data, "SSL_get_shutdown() returned SSL_SENT_SHUTDOWN");
|
||||
break;
|
||||
case SSL_RECEIVED_SHUTDOWN:
|
||||
infof(data, "SSL_get_shutdown() returned SSL_RECEIVED_SHUTDOWN");
|
||||
break;
|
||||
case SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN:
|
||||
infof(data, "SSL_get_shutdown() returned SSL_SENT_SHUTDOWN|"
|
||||
"SSL_RECEIVED__SHUTDOWN");
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
SSL_free(octx->ssl);
|
||||
octx->ssl = NULL;
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void ossl_session_free(void *sessionid, size_t idsize)
|
||||
{
|
||||
/* free the ID */
|
||||
|
@ -48,6 +48,7 @@ struct rustls_ssl_backend_data
|
||||
struct rustls_connection *conn;
|
||||
size_t plain_out_buffered;
|
||||
BIT(data_in_pending);
|
||||
BIT(sent_shutdown);
|
||||
};
|
||||
|
||||
/* For a given rustls_result error code, return the best-matching CURLcode. */
|
||||
@ -727,22 +728,90 @@ cr_get_internals(struct ssl_connect_data *connssl,
|
||||
return &backend->conn;
|
||||
}
|
||||
|
||||
static CURLcode
|
||||
cr_shutdown(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
bool send_shutdown, bool *done)
|
||||
{
|
||||
struct ssl_connect_data *connssl = cf->ctx;
|
||||
struct rustls_ssl_backend_data *backend =
|
||||
(struct rustls_ssl_backend_data *)connssl->backend;
|
||||
CURLcode result = CURLE_OK;
|
||||
ssize_t nwritten, nread;
|
||||
char buf[1024];
|
||||
size_t i;
|
||||
|
||||
DEBUGASSERT(backend);
|
||||
if(!backend->conn || connssl->shutdown) {
|
||||
*done = TRUE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
connssl->io_need = CURL_SSL_IO_NEED_NONE;
|
||||
*done = FALSE;
|
||||
|
||||
if(!backend->sent_shutdown) {
|
||||
/* do this only once */
|
||||
backend->sent_shutdown = TRUE;
|
||||
if(send_shutdown) {
|
||||
rustls_connection_send_close_notify(backend->conn);
|
||||
}
|
||||
}
|
||||
|
||||
nwritten = cr_send(cf, data, NULL, 0, &result);
|
||||
if(nwritten < 0) {
|
||||
if(result == CURLE_AGAIN) {
|
||||
connssl->io_need = CURL_SSL_IO_NEED_SEND;
|
||||
result = CURLE_OK;
|
||||
goto out;
|
||||
}
|
||||
DEBUGASSERT(result);
|
||||
CURL_TRC_CF(data, cf, "shutdown send failed: %d", result);
|
||||
goto out;
|
||||
}
|
||||
|
||||
for(i = 0; i < 10; ++i) {
|
||||
nread = cr_recv(cf, data, buf, (int)sizeof(buf), &result);
|
||||
if(nread <= 0)
|
||||
break;
|
||||
}
|
||||
|
||||
if(nread > 0) {
|
||||
/* still data coming in? */
|
||||
}
|
||||
else if(nread == 0) {
|
||||
/* We got the close notify alert and are done. */
|
||||
*done = TRUE;
|
||||
}
|
||||
else if(result == CURLE_AGAIN) {
|
||||
connssl->io_need = CURL_SSL_IO_NEED_RECV;
|
||||
result = CURLE_OK;
|
||||
}
|
||||
else {
|
||||
DEBUGASSERT(result);
|
||||
CURL_TRC_CF(data, cf, "shutdown, error: %d", result);
|
||||
}
|
||||
|
||||
out:
|
||||
connssl->shutdown = (result || *done);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void
|
||||
cr_close(struct Curl_cfilter *cf, struct Curl_easy *data)
|
||||
{
|
||||
struct ssl_connect_data *connssl = cf->ctx;
|
||||
struct rustls_ssl_backend_data *backend =
|
||||
(struct rustls_ssl_backend_data *)connssl->backend;
|
||||
CURLcode tmperr = CURLE_OK;
|
||||
ssize_t n = 0;
|
||||
|
||||
DEBUGASSERT(backend);
|
||||
if(backend->conn && !connssl->peer_closed) {
|
||||
CURL_TRC_CF(data, cf, "closing connection, send notify");
|
||||
rustls_connection_send_close_notify(backend->conn);
|
||||
n = cr_send(cf, data, NULL, 0, &tmperr);
|
||||
if(n < 0) {
|
||||
failf(data, "rustls: error sending close_notify: %d", tmperr);
|
||||
if(backend->conn) {
|
||||
/* Send the TLS shutdown if have not done so already and are still
|
||||
* connected *and* if the peer did not already close the connection. */
|
||||
if(cf->connected && !connssl->shutdown &&
|
||||
cf->next && cf->next->connected && !connssl->peer_closed) {
|
||||
bool done;
|
||||
(void)cr_shutdown(cf, data, TRUE, &done);
|
||||
}
|
||||
|
||||
rustls_connection_free(backend->conn);
|
||||
@ -770,7 +839,7 @@ const struct Curl_ssl Curl_ssl_rustls = {
|
||||
Curl_none_cleanup, /* cleanup */
|
||||
cr_version, /* version */
|
||||
Curl_none_check_cxn, /* check_cxn */
|
||||
Curl_none_shutdown, /* shutdown */
|
||||
cr_shutdown, /* shutdown */
|
||||
cr_data_pending, /* data_pending */
|
||||
Curl_none_random, /* random */
|
||||
Curl_none_cert_status_request, /* cert_status_request */
|
||||
|
@ -2470,8 +2470,9 @@ static bool schannel_data_pending(struct Curl_cfilter *cf,
|
||||
/* shut down the SSL connection and clean up related memory.
|
||||
this function can be called multiple times on the same connection including
|
||||
if the SSL connection failed (eg connection made but failed handshake). */
|
||||
static int schannel_shutdown(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data)
|
||||
static CURLcode schannel_shutdown(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
bool send_shutdown, bool *done)
|
||||
{
|
||||
/* See https://msdn.microsoft.com/en-us/library/windows/desktop/aa380138.aspx
|
||||
* Shutting Down an Schannel Connection
|
||||
@ -2479,10 +2480,20 @@ static int schannel_shutdown(struct Curl_cfilter *cf,
|
||||
struct ssl_connect_data *connssl = cf->ctx;
|
||||
struct schannel_ssl_backend_data *backend =
|
||||
(struct schannel_ssl_backend_data *)connssl->backend;
|
||||
CURLcode result = CURLE_OK;
|
||||
|
||||
if(connssl->shutdown) {
|
||||
*done = TRUE;
|
||||
return CURLE_OK;
|
||||
}
|
||||
|
||||
DEBUGASSERT(data);
|
||||
DEBUGASSERT(backend);
|
||||
|
||||
/* Not supported in schannel */
|
||||
(void)send_shutdown;
|
||||
|
||||
*done = FALSE;
|
||||
if(backend->ctxt) {
|
||||
infof(data, "schannel: shutting down SSL/TLS connection with %s port %d",
|
||||
connssl->peer.hostname, connssl->peer.port);
|
||||
@ -2494,7 +2505,6 @@ static int schannel_shutdown(struct Curl_cfilter *cf,
|
||||
SECURITY_STATUS sspi_status;
|
||||
SecBuffer outbuf;
|
||||
SecBufferDesc outbuf_desc;
|
||||
CURLcode result;
|
||||
DWORD dwshut = SCHANNEL_SHUTDOWN;
|
||||
|
||||
InitSecBuffer(&Buffer, SECBUFFER_TOKEN, &dwshut, sizeof(dwshut));
|
||||
@ -2507,6 +2517,8 @@ static int schannel_shutdown(struct Curl_cfilter *cf,
|
||||
char buffer[STRERROR_LEN];
|
||||
failf(data, "schannel: ApplyControlToken failure: %s",
|
||||
Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer)));
|
||||
result = CURLE_SEND_ERROR;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* setup output buffer */
|
||||
@ -2536,10 +2548,32 @@ static int schannel_shutdown(struct Curl_cfilter *cf,
|
||||
if((result != CURLE_OK) || (outbuf.cbBuffer != (size_t) written)) {
|
||||
infof(data, "schannel: failed to send close msg: %s"
|
||||
" (bytes written: %zd)", curl_easy_strerror(result), written);
|
||||
result = CURLE_SEND_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
connssl->shutdown = (result || *done);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void schannel_close(struct Curl_cfilter *cf, struct Curl_easy *data)
|
||||
{
|
||||
struct ssl_connect_data *connssl = cf->ctx;
|
||||
struct schannel_ssl_backend_data *backend =
|
||||
(struct schannel_ssl_backend_data *)connssl->backend;
|
||||
|
||||
DEBUGASSERT(data);
|
||||
DEBUGASSERT(backend);
|
||||
|
||||
if(backend->cred && backend->ctxt &&
|
||||
cf->connected && !connssl->shutdown &&
|
||||
cf->next && cf->next->connected && !connssl->peer_closed) {
|
||||
bool done;
|
||||
(void)schannel_shutdown(cf, data, TRUE, &done);
|
||||
}
|
||||
|
||||
/* free SSPI Schannel API security context handle */
|
||||
if(backend->ctxt) {
|
||||
DEBUGF(infof(data, "schannel: clear security context handle"));
|
||||
@ -2569,13 +2603,6 @@ static int schannel_shutdown(struct Curl_cfilter *cf,
|
||||
backend->decdata_length = 0;
|
||||
backend->decdata_offset = 0;
|
||||
}
|
||||
|
||||
return CURLE_OK;
|
||||
}
|
||||
|
||||
static void schannel_close(struct Curl_cfilter *cf, struct Curl_easy *data)
|
||||
{
|
||||
schannel_shutdown(cf, data);
|
||||
}
|
||||
|
||||
static int schannel_init(void)
|
||||
|
@ -153,6 +153,7 @@ struct st_ssl_backend_data {
|
||||
SSLContextRef ssl_ctx;
|
||||
bool ssl_direction; /* true if writing, false if reading */
|
||||
size_t ssl_write_buffered_length;
|
||||
BIT(sent_shutdown);
|
||||
};
|
||||
|
||||
/* Create the list of default ciphers to use by making an intersection of the
|
||||
@ -2555,6 +2556,92 @@ static CURLcode sectransp_connect(struct Curl_cfilter *cf,
|
||||
return CURLE_OK;
|
||||
}
|
||||
|
||||
static ssize_t sectransp_recv(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
char *buf,
|
||||
size_t buffersize,
|
||||
CURLcode *curlcode);
|
||||
|
||||
static CURLcode sectransp_shutdown(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
bool send_shutdown, bool *done)
|
||||
{
|
||||
struct ssl_connect_data *connssl = cf->ctx;
|
||||
struct st_ssl_backend_data *backend =
|
||||
(struct st_ssl_backend_data *)connssl->backend;
|
||||
CURLcode result = CURLE_OK;
|
||||
ssize_t nread;
|
||||
char buf[1024];
|
||||
size_t i;
|
||||
|
||||
DEBUGASSERT(backend);
|
||||
if(!backend->ssl_ctx || connssl->shutdown) {
|
||||
*done = TRUE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
connssl->io_need = CURL_SSL_IO_NEED_NONE;
|
||||
*done = FALSE;
|
||||
|
||||
if(send_shutdown && !backend->sent_shutdown) {
|
||||
OSStatus err;
|
||||
|
||||
CURL_TRC_CF(data, cf, "shutdown, send close notify");
|
||||
err = SSLClose(backend->ssl_ctx);
|
||||
switch(err) {
|
||||
case noErr:
|
||||
backend->sent_shutdown = TRUE;
|
||||
break;
|
||||
case errSSLWouldBlock:
|
||||
connssl->io_need = CURL_SSL_IO_NEED_SEND;
|
||||
result = CURLE_OK;
|
||||
goto out;
|
||||
default:
|
||||
CURL_TRC_CF(data, cf, "shutdown, error: %d", (int)err);
|
||||
result = CURLE_SEND_ERROR;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
for(i = 0; i < 10; ++i) {
|
||||
if(!backend->sent_shutdown) {
|
||||
nread = sectransp_recv(cf, data, buf, (int)sizeof(buf), &result);
|
||||
}
|
||||
else {
|
||||
/* We would like to read the close notify from the server using
|
||||
* secure transport, however SSLRead() no longer works after we
|
||||
* sent the notify from our side. So, we just read from the
|
||||
* underlying filter and hope it will end. */
|
||||
nread = Curl_conn_cf_recv(cf->next, data, buf, sizeof(buf), &result);
|
||||
}
|
||||
CURL_TRC_CF(data, cf, "shutdown read -> %zd, %d", nread, result);
|
||||
if(nread <= 0)
|
||||
break;
|
||||
}
|
||||
|
||||
if(nread > 0) {
|
||||
/* still data coming in? */
|
||||
connssl->io_need = CURL_SSL_IO_NEED_RECV;
|
||||
}
|
||||
else if(nread == 0) {
|
||||
/* We got the close notify alert and are done. */
|
||||
CURL_TRC_CF(data, cf, "shutdown done");
|
||||
*done = TRUE;
|
||||
}
|
||||
else if(result == CURLE_AGAIN) {
|
||||
connssl->io_need = CURL_SSL_IO_NEED_RECV;
|
||||
result = CURLE_OK;
|
||||
}
|
||||
else {
|
||||
DEBUGASSERT(result);
|
||||
CURL_TRC_CF(data, cf, "shutdown, error: %d", result);
|
||||
}
|
||||
|
||||
out:
|
||||
connssl->shutdown = (result || *done);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void sectransp_close(struct Curl_cfilter *cf, struct Curl_easy *data)
|
||||
{
|
||||
struct ssl_connect_data *connssl = cf->ctx;
|
||||
@ -2567,7 +2654,12 @@ static void sectransp_close(struct Curl_cfilter *cf, struct Curl_easy *data)
|
||||
|
||||
if(backend->ssl_ctx) {
|
||||
CURL_TRC_CF(data, cf, "close");
|
||||
(void)SSLClose(backend->ssl_ctx);
|
||||
if(cf->connected && !connssl->shutdown &&
|
||||
cf->next && cf->next->connected && !connssl->peer_closed) {
|
||||
bool done;
|
||||
(void)sectransp_shutdown(cf, data, TRUE, &done);
|
||||
}
|
||||
|
||||
#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS
|
||||
if(SSLCreateContext)
|
||||
CFRelease(backend->ssl_ctx);
|
||||
@ -2582,69 +2674,6 @@ static void sectransp_close(struct Curl_cfilter *cf, struct Curl_easy *data)
|
||||
}
|
||||
}
|
||||
|
||||
static int sectransp_shutdown(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data)
|
||||
{
|
||||
struct ssl_connect_data *connssl = cf->ctx;
|
||||
struct st_ssl_backend_data *backend =
|
||||
(struct st_ssl_backend_data *)connssl->backend;
|
||||
ssize_t nread;
|
||||
int what;
|
||||
int rc;
|
||||
char buf[120];
|
||||
int loop = 10; /* avoid getting stuck */
|
||||
CURLcode result;
|
||||
|
||||
DEBUGASSERT(backend);
|
||||
|
||||
if(!backend->ssl_ctx)
|
||||
return 0;
|
||||
|
||||
#ifndef CURL_DISABLE_FTP
|
||||
if(data->set.ftp_ccc != CURLFTPSSL_CCC_ACTIVE)
|
||||
return 0;
|
||||
#endif
|
||||
|
||||
sectransp_close(cf, data);
|
||||
|
||||
rc = 0;
|
||||
|
||||
what = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data),
|
||||
SSL_SHUTDOWN_TIMEOUT);
|
||||
|
||||
CURL_TRC_CF(data, cf, "shutdown");
|
||||
while(loop--) {
|
||||
if(what < 0) {
|
||||
/* anything that gets here is fatally bad */
|
||||
failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO);
|
||||
rc = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
if(!what) { /* timeout */
|
||||
failf(data, "SSL shutdown timeout");
|
||||
break;
|
||||
}
|
||||
|
||||
/* Something to read, let's do it and hope that it is the close
|
||||
notify alert from the server. No way to SSL_Read now, so use read(). */
|
||||
|
||||
nread = Curl_conn_cf_recv(cf->next, data, buf, sizeof(buf), &result);
|
||||
|
||||
if(nread < 0) {
|
||||
failf(data, "read: %s", curl_easy_strerror(result));
|
||||
rc = -1;
|
||||
}
|
||||
|
||||
if(nread <= 0)
|
||||
break;
|
||||
|
||||
what = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data), 0);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static size_t sectransp_version(char *buffer, size_t size)
|
||||
{
|
||||
return msnprintf(buffer, size, "SecureTransport");
|
||||
|
111
lib/vtls/vtls.c
111
lib/vtls/vtls.c
@ -68,6 +68,8 @@
|
||||
#include "curl_base64.h"
|
||||
#include "curl_printf.h"
|
||||
#include "inet_pton.h"
|
||||
#include "connect.h"
|
||||
#include "select.h"
|
||||
#include "strdup.h"
|
||||
|
||||
/* The last #include files should be: */
|
||||
@ -1168,12 +1170,18 @@ int Curl_none_init(void)
|
||||
void Curl_none_cleanup(void)
|
||||
{ }
|
||||
|
||||
int Curl_none_shutdown(struct Curl_cfilter *cf UNUSED_PARAM,
|
||||
struct Curl_easy *data UNUSED_PARAM)
|
||||
CURLcode Curl_none_shutdown(struct Curl_cfilter *cf UNUSED_PARAM,
|
||||
struct Curl_easy *data UNUSED_PARAM,
|
||||
bool send_shutdown UNUSED_PARAM,
|
||||
bool *done)
|
||||
{
|
||||
(void)data;
|
||||
(void)cf;
|
||||
return 0;
|
||||
(void)send_shutdown;
|
||||
/* Every SSL backend should have a shutdown implementation. Until we
|
||||
* have implemented that, we put this fake in place. */
|
||||
*done = TRUE;
|
||||
return CURLE_OK;
|
||||
}
|
||||
|
||||
int Curl_none_check_cxn(struct Curl_cfilter *cf, struct Curl_easy *data)
|
||||
@ -1745,17 +1753,34 @@ static ssize_t ssl_cf_recv(struct Curl_cfilter *cf,
|
||||
return nread;
|
||||
}
|
||||
|
||||
static CURLcode ssl_cf_shutdown(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
bool *done)
|
||||
{
|
||||
struct ssl_connect_data *connssl = cf->ctx;
|
||||
struct cf_call_data save;
|
||||
CURLcode result = CURLE_OK;
|
||||
|
||||
*done = TRUE;
|
||||
if(!connssl->shutdown) {
|
||||
CF_DATA_SAVE(save, cf, data);
|
||||
result = Curl_ssl->shut_down(cf, data, TRUE, done);
|
||||
CURL_TRC_CF(data, cf, "cf_shutdown -> %d, done=%d", result, *done);
|
||||
CF_DATA_RESTORE(cf, save);
|
||||
connssl->shutdown = (result || *done);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static void ssl_cf_adjust_pollset(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
struct easy_pollset *ps)
|
||||
struct Curl_easy *data,
|
||||
struct easy_pollset *ps)
|
||||
{
|
||||
struct cf_call_data save;
|
||||
|
||||
if(!cf->connected) {
|
||||
CF_DATA_SAVE(save, cf, data);
|
||||
Curl_ssl->adjust_pollset(cf, data, ps);
|
||||
CF_DATA_RESTORE(cf, save);
|
||||
}
|
||||
CF_DATA_SAVE(save, cf, data);
|
||||
Curl_ssl->adjust_pollset(cf, data, ps);
|
||||
CF_DATA_RESTORE(cf, save);
|
||||
}
|
||||
|
||||
static CURLcode ssl_cf_cntrl(struct Curl_cfilter *cf,
|
||||
@ -1845,6 +1870,7 @@ struct Curl_cftype Curl_cft_ssl = {
|
||||
ssl_cf_destroy,
|
||||
ssl_cf_connect,
|
||||
ssl_cf_close,
|
||||
ssl_cf_shutdown,
|
||||
Curl_cf_def_get_host,
|
||||
ssl_cf_adjust_pollset,
|
||||
ssl_cf_data_pending,
|
||||
@ -1865,6 +1891,7 @@ struct Curl_cftype Curl_cft_ssl_proxy = {
|
||||
ssl_cf_destroy,
|
||||
ssl_cf_connect,
|
||||
ssl_cf_close,
|
||||
ssl_cf_shutdown,
|
||||
Curl_cf_def_get_host,
|
||||
ssl_cf_adjust_pollset,
|
||||
ssl_cf_data_pending,
|
||||
@ -2015,19 +2042,77 @@ void *Curl_ssl_get_internals(struct Curl_easy *data, int sockindex,
|
||||
return result;
|
||||
}
|
||||
|
||||
static CURLcode vtls_shutdown_blocking(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
bool send_shutdown, bool *done)
|
||||
{
|
||||
struct ssl_connect_data *connssl = cf->ctx;
|
||||
struct cf_call_data save;
|
||||
CURLcode result = CURLE_OK;
|
||||
timediff_t timeout_ms;
|
||||
int what, loop = 10;
|
||||
|
||||
if(connssl->shutdown) {
|
||||
*done = TRUE;
|
||||
return CURLE_OK;
|
||||
}
|
||||
CF_DATA_SAVE(save, cf, data);
|
||||
|
||||
*done = FALSE;
|
||||
while(!result && !*done && loop--) {
|
||||
timeout_ms = Curl_shutdown_timeleft(cf->conn, cf->sockindex, NULL);
|
||||
|
||||
if(timeout_ms < 0) {
|
||||
/* no need to continue if time is already up */
|
||||
failf(data, "SSL shutdown timeout");
|
||||
return CURLE_OPERATION_TIMEDOUT;
|
||||
}
|
||||
|
||||
result = Curl_ssl->shut_down(cf, data, send_shutdown, done);
|
||||
if(result ||*done)
|
||||
goto out;
|
||||
|
||||
if(connssl->io_need) {
|
||||
what = Curl_conn_cf_poll(cf, data, timeout_ms);
|
||||
if(what < 0) {
|
||||
/* fatal error */
|
||||
failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO);
|
||||
result = CURLE_RECV_ERROR;
|
||||
goto out;
|
||||
}
|
||||
else if(0 == what) {
|
||||
/* timeout */
|
||||
failf(data, "SSL shutdown timeout");
|
||||
result = CURLE_OPERATION_TIMEDOUT;
|
||||
goto out;
|
||||
}
|
||||
/* socket is readable or writable */
|
||||
}
|
||||
}
|
||||
out:
|
||||
CF_DATA_RESTORE(cf, save);
|
||||
connssl->shutdown = (result || *done);
|
||||
return result;
|
||||
}
|
||||
|
||||
CURLcode Curl_ssl_cfilter_remove(struct Curl_easy *data,
|
||||
int sockindex)
|
||||
int sockindex, bool send_shutdown)
|
||||
{
|
||||
struct Curl_cfilter *cf, *head;
|
||||
CURLcode result = CURLE_OK;
|
||||
|
||||
(void)data;
|
||||
head = data->conn? data->conn->cfilter[sockindex] : NULL;
|
||||
for(cf = head; cf; cf = cf->next) {
|
||||
if(cf->cft == &Curl_cft_ssl) {
|
||||
if(Curl_ssl->shut_down(cf, data))
|
||||
bool done;
|
||||
CURL_TRC_CF(data, cf, "shutdown and remove SSL, start");
|
||||
Curl_shutdown_start(data, sockindex, NULL);
|
||||
result = vtls_shutdown_blocking(cf, data, send_shutdown, &done);
|
||||
Curl_shutdown_clear(data, sockindex);
|
||||
if(!result && !done) /* blocking failed? */
|
||||
result = CURLE_SSL_SHUTDOWN_FAILED;
|
||||
Curl_conn_cf_discard_sub(head, cf, data, FALSE);
|
||||
CURL_TRC_CF(data, cf, "shutdown and remove SSL, done -> %d", result);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -191,7 +191,7 @@ CURLcode Curl_cf_ssl_insert_after(struct Curl_cfilter *cf_at,
|
||||
struct Curl_easy *data);
|
||||
|
||||
CURLcode Curl_ssl_cfilter_remove(struct Curl_easy *data,
|
||||
int sockindex);
|
||||
int sockindex, bool send_shutdown);
|
||||
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
CURLcode Curl_cf_ssl_proxy_insert_after(struct Curl_cfilter *cf_at,
|
||||
@ -250,7 +250,7 @@ extern struct Curl_cftype Curl_cft_ssl_proxy;
|
||||
#define Curl_ssl_get_internals(a,b,c,d) NULL
|
||||
#define Curl_ssl_supports(a,b) FALSE
|
||||
#define Curl_ssl_cfilter_add(a,b,c) CURLE_NOT_BUILT_IN
|
||||
#define Curl_ssl_cfilter_remove(a,b) CURLE_OK
|
||||
#define Curl_ssl_cfilter_remove(a,b,c) CURLE_OK
|
||||
#define Curl_ssl_cf_get_config(a,b) NULL
|
||||
#define Curl_ssl_cf_get_primary_config(a) NULL
|
||||
#endif
|
||||
|
@ -94,6 +94,7 @@ struct ssl_connect_data {
|
||||
int io_need; /* TLS signals special SEND/RECV needs */
|
||||
BIT(use_alpn); /* if ALPN shall be used in handshake */
|
||||
BIT(peer_closed); /* peer has closed connection */
|
||||
BIT(shutdown); /* graceful close notify finished */
|
||||
};
|
||||
|
||||
|
||||
@ -118,8 +119,8 @@ struct Curl_ssl {
|
||||
|
||||
size_t (*version)(char *buffer, size_t size);
|
||||
int (*check_cxn)(struct Curl_cfilter *cf, struct Curl_easy *data);
|
||||
int (*shut_down)(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data);
|
||||
CURLcode (*shut_down)(struct Curl_cfilter *cf, struct Curl_easy *data,
|
||||
bool send_shutdown, bool *done);
|
||||
bool (*data_pending)(struct Curl_cfilter *cf,
|
||||
const struct Curl_easy *data);
|
||||
|
||||
@ -134,9 +135,8 @@ struct Curl_ssl {
|
||||
struct Curl_easy *data,
|
||||
bool *done);
|
||||
|
||||
/* During handshake, adjust the pollset to include the socket
|
||||
* for POLLOUT or POLLIN as needed.
|
||||
* Mandatory. */
|
||||
/* During handshake/shutdown, adjust the pollset to include the socket
|
||||
* for POLLOUT or POLLIN as needed. Mandatory. */
|
||||
void (*adjust_pollset)(struct Curl_cfilter *cf, struct Curl_easy *data,
|
||||
struct easy_pollset *ps);
|
||||
void *(*get_internals)(struct ssl_connect_data *connssl, CURLINFO info);
|
||||
@ -166,7 +166,8 @@ extern const struct Curl_ssl *Curl_ssl;
|
||||
|
||||
int Curl_none_init(void);
|
||||
void Curl_none_cleanup(void);
|
||||
int Curl_none_shutdown(struct Curl_cfilter *cf, struct Curl_easy *data);
|
||||
CURLcode Curl_none_shutdown(struct Curl_cfilter *cf, struct Curl_easy *data,
|
||||
bool send_shutdown, bool *done);
|
||||
int Curl_none_check_cxn(struct Curl_cfilter *cf, struct Curl_easy *data);
|
||||
CURLcode Curl_none_random(struct Curl_easy *data, unsigned char *entropy,
|
||||
size_t length);
|
||||
|
@ -1341,6 +1341,99 @@ static ssize_t wolfssl_send(struct Curl_cfilter *cf,
|
||||
return rc;
|
||||
}
|
||||
|
||||
static CURLcode wolfssl_shutdown(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
bool send_shutdown, bool *done)
|
||||
{
|
||||
struct ssl_connect_data *connssl = cf->ctx;
|
||||
struct wolfssl_ctx *wctx = (struct wolfssl_ctx *)connssl->backend;
|
||||
CURLcode result = CURLE_OK;
|
||||
char buf[1024];
|
||||
int nread, err;
|
||||
|
||||
DEBUGASSERT(wctx);
|
||||
if(!wctx->handle || connssl->shutdown) {
|
||||
*done = TRUE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
connssl->io_need = CURL_SSL_IO_NEED_NONE;
|
||||
*done = FALSE;
|
||||
if(!(wolfSSL_get_shutdown(wctx->handle) & SSL_SENT_SHUTDOWN)) {
|
||||
/* We have not started the shutdown from our side yet. Check
|
||||
* if the server already sent us one. */
|
||||
ERR_clear_error();
|
||||
nread = wolfSSL_read(wctx->handle, buf, (int)sizeof(buf));
|
||||
err = wolfSSL_get_error(wctx->handle, nread);
|
||||
if(!nread && err == SSL_ERROR_ZERO_RETURN) {
|
||||
bool input_pending;
|
||||
/* Yes, it did. */
|
||||
if(!send_shutdown) {
|
||||
connssl->shutdown = TRUE;
|
||||
CURL_TRC_CF(data, cf, "SSL shutdown received, not sending");
|
||||
goto out;
|
||||
}
|
||||
else if(!cf->next->cft->is_alive(cf->next, data, &input_pending)) {
|
||||
/* Server closed the connection after its closy notify. It
|
||||
* seems not interested to see our close notify, so do not
|
||||
* send it. We are done. */
|
||||
connssl->peer_closed = TRUE;
|
||||
connssl->shutdown = TRUE;
|
||||
CURL_TRC_CF(data, cf, "peer closed connection");
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(send_shutdown && wolfSSL_shutdown(wctx->handle) == 1) {
|
||||
CURL_TRC_CF(data, cf, "SSL shutdown finished");
|
||||
*done = TRUE;
|
||||
goto out;
|
||||
}
|
||||
else {
|
||||
size_t i;
|
||||
/* SSL should now have started the shutdown from our side. Since it
|
||||
* was not complete, we are lacking the close notify from the server. */
|
||||
for(i = 0; i < 10; ++i) {
|
||||
ERR_clear_error();
|
||||
nread = wolfSSL_read(wctx->handle, buf, (int)sizeof(buf));
|
||||
if(nread <= 0)
|
||||
break;
|
||||
}
|
||||
err = wolfSSL_get_error(wctx->handle, nread);
|
||||
switch(err) {
|
||||
case SSL_ERROR_ZERO_RETURN: /* no more data */
|
||||
CURL_TRC_CF(data, cf, "SSL shutdown received");
|
||||
*done = TRUE;
|
||||
break;
|
||||
case SSL_ERROR_NONE: /* just did not get anything */
|
||||
case SSL_ERROR_WANT_READ:
|
||||
/* SSL has send its notify and now wants to read the reply
|
||||
* from the server. We are not really interested in that. */
|
||||
CURL_TRC_CF(data, cf, "SSL shutdown sent, want receive");
|
||||
connssl->io_need = CURL_SSL_IO_NEED_RECV;
|
||||
break;
|
||||
case SSL_ERROR_WANT_WRITE:
|
||||
CURL_TRC_CF(data, cf, "SSL shutdown send blocked");
|
||||
connssl->io_need = CURL_SSL_IO_NEED_SEND;
|
||||
break;
|
||||
default: {
|
||||
char error_buffer[WOLFSSL_MAX_ERROR_SZ];
|
||||
int detail = wolfSSL_get_error(wctx->handle, err);
|
||||
CURL_TRC_CF(data, cf, "SSL shutdown, error: '%s'(%d)",
|
||||
wolfSSL_ERR_error_string((unsigned long)err, error_buffer),
|
||||
detail);
|
||||
result = CURLE_RECV_ERROR;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
connssl->shutdown = (result || *done);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void wolfssl_close(struct Curl_cfilter *cf, struct Curl_easy *data)
|
||||
{
|
||||
struct ssl_connect_data *connssl = cf->ctx;
|
||||
@ -1352,12 +1445,11 @@ static void wolfssl_close(struct Curl_cfilter *cf, struct Curl_easy *data)
|
||||
DEBUGASSERT(backend);
|
||||
|
||||
if(backend->handle) {
|
||||
char buf[32];
|
||||
/* Maybe the server has already sent a close notify alert.
|
||||
Read it to avoid an RST on the TCP connection. */
|
||||
(void)wolfSSL_read(backend->handle, buf, (int)sizeof(buf));
|
||||
if(!connssl->peer_closed)
|
||||
(void)wolfSSL_shutdown(backend->handle);
|
||||
if(cf->connected && !connssl->shutdown &&
|
||||
cf->next && cf->next->connected && !connssl->peer_closed) {
|
||||
bool done;
|
||||
(void)wolfssl_shutdown(cf, data, TRUE, &done);
|
||||
}
|
||||
wolfSSL_free(backend->handle);
|
||||
backend->handle = NULL;
|
||||
}
|
||||
@ -1468,31 +1560,6 @@ static bool wolfssl_data_pending(struct Curl_cfilter *cf,
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* This function is called to shut down the SSL layer but keep the
|
||||
* socket open (CCC - Clear Command Channel)
|
||||
*/
|
||||
static int wolfssl_shutdown(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data)
|
||||
{
|
||||
struct ssl_connect_data *ctx = cf->ctx;
|
||||
struct wolfssl_ctx *backend;
|
||||
int retval = 0;
|
||||
|
||||
(void)data;
|
||||
DEBUGASSERT(ctx && ctx->backend);
|
||||
|
||||
backend = (struct wolfssl_ctx *)ctx->backend;
|
||||
if(backend->handle) {
|
||||
wolfSSL_ERR_clear_error();
|
||||
wolfSSL_free(backend->handle);
|
||||
backend->handle = NULL;
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
static CURLcode
|
||||
wolfssl_connect_common(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
|
@ -38,9 +38,6 @@ log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@pytest.mark.skipif(condition=not Env.has_vsftpd(), reason=f"missing vsftpd")
|
||||
# rustsl: transfers sometimes fail with "received corrupt message of type InvalidContentType"
|
||||
# sporadic, never seen when filter tracing is on
|
||||
@pytest.mark.skipif(condition=Env.curl_uses_lib('rustls-ffi'), reason=f"rustls unreliable here")
|
||||
class TestVsFTPD:
|
||||
|
||||
SUPPORTS_SSL = True
|
||||
|
@ -159,6 +159,7 @@ static struct Curl_cftype cft_test = {
|
||||
cf_test_destroy,
|
||||
cf_test_connect,
|
||||
Curl_cf_def_close,
|
||||
Curl_cf_def_shutdown,
|
||||
Curl_cf_def_get_host,
|
||||
Curl_cf_def_adjust_pollset,
|
||||
Curl_cf_def_data_pending,
|
||||
|
Loading…
Reference in New Issue
Block a user