diff options
Diffstat (limited to 'lib')
148 files changed, 6652 insertions, 4154 deletions
diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 51d5257..e80df5f 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -31,9 +31,10 @@ configure_file(curl_config.h.cmake transform_makefile_inc("Makefile.inc" "${CMAKE_CURRENT_BINARY_DIR}/Makefile.inc.cmake") include(${CMAKE_CURRENT_BINARY_DIR}/Makefile.inc.cmake) -list(APPEND HHEADERS - ${CMAKE_CURRENT_BINARY_DIR}/curl_config.h - ) +# DllMain is added later for DLL builds only. +list(REMOVE_ITEM CSOURCES dllmain.c) + +list(APPEND HHEADERS ${CMAKE_CURRENT_BINARY_DIR}/curl_config.h) # The rest of the build @@ -55,6 +56,7 @@ if(BUILD_TESTING) ${HHEADERS} ${CSOURCES} ) target_compile_definitions(curlu PUBLIC UNITTESTS CURL_STATICLIB) + target_link_libraries(curlu PRIVATE ${CURL_LIBS}) endif() if(ENABLE_CURLDEBUG) @@ -63,10 +65,6 @@ if(ENABLE_CURLDEBUG) set_source_files_properties(memdebug.c curl_multibyte.c PROPERTIES SKIP_UNITY_BUILD_INCLUSION ON) endif() -if(BUILD_TESTING) - target_link_libraries(curlu PRIVATE ${CURL_LIBS}) -endif() - transform_makefile_inc("Makefile.soname" "${CMAKE_CURRENT_BINARY_DIR}/Makefile.soname.cmake") include(${CMAKE_CURRENT_BINARY_DIR}/Makefile.soname.cmake) @@ -114,17 +112,17 @@ if(NOT DEFINED SHARE_LIB_OBJECT) endif() endif() -if(WIN32) - # Define CURL_STATICLIB always, to disable __declspec(dllexport) for exported - # libcurl symbols. We handle exports via libcurl.def instead. Except with - # symbol hiding disabled or debug mode enabled, when we export _all_ symbols - # from libcurl DLL, without using libcurl.def. - add_definitions("-DCURL_STATICLIB") -endif() - if(SHARE_LIB_OBJECT) set(LIB_OBJECT "libcurl_object") add_library(${LIB_OBJECT} OBJECT ${HHEADERS} ${CSOURCES}) + if(WIN32) + # Define CURL_STATICLIB always, to disable __declspec(dllexport) for + # exported libcurl symbols. We handle exports via libcurl.def instead. + # Except with symbol hiding disabled or debug mode enabled, when we export + # _all_ symbols from libcurl DLL, without using libcurl.def. + set_property(TARGET ${LIB_OBJECT} APPEND + PROPERTY COMPILE_DEFINITIONS "CURL_STATICLIB") + endif() target_link_libraries(${LIB_OBJECT} PRIVATE ${CURL_LIBS}) set_target_properties(${LIB_OBJECT} PROPERTIES POSITION_INDEPENDENT_CODE ON) @@ -152,6 +150,10 @@ if(BUILD_STATIC_LIBS) list(APPEND libcurl_export ${LIB_STATIC}) add_library(${LIB_STATIC} STATIC ${LIB_SOURCE}) add_library(${PROJECT_NAME}::${LIB_STATIC} ALIAS ${LIB_STATIC}) + if(WIN32) + set_property(TARGET ${LIB_OBJECT} APPEND + PROPERTY COMPILE_DEFINITIONS "CURL_STATICLIB") + endif() target_link_libraries(${LIB_STATIC} PRIVATE ${CURL_LIBS}) # Remove the "lib" prefix since the library is already named "libcurl". set_target_properties(${LIB_STATIC} PROPERTIES @@ -181,6 +183,15 @@ if(BUILD_SHARED_LIBS) list(APPEND libcurl_export ${LIB_SHARED}) add_library(${LIB_SHARED} SHARED ${LIB_SOURCE}) add_library(${PROJECT_NAME}::${LIB_SHARED} ALIAS ${LIB_SHARED}) + if(WIN32 OR CYGWIN) + if(CYGWIN) + # For cygwin always compile dllmain.c as a separate unit since it + # includes windows.h, which shouldn't be included in other units. + set_source_files_properties(dllmain.c PROPERTIES + SKIP_UNITY_BUILD_INCLUSION ON) + endif() + set_property(TARGET ${LIB_SHARED} APPEND PROPERTY SOURCES dllmain.c) + endif() if(WIN32) set_property(TARGET ${LIB_SHARED} APPEND PROPERTY SOURCES libcurl.rc) if(HIDES_CURL_PRIVATE_SYMBOLS) diff --git a/lib/Makefile.inc b/lib/Makefile.inc index 400e2b1..66680f3 100644 --- a/lib/Makefile.inc +++ b/lib/Makefile.inc @@ -44,6 +44,7 @@ LIB_VAUTH_HFILES = \ LIB_VTLS_CFILES = \ vtls/bearssl.c \ + vtls/cipher_suite.c \ vtls/gtls.c \ vtls/hostcheck.c \ vtls/keylog.c \ @@ -60,6 +61,7 @@ LIB_VTLS_CFILES = \ LIB_VTLS_HFILES = \ vtls/bearssl.h \ + vtls/cipher_suite.h \ vtls/gtls.h \ vtls/hostcheck.h \ vtls/keylog.h \ @@ -129,7 +131,6 @@ LIB_CFILES = \ curl_memrchr.c \ curl_multibyte.c \ curl_ntlm_core.c \ - curl_ntlm_wb.c \ curl_path.c \ curl_range.c \ curl_rtmp.c \ @@ -140,6 +141,7 @@ LIB_CFILES = \ curl_trc.c \ cw-out.c \ dict.c \ + dllmain.c \ doh.c \ dynbuf.c \ dynhds.c \ @@ -271,7 +273,6 @@ LIB_HFILES = \ curl_memrchr.h \ curl_multibyte.h \ curl_ntlm_core.h \ - curl_ntlm_wb.h \ curl_path.h \ curl_printf.h \ curl_range.h \ diff --git a/lib/altsvc.c b/lib/altsvc.c index c12d7bd..b72a596 100644 --- a/lib/altsvc.c +++ b/lib/altsvc.c @@ -191,7 +191,7 @@ static CURLcode altsvc_add(struct altsvcinfo *asi, char *line) as->expires = expires; as->prio = prio; as->persist = persist ? 1 : 0; - Curl_llist_insert_next(&asi->list, asi->list.tail, as, &as->node); + Curl_llist_append(&asi->list, as, &as->node); } } @@ -252,7 +252,7 @@ static CURLcode altsvc_out(struct altsvc *as, FILE *fp) CURLcode result = Curl_gmtime(as->expires, &stamp); if(result) return result; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 else { char ipv6_unused[16]; if(1 == Curl_inet_pton(AF_INET6, as->dst.host, ipv6_unused)) { @@ -303,7 +303,7 @@ struct altsvcinfo *Curl_altsvc_init(void) #ifdef USE_HTTP2 | CURLALTSVC_H2 #endif -#ifdef ENABLE_QUIC +#ifdef USE_HTTP3 | CURLALTSVC_H3 #endif ; @@ -643,7 +643,7 @@ CURLcode Curl_altsvc_parse(struct Curl_easy *data, account. [See RFC 7838 section 3.1] */ as->expires = maxage + time(NULL); as->persist = persist; - Curl_llist_insert_next(&asi->list, asi->list.tail, as, &as->node); + Curl_llist_append(&asi->list, as, &as->node); infof(data, "Added alt-svc: %s:%d over %s", dsthost, dstport, Curl_alpnid2str(dstalpnid)); } diff --git a/lib/asyn-ares.c b/lib/asyn-ares.c index 3d718b3..8fed617 100644 --- a/lib/asyn-ares.c +++ b/lib/asyn-ares.c @@ -539,7 +539,7 @@ static void compound_results(struct thread_data *res, if(!ai) return; -#ifdef ENABLE_IPV6 /* CURLRES_IPV6 */ +#ifdef USE_IPV6 /* CURLRES_IPV6 */ if(res->temp_ai && res->temp_ai->ai_family == PF_INET6) { /* We have results already, put the new IPv6 entries at the head of the list. */ @@ -684,7 +684,7 @@ static struct Curl_addrinfo *ares2addr(struct ares_addrinfo_node *node) /* settle family-specific sockaddr structure size. */ if(ai->ai_family == AF_INET) ss_size = sizeof(struct sockaddr_in); -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 else if(ai->ai_family == AF_INET6) ss_size = sizeof(struct sockaddr_in6); #endif @@ -932,7 +932,7 @@ CURLcode Curl_set_dns_local_ip4(struct Curl_easy *data, CURLcode Curl_set_dns_local_ip6(struct Curl_easy *data, const char *local_ip6) { -#if defined(HAVE_CARES_SET_LOCAL) && defined(ENABLE_IPV6) +#if defined(HAVE_CARES_SET_LOCAL) && defined(USE_IPV6) unsigned char a6[INET6_ADDRSTRLEN]; if((!local_ip6) || (local_ip6[0] == 0)) { diff --git a/lib/asyn-thread.c b/lib/asyn-thread.c index 5b9d504..1760d6c 100644 --- a/lib/asyn-thread.c +++ b/lib/asyn-thread.c @@ -325,7 +325,7 @@ query_complete(DWORD err, DWORD bytes, LPWSAOVERLAPPED overlapped) /* settle family-specific sockaddr structure size. */ if(ai->ai_family == AF_INET) ss_size = sizeof(struct sockaddr_in); -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 else if(ai->ai_family == AF_INET6) ss_size = sizeof(struct sockaddr_in6); #endif @@ -444,7 +444,7 @@ query_complete(DWORD err, DWORD bytes, LPWSAOVERLAPPED overlapped) /* * getaddrinfo_thread() resolves a name and then exits. * - * For builds without ARES, but with ENABLE_IPV6, create a resolver thread + * For builds without ARES, but with USE_IPV6, create a resolver thread * and wait on it. */ static unsigned int CURL_STDCALL getaddrinfo_thread(void *arg) @@ -554,11 +554,15 @@ static void destroy_async_data(struct Curl_async *async) if(!done) { #ifdef _WIN32 - if(td->complete_ev) + if(td->complete_ev) { CloseHandle(td->complete_ev); - else + td->complete_ev = NULL; + } #endif - Curl_thread_destroy(td->thread_hnd); + if(td->thread_hnd != curl_thread_t_null) { + Curl_thread_destroy(td->thread_hnd); + td->thread_hnd = curl_thread_t_null; + } } else { #ifdef _WIN32 @@ -566,6 +570,7 @@ static void destroy_async_data(struct Curl_async *async) Curl_GetAddrInfoExCancel(&td->tsd.w8.cancel_ev); WaitForSingleObject(td->complete_ev, INFINITE); CloseHandle(td->complete_ev); + td->complete_ev = NULL; } #endif if(td->thread_hnd != curl_thread_t_null) @@ -672,7 +677,7 @@ static bool init_resolve_thread(struct Curl_easy *data, td->thread_hnd = Curl_thread_create(gethostbyname_thread, &td->tsd); #endif - if(!td->thread_hnd) { + if(td->thread_hnd == curl_thread_t_null) { /* The thread never started, so mark it as done here for proper cleanup. */ td->tsd.done = 1; err = errno; @@ -713,6 +718,7 @@ static CURLcode thread_wait_resolv(struct Curl_easy *data, if(td->complete_ev) { WaitForSingleObject(td->complete_ev, INFINITE); CloseHandle(td->complete_ev); + td->complete_ev = NULL; if(entry) result = getaddrinfo_complete(data); } @@ -754,6 +760,13 @@ void Curl_resolver_kill(struct Curl_easy *data) /* If we're still resolving, we must wait for the threads to fully clean up, unfortunately. Otherwise, we can simply cancel to clean up any resolver data. */ +#ifdef _WIN32 + if(td && td->complete_ev) { + Curl_GetAddrInfoExCancel(&td->tsd.w8.cancel_ev); + (void)thread_wait_resolv(data, NULL, FALSE); + } + else +#endif if(td && td->thread_hnd != curl_thread_t_null && (data->set.quick_exit != 1L)) (void)thread_wait_resolv(data, NULL, FALSE); diff --git a/lib/base64.c b/lib/base64.c index 919eb62..8373115 100644 --- a/lib/base64.c +++ b/lib/base64.c @@ -243,7 +243,7 @@ static CURLcode base64_encode(const char *table64, *outptr = base64data; /* Return the length of the new data */ - *outlen = output - base64data; + *outlen = (size_t)(output - base64data); return CURLE_OK; } @@ -85,7 +85,7 @@ void Curl_bufcp_free(struct bufc_pool *pool); * preferably never fail (except for memory exhaustion). * * By default and without a pool, a bufq will keep chunks that read - * read empty in its `spare` list. Option `BUFQ_OPT_NO_SPARES` will + * empty in its `spare` list. Option `BUFQ_OPT_NO_SPARES` will * disable that and free chunks once they become empty. * * When providing a pool to a bufq, all chunk creation and spare handling diff --git a/lib/c-hyper.c b/lib/c-hyper.c index 247d59f..0593d97 100644 --- a/lib/c-hyper.c +++ b/lib/c-hyper.c @@ -171,7 +171,7 @@ static int hyper_each_header(void *userdata, len = Curl_dyn_len(&data->state.headerb); headp = Curl_dyn_ptr(&data->state.headerb); - result = Curl_http_header(data, data->conn, headp, len); + result = Curl_http_header(data, headp, len); if(result) { data->state.hresult = result; return HYPER_ITER_BREAK; @@ -980,11 +980,13 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) goto error; } +#ifndef CURL_DISABLE_PROXY if(data->state.aptr.proxyuserpwd) { result = Curl_hyper_header(data, headers, data->state.aptr.proxyuserpwd); if(result) goto error; } +#endif if(data->state.aptr.userpwd) { result = Curl_hyper_header(data, headers, data->state.aptr.userpwd); @@ -1137,7 +1139,9 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) /* clear userpwd and proxyuserpwd to avoid reusing old credentials * from reused connections */ Curl_safefree(data->state.aptr.userpwd); +#ifndef CURL_DISABLE_PROXY Curl_safefree(data->state.aptr.proxyuserpwd); +#endif return CURLE_OK; error: DEBUGASSERT(result); diff --git a/lib/cf-h1-proxy.c b/lib/cf-h1-proxy.c index ed6322c..093c33b 100644 --- a/lib/cf-h1-proxy.c +++ b/lib/cf-h1-proxy.c @@ -195,14 +195,16 @@ static void h1_tunnel_go_state(struct Curl_cfilter *cf, static void tunnel_free(struct Curl_cfilter *cf, struct Curl_easy *data) { - struct h1_tunnel_state *ts = cf->ctx; - if(ts) { - h1_tunnel_go_state(cf, ts, H1_TUNNEL_FAILED, data); - Curl_dyn_free(&ts->rcvbuf); - Curl_dyn_free(&ts->request_data); - Curl_httpchunk_free(data, &ts->ch); - free(ts); - cf->ctx = NULL; + if(cf) { + struct h1_tunnel_state *ts = cf->ctx; + if(ts) { + h1_tunnel_go_state(cf, ts, H1_TUNNEL_FAILED, data); + Curl_dyn_free(&ts->rcvbuf); + Curl_dyn_free(&ts->request_data); + Curl_httpchunk_free(data, &ts->ch); + free(ts); + cf->ctx = NULL; + } } } @@ -1057,18 +1059,20 @@ static void cf_h1_proxy_close(struct Curl_cfilter *cf, struct Curl_easy *data) { CURL_TRC_CF(data, cf, "close"); - cf->connected = FALSE; - if(cf->ctx) { - h1_tunnel_go_state(cf, cf->ctx, H1_TUNNEL_INIT, data); + if(cf) { + cf->connected = FALSE; + if(cf->ctx) { + h1_tunnel_go_state(cf, cf->ctx, H1_TUNNEL_INIT, data); + } + if(cf->next) + cf->next->cft->do_close(cf->next, data); } - if(cf->next) - cf->next->cft->do_close(cf->next, data); } struct Curl_cftype Curl_cft_h1_proxy = { "H1-PROXY", - CF_TYPE_IP_CONNECT, + CF_TYPE_IP_CONNECT|CF_TYPE_PROXY, 0, cf_h1_proxy_destroy, cf_h1_proxy_connect, diff --git a/lib/cf-h2-proxy.c b/lib/cf-h2-proxy.c index 78dc222..0bff15f 100644 --- a/lib/cf-h2-proxy.c +++ b/lib/cf-h2-proxy.c @@ -1532,7 +1532,7 @@ static bool cf_h2_proxy_is_alive(struct Curl_cfilter *cf, struct Curl_cftype Curl_cft_h2_proxy = { "H2-PROXY", - CF_TYPE_IP_CONNECT, + CF_TYPE_IP_CONNECT|CF_TYPE_PROXY, CURL_LOG_LVL_NONE, cf_h2_proxy_destroy, cf_h2_proxy_connect, diff --git a/lib/cf-haproxy.c b/lib/cf-haproxy.c index 4043922..2abc4d7 100644 --- a/lib/cf-haproxy.c +++ b/lib/cf-haproxy.c @@ -189,7 +189,7 @@ static void cf_haproxy_adjust_pollset(struct Curl_cfilter *cf, struct Curl_cftype Curl_cft_haproxy = { "HAPROXY", - 0, + CF_TYPE_PROXY, 0, cf_haproxy_destroy, cf_haproxy_connect, diff --git a/lib/cf-https-connect.c b/lib/cf-https-connect.c index b23fa05..50ac8d4 100644 --- a/lib/cf-https-connect.c +++ b/lib/cf-https-connect.c @@ -102,8 +102,8 @@ struct cf_hc_ctx { CURLcode result; /* overall result */ struct cf_hc_baller h3_baller; struct cf_hc_baller h21_baller; - int soft_eyeballs_timeout_ms; - int hard_eyeballs_timeout_ms; + unsigned int soft_eyeballs_timeout_ms; + unsigned int hard_eyeballs_timeout_ms; }; static void cf_hc_baller_init(struct cf_hc_baller *b, diff --git a/lib/cf-socket.c b/lib/cf-socket.c index 1de5100..3e87889 100644 --- a/lib/cf-socket.c +++ b/lib/cf-socket.c @@ -81,7 +81,7 @@ #include "memdebug.h" -#if defined(ENABLE_IPV6) && defined(IPV6_V6ONLY) && defined(_WIN32) +#if defined(USE_IPV6) && defined(IPV6_V6ONLY) && defined(_WIN32) /* It makes support for IPv4-mapped IPv6 addresses. * Linux kernel, NetBSD, FreeBSD and Darwin: default is off; * Windows Vista and later: default is on; @@ -287,7 +287,7 @@ static CURLcode socket_open(struct Curl_easy *data, /* no socket, no connection */ return CURLE_COULDNT_CONNECT; -#if defined(ENABLE_IPV6) && defined(HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID) +#if defined(USE_IPV6) && defined(HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID) if(data->conn->scope_id && (addr->family == AF_INET6)) { struct sockaddr_in6 * const sa6 = (void *)&addr->sa_addr; sa6->sin6_scope_id = data->conn->scope_id; @@ -405,7 +405,7 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, struct sockaddr *sock = (struct sockaddr *)&sa; /* bind to this address */ curl_socklen_t sizeof_sa = 0; /* size of the data sock points to */ struct sockaddr_in *si4 = (struct sockaddr_in *)&sa; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 struct sockaddr_in6 *si6 = (struct sockaddr_in6 *)&sa; #endif @@ -419,7 +419,7 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, #ifdef IP_BIND_ADDRESS_NO_PORT int on = 1; #endif -#ifndef ENABLE_IPV6 +#ifndef USE_IPV6 (void)scope; #endif @@ -475,7 +475,7 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, #endif switch(Curl_if2ip(af, -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 scope, conn->scope_id, #endif dev, myhost, sizeof(myhost))) { @@ -514,7 +514,7 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, if(af == AF_INET) conn->ip_version = CURL_IPRESOLVE_V4; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 else if(af == AF_INET6) conn->ip_version = CURL_IPRESOLVE_V6; #endif @@ -547,7 +547,7 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, } if(done > 0) { -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 /* IPv6 address */ if(af == AF_INET6) { #ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID @@ -596,7 +596,7 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, } else { /* no device was given, prepare sa to match af's needs */ -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 if(af == AF_INET6) { si6->sin6_family = AF_INET6; si6->sin6_port = htons(port); @@ -616,16 +616,6 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, for(;;) { if(bind(sockfd, sock, sizeof_sa) >= 0) { /* we succeeded to bind */ - struct Curl_sockaddr_storage add; - curl_socklen_t size = sizeof(add); - memset(&add, 0, sizeof(struct Curl_sockaddr_storage)); - if(getsockname(sockfd, (struct sockaddr *) &add, &size) < 0) { - char buffer[STRERROR_LEN]; - data->state.os_errno = error = SOCKERRNO; - failf(data, "getsockname() failed with errno %d: %s", - error, Curl_strerror(error, buffer, sizeof(buffer))); - return CURLE_INTERFACE_FAILED; - } infof(data, "Local port: %hu", port); conn->bits.bound = TRUE; return CURLE_OK; @@ -639,7 +629,7 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, /* We reuse/clobber the port variable here below */ if(sock->sa_family == AF_INET) si4->sin_port = ntohs(port); -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 else si6->sin6_port = ntohs(port); #endif @@ -923,7 +913,8 @@ static CURLcode set_local_ip(struct Curl_cfilter *cf, struct cf_socket_ctx *ctx = cf->ctx; #ifdef HAVE_GETSOCKNAME - if(!(data->conn->handler->protocol & CURLPROTO_TFTP)) { + if((ctx->sock != CURL_SOCKET_BAD) && + !(data->conn->handler->protocol & CURLPROTO_TFTP)) { /* TFTP does not connect, so it cannot get the IP like this */ char buffer[STRERROR_LEN]; @@ -946,8 +937,8 @@ static CURLcode set_local_ip(struct Curl_cfilter *cf, } #else (void)data; - ctx->l_ip[0] = 0; - ctx->l_port = -1; + ctx->ip.local_ip[0] = 0; + ctx->ip.local_port = -1; #endif return CURLE_OK; } @@ -991,7 +982,7 @@ static CURLcode cf_socket_open(struct Curl_cfilter *cf, if(result) goto out; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 if(ctx->addr.family == AF_INET6) { set_ipv6_v6only(ctx->sock, 0); infof(data, " Trying [%s]:%d...", ctx->ip.remote_ip, ctx->ip.remote_port); @@ -1000,7 +991,7 @@ static CURLcode cf_socket_open(struct Curl_cfilter *cf, #endif infof(data, " Trying %s:%d...", ctx->ip.remote_ip, ctx->ip.remote_port); -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 is_tcp = (ctx->addr.family == AF_INET || ctx->addr.family == AF_INET6) && ctx->addr.socktype == SOCK_STREAM; @@ -1037,7 +1028,7 @@ static CURLcode cf_socket_open(struct Curl_cfilter *cf, #ifndef CURL_DISABLE_BINDLOCAL /* possibly bind the local end to an IP, interface or port */ if(ctx->addr.family == AF_INET -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 || ctx->addr.family == AF_INET6 #endif ) { @@ -1288,7 +1279,7 @@ static ssize_t cf_socket_send(struct Curl_cfilter *cf, struct Curl_easy *data, #ifdef DEBUGBUILD /* simulate network blocking/partial writes */ if(ctx->wblock_percent > 0) { - unsigned char c; + unsigned char c = 0; Curl_rand(data, &c, 1); if(c >= ((100-ctx->wblock_percent)*256/100)) { CURL_TRC_CF(data, cf, "send(len=%zu) SIMULATE EWOULDBLOCK", orig_len); @@ -1366,7 +1357,7 @@ static ssize_t cf_socket_recv(struct Curl_cfilter *cf, struct Curl_easy *data, #ifdef DEBUGBUILD /* simulate network blocking/partial reads */ if(cf->cft != &Curl_cft_udp && ctx->rblock_percent > 0) { - unsigned char c; + unsigned char c = 0; Curl_rand(data, &c, 1); if(c >= ((100-ctx->rblock_percent)*256/100)) { CURL_TRC_CF(data, cf, "recv(len=%zu) SIMULATE EWOULDBLOCK", len); @@ -1449,7 +1440,7 @@ static void cf_socket_active(struct Curl_cfilter *cf, struct Curl_easy *data) /* the first socket info gets some specials */ if(cf->sockindex == FIRSTSOCKET) { cf->conn->remote_addr = &ctx->addr; - #ifdef ENABLE_IPV6 + #ifdef USE_IPV6 cf->conn->bits.ipv6 = (ctx->addr.family == AF_INET6)? TRUE : FALSE; #endif Curl_persistconninfo(data, cf->conn, &ctx->ip); diff --git a/lib/cfilters.c b/lib/cfilters.c index 32329cf..a327fa1 100644 --- a/lib/cfilters.c +++ b/lib/cfilters.c @@ -590,7 +590,7 @@ CURLcode Curl_conn_ev_data_idle(struct Curl_easy *data) /** * Notify connection filters that the transfer represented by `data` - * is donw with sending data (e.g. has uploaded everything). + * is done with sending data (e.g. has uploaded everything). */ void Curl_conn_ev_data_done_send(struct Curl_easy *data) { @@ -670,6 +670,19 @@ size_t Curl_conn_get_max_concurrent(struct Curl_easy *data, return (result || n <= 0)? 1 : (size_t)n; } +int Curl_conn_get_stream_error(struct Curl_easy *data, + struct connectdata *conn, + int sockindex) +{ + CURLcode result; + int n = 0; + + struct Curl_cfilter *cf = conn->cfilter[sockindex]; + result = cf? cf->cft->query(cf, data, CF_QUERY_STREAM_ERROR, + &n, NULL) : CURLE_UNKNOWN_OPTION; + return (result || n < 0)? 0 : n; +} + int Curl_conn_sockindex(struct Curl_easy *data, curl_socket_t sockfd) { if(data && data->conn && diff --git a/lib/cfilters.h b/lib/cfilters.h index c90b1f4..dcfc1b7 100644 --- a/lib/cfilters.h +++ b/lib/cfilters.h @@ -160,6 +160,7 @@ typedef CURLcode Curl_cft_cntrl(struct Curl_cfilter *cf, #define CF_QUERY_SOCKET 3 /* - curl_socket_t */ #define CF_QUERY_TIMER_CONNECT 4 /* - struct curltime */ #define CF_QUERY_TIMER_APPCONNECT 5 /* - struct curltime */ +#define CF_QUERY_STREAM_ERROR 6 /* error code - */ /** * Query the cfilter for properties. Filters ignorant of a query will @@ -178,10 +179,12 @@ typedef CURLcode Curl_cft_query(struct Curl_cfilter *cf, * connection, etc. * CF_TYPE_SSL: provide SSL/TLS * CF_TYPE_MULTIPLEX: provides multiplexing of easy handles + * CF_TYPE_PROXY provides proxying */ #define CF_TYPE_IP_CONNECT (1 << 0) #define CF_TYPE_SSL (1 << 1) #define CF_TYPE_MULTIPLEX (1 << 2) +#define CF_TYPE_PROXY (1 << 3) /* A connection filter type, e.g. specific implementation. */ struct Curl_cftype { @@ -449,7 +452,7 @@ CURLcode Curl_conn_ev_data_idle(struct Curl_easy *data); /** * Notify connection filters that the transfer represented by `data` - * is donw with sending data (e.g. has uploaded everything). + * is done with sending data (e.g. has uploaded everything). */ void Curl_conn_ev_data_done_send(struct Curl_easy *data); @@ -496,6 +499,12 @@ size_t Curl_conn_get_max_concurrent(struct Curl_easy *data, struct connectdata *conn, int sockindex); +/** + * Get the underlying error code for a transfer stream or 0 if not known. + */ +int Curl_conn_get_stream_error(struct Curl_easy *data, + struct connectdata *conn, + int sockindex); /** * Get the index of the given socket in the connection's sockets. diff --git a/lib/conncache.c b/lib/conncache.c index 0d26090..2b5ee4f 100644 --- a/lib/conncache.c +++ b/lib/conncache.c @@ -68,8 +68,7 @@ static void bundle_destroy(struct connectbundle *bundle) static void bundle_add_conn(struct connectbundle *bundle, struct connectdata *conn) { - Curl_llist_insert_next(&bundle->conn_list, bundle->conn_list.tail, conn, - &conn->bundle_node); + Curl_llist_append(&bundle->conn_list, conn, &conn->bundle_node); conn->bundle = bundle; bundle->num_connections++; } @@ -101,7 +100,7 @@ static void free_bundle_hash_entry(void *freethis) bundle_destroy(b); } -int Curl_conncache_init(struct conncache *connc, int size) +int Curl_conncache_init(struct conncache *connc, size_t size) { /* allocate a new easy handle to use when closing cached connections */ connc->closure_handle = curl_easy_init(); @@ -141,7 +140,7 @@ static void hashkey(struct connectdata *conn, char *buf, size_t len) hostname = conn->host.name; /* put the numbers first so that the hostname gets cut off if too long */ -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 msnprintf(buf, len, "%u/%ld/%s", conn->scope_id, port, hostname); #else msnprintf(buf, len, "%ld/%s", port, hostname); diff --git a/lib/conncache.h b/lib/conncache.h index c60f844..e685123 100644 --- a/lib/conncache.h +++ b/lib/conncache.h @@ -85,7 +85,7 @@ struct connectbundle { }; /* returns 1 on error, 0 is fine */ -int Curl_conncache_init(struct conncache *, int size); +int Curl_conncache_init(struct conncache *, size_t size); void Curl_conncache_destroy(struct conncache *connc); /* return the correct bundle, to a host or a proxy */ diff --git a/lib/connect.c b/lib/connect.c index e457006..bf85e64 100644 --- a/lib/connect.c +++ b/lib/connect.c @@ -195,7 +195,7 @@ bool Curl_addr2string(struct sockaddr *sa, curl_socklen_t salen, char *addr, int *port) { struct sockaddr_in *si = NULL; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 struct sockaddr_in6 *si6 = NULL; #endif #if (defined(HAVE_SYS_UN_H) || defined(WIN32_SOCKADDR_UN)) && defined(AF_UNIX) @@ -214,7 +214,7 @@ bool Curl_addr2string(struct sockaddr *sa, curl_socklen_t salen, return TRUE; } break; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 case AF_INET6: si6 = (struct sockaddr_in6 *)(void *) sa; if(Curl_inet_ntop(sa->sa_family, &si6->sin6_addr, @@ -401,7 +401,7 @@ static CURLcode eyeballer_new(struct eyeballer **pballer, return CURLE_OUT_OF_MEMORY; baller->name = ((ai_family == AF_INET)? "ipv4" : ( -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 (ai_family == AF_INET6)? "ipv6" : #endif "ip")); @@ -779,7 +779,7 @@ static CURLcode start_connect(struct Curl_cfilter *cf, /* any IP version is allowed */ ai_family0 = remotehost->addr? remotehost->addr->ai_family : 0; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 ai_family1 = ai_family0 == AF_INET6 ? AF_INET : AF_INET6; #else @@ -790,7 +790,7 @@ static CURLcode start_connect(struct Curl_cfilter *cf, /* only one IP version is allowed */ ai_family0 = (conn->ip_version == CURL_IPRESOLVE_V4) ? AF_INET : -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 AF_INET6; #else AF_UNSPEC; @@ -1117,7 +1117,7 @@ const #endif struct transport_provider transport_providers[] = { { TRNSPRT_TCP, Curl_cf_tcp_create }, -#ifdef ENABLE_QUIC +#ifdef USE_HTTP3 { TRNSPRT_QUIC, Curl_cf_quic_create }, #endif #ifndef CURL_DISABLE_TFTP diff --git a/lib/content_encoding.c b/lib/content_encoding.c index c1abf24..d34d3a1 100644 --- a/lib/content_encoding.c +++ b/lib/content_encoding.c @@ -300,7 +300,7 @@ static CURLcode deflate_do_write(struct Curl_easy *data, struct zlib_writer *zp = (struct zlib_writer *) writer; z_stream *z = &zp->z; /* zlib state structure */ - if(!(type & CLIENTWRITE_BODY)) + if(!(type & CLIENTWRITE_BODY) || !nbytes) return Curl_cwriter_write(data, writer->next, type, buf, nbytes); /* Set the compressed input when this function is called */ @@ -457,7 +457,7 @@ static CURLcode gzip_do_write(struct Curl_easy *data, struct zlib_writer *zp = (struct zlib_writer *) writer; z_stream *z = &zp->z; /* zlib state structure */ - if(!(type & CLIENTWRITE_BODY)) + if(!(type & CLIENTWRITE_BODY) || !nbytes) return Curl_cwriter_write(data, writer->next, type, buf, nbytes); if(zp->zlib_init == ZLIB_INIT_GZIP) { @@ -669,7 +669,7 @@ static CURLcode brotli_do_write(struct Curl_easy *data, CURLcode result = CURLE_OK; BrotliDecoderResult r = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT; - if(!(type & CLIENTWRITE_BODY)) + if(!(type & CLIENTWRITE_BODY) || !nbytes) return Curl_cwriter_write(data, writer->next, type, buf, nbytes); if(!bp->br) @@ -762,7 +762,7 @@ static CURLcode zstd_do_write(struct Curl_easy *data, ZSTD_outBuffer out; size_t errorCode; - if(!(type & CLIENTWRITE_BODY)) + if(!(type & CLIENTWRITE_BODY) || !nbytes) return Curl_cwriter_write(data, writer->next, type, buf, nbytes); if(!zp->decomp) { @@ -916,7 +916,7 @@ static CURLcode error_do_write(struct Curl_easy *data, (void) buf; (void) nbytes; - if(!(type & CLIENTWRITE_BODY)) + if(!(type & CLIENTWRITE_BODY) || !nbytes) return Curl_cwriter_write(data, writer->next, type, buf, nbytes); failf(data, "Unrecognized content encoding type. " @@ -978,6 +978,7 @@ CURLcode Curl_build_unencoding_stack(struct Curl_easy *data, do { const char *name; size_t namelen; + bool is_chunked = FALSE; /* Parse a single encoding name. */ while(ISBLANK(*enclist) || *enclist == ',') @@ -993,10 +994,11 @@ CURLcode Curl_build_unencoding_stack(struct Curl_easy *data, const struct Curl_cwtype *cwt; struct Curl_cwriter *writer; + is_chunked = (is_transfer && (namelen == 7) && + strncasecompare(name, "chunked", 7)); /* if we skip the decoding in this phase, do not look further. * Exception is "chunked" transfer-encoding which always must happen */ - if((is_transfer && !data->set.http_transfer_encoding && - (namelen != 7 || !strncasecompare(name, "chunked", 7))) || + if((is_transfer && !data->set.http_transfer_encoding && !is_chunked) || (!is_transfer && data->set.http_ce_skip)) { /* not requested, ignore */ return CURLE_OK; @@ -1009,6 +1011,31 @@ CURLcode Curl_build_unencoding_stack(struct Curl_easy *data, } cwt = find_unencode_writer(name, namelen, phase); + if(cwt && is_chunked && Curl_cwriter_get_by_type(data, cwt)) { + /* A 'chunked' transfer encoding has already been added. + * Ignore duplicates. See #13451. + * Also RFC 9112, ch. 6.1: + * "A sender MUST NOT apply the chunked transfer coding more than + * once to a message body." + */ + return CURLE_OK; + } + + if(is_transfer && !is_chunked && + Curl_cwriter_get_by_name(data, "chunked")) { + /* RFC 9112, ch. 6.1: + * "If any transfer coding other than chunked is applied to a + * response's content, the sender MUST either apply chunked as the + * final transfer coding or terminate the message by closing the + * connection." + * "chunked" must be the last added to be the first in its phase, + * reject this. + */ + failf(data, "Reject response due to 'chunked' not being the last " + "Transfer-Encoding"); + return CURLE_BAD_CONTENT_ENCODING; + } + if(!cwt) cwt = &error_writer; /* Defer error at use. */ diff --git a/lib/cookie.c b/lib/cookie.c index c1ed291..837caaa 100644 --- a/lib/cookie.c +++ b/lib/cookie.c @@ -886,7 +886,8 @@ Curl_cookie_add(struct Curl_easy *data, * Now loop through the fields and init the struct we already have * allocated */ - for(ptr = firstptr, fields = 0; ptr && !badcookie; + fields = 0; + for(ptr = firstptr; ptr && !badcookie; ptr = strtok_r(NULL, "\t", &tok_buf), fields++) { switch(fields) { case 0: diff --git a/lib/curl_addrinfo.c b/lib/curl_addrinfo.c index f9211d3..c32f24d 100644 --- a/lib/curl_addrinfo.c +++ b/lib/curl_addrinfo.c @@ -130,7 +130,7 @@ Curl_getaddrinfo_ex(const char *nodename, /* settle family-specific sockaddr structure size. */ if(ai->ai_family == AF_INET) ss_size = sizeof(struct sockaddr_in); -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 else if(ai->ai_family == AF_INET6) ss_size = sizeof(struct sockaddr_in6); #endif @@ -259,7 +259,7 @@ Curl_he2ai(const struct hostent *he, int port) struct Curl_addrinfo *prevai = NULL; struct Curl_addrinfo *firstai = NULL; struct sockaddr_in *addr; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 struct sockaddr_in6 *addr6; #endif CURLcode result = CURLE_OK; @@ -275,7 +275,7 @@ Curl_he2ai(const struct hostent *he, int port) for(i = 0; (curr = he->h_addr_list[i]) != NULL; i++) { size_t ss_size; size_t namelen = strlen(he->h_name) + 1; /* include null-terminator */ -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 if(he->h_addrtype == AF_INET6) ss_size = sizeof(struct sockaddr_in6); else @@ -321,7 +321,7 @@ Curl_he2ai(const struct hostent *he, int port) addr->sin_port = htons((unsigned short)port); break; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 case AF_INET6: addr6 = (void *)ai->ai_addr; /* storage area for this info */ @@ -348,7 +348,7 @@ struct namebuff { struct hostent hostentry; union { struct in_addr ina4; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 struct in6_addr ina6; #endif } addrentry; @@ -401,7 +401,7 @@ Curl_ip2addr(int af, const void *inaddr, const char *hostname, int port) addrentry = (void *)&buf->addrentry.ina4; memcpy(addrentry, inaddr, sizeof(struct in_addr)); break; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 case AF_INET6: addrsize = sizeof(struct in6_addr); addrentry = (void *)&buf->addrentry.ina6; @@ -447,7 +447,7 @@ struct Curl_addrinfo *Curl_str2addr(char *address, int port) if(Curl_inet_pton(AF_INET, address, &in) > 0) /* This is a dotted IP address 123.123.123.123-style */ return Curl_ip2addr(AF_INET, &in, address, port); -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 { struct in6_addr in6; if(Curl_inet_pton(AF_INET6, address, &in6) > 0) @@ -570,7 +570,7 @@ void Curl_addrinfo_set_port(struct Curl_addrinfo *addrinfo, int port) { struct Curl_addrinfo *ca; struct sockaddr_in *addr; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 struct sockaddr_in6 *addr6; #endif for(ca = addrinfo; ca != NULL; ca = ca->ai_next) { @@ -580,7 +580,7 @@ void Curl_addrinfo_set_port(struct Curl_addrinfo *addrinfo, int port) addr->sin_port = htons((unsigned short)port); break; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 case AF_INET6: addr6 = (void *)ca->ai_addr; /* storage area for this info */ addr6->sin6_port = htons((unsigned short)port); diff --git a/lib/curl_config.h.cmake b/lib/curl_config.h.cmake index 0f4db69..3a46c64 100644 --- a/lib/curl_config.h.cmake +++ b/lib/curl_config.h.cmake @@ -163,7 +163,7 @@ #cmakedefine USE_WIN32_LDAP 1 /* Define if you want to enable IPv6 support */ -#cmakedefine ENABLE_IPV6 1 +#cmakedefine USE_IPV6 1 /* Define to 1 if you have the alarm function. */ #cmakedefine HAVE_ALARM 1 @@ -199,6 +199,12 @@ /* Define to 1 if you have the `closesocket' function. */ #cmakedefine HAVE_CLOSESOCKET 1 +/* Define to 1 if you have the <dirent.h> header file. */ +#cmakedefine HAVE_DIRENT_H 1 + +/* Define to 1 if you have the `opendir' function. */ +#cmakedefine HAVE_OPENDIR 1 + /* Define to 1 if you have the fcntl function. */ #cmakedefine HAVE_FCNTL 1 @@ -704,6 +710,9 @@ ${SIZEOF_TIME_T_CODE} /* if OpenSSL is in use */ #cmakedefine USE_OPENSSL 1 +/* if librtmp/rtmpdump is in use */ +#cmakedefine USE_LIBRTMP 1 + /* Define to 1 if you don't want the OpenSSL configuration to be loaded automatically */ #cmakedefine CURL_DISABLE_OPENSSL_AUTO_LOAD_CONFIG 1 @@ -767,12 +776,6 @@ ${SIZEOF_TIME_T_CODE} /* Type to use in place of in_addr_t when system does not provide it. */ #cmakedefine in_addr_t ${in_addr_t} -/* Define to `__inline__' or `__inline' if that's what the C compiler - calls it, or to nothing if 'inline' is not supported under any name. */ -#ifndef __cplusplus -#undef inline -#endif - /* Define to `unsigned int' if <sys/types.h> does not define. */ #cmakedefine size_t ${size_t} @@ -785,6 +788,9 @@ ${SIZEOF_TIME_T_CODE} /* to enable Windows IDN */ #cmakedefine USE_WIN32_IDN 1 +/* to enable Apple IDN */ +#cmakedefine USE_APPLE_IDN 1 + /* Define to 1 to enable websocket support. */ #cmakedefine USE_WEBSOCKETS 1 @@ -796,3 +802,9 @@ ${SIZEOF_TIME_T_CODE} /* Define to 1 to enable TLS-SRP support. */ #cmakedefine USE_TLS_SRP 1 + +/* Define to 1 to query for HTTPSRR when using DoH */ +#cmakedefine USE_HTTPSRR 1 + +/* if ECH support is available */ +#cmakedefine USE_ECH 1 diff --git a/lib/curl_gethostname.c b/lib/curl_gethostname.c index 706b2e6..bd9b220 100644 --- a/lib/curl_gethostname.c +++ b/lib/curl_gethostname.c @@ -68,7 +68,7 @@ int Curl_gethostname(char * const name, GETHOSTNAME_TYPE_ARG2 namelen) /* Override host name when environment variable CURL_GETHOSTNAME is set */ const char *force_hostname = getenv("CURL_GETHOSTNAME"); if(force_hostname) { - strncpy(name, force_hostname, namelen); + strncpy(name, force_hostname, namelen - 1); err = 0; } else { diff --git a/lib/curl_multibyte.c b/lib/curl_multibyte.c index ff21098..86ac74f 100644 --- a/lib/curl_multibyte.c +++ b/lib/curl_multibyte.c @@ -159,21 +159,4 @@ int curlx_win32_stat(const char *path, struct_stat *buffer) #endif } -int curlx_win32_access(const char *path, int mode) -{ -#if defined(_UNICODE) - int result = -1; - wchar_t *path_w = curlx_convert_UTF8_to_wchar(path); - if(path_w) { - result = _waccess(path_w, mode); - curlx_unicodefree(path_w); - } - else - errno = EINVAL; - return result; -#else - return _access(path, mode); -#endif -} - #endif /* USE_WIN32_LARGE_FILES || USE_WIN32_SMALL_FILES */ diff --git a/lib/curl_ntlm_wb.c b/lib/curl_ntlm_wb.c deleted file mode 100644 index acb0093..0000000 --- a/lib/curl_ntlm_wb.c +++ /dev/null @@ -1,500 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - * SPDX-License-Identifier: curl - * - ***************************************************************************/ - -#include "curl_setup.h" - -#if !defined(CURL_DISABLE_HTTP) && defined(USE_NTLM) && \ - defined(NTLM_WB_ENABLED) - -/* - * NTLM details: - * - * https://davenport.sourceforge.net/ntlm.html - * https://www.innovation.ch/java/ntlm.html - */ - -#define DEBUG_ME 0 - -#ifdef HAVE_SYS_WAIT_H -#include <sys/wait.h> -#endif -#include <signal.h> -#ifdef HAVE_PWD_H -#include <pwd.h> -#endif - -#include "urldata.h" -#include "sendf.h" -#include "select.h" -#include "vauth/ntlm.h" -#include "curl_ntlm_core.h" -#include "curl_ntlm_wb.h" -#include "url.h" -#include "strerror.h" -#include "strdup.h" -#include "strcase.h" - -/* The last 3 #include files should be in this order */ -#include "curl_printf.h" -#include "curl_memory.h" -#include "memdebug.h" - -#if DEBUG_ME -# define DEBUG_OUT(x) x -#else -# define DEBUG_OUT(x) Curl_nop_stmt -#endif - -/* Portable 'sclose_nolog' used only in child process instead of 'sclose' - to avoid fooling the socket leak detector */ -#ifdef HAVE_PIPE -# define sclose_nolog(x) close((x)) -#elif defined(HAVE_CLOSESOCKET) -# define sclose_nolog(x) closesocket((x)) -#elif defined(HAVE_CLOSESOCKET_CAMEL) -# define sclose_nolog(x) CloseSocket((x)) -#else -# define sclose_nolog(x) close((x)) -#endif - -static void ntlm_wb_cleanup(struct ntlmdata *ntlm) -{ - if(ntlm->ntlm_auth_hlpr_socket != CURL_SOCKET_BAD) { - sclose(ntlm->ntlm_auth_hlpr_socket); - ntlm->ntlm_auth_hlpr_socket = CURL_SOCKET_BAD; - } - - if(ntlm->ntlm_auth_hlpr_pid) { - int i; - for(i = 0; i < 4; i++) { - pid_t ret = waitpid(ntlm->ntlm_auth_hlpr_pid, NULL, WNOHANG); - if(ret == ntlm->ntlm_auth_hlpr_pid || errno == ECHILD) - break; - switch(i) { - case 0: - kill(ntlm->ntlm_auth_hlpr_pid, SIGTERM); - break; - case 1: - /* Give the process another moment to shut down cleanly before - bringing down the axe */ - Curl_wait_ms(1); - break; - case 2: - kill(ntlm->ntlm_auth_hlpr_pid, SIGKILL); - break; - case 3: - break; - } - } - ntlm->ntlm_auth_hlpr_pid = 0; - } - - Curl_safefree(ntlm->challenge); - Curl_safefree(ntlm->response); -} - -static CURLcode ntlm_wb_init(struct Curl_easy *data, struct ntlmdata *ntlm, - const char *userp) -{ - curl_socket_t sockfds[2]; - pid_t child_pid; - const char *username; - char *slash, *domain = NULL; - const char *ntlm_auth = NULL; - char *ntlm_auth_alloc = NULL; -#if defined(HAVE_GETPWUID_R) && defined(HAVE_GETEUID) - struct passwd pw, *pw_res; - char pwbuf[1024]; -#endif - char buffer[STRERROR_LEN]; - -#if defined(CURL_DISABLE_VERBOSE_STRINGS) - (void) data; -#endif - - /* Return if communication with ntlm_auth already set up */ - if(ntlm->ntlm_auth_hlpr_socket != CURL_SOCKET_BAD || - ntlm->ntlm_auth_hlpr_pid) - return CURLE_OK; - - username = userp; - /* The real ntlm_auth really doesn't like being invoked with an - empty username. It won't make inferences for itself, and expects - the client to do so (mostly because it's really designed for - servers like squid to use for auth, and client support is an - afterthought for it). So try hard to provide a suitable username - if we don't already have one. But if we can't, provide the - empty one anyway. Perhaps they have an implementation of the - ntlm_auth helper which *doesn't* need it so we might as well try */ - if(!username || !username[0]) { - username = getenv("NTLMUSER"); - if(!username || !username[0]) - username = getenv("LOGNAME"); - if(!username || !username[0]) - username = getenv("USER"); -#if defined(HAVE_GETPWUID_R) && defined(HAVE_GETEUID) - if((!username || !username[0]) && - !getpwuid_r(geteuid(), &pw, pwbuf, sizeof(pwbuf), &pw_res) && - pw_res) { - username = pw.pw_name; - } -#endif - if(!username || !username[0]) - username = userp; - } - slash = strpbrk(username, "\\/"); - if(slash) { - domain = strdup(username); - if(!domain) - return CURLE_OUT_OF_MEMORY; - slash = domain + (slash - username); - *slash = '\0'; - username = username + (slash - domain) + 1; - } - - /* For testing purposes, when DEBUGBUILD is defined and environment - variable CURL_NTLM_WB_FILE is set a fake_ntlm is used to perform - NTLM challenge/response which only accepts commands and output - strings pre-written in test case definitions */ -#ifdef DEBUGBUILD - ntlm_auth_alloc = curl_getenv("CURL_NTLM_WB_FILE"); - if(ntlm_auth_alloc) - ntlm_auth = ntlm_auth_alloc; - else -#endif - ntlm_auth = NTLM_WB_FILE; - - if(access(ntlm_auth, X_OK) != 0) { - failf(data, "Could not access ntlm_auth: %s errno %d: %s", - ntlm_auth, errno, Curl_strerror(errno, buffer, sizeof(buffer))); - goto done; - } - - if(wakeup_create(sockfds)) { - failf(data, "Could not open socket pair. errno %d: %s", - errno, Curl_strerror(errno, buffer, sizeof(buffer))); - goto done; - } - - child_pid = fork(); - if(child_pid == -1) { - wakeup_close(sockfds[0]); - wakeup_close(sockfds[1]); - failf(data, "Could not fork. errno %d: %s", - errno, Curl_strerror(errno, buffer, sizeof(buffer))); - goto done; - } - else if(!child_pid) { - /* - * child process - */ - - /* Don't use sclose in the child since it fools the socket leak detector */ - sclose_nolog(sockfds[0]); - if(dup2(sockfds[1], STDIN_FILENO) == -1) { - failf(data, "Could not redirect child stdin. errno %d: %s", - errno, Curl_strerror(errno, buffer, sizeof(buffer))); - exit(1); - } - - if(dup2(sockfds[1], STDOUT_FILENO) == -1) { - failf(data, "Could not redirect child stdout. errno %d: %s", - errno, Curl_strerror(errno, buffer, sizeof(buffer))); - exit(1); - } - - if(domain) - execl(ntlm_auth, ntlm_auth, - "--helper-protocol", "ntlmssp-client-1", - "--use-cached-creds", - "--username", username, - "--domain", domain, - NULL); - else - execl(ntlm_auth, ntlm_auth, - "--helper-protocol", "ntlmssp-client-1", - "--use-cached-creds", - "--username", username, - NULL); - - sclose_nolog(sockfds[1]); - failf(data, "Could not execl(). errno %d: %s", - errno, Curl_strerror(errno, buffer, sizeof(buffer))); - exit(1); - } - - sclose(sockfds[1]); - ntlm->ntlm_auth_hlpr_socket = sockfds[0]; - ntlm->ntlm_auth_hlpr_pid = child_pid; - free(domain); - free(ntlm_auth_alloc); - return CURLE_OK; - -done: - free(domain); - free(ntlm_auth_alloc); - return CURLE_REMOTE_ACCESS_DENIED; -} - -/* if larger than this, something is seriously wrong */ -#define MAX_NTLM_WB_RESPONSE 100000 - -static CURLcode ntlm_wb_response(struct Curl_easy *data, struct ntlmdata *ntlm, - const char *input, curlntlm state) -{ - size_t len_in = strlen(input), len_out = 0; - struct dynbuf b; - char *ptr = NULL; - unsigned char buf[1024]; - Curl_dyn_init(&b, MAX_NTLM_WB_RESPONSE); - - while(len_in > 0) { - ssize_t written = wakeup_write(ntlm->ntlm_auth_hlpr_socket, input, len_in); - if(written == -1) { - /* Interrupted by a signal, retry it */ - if(errno == EINTR) - continue; - /* write failed if other errors happen */ - goto done; - } - input += written; - len_in -= written; - } - /* Read one line */ - while(1) { - ssize_t size = - wakeup_read(ntlm->ntlm_auth_hlpr_socket, buf, sizeof(buf)); - if(size == -1) { - if(errno == EINTR) - continue; - goto done; - } - else if(size == 0) - goto done; - - if(Curl_dyn_addn(&b, buf, size)) - goto done; - - len_out = Curl_dyn_len(&b); - ptr = Curl_dyn_ptr(&b); - if(len_out && ptr[len_out - 1] == '\n') { - ptr[len_out - 1] = '\0'; - break; /* done! */ - } - /* loop */ - } - - /* Samba/winbind installed but not configured */ - if(state == NTLMSTATE_TYPE1 && - len_out == 3 && - ptr[0] == 'P' && ptr[1] == 'W') - goto done; - /* invalid response */ - if(len_out < 4) - goto done; - if(state == NTLMSTATE_TYPE1 && - (ptr[0]!='Y' || ptr[1]!='R' || ptr[2]!=' ')) - goto done; - if(state == NTLMSTATE_TYPE2 && - (ptr[0]!='K' || ptr[1]!='K' || ptr[2]!=' ') && - (ptr[0]!='A' || ptr[1]!='F' || ptr[2]!=' ')) - goto done; - - ntlm->response = strdup(ptr + 3); - Curl_dyn_free(&b); - if(!ntlm->response) - return CURLE_OUT_OF_MEMORY; - return CURLE_OK; -done: - Curl_dyn_free(&b); - return CURLE_REMOTE_ACCESS_DENIED; -} - -CURLcode Curl_input_ntlm_wb(struct Curl_easy *data, - struct connectdata *conn, - bool proxy, - const char *header) -{ - struct ntlmdata *ntlm = proxy ? &conn->proxyntlm : &conn->ntlm; - curlntlm *state = proxy ? &conn->proxy_ntlm_state : &conn->http_ntlm_state; - - (void) data; /* In case it gets unused by nop log macros. */ - - if(!checkprefix("NTLM", header)) - return CURLE_BAD_CONTENT_ENCODING; - - header += strlen("NTLM"); - while(*header && ISSPACE(*header)) - header++; - - if(*header) { - ntlm->challenge = strdup(header); - if(!ntlm->challenge) - return CURLE_OUT_OF_MEMORY; - - *state = NTLMSTATE_TYPE2; /* We got a type-2 message */ - } - else { - if(*state == NTLMSTATE_LAST) { - infof(data, "NTLM auth restarted"); - Curl_http_auth_cleanup_ntlm_wb(conn); - } - else if(*state == NTLMSTATE_TYPE3) { - infof(data, "NTLM handshake rejected"); - Curl_http_auth_cleanup_ntlm_wb(conn); - *state = NTLMSTATE_NONE; - return CURLE_REMOTE_ACCESS_DENIED; - } - else if(*state >= NTLMSTATE_TYPE1) { - infof(data, "NTLM handshake failure (internal error)"); - return CURLE_REMOTE_ACCESS_DENIED; - } - - *state = NTLMSTATE_TYPE1; /* We should send away a type-1 */ - } - - return CURLE_OK; -} - -/* - * This is for creating ntlm header output by delegating challenge/response - * to Samba's winbind daemon helper ntlm_auth. - */ -CURLcode Curl_output_ntlm_wb(struct Curl_easy *data, struct connectdata *conn, - bool proxy) -{ - /* point to the address of the pointer that holds the string to send to the - server, which is for a plain host or for an HTTP proxy */ - char **allocuserpwd; - /* point to the name and password for this */ - const char *userp; - struct ntlmdata *ntlm; - curlntlm *state; - struct auth *authp; - - CURLcode res = CURLE_OK; - - DEBUGASSERT(conn); - DEBUGASSERT(data); - - if(proxy) { -#ifndef CURL_DISABLE_PROXY - allocuserpwd = &data->state.aptr.proxyuserpwd; - userp = conn->http_proxy.user; - ntlm = &conn->proxyntlm; - state = &conn->proxy_ntlm_state; - authp = &data->state.authproxy; -#else - return CURLE_NOT_BUILT_IN; -#endif - } - else { - allocuserpwd = &data->state.aptr.userpwd; - userp = conn->user; - ntlm = &conn->ntlm; - state = &conn->http_ntlm_state; - authp = &data->state.authhost; - } - authp->done = FALSE; - - /* not set means empty */ - if(!userp) - userp = ""; - - switch(*state) { - case NTLMSTATE_TYPE1: - default: - /* Use Samba's 'winbind' daemon to support NTLM authentication, - * by delegating the NTLM challenge/response protocol to a helper - * in ntlm_auth. - * https://web.archive.org/web/20190925164737 - * /devel.squid-cache.org/ntlm/squid_helper_protocol.html - * https://www.samba.org/samba/docs/man/manpages-3/winbindd.8.html - * https://www.samba.org/samba/docs/man/manpages-3/ntlm_auth.1.html - * Preprocessor symbol 'NTLM_WB_ENABLED' is defined when this - * feature is enabled and 'NTLM_WB_FILE' symbol holds absolute - * filename of ntlm_auth helper. - * If NTLM authentication using winbind fails, go back to original - * request handling process. - */ - /* Create communication with ntlm_auth */ - res = ntlm_wb_init(data, ntlm, userp); - if(res) - return res; - res = ntlm_wb_response(data, ntlm, "YR\n", *state); - if(res) - return res; - - free(*allocuserpwd); - *allocuserpwd = aprintf("%sAuthorization: NTLM %s\r\n", - proxy ? "Proxy-" : "", - ntlm->response); - DEBUG_OUT(fprintf(stderr, "**** Header %s\n ", *allocuserpwd)); - Curl_safefree(ntlm->response); - if(!*allocuserpwd) - return CURLE_OUT_OF_MEMORY; - break; - - case NTLMSTATE_TYPE2: { - char *input = aprintf("TT %s\n", ntlm->challenge); - if(!input) - return CURLE_OUT_OF_MEMORY; - res = ntlm_wb_response(data, ntlm, input, *state); - free(input); - if(res) - return res; - - free(*allocuserpwd); - *allocuserpwd = aprintf("%sAuthorization: NTLM %s\r\n", - proxy ? "Proxy-" : "", - ntlm->response); - DEBUG_OUT(fprintf(stderr, "**** %s\n ", *allocuserpwd)); - *state = NTLMSTATE_TYPE3; /* we sent a type-3 */ - authp->done = TRUE; - Curl_http_auth_cleanup_ntlm_wb(conn); - if(!*allocuserpwd) - return CURLE_OUT_OF_MEMORY; - break; - } - case NTLMSTATE_TYPE3: - /* connection is already authenticated, - * don't send a header in future requests */ - *state = NTLMSTATE_LAST; - FALLTHROUGH(); - case NTLMSTATE_LAST: - Curl_safefree(*allocuserpwd); - authp->done = TRUE; - break; - } - - return CURLE_OK; -} - -void Curl_http_auth_cleanup_ntlm_wb(struct connectdata *conn) -{ - ntlm_wb_cleanup(&conn->ntlm); - ntlm_wb_cleanup(&conn->proxyntlm); -} - -#endif /* !CURL_DISABLE_HTTP && USE_NTLM && NTLM_WB_ENABLED */ diff --git a/lib/curl_path.c b/lib/curl_path.c index 856423d..144f880 100644 --- a/lib/curl_path.c +++ b/lib/curl_path.c @@ -98,8 +98,8 @@ CURLcode Curl_getworkingpath(struct Curl_easy *data, return CURLE_OK; } -/* The get_pathname() function is being borrowed from OpenSSH sftp.c - version 4.6p1. */ +/* The original get_pathname() function came from OpenSSH sftp.c version + 4.6p1. */ /* * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org> * @@ -115,38 +115,37 @@ CURLcode Curl_getworkingpath(struct Curl_easy *data, * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -CURLcode Curl_get_pathname(const char **cpp, char **path, char *homedir) + +#define MAX_PATHLENGTH 65535 /* arbitrary long */ + +CURLcode Curl_get_pathname(const char **cpp, char **path, const char *homedir) { const char *cp = *cpp, *end; char quot; - unsigned int i, j; - size_t fullPathLength, pathLength; - bool relativePath = false; + unsigned int i; static const char WHITESPACE[] = " \t\r\n"; + struct dynbuf out; + CURLcode result; DEBUGASSERT(homedir); - if(!*cp || !homedir) { - *cpp = NULL; - *path = NULL; + *path = NULL; + *cpp = NULL; + if(!*cp || !homedir) return CURLE_QUOTE_ERROR; - } + + Curl_dyn_init(&out, MAX_PATHLENGTH); + /* Ignore leading whitespace */ cp += strspn(cp, WHITESPACE); - /* Allocate enough space for home directory and filename + separator */ - fullPathLength = strlen(cp) + strlen(homedir) + 2; - *path = malloc(fullPathLength); - if(!*path) - return CURLE_OUT_OF_MEMORY; /* Check for quoted filenames */ if(*cp == '\"' || *cp == '\'') { quot = *cp++; /* Search for terminating quote, unescape some chars */ - for(i = j = 0; i <= strlen(cp); i++) { + for(i = 0; i <= strlen(cp); i++) { if(cp[i] == quot) { /* Found quote */ i++; - (*path)[j] = '\0'; break; } if(cp[i] == '\0') { /* End of string */ @@ -159,40 +158,45 @@ CURLcode Curl_get_pathname(const char **cpp, char **path, char *homedir) goto fail; } } - (*path)[j++] = cp[i]; + result = Curl_dyn_addn(&out, &cp[i], 1); + if(result) + return result; } - if(j == 0) { + if(!Curl_dyn_len(&out)) goto fail; - } - *cpp = cp + i + strspn(cp + i, WHITESPACE); + + /* return pointer to second parameter if it exists */ + *cpp = &cp[i] + strspn(&cp[i], WHITESPACE); } else { /* Read to end of filename - either to whitespace or terminator */ end = strpbrk(cp, WHITESPACE); if(!end) end = strchr(cp, '\0'); + /* return pointer to second parameter if it exists */ *cpp = end + strspn(end, WHITESPACE); - pathLength = 0; - relativePath = (cp[0] == '/' && cp[1] == '~' && cp[2] == '/'); + /* Handling for relative path - prepend home directory */ - if(relativePath) { - strcpy(*path, homedir); - pathLength = strlen(homedir); - (*path)[pathLength++] = '/'; - (*path)[pathLength] = '\0'; + if(cp[0] == '/' && cp[1] == '~' && cp[2] == '/') { + result = Curl_dyn_add(&out, homedir); + if(!result) + result = Curl_dyn_addn(&out, "/", 1); + if(result) + return result; cp += 3; } /* Copy path name up until first "whitespace" */ - memcpy(&(*path)[pathLength], cp, (int)(end - cp)); - pathLength += (int)(end - cp); - (*path)[pathLength] = '\0'; + result = Curl_dyn_addn(&out, cp, (end - cp)); + if(result) + return result; } + *path = Curl_dyn_ptr(&out); return CURLE_OK; fail: - Curl_safefree(*path); + Curl_dyn_free(&out); return CURLE_QUOTE_ERROR; } diff --git a/lib/curl_path.h b/lib/curl_path.h index cbe51c2..6fdb2fd 100644 --- a/lib/curl_path.h +++ b/lib/curl_path.h @@ -45,5 +45,5 @@ CURLcode Curl_getworkingpath(struct Curl_easy *data, char *homedir, char **path); -CURLcode Curl_get_pathname(const char **cpp, char **path, char *homedir); +CURLcode Curl_get_pathname(const char **cpp, char **path, const char *homedir); #endif /* HEADER_CURL_PATH_H */ diff --git a/lib/curl_rtmp.c b/lib/curl_rtmp.c index b2f2ada..76eff78 100644 --- a/lib/curl_rtmp.c +++ b/lib/curl_rtmp.c @@ -35,8 +35,10 @@ #include "warnless.h" #include <curl/curl.h> #include <librtmp/rtmp.h> + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" #include "curl_memory.h" -/* The last #include file should be: */ #include "memdebug.h" #if defined(_WIN32) && !defined(USE_LWIPSOCK) @@ -66,7 +68,7 @@ static Curl_send rtmp_send; */ const struct Curl_handler Curl_handler_rtmp = { - "RTMP", /* scheme */ + "rtmp", /* scheme */ rtmp_setup_connection, /* setup_connection */ rtmp_do, /* do_it */ rtmp_done, /* done */ @@ -80,6 +82,7 @@ const struct Curl_handler Curl_handler_rtmp = { ZERO_NULL, /* perform_getsock */ rtmp_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_RTMP, /* defport */ @@ -89,7 +92,7 @@ const struct Curl_handler Curl_handler_rtmp = { }; const struct Curl_handler Curl_handler_rtmpt = { - "RTMPT", /* scheme */ + "rtmpt", /* scheme */ rtmp_setup_connection, /* setup_connection */ rtmp_do, /* do_it */ rtmp_done, /* done */ @@ -103,6 +106,7 @@ const struct Curl_handler Curl_handler_rtmpt = { ZERO_NULL, /* perform_getsock */ rtmp_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_RTMPT, /* defport */ @@ -112,7 +116,7 @@ const struct Curl_handler Curl_handler_rtmpt = { }; const struct Curl_handler Curl_handler_rtmpe = { - "RTMPE", /* scheme */ + "rtmpe", /* scheme */ rtmp_setup_connection, /* setup_connection */ rtmp_do, /* do_it */ rtmp_done, /* done */ @@ -126,6 +130,7 @@ const struct Curl_handler Curl_handler_rtmpe = { ZERO_NULL, /* perform_getsock */ rtmp_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_RTMP, /* defport */ @@ -135,7 +140,7 @@ const struct Curl_handler Curl_handler_rtmpe = { }; const struct Curl_handler Curl_handler_rtmpte = { - "RTMPTE", /* scheme */ + "rtmpte", /* scheme */ rtmp_setup_connection, /* setup_connection */ rtmp_do, /* do_it */ rtmp_done, /* done */ @@ -149,6 +154,7 @@ const struct Curl_handler Curl_handler_rtmpte = { ZERO_NULL, /* perform_getsock */ rtmp_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_RTMPT, /* defport */ @@ -158,7 +164,7 @@ const struct Curl_handler Curl_handler_rtmpte = { }; const struct Curl_handler Curl_handler_rtmps = { - "RTMPS", /* scheme */ + "rtmps", /* scheme */ rtmp_setup_connection, /* setup_connection */ rtmp_do, /* do_it */ rtmp_done, /* done */ @@ -172,6 +178,7 @@ const struct Curl_handler Curl_handler_rtmps = { ZERO_NULL, /* perform_getsock */ rtmp_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_RTMPS, /* defport */ @@ -181,7 +188,7 @@ const struct Curl_handler Curl_handler_rtmps = { }; const struct Curl_handler Curl_handler_rtmpts = { - "RTMPTS", /* scheme */ + "rtmpts", /* scheme */ rtmp_setup_connection, /* setup_connection */ rtmp_do, /* do_it */ rtmp_done, /* done */ @@ -195,6 +202,7 @@ const struct Curl_handler Curl_handler_rtmpts = { ZERO_NULL, /* perform_getsock */ rtmp_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_RTMPS, /* defport */ @@ -335,4 +343,20 @@ static ssize_t rtmp_send(struct Curl_easy *data, int sockindex, return num; } + +void Curl_rtmp_version(char *version, size_t len) +{ + char suff[2]; + if(RTMP_LIB_VERSION & 0xff) { + suff[0] = (RTMP_LIB_VERSION & 0xff) + 'a' - 1; + suff[1] = '\0'; + } + else + suff[0] = '\0'; + + msnprintf(version, len, "librtmp/%d.%d%s", + RTMP_LIB_VERSION >> 16, (RTMP_LIB_VERSION >> 8) & 0xff, + suff); +} + #endif /* USE_LIBRTMP */ diff --git a/lib/curl_rtmp.h b/lib/curl_rtmp.h index 9b93ee0..339d3a4 100644 --- a/lib/curl_rtmp.h +++ b/lib/curl_rtmp.h @@ -30,6 +30,8 @@ extern const struct Curl_handler Curl_handler_rtmpe; extern const struct Curl_handler Curl_handler_rtmpte; extern const struct Curl_handler Curl_handler_rtmps; extern const struct Curl_handler Curl_handler_rtmpts; + +void Curl_rtmp_version(char *version, size_t len); #endif #endif /* HEADER_CURL_RTMP_H */ diff --git a/lib/curl_sasl.c b/lib/curl_sasl.c index 66639cb..ba8911b 100644 --- a/lib/curl_sasl.c +++ b/lib/curl_sasl.c @@ -376,7 +376,7 @@ CURLcode Curl_sasl_start(struct SASL *sasl, struct Curl_easy *data, sasl->authused = SASL_MECH_EXTERNAL; if(force_ir || data->set.sasl_ir) - result = Curl_auth_create_external_message(conn->user, &resp); + Curl_auth_create_external_message(conn->user, &resp); } else if(data->state.aptr.user) { #if defined(USE_KERBEROS5) @@ -498,7 +498,7 @@ CURLcode Curl_sasl_start(struct SASL *sasl, struct Curl_easy *data, sasl->authused = SASL_MECH_LOGIN; if(force_ir || data->set.sasl_ir) - result = Curl_auth_create_login_message(conn->user, &resp); + Curl_auth_create_login_message(conn->user, &resp); } } @@ -576,14 +576,14 @@ CURLcode Curl_sasl_continue(struct SASL *sasl, struct Curl_easy *data, conn->user, conn->passwd, &resp); break; case SASL_LOGIN: - result = Curl_auth_create_login_message(conn->user, &resp); + Curl_auth_create_login_message(conn->user, &resp); newstate = SASL_LOGIN_PASSWD; break; case SASL_LOGIN_PASSWD: - result = Curl_auth_create_login_message(conn->passwd, &resp); + Curl_auth_create_login_message(conn->passwd, &resp); break; case SASL_EXTERNAL: - result = Curl_auth_create_external_message(conn->user, &resp); + Curl_auth_create_external_message(conn->user, &resp); break; #ifdef USE_GSASL case SASL_GSASL: diff --git a/lib/curl_setup.h b/lib/curl_setup.h index 286acc3..6c05b87 100644 --- a/lib/curl_setup.h +++ b/lib/curl_setup.h @@ -72,6 +72,11 @@ # endif #endif +/* Compatibility */ +#if defined(ENABLE_IPV6) +# define USE_IPV6 1 +#endif + /* * Include configuration script results or hand-crafted * configuration file for platforms which lack config tool. @@ -286,7 +291,8 @@ /* based on logic in "curl/mprintf.h" */ -#if (defined(__GNUC__) || defined(__clang__)) && \ +#if (defined(__GNUC__) || defined(__clang__) || \ + defined(__IAR_SYSTEMS_ICC__)) && \ defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \ !defined(CURL_NO_FMT_CHECKS) #if defined(__MINGW32__) && !defined(__clang__) @@ -309,7 +315,7 @@ #include <TargetConditionals.h> #define USE_RESOLVE_ON_IPS 1 # if TARGET_OS_MAC && !(defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) && \ - defined(ENABLE_IPV6) + defined(USE_IPV6) # define CURL_MACOS_CALL_COPYPROXIES 1 # endif #endif @@ -404,11 +410,9 @@ # define LSEEK_ERROR (__int64)-1 # define open curlx_win32_open # define fopen(fname,mode) curlx_win32_fopen(fname, mode) -# define access(fname,mode) curlx_win32_access(fname, mode) int curlx_win32_open(const char *filename, int oflag, ...); int curlx_win32_stat(const char *path, struct_stat *buffer); FILE *curlx_win32_fopen(const char *filename, const char *mode); - int curlx_win32_access(const char *path, int mode); #endif /* @@ -427,11 +431,9 @@ # define struct_stat struct _stat # define open curlx_win32_open # define fopen(fname,mode) curlx_win32_fopen(fname, mode) -# define access(fname,mode) curlx_win32_access(fname, mode) int curlx_win32_stat(const char *path, struct_stat *buffer); int curlx_win32_open(const char *filename, int oflag, ...); FILE *curlx_win32_fopen(const char *filename, const char *mode); - int curlx_win32_access(const char *path, int mode); # endif # define LSEEK_ERROR (long)-1 #endif @@ -506,11 +508,14 @@ # error "curl_off_t must be exactly 64 bits" #else typedef unsigned CURL_TYPEOF_CURL_OFF_T curl_uint64_t; + typedef CURL_TYPEOF_CURL_OFF_T curl_int64_t; # ifndef CURL_SUFFIX_CURL_OFF_TU # error "CURL_SUFFIX_CURL_OFF_TU must be defined" # endif # define CURL_UINT64_SUFFIX CURL_SUFFIX_CURL_OFF_TU # define CURL_UINT64_C(val) CURL_CONC_MACROS(val,CURL_UINT64_SUFFIX) +# define CURL_PRId64 CURL_FORMAT_CURL_OFF_T +# define CURL_PRIu64 CURL_FORMAT_CURL_OFF_TU #endif #if (SIZEOF_TIME_T == 4) @@ -617,9 +622,9 @@ * Mutually exclusive CURLRES_* definitions. */ -#if defined(ENABLE_IPV6) && defined(HAVE_GETADDRINFO) +#if defined(USE_IPV6) && defined(HAVE_GETADDRINFO) # define CURLRES_IPV6 -#elif defined(ENABLE_IPV6) && (defined(_WIN32) || defined(__CYGWIN__)) +#elif defined(USE_IPV6) && (defined(_WIN32) || defined(__CYGWIN__)) /* assume on Windows that IPv6 without getaddrinfo is a broken build */ # error "Unexpected build: IPv6 is enabled but getaddrinfo was not found." #else @@ -641,13 +646,14 @@ /* ---------------------------------------------------------------- */ -#if defined(HAVE_LIBIDN2) && defined(HAVE_IDN2_H) && !defined(USE_WIN32_IDN) +#if defined(HAVE_LIBIDN2) && defined(HAVE_IDN2_H) && \ + !defined(USE_WIN32_IDN) && !defined(USE_APPLE_IDN) /* The lib and header are present */ #define USE_LIBIDN2 #endif -#if defined(USE_LIBIDN2) && defined(USE_WIN32_IDN) -#error "Both libidn2 and WinIDN are enabled, choose one." +#if defined(USE_LIBIDN2) && (defined(USE_WIN32_IDN) || defined(USE_APPLE_IDN)) +#error "libidn2 cannot be enabled with WinIDN or AppleIDN, choose one." #endif #define LIBIDN_REQUIRED_VERSION "0.4.1" @@ -701,6 +707,13 @@ ((__GNUC__ == 2) && defined(__GNUC_MINOR__) && (__GNUC_MINOR__ >= 7))) # define UNUSED_PARAM __attribute__((__unused__)) # define WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#elif defined(__IAR_SYSTEMS_ICC__) +# define UNUSED_PARAM __attribute__((__unused__)) +# if (__VER__ >= 9040001) +# define WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +# else +# define WARN_UNUSED_RESULT +# endif #else # define UNUSED_PARAM /* NOTHING */ # define WARN_UNUSED_RESULT @@ -709,7 +722,8 @@ /* noreturn attribute */ #if !defined(CURL_NORETURN) -#if (defined(__GNUC__) && (__GNUC__ >= 3)) || defined(__clang__) +#if (defined(__GNUC__) && (__GNUC__ >= 3)) || defined(__clang__) || \ + defined(__IAR_SYSTEMS_ICC__) # define CURL_NORETURN __attribute__((__noreturn__)) #elif defined(_MSC_VER) && (_MSC_VER >= 1200) # define CURL_NORETURN __declspec(noreturn) @@ -852,7 +866,6 @@ int getpwuid_r(uid_t uid, struct passwd *pwd, char *buf, #error "Multi-SSL combined with QUIC is not supported" #endif -#define ENABLE_QUIC #define USE_HTTP3 #endif @@ -883,4 +896,26 @@ int getpwuid_r(uid_t uid, struct passwd *pwd, char *buf, #define OPENSSL_SUPPRESS_DEPRECATED #endif +#if defined(inline) + /* 'inline' is defined as macro and assumed to be correct */ + /* No need for 'inline' replacement */ +#elif defined(__cplusplus) + /* The code is compiled with C++ compiler. + C++ always supports 'inline'. */ + /* No need for 'inline' replacement */ +#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901 + /* C99 (and later) supports 'inline' keyword */ + /* No need for 'inline' replacement */ +#elif defined(__GNUC__) && __GNUC__ >= 3 + /* GCC supports '__inline__' as an extension */ +# define inline __inline__ +#elif defined(_MSC_VER) && _MSC_VER >= 1400 + /* MSC supports '__inline' from VS 2005 (or even earlier) */ +# define inline __inline +#else + /* Probably 'inline' is not supported by compiler. + Define to the empty string to be on the safe side. */ +# define inline /* empty */ +#endif + #endif /* HEADER_CURL_SETUP_H */ diff --git a/lib/curl_setup_once.h b/lib/curl_setup_once.h index bf0ee66..40c7bcb 100644 --- a/lib/curl_setup_once.h +++ b/lib/curl_setup_once.h @@ -164,9 +164,7 @@ struct timeval { (RECV_TYPE_ARG4)(0)) #else /* HAVE_RECV */ #ifndef sread - /* */ - Error Missing_definition_of_macro_sread - /* */ +#error "Missing definition of macro sread!" #endif #endif /* HAVE_RECV */ @@ -184,9 +182,7 @@ struct timeval { (SEND_TYPE_ARG4)(SEND_4TH_ARG)) #else /* HAVE_SEND */ #ifndef swrite - /* */ - Error Missing_definition_of_macro_swrite - /* */ +#error "Missing definition of macro swrite!" #endif #endif /* HAVE_SEND */ diff --git a/lib/curl_sha512_256.c b/lib/curl_sha512_256.c index 8af3839..5f2e995 100644 --- a/lib/curl_sha512_256.c +++ b/lib/curl_sha512_256.c @@ -44,7 +44,7 @@ # include <openssl/opensslv.h> # if (!defined(LIBRESSL_VERSION_NUMBER) && \ defined(OPENSSL_VERSION_NUMBER) && \ - (OPENSSL_VERSION_NUMBER >= 0x10100010L)) || \ + (OPENSSL_VERSION_NUMBER >= 0x10101000L)) || \ (defined(LIBRESSL_VERSION_NUMBER) && \ (LIBRESSL_VERSION_NUMBER >= 0x3080000fL)) # include <openssl/opensslconf.h> @@ -52,6 +52,27 @@ # include <openssl/evp.h> # define USE_OPENSSL_SHA512_256 1 # define HAS_SHA512_256_IMPLEMENTATION 1 +# ifdef __NetBSD__ +/* Some NetBSD versions has a bug in SHA-512/256. + * See https://gnats.netbsd.org/cgi-bin/query-pr-single.pl?number=58039 + * The problematic versions: + * - NetBSD before 9.4 + * - NetBSD 9 all development versions (9.99.x) + * - NetBSD 10 development versions (10.99.x) before 10.99.11 + * The bug was fixed in NetBSD 9.4 release, NetBSD 10.0 release, + * NetBSD 10.99.11 development. + * It is safe to apply the workaround even if the bug is not present, as + * the workaround just reduces performance slightly. */ +# include <sys/param.h> +# if __NetBSD_Version__ < 904000000 || \ + (__NetBSD_Version__ >= 999000000 && \ + __NetBSD_Version__ < 1000000000) || \ + (__NetBSD_Version__ >= 1099000000 && \ + __NetBSD_Version__ < 1099001100) +# define NEED_NETBSD_SHA512_256_WORKAROUND 1 +# include <string.h> +# endif +# endif # endif # endif #endif /* USE_OPENSSL */ @@ -153,7 +174,7 @@ Curl_sha512_256_finish(unsigned char *digest, CURLcode ret; Curl_sha512_256_ctx *const ctx = (Curl_sha512_256_ctx *)context; -#ifdef __NetBSD__ +#ifdef NEED_NETBSD_SHA512_256_WORKAROUND /* Use a larger buffer to work around a bug in NetBSD: https://gnats.netbsd.org/cgi-bin/query-pr-single.pl?number=58039 */ unsigned char tmp_digest[SHA512_256_DIGEST_SIZE * 2]; @@ -161,9 +182,10 @@ Curl_sha512_256_finish(unsigned char *digest, tmp_digest, NULL) ? CURLE_OK : CURLE_SSL_CIPHER; if(ret == CURLE_OK) memcpy(digest, tmp_digest, SHA512_256_DIGEST_SIZE); -#else /* ! __NetBSD__ */ + explicit_memset(tmp_digest, 0, sizeof(tmp_digest)); +#else /* ! NEED_NETBSD_SHA512_256_WORKAROUND */ ret = EVP_DigestFinal_ex(*ctx, digest, NULL) ? CURLE_OK : CURLE_SSL_CIPHER; -#endif /* ! __NetBSD__ */ +#endif /* ! NEED_NETBSD_SHA512_256_WORKAROUND */ EVP_MD_CTX_destroy(*ctx); *ctx = NULL; @@ -264,29 +286,13 @@ Curl_sha512_256_finish(unsigned char *digest, defined(_MSC_VER) && !defined(__GNUC__) && !defined(__clang__) # if _MSC_VER >= 1400 # define MHDX_INLINE __forceinline -# else -# define MHDX_INLINE /* empty */ # endif #endif #if !defined(MHDX_INLINE) -# if defined(inline) - /* Assume that 'inline' macro was already defined correctly by - * the build system. */ -# define MHDX_INLINE inline -# elif defined(__cplusplus) - /* The code is compiled with C++ compiler. - * C++ always supports 'inline'. */ -# define MHDX_INLINE inline -# elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901 - /* C99 (and later) supports 'inline' keyword */ -# define MHDX_INLINE inline -# elif defined(__GNUC__) && __GNUC__ >= 3 - /* GCC supports '__inline__' as an extension */ -# define MHDX_INLINE __inline__ -# else -# define MHDX_INLINE /* empty */ -# endif + /* Assume that 'inline' keyword works or the + * macro was already defined correctly. */ +# define MHDX_INLINE inline #endif /* Bits manipulation macros and functions. diff --git a/lib/curl_threads.c b/lib/curl_threads.c index 222d936..93fa2da 100644 --- a/lib/curl_threads.c +++ b/lib/curl_threads.c @@ -131,7 +131,8 @@ curl_thread_t Curl_thread_create(unsigned int (CURL_STDCALL *func) (void *), void Curl_thread_destroy(curl_thread_t hnd) { - CloseHandle(hnd); + if(hnd != curl_thread_t_null) + CloseHandle(hnd); } int Curl_thread_join(curl_thread_t *hnd) diff --git a/lib/curl_trc.c b/lib/curl_trc.c index fa6ad22..f017e21 100644 --- a/lib/curl_trc.c +++ b/lib/curl_trc.c @@ -111,21 +111,30 @@ void Curl_failf(struct Curl_easy *data, const char *fmt, ...) /* Curl_infof() is for info message along the way */ #define MAXINFO 2048 +static void trc_infof(struct Curl_easy *data, struct curl_trc_feat *feat, + const char * const fmt, va_list ap) CURL_PRINTF(3, 0); + +static void trc_infof(struct Curl_easy *data, struct curl_trc_feat *feat, + const char * const fmt, va_list ap) +{ + int len = 0; + char buffer[MAXINFO + 2]; + if(feat) + len = msnprintf(buffer, MAXINFO, "[%s] ", feat->name); + len += mvsnprintf(buffer + len, MAXINFO - len, fmt, ap); + buffer[len++] = '\n'; + buffer[len] = '\0'; + Curl_debug(data, CURLINFO_TEXT, buffer, len); +} + void Curl_infof(struct Curl_easy *data, const char *fmt, ...) { DEBUGASSERT(!strchr(fmt, '\n')); if(Curl_trc_is_verbose(data)) { va_list ap; - int len = 0; - char buffer[MAXINFO + 2]; - if(data->state.feat) - len = msnprintf(buffer, MAXINFO, "[%s] ", data->state.feat->name); va_start(ap, fmt); - len += mvsnprintf(buffer + len, MAXINFO - len, fmt, ap); + trc_infof(data, data->state.feat, fmt, ap); va_end(ap); - buffer[len++] = '\n'; - buffer[len] = '\0'; - Curl_debug(data, CURLINFO_TEXT, buffer, len); } } @@ -154,7 +163,61 @@ void Curl_trc_cf_infof(struct Curl_easy *data, struct Curl_cfilter *cf, } } +struct curl_trc_feat Curl_trc_feat_read = { + "READ", + CURL_LOG_LVL_NONE, +}; +struct curl_trc_feat Curl_trc_feat_write = { + "WRITE", + CURL_LOG_LVL_NONE, +}; + +void Curl_trc_read(struct Curl_easy *data, const char *fmt, ...) +{ + DEBUGASSERT(!strchr(fmt, '\n')); + if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_read)) { + va_list ap; + va_start(ap, fmt); + trc_infof(data, &Curl_trc_feat_read, fmt, ap); + va_end(ap); + } +} + +void Curl_trc_write(struct Curl_easy *data, const char *fmt, ...) +{ + DEBUGASSERT(!strchr(fmt, '\n')); + if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_write)) { + va_list ap; + va_start(ap, fmt); + trc_infof(data, &Curl_trc_feat_write, fmt, ap); + va_end(ap); + } +} + +#ifndef CURL_DISABLE_FTP +struct curl_trc_feat Curl_trc_feat_ftp = { + "FTP", + CURL_LOG_LVL_NONE, +}; + +void Curl_trc_ftp(struct Curl_easy *data, const char *fmt, ...) +{ + DEBUGASSERT(!strchr(fmt, '\n')); + if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_ftp)) { + va_list ap; + va_start(ap, fmt); + trc_infof(data, &Curl_trc_feat_ftp, fmt, ap); + va_end(ap); + } +} +#endif /* !CURL_DISABLE_FTP */ + static struct curl_trc_feat *trc_feats[] = { + &Curl_trc_feat_read, + &Curl_trc_feat_write, +#ifndef CURL_DISABLE_FTP + &Curl_trc_feat_ftp, +#endif #ifndef CURL_DISABLE_DOH &Curl_doh_trc, #endif @@ -188,7 +251,7 @@ static struct Curl_cftype *cf_types[] = { &Curl_cft_haproxy, &Curl_cft_socks_proxy, #endif /* !CURL_DISABLE_PROXY */ -#ifdef ENABLE_QUIC +#ifdef USE_HTTP3 &Curl_cft_http3, #endif #if !defined(CURL_DISABLE_HTTP) && !defined(USE_HYPER) diff --git a/lib/curl_trc.h b/lib/curl_trc.h index 92b0533..3d38018 100644 --- a/lib/curl_trc.h +++ b/lib/curl_trc.h @@ -77,12 +77,32 @@ void Curl_failf(struct Curl_easy *data, #define CURL_TRC_CF(data, cf, ...) \ do { if(Curl_trc_cf_is_verbose(cf, data)) \ Curl_trc_cf_infof(data, cf, __VA_ARGS__); } while(0) +#define CURL_TRC_WRITE(data, ...) \ + do { if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_write)) \ + Curl_trc_write(data, __VA_ARGS__); } while(0) +#define CURL_TRC_READ(data, ...) \ + do { if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_read)) \ + Curl_trc_read(data, __VA_ARGS__); } while(0) + +#ifndef CURL_DISABLE_FTP +#define CURL_TRC_FTP(data, ...) \ + do { if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_ftp)) \ + Curl_trc_ftp(data, __VA_ARGS__); } while(0) +#endif /* !CURL_DISABLE_FTP */ + +#else /* CURL_HAVE_C99 */ -#else #define infof Curl_infof #define CURL_TRC_CF Curl_trc_cf_infof +#define CURL_TRC_WRITE Curl_trc_write +#define CURL_TRC_READ Curl_trc_read + +#ifndef CURL_DISABLE_FTP +#define CURL_TRC_FTP Curl_trc_ftp #endif +#endif /* !CURL_HAVE_C99 */ + #ifndef CURL_DISABLE_VERBOSE_STRINGS /* informational messages enabled */ @@ -90,6 +110,8 @@ struct curl_trc_feat { const char *name; int log_level; }; +extern struct curl_trc_feat Curl_trc_feat_read; +extern struct curl_trc_feat Curl_trc_feat_write; #define Curl_trc_is_verbose(data) \ ((data) && (data)->set.verbose && \ @@ -97,10 +119,10 @@ struct curl_trc_feat { ((data)->state.feat->log_level >= CURL_LOG_LVL_INFO))) #define Curl_trc_cf_is_verbose(cf, data) \ (Curl_trc_is_verbose(data) && \ - (cf) && (cf)->cft->log_level >= CURL_LOG_LVL_INFO) + (cf) && (cf)->cft->log_level >= CURL_LOG_LVL_INFO) #define Curl_trc_ft_is_verbose(data, ft) \ - (Curl_trc_is_verbose(data) && \ - (ft)->log_level >= CURL_LOG_LVL_INFO) + (Curl_trc_is_verbose(data) && \ + (ft)->log_level >= CURL_LOG_LVL_INFO) /** * Output an informational message when transfer's verbose logging is enabled. @@ -114,13 +136,26 @@ void Curl_infof(struct Curl_easy *data, */ void Curl_trc_cf_infof(struct Curl_easy *data, struct Curl_cfilter *cf, const char *fmt, ...) CURL_PRINTF(3, 4); +void Curl_trc_ft_infof(struct Curl_easy *data, struct curl_trc_feat *ft, + const char *fmt, ...) CURL_PRINTF(3, 4); +void Curl_trc_write(struct Curl_easy *data, + const char *fmt, ...) CURL_PRINTF(2, 3); +void Curl_trc_read(struct Curl_easy *data, + const char *fmt, ...) CURL_PRINTF(2, 3); + +#ifndef CURL_DISABLE_FTP +extern struct curl_trc_feat Curl_trc_feat_ftp; +void Curl_trc_ftp(struct Curl_easy *data, + const char *fmt, ...) CURL_PRINTF(2, 3); +#endif + #else /* defined(CURL_DISABLE_VERBOSE_STRINGS) */ /* All informational messages are not compiled in for size savings */ -#define Curl_trc_is_verbose(d) ((void)(d), FALSE) -#define Curl_trc_cf_is_verbose(x,y) ((void)(x), (void)(y), FALSE) -#define Curl_trc_ft_is_verbose(x,y) ((void)(x), (void)(y), FALSE) +#define Curl_trc_is_verbose(d) (FALSE) +#define Curl_trc_cf_is_verbose(x,y) (FALSE) +#define Curl_trc_ft_is_verbose(x,y) (FALSE) static void Curl_infof(struct Curl_easy *data, const char *fmt, ...) { @@ -134,6 +169,32 @@ static void Curl_trc_cf_infof(struct Curl_easy *data, (void)data; (void)cf; (void)fmt; } +struct curl_trc_feat; + +static void Curl_trc_ft_infof(struct Curl_easy *data, + struct curl_trc_feat *ft, + const char *fmt, ...) +{ + (void)data; (void)ft; (void)fmt; +} + +static void Curl_trc_write(struct Curl_easy *data, const char *fmt, ...) +{ + (void)data; (void)fmt; +} + +static void Curl_trc_read(struct Curl_easy *data, const char *fmt, ...) +{ + (void)data; (void)fmt; +} + +#ifndef CURL_DISABLE_FTP +static void Curl_trc_ftp(struct Curl_easy *data, const char *fmt, ...) +{ + (void)data; (void)fmt; +} +#endif + #endif /* !defined(CURL_DISABLE_VERBOSE_STRINGS) */ #endif /* HEADER_CURL_TRC_H */ diff --git a/lib/curlx.h b/lib/curlx.h index 7a753d6..54e4279 100644 --- a/lib/curlx.h +++ b/lib/curlx.h @@ -77,7 +77,6 @@ */ -#define curlx_getenv curl_getenv #define curlx_mvsnprintf curl_mvsnprintf #define curlx_msnprintf curl_msnprintf #define curlx_maprintf curl_maprintf diff --git a/lib/cw-out.c b/lib/cw-out.c index c168837..4e56c6a 100644 --- a/lib/cw-out.c +++ b/lib/cw-out.c @@ -102,6 +102,8 @@ static void cw_out_buf_free(struct cw_out_buf *cwbuf) struct cw_out_ctx { struct Curl_cwriter super; struct cw_out_buf *buf; + BIT(paused); + BIT(errored); }; static CURLcode cw_out_write(struct Curl_easy *data, @@ -201,7 +203,10 @@ static CURLcode cw_out_ptr_flush(struct cw_out_ctx *ctx, size_t max_write, min_write; size_t wlen, nwritten; - (void)ctx; + /* If we errored once, we do not invoke the client callback again */ + if(ctx->errored) + return CURLE_WRITE_ERROR; + /* write callbacks may get NULLed by the client between calls. */ cw_get_writefunc(data, otype, &wcb, &wcb_data, &max_write, &min_write); if(!wcb) { @@ -210,13 +215,16 @@ static CURLcode cw_out_ptr_flush(struct cw_out_ctx *ctx, } *pconsumed = 0; - while(blen && !(data->req.keepon & KEEP_RECV_PAUSE)) { + while(blen && !ctx->paused) { if(!flush_all && blen < min_write) break; wlen = max_write? CURLMIN(blen, max_write) : blen; Curl_set_in_callback(data, TRUE); nwritten = wcb((char *)buf, 1, wlen, wcb_data); Curl_set_in_callback(data, FALSE); + CURL_TRC_WRITE(data, "cw_out, wrote %zu %s bytes -> %zu", + wlen, (otype == CW_OUT_BODY)? "body" : "header", + nwritten); if(CURL_WRITEFUNC_PAUSE == nwritten) { if(data->conn && data->conn->handler->flags & PROTOPT_NONETWORK) { /* Protocols that work without network cannot be paused. This is @@ -227,9 +235,15 @@ static CURLcode cw_out_ptr_flush(struct cw_out_ctx *ctx, } /* mark the connection as RECV paused */ data->req.keepon |= KEEP_RECV_PAUSE; + ctx->paused = TRUE; + CURL_TRC_WRITE(data, "cw_out, PAUSE requested by client"); break; } - if(nwritten != wlen) { + else if(CURL_WRITEFUNC_ERROR == nwritten) { + failf(data, "client returned ERROR on write of %zu bytes", wlen); + return CURLE_WRITE_ERROR; + } + else if(nwritten != wlen) { failf(data, "Failure writing output to destination, " "passed %zu returned %zd", wlen, nwritten); return CURLE_WRITE_ERROR; @@ -283,7 +297,7 @@ static CURLcode cw_out_flush_chain(struct cw_out_ctx *ctx, if(!cwbuf) return CURLE_OK; - if(data->req.keepon & KEEP_RECV_PAUSE) + if(ctx->paused) return CURLE_OK; /* write the end of the chain until it blocks or gets empty */ @@ -296,7 +310,7 @@ static CURLcode cw_out_flush_chain(struct cw_out_ctx *ctx, return result; if(*plast) { /* could not write last, paused again? */ - DEBUGASSERT(data->req.keepon & KEEP_RECV_PAUSE); + DEBUGASSERT(ctx->paused); return CURLE_OK; } } @@ -338,14 +352,14 @@ static CURLcode cw_out_do_write(struct cw_out_ctx *ctx, bool flush_all, const char *buf, size_t blen) { - CURLcode result; + CURLcode result = CURLE_OK; /* if we have buffered data and it is a different type than what * we are writing now, try to flush all */ if(ctx->buf && ctx->buf->type != otype) { result = cw_out_flush_chain(ctx, data, &ctx->buf, TRUE); if(result) - return result; + goto out; } if(ctx->buf) { @@ -355,7 +369,7 @@ static CURLcode cw_out_do_write(struct cw_out_ctx *ctx, return result; result = cw_out_flush_chain(ctx, data, &ctx->buf, flush_all); if(result) - return result; + goto out; } else { /* nothing buffered, try direct write */ @@ -368,10 +382,18 @@ static CURLcode cw_out_do_write(struct cw_out_ctx *ctx, /* did not write all, append the rest */ result = cw_out_append(ctx, otype, buf + consumed, blen - consumed); if(result) - return result; + goto out; } } - return CURLE_OK; + +out: + if(result) { + /* We do not want to invoked client callbacks a second time after + * encountering an error. See issue #13337 */ + ctx->errored = TRUE; + cw_out_bufs_free(ctx); + } + return result; } static CURLcode cw_out_write(struct Curl_easy *data, @@ -409,10 +431,12 @@ bool Curl_cw_out_is_paused(struct Curl_easy *data) return FALSE; ctx = (struct cw_out_ctx *)cw_out; - return cw_out_bufs_len(ctx) > 0; + CURL_TRC_WRITE(data, "cw-out is%spaused", ctx->paused? "" : " not"); + return ctx->paused; } -static CURLcode cw_out_flush(struct Curl_easy *data, bool flush_all) +static CURLcode cw_out_flush(struct Curl_easy *data, + bool unpause, bool flush_all) { struct Curl_cwriter *cw_out; CURLcode result = CURLE_OK; @@ -420,18 +444,31 @@ static CURLcode cw_out_flush(struct Curl_easy *data, bool flush_all) cw_out = Curl_cwriter_get_by_type(data, &Curl_cwt_out); if(cw_out) { struct cw_out_ctx *ctx = (struct cw_out_ctx *)cw_out; + if(ctx->errored) + return CURLE_WRITE_ERROR; + if(unpause && ctx->paused) + ctx->paused = FALSE; + if(ctx->paused) + return CURLE_OK; /* not doing it */ result = cw_out_flush_chain(ctx, data, &ctx->buf, flush_all); + if(result) { + ctx->errored = TRUE; + cw_out_bufs_free(ctx); + return result; + } } return result; } -CURLcode Curl_cw_out_flush(struct Curl_easy *data) +CURLcode Curl_cw_out_unpause(struct Curl_easy *data) { - return cw_out_flush(data, FALSE); + CURL_TRC_WRITE(data, "cw-out unpause"); + return cw_out_flush(data, TRUE, FALSE); } CURLcode Curl_cw_out_done(struct Curl_easy *data) { - return cw_out_flush(data, TRUE); + CURL_TRC_WRITE(data, "cw-out done"); + return cw_out_flush(data, FALSE, TRUE); } diff --git a/lib/cw-out.h b/lib/cw-out.h index c13e853..ca4c2e4 100644 --- a/lib/cw-out.h +++ b/lib/cw-out.h @@ -43,7 +43,7 @@ bool Curl_cw_out_is_paused(struct Curl_easy *data); /** * Flush any buffered date to the client, chunk collation still applies. */ -CURLcode Curl_cw_out_flush(struct Curl_easy *data); +CURLcode Curl_cw_out_unpause(struct Curl_easy *data); /** * Mark EndOfStream reached and flush ALL data to the client. @@ -76,7 +76,7 @@ static CURLcode dict_do(struct Curl_easy *data, bool *done); */ const struct Curl_handler Curl_handler_dict = { - "DICT", /* scheme */ + "dict", /* scheme */ ZERO_NULL, /* setup_connection */ dict_do, /* do_it */ ZERO_NULL, /* done */ @@ -90,6 +90,7 @@ const struct Curl_handler Curl_handler_dict = { ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_DICT, /* defport */ diff --git a/lib/dllmain.c b/lib/dllmain.c new file mode 100644 index 0000000..41e97b3 --- /dev/null +++ b/lib/dllmain.c @@ -0,0 +1,81 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef USE_OPENSSL +#include <openssl/crypto.h> +#endif + +/* The fourth-to-last include */ +#ifdef __CYGWIN__ +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#ifdef _WIN32 +#undef _WIN32 +#endif +#endif + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +/* DllMain() must only be defined for Windows and Cygwin DLL builds. */ +#if (defined(_WIN32) || defined(__CYGWIN__)) && !defined(CURL_STATICLIB) + +#if defined(USE_OPENSSL) && \ + !defined(OPENSSL_IS_AWSLC) && \ + !defined(OPENSSL_IS_BORINGSSL) && \ + !defined(LIBRESSL_VERSION_NUMBER) && \ + (OPENSSL_VERSION_NUMBER >= 0x10100000L) +#define PREVENT_OPENSSL_MEMLEAK +#endif + +#ifdef PREVENT_OPENSSL_MEMLEAK +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved); +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) +{ + (void)hinstDLL; + (void)lpvReserved; + + switch(fdwReason) { + case DLL_PROCESS_ATTACH: + break; + case DLL_PROCESS_DETACH: + break; + case DLL_THREAD_ATTACH: + break; + case DLL_THREAD_DETACH: + /* Call OPENSSL_thread_stop to prevent a memory leak in case OpenSSL is + linked statically. + https://github.com/curl/curl/issues/12327#issuecomment-1826405944 */ + OPENSSL_thread_stop(); + break; + } + return TRUE; +} +#endif /* OpenSSL */ + +#endif /* DLL build */ @@ -42,9 +42,13 @@ #include "curl_printf.h" #include "curl_memory.h" #include "memdebug.h" +#include "escape.h" #define DNS_CLASS_IN 0x01 +/* local_print_buf truncates if the hex string will be more than this */ +#define LOCAL_PB_HEXMAX 400 + #ifndef CURL_DISABLE_VERBOSE_STRINGS static const char * const errors[]={ "", @@ -187,6 +191,26 @@ doh_write_cb(const void *contents, size_t size, size_t nmemb, void *userp) return realsize; } +#if defined(USE_HTTPSRR) && defined(CURLDEBUG) +static void local_print_buf(struct Curl_easy *data, + const char *prefix, + unsigned char *buf, size_t len) +{ + unsigned char hexstr[LOCAL_PB_HEXMAX]; + size_t hlen = LOCAL_PB_HEXMAX; + bool truncated = false; + + if(len > (LOCAL_PB_HEXMAX / 2)) + truncated = true; + Curl_hexencode(buf, len, hexstr, hlen); + if(!truncated) + infof(data, "%s: len=%d, val=%s", prefix, (int)len, hexstr); + else + infof(data, "%s: len=%d (truncated)val=%s", prefix, (int)len, hexstr); + return; +} +#endif + /* called from multi.c when this DoH transfer is complete */ static int doh_done(struct Curl_easy *doh, CURLcode result) { @@ -379,6 +403,12 @@ struct Curl_addrinfo *Curl_doh(struct Curl_easy *data, int slot; struct dohdata *dohp; struct connectdata *conn = data->conn; +#ifdef USE_HTTPSRR + /* for now, this is only used when ECH is enabled */ +# ifdef USE_ECH + char *qname = NULL; +# endif +#endif *waitp = FALSE; (void)hostname; (void)port; @@ -408,7 +438,7 @@ struct Curl_addrinfo *Curl_doh(struct Curl_easy *data, goto error; dohp->pending++; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 if((conn->ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) { /* create IPv6 DoH request */ result = dohprobe(data, &dohp->probe[DOH_PROBE_SLOT_IPADDR_V6], @@ -419,6 +449,37 @@ struct Curl_addrinfo *Curl_doh(struct Curl_easy *data, dohp->pending++; } #endif + +#ifdef USE_HTTPSRR + /* + * TODO: Figure out the conditions under which we want to make + * a request for an HTTPS RR when we are not doing ECH. For now, + * making this request breaks a bunch of DoH tests, e.g. test2100, + * where the additional request doesn't match the pre-cooked data + * files, so there's a bit of work attached to making the request + * in a non-ECH use-case. For the present, we'll only make the + * request when ECH is enabled in the build and is being used for + * the curl operation. + */ +# ifdef USE_ECH + if(data->set.tls_ech & CURLECH_ENABLE + || data->set.tls_ech & CURLECH_HARD) { + if(port == 443) + qname = strdup(hostname); + else + qname = aprintf("_%d._https.%s", port, hostname); + if(!qname) + goto error; + result = dohprobe(data, &dohp->probe[DOH_PROBE_SLOT_HTTPS], + DNS_TYPE_HTTPS, qname, data->set.str[STRING_DOH], + data->multi, dohp->headers); + free(qname); + if(result) + goto error; + dohp->pending++; + } +# endif +#endif *waitp = TRUE; /* this never returns synchronously */ return NULL; @@ -501,6 +562,25 @@ static DOHcode store_aaaa(const unsigned char *doh, return DOH_OK; } +#ifdef USE_HTTPSRR +static DOHcode store_https(const unsigned char *doh, + int index, + struct dohentry *d, + uint16_t len) +{ + /* silently ignore RRs over the limit */ + if(d->numhttps_rrs < DOH_MAX_HTTPS) { + struct dohhttps_rr *h = &d->https_rrs[d->numhttps_rrs]; + h->val = Curl_memdup(&doh[index], len); + if(!h->val) + return DOH_OUT_OF_MEM; + h->len = len; + d->numhttps_rrs++; + } + return DOH_OK; +} +#endif + static DOHcode store_cname(const unsigned char *doh, size_t dohlen, unsigned int index, @@ -563,7 +643,8 @@ static DOHcode rdata(const unsigned char *doh, /* RDATA - A (TYPE 1): 4 bytes - AAAA (TYPE 28): 16 bytes - - NS (TYPE 2): N bytes */ + - NS (TYPE 2): N bytes + - HTTPS (TYPE 65): N bytes */ DOHcode rc; switch(type) { @@ -581,6 +662,13 @@ static DOHcode rdata(const unsigned char *doh, if(rc) return rc; break; +#ifdef USE_HTTPSRR + case DNS_TYPE_HTTPS: + rc = store_https(doh, index, d, rdlength); + if(rc) + return rc; + break; +#endif case DNS_TYPE_CNAME: rc = store_cname(doh, dohlen, index, d); if(rc) @@ -737,7 +825,11 @@ UNITTEST DOHcode doh_decode(const unsigned char *doh, if(index != dohlen) return DOH_DNS_MALFORMAT; /* something is wrong */ +#ifdef USE_HTTTPS + if((type != DNS_TYPE_NS) && !d->numcname && !d->numaddr && !d->numhttps_rrs) +#else if((type != DNS_TYPE_NS) && !d->numcname && !d->numaddr) +#endif /* nothing stored! */ return DOH_NO_CONTENT; @@ -776,6 +868,16 @@ static void showdoh(struct Curl_easy *data, infof(data, "%s", buffer); } } +#ifdef USE_HTTPSRR + for(i = 0; i < d->numhttps_rrs; i++) { +# ifdef CURLDEBUG + local_print_buf(data, "DoH HTTPS", + d->https_rrs[i].val, d->https_rrs[i].len); +# else + infof(data, "DoH HTTPS RR: length %d", d->https_rrs[i].len); +# endif + } +#endif for(i = 0; i < d->numcname; i++) { infof(data, "CNAME: %s", Curl_dyn_ptr(&d->cname[i])); } @@ -804,7 +906,7 @@ static CURLcode doh2ai(const struct dohentry *de, const char *hostname, struct Curl_addrinfo *prevai = NULL; struct Curl_addrinfo *firstai = NULL; struct sockaddr_in *addr; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 struct sockaddr_in6 *addr6; #endif CURLcode result = CURLE_OK; @@ -820,7 +922,7 @@ static CURLcode doh2ai(const struct dohentry *de, const char *hostname, size_t ss_size; CURL_SA_FAMILY_T addrtype; if(de->addr[i].type == DNS_TYPE_AAAA) { -#ifndef ENABLE_IPV6 +#ifndef USE_IPV6 /* we can't handle IPv6 addresses */ continue; #else @@ -869,7 +971,7 @@ static CURLcode doh2ai(const struct dohentry *de, const char *hostname, addr->sin_port = htons((unsigned short)port); break; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 case AF_INET6: addr6 = (void *)ai->ai_addr; /* storage area for this info */ DEBUGASSERT(sizeof(struct in6_addr) == sizeof(de->addr[i].ip.v6)); @@ -895,7 +997,18 @@ static CURLcode doh2ai(const struct dohentry *de, const char *hostname, #ifndef CURL_DISABLE_VERBOSE_STRINGS static const char *type2name(DNStype dnstype) { - return (dnstype == DNS_TYPE_A)?"A":"AAAA"; + switch(dnstype) { + case DNS_TYPE_A: + return "A"; + case DNS_TYPE_AAAA: + return "AAAA"; +#ifdef USE_HTTPSRR + case DNS_TYPE_HTTPS: + return "HTTPS"; +#endif + default: + return "unknown"; + } } #endif @@ -905,8 +1018,270 @@ UNITTEST void de_cleanup(struct dohentry *d) for(i = 0; i < d->numcname; i++) { Curl_dyn_free(&d->cname[i]); } +#ifdef USE_HTTPSRR + for(i = 0; i < d->numhttps_rrs; i++) + free(d->https_rrs[i].val); +#endif +} + +#ifdef USE_HTTPSRR + +/* + * @brief decode the DNS name in a binary RRData + * @param buf points to the buffer (in/out) + * @param remaining points to the remaining buffer length (in/out) + * @param dnsname returns the string form name on success + * @return is 1 for success, error otherwise + * + * The encoding here is defined in + * https://tools.ietf.org/html/rfc1035#section-3.1 + * + * The input buffer pointer will be modified so it points to + * just after the end of the DNS name encoding on output. (And + * that's why it's an "unsigned char **" :-) + */ +static CURLcode local_decode_rdata_name(unsigned char **buf, size_t *remaining, + char **dnsname) +{ + unsigned char *cp = NULL; + int rem = 0; + unsigned char clen = 0; /* chunk len */ + struct dynbuf thename; + + DEBUGASSERT(buf && remaining && dnsname); + if(!buf || !remaining || !dnsname) + return CURLE_OUT_OF_MEMORY; + rem = (int)*remaining; + if(rem <= 0) { + Curl_dyn_free(&thename); + return CURLE_OUT_OF_MEMORY; + } + Curl_dyn_init(&thename, CURL_MAXLEN_host_name); + cp = *buf; + clen = *cp++; + if(clen == 0) { + /* special case - return "." as name */ + if(Curl_dyn_addn(&thename, ".", 1)) + return CURLE_OUT_OF_MEMORY; + } + while(clen) { + if(clen >= rem) { + Curl_dyn_free(&thename); + return CURLE_OUT_OF_MEMORY; + } + if(Curl_dyn_addn(&thename, cp, clen) || + Curl_dyn_addn(&thename, ".", 1)) + return CURLE_TOO_LARGE; + + cp += clen; + rem -= (clen + 1); + if(rem <= 0) { + Curl_dyn_free(&thename); + return CURLE_OUT_OF_MEMORY; + } + clen = *cp++; + } + *buf = cp; + *remaining = rem - 1; + *dnsname = Curl_dyn_ptr(&thename); + return CURLE_OK; } +static CURLcode local_decode_rdata_alpn(unsigned char *rrval, size_t len, + char **alpns) +{ + /* + * spec here is as per draft-ietf-dnsop-svcb-https, section-7.1.1 + * encoding is catenated list of strings each preceded by a one + * octet length + * output is comma-sep list of the strings + * implementations may or may not handle quoting of comma within + * string values, so we might see a comma within the wire format + * version of a string, in which case we'll precede that by a + * backslash - same goes for a backslash character, and of course + * we need to use two backslashes in strings when we mean one;-) + */ + int remaining = (int) len; + char *oval; + size_t i; + unsigned char *cp = rrval; + struct dynbuf dval; + + if(!alpns) + return CURLE_OUT_OF_MEMORY; + Curl_dyn_init(&dval, DYN_DOH_RESPONSE); + remaining = (int)len; + cp = rrval; + while(remaining > 0) { + size_t tlen = (size_t) *cp++; + + /* if not 1st time, add comma */ + if(remaining != (int)len && Curl_dyn_addn(&dval, ",", 1)) + goto err; + remaining--; + if(tlen > (size_t)remaining) + goto err; + /* add escape char if needed, clunky but easier to read */ + for(i = 0; i != tlen; i++) { + if('\\' == *cp || ',' == *cp) { + if(Curl_dyn_addn(&dval, "\\", 1)) + goto err; + } + if(Curl_dyn_addn(&dval, cp++, 1)) + goto err; + } + remaining -= (int)tlen; + } + /* this string is always null terminated */ + oval = Curl_dyn_ptr(&dval); + if(!oval) + goto err; + *alpns = oval; + return CURLE_OK; +err: + Curl_dyn_free(&dval); + return CURLE_BAD_CONTENT_ENCODING; +} + +#ifdef CURLDEBUG +static CURLcode test_alpn_escapes(void) +{ + /* we'll use an example from draft-ietf-dnsop-svcb, figure 10 */ + static unsigned char example[] = { + 0x08, /* length 8 */ + 0x66, 0x5c, 0x6f, 0x6f, 0x2c, 0x62, 0x61, 0x72, /* value "f\\oo,bar" */ + 0x02, /* length 2 */ + 0x68, 0x32 /* value "h2" */ + }; + size_t example_len = sizeof(example); + char *aval = NULL; + static const char *expected = "f\\\\oo\\,bar,h2"; + + if(local_decode_rdata_alpn(example, example_len, &aval) != CURLE_OK) + return CURLE_BAD_CONTENT_ENCODING; + if(strlen(aval) != strlen(expected)) + return CURLE_BAD_CONTENT_ENCODING; + if(memcmp(aval, expected, strlen(aval))) + return CURLE_BAD_CONTENT_ENCODING; + return CURLE_OK; +} +#endif + +static CURLcode Curl_doh_decode_httpsrr(unsigned char *rrval, size_t len, + struct Curl_https_rrinfo **hrr) +{ + size_t remaining = len; + unsigned char *cp = rrval; + uint16_t pcode = 0, plen = 0; + struct Curl_https_rrinfo *lhrr = NULL; + char *dnsname = NULL; + +#ifdef CURLDEBUG + /* a few tests of escaping, shouldn't be here but ok for now */ + if(test_alpn_escapes() != CURLE_OK) + return CURLE_OUT_OF_MEMORY; +#endif + lhrr = calloc(1, sizeof(struct Curl_https_rrinfo)); + if(!lhrr) + return CURLE_OUT_OF_MEMORY; + lhrr->val = Curl_memdup(rrval, len); + if(!lhrr->val) + goto err; + lhrr->len = len; + if(remaining <= 2) + goto err; + lhrr->priority = (uint16_t)((cp[0] << 8) + cp[1]); + cp += 2; + remaining -= (uint16_t)2; + if(local_decode_rdata_name(&cp, &remaining, &dnsname) != CURLE_OK) + goto err; + lhrr->target = dnsname; + while(remaining >= 4) { + pcode = (uint16_t)((*cp << 8) + (*(cp + 1))); + cp += 2; + plen = (uint16_t)((*cp << 8) + (*(cp + 1))); + cp += 2; + remaining -= 4; + if(pcode == HTTPS_RR_CODE_ALPN) { + if(local_decode_rdata_alpn(cp, plen, &lhrr->alpns) != CURLE_OK) + goto err; + } + if(pcode == HTTPS_RR_CODE_NO_DEF_ALPN) + lhrr->no_def_alpn = TRUE; + else if(pcode == HTTPS_RR_CODE_IPV4) { + lhrr->ipv4hints = Curl_memdup(cp, plen); + if(!lhrr->ipv4hints) + goto err; + lhrr->ipv4hints_len = (size_t)plen; + } + else if(pcode == HTTPS_RR_CODE_ECH) { + lhrr->echconfiglist = Curl_memdup(cp, plen); + if(!lhrr->echconfiglist) + goto err; + lhrr->echconfiglist_len = (size_t)plen; + } + else if(pcode == HTTPS_RR_CODE_IPV6) { + lhrr->ipv6hints = Curl_memdup(cp, plen); + if(!lhrr->ipv6hints) + goto err; + lhrr->ipv6hints_len = (size_t)plen; + } + if(plen > 0 && plen <= remaining) { + cp += plen; + remaining -= plen; + } + } + DEBUGASSERT(!remaining); + *hrr = lhrr; + return CURLE_OK; +err: + if(lhrr) { + free(lhrr->target); + free(lhrr->echconfiglist); + free(lhrr->val); + free(lhrr); + } + return CURLE_OUT_OF_MEMORY; +} + +# ifdef CURLDEBUG +static void local_print_httpsrr(struct Curl_easy *data, + struct Curl_https_rrinfo *hrr) +{ + DEBUGASSERT(hrr); + infof(data, "HTTPS RR: priority %d, target: %s", + hrr->priority, hrr->target); + if(hrr->alpns) + infof(data, "HTTPS RR: alpns %s", hrr->alpns); + else + infof(data, "HTTPS RR: no alpns"); + if(hrr->no_def_alpn) + infof(data, "HTTPS RR: no_def_alpn set"); + else + infof(data, "HTTPS RR: no_def_alpn not set"); + if(hrr->ipv4hints) { + local_print_buf(data, "HTTPS RR: ipv4hints", + hrr->ipv4hints, hrr->ipv4hints_len); + } + else + infof(data, "HTTPS RR: no ipv4hints"); + if(hrr->echconfiglist) { + local_print_buf(data, "HTTPS RR: ECHConfigList", + hrr->echconfiglist, hrr->echconfiglist_len); + } + else + infof(data, "HTTPS RR: no ECHConfigList"); + if(hrr->ipv6hints) { + local_print_buf(data, "HTTPS RR: ipv6hint", + hrr->ipv6hints, hrr->ipv6hints_len); + } + else + infof(data, "HTTPS RR: no ipv6hints"); + return; +} +# endif +#endif + CURLcode Curl_doh_is_resolved(struct Curl_easy *data, struct Curl_dns_entry **dnsp) { @@ -923,9 +1298,15 @@ CURLcode Curl_doh_is_resolved(struct Curl_easy *data, CURLE_COULDNT_RESOLVE_HOST; } else if(!dohp->pending) { +#ifndef USE_HTTPSRR DOHcode rc[DOH_PROBE_SLOTS] = { DOH_OK, DOH_OK }; +#else + DOHcode rc[DOH_PROBE_SLOTS] = { + DOH_OK, DOH_OK, DOH_OK + }; +#endif struct dohentry de; int slot; /* remove DoH handles from multi handle and close them */ @@ -991,6 +1372,22 @@ CURLcode Curl_doh_is_resolved(struct Curl_easy *data, } /* address processing done */ /* Now process any build-specific attributes retrieved from DNS */ +#ifdef USE_HTTPSRR + if(de.numhttps_rrs > 0 && result == CURLE_OK && *dnsp) { + struct Curl_https_rrinfo *hrr = NULL; + result = Curl_doh_decode_httpsrr(de.https_rrs->val, de.https_rrs->len, + &hrr); + if(result) { + infof(data, "Failed to decode HTTPS RR"); + return result; + } + infof(data, "Some HTTPS RR to process"); +# ifdef CURLDEBUG + local_print_httpsrr(data, hrr); +# endif + (*dnsp)->hinfo = hrr; + } +#endif /* All done */ de_cleanup(&de); @@ -26,6 +26,9 @@ #include "urldata.h" #include "curl_addrinfo.h" +#ifdef USE_HTTPSRR +# include <stdint.h> +#endif #ifndef CURL_DISABLE_DOH @@ -51,7 +54,8 @@ typedef enum { DNS_TYPE_NS = 2, DNS_TYPE_CNAME = 5, DNS_TYPE_AAAA = 28, - DNS_TYPE_DNAME = 39 /* RFC6672 */ + DNS_TYPE_DNAME = 39, /* RFC6672 */ + DNS_TYPE_HTTPS = 65 } DNStype; /* one of these for each DoH request */ @@ -84,10 +88,9 @@ struct Curl_addrinfo *Curl_doh(struct Curl_easy *data, CURLcode Curl_doh_is_resolved(struct Curl_easy *data, struct Curl_dns_entry **dns); -int Curl_doh_getsock(struct connectdata *conn, curl_socket_t *socks); - #define DOH_MAX_ADDR 24 #define DOH_MAX_CNAME 4 +#define DOH_MAX_HTTPS 4 struct dohaddr { int type; @@ -97,12 +100,44 @@ struct dohaddr { } ip; }; +#ifdef USE_HTTPSRR + +/* + * These are the code points for DNS wire format SvcParams as + * per draft-ietf-dnsop-svcb-https + * Not all are supported now, and even those that are may need + * more work in future to fully support the spec. + */ +#define HTTPS_RR_CODE_ALPN 0x01 +#define HTTPS_RR_CODE_NO_DEF_ALPN 0x02 +#define HTTPS_RR_CODE_PORT 0x03 +#define HTTPS_RR_CODE_IPV4 0x04 +#define HTTPS_RR_CODE_ECH 0x05 +#define HTTPS_RR_CODE_IPV6 0x06 + +/* + * These may need escaping when found within an alpn string + * value. + */ +#define COMMA_CHAR ',' +#define BACKSLASH_CHAR '\\' + +struct dohhttps_rr { + uint16_t len; /* raw encoded length */ + unsigned char *val; /* raw encoded octets */ +}; +#endif + struct dohentry { struct dynbuf cname[DOH_MAX_CNAME]; struct dohaddr addr[DOH_MAX_ADDR]; int numaddr; unsigned int ttl; int numcname; +#ifdef USE_HTTPSRR + struct dohhttps_rr https_rrs[DOH_MAX_HTTPS]; + int numhttps_rrs; +#endif }; diff --git a/lib/dynbuf.c b/lib/dynbuf.c index a4c599d..3b62eaf 100644 --- a/lib/dynbuf.c +++ b/lib/dynbuf.c @@ -213,7 +213,7 @@ CURLcode Curl_dyn_vaddf(struct dynbuf *s, const char *fmt, va_list ap) } /* If we failed, we cleanup the whole buffer and return error */ Curl_dyn_free(s); - return CURLE_OK; + return CURLE_OUT_OF_MEMORY; #endif } @@ -58,7 +58,6 @@ #include "multiif.h" #include "select.h" #include "cfilters.h" -#include "cw-out.h" #include "sendf.h" /* for failf function prototype */ #include "connect.h" /* for Curl_getconnectinfo */ #include "slist.h" @@ -729,6 +728,8 @@ static CURLcode easy_perform(struct Curl_easy *data, bool events) /* clear this as early as possible */ data->set.errorbuffer[0] = 0; + data->state.os_errno = 0; + if(data->multi) { failf(data, "easy handle already used in multi handle"); return CURLE_FAILED_INIT; @@ -1086,6 +1087,7 @@ CURLcode curl_easy_pause(struct Curl_easy *data, int action) int oldstate; int newstate; bool recursive = FALSE; + bool keep_changed, unpause_read, not_all_paused; if(!GOOD_EASY_HANDLE(data) || !data->conn) /* crazy input, don't continue */ @@ -1101,51 +1103,47 @@ CURLcode curl_easy_pause(struct Curl_easy *data, int action) ((action & CURLPAUSE_RECV)?KEEP_RECV_PAUSE:0) | ((action & CURLPAUSE_SEND)?KEEP_SEND_PAUSE:0); - if((newstate & (KEEP_RECV_PAUSE| KEEP_SEND_PAUSE)) == oldstate) { - /* Not changing any pause state, return */ - DEBUGF(infof(data, "pause: no change, early return")); - return CURLE_OK; - } - - /* Unpause parts in active mime tree. */ - if((k->keepon & ~newstate & KEEP_SEND_PAUSE) && - (data->mstate == MSTATE_PERFORMING || - data->mstate == MSTATE_RATELIMITING)) { - result = Curl_creader_unpause(data); - if(result) - return result; - } - - /* put it back in the keepon */ + keep_changed = ((newstate & (KEEP_RECV_PAUSE| KEEP_SEND_PAUSE)) != oldstate); + not_all_paused = (newstate & (KEEP_RECV_PAUSE|KEEP_SEND_PAUSE)) != + (KEEP_RECV_PAUSE|KEEP_SEND_PAUSE); + unpause_read = ((k->keepon & ~newstate & KEEP_SEND_PAUSE) && + (data->mstate == MSTATE_PERFORMING || + data->mstate == MSTATE_RATELIMITING)); + /* Unpausing writes is detected on the next run in + * transfer.c:Curl_readwrite(). This is because this may result + * in a transfer error if the application's callbacks fail */ + + /* Set the new keepon state, so it takes effect no matter what error + * may happen afterwards. */ k->keepon = newstate; - if(!(newstate & KEEP_RECV_PAUSE)) { - Curl_conn_ev_data_pause(data, FALSE); - result = Curl_cw_out_flush(data); - if(result) - return result; - } - - /* if there's no error and we're not pausing both directions, we want - to have this handle checked soon */ - if((newstate & (KEEP_RECV_PAUSE|KEEP_SEND_PAUSE)) != - (KEEP_RECV_PAUSE|KEEP_SEND_PAUSE)) { - Curl_expire(data, 0, EXPIRE_RUN_NOW); /* get this handle going again */ - + /* If not completely pausing both directions now, run again in any case. */ + if(not_all_paused) { + Curl_expire(data, 0, EXPIRE_RUN_NOW); /* reset the too-slow time keeper */ data->state.keeps_speed.tv_sec = 0; - - if(!Curl_cw_out_is_paused(data)) - /* if not pausing again, force a recv/send check of this connection as - the data might've been read off the socket already */ - data->state.select_bits = CURL_CSELECT_IN | CURL_CSELECT_OUT; - if(data->multi) { - if(Curl_update_timer(data->multi)) - return CURLE_ABORTED_BY_CALLBACK; + /* Simulate socket events on next run for unpaused directions */ + if(!(newstate & KEEP_SEND_PAUSE)) + data->state.select_bits |= CURL_CSELECT_OUT; + if(!(newstate & KEEP_RECV_PAUSE)) + data->state.select_bits |= CURL_CSELECT_IN; + /* On changes, tell application to update its timers. */ + if(keep_changed && data->multi) { + if(Curl_update_timer(data->multi)) { + result = CURLE_ABORTED_BY_CALLBACK; + goto out; + } } } - if(!data->state.done) + if(unpause_read) { + result = Curl_creader_unpause(data); + if(result) + goto out; + } + +out: + if(!result && !data->state.done && keep_changed) /* This transfer may have been moved in or out of the bundle, update the corresponding socket callback, if used */ result = Curl_updatesocket(data); @@ -1305,7 +1303,7 @@ static int conn_upkeep(struct Curl_easy *data, conn->handler->connection_check(data, conn, CONNCHECK_KEEPALIVE); } else { - /* Do the generic action on the FIRSTSOCKE filter chain */ + /* Do the generic action on the FIRSTSOCKET filter chain */ Curl_conn_keep_alive(data, conn, FIRSTSOCKET); } Curl_detach_connection(data); diff --git a/lib/easyoptions.c b/lib/easyoptions.c index 9c4438a..c79d136 100644 --- a/lib/easyoptions.c +++ b/lib/easyoptions.c @@ -86,6 +86,7 @@ struct curl_easyoption Curl_easyopts[] = { {"DOH_SSL_VERIFYPEER", CURLOPT_DOH_SSL_VERIFYPEER, CURLOT_LONG, 0}, {"DOH_SSL_VERIFYSTATUS", CURLOPT_DOH_SSL_VERIFYSTATUS, CURLOT_LONG, 0}, {"DOH_URL", CURLOPT_DOH_URL, CURLOT_STRING, 0}, + {"ECH", CURLOPT_ECH, CURLOT_STRING, 0}, {"EGDSOCKET", CURLOPT_EGDSOCKET, CURLOT_STRING, 0}, {"ENCODING", CURLOPT_ACCEPT_ENCODING, CURLOT_STRING, CURLOT_FLAG_ALIAS}, {"ERRORBUFFER", CURLOPT_ERRORBUFFER, CURLOT_OBJECT, 0}, @@ -375,6 +376,6 @@ struct curl_easyoption Curl_easyopts[] = { */ int Curl_easyopts_check(void) { - return ((CURLOPT_LASTENTRY%10000) != (324 + 1)); + return ((CURLOPT_LASTENTRY%10000) != (325 + 1)); } #endif @@ -50,6 +50,14 @@ #include <fcntl.h> #endif +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif + +#ifdef HAVE_DIRENT_H +#include <dirent.h> +#endif + #include "strtoofft.h" #include "urldata.h" #include <curl/curl.h> @@ -101,7 +109,7 @@ static CURLcode file_setup_connection(struct Curl_easy *data, */ const struct Curl_handler Curl_handler_file = { - "FILE", /* scheme */ + "file", /* scheme */ file_setup_connection, /* setup_connection */ file_do, /* do_it */ file_done, /* done */ @@ -115,6 +123,7 @@ const struct Curl_handler Curl_handler_file = { ZERO_NULL, /* perform_getsock */ file_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ 0, /* defport */ @@ -446,12 +455,9 @@ static CURLcode file_do(struct Curl_easy *data, bool *done) fstated = TRUE; } - if(fstated && !data->state.range && data->set.timecondition) { - if(!Curl_meets_timecondition(data, data->info.filetime)) { - *done = TRUE; - return CURLE_OK; - } - } + if(fstated && !data->state.range && data->set.timecondition && + !Curl_meets_timecondition(data, data->info.filetime)) + return CURLE_OK; if(fstated) { time_t filetime; @@ -543,49 +549,85 @@ static CURLcode file_do(struct Curl_easy *data, bool *done) Curl_pgrsSetDownloadSize(data, expected_size); if(data->state.resume_from) { - if(data->state.resume_from != - lseek(fd, data->state.resume_from, SEEK_SET)) + if(!S_ISDIR(statbuf.st_mode)) { + if(data->state.resume_from != + lseek(fd, data->state.resume_from, SEEK_SET)) + return CURLE_BAD_DOWNLOAD_RESUME; + } + else { return CURLE_BAD_DOWNLOAD_RESUME; + } } result = Curl_multi_xfer_buf_borrow(data, &xfer_buf, &xfer_blen); if(result) goto out; - while(!result) { - ssize_t nread; - /* Don't fill a whole buffer if we want less than all data */ - size_t bytestoread; + if(!S_ISDIR(statbuf.st_mode)) { + while(!result) { + ssize_t nread; + /* Don't fill a whole buffer if we want less than all data */ + size_t bytestoread; - if(size_known) { - bytestoread = (expected_size < (curl_off_t)(xfer_blen-1)) ? - curlx_sotouz(expected_size) : (xfer_blen-1); - } - else - bytestoread = xfer_blen-1; + if(size_known) { + bytestoread = (expected_size < (curl_off_t)(xfer_blen-1)) ? + curlx_sotouz(expected_size) : (xfer_blen-1); + } + else + bytestoread = xfer_blen-1; - nread = read(fd, xfer_buf, bytestoread); + nread = read(fd, xfer_buf, bytestoread); - if(nread > 0) - xfer_buf[nread] = 0; + if(nread > 0) + xfer_buf[nread] = 0; - if(nread <= 0 || (size_known && (expected_size == 0))) - break; + if(nread <= 0 || (size_known && (expected_size == 0))) + break; - if(size_known) - expected_size -= nread; + if(size_known) + expected_size -= nread; - result = Curl_client_write(data, CLIENTWRITE_BODY, xfer_buf, nread); - if(result) - goto out; + result = Curl_client_write(data, CLIENTWRITE_BODY, xfer_buf, nread); + if(result) + goto out; - if(Curl_pgrsUpdate(data)) - result = CURLE_ABORTED_BY_CALLBACK; - else - result = Curl_speedcheck(data, Curl_now()); - if(result) + if(Curl_pgrsUpdate(data)) + result = CURLE_ABORTED_BY_CALLBACK; + else + result = Curl_speedcheck(data, Curl_now()); + if(result) + goto out; + } + } + else { +#ifdef HAVE_OPENDIR + DIR *dir = opendir(file->path); + struct dirent *entry; + + if(!dir) { + result = CURLE_READ_ERROR; goto out; + } + else { + while((entry = readdir(dir))) { + if(entry->d_name[0] != '.') { + result = Curl_client_write(data, CLIENTWRITE_BODY, + entry->d_name, strlen(entry->d_name)); + if(result) + break; + result = Curl_client_write(data, CLIENTWRITE_BODY, "\n", 1); + if(result) + break; + } + } + closedir(dir); + } +#else + failf(data, "Directory listing not yet implemented on this platform."); + result = CURLE_READ_ERROR; +#endif } + if(Curl_pgrsUpdate(data)) result = CURLE_ABORTED_BY_CALLBACK; @@ -95,19 +95,89 @@ #ifdef CURL_DISABLE_VERBOSE_STRINGS #define ftp_pasv_verbose(a,b,c,d) Curl_nop_stmt +#define FTP_CSTATE(c) "" +#define FTP_DSTATE(d) "" +#else /* CURL_DISABLE_VERBOSE_STRINGS */ + /* for tracing purposes */ +static const char * const ftp_state_names[]={ + "STOP", + "WAIT220", + "AUTH", + "USER", + "PASS", + "ACCT", + "PBSZ", + "PROT", + "CCC", + "PWD", + "SYST", + "NAMEFMT", + "QUOTE", + "RETR_PREQUOTE", + "STOR_PREQUOTE", + "POSTQUOTE", + "CWD", + "MKD", + "MDTM", + "TYPE", + "LIST_TYPE", + "RETR_TYPE", + "STOR_TYPE", + "SIZE", + "RETR_SIZE", + "STOR_SIZE", + "REST", + "RETR_REST", + "PORT", + "PRET", + "PASV", + "LIST", + "RETR", + "STOR", + "QUIT" +}; +#define FTP_CSTATE(c) ((c)? ftp_state_names[(c)->proto.ftpc.state] : "???") +#define FTP_DSTATE(d) (((d) && (d)->conn)? \ + ftp_state_names[(d)->conn->proto.ftpc.state] : "???") + +#endif /* !CURL_DISABLE_VERBOSE_STRINGS */ + +/* This is the ONLY way to change FTP state! */ +static void _ftp_state(struct Curl_easy *data, + ftpstate newstate +#ifdef DEBUGBUILD + , int lineno +#endif + ) +{ + struct connectdata *conn = data->conn; + struct ftp_conn *ftpc = &conn->proto.ftpc; + +#if defined(CURL_DISABLE_VERBOSE_STRINGS) +#ifdef DEBUGBUILD + (void)lineno; +#endif +#else /* CURL_DISABLE_VERBOSE_STRINGS */ + if(ftpc->state != newstate) +#ifdef DEBUGBUILD + CURL_TRC_FTP(data, "[%s] -> [%s] (line %d)", FTP_DSTATE(data), + ftp_state_names[newstate], lineno); +#else + CURL_TRC_FTP(data, "[%s] -> [%s]", FTP_DSTATE(data), + ftp_state_names[newstate]); #endif +#endif /* !CURL_DISABLE_VERBOSE_STRINGS */ + + ftpc->state = newstate; +} + /* Local API functions */ #ifndef DEBUGBUILD -static void _ftp_state(struct Curl_easy *data, - ftpstate newstate); #define ftp_state(x,y) _ftp_state(x,y) -#else -static void _ftp_state(struct Curl_easy *data, - ftpstate newstate, - int lineno); +#else /* !DEBUGBUILD */ #define ftp_state(x,y) _ftp_state(x,y,__LINE__) -#endif +#endif /* DEBUGBUILD */ static CURLcode ftp_sendquote(struct Curl_easy *data, struct connectdata *conn, @@ -163,7 +233,7 @@ static CURLcode ftp_dophase_done(struct Curl_easy *data, */ const struct Curl_handler Curl_handler_ftp = { - "FTP", /* scheme */ + "ftp", /* scheme */ ftp_setup_connection, /* setup_connection */ ftp_do, /* do_it */ ftp_done, /* done */ @@ -177,6 +247,7 @@ const struct Curl_handler Curl_handler_ftp = { ZERO_NULL, /* perform_getsock */ ftp_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_FTP, /* defport */ @@ -194,7 +265,7 @@ const struct Curl_handler Curl_handler_ftp = { */ const struct Curl_handler Curl_handler_ftps = { - "FTPS", /* scheme */ + "ftps", /* scheme */ ftp_setup_connection, /* setup_connection */ ftp_do, /* do_it */ ftp_done, /* done */ @@ -208,6 +279,7 @@ const struct Curl_handler Curl_handler_ftps = { ZERO_NULL, /* perform_getsock */ ftp_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_FTPS, /* defport */ @@ -221,6 +293,7 @@ const struct Curl_handler Curl_handler_ftps = { static void close_secondarysocket(struct Curl_easy *data, struct connectdata *conn) { + CURL_TRC_FTP(data, "[%s] closing DATA connection", FTP_DSTATE(data)); Curl_conn_close(data, SECONDARYSOCKET); Curl_conn_cf_discard_all(data, conn, SECONDARYSOCKET); } @@ -360,7 +433,7 @@ static CURLcode AcceptServerConnect(struct Curl_easy *data) struct connectdata *conn = data->conn; curl_socket_t sock = conn->sock[SECONDARYSOCKET]; curl_socket_t s = CURL_SOCKET_BAD; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 struct Curl_sockaddr_storage add; #else struct sockaddr_in add; @@ -386,8 +459,10 @@ static CURLcode AcceptServerConnect(struct Curl_easy *data) (void)curlx_nonblock(s, TRUE); /* enable non-blocking */ /* Replace any filter on SECONDARY with one listening on this socket */ result = Curl_conn_tcp_accepted_set(data, conn, SECONDARYSOCKET, &s); - if(result) + if(result) { + sclose(s); return result; + } if(data->set.fsockopt) { int error = 0; @@ -562,7 +637,7 @@ static CURLcode InitiateTransfer(struct Curl_easy *data) struct connectdata *conn = data->conn; bool connected; - DEBUGF(infof(data, "ftp InitiateTransfer()")); + CURL_TRC_FTP(data, "InitiateTransfer()"); if(conn->bits.ftp_use_data_ssl && data->set.ftp_use_port && !Curl_conn_is_ssl(conn, SECONDARYSOCKET)) { result = Curl_ssl_cfilter_add(data, conn, SECONDARYSOCKET); @@ -645,7 +720,7 @@ static CURLcode AllowServerConnect(struct Curl_easy *data, bool *connected) } out: - DEBUGF(infof(data, "ftp AllowServerConnect() -> %d", result)); + CURL_TRC_FTP(data, "AllowServerConnect() -> %d", result); return result; } @@ -827,73 +902,6 @@ CURLcode Curl_GetFTPResponse(struct Curl_easy *data, return result; } -#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) - /* for debug purposes */ -static const char * const ftp_state_names[]={ - "STOP", - "WAIT220", - "AUTH", - "USER", - "PASS", - "ACCT", - "PBSZ", - "PROT", - "CCC", - "PWD", - "SYST", - "NAMEFMT", - "QUOTE", - "RETR_PREQUOTE", - "STOR_PREQUOTE", - "POSTQUOTE", - "CWD", - "MKD", - "MDTM", - "TYPE", - "LIST_TYPE", - "RETR_TYPE", - "STOR_TYPE", - "SIZE", - "RETR_SIZE", - "STOR_SIZE", - "REST", - "RETR_REST", - "PORT", - "PRET", - "PASV", - "LIST", - "RETR", - "STOR", - "QUIT" -}; -#endif - -/* This is the ONLY way to change FTP state! */ -static void _ftp_state(struct Curl_easy *data, - ftpstate newstate -#ifdef DEBUGBUILD - , int lineno -#endif - ) -{ - struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = &conn->proto.ftpc; - -#if defined(DEBUGBUILD) - -#if defined(CURL_DISABLE_VERBOSE_STRINGS) - (void) lineno; -#else - if(ftpc->state != newstate) - infof(data, "FTP %p (line %d) state change from %s to %s", - (void *)ftpc, lineno, ftp_state_names[ftpc->state], - ftp_state_names[newstate]); -#endif -#endif - - ftpc->state = newstate; -} - static CURLcode ftp_state_user(struct Curl_easy *data, struct connectdata *conn) { @@ -937,7 +945,7 @@ static int ftp_domore_getsock(struct Curl_easy *data, * remote site, or we could wait for that site to connect to us. Or just * handle ordinary commands. */ - DEBUGF(infof(data, "ftp_domore_getsock()")); + CURL_TRC_FTP(data, "[%s] ftp_domore_getsock()", FTP_DSTATE(data)); if(FTP_STOP == ftpc->state) { /* if stopped and still in this state, then we're also waiting for a @@ -1027,7 +1035,7 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, char hbuf[NI_MAXHOST]; struct sockaddr *sa = (struct sockaddr *)&ss; struct sockaddr_in * const sa4 = (void *)sa; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 struct sockaddr_in6 * const sa6 = (void *)sa; #endif static const char mode[][5] = { "EPRT", "PORT" }; @@ -1054,7 +1062,7 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, (strlen(data->set.str[STRING_FTPPORT]) > 1)) { char *ip_end = NULL; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 if(*string_ftpport == '[') { /* [ipv6]:port(-range) */ char *ip_start = string_ftpport + 1; @@ -1076,7 +1084,7 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, if(ip_end) { /* either ipv6 or (ipv4|domain|interface):port(-range) */ addrlen = ip_end - string_ftpport; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 if(Curl_inet_pton(AF_INET6, string_ftpport, &sa6->sin6_addr) == 1) { /* ipv6 */ port_min = port_max = 0; @@ -1122,7 +1130,7 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, /* attempt to get the address of the given interface name */ switch(Curl_if2ip(conn->remote_addr->family, -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 Curl_ipv6_scope(&conn->remote_addr->sa_addr), conn->scope_id, #endif @@ -1154,7 +1162,7 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, goto out; } switch(sa->sa_family) { -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 case AF_INET6: r = Curl_inet_ntop(sa->sa_family, &sa6->sin6_addr, hbuf, sizeof(hbuf)); break; @@ -1204,7 +1212,8 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, Curl_strerror(error, buffer, sizeof(buffer))); goto out; } - DEBUGF(infof(data, "ftp_state_use_port(), opened socket")); + CURL_TRC_FTP(data, "[%s] ftp_state_use_port(), opened socket", + FTP_DSTATE(data)); /* step 3, bind to a suitable local address */ @@ -1214,7 +1223,7 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, for(port = port_min; port <= port_max;) { if(sa->sa_family == AF_INET) sa4->sin_port = htons(port); -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 else sa6->sin6_port = htons(port); #endif @@ -1265,7 +1274,8 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); goto out; } - DEBUGF(infof(data, "ftp_state_use_port(), socket bound to port %d", port)); + CURL_TRC_FTP(data, "[%s] ftp_state_use_port(), socket bound to port %d", + FTP_DSTATE(data), port); /* step 4, listen on the socket */ @@ -1274,7 +1284,8 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); goto out; } - DEBUGF(infof(data, "ftp_state_use_port(), listening on %d", port)); + CURL_TRC_FTP(data, "[%s] ftp_state_use_port(), listening on %d", + FTP_DSTATE(data), port); /* step 5, send the proper FTP command */ @@ -1282,7 +1293,7 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, below */ Curl_printable_address(ai, myhost, sizeof(myhost)); -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 if(!conn->bits.ftp_use_eprt && conn->bits.ipv6) /* EPRT is disabled but we are connected to a IPv6 host, so we ignore the request and enable EPRT again! */ @@ -1309,7 +1320,7 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, case AF_INET: port = ntohs(sa4->sin_port); break; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 case AF_INET6: port = ntohs(sa6->sin6_port); break; @@ -2349,7 +2360,7 @@ static CURLcode ftp_state_retr(struct Curl_easy *data, struct connectdata *conn = data->conn; struct ftp_conn *ftpc = &conn->proto.ftpc; - DEBUGF(infof(data, "ftp_state_retr()")); + CURL_TRC_FTP(data, "[%s] ftp_state_retr()", FTP_DSTATE(data)); if(data->set.max_filesize && (filesize > data->set.max_filesize)) { failf(data, "Maximum file size exceeded"); return CURLE_FILESIZE_EXCEEDED; @@ -3064,7 +3075,7 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, } } ftp_state(data, FTP_STOP); /* we are done with the CONNECT phase! */ - DEBUGF(infof(data, "protocol connect phase DONE")); + CURL_TRC_FTP(data, "[%s] protocol connect phase DONE", FTP_DSTATE(data)); break; case FTP_SYST: @@ -3109,7 +3120,7 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, } ftp_state(data, FTP_STOP); /* we are done with the CONNECT phase! */ - DEBUGF(infof(data, "protocol connect phase DONE")); + CURL_TRC_FTP(data, "[%s] protocol connect phase DONE", FTP_DSTATE(data)); break; case FTP_NAMEFMT: @@ -3120,7 +3131,7 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, } ftp_state(data, FTP_STOP); /* we are done with the CONNECT phase! */ - DEBUGF(infof(data, "protocol connect phase DONE")); + CURL_TRC_FTP(data, "[%s] protocol connect phase DONE", FTP_DSTATE(data)); break; case FTP_QUOTE: @@ -3549,6 +3560,7 @@ static CURLcode ftp_done(struct Curl_easy *data, CURLcode status, /* Send any post-transfer QUOTE strings? */ if(!status && !result && !premature && data->set.postquote) result = ftp_sendquote(data, conn, data->set.postquote); + CURL_TRC_FTP(data, "[%s] done, result=%d", FTP_DSTATE(data), result); Curl_safefree(ftp->pathalloc); return result; } @@ -3817,7 +3829,8 @@ static CURLcode ftp_do_more(struct Curl_easy *data, int *completep) if(!ftpc->wait_data_conn) { /* no waiting for the data connection so this is now complete */ *completep = 1; - DEBUGF(infof(data, "DO-MORE phase ends with %d", (int)result)); + CURL_TRC_FTP(data, "[%s] DO-MORE phase ends with %d", FTP_DSTATE(data), + (int)result); } return result; @@ -3841,7 +3854,7 @@ CURLcode ftp_perform(struct Curl_easy *data, /* this is FTP and no proxy */ CURLcode result = CURLE_OK; - DEBUGF(infof(data, "DO phase starts")); + CURL_TRC_FTP(data, "[%s] DO phase starts", FTP_DSTATE(data)); if(data->req.no_body) { /* requested no body means no transfer... */ @@ -3861,10 +3874,15 @@ CURLcode ftp_perform(struct Curl_easy *data, *connected = Curl_conn_is_connected(data->conn, SECONDARYSOCKET); - infof(data, "ftp_perform ends with SECONDARY: %d", *connected); + if(*connected) + infof(data, "[FTP] [%s] perform, DATA connection established", + FTP_DSTATE(data)); + else + CURL_TRC_FTP(data, "[%s] perform, awaiting DATA connect", + FTP_DSTATE(data)); if(*dophase_done) - DEBUGF(infof(data, "DO phase is complete1")); + CURL_TRC_FTP(data, "[%s] DO phase is complete1", FTP_DSTATE(data)); return result; } @@ -4431,11 +4449,11 @@ static CURLcode ftp_doing(struct Curl_easy *data, CURLcode result = ftp_multi_statemach(data, dophase_done); if(result) - DEBUGF(infof(data, "DO phase failed")); + CURL_TRC_FTP(data, "[%s] DO phase failed", FTP_DSTATE(data)); else if(*dophase_done) { result = ftp_dophase_done(data, FALSE /* not connected */); - DEBUGF(infof(data, "DO phase is complete2")); + CURL_TRC_FTP(data, "[%s] DO phase is complete2", FTP_DSTATE(data)); } return result; } @@ -4559,6 +4577,7 @@ static CURLcode ftp_setup_connection(struct Curl_easy *data, ftpc->use_ssl = data->set.use_ssl; ftpc->ccc = data->set.ftp_ccc; + CURL_TRC_FTP(data, "[%s] setup connection -> %d", FTP_CSTATE(conn), result); return result; } diff --git a/lib/ftplistparser.c b/lib/ftplistparser.c index 82f1ea0..448f3a4 100644 --- a/lib/ftplistparser.c +++ b/lib/ftplistparser.c @@ -349,7 +349,7 @@ static CURLcode ftp_pl_insert_finfo(struct Curl_easy *data, Curl_set_in_callback(data, false); if(add) { - Curl_llist_insert_next(llist, llist->tail, finfo, &infop->list); + Curl_llist_append(llist, finfo, &infop->list); } else { Curl_fileinfo_cleanup(infop); diff --git a/lib/getinfo.c b/lib/getinfo.c index dd43643..e423f0b 100644 --- a/lib/getinfo.c +++ b/lib/getinfo.c @@ -161,7 +161,11 @@ static CURLcode getinfo_char(struct Curl_easy *data, CURLINFO info, *param_charp = data->info.primary.local_ip; break; case CURLINFO_RTSP_SESSION_ID: +#ifndef CURL_DISABLE_RTSP *param_charp = data->set.str[STRING_RTSP_SESSION_ID]; +#else + *param_charp = NULL; +#endif break; case CURLINFO_SCHEME: *param_charp = data->info.conn_scheme; diff --git a/lib/gopher.c b/lib/gopher.c index e1a1ba6..311bd37 100644 --- a/lib/gopher.c +++ b/lib/gopher.c @@ -62,7 +62,7 @@ static CURLcode gopher_connecting(struct Curl_easy *data, bool *done); */ const struct Curl_handler Curl_handler_gopher = { - "GOPHER", /* scheme */ + "gopher", /* scheme */ ZERO_NULL, /* setup_connection */ gopher_do, /* do_it */ ZERO_NULL, /* done */ @@ -76,6 +76,7 @@ const struct Curl_handler Curl_handler_gopher = { ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_GOPHER, /* defport */ @@ -86,7 +87,7 @@ const struct Curl_handler Curl_handler_gopher = { #ifdef USE_SSL const struct Curl_handler Curl_handler_gophers = { - "GOPHERS", /* scheme */ + "gophers", /* scheme */ ZERO_NULL, /* setup_connection */ gopher_do, /* do_it */ ZERO_NULL, /* done */ @@ -100,6 +101,7 @@ const struct Curl_handler Curl_handler_gophers = { ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_GOPHER, /* defport */ @@ -57,7 +57,7 @@ hash_element_dtor(void *user, void *element) */ void Curl_hash_init(struct Curl_hash *h, - int slots, + size_t slots, hash_function hfunc, comp_function comparator, Curl_hash_dtor dtor) @@ -111,7 +111,7 @@ Curl_hash_add(struct Curl_hash *h, void *key, size_t key_len, void *p) DEBUGASSERT(h); DEBUGASSERT(h->slots); if(!h->table) { - int i; + size_t i; h->table = malloc(h->slots * sizeof(struct Curl_llist)); if(!h->table) return NULL; /* OOM */ @@ -132,7 +132,7 @@ Curl_hash_add(struct Curl_hash *h, void *key, size_t key_len, void *p) he = mk_hash_element(key, key_len, p); if(he) { - Curl_llist_insert_next(l, l->tail, he, &he->list); + Curl_llist_append(l, he, &he->list); ++h->size; return p; /* return the new entry */ } @@ -192,25 +192,6 @@ Curl_hash_pick(struct Curl_hash *h, void *key, size_t key_len) return NULL; } -#if defined(DEBUGBUILD) && defined(AGGRESSIVE_TEST) -void -Curl_hash_apply(Curl_hash *h, void *user, - void (*cb)(void *user, void *ptr)) -{ - struct Curl_llist_element *le; - int i; - - for(i = 0; i < h->slots; ++i) { - for(le = (h->table[i])->head; - le; - le = le->next) { - Curl_hash_element *el = le->ptr; - cb(user, el->ptr); - } - } -} -#endif - /* Destroys all the entries in the given hash and resets its attributes, * prepping the given hash for [static|dynamic] deallocation. * @@ -222,7 +203,7 @@ void Curl_hash_destroy(struct Curl_hash *h) { if(h->table) { - int i; + size_t i; for(i = 0; i < h->slots; ++i) { Curl_llist_destroy(&h->table[i], (void *) h); } @@ -250,7 +231,7 @@ Curl_hash_clean_with_criterium(struct Curl_hash *h, void *user, struct Curl_llist_element *le; struct Curl_llist_element *lnext; struct Curl_llist *list; - int i; + size_t i; if(!h || !h->table) return; @@ -316,7 +297,7 @@ Curl_hash_next_element(struct Curl_hash_iterator *iter) /* If we have reached the end of the list, find the next one */ if(!iter->current_element) { - int i; + size_t i; for(i = iter->slot_index; i < h->slots; i++) { if(h->table[i].head) { iter->current_element = h->table[i].head; @@ -339,7 +320,7 @@ void Curl_hash_print(struct Curl_hash *h, { struct Curl_hash_iterator iter; struct Curl_hash_element *he; - int last_index = -1; + size_t last_index = ~0; if(!h) return; @@ -352,7 +333,7 @@ void Curl_hash_print(struct Curl_hash *h, while(he) { if(iter.slot_index != last_index) { fprintf(stderr, "index %d:", iter.slot_index); - if(last_index >= 0) { + if(last_index != ~0) { fprintf(stderr, "\n"); } last_index = iter.slot_index; @@ -368,3 +349,25 @@ void Curl_hash_print(struct Curl_hash *h, fprintf(stderr, "\n"); } #endif + +void Curl_hash_offt_init(struct Curl_hash *h, + size_t slots, + Curl_hash_dtor dtor) +{ + Curl_hash_init(h, slots, Curl_hash_str, Curl_str_key_compare, dtor); +} + +void *Curl_hash_offt_set(struct Curl_hash *h, curl_off_t id, void *elem) +{ + return Curl_hash_add(h, &id, sizeof(id), elem); +} + +int Curl_hash_offt_remove(struct Curl_hash *h, curl_off_t id) +{ + return Curl_hash_delete(h, &id, sizeof(id)); +} + +void *Curl_hash_offt_get(struct Curl_hash *h, curl_off_t id) +{ + return Curl_hash_pick(h, &id, sizeof(id)); +} @@ -54,7 +54,7 @@ struct Curl_hash { /* Comparator function to compare keys */ comp_function comp_func; Curl_hash_dtor dtor; - int slots; + size_t slots; size_t size; }; @@ -67,12 +67,12 @@ struct Curl_hash_element { struct Curl_hash_iterator { struct Curl_hash *hash; - int slot_index; + size_t slot_index; struct Curl_llist_element *current_element; }; void Curl_hash_init(struct Curl_hash *h, - int slots, + size_t slots, hash_function hfunc, comp_function comparator, Curl_hash_dtor dtor); @@ -80,8 +80,6 @@ void Curl_hash_init(struct Curl_hash *h, void *Curl_hash_add(struct Curl_hash *h, void *key, size_t key_len, void *p); int Curl_hash_delete(struct Curl_hash *h, void *key, size_t key_len); void *Curl_hash_pick(struct Curl_hash *, void *key, size_t key_len); -void Curl_hash_apply(struct Curl_hash *h, void *user, - void (*cb)(void *user, void *ptr)); #define Curl_hash_count(h) ((h)->size) void Curl_hash_destroy(struct Curl_hash *h); void Curl_hash_clean(struct Curl_hash *h); @@ -98,5 +96,13 @@ Curl_hash_next_element(struct Curl_hash_iterator *iter); void Curl_hash_print(struct Curl_hash *h, void (*func)(void *)); +/* Hash for `curl_off_t` as key */ +void Curl_hash_offt_init(struct Curl_hash *h, size_t slots, + Curl_hash_dtor dtor); + +void *Curl_hash_offt_set(struct Curl_hash *h, curl_off_t id, void *elem); +int Curl_hash_offt_remove(struct Curl_hash *h, curl_off_t id); +void *Curl_hash_offt_get(struct Curl_hash *h, curl_off_t id); + #endif /* HEADER_CURL_HASH_H */ diff --git a/lib/headers.c b/lib/headers.c index 0c53dec..9a5692e 100644 --- a/lib/headers.c +++ b/lib/headers.c @@ -253,7 +253,7 @@ static CURLcode unfold_value(struct Curl_easy *data, const char *value, newhs = Curl_saferealloc(hs, sizeof(*hs) + vlen + oalloc + 1); if(!newhs) return CURLE_OUT_OF_MEMORY; - /* ->name' and ->value point into ->buffer (to keep the header allocation + /* ->name and ->value point into ->buffer (to keep the header allocation in a single memory block), which now potentially have moved. Adjust them. */ newhs->name = newhs->buffer; @@ -264,8 +264,7 @@ static CURLcode unfold_value(struct Curl_easy *data, const char *value, newhs->value[olen + vlen] = 0; /* null-terminate at newline */ /* insert this node into the list of headers */ - Curl_llist_insert_next(&data->state.httphdrs, data->state.httphdrs.tail, - newhs, &newhs->node); + Curl_llist_append(&data->state.httphdrs, newhs, &newhs->node); data->state.prevhead = newhs; return CURLE_OK; } @@ -328,8 +327,7 @@ CURLcode Curl_headers_push(struct Curl_easy *data, const char *header, hs->request = data->state.requests; /* insert this node into the list of headers */ - Curl_llist_insert_next(&data->state.httphdrs, data->state.httphdrs.tail, - hs, &hs->node); + Curl_llist_append(&data->state.httphdrs, hs, &hs->node); data->state.prevhead = hs; } else @@ -361,6 +359,8 @@ static CURLcode hds_cw_collect_write(struct Curl_easy *data, (type & CLIENTWRITE_TRAILER ? CURLH_TRAILER : CURLH_HEADER))); CURLcode result = Curl_headers_push(data, buf, htype); + CURL_TRC_WRITE(data, "header_collect pushed(type=%x, len=%zu) -> %d", + htype, blen, result); if(result) return result; } diff --git a/lib/hostip.c b/lib/hostip.c index 442817d..93c36e0 100644 --- a/lib/hostip.c +++ b/lib/hostip.c @@ -144,7 +144,7 @@ void Curl_printable_address(const struct Curl_addrinfo *ai, char *buf, (void)Curl_inet_ntop(ai->ai_family, (const void *)ipaddr4, buf, bufsize); break; } -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 case AF_INET6: { const struct sockaddr_in6 *sa6 = (const void *)ai->ai_addr; const struct in6_addr *ipaddr6 = &sa6->sin6_addr; @@ -167,17 +167,12 @@ create_hostcache_id(const char *name, int port, char *ptr, size_t buflen) { size_t len = nlen ? nlen : strlen(name); - size_t olen = 0; DEBUGASSERT(buflen >= MAX_HOSTCACHE_LEN); if(len > (buflen - 7)) len = buflen - 7; /* store and lower case the name */ - while(len--) { - *ptr++ = Curl_raw_tolower(*name++); - olen++; - } - olen += msnprintf(ptr, 7, ":%u", port); - return olen; + Curl_strntolower(ptr, name, len); + return msnprintf(&ptr[len], 7, ":%u", port) + len; } struct hostcache_prune_data { @@ -249,7 +244,7 @@ void Curl_hostcache_prune(struct Curl_easy *data) if(data->share) Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); - time(&now); + now = time(NULL); do { /* Remove outdated and unused entries from the hostcache */ @@ -303,7 +298,7 @@ static struct Curl_dns_entry *fetch_addr(struct Curl_easy *data, /* See whether the returned entry is stale. Done before we release lock */ struct hostcache_prune_data user; - time(&user.now); + user.now = time(NULL); user.cache_timeout = data->set.dns_cache_timeout; user.oldest = 0; @@ -523,7 +518,7 @@ Curl_cache_addr(struct Curl_easy *data, return dns; } -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 /* return a static IPv6 ::1 for the name */ static struct Curl_addrinfo *get_localhost6(int port, const char *name) { @@ -600,7 +595,7 @@ static struct Curl_addrinfo *get_localhost(int port, const char *name) return ca6; } -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 /* * Curl_ipv6works() returns TRUE if IPv6 seems to work. */ @@ -632,7 +627,7 @@ bool Curl_ipv6works(struct Curl_easy *data) return (ipv6_works>0)?TRUE:FALSE; } } -#endif /* ENABLE_IPV6 */ +#endif /* USE_IPV6 */ /* * Curl_host_is_ipnum() returns TRUE if the given string is a numerical IPv4 @@ -641,11 +636,11 @@ bool Curl_ipv6works(struct Curl_easy *data) bool Curl_host_is_ipnum(const char *hostname) { struct in_addr in; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 struct in6_addr in6; #endif if(Curl_inet_pton(AF_INET, hostname, &in) > 0 -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 || Curl_inet_pton(AF_INET6, hostname, &in6) > 0 #endif ) @@ -760,7 +755,7 @@ enum resolve_t Curl_resolv(struct Curl_easy *data, if(!addr) return CURLRESOLV_ERROR; } -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 else { struct in6_addr in6; /* check if this is an IPv6 address string */ @@ -771,7 +766,7 @@ enum resolve_t Curl_resolv(struct Curl_easy *data, return CURLRESOLV_ERROR; } } -#endif /* ENABLE_IPV6 */ +#endif /* USE_IPV6 */ #else /* if USE_RESOLVE_ON_IPS */ #ifndef CURL_DISABLE_DOH @@ -779,7 +774,7 @@ enum resolve_t Curl_resolv(struct Curl_easy *data, if(Curl_inet_pton(AF_INET, hostname, &in) > 0) /* This is a dotted IP address 123.123.123.123-style */ ipnum = TRUE; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 else { struct in6_addr in6; /* check if this is an IPv6 address string */ @@ -787,7 +782,7 @@ enum resolve_t Curl_resolv(struct Curl_easy *data, /* This is an IPv6 address literal */ ipnum = TRUE; } -#endif /* ENABLE_IPV6 */ +#endif /* USE_IPV6 */ #endif /* CURL_DISABLE_DOH */ #endif /* !USE_RESOLVE_ON_IPS */ @@ -1070,6 +1065,23 @@ static void freednsentry(void *freethis) dns->inuse--; if(dns->inuse == 0) { Curl_freeaddrinfo(dns->addr); +#ifdef USE_HTTPSRR + if(dns->hinfo) { + if(dns->hinfo->target) + free(dns->hinfo->target); + if(dns->hinfo->alpns) + free(dns->hinfo->alpns); + if(dns->hinfo->ipv4hints) + free(dns->hinfo->ipv4hints); + if(dns->hinfo->echconfiglist) + free(dns->hinfo->echconfiglist); + if(dns->hinfo->ipv6hints) + free(dns->hinfo->ipv6hints); + if(dns->hinfo->val) + free(dns->hinfo->val); + free(dns->hinfo); + } +#endif free(dns); } } @@ -1077,7 +1089,7 @@ static void freednsentry(void *freethis) /* * Curl_init_dnscache() inits a new DNS cache. */ -void Curl_init_dnscache(struct Curl_hash *hash, int size) +void Curl_init_dnscache(struct Curl_hash *hash, size_t size) { Curl_hash_init(hash, size, Curl_hash_str, Curl_str_key_compare, freednsentry); @@ -1210,7 +1222,7 @@ CURLcode Curl_loadhostpairs(struct Curl_easy *data) memcpy(address, addr_begin, alen); address[alen] = '\0'; -#ifndef ENABLE_IPV6 +#ifndef USE_IPV6 if(strchr(address, ':')) { infof(data, "Ignoring resolve address '%s', missing IPv6 support.", address); diff --git a/lib/hostip.h b/lib/hostip.h index fb53a57..bf4e94d 100644 --- a/lib/hostip.h +++ b/lib/hostip.h @@ -32,6 +32,10 @@ #include <setjmp.h> +#ifdef USE_HTTPSRR +# include <stdint.h> +#endif + /* Allocate enough memory to hold the full name information structs and * everything. OSF1 is known to require at least 8872 bytes. The buffer * required for storing all possible aliases and IP numbers is according to @@ -58,8 +62,41 @@ struct connectdata; */ struct Curl_hash *Curl_global_host_cache_init(void); +#ifdef USE_HTTPSRR + +#define CURL_MAXLEN_host_name 253 + +struct Curl_https_rrinfo { + size_t len; /* raw encoded length */ + unsigned char *val; /* raw encoded octets */ + /* + * fields from HTTPS RR, with the mandatory fields + * first (priority, target), then the others in the + * order of the keytag numbers defined at + * https://datatracker.ietf.org/doc/html/rfc9460#section-14.3.2 + */ + uint16_t priority; + char *target; + char *alpns; /* keytag = 1 */ + bool no_def_alpn; /* keytag = 2 */ + /* + * we don't support ports (keytag = 3) as we don't support + * port-switching yet + */ + unsigned char *ipv4hints; /* keytag = 4 */ + size_t ipv4hints_len; + unsigned char *echconfiglist; /* keytag = 5 */ + size_t echconfiglist_len; + unsigned char *ipv6hints; /* keytag = 6 */ + size_t ipv6hints_len; +}; +#endif + struct Curl_dns_entry { struct Curl_addrinfo *addr; +#ifdef USE_HTTPSRR + struct Curl_https_rrinfo *hinfo; +#endif /* timestamp == 0 -- permanent CURLOPT_RESOLVE entry (doesn't time out) */ time_t timestamp; /* use-counter, use Curl_resolv_unlock to release reference */ @@ -96,7 +133,7 @@ enum resolve_t Curl_resolv_timeout(struct Curl_easy *data, struct Curl_dns_entry **dnsentry, timediff_t timeoutms); -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 /* * Curl_ipv6works() returns TRUE if IPv6 seems to work. */ @@ -129,7 +166,7 @@ void Curl_resolv_unlock(struct Curl_easy *data, struct Curl_dns_entry *dns); /* init a new dns cache */ -void Curl_init_dnscache(struct Curl_hash *hash, int hashsize); +void Curl_init_dnscache(struct Curl_hash *hash, size_t hashsize); /* prune old entries from the DNS cache */ void Curl_hostcache_prune(struct Curl_easy *data); @@ -107,11 +107,6 @@ void Curl_hsts_cleanup(struct hsts **hp) } } -static struct stsentry *hsts_entry(void) -{ - return calloc(1, sizeof(struct stsentry)); -} - static CURLcode hsts_create(struct hsts *h, const char *hostname, bool subdomains, @@ -127,7 +122,7 @@ static CURLcode hsts_create(struct hsts *h, --hlen; if(hlen) { char *duphost; - struct stsentry *sts = hsts_entry(); + struct stsentry *sts = calloc(1, sizeof(struct stsentry)); if(!sts) return CURLE_OUT_OF_MEMORY; @@ -140,7 +135,7 @@ static CURLcode hsts_create(struct hsts *h, sts->host = duphost; sts->expires = expires; sts->includeSubDomains = subdomains; - Curl_llist_insert_next(&h->list, h->list.tail, sts, &sts->node); + Curl_llist_append(&h->list, sts, &sts->node); } return CURLE_OK; } @@ -528,8 +523,11 @@ static CURLcode hsts_load(struct hsts *h, const char *file) char *lineptr = Curl_dyn_ptr(&buf); while(*lineptr && ISBLANK(*lineptr)) lineptr++; - if(*lineptr == '#') - /* skip commented lines */ + /* + * Skip empty or commented lines, since we know the line will have a + * trailing newline from Curl_get_line we can treat length 1 as empty. + */ + if((*lineptr == '#') || strlen(lineptr) <= 1) continue; hsts_add(h, lineptr); @@ -65,7 +65,6 @@ #include "vquic/vquic.h" #include "http_digest.h" #include "http_ntlm.h" -#include "curl_ntlm_wb.h" #include "http_negotiate.h" #include "http_aws_sigv4.h" #include "url.h" @@ -101,7 +100,7 @@ * Forward declarations. */ -static bool http_should_fail(struct Curl_easy *data); +static bool http_should_fail(struct Curl_easy *data, int httpcode); static bool http_exp100_is_waiting(struct Curl_easy *data); static CURLcode http_exp100_add_reader(struct Curl_easy *data); static void http_exp100_send_anyway(struct Curl_easy *data); @@ -110,7 +109,7 @@ static void http_exp100_send_anyway(struct Curl_easy *data); * HTTP handler interface. */ const struct Curl_handler Curl_handler_http = { - "HTTP", /* scheme */ + "http", /* scheme */ Curl_http_setup_conn, /* setup_connection */ Curl_http, /* do_it */ Curl_http_done, /* done */ @@ -124,6 +123,7 @@ const struct Curl_handler Curl_handler_http = { ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ Curl_http_write_resp, /* write_resp */ + Curl_http_write_resp_hd, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_HTTP, /* defport */ @@ -138,7 +138,7 @@ const struct Curl_handler Curl_handler_http = { * HTTPS handler interface. */ const struct Curl_handler Curl_handler_https = { - "HTTPS", /* scheme */ + "https", /* scheme */ Curl_http_setup_conn, /* setup_connection */ Curl_http, /* do_it */ Curl_http_done, /* done */ @@ -152,6 +152,7 @@ const struct Curl_handler Curl_handler_https = { ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ Curl_http_write_resp, /* write_resp */ + Curl_http_write_resp_hd, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_HTTPS, /* defport */ @@ -244,8 +245,6 @@ char *Curl_copy_header_value(const char *header) while(*start && ISSPACE(*start)) start++; - /* data is in the host encoding so - use '\r' and '\n' instead of 0x0d and 0x0a */ end = strchr(start, '\r'); if(!end) end = strchr(start, '\n'); @@ -386,8 +385,6 @@ static bool pickoneauth(struct auth *pick, unsigned long mask) #endif else if(avail & CURLAUTH_NTLM) pick->picked = CURLAUTH_NTLM; - else if(avail & CURLAUTH_NTLM_WB) - pick->picked = CURLAUTH_NTLM_WB; #ifndef CURL_DISABLE_BASIC_AUTH else if(avail & CURLAUTH_BASIC) pick->picked = CURLAUTH_BASIC; @@ -446,9 +443,7 @@ static CURLcode http_perhapsrewind(struct Curl_easy *data, /* We'd like to abort the upload - but should we? */ #if defined(USE_NTLM) if((data->state.authproxy.picked == CURLAUTH_NTLM) || - (data->state.authhost.picked == CURLAUTH_NTLM) || - (data->state.authproxy.picked == CURLAUTH_NTLM_WB) || - (data->state.authhost.picked == CURLAUTH_NTLM_WB)) { + (data->state.authhost.picked == CURLAUTH_NTLM)) { ongoing_auth = "NTML"; if((conn->http_ntlm_state != NTLMSTATE_NONE) || (conn->proxy_ntlm_state != NTLMSTATE_NONE)) { @@ -570,7 +565,7 @@ CURLcode Curl_http_auth_act(struct Curl_easy *data) data->state.authhost.done = TRUE; } } - if(http_should_fail(data)) { + if(http_should_fail(data, data->req.httpcode)) { failf(data, "The requested URL returned error: %d", data->req.httpcode); result = CURLE_HTTP_RETURNED_ERROR; @@ -627,15 +622,6 @@ output_auth_headers(struct Curl_easy *data, } else #endif -#if defined(USE_NTLM) && defined(NTLM_WB_ENABLED) - if(authstatus->picked == CURLAUTH_NTLM_WB) { - auth = "NTLM_WB"; - result = Curl_output_ntlm_wb(data, conn, proxy); - if(result) - return result; - } - else -#endif #ifndef CURL_DISABLE_DIGEST_AUTH if(authstatus->picked == CURLAUTH_DIGEST) { auth = "Digest"; @@ -925,31 +911,15 @@ CURLcode Curl_http_input_auth(struct Curl_easy *data, bool proxy, /* NTLM support requires the SSL crypto libs */ if(checkprefix("NTLM", auth) && is_valid_auth_separator(auth[4])) { if((authp->avail & CURLAUTH_NTLM) || - (authp->avail & CURLAUTH_NTLM_WB) || Curl_auth_is_ntlm_supported()) { *availp |= CURLAUTH_NTLM; authp->avail |= CURLAUTH_NTLM; - if(authp->picked == CURLAUTH_NTLM || - authp->picked == CURLAUTH_NTLM_WB) { + if(authp->picked == CURLAUTH_NTLM) { /* NTLM authentication is picked and activated */ CURLcode result = Curl_input_ntlm(data, proxy, auth); if(!result) { data->state.authproblem = FALSE; -#ifdef NTLM_WB_ENABLED - if(authp->picked == CURLAUTH_NTLM_WB) { - *availp &= ~CURLAUTH_NTLM; - authp->avail &= ~CURLAUTH_NTLM; - *availp |= CURLAUTH_NTLM_WB; - authp->avail |= CURLAUTH_NTLM_WB; - - result = Curl_input_ntlm_wb(data, conn, proxy, auth); - if(result) { - infof(data, "Authentication problem. Ignoring this."); - data->state.authproblem = TRUE; - } - } -#endif } else { infof(data, "Authentication problem. Ignoring this."); @@ -1036,21 +1006,18 @@ CURLcode Curl_http_input_auth(struct Curl_easy *data, bool proxy, } /** - * http_should_fail() determines whether an HTTP response has gotten us + * http_should_fail() determines whether an HTTP response code has gotten us * into an error state or not. * * @retval FALSE communications should continue * * @retval TRUE communications should not continue */ -static bool http_should_fail(struct Curl_easy *data) +static bool http_should_fail(struct Curl_easy *data, int httpcode) { - int httpcode; DEBUGASSERT(data); DEBUGASSERT(data->conn); - httpcode = data->req.httpcode; - /* ** If we haven't been asked to fail on error, ** don't fail. @@ -2046,8 +2013,19 @@ static CURLcode set_reader(struct Curl_easy *data, Curl_HttpReq httpreq) else result = Curl_creader_set_null(data); } - else { /* we read the bytes from the callback */ - result = Curl_creader_set_fread(data, postsize); + else { + /* we read the bytes from the callback. In case "chunked" encoding + * is forced by the application, we disregard `postsize`. This is + * a backward compatibility decision to earlier versions where + * chunking disregarded this. See issue #13229. */ + bool chunked = FALSE; + char *ptr = Curl_checkheaders(data, STRCONST("Transfer-Encoding")); + if(ptr) { + /* Some kind of TE is requested, check if 'chunked' is chosen */ + chunked = Curl_compareheader(ptr, STRCONST("Transfer-Encoding:"), + STRCONST("chunked")); + } + result = Curl_creader_set_fread(data, chunked? -1 : postsize); } return result; @@ -2115,6 +2093,13 @@ CURLcode Curl_http_req_set_reader(struct Curl_easy *data, data->req.upload_chunky = Curl_compareheader(ptr, STRCONST("Transfer-Encoding:"), STRCONST("chunked")); + if(data->req.upload_chunky && + Curl_use_http_1_1plus(data, data->conn) && + (data->conn->httpversion >= 20)) { + infof(data, "suppressing chunked transfer encoding on connection " + "using HTTP version 2 or higher"); + data->req.upload_chunky = FALSE; + } } else { curl_off_t req_clen = Curl_creader_total_length(data); @@ -2253,9 +2238,11 @@ CURLcode Curl_http_req_complete(struct Curl_easy *data, /* end of headers */ result = Curl_dyn_addn(r, STRCONST("\r\n")); - Curl_pgrsSetUploadSize(data, req_clen); - if(announced_exp100) - result = http_exp100_add_reader(data); + if(!result) { + Curl_pgrsSetUploadSize(data, req_clen); + if(announced_exp100) + result = http_exp100_add_reader(data); + } out: if(!result) { @@ -2666,8 +2653,12 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) httpstring, (data->state.aptr.host?data->state.aptr.host:""), +#ifndef CURL_DISABLE_PROXY data->state.aptr.proxyuserpwd? data->state.aptr.proxyuserpwd:"", +#else + "", +#endif data->state.aptr.userpwd?data->state.aptr.userpwd:"", (data->state.use_range && data->state.aptr.rangeline)? data->state.aptr.rangeline:"", @@ -2701,7 +2692,9 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) /* clear userpwd and proxyuserpwd to avoid reusing old credentials * from reused connections */ Curl_safefree(data->state.aptr.userpwd); +#ifndef CURL_DISABLE_PROXY Curl_safefree(data->state.aptr.proxyuserpwd); +#endif free(altused); if(result) { @@ -2842,9 +2835,10 @@ checkprotoprefix(struct Curl_easy *data, struct connectdata *conn, /* * Curl_http_header() parses a single response header. */ -CURLcode Curl_http_header(struct Curl_easy *data, struct connectdata *conn, - char *hd, size_t hdlen) +CURLcode Curl_http_header(struct Curl_easy *data, + const char *hd, size_t hdlen) { + struct connectdata *conn = data->conn; CURLcode result; struct SingleRequest *k = &data->req; const char *v; @@ -2854,7 +2848,7 @@ CURLcode Curl_http_header(struct Curl_easy *data, struct connectdata *conn, case 'A': #ifndef CURL_DISABLE_ALTSVC v = (data->asi && - ((conn->handler->flags & PROTOPT_SSL) || + ((data->conn->handler->flags & PROTOPT_SSL) || #ifdef CURLDEBUG /* allow debug builds to circumvent the HTTPS restriction */ getenv("CURL_ALTSVC_HTTP") @@ -3145,19 +3139,23 @@ CURLcode Curl_http_header(struct Curl_easy *data, struct connectdata *conn, break; case 't': case 'T': - v = !k->http_bodyless? HD_VAL(hd, hdlen, "Transfer-Encoding:") : NULL; + /* RFC 9112, ch. 6.1 + * "Transfer-Encoding MAY be sent in a response to a HEAD request or + * in a 304 (Not Modified) response (Section 15.4.5 of [HTTP]) to a + * GET request, neither of which includes a message body, to indicate + * that the origin server would have applied a transfer coding to the + * message body if the request had been an unconditional GET." + * + * Read: in these cases the 'Transfer-Encoding' does not apply + * to any data following the response headers. Do not add any decoders. + */ + v = (!k->http_bodyless && + (data->state.httpreq != HTTPREQ_HEAD) && + (k->httpcode != 304))? + HD_VAL(hd, hdlen, "Transfer-Encoding:") : NULL; if(v) { /* One or more encodings. We check for chunked and/or a compression algorithm. */ - /* - * [RFC 2616, section 3.6.1] A 'chunked' transfer encoding - * means that the server will send a series of "chunks". Each - * chunk starts with line with info (including size of the - * coming block) (terminated with CRLF), then a block of data - * with the previously mentioned size. There can be any amount - * of chunks, and a chunk-data set to zero signals the - * end-of-chunks. */ - result = Curl_build_unencoding_stack(data, v, TRUE); if(result) return result; @@ -3207,13 +3205,16 @@ CURLcode Curl_http_statusline(struct Curl_easy *data, #ifdef USE_HTTP2 case 20: #endif -#ifdef ENABLE_QUIC +#ifdef USE_HTTP3 case 30: #endif - /* TODO: we should verify that responses do not switch major - * HTTP version of the connection. Now, it seems we might accept - * a HTTP/2 response on a HTTP/1.1 connection, which is wrong. */ - conn->httpversion = (unsigned char)k->httpversion; + /* no major version switch mid-connection */ + if(conn->httpversion && + (k->httpversion/10 != conn->httpversion/10)) { + failf(data, "Version mismatch (from HTTP/%u to HTTP/%u)", + conn->httpversion/10, k->httpversion/10); + return CURLE_UNSUPPORTED_PROTOCOL; + } break; default: failf(data, "Unsupported HTTP version (%u.%d) in response", @@ -3258,10 +3259,6 @@ CURLcode Curl_http_statusline(struct Curl_easy *data, of the protocol */ conn->bundle->multiuse = BUNDLE_MULTIPLEX; } - else if(k->httpversion >= 11 && !conn->bits.close) { - /* If HTTP version is >= 1.1 and connection is persistent */ - DEBUGF(infof(data, "HTTP 1.1 or later with persistent connection")); - } k->http_bodyless = k->httpcode >= 100 && k->httpcode < 200; switch(k->httpcode) { @@ -3312,12 +3309,11 @@ CURLcode Curl_http_size(struct Curl_easy *data) return CURLE_OK; } -static CURLcode verify_header(struct Curl_easy *data) +static CURLcode verify_header(struct Curl_easy *data, + const char *hd, size_t hdlen) { struct SingleRequest *k = &data->req; - const char *header = Curl_dyn_ptr(&data->state.headerb); - size_t hlen = Curl_dyn_len(&data->state.headerb); - char *ptr = memchr(header, 0x00, hlen); + char *ptr = memchr(hd, 0x00, hdlen); if(ptr) { /* this is bad, bail out */ failf(data, "Nul byte in header"); @@ -3326,11 +3322,11 @@ static CURLcode verify_header(struct Curl_easy *data) if(k->headerline < 2) /* the first "header" is the status-line and it has no colon */ return CURLE_OK; - if(((header[0] == ' ') || (header[0] == '\t')) && k->headerline > 2) + if(((hd[0] == ' ') || (hd[0] == '\t')) && k->headerline > 2) /* line folding, can't happen on line 2 */ ; else { - ptr = memchr(header, ':', hlen); + ptr = memchr(hd, ':', hdlen); if(!ptr) { /* this is bad, bail out */ failf(data, "Header without colon"); @@ -3375,7 +3371,6 @@ static CURLcode http_on_response(struct Curl_easy *data, struct connectdata *conn = data->conn; CURLcode result = CURLE_OK; struct SingleRequest *k = &data->req; - bool switch_to_h2 = FALSE; (void)buf; /* not used without HTTP2 enabled */ *pconsumed = 0; @@ -3394,96 +3389,92 @@ static CURLcode http_on_response(struct Curl_easy *data, return CURLE_UNSUPPORTED_PROTOCOL; } else if(k->httpcode < 200) { - /* "A user agent MAY ignore unexpected 1xx status responses." */ + /* "A user agent MAY ignore unexpected 1xx status responses." + * By default, we expect to get more responses after this one. */ + k->header = TRUE; + k->headerline = 0; /* restart the header line counter */ + switch(k->httpcode) { case 100: /* * We have made an HTTP PUT or POST and this is 1.1-lingo * that tells us that the server is OK with this and ready * to receive the data. - * However, we'll get more headers now so we must get - * back into the header-parsing state! */ - k->header = TRUE; - k->headerline = 0; /* restart the header line counter */ - - /* if we did wait for this do enable write now! */ Curl_http_exp100_got100(data); break; case 101: - if(conn->httpversion == 11) { - /* Switching Protocols only allowed from HTTP/1.1 */ - if(k->upgr101 == UPGR101_H2) { - /* Switching to HTTP/2 */ - infof(data, "Received 101, Switching to HTTP/2"); - k->upgr101 = UPGR101_RECEIVED; - - /* we'll get more headers (HTTP/2 response) */ - k->header = TRUE; - k->headerline = 0; /* restart the header line counter */ - switch_to_h2 = TRUE; - } -#ifdef USE_WEBSOCKETS - else if(k->upgr101 == UPGR101_WS) { - /* verify the response */ - result = Curl_ws_accept(data, buf, blen); - if(result) - return result; - k->header = FALSE; /* no more header to parse! */ - *pconsumed += blen; /* ws accept handled the data */ - blen = 0; - if(data->set.connect_only) - k->keepon &= ~KEEP_RECV; /* read no more content */ - } -#endif - else { - /* Not switching to another protocol */ - k->header = FALSE; /* no more header to parse! */ - } - } - else { + /* Switching Protocols only allowed from HTTP/1.1 */ + if(conn->httpversion != 11) { /* invalid for other HTTP versions */ failf(data, "unexpected 101 response code"); return CURLE_WEIRD_SERVER_REPLY; } + if(k->upgr101 == UPGR101_H2) { + /* Switching to HTTP/2, where we will get more responses */ + infof(data, "Received 101, Switching to HTTP/2"); + k->upgr101 = UPGR101_RECEIVED; + /* We expect more response from HTTP/2 later */ + k->header = TRUE; + k->headerline = 0; /* restart the header line counter */ + /* Any remaining `buf` bytes are already HTTP/2 and passed to + * be processed. */ + result = Curl_http2_upgrade(data, conn, FIRSTSOCKET, buf, blen); + if(result) + return result; + *pconsumed += blen; + } +#ifdef USE_WEBSOCKETS + else if(k->upgr101 == UPGR101_WS) { + /* verify the response. Any passed `buf` bytes are already in + * WebSockets format and taken in by the protocol handler. */ + result = Curl_ws_accept(data, buf, blen); + if(result) + return result; + *pconsumed += blen; /* ws accept handled the data */ + k->header = FALSE; /* we will not get more responses */ + if(data->set.connect_only) + k->keepon &= ~KEEP_RECV; /* read no more content */ + } +#endif + else { + /* We silently accept this as the final response. + * TODO: this looks, uhm, wrong. What are we switching to if we + * did not ask for an Upgrade? Maybe the application provided an + * `Upgrade: xxx` header? */ + k->header = FALSE; + } break; default: - /* the status code 1xx indicates a provisional response, so - we'll get another set of headers */ - k->header = TRUE; - k->headerline = 0; /* restart the header line counter */ + /* The server may send us other 1xx responses, like informative + * 103. This have no influence on request processing and we expect + * to receive a final response eventually. */ break; } + return result; } - else { - /* k->httpcode >= 200, final response */ - k->header = FALSE; - if(k->upgr101 == UPGR101_H2) { - /* A requested upgrade was denied, poke the multi handle to possibly - allow a pending pipewait to continue */ - Curl_multi_connchanged(data->multi); - } + /* k->httpcode >= 200, final response */ + k->header = FALSE; - if((k->size == -1) && !k->chunk && !conn->bits.close && - (conn->httpversion == 11) && - !(conn->handler->protocol & CURLPROTO_RTSP) && - data->state.httpreq != HTTPREQ_HEAD) { - /* On HTTP 1.1, when connection is not to get closed, but no - Content-Length nor Transfer-Encoding chunked have been - received, according to RFC2616 section 4.4 point 5, we - assume that the server will close the connection to - signal the end of the document. */ - infof(data, "no chunk, no close, no size. Assume close to " - "signal end"); - streamclose(conn, "HTTP: No end-of-message indicator"); - } + if(k->upgr101 == UPGR101_H2) { + /* A requested upgrade was denied, poke the multi handle to possibly + allow a pending pipewait to continue */ + Curl_multi_connchanged(data->multi); } - if(!k->header) { - result = Curl_http_size(data); - if(result) - return result; + if((k->size == -1) && !k->chunk && !conn->bits.close && + (conn->httpversion == 11) && + !(conn->handler->protocol & CURLPROTO_RTSP) && + data->state.httpreq != HTTPREQ_HEAD) { + /* On HTTP 1.1, when connection is not to get closed, but no + Content-Length nor Transfer-Encoding chunked have been + received, according to RFC2616 section 4.4 point 5, we + assume that the server will close the connection to + signal the end of the document. */ + infof(data, "no chunk, no close, no size. Assume close to " + "signal end"); + streamclose(conn, "HTTP: No end-of-message indicator"); } /* At this point we have some idea about the fate of the connection. @@ -3517,31 +3508,25 @@ static CURLcode http_on_response(struct Curl_easy *data, } #endif - /* - * When all the headers have been parsed, see if we should give - * up and return an error. - */ - if(http_should_fail(data)) { - failf(data, "The requested URL returned error: %d", - k->httpcode); - return CURLE_HTTP_RETURNED_ERROR; - } - #ifdef USE_WEBSOCKETS - /* All non-101 HTTP status codes are bad when wanting to upgrade to - websockets */ + /* All >=200 HTTP status codes are errors when wanting websockets */ if(data->req.upgr101 == UPGR101_WS) { failf(data, "Refused WebSockets upgrade: %d", k->httpcode); return CURLE_HTTP_RETURNED_ERROR; } #endif + /* Check if this response means the transfer errored. */ + if(http_should_fail(data, data->req.httpcode)) { + failf(data, "The requested URL returned error: %d", + k->httpcode); + return CURLE_HTTP_RETURNED_ERROR; + } /* Curl_http_auth_act() checks what authentication methods * that are available and decides which one (if any) to * use. It will set 'newurl' if an auth method was picked. */ result = Curl_http_auth_act(data); - if(result) return result; @@ -3612,65 +3597,244 @@ static CURLcode http_on_response(struct Curl_easy *data, infof(data, "Keep sending data to get tossed away"); k->keepon |= KEEP_SEND; } + } - if(!k->header) { - /* - * really end-of-headers. - * - * If we requested a "no body", this is a good time to get - * out and return home. + /* This is the last response that we will got for the current request. + * Check on the body size and determine if the response is complete. + */ + result = Curl_http_size(data); + if(result) + return result; + + /* If we requested a "no body", this is a good time to get + * out and return home. + */ + if(data->req.no_body) + k->download_done = TRUE; + + /* If max download size is *zero* (nothing) we already have + nothing and can safely return ok now! But for HTTP/2, we'd + like to call http2_handle_stream_close to properly close a + stream. In order to do this, we keep reading until we + close the stream. */ + if(0 == k->maxdownload + && !Curl_conn_is_http2(data, conn, FIRSTSOCKET) + && !Curl_conn_is_http3(data, conn, FIRSTSOCKET)) + k->download_done = TRUE; + + /* final response without error, prepare to receive the body */ + return Curl_http_firstwrite(data); +} + +static CURLcode http_rw_hd(struct Curl_easy *data, + const char *hd, size_t hdlen, + const char *buf_remain, size_t blen, + size_t *pconsumed) +{ + CURLcode result = CURLE_OK; + struct SingleRequest *k = &data->req; + int writetype; + + *pconsumed = 0; + if((0x0a == *hd) || (0x0d == *hd)) { + /* Empty header line means end of headers! */ + size_t consumed; + + /* now, only output this if the header AND body are requested: */ - if(data->req.no_body) - k->download_done = TRUE; - - /* If max download size is *zero* (nothing) we already have - nothing and can safely return ok now! But for HTTP/2, we'd - like to call http2_handle_stream_close to properly close a - stream. In order to do this, we keep reading until we - close the stream. */ - if(0 == k->maxdownload - && !Curl_conn_is_http2(data, conn, FIRSTSOCKET) - && !Curl_conn_is_http3(data, conn, FIRSTSOCKET)) - k->download_done = TRUE; - } - - if(switch_to_h2) { - /* Having handled the headers, we can do the HTTP/2 switch. - * Any remaining `buf` bytes are already HTTP/2 and passed to - * be processed. */ - result = Curl_http2_upgrade(data, conn, FIRSTSOCKET, buf, blen); + Curl_debug(data, CURLINFO_HEADER_IN, (char *)hd, hdlen); + + writetype = CLIENTWRITE_HEADER | + ((k->httpcode/100 == 1) ? CLIENTWRITE_1XX : 0); + + result = Curl_client_write(data, writetype, hd, hdlen); + if(result) + return result; + + result = Curl_bump_headersize(data, hdlen, FALSE); if(result) return result; - *pconsumed += blen; + + data->req.deductheadercount = + (100 <= k->httpcode && 199 >= k->httpcode)?data->req.headerbytecount:0; + + /* analyze the response to find out what to do. */ + /* Caveat: we clear anything in the header brigade, because a + * response might switch HTTP version which may call use recursively. + * Not nice, but that is currently the way of things. */ + Curl_dyn_reset(&data->state.headerb); + result = http_on_response(data, buf_remain, blen, &consumed); + if(result) + return result; + *pconsumed += consumed; + return CURLE_OK; + } + + /* + * Checks for special headers coming up. + */ + + writetype = CLIENTWRITE_HEADER; + if(!k->headerline++) { + /* This is the first header, it MUST be the error code line + or else we consider this to be the body right away! */ + bool fine_statusline = FALSE; + + k->httpversion = 0; /* Don't know yet */ + if(data->conn->handler->protocol & PROTO_FAMILY_HTTP) { + /* + * https://datatracker.ietf.org/doc/html/rfc7230#section-3.1.2 + * + * The response code is always a three-digit number in HTTP as the spec + * says. We allow any three-digit number here, but we cannot make + * guarantees on future behaviors since it isn't within the protocol. + */ + const char *p = hd; + + while(*p && ISBLANK(*p)) + p++; + if(!strncmp(p, "HTTP/", 5)) { + p += 5; + switch(*p) { + case '1': + p++; + if((p[0] == '.') && (p[1] == '0' || p[1] == '1')) { + if(ISBLANK(p[2])) { + k->httpversion = 10 + (p[1] - '0'); + p += 3; + if(ISDIGIT(p[0]) && ISDIGIT(p[1]) && ISDIGIT(p[2])) { + k->httpcode = (p[0] - '0') * 100 + (p[1] - '0') * 10 + + (p[2] - '0'); + p += 3; + if(ISSPACE(*p)) + fine_statusline = TRUE; + } + } + } + if(!fine_statusline) { + failf(data, "Unsupported HTTP/1 subversion in response"); + return CURLE_UNSUPPORTED_PROTOCOL; + } + break; + case '2': + case '3': + if(!ISBLANK(p[1])) + break; + k->httpversion = (*p - '0') * 10; + p += 2; + if(ISDIGIT(p[0]) && ISDIGIT(p[1]) && ISDIGIT(p[2])) { + k->httpcode = (p[0] - '0') * 100 + (p[1] - '0') * 10 + + (p[2] - '0'); + p += 3; + if(!ISSPACE(*p)) + break; + fine_statusline = TRUE; + } + break; + default: /* unsupported */ + failf(data, "Unsupported HTTP version in response"); + return CURLE_UNSUPPORTED_PROTOCOL; + } + } + + if(!fine_statusline) { + /* If user has set option HTTP200ALIASES, + compare header line against list of aliases + */ + statusline check = checkhttpprefix(data, hd, hdlen); + if(check == STATUS_DONE) { + fine_statusline = TRUE; + k->httpcode = 200; + k->httpversion = 10; + } + } + } + else if(data->conn->handler->protocol & CURLPROTO_RTSP) { + const char *p = hd; + while(*p && ISBLANK(*p)) + p++; + if(!strncmp(p, "RTSP/", 5)) { + p += 5; + if(ISDIGIT(*p)) { + p++; + if((p[0] == '.') && ISDIGIT(p[1])) { + if(ISBLANK(p[2])) { + p += 3; + if(ISDIGIT(p[0]) && ISDIGIT(p[1]) && ISDIGIT(p[2])) { + k->httpcode = (p[0] - '0') * 100 + (p[1] - '0') * 10 + + (p[2] - '0'); + p += 3; + if(ISSPACE(*p)) { + fine_statusline = TRUE; + k->httpversion = 11; /* RTSP acts like HTTP 1.1 */ + } + } + } + } + } + if(!fine_statusline) + return CURLE_WEIRD_SERVER_REPLY; + } + } + + if(fine_statusline) { + result = Curl_http_statusline(data, data->conn); + if(result) + return result; + writetype |= CLIENTWRITE_STATUS; + } + else { + k->header = FALSE; /* this is not a header line */ + return CURLE_WEIRD_SERVER_REPLY; + } } + result = verify_header(data, hd, hdlen); + if(result) + return result; + + result = Curl_http_header(data, hd, hdlen); + if(result) + return result; + + /* + * Taken in one (more) header. Write it to the client. + */ + Curl_debug(data, CURLINFO_HEADER_IN, (char *)hd, hdlen); + + if(k->httpcode/100 == 1) + writetype |= CLIENTWRITE_1XX; + result = Curl_client_write(data, writetype, hd, hdlen); + if(result) + return result; + + result = Curl_bump_headersize(data, hdlen, FALSE); + if(result) + return result; + return CURLE_OK; } + /* * Read any HTTP header lines from the server and pass them to the client app. */ -static CURLcode http_rw_headers(struct Curl_easy *data, - const char *buf, size_t blen, - size_t *pconsumed) +static CURLcode http_parse_headers(struct Curl_easy *data, + const char *buf, size_t blen, + size_t *pconsumed) { struct connectdata *conn = data->conn; CURLcode result = CURLE_OK; struct SingleRequest *k = &data->req; - char *hd; - size_t hdlen; char *end_ptr; bool leftover_body = FALSE; /* header line within buffer loop */ *pconsumed = 0; - do { - size_t line_length; - int writetype; - - /* data is in network encoding so use 0x0a instead of '\n' */ - end_ptr = memchr(buf, 0x0a, blen); + while(blen && k->header) { + size_t consumed; + end_ptr = memchr(buf, '\n', blen); if(!end_ptr) { /* Not a complete header line within buffer, append the data to the end of the headerbuff. */ @@ -3706,14 +3870,13 @@ static CURLcode http_rw_headers(struct Curl_easy *data, } /* decrease the size of the remaining (supposed) header line */ - line_length = (end_ptr - buf) + 1; - result = Curl_dyn_addn(&data->state.headerb, buf, line_length); + consumed = (end_ptr - buf) + 1; + result = Curl_dyn_addn(&data->state.headerb, buf, consumed); if(result) return result; - - blen -= line_length; - buf += line_length; - *pconsumed += line_length; + blen -= consumed; + buf += consumed; + *pconsumed += consumed; /**** * We now have a FULL header line in 'headerb'. @@ -3741,195 +3904,21 @@ static CURLcode http_rw_headers(struct Curl_easy *data, } } - /* headers are in network encoding so use 0x0a and 0x0d instead of '\n' - and '\r' */ - hd = Curl_dyn_ptr(&data->state.headerb); - hdlen = Curl_dyn_len(&data->state.headerb); - if((0x0a == *hd) || (0x0d == *hd)) { - /* Empty header line means end of headers! */ - size_t consumed; - - /* now, only output this if the header AND body are requested: - */ - Curl_debug(data, CURLINFO_HEADER_IN, hd, hdlen); - - writetype = CLIENTWRITE_HEADER | - ((k->httpcode/100 == 1) ? CLIENTWRITE_1XX : 0); - - result = Curl_client_write(data, writetype, hd, hdlen); - if(result) - return result; - - result = Curl_bump_headersize(data, hdlen, FALSE); - if(result) - return result; - /* We are done with this line. We reset because response - * processing might switch to HTTP/2 and that might call us - * directly again. */ - Curl_dyn_reset(&data->state.headerb); - - data->req.deductheadercount = - (100 <= k->httpcode && 199 >= k->httpcode)?data->req.headerbytecount:0; - - /* analyze the response to find out what to do */ - result = http_on_response(data, buf, blen, &consumed); - if(result) - return result; - *pconsumed += consumed; + result = http_rw_hd(data, Curl_dyn_ptr(&data->state.headerb), + Curl_dyn_len(&data->state.headerb), + buf, blen, &consumed); + /* We are done with this line. We reset because response + * processing might switch to HTTP/2 and that might call us + * directly again. */ + Curl_dyn_reset(&data->state.headerb); + if(consumed) { blen -= consumed; buf += consumed; - - if(!k->header || !blen) - goto out; /* exit header line loop */ - - continue; - } - - /* - * Checks for special headers coming up. - */ - - writetype = CLIENTWRITE_HEADER; - if(!k->headerline++) { - /* This is the first header, it MUST be the error code line - or else we consider this to be the body right away! */ - bool fine_statusline = FALSE; - - k->httpversion = 0; /* Don't know yet */ - if(conn->handler->protocol & PROTO_FAMILY_HTTP) { - /* - * https://datatracker.ietf.org/doc/html/rfc7230#section-3.1.2 - * - * The response code is always a three-digit number in HTTP as the spec - * says. We allow any three-digit number here, but we cannot make - * guarantees on future behaviors since it isn't within the protocol. - */ - char *p = hd; - - while(*p && ISBLANK(*p)) - p++; - if(!strncmp(p, "HTTP/", 5)) { - p += 5; - switch(*p) { - case '1': - p++; - if((p[0] == '.') && (p[1] == '0' || p[1] == '1')) { - if(ISBLANK(p[2])) { - k->httpversion = 10 + (p[1] - '0'); - p += 3; - if(ISDIGIT(p[0]) && ISDIGIT(p[1]) && ISDIGIT(p[2])) { - k->httpcode = (p[0] - '0') * 100 + (p[1] - '0') * 10 + - (p[2] - '0'); - p += 3; - if(ISSPACE(*p)) - fine_statusline = TRUE; - } - } - } - if(!fine_statusline) { - failf(data, "Unsupported HTTP/1 subversion in response"); - return CURLE_UNSUPPORTED_PROTOCOL; - } - break; - case '2': - case '3': - if(!ISBLANK(p[1])) - break; - k->httpversion = (*p - '0') * 10; - p += 2; - if(ISDIGIT(p[0]) && ISDIGIT(p[1]) && ISDIGIT(p[2])) { - k->httpcode = (p[0] - '0') * 100 + (p[1] - '0') * 10 + - (p[2] - '0'); - p += 3; - if(!ISSPACE(*p)) - break; - fine_statusline = TRUE; - } - break; - default: /* unsupported */ - failf(data, "Unsupported HTTP version in response"); - return CURLE_UNSUPPORTED_PROTOCOL; - } - } - - if(!fine_statusline) { - /* If user has set option HTTP200ALIASES, - compare header line against list of aliases - */ - statusline check = checkhttpprefix(data, hd, hdlen); - if(check == STATUS_DONE) { - fine_statusline = TRUE; - k->httpcode = 200; - k->httpversion = 10; - } - } - } - else if(conn->handler->protocol & CURLPROTO_RTSP) { - char *p = hd; - while(*p && ISBLANK(*p)) - p++; - if(!strncmp(p, "RTSP/", 5)) { - p += 5; - if(ISDIGIT(*p)) { - p++; - if((p[0] == '.') && ISDIGIT(p[1])) { - if(ISBLANK(p[2])) { - p += 3; - if(ISDIGIT(p[0]) && ISDIGIT(p[1]) && ISDIGIT(p[2])) { - k->httpcode = (p[0] - '0') * 100 + (p[1] - '0') * 10 + - (p[2] - '0'); - p += 3; - if(ISSPACE(*p)) { - fine_statusline = TRUE; - k->httpversion = 11; /* RTSP acts like HTTP 1.1 */ - } - } - } - } - } - if(!fine_statusline) - return CURLE_WEIRD_SERVER_REPLY; - } - } - - if(fine_statusline) { - result = Curl_http_statusline(data, conn); - if(result) - return result; - writetype |= CLIENTWRITE_STATUS; - } - else { - k->header = FALSE; /* this is not a header line */ - break; - } + *pconsumed += consumed; } - - result = verify_header(data); - if(result) - return result; - - result = Curl_http_header(data, conn, hd, hdlen); - if(result) - return result; - - /* - * Taken in one (more) header. Write it to the client. - */ - Curl_debug(data, CURLINFO_HEADER_IN, hd, hdlen); - - if(k->httpcode/100 == 1) - writetype |= CLIENTWRITE_1XX; - result = Curl_client_write(data, writetype, hd, hdlen); if(result) return result; - - result = Curl_bump_headersize(data, hdlen, FALSE); - if(result) - return result; - - Curl_dyn_reset(&data->state.headerb); } - while(blen); /* We might have reached the end of the header part here, but there might be a non-header part left in the end of the read @@ -3941,6 +3930,22 @@ out: return CURLE_OK; } +CURLcode Curl_http_write_resp_hd(struct Curl_easy *data, + const char *hd, size_t hdlen, + bool is_eos) +{ + CURLcode result; + size_t consumed; + char tmp = 0; + + result = http_rw_hd(data, hd, hdlen, &tmp, 0, &consumed); + if(!result && is_eos) { + result = Curl_client_write(data, (CLIENTWRITE_BODY|CLIENTWRITE_EOS), + &tmp, 0); + } + return result; +} + /* * HTTP protocol `write_resp` implementation. Will parse headers * when not done yet and otherwise return without consuming data. @@ -3956,11 +3961,8 @@ CURLcode Curl_http_write_resp_hds(struct Curl_easy *data, else { CURLcode result; - result = http_rw_headers(data, buf, blen, pconsumed); + result = http_parse_headers(data, buf, blen, pconsumed); if(!result && !data->req.header) { - /* we have successfully finished parsing the HEADERs */ - result = Curl_http_firstwrite(data); - if(!data->req.no_body && Curl_dyn_len(&data->state.headerb)) { /* leftover from parsing something that turned out not * to be a header, only happens if we allow for @@ -44,7 +44,7 @@ typedef enum { #ifndef CURL_DISABLE_HTTP -#if defined(ENABLE_QUIC) +#if defined(USE_HTTP3) #include <stdint.h> #endif @@ -102,8 +102,8 @@ CURLcode Curl_http_target(struct Curl_easy *data, struct connectdata *conn, struct dynbuf *req); CURLcode Curl_http_statusline(struct Curl_easy *data, struct connectdata *conn); -CURLcode Curl_http_header(struct Curl_easy *data, struct connectdata *conn, - char *headp, size_t hdlen); +CURLcode Curl_http_header(struct Curl_easy *data, + const char *hd, size_t hdlen); CURLcode Curl_transferencode(struct Curl_easy *data); CURLcode Curl_http_req_set_reader(struct Curl_easy *data, Curl_HttpReq httpreq, @@ -134,6 +134,9 @@ int Curl_http_getsock_do(struct Curl_easy *data, struct connectdata *conn, CURLcode Curl_http_write_resp(struct Curl_easy *data, const char *buf, size_t blen, bool is_eos); +CURLcode Curl_http_write_resp_hd(struct Curl_easy *data, + const char *hd, size_t hdlen, + bool is_eos); /* These functions are in http.c */ CURLcode Curl_http_input_auth(struct Curl_easy *data, bool proxy, @@ -185,12 +188,8 @@ void Curl_http_exp100_got100(struct Curl_easy *data); * HTTP unique setup ***************************************************************************/ struct HTTP { -#ifndef CURL_DISABLE_HTTP - void *h2_ctx; /* HTTP/2 implementation context */ - void *h3_ctx; /* HTTP/3 implementation context */ -#else + /* TODO: no longer used, we should remove it from SingleRequest */ char unused; -#endif }; CURLcode Curl_http_size(struct Curl_easy *data); diff --git a/lib/http2.c b/lib/http2.c index 99d7f3b..f0f7b56 100644 --- a/lib/http2.c +++ b/lib/http2.c @@ -29,6 +29,7 @@ #include <nghttp2/nghttp2.h> #include "urldata.h" #include "bufq.h" +#include "hash.h" #include "http1.h" #include "http2.h" #include "http.h" @@ -127,7 +128,9 @@ struct cf_h2_ctx { struct bufq inbufq; /* network input */ struct bufq outbufq; /* network output */ struct bufc_pool stream_bufcp; /* spares for stream buffers */ + struct dynbuf scratch; /* scratch buffer for temp use */ + 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; int32_t goaway_error; @@ -153,6 +156,9 @@ static void cf_h2_ctx_clear(struct cf_h2_ctx *ctx) Curl_bufq_free(&ctx->inbufq); Curl_bufq_free(&ctx->outbufq); Curl_bufcp_free(&ctx->stream_bufcp); + Curl_dyn_free(&ctx->scratch); + Curl_hash_clean(&ctx->streams); + Curl_hash_destroy(&ctx->streams); memset(ctx, 0, sizeof(*ctx)); ctx->call_data = save; } @@ -187,6 +193,7 @@ struct h2_stream_ctx { int status_code; /* HTTP response status code */ uint32_t error; /* stream error code */ + CURLcode xfer_result; /* Result of writing out response */ uint32_t local_window_size; /* the local recv window size */ int32_t id; /* HTTP/2 protocol identifier for stream */ BIT(resp_hds_complete); /* we have a complete, final response */ @@ -198,13 +205,58 @@ struct h2_stream_ctx { buffered data in stream->sendbuf to upload. */ }; -#define H2_STREAM_CTX(d) ((struct h2_stream_ctx *)(((d) && \ - (d)->req.p.http)? \ - ((struct HTTP *)(d)->req.p.http)->h2_ctx \ - : NULL)) -#define H2_STREAM_LCTX(d) ((struct HTTP *)(d)->req.p.http)->h2_ctx -#define H2_STREAM_ID(d) (H2_STREAM_CTX(d)? \ - H2_STREAM_CTX(d)->id : -2) +#define H2_STREAM_CTX(ctx,data) ((struct h2_stream_ctx *)(\ + data? Curl_hash_offt_get(&(ctx)->streams, (data)->id) : NULL)) + +static struct h2_stream_ctx *h2_stream_ctx_create(struct cf_h2_ctx *ctx) +{ + struct h2_stream_ctx *stream; + + (void)ctx; + stream = calloc(1, sizeof(*stream)); + if(!stream) + return NULL; + + stream->id = -1; + Curl_bufq_initp(&stream->sendbuf, &ctx->stream_bufcp, + H2_STREAM_SEND_CHUNKS, BUFQ_OPT_NONE); + Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN); + Curl_dynhds_init(&stream->resp_trailers, 0, DYN_HTTP_REQUEST); + stream->resp_hds_len = 0; + stream->bodystarted = FALSE; + stream->status_code = -1; + stream->closed = FALSE; + stream->close_handled = FALSE; + stream->error = NGHTTP2_NO_ERROR; + stream->local_window_size = H2_STREAM_WINDOW_SIZE; + stream->upload_left = 0; + stream->nrcvd_data = 0; + return stream; +} + +static void free_push_headers(struct h2_stream_ctx *stream) +{ + size_t i; + for(i = 0; i<stream->push_headers_used; i++) + free(stream->push_headers[i]); + Curl_safefree(stream->push_headers); + stream->push_headers_used = 0; +} + +static void h2_stream_ctx_free(struct h2_stream_ctx *stream) +{ + Curl_bufq_free(&stream->sendbuf); + Curl_h1_req_parse_free(&stream->h1); + Curl_dynhds_free(&stream->resp_trailers); + free_push_headers(stream); + free(stream); +} + +static void h2_stream_hash_free(void *stream) +{ + DEBUGASSERT(stream); + h2_stream_ctx_free((struct h2_stream_ctx *)stream); +} /* * Mark this transfer to get "drained". @@ -241,49 +293,29 @@ static CURLcode http2_data_setup(struct Curl_cfilter *cf, failf(data, "initialization failure, transfer not http initialized"); return CURLE_FAILED_INIT; } - stream = H2_STREAM_CTX(data); + stream = H2_STREAM_CTX(ctx, data); if(stream) { *pstream = stream; return CURLE_OK; } - stream = calloc(1, sizeof(*stream)); + stream = h2_stream_ctx_create(ctx); if(!stream) return CURLE_OUT_OF_MEMORY; - stream->id = -1; - Curl_bufq_initp(&stream->sendbuf, &ctx->stream_bufcp, - H2_STREAM_SEND_CHUNKS, BUFQ_OPT_NONE); - Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN); - Curl_dynhds_init(&stream->resp_trailers, 0, DYN_HTTP_REQUEST); - stream->resp_hds_len = 0; - stream->bodystarted = FALSE; - stream->status_code = -1; - stream->closed = FALSE; - stream->close_handled = FALSE; - stream->error = NGHTTP2_NO_ERROR; - stream->local_window_size = H2_STREAM_WINDOW_SIZE; - stream->upload_left = 0; - stream->nrcvd_data = 0; + if(!Curl_hash_offt_set(&ctx->streams, data->id, stream)) { + h2_stream_ctx_free(stream); + return CURLE_OUT_OF_MEMORY; + } - H2_STREAM_LCTX(data) = stream; *pstream = stream; return CURLE_OK; } -static void free_push_headers(struct h2_stream_ctx *stream) -{ - size_t i; - for(i = 0; i<stream->push_headers_used; i++) - free(stream->push_headers[i]); - Curl_safefree(stream->push_headers); - stream->push_headers_used = 0; -} - static void http2_data_done(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_h2_ctx *ctx = cf->ctx; - struct h2_stream_ctx *stream = H2_STREAM_CTX(data); + struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); DEBUGASSERT(ctx); if(!stream) @@ -310,12 +342,7 @@ static void http2_data_done(struct Curl_cfilter *cf, struct Curl_easy *data) nghttp2_session_send(ctx->h2); } - Curl_bufq_free(&stream->sendbuf); - Curl_h1_req_parse_free(&stream->h1); - Curl_dynhds_free(&stream->resp_trailers); - free_push_headers(stream); - free(stream); - H2_STREAM_LCTX(data) = NULL; + Curl_hash_offt_remove(&ctx->streams, data->id); } static int h2_client_new(struct Curl_cfilter *cf, @@ -408,6 +435,8 @@ static CURLcode cf_h2_ctx_init(struct Curl_cfilter *cf, Curl_bufcp_init(&ctx->stream_bufcp, H2_CHUNK_SIZE, H2_STREAM_POOL_SPARES); Curl_bufq_initp(&ctx->inbufq, &ctx->stream_bufcp, H2_NW_RECV_CHUNKS, 0); 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; rc = nghttp2_session_callbacks_new(&cbs); @@ -706,6 +735,7 @@ static ssize_t send_callback(nghttp2_session *h2, the struct are hidden from the user. */ struct curl_pushheaders { struct Curl_easy *data; + struct h2_stream_ctx *stream; const nghttp2_push_promise *frame; }; @@ -719,9 +749,8 @@ char *curl_pushheader_bynum(struct curl_pushheaders *h, size_t num) if(!h || !GOOD_EASY_HANDLE(h->data)) return NULL; else { - struct h2_stream_ctx *stream = H2_STREAM_CTX(h->data); - if(stream && num < stream->push_headers_used) - return stream->push_headers[num]; + if(h->stream && num < h->stream->push_headers_used) + return h->stream->push_headers[num]; } return NULL; } @@ -744,7 +773,7 @@ char *curl_pushheader_byname(struct curl_pushheaders *h, const char *header) !strcmp(header, ":") || strchr(header + 1, ':')) return NULL; - stream = H2_STREAM_CTX(h->data); + stream = h->stream; if(!stream) return NULL; @@ -804,7 +833,7 @@ static int set_transfer_url(struct Curl_easy *data, v = curl_pushheader_byname(hp, HTTP_PSEUDO_AUTHORITY); if(v) { - uc = Curl_url_set_authority(u, v, CURLU_DISALLOW_USER); + uc = Curl_url_set_authority(u, v); if(uc) { rc = 2; goto fail; @@ -867,12 +896,10 @@ static int push_promise(struct Curl_cfilter *cf, goto fail; } - heads.data = data; - heads.frame = frame; /* ask the application */ CURL_TRC_CF(data, cf, "Got PUSH_PROMISE, ask application"); - stream = H2_STREAM_CTX(data); + stream = H2_STREAM_CTX(ctx, data); if(!stream) { failf(data, "Internal NULL stream"); discard_newhandle(cf, newhandle); @@ -880,6 +907,10 @@ static int push_promise(struct Curl_cfilter *cf, goto fail; } + heads.data = data; + heads.stream = stream; + heads.frame = frame; + rv = set_transfer_url(newhandle, &heads); if(rv) { discard_newhandle(cf, newhandle); @@ -945,12 +976,39 @@ fail: return rv; } -static CURLcode recvbuf_write_hds(struct Curl_cfilter *cf, +static void h2_xfer_write_resp_hd(struct Curl_cfilter *cf, struct Curl_easy *data, - const char *buf, size_t blen) + struct h2_stream_ctx *stream, + const char *buf, size_t blen, bool eos) { - (void)cf; - return Curl_xfer_write_resp(data, (char *)buf, blen, FALSE); + + /* If we already encountered an error, skip further writes */ + if(!stream->xfer_result) { + stream->xfer_result = Curl_xfer_write_resp_hd(data, buf, blen, eos); + if(stream->xfer_result) + CURL_TRC_CF(data, cf, "[%d] error %d writing %zu bytes of headers", + stream->id, stream->xfer_result, blen); + } +} + +static void h2_xfer_write_resp(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h2_stream_ctx *stream, + const char *buf, size_t blen, bool eos) +{ + + /* If we already encountered an error, skip further writes */ + if(!stream->xfer_result) + stream->xfer_result = Curl_xfer_write_resp(data, buf, blen, eos); + /* If the transfer write is errored, we do not want any more data */ + if(stream->xfer_result) { + struct cf_h2_ctx *ctx = cf->ctx; + CURL_TRC_CF(data, cf, "[%d] error %d writing %zu bytes of data, " + "RST-ing stream", + stream->id, stream->xfer_result, blen); + nghttp2_submit_rst_stream(ctx->h2, 0, stream->id, + NGHTTP2_ERR_CALLBACK_FAILURE); + } } static CURLcode on_stream_frame(struct Curl_cfilter *cf, @@ -958,9 +1016,8 @@ static CURLcode on_stream_frame(struct Curl_cfilter *cf, const nghttp2_frame *frame) { struct cf_h2_ctx *ctx = cf->ctx; - struct h2_stream_ctx *stream = H2_STREAM_CTX(data); + struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); int32_t stream_id = frame->hd.stream_id; - CURLcode result; int rv; if(!stream) { @@ -1008,9 +1065,7 @@ static CURLcode on_stream_frame(struct Curl_cfilter *cf, stream->status_code = -1; } - result = recvbuf_write_hds(cf, data, STRCONST("\r\n")); - if(result) - return result; + h2_xfer_write_resp_hd(cf, data, stream, STRCONST("\r\n"), stream->closed); if(stream->status_code / 100 != 1) { stream->resp_hds_complete = TRUE; @@ -1189,7 +1244,7 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, * servers send an explicit WINDOW_UPDATE, but not all seem to do that. * To be safe, we UNHOLD a stream in order not to stall. */ if(CURL_WANT_SEND(data)) { - struct h2_stream_ctx *stream = H2_STREAM_CTX(data); + struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); if(stream) drain_stream(cf, data, stream); } @@ -1229,7 +1284,6 @@ static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags, struct cf_h2_ctx *ctx = cf->ctx; struct h2_stream_ctx *stream; struct Curl_easy *data_s; - CURLcode result; (void)flags; DEBUGASSERT(stream_id); /* should never be a zero stream ID here */ @@ -1248,13 +1302,11 @@ static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags, return 0; } - stream = H2_STREAM_CTX(data_s); + stream = H2_STREAM_CTX(ctx, data_s); if(!stream) return NGHTTP2_ERR_CALLBACK_FAILURE; - result = Curl_xfer_write_resp(data_s, (char *)mem, len, FALSE); - if(result && result != CURLE_AGAIN) - return NGHTTP2_ERR_CALLBACK_FAILURE; + h2_xfer_write_resp(cf, data_s, stream, (char *)mem, len, FALSE); nghttp2_session_consume(ctx->h2, stream_id, len); stream->nrcvd_data += (curl_off_t)len; @@ -1268,6 +1320,7 @@ static int on_stream_close(nghttp2_session *session, int32_t stream_id, uint32_t error_code, void *userp) { struct Curl_cfilter *cf = userp; + struct cf_h2_ctx *ctx = cf->ctx; struct Curl_easy *data_s, *call_data = CF_DATA_CURRENT(cf); struct h2_stream_ctx *stream; int rv; @@ -1292,7 +1345,7 @@ static int on_stream_close(nghttp2_session *session, int32_t stream_id, (void)nghttp2_session_set_stream_user_data(session, stream_id, 0); return NGHTTP2_ERR_CALLBACK_FAILURE; } - stream = H2_STREAM_CTX(data_s); + stream = H2_STREAM_CTX(ctx, data_s); if(!stream) { CURL_TRC_CF(data_s, cf, "[%d] on_stream_close, GOOD easy but no stream", stream_id); @@ -1327,6 +1380,7 @@ static int on_begin_headers(nghttp2_session *session, const nghttp2_frame *frame, void *userp) { struct Curl_cfilter *cf = userp; + struct cf_h2_ctx *ctx = cf->ctx; struct h2_stream_ctx *stream; struct Curl_easy *data_s = NULL; @@ -1340,7 +1394,7 @@ static int on_begin_headers(nghttp2_session *session, return 0; } - stream = H2_STREAM_CTX(data_s); + stream = H2_STREAM_CTX(ctx, data_s); if(!stream || !stream->bodystarted) { return 0; } @@ -1356,6 +1410,7 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, void *userp) { struct Curl_cfilter *cf = userp; + struct cf_h2_ctx *ctx = cf->ctx; struct h2_stream_ctx *stream; struct Curl_easy *data_s; int32_t stream_id = frame->hd.stream_id; @@ -1371,7 +1426,7 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, internal error more than anything else! */ return NGHTTP2_ERR_CALLBACK_FAILURE; - stream = H2_STREAM_CTX(data_s); + stream = H2_STREAM_CTX(ctx, data_s); if(!stream) { failf(data_s, "Internal NULL stream"); return NGHTTP2_ERR_CALLBACK_FAILURE; @@ -1465,14 +1520,15 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, result = Curl_headers_push(data_s, buffer, CURLH_PSEUDO); if(result) return NGHTTP2_ERR_CALLBACK_FAILURE; - result = recvbuf_write_hds(cf, data_s, STRCONST("HTTP/2 ")); - if(result) - return NGHTTP2_ERR_CALLBACK_FAILURE; - result = recvbuf_write_hds(cf, data_s, (const char *)value, valuelen); - if(result) - return NGHTTP2_ERR_CALLBACK_FAILURE; - /* the space character after the status code is mandatory */ - result = recvbuf_write_hds(cf, data_s, STRCONST(" \r\n")); + Curl_dyn_reset(&ctx->scratch); + result = Curl_dyn_addn(&ctx->scratch, STRCONST("HTTP/2 ")); + if(!result) + result = Curl_dyn_addn(&ctx->scratch, value, valuelen); + if(!result) + result = Curl_dyn_addn(&ctx->scratch, STRCONST(" \r\n")); + if(!result) + h2_xfer_write_resp_hd(cf, data_s, stream, Curl_dyn_ptr(&ctx->scratch), + Curl_dyn_len(&ctx->scratch), FALSE); if(result) return NGHTTP2_ERR_CALLBACK_FAILURE; /* if we receive data for another handle, wake that up */ @@ -1487,16 +1543,17 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, /* nghttp2 guarantees that namelen > 0, and :status was already received, and this is not pseudo-header field . */ /* convert to an HTTP1-style header */ - result = recvbuf_write_hds(cf, data_s, (const char *)name, namelen); - if(result) - return NGHTTP2_ERR_CALLBACK_FAILURE; - result = recvbuf_write_hds(cf, data_s, STRCONST(": ")); - if(result) - return NGHTTP2_ERR_CALLBACK_FAILURE; - result = recvbuf_write_hds(cf, data_s, (const char *)value, valuelen); - if(result) - return NGHTTP2_ERR_CALLBACK_FAILURE; - result = recvbuf_write_hds(cf, data_s, STRCONST("\r\n")); + Curl_dyn_reset(&ctx->scratch); + result = Curl_dyn_addn(&ctx->scratch, (const char *)name, namelen); + if(!result) + result = Curl_dyn_addn(&ctx->scratch, STRCONST(": ")); + if(!result) + result = Curl_dyn_addn(&ctx->scratch, (const char *)value, valuelen); + if(!result) + result = Curl_dyn_addn(&ctx->scratch, STRCONST("\r\n")); + if(!result) + h2_xfer_write_resp_hd(cf, data_s, stream, Curl_dyn_ptr(&ctx->scratch), + Curl_dyn_len(&ctx->scratch), FALSE); if(result) return NGHTTP2_ERR_CALLBACK_FAILURE; /* if we receive data for another handle, wake that up */ @@ -1517,6 +1574,7 @@ static ssize_t req_body_read_callback(nghttp2_session *session, void *userp) { struct Curl_cfilter *cf = userp; + struct cf_h2_ctx *ctx = cf->ctx; struct Curl_easy *data_s; struct h2_stream_ctx *stream = NULL; CURLcode result; @@ -1533,7 +1591,7 @@ static ssize_t req_body_read_callback(nghttp2_session *session, internal error more than anything else! */ return NGHTTP2_ERR_CALLBACK_FAILURE; - stream = H2_STREAM_CTX(data_s); + stream = H2_STREAM_CTX(ctx, data_s); if(!stream) return NGHTTP2_ERR_CALLBACK_FAILURE; } @@ -1620,7 +1678,7 @@ static CURLcode http2_data_done_send(struct Curl_cfilter *cf, { struct cf_h2_ctx *ctx = cf->ctx; CURLcode result = CURLE_OK; - struct h2_stream_ctx *stream = H2_STREAM_CTX(data); + struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); if(!ctx || !ctx->h2 || !stream) goto out; @@ -1658,6 +1716,15 @@ static ssize_t http2_handle_stream_close(struct Curl_cfilter *cf, return -1; } else if(stream->error != NGHTTP2_NO_ERROR) { + if(stream->resp_hds_complete && data->req.no_body) { + CURL_TRC_CF(data, cf, "[%d] error after response headers, but we did " + "not want a body anyway, ignore: %s (err %u)", + stream->id, nghttp2_http2_strerror(stream->error), + stream->error); + stream->close_handled = TRUE; + *err = CURLE_OK; + goto out; + } failf(data, "HTTP/2 stream %u was not closed cleanly: %s (err %u)", stream->id, nghttp2_http2_strerror(stream->error), stream->error); @@ -1736,11 +1803,12 @@ static int sweight_in_effect(const struct Curl_easy *data) * struct. */ -static void h2_pri_spec(struct Curl_easy *data, +static void h2_pri_spec(struct cf_h2_ctx *ctx, + struct Curl_easy *data, nghttp2_priority_spec *pri_spec) { struct Curl_data_priority *prio = &data->set.priority; - struct h2_stream_ctx *depstream = H2_STREAM_CTX(prio->parent); + struct h2_stream_ctx *depstream = H2_STREAM_CTX(ctx, prio->parent); int32_t depstream_id = depstream? depstream->id:0; nghttp2_priority_spec_init(pri_spec, depstream_id, sweight_wanted(data), @@ -1758,7 +1826,7 @@ static CURLcode h2_progress_egress(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_h2_ctx *ctx = cf->ctx; - struct h2_stream_ctx *stream = H2_STREAM_CTX(data); + struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); int rv = 0; if(stream && stream->id > 0 && @@ -1768,7 +1836,7 @@ static CURLcode h2_progress_egress(struct Curl_cfilter *cf, /* send new weight and/or dependency */ nghttp2_priority_spec pri_spec; - h2_pri_spec(data, &pri_spec); + h2_pri_spec(ctx, data, &pri_spec); CURL_TRC_CF(data, cf, "[%d] Queuing PRIORITY", stream->id); DEBUGASSERT(stream->id != -1); rv = nghttp2_submit_priority(ctx->h2, NGHTTP2_FLAG_NONE, @@ -1799,7 +1867,12 @@ static ssize_t stream_recv(struct Curl_cfilter *cf, struct Curl_easy *data, (void)buf; *err = CURLE_AGAIN; - if(stream->closed) { + if(stream->xfer_result) { + CURL_TRC_CF(data, cf, "[%d] xfer write failed", stream->id); + *err = stream->xfer_result; + nread = -1; + } + else if(stream->closed) { CURL_TRC_CF(data, cf, "[%d] returning CLOSE", stream->id); nread = http2_handle_stream_close(cf, data, stream, err); } @@ -1838,7 +1911,7 @@ static CURLcode h2_progress_ingress(struct Curl_cfilter *cf, * it is time to stop due to connection close or us not processing * all network input */ while(!ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) { - stream = H2_STREAM_CTX(data); + stream = H2_STREAM_CTX(ctx, data); if(stream && (stream->closed || !data_max_bytes)) { /* We would like to abort here and stop processing, so that * the transfer loop can handle the data/close here. However, @@ -1884,7 +1957,7 @@ static ssize_t cf_h2_recv(struct Curl_cfilter *cf, struct Curl_easy *data, char *buf, size_t len, CURLcode *err) { struct cf_h2_ctx *ctx = cf->ctx; - struct h2_stream_ctx *stream = H2_STREAM_CTX(data); + struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); ssize_t nread = -1; CURLcode result; struct cf_call_data save; @@ -2016,7 +2089,7 @@ static ssize_t h2_submit(struct h2_stream_ctx **pstream, goto out; } - h2_pri_spec(data, &pri_spec); + h2_pri_spec(ctx, data, &pri_spec); if(!nghttp2_session_check_request_allowed(ctx->h2)) CURL_TRC_CF(data, cf, "send request NOT allowed (via nghttp2)"); @@ -2113,7 +2186,7 @@ static ssize_t cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data, const void *buf, size_t len, CURLcode *err) { struct cf_h2_ctx *ctx = cf->ctx; - struct h2_stream_ctx *stream = H2_STREAM_CTX(data); + struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); struct cf_call_data save; int rv; ssize_t nwritten; @@ -2294,7 +2367,7 @@ static void cf_h2_adjust_pollset(struct Curl_cfilter *cf, sock = Curl_conn_cf_get_socket(cf, data); Curl_pollset_check(data, ps, sock, &want_recv, &want_send); if(want_recv || want_send) { - struct h2_stream_ctx *stream = H2_STREAM_CTX(data); + struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); struct cf_call_data save; bool c_exhaust, s_exhaust; @@ -2395,7 +2468,7 @@ static CURLcode http2_data_pause(struct Curl_cfilter *cf, { #ifdef NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE struct cf_h2_ctx *ctx = cf->ctx; - struct h2_stream_ctx *stream = H2_STREAM_CTX(data); + struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); DEBUGASSERT(data); if(ctx && ctx->h2 && stream) { @@ -2480,7 +2553,7 @@ static bool cf_h2_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data) { struct cf_h2_ctx *ctx = cf->ctx; - struct h2_stream_ctx *stream = H2_STREAM_CTX(data); + struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); if(ctx && (!Curl_bufq_is_empty(&ctx->inbufq) || (stream && !Curl_bufq_is_empty(&stream->sendbuf)))) @@ -2539,6 +2612,11 @@ static CURLcode cf_h2_query(struct Curl_cfilter *cf, *pres1 = (effective_max > INT_MAX)? INT_MAX : (int)effective_max; CF_DATA_RESTORE(cf, save); return CURLE_OK; + case CF_QUERY_STREAM_ERROR: { + struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); + *pres1 = stream? (int)stream->error : 0; + return CURLE_OK; + } default: break; } @@ -2768,8 +2846,11 @@ CURLcode Curl_http2_upgrade(struct Curl_easy *data, CURLE_HTTP2_STREAM error! */ bool Curl_h2_http_1_1_error(struct Curl_easy *data) { - struct h2_stream_ctx *stream = H2_STREAM_CTX(data); - return (stream && stream->error == NGHTTP2_HTTP_1_1_REQUIRED); + if(Curl_conn_is_http2(data, data->conn, FIRSTSOCKET)) { + int err = Curl_conn_get_stream_error(data, data->conn, FIRSTSOCKET); + return (err == NGHTTP2_HTTP_1_1_REQUIRED); + } + return FALSE; } #else /* !USE_NGHTTP2 */ diff --git a/lib/http_aws_sigv4.c b/lib/http_aws_sigv4.c index c938291..98cc033 100644 --- a/lib/http_aws_sigv4.c +++ b/lib/http_aws_sigv4.c @@ -158,10 +158,7 @@ static CURLcode make_headers(struct Curl_easy *data, msnprintf(date_full_hdr, DATE_FULL_HDR_LEN, "x-%s-date:%s", provider1, timestamp); - if(Curl_checkheaders(data, STRCONST("Host"))) { - head = NULL; - } - else { + if(!Curl_checkheaders(data, STRCONST("Host"))) { char full_host[FULL_HOST_LEN + 1]; if(data->state.aptr.host) { diff --git a/lib/http_chunks.c b/lib/http_chunks.c index ac9d724..48e7e46 100644 --- a/lib/http_chunks.c +++ b/lib/http_chunks.c @@ -28,6 +28,7 @@ #include "urldata.h" /* it includes http_chunks.h */ #include "curl_printf.h" +#include "curl_trc.h" #include "sendf.h" /* for the client write stuff */ #include "dynbuf.h" #include "content_encoding.h" @@ -185,8 +186,11 @@ static CURLcode httpchunk_readwrite(struct Curl_easy *data, if(0 == ch->datasize) { ch->state = CHUNK_TRAILER; /* now check for trailers */ } - else + else { ch->state = CHUNK_DATA; + CURL_TRC_WRITE(data, "http_chunked, chunk start of %" + CURL_FORMAT_CURL_OFF_T " bytes", ch->datasize); + } } buf++; @@ -221,6 +225,9 @@ static CURLcode httpchunk_readwrite(struct Curl_easy *data, ch->datasize -= piece; /* decrease amount left to expect */ buf += piece; /* move read pointer forward */ blen -= piece; /* decrease space left in this round */ + CURL_TRC_WRITE(data, "http_chunked, write %zu body bytes, %" + CURL_FORMAT_CURL_OFF_T " bytes in chunk remain", + piece, ch->datasize); if(0 == ch->datasize) /* end of data this round, we now expect a trailing CRLF */ @@ -340,11 +347,14 @@ static CURLcode httpchunk_readwrite(struct Curl_easy *data, even if there's no more chunks to read */ ch->datasize = blen; ch->state = CHUNK_DONE; + CURL_TRC_WRITE(data, "http_chunk, response complete"); return CURLE_OK; } else { ch->state = CHUNK_FAILED; ch->last_code = CHUNKE_BAD_CHUNK; + CURL_TRC_WRITE(data, "http_chunk error, expected 0x0a, seeing 0x%ux", + (unsigned int)*buf); return CURLE_RECV_ERROR; } case CHUNK_DONE: @@ -498,6 +508,7 @@ static CURLcode add_last_chunk(struct Curl_easy *data, int rc; if(!data->set.trailer_callback) { + CURL_TRC_READ(data, "http_chunk, added last, empty chunk"); return Curl_bufq_cwrite(&ctx->chunkbuf, STRCONST("0\r\n\r\n"), &n); } @@ -535,6 +546,8 @@ static CURLcode add_last_chunk(struct Curl_easy *data, out: curl_slist_free_all(trailers); + CURL_TRC_READ(data, "http_chunk, added last chunk with trailers " + "from client -> %d", result); return result; } @@ -581,6 +594,8 @@ static CURLcode add_chunk(struct Curl_easy *data, result = Curl_bufq_cwrite(&ctx->chunkbuf, buf, nread, &n); if(!result) result = Curl_bufq_cwrite(&ctx->chunkbuf, "\r\n", 2, &n); + CURL_TRC_READ(data, "http_chunk, made chunk of %zu bytes -> %d", + nread, result); if(result) return result; } diff --git a/lib/http_negotiate.c b/lib/http_negotiate.c index 153e3d4..4cbe2df 100644 --- a/lib/http_negotiate.c +++ b/lib/http_negotiate.c @@ -120,16 +120,29 @@ CURLcode Curl_input_negotiate(struct Curl_easy *data, struct connectdata *conn, CURLcode Curl_output_negotiate(struct Curl_easy *data, struct connectdata *conn, bool proxy) { - struct negotiatedata *neg_ctx = proxy ? &conn->proxyneg : - &conn->negotiate; - struct auth *authp = proxy ? &data->state.authproxy : &data->state.authhost; - curlnegotiate *state = proxy ? &conn->proxy_negotiate_state : - &conn->http_negotiate_state; + struct negotiatedata *neg_ctx; + struct auth *authp; + curlnegotiate *state; char *base64 = NULL; size_t len = 0; char *userp; CURLcode result; + if(proxy) { +#ifndef CURL_DISABLE_PROXY + neg_ctx = &conn->proxyneg; + authp = &data->state.authproxy; + state = &conn->proxy_negotiate_state; +#else + return CURLE_NOT_BUILT_IN; +#endif + } + else { + neg_ctx = &conn->negotiate; + authp = &data->state.authhost; + state = &conn->http_negotiate_state; + } + authp->done = FALSE; if(*state == GSS_AUTHRECV) { @@ -171,8 +184,10 @@ CURLcode Curl_output_negotiate(struct Curl_easy *data, base64); if(proxy) { +#ifndef CURL_DISABLE_PROXY Curl_safefree(data->state.aptr.proxyuserpwd); data->state.aptr.proxyuserpwd = userp; +#endif } else { Curl_safefree(data->state.aptr.userpwd); diff --git a/lib/http_ntlm.c b/lib/http_ntlm.c index b845ddf..3dccb5c 100644 --- a/lib/http_ntlm.c +++ b/lib/http_ntlm.c @@ -40,7 +40,6 @@ #include "strcase.h" #include "http_ntlm.h" #include "curl_ntlm_core.h" -#include "curl_ntlm_wb.h" #include "curl_base64.h" #include "vauth/vauth.h" #include "url.h" @@ -266,10 +265,6 @@ void Curl_http_auth_cleanup_ntlm(struct connectdata *conn) { Curl_auth_cleanup_ntlm(&conn->ntlm); Curl_auth_cleanup_ntlm(&conn->proxyntlm); - -#if defined(NTLM_WB_ENABLED) - Curl_http_auth_cleanup_ntlm_wb(conn); -#endif } #endif /* !CURL_DISABLE_HTTP && USE_NTLM */ diff --git a/lib/http_proxy.c b/lib/http_proxy.c index 113c43a..7c035d4 100644 --- a/lib/http_proxy.c +++ b/lib/http_proxy.c @@ -293,7 +293,7 @@ static void http_proxy_cf_close(struct Curl_cfilter *cf, struct Curl_cftype Curl_cft_http_proxy = { "HTTP-PROXY", - CF_TYPE_IP_CONNECT, + CF_TYPE_IP_CONNECT|CF_TYPE_PROXY, 0, http_proxy_cf_destroy, http_proxy_cf_connect, @@ -50,6 +50,63 @@ #include "curl_memory.h" #include "memdebug.h" +/* for macOS and iOS targets */ +#if defined(USE_APPLE_IDN) +#include <unicode/uidna.h> + +static CURLcode mac_idn_to_ascii(const char *in, char **out) +{ + UErrorCode err = U_ZERO_ERROR; + UIDNA* idna = uidna_openUTS46(UIDNA_CHECK_BIDI, &err); + if(U_FAILURE(err)) { + return CURLE_OUT_OF_MEMORY; + } + else { + UIDNAInfo info = UIDNA_INFO_INITIALIZER; + char buffer[256] = {0}; + (void)uidna_nameToASCII_UTF8(idna, in, -1, buffer, + sizeof(buffer), &info, &err); + uidna_close(idna); + if(U_FAILURE(err)) { + return CURLE_URL_MALFORMAT; + } + else { + *out = strdup(buffer); + if(*out) + return CURLE_OK; + else + return CURLE_OUT_OF_MEMORY; + } + } +} + +static CURLcode mac_ascii_to_idn(const char *in, char **out) +{ + UErrorCode err = U_ZERO_ERROR; + UIDNA* idna = uidna_openUTS46(UIDNA_CHECK_BIDI, &err); + if(U_FAILURE(err)) { + return CURLE_OUT_OF_MEMORY; + } + else { + UIDNAInfo info = UIDNA_INFO_INITIALIZER; + char buffer[256] = {0}; + (void)uidna_nameToUnicodeUTF8(idna, in, -1, buffer, + sizeof(buffer), &info, &err); + uidna_close(idna); + if(U_FAILURE(err)) { + return CURLE_URL_MALFORMAT; + } + else { + *out = strdup(buffer); + if(*out) + return CURLE_OK; + else + return CURLE_OUT_OF_MEMORY; + } + } +} +#endif + #ifdef USE_WIN32_IDN /* using Windows kernel32 and normaliz libraries. */ @@ -181,6 +238,8 @@ static CURLcode idn_decode(const char *input, char **output) result = CURLE_NOT_BUILT_IN; #elif defined(USE_WIN32_IDN) result = win32_idn_to_ascii(input, &decoded); +#elif defined(USE_APPLE_IDN) + result = mac_idn_to_ascii(input, &decoded); #endif if(!result) *output = decoded; @@ -198,6 +257,10 @@ static CURLcode idn_encode(const char *puny, char **output) CURLcode result = win32_ascii_to_idn(puny, &enc); if(result) return result; +#elif defined(USE_APPLE_IDN) + CURLcode result = mac_ascii_to_idn(puny, &enc); + if(result) + return result; #endif *output = enc; return CURLE_OK; @@ -246,11 +309,7 @@ CURLcode Curl_idn_encode(const char *puny, char **output) */ void Curl_free_idnconverted_hostname(struct hostname *host) { - if(host->encalloc) { - /* must be freed with idn2_free() if allocated by libidn */ - Curl_idn_free(host->encalloc); - host->encalloc = NULL; - } + Curl_safefree(host->encalloc); } #endif /* USE_IDN */ @@ -267,20 +326,11 @@ CURLcode Curl_idnconvert_hostname(struct hostname *host) /* Check name for non-ASCII and convert hostname if we can */ if(!Curl_is_ASCII_name(host->name)) { char *decoded; - CURLcode result = idn_decode(host->name, &decoded); - if(!result) { - if(!*decoded) { - /* zero length is a bad host name */ - Curl_idn_free(decoded); - return CURLE_URL_MALFORMAT; - } - /* successful */ - host->encalloc = decoded; - /* change the name pointer to point to the encoded hostname */ - host->name = host->encalloc; - } - else + CURLcode result = Curl_idn_decode(host->name, &decoded); + if(result) return result; + /* successful */ + host->name = host->encalloc = decoded; } #endif return CURLE_OK; @@ -26,16 +26,11 @@ bool Curl_is_ASCII_name(const char *hostname); CURLcode Curl_idnconvert_hostname(struct hostname *host); -#if defined(USE_LIBIDN2) || defined(USE_WIN32_IDN) +#if defined(USE_LIBIDN2) || defined(USE_WIN32_IDN) || defined(USE_APPLE_IDN) #define USE_IDN void Curl_free_idnconverted_hostname(struct hostname *host); CURLcode Curl_idn_decode(const char *input, char **output); CURLcode Curl_idn_encode(const char *input, char **output); -#ifdef USE_LIBIDN2 -#define Curl_idn_free(x) idn2_free(x) -#else -#define Curl_idn_free(x) free(x) -#endif #else #define Curl_free_idnconverted_hostname(x) diff --git a/lib/if2ip.c b/lib/if2ip.c index 5249f6c..42e1450 100644 --- a/lib/if2ip.c +++ b/lib/if2ip.c @@ -62,7 +62,7 @@ /* ------------------------------------------------------------------ */ -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 /* Return the scope of the given address. */ unsigned int Curl_ipv6_scope(const struct sockaddr *sa) { @@ -97,17 +97,17 @@ unsigned int Curl_ipv6_scope(const struct sockaddr *sa) #if defined(HAVE_GETIFADDRS) if2ip_result_t Curl_if2ip(int af, -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 unsigned int remote_scope, unsigned int local_scope_id, #endif const char *interf, - char *buf, int buf_size) + char *buf, size_t buf_size) { struct ifaddrs *iface, *head; if2ip_result_t res = IF2IP_NOT_FOUND; -#if defined(ENABLE_IPV6) && \ +#if defined(USE_IPV6) && \ !defined(HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID) (void) local_scope_id; #endif @@ -121,7 +121,7 @@ if2ip_result_t Curl_if2ip(int af, const char *ip; char scope[12] = ""; char ipstr[64]; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 if(af == AF_INET6) { #ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID unsigned int scopeid = 0; @@ -182,12 +182,12 @@ if2ip_result_t Curl_if2ip(int af, #elif defined(HAVE_IOCTL_SIOCGIFADDR) if2ip_result_t Curl_if2ip(int af, -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 unsigned int remote_scope, unsigned int local_scope_id, #endif const char *interf, - char *buf, int buf_size) + char *buf, size_t buf_size) { struct ifreq req; struct in_addr in; @@ -196,7 +196,7 @@ if2ip_result_t Curl_if2ip(int af, size_t len; const char *r; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 (void)remote_scope; (void)local_scope_id; #endif @@ -237,15 +237,15 @@ if2ip_result_t Curl_if2ip(int af, #else if2ip_result_t Curl_if2ip(int af, -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 unsigned int remote_scope, unsigned int local_scope_id, #endif const char *interf, - char *buf, int buf_size) + char *buf, size_t buf_size) { (void) af; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 (void) remote_scope; (void) local_scope_id; #endif diff --git a/lib/if2ip.h b/lib/if2ip.h index 1f97350..f4b2f4c 100644 --- a/lib/if2ip.h +++ b/lib/if2ip.h @@ -32,7 +32,7 @@ #define IPV6_SCOPE_UNIQUELOCAL 3 /* Unique local */ #define IPV6_SCOPE_NODELOCAL 4 /* Loopback. */ -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 unsigned int Curl_ipv6_scope(const struct sockaddr *sa); #else #define Curl_ipv6_scope(x) 0 @@ -45,12 +45,12 @@ typedef enum { } if2ip_result_t; if2ip_result_t Curl_if2ip(int af, -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 unsigned int remote_scope, unsigned int local_scope_id, #endif const char *interf, - char *buf, int buf_size); + char *buf, size_t buf_size); #ifdef __INTERIX @@ -117,7 +117,7 @@ static CURLcode imap_get_message(struct Curl_easy *data, struct bufref *out); */ const struct Curl_handler Curl_handler_imap = { - "IMAP", /* scheme */ + "imap", /* scheme */ imap_setup_connection, /* setup_connection */ imap_do, /* do_it */ imap_done, /* done */ @@ -131,6 +131,7 @@ const struct Curl_handler Curl_handler_imap = { ZERO_NULL, /* perform_getsock */ imap_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_IMAP, /* defport */ @@ -146,7 +147,7 @@ const struct Curl_handler Curl_handler_imap = { */ const struct Curl_handler Curl_handler_imaps = { - "IMAPS", /* scheme */ + "imaps", /* scheme */ imap_setup_connection, /* setup_connection */ imap_do, /* do_it */ imap_done, /* done */ @@ -160,6 +161,7 @@ const struct Curl_handler Curl_handler_imaps = { ZERO_NULL, /* perform_getsock */ imap_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_IMAPS, /* defport */ diff --git a/lib/inet_ntop.c b/lib/inet_ntop.c index c9cee0c..4520b87 100644 --- a/lib/inet_ntop.c +++ b/lib/inet_ntop.c @@ -42,11 +42,11 @@ #define INT16SZ 2 /* - * If ENABLE_IPV6 is disabled, we still want to parse IPv6 addresses, so make + * If USE_IPV6 is disabled, we still want to parse IPv6 addresses, so make * sure we have _some_ value for AF_INET6 without polluting our fake value * everywhere. */ -#if !defined(ENABLE_IPV6) && !defined(AF_INET6) +#if !defined(USE_IPV6) && !defined(AF_INET6) #define AF_INET6 (AF_INET + 1) #endif diff --git a/lib/inet_pton.c b/lib/inet_pton.c index 176cc95..9cfcec1 100644 --- a/lib/inet_pton.c +++ b/lib/inet_pton.c @@ -39,11 +39,11 @@ #define INT16SZ 2 /* - * If ENABLE_IPV6 is disabled, we still want to parse IPv6 addresses, so make + * If USE_IPV6 is disabled, we still want to parse IPv6 addresses, so make * sure we have _some_ value for AF_INET6 without polluting our fake value * everywhere. */ -#if !defined(ENABLE_IPV6) && !defined(AF_INET6) +#if !defined(USE_IPV6) && !defined(AF_INET6) #define AF_INET6 (AF_INET + 1) #endif @@ -524,24 +524,33 @@ static CURLcode read_data(struct Curl_easy *data, int sockindex, return result; if(len) { - /* only realloc if there was a length */ len = ntohl(len); if(len > CURL_MAX_INPUT_LENGTH) - len = 0; - else - buf->data = Curl_saferealloc(buf->data, len); + return CURLE_TOO_LARGE; + + Curl_dyn_reset(&buf->buf); } - if(!len || !buf->data) - return CURLE_OUT_OF_MEMORY; + else + return CURLE_RECV_ERROR; - result = socket_read(data, sockindex, buf->data, len); - if(result) - return result; - nread = conn->mech->decode(conn->app_data, buf->data, len, - conn->data_prot, conn); + do { + char buffer[1024]; + nread = CURLMIN(len, (int)sizeof(buffer)); + result = socket_read(data, sockindex, buffer, nread); + if(result) + return result; + result = Curl_dyn_addn(&buf->buf, buffer, nread); + if(result) + return result; + len -= nread; + } while(len); + /* this decodes the dynbuf *in place* */ + nread = conn->mech->decode(conn->app_data, + Curl_dyn_ptr(&buf->buf), + len, conn->data_prot, conn); if(nread < 0) return CURLE_RECV_ERROR; - buf->size = (size_t)nread; + Curl_dyn_setlen(&buf->buf, nread); buf->index = 0; return CURLE_OK; } @@ -549,9 +558,10 @@ static CURLcode read_data(struct Curl_easy *data, int sockindex, static size_t buffer_read(struct krb5buffer *buf, void *data, size_t len) { - if(buf->size - buf->index < len) - len = buf->size - buf->index; - memcpy(data, (char *)buf->data + buf->index, len); + size_t size = Curl_dyn_len(&buf->buf); + if(size - buf->index < len) + len = size - buf->index; + memcpy(data, Curl_dyn_ptr(&buf->buf) + buf->index, len); buf->index += len; return len; } @@ -586,7 +596,7 @@ static ssize_t sec_recv(struct Curl_easy *data, int sockindex, while(len > 0) { if(read_data(data, sockindex, &conn->in_buffer)) return -1; - if(conn->in_buffer.size == 0) { + if(Curl_dyn_len(&conn->in_buffer.buf) == 0) { if(bytes_read > 0) conn->in_buffer.eof_flag = 1; return bytes_read; @@ -835,6 +845,7 @@ static CURLcode choose_mech(struct Curl_easy *data, struct connectdata *conn) mech->name); return CURLE_FAILED_INIT; } + Curl_dyn_init(&conn->in_buffer.buf, CURL_MAX_INPUT_LENGTH); } infof(data, "Trying mechanism %s...", mech->name); @@ -899,15 +910,10 @@ Curl_sec_end(struct connectdata *conn) { if(conn->mech && conn->mech->end) conn->mech->end(conn->app_data); - free(conn->app_data); - conn->app_data = NULL; - if(conn->in_buffer.data) { - free(conn->in_buffer.data); - conn->in_buffer.data = NULL; - conn->in_buffer.size = 0; - conn->in_buffer.index = 0; - conn->in_buffer.eof_flag = 0; - } + Curl_safefree(conn->app_data); + Curl_dyn_free(&conn->in_buffer.buf); + conn->in_buffer.index = 0; + conn->in_buffer.eof_flag = 0; conn->sec_complete = 0; conn->data_prot = PROT_CLEAR; conn->mech = NULL; @@ -164,7 +164,7 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done); */ const struct Curl_handler Curl_handler_ldap = { - "LDAP", /* scheme */ + "ldap", /* scheme */ ZERO_NULL, /* setup_connection */ ldap_do, /* do_it */ ZERO_NULL, /* done */ @@ -178,6 +178,7 @@ const struct Curl_handler Curl_handler_ldap = { ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_LDAP, /* defport */ @@ -192,7 +193,7 @@ const struct Curl_handler Curl_handler_ldap = { */ const struct Curl_handler Curl_handler_ldaps = { - "LDAPS", /* scheme */ + "ldaps", /* scheme */ ZERO_NULL, /* setup_connection */ ldap_do, /* do_it */ ZERO_NULL, /* done */ @@ -206,6 +207,7 @@ const struct Curl_handler Curl_handler_ldaps = { ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_LDAPS, /* defport */ @@ -483,6 +485,8 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) } */ #else + (void)ldap_option; + (void)ldap_ca; /* we should probably never come up to here since configure should check in first place if we can support LDAP SSL/TLS */ failf(data, "LDAP local: SSL/TLS not supported with this version " @@ -544,7 +548,8 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) goto quit; } - for(num = 0, entryIterator = ldap_first_entry(server, ldapmsg); + num = 0; + for(entryIterator = ldap_first_entry(server, ldapmsg); entryIterator; entryIterator = ldap_next_entry(server, entryIterator), num++) { BerElement *ber = NULL; diff --git a/lib/llist.c b/lib/llist.c index 5b6b033..716f0cd 100644 --- a/lib/llist.c +++ b/lib/llist.c @@ -89,6 +89,22 @@ Curl_llist_insert_next(struct Curl_llist *list, struct Curl_llist_element *e, } /* + * Curl_llist_append() + * + * Adds a new list element to the end of the list. + * + * The 'ne' argument should be a pointer into the object to store. + * + * @unittest: 1300 + */ +void +Curl_llist_append(struct Curl_llist *list, const void *p, + struct Curl_llist_element *ne) +{ + Curl_llist_insert_next(list, list->tail, p, ne); +} + +/* * @unittest: 1300 */ void diff --git a/lib/llist.h b/lib/llist.h index 320580e..d75582f 100644 --- a/lib/llist.h +++ b/lib/llist.h @@ -45,6 +45,8 @@ struct Curl_llist { void Curl_llist_init(struct Curl_llist *, Curl_llist_dtor); void Curl_llist_insert_next(struct Curl_llist *, struct Curl_llist_element *, const void *, struct Curl_llist_element *node); +void Curl_llist_append(struct Curl_llist *, + const void *, struct Curl_llist_element *node); void Curl_llist_remove(struct Curl_llist *, struct Curl_llist_element *, void *); size_t Curl_llist_count(struct Curl_llist *); @@ -55,7 +55,8 @@ #else #include <mbedtls/config.h> #endif -#if(MBEDTLS_VERSION_NUMBER >= 0x02070000) +#if(MBEDTLS_VERSION_NUMBER >= 0x02070000) && \ + (MBEDTLS_VERSION_NUMBER < 0x03000000) #define HAS_MBEDTLS_RESULT_CODE_BASED_FUNCTIONS #endif #endif /* USE_MBEDTLS */ @@ -1413,36 +1413,35 @@ CURLcode curl_mime_filedata(curl_mimepart *part, const char *filename) char *base; struct_stat sbuf; - if(stat(filename, &sbuf) || access(filename, R_OK)) + if(stat(filename, &sbuf)) result = CURLE_READ_ERROR; - - part->data = strdup(filename); - if(!part->data) - result = CURLE_OUT_OF_MEMORY; - - part->datasize = -1; - if(!result && S_ISREG(sbuf.st_mode)) { - part->datasize = filesize(filename, sbuf); - part->seekfunc = mime_file_seek; - } - - part->readfunc = mime_file_read; - part->freefunc = mime_file_free; - part->kind = MIMEKIND_FILE; - - /* As a side effect, set the filename to the current file's base name. - It is possible to withdraw this by explicitly calling - curl_mime_filename() with a NULL filename argument after the current - call. */ - base = strippath(filename); - if(!base) - result = CURLE_OUT_OF_MEMORY; else { - CURLcode res = curl_mime_filename(part, base); + part->data = strdup(filename); + if(!part->data) + result = CURLE_OUT_OF_MEMORY; + else { + part->datasize = -1; + if(S_ISREG(sbuf.st_mode)) { + part->datasize = filesize(filename, sbuf); + part->seekfunc = mime_file_seek; + } - if(res) - result = res; - free(base); + part->readfunc = mime_file_read; + part->freefunc = mime_file_free; + part->kind = MIMEKIND_FILE; + + /* As a side effect, set the filename to the current file's base name. + It is possible to withdraw this by explicitly calling + curl_mime_filename() with a NULL filename argument after the current + call. */ + base = strippath(filename); + if(!base) + result = CURLE_OUT_OF_MEMORY; + else { + result = curl_mime_filename(part, base); + free(base); + } + } } } return result; @@ -1971,7 +1970,7 @@ static CURLcode cr_mime_read(struct Curl_easy *data, switch(nread) { case 0: if((ctx->total_len >= 0) && (ctx->read_len < ctx->total_len)) { - failf(data, "client mime read EOF fail, only " + failf(data, "client mime read EOF fail, " "only %"CURL_FORMAT_CURL_OFF_T"/%"CURL_FORMAT_CURL_OFF_T " of needed bytes read", ctx->read_len, ctx->total_len); return CURLE_READ_ERROR; diff --git a/lib/mprintf.c b/lib/mprintf.c index 4c60d13..1829abc 100644 --- a/lib/mprintf.c +++ b/lib/mprintf.c @@ -77,7 +77,7 @@ static const char upper_digits[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; #define OUTCHAR(x) \ do { \ - if(!stream(x, userp)) \ + if(!stream((unsigned char)x, userp)) \ done++; \ else \ return done; /* return on failure */ \ @@ -243,7 +243,7 @@ static int parsefmt(const char *format, struct va_input *iptr; bool loopit = TRUE; fmt++; - outlen = fmt - start - 1; + outlen = (size_t)(fmt - start - 1); if(*fmt == '%') { /* this means a %% that should be output only as %. Create an output segment. */ @@ -261,7 +261,8 @@ static int parsefmt(const char *format, continue; /* while */ } - flags = width = precision = 0; + flags = 0; + width = precision = 0; if(use_dollar != DOLLAR_NOPE) { param = dollarstring(fmt, &fmt); @@ -291,7 +292,7 @@ static int parsefmt(const char *format, break; case '-': flags |= FLAGS_LEFT; - flags &= ~FLAGS_PAD_NIL; + flags &= ~(unsigned int)FLAGS_PAD_NIL; break; case '#': flags |= FLAGS_ALT; @@ -549,7 +550,7 @@ static int parsefmt(const char *format, optr = &out[ocount++]; if(ocount > MAX_SEGMENTS) return PFMT_MANYSEGS; - optr->input = param; + optr->input = (unsigned int)param; optr->flags = flags; optr->width = width; optr->precision = precision; @@ -562,7 +563,7 @@ static int parsefmt(const char *format, } /* is there a trailing piece */ - outlen = fmt - start; + outlen = (size_t)(fmt - start); if(outlen) { optr = &out[ocount++]; if(ocount > MAX_SEGMENTS) @@ -688,7 +689,7 @@ static int formatf( mp_intmax_t signed_num; /* Used to convert negative in positive. */ char *w; size_t outlen = optr->outlen; - int flags = optr->flags; + unsigned int flags = optr->flags; if(outlen) { char *str = optr->start; @@ -710,7 +711,7 @@ static int formatf( else width = -width; flags |= FLAGS_LEFT; - flags &= ~FLAGS_PAD_NIL; + flags &= ~(unsigned int)FLAGS_PAD_NIL; } } else @@ -867,7 +868,7 @@ number: str = nilstr; len = sizeof(nilstr) - 1; /* Disable quotes around (nil) */ - flags &= (~FLAGS_ALT); + flags &= ~(unsigned int)FLAGS_ALT; } else { str = ""; @@ -886,13 +887,13 @@ number: if(flags & FLAGS_ALT) OUTCHAR('"'); - if(!(flags&FLAGS_LEFT)) + if(!(flags & FLAGS_LEFT)) while(width-- > 0) OUTCHAR(' '); for(; len && *str; len--) OUTCHAR(*str++); - if(flags&FLAGS_LEFT) + if(flags & FLAGS_LEFT) while(width-- > 0) OUTCHAR(' '); @@ -952,12 +953,13 @@ number: *fptr = 0; if(width >= 0) { + size_t dlen; if(width >= (int)sizeof(work)) width = sizeof(work)-1; /* RECURSIVE USAGE */ - len = curl_msnprintf(fptr, left, "%d", width); - fptr += len; - left -= len; + dlen = (size_t)curl_msnprintf(fptr, left, "%d", width); + fptr += dlen; + left -= dlen; } if(prec >= 0) { /* for each digit in the integer part, we can have one less @@ -965,7 +967,7 @@ number: size_t maxprec = sizeof(work) - 2; double val = iptr->val.dnum; if(width > 0 && prec <= width) - maxprec -= width; + maxprec -= (size_t)width; while(val >= 10.0) { val /= 10; maxprec--; @@ -1039,7 +1041,7 @@ static int addbyter(unsigned char outc, void *f) struct nsprintf *infop = f; if(infop->length < infop->max) { /* only do this if we haven't reached max length yet */ - *infop->buffer++ = outc; /* store */ + *infop->buffer++ = (char)outc; /* store */ infop->length++; /* we are now one byte larger */ return 0; /* fputc() returns like this on success */ } @@ -1139,7 +1141,7 @@ char *curl_maprintf(const char *format, ...) static int storebuffer(unsigned char outc, void *f) { char **buffer = f; - **buffer = outc; + **buffer = (char)outc; (*buffer)++; return 0; } @@ -1160,9 +1162,7 @@ static int fputc_wrapper(unsigned char outc, void *f) int out = outc; FILE *s = f; int rc = fputc(out, s); - if(rc == out) - return 0; - return 1; + return rc == EOF; } int curl_mprintf(const char *format, ...) @@ -75,7 +75,7 @@ static CURLcode mqtt_setup_conn(struct Curl_easy *data, */ const struct Curl_handler Curl_handler_mqtt = { - "MQTT", /* scheme */ + "mqtt", /* scheme */ mqtt_setup_conn, /* setup_connection */ mqtt_do, /* do_it */ mqtt_done, /* done */ @@ -89,6 +89,7 @@ const struct Curl_handler Curl_handler_mqtt = { ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_MQTT, /* defport */ @@ -776,12 +777,12 @@ static CURLcode mqtt_doing(struct Curl_easy *data, bool *done) case MQTT_REMAINING_LENGTH: do { result = Curl_xfer_recv(data, (char *)&byte, 1, &nread); - if(!nread) + if(result || !nread) break; Curl_debug(data, CURLINFO_HEADER_IN, (char *)&byte, 1); mq->pkt_hd[mq->npacket++] = byte; } while((byte & 0x80) && (mq->npacket < 4)); - if(nread && (byte & 0x80)) + if(!result && nread && (byte & 0x80)) /* MQTT supports up to 127 * 128^0 + 127 * 128^1 + 127 * 128^2 + 127 * 128^3 bytes. server tried to send more */ result = CURLE_WEIRD_SERVER_REPLY; diff --git a/lib/multi.c b/lib/multi.c index ed9cac7..6bbdfe2 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -86,6 +86,8 @@ ((x) && (x)->magic == CURL_MULTI_HANDLE) #endif +static void move_pending_to_connect(struct Curl_multi *multi, + struct Curl_easy *data); static CURLMcode singlesocket(struct Curl_multi *multi, struct Curl_easy *data); static CURLMcode add_next_timeout(struct curltime now, @@ -100,6 +102,7 @@ static void multi_xfer_bufs_free(struct Curl_multi *multi); static const char * const multi_statename[]={ "INIT", "PENDING", + "SETUP", "CONNECT", "RESOLVING", "CONNECTING", @@ -149,6 +152,7 @@ static void mstate(struct Curl_easy *data, CURLMstate state static const init_multistate_func finit[MSTATE_LAST] = { NULL, /* INIT */ NULL, /* PENDING */ + NULL, /* SETUP */ Curl_init_CONNECT, /* CONNECT */ NULL, /* RESOLVING */ NULL, /* CONNECTING */ @@ -360,7 +364,7 @@ static size_t hash_fd(void *key, size_t key_length, size_t slots_num) * per call." * */ -static void sh_init(struct Curl_hash *hash, int hashsize) +static void sh_init(struct Curl_hash *hash, size_t hashsize) { Curl_hash_init(hash, hashsize, hash_fd, fd_key_compare, sh_freeentry); @@ -374,13 +378,12 @@ static void sh_init(struct Curl_hash *hash, int hashsize) */ static void multi_addmsg(struct Curl_multi *multi, struct Curl_message *msg) { - Curl_llist_insert_next(&multi->msglist, multi->msglist.tail, msg, - &msg->list); + Curl_llist_append(&multi->msglist, msg, &msg->list); } -struct Curl_multi *Curl_multi_handle(int hashsize, /* socket hash */ - int chashsize, /* connection hash */ - int dnssize) /* dns hash */ +struct Curl_multi *Curl_multi_handle(size_t hashsize, /* socket hash */ + size_t chashsize, /* connection hash */ + size_t dnssize) /* dns hash */ { struct Curl_multi *multi = calloc(1, sizeof(struct Curl_multi)); @@ -549,6 +552,8 @@ CURLMcode curl_multi_add_handle(struct Curl_multi *multi, if(data->set.errorbuffer) data->set.errorbuffer[0] = 0; + data->state.os_errno = 0; + /* make the Curl_easy refer back to this multi handle - before Curl_expire() is called. */ data->multi = multi; @@ -1002,8 +1007,7 @@ void Curl_attach_connection(struct Curl_easy *data, DEBUGASSERT(!data->conn); DEBUGASSERT(conn); data->conn = conn; - Curl_llist_insert_next(&conn->easyq, conn->easyq.tail, data, - &data->conn_queue); + Curl_llist_append(&conn->easyq, data, &data->conn_queue); if(conn->handler && conn->handler->attach) conn->handler->attach(data, conn); Curl_conn_ev_data_attach(conn, data); @@ -1112,6 +1116,7 @@ static void multi_getsock(struct Curl_easy *data, switch(data->mstate) { case MSTATE_INIT: case MSTATE_PENDING: + case MSTATE_SETUP: case MSTATE_CONNECT: /* nothing to poll for yet */ break; @@ -1208,6 +1213,68 @@ CURLMcode curl_multi_fdset(struct Curl_multi *multi, return CURLM_OK; } +CURLMcode curl_multi_waitfds(struct Curl_multi *multi, + struct curl_waitfd *ufds, + unsigned int size, + unsigned int *fd_count) +{ + struct Curl_easy *data; + unsigned int nfds = 0; + struct easy_pollset ps; + unsigned int i; + CURLMcode result = CURLM_OK; + struct curl_waitfd *ufd; + unsigned int j; + + if(!ufds) + return CURLM_BAD_FUNCTION_ARGUMENT; + + if(!GOOD_MULTI_HANDLE(multi)) + return CURLM_BAD_HANDLE; + + if(multi->in_callback) + return CURLM_RECURSIVE_API_CALL; + + memset(&ps, 0, sizeof(ps)); + for(data = multi->easyp; data; data = data->next) { + multi_getsock(data, &ps); + + for(i = 0; i < ps.num; i++) { + if(nfds < size) { + curl_socket_t fd = ps.sockets[i]; + int fd_idx = -1; + + /* Simple linear search to skip an already added descriptor */ + for(j = 0; j < nfds; j++) { + if(ufds[j].fd == fd) { + fd_idx = (int)j; + break; + } + } + + if(fd_idx < 0) { + ufd = &ufds[nfds++]; + ufd->fd = ps.sockets[i]; + ufd->events = 0; + } + else + ufd = &ufds[fd_idx]; + + if(ps.actions[i] & CURL_POLL_IN) + ufd->events |= CURL_WAIT_POLLIN; + if(ps.actions[i] & CURL_POLL_OUT) + ufd->events |= CURL_WAIT_POLLOUT; + } + else + return CURLM_OUT_OF_MEMORY; + } + } + + if(fd_count) + *fd_count = nfds; + return result; +} + #ifdef USE_WINSOCK /* Reset FD_WRITE for TCP sockets. Nothing is actually sent. UDP sockets can't * be reset this way because an empty datagram would be sent. #9203 @@ -1224,6 +1291,29 @@ static void reset_socket_fdwrite(curl_socket_t s) } #endif +static CURLMcode ufds_increase(struct pollfd **pfds, unsigned int *pfds_len, + unsigned int inc, bool *is_malloced) +{ + struct pollfd *new_fds, *old_fds = *pfds; + unsigned int new_len = *pfds_len + inc; + + new_fds = calloc(new_len, sizeof(struct pollfd)); + if(!new_fds) { + if(*is_malloced) + free(old_fds); + *pfds = NULL; + *pfds_len = 0; + return CURLM_OUT_OF_MEMORY; + } + memcpy(new_fds, old_fds, (*pfds_len) * sizeof(struct pollfd)); + if(*is_malloced) + free(old_fds); + *pfds = new_fds; + *pfds_len = new_len; + *is_malloced = TRUE; + return CURLM_OK; +} + #define NUM_POLLS_ON_STACK 10 static CURLMcode multi_wait(struct Curl_multi *multi, @@ -1237,12 +1327,12 @@ static CURLMcode multi_wait(struct Curl_multi *multi, struct Curl_easy *data; struct easy_pollset ps; size_t i; - unsigned int nfds = 0; - unsigned int curlfds; long timeout_internal; int retcode = 0; struct pollfd a_few_on_stack[NUM_POLLS_ON_STACK]; struct pollfd *ufds = &a_few_on_stack[0]; + unsigned int ufds_len = NUM_POLLS_ON_STACK; + unsigned int nfds = 0, curl_nfds = 0; /* how many ufds are in use */ bool ufds_malloc = FALSE; #ifdef USE_WINSOCK WSANETWORKEVENTS wsa_events; @@ -1261,13 +1351,6 @@ static CURLMcode multi_wait(struct Curl_multi *multi, if(timeout_ms < 0) return CURLM_BAD_FUNCTION_ARGUMENT; - /* Count up how many fds we have from the multi handle */ - memset(&ps, 0, sizeof(ps)); - for(data = multi->easyp; data; data = data->next) { - multi_getsock(data, &ps); - nfds += ps.num; - } - /* If the internally desired timeout is actually shorter than requested from the outside, then use the shorter time! But only if the internal timer is actually larger than -1! */ @@ -1275,70 +1358,60 @@ static CURLMcode multi_wait(struct Curl_multi *multi, if((timeout_internal >= 0) && (timeout_internal < (long)timeout_ms)) timeout_ms = (int)timeout_internal; - curlfds = nfds; /* number of internal file descriptors */ - nfds += extra_nfds; /* add the externally provided ones */ - -#ifdef ENABLE_WAKEUP -#ifdef USE_WINSOCK - if(use_wakeup) { -#else - if(use_wakeup && multi->wakeup_pair[0] != CURL_SOCKET_BAD) { -#endif - ++nfds; - } -#endif - - if(nfds > NUM_POLLS_ON_STACK) { - /* 'nfds' is a 32 bit value and 'struct pollfd' is typically 8 bytes - big, so at 2^29 sockets this value might wrap. When a process gets - the capability to actually handle over 500 million sockets this - calculation needs a integer overflow check. */ - ufds = malloc(nfds * sizeof(struct pollfd)); - if(!ufds) - return CURLM_OUT_OF_MEMORY; - ufds_malloc = TRUE; - } - nfds = 0; - - /* only do the second loop if we found descriptors in the first stage run - above */ + memset(ufds, 0, ufds_len * sizeof(struct pollfd)); + memset(&ps, 0, sizeof(ps)); - if(curlfds) { - /* Add the curl handles to our pollfds first */ - for(data = multi->easyp; data; data = data->next) { - multi_getsock(data, &ps); + /* Add the curl handles to our pollfds first */ + for(data = multi->easyp; data; data = data->next) { + multi_getsock(data, &ps); - for(i = 0; i < ps.num; i++) { - struct pollfd *ufd = &ufds[nfds++]; + for(i = 0; i < ps.num; i++) { + short events = 0; #ifdef USE_WINSOCK - long mask = 0; + long mask = 0; #endif - ufd->fd = ps.sockets[i]; - ufd->events = 0; - if(ps.actions[i] & CURL_POLL_IN) { + if(ps.actions[i] & CURL_POLL_IN) { #ifdef USE_WINSOCK - mask |= FD_READ|FD_ACCEPT|FD_CLOSE; + mask |= FD_READ|FD_ACCEPT|FD_CLOSE; #endif - ufd->events |= POLLIN; - } - if(ps.actions[i] & CURL_POLL_OUT) { + events |= POLLIN; + } + if(ps.actions[i] & CURL_POLL_OUT) { #ifdef USE_WINSOCK - mask |= FD_WRITE|FD_CONNECT|FD_CLOSE; - reset_socket_fdwrite(ps.sockets[i]); + mask |= FD_WRITE|FD_CONNECT|FD_CLOSE; + reset_socket_fdwrite(ps.sockets[i]); #endif - ufd->events |= POLLOUT; + events |= POLLOUT; + } + if(events) { + if(nfds && ps.sockets[i] == ufds[nfds-1].fd) { + ufds[nfds-1].events |= events; } + else { + if(nfds >= ufds_len) { + if(ufds_increase(&ufds, &ufds_len, 100, &ufds_malloc)) + return CURLM_OUT_OF_MEMORY; + } + DEBUGASSERT(nfds < ufds_len); + ufds[nfds].fd = ps.sockets[i]; + ufds[nfds].events = events; + ++nfds; + } + } #ifdef USE_WINSOCK + if(mask) { if(WSAEventSelect(ps.sockets[i], multi->wsa_event, mask) != 0) { if(ufds_malloc) free(ufds); return CURLM_INTERNAL_ERROR; } -#endif } +#endif } } + curl_nfds = nfds; /* what curl internally used in ufds */ + /* Add external file descriptions from poll-like struct curl_waitfd */ for(i = 0; i < extra_nfds; i++) { #ifdef USE_WINSOCK @@ -1357,6 +1430,11 @@ static CURLMcode multi_wait(struct Curl_multi *multi, return CURLM_INTERNAL_ERROR; } #endif + if(nfds >= ufds_len) { + if(ufds_increase(&ufds, &ufds_len, 100, &ufds_malloc)) + return CURLM_OUT_OF_MEMORY; + } + DEBUGASSERT(nfds < ufds_len); ufds[nfds].fd = extra_fds[i].fd; ufds[nfds].events = 0; if(extra_fds[i].events & CURL_WAIT_POLLIN) @@ -1371,6 +1449,11 @@ static CURLMcode multi_wait(struct Curl_multi *multi, #ifdef ENABLE_WAKEUP #ifndef USE_WINSOCK if(use_wakeup && multi->wakeup_pair[0] != CURL_SOCKET_BAD) { + if(nfds >= ufds_len) { + if(ufds_increase(&ufds, &ufds_len, 100, &ufds_malloc)) + return CURLM_OUT_OF_MEMORY; + } + DEBUGASSERT(nfds < ufds_len); ufds[nfds].fd = multi->wakeup_pair[0]; ufds[nfds].events = POLLIN; ++nfds; @@ -1410,7 +1493,7 @@ static CURLMcode multi_wait(struct Curl_multi *multi, struct, the bit values of the actual underlying poll() implementation may not be the same as the ones in the public libcurl API! */ for(i = 0; i < extra_nfds; i++) { - unsigned r = ufds[curlfds + i].revents; + unsigned r = ufds[curl_nfds + i].revents; unsigned short mask = 0; #ifdef USE_WINSOCK curl_socket_t s = extra_fds[i].fd; @@ -1443,7 +1526,7 @@ static CURLMcode multi_wait(struct Curl_multi *multi, #ifdef USE_WINSOCK /* Count up all our own sockets that had activity, and remove them from the event. */ - if(curlfds) { + if(curl_nfds) { for(data = multi->easyp; data; data = data->next) { multi_getsock(data, &ps); @@ -1464,7 +1547,7 @@ static CURLMcode multi_wait(struct Curl_multi *multi, #else #ifdef ENABLE_WAKEUP if(use_wakeup && multi->wakeup_pair[0] != CURL_SOCKET_BAD) { - if(ufds[curlfds + extra_nfds].revents & POLLIN) { + if(ufds[curl_nfds + extra_nfds].revents & POLLIN) { char buf[64]; ssize_t nread; while(1) { @@ -1684,47 +1767,47 @@ static bool multi_handle_timeout(struct Curl_easy *data, CURLcode *result, bool connect_timeout) { - timediff_t timeout_ms; - timeout_ms = Curl_timeleft(data, now, connect_timeout); - + timediff_t timeout_ms = Curl_timeleft(data, now, connect_timeout); if(timeout_ms < 0) { /* Handle timed out */ + struct curltime since; + if(connect_timeout) + since = data->progress.t_startsingle; + else + since = data->progress.t_startop; if(data->mstate == MSTATE_RESOLVING) failf(data, "Resolving timed out after %" CURL_FORMAT_TIMEDIFF_T - " milliseconds", - Curl_timediff(*now, data->progress.t_startsingle)); + " milliseconds", Curl_timediff(*now, since)); else if(data->mstate == MSTATE_CONNECTING) failf(data, "Connection timed out after %" CURL_FORMAT_TIMEDIFF_T - " milliseconds", - Curl_timediff(*now, data->progress.t_startsingle)); + " milliseconds", Curl_timediff(*now, since)); else { struct SingleRequest *k = &data->req; if(k->size != -1) { failf(data, "Operation timed out after %" CURL_FORMAT_TIMEDIFF_T " milliseconds with %" CURL_FORMAT_CURL_OFF_T " out of %" CURL_FORMAT_CURL_OFF_T " bytes received", - Curl_timediff(*now, data->progress.t_startsingle), - k->bytecount, k->size); + Curl_timediff(*now, since), k->bytecount, k->size); } else { failf(data, "Operation timed out after %" CURL_FORMAT_TIMEDIFF_T " milliseconds with %" CURL_FORMAT_CURL_OFF_T - " bytes received", - Curl_timediff(*now, data->progress.t_startsingle), - k->bytecount); + " bytes received", Curl_timediff(*now, since), k->bytecount); } } - - /* Force connection closed if the connection has indeed been used */ - if(data->mstate > MSTATE_DO) { - streamclose(data->conn, "Disconnected with pending data"); - *stream_error = TRUE; - } *result = CURLE_OPERATION_TIMEDOUT; - (void)multi_done(data, *result, TRUE); + if(data->conn) { + /* Force connection closed if the connection has indeed been used */ + if(data->mstate > MSTATE_DO) { + streamclose(data->conn, "Disconnect due to timeout"); + *stream_error = TRUE; + } + (void)multi_done(data, *result, TRUE); + } + return TRUE; } - return (timeout_ms < 0); + return FALSE; } /* @@ -1816,19 +1899,6 @@ static CURLcode protocol_connect(struct Curl_easy *data, return result; /* pass back status */ } -/* - * Curl_preconnect() is called immediately before a connect starts. When a - * redirect is followed, this is then called multiple times during a single - * transfer. - */ -CURLcode Curl_preconnect(struct Curl_easy *data) -{ - /* this used to do data->state.buffer allocation, - maybe remove completely now? */ - (void)data; - return CURLE_OK; -} - static void set_in_callback(struct Curl_multi *multi, bool value) { multi->in_callback = value; @@ -1882,51 +1952,44 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, return CURLM_INTERNAL_ERROR; } - if(data->conn && - (data->mstate >= MSTATE_CONNECT) && - (data->mstate < MSTATE_COMPLETED)) { - /* Check for overall operation timeout here but defer handling the - * connection timeout to later, to allow for a connection to be set up - * in the window since we last checked timeout. This prevents us - * tearing down a completed connection in the case where we were slow - * to check the timeout (e.g. process descheduled during this loop). - * We set connect_timeout=FALSE to do this. */ - - /* we need to wait for the connect state as only then is the start time - stored, but we must not check already completed handles */ - if(multi_handle_timeout(data, nowp, &stream_error, &result, FALSE)) { - /* Skip the statemachine and go directly to error handling section. */ - goto statemachine_end; - } - } + /* Wait for the connect state as only then is the start time stored, but + we must not check already completed handles */ + if((data->mstate >= MSTATE_CONNECT) && (data->mstate < MSTATE_COMPLETED) && + multi_handle_timeout(data, nowp, &stream_error, &result, FALSE)) + /* Skip the statemachine and go directly to error handling section. */ + goto statemachine_end; switch(data->mstate) { case MSTATE_INIT: - /* init this transfer. */ + /* Transitional state. init this transfer. A handle never comes + back to this state. */ result = Curl_pretransfer(data); - - if(!result) { - /* after init, go CONNECT */ - multistate(data, MSTATE_CONNECT); - *nowp = Curl_pgrsTime(data, TIMER_STARTOP); - rc = CURLM_CALL_MULTI_PERFORM; - } - break; - - case MSTATE_CONNECT: - /* Connect. We want to get a connection identifier filled in. */ - /* init this transfer. */ - result = Curl_preconnect(data); if(result) break; + /* after init, go SETUP */ + multistate(data, MSTATE_SETUP); + (void)Curl_pgrsTime(data, TIMER_STARTOP); + FALLTHROUGH(); + + case MSTATE_SETUP: + /* Transitional state. Setup things for a new transfer. The handle + can come back to this state on a redirect. */ *nowp = Curl_pgrsTime(data, TIMER_STARTSINGLE); if(data->set.timeout) Curl_expire(data, data->set.timeout, EXPIRE_TIMEOUT); - if(data->set.connecttimeout) + /* Since a connection might go to pending and back to CONNECT several + times before it actually takes off, we need to set the timeout once + in SETUP before we enter CONNECT the first time. */ Curl_expire(data, data->set.connecttimeout, EXPIRE_CONNECTTIMEOUT); + multistate(data, MSTATE_CONNECT); + FALLTHROUGH(); + + case MSTATE_CONNECT: + /* Connect. We want to get a connection identifier filled in. This state + can be entered from SETUP and from PENDING. */ result = Curl_connect(data, &async, &connected); if(CURLE_NO_CONNECTION_AVAILABLE == result) { /* There was no connection available. We will go to the pending @@ -1934,18 +1997,14 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, multistate(data, MSTATE_PENDING); /* add this handle to the list of connect-pending handles */ - Curl_llist_insert_next(&multi->pending, multi->pending.tail, data, - &data->connect_queue); + Curl_llist_append(&multi->pending, data, &data->connect_queue); /* unlink from the main list */ unlink_easy(multi, data); result = CURLE_OK; break; } - else if(data->state.previouslypending) { - /* this transfer comes from the pending queue so try move another */ - infof(data, "Transfer was pending, now try another"); + else process_pending_handles(data->multi); - } if(!result) { *nowp = Curl_pgrsTime(data, TIMER_POSTQUEUE); @@ -2226,7 +2285,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, follow = FOLLOW_RETRY; drc = Curl_follow(data, newurl, follow); if(!drc) { - multistate(data, MSTATE_CONNECT); + multistate(data, MSTATE_SETUP); rc = CURLM_CALL_MULTI_PERFORM; result = CURLE_OK; } @@ -2463,7 +2522,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, Curl_posttransfer(data); multi_done(data, result, TRUE); } - else if(data->req.done) { + else if(data->req.done && !Curl_cwriter_is_paused(data)) { /* call this even if the readwrite function returned error */ Curl_posttransfer(data); @@ -2486,10 +2545,9 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, /* multi_done() might return CURLE_GOT_NOTHING */ result = Curl_follow(data, newurl, follow); if(!result) { - multistate(data, MSTATE_CONNECT); + multistate(data, MSTATE_SETUP); rc = CURLM_CALL_MULTI_PERFORM; } - free(newurl); } else { /* after the transfer is done, go DONE */ @@ -2501,7 +2559,6 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, newurl = data->req.location; data->req.location = NULL; result = Curl_follow(data, newurl, FOLLOW_FAKE); - free(newurl); if(result) { stream_error = TRUE; result = multi_done(data, result, TRUE); @@ -2520,6 +2577,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, transfers */ Curl_expire(data, 0, EXPIRE_RUN_NOW); } + free(newurl); break; } @@ -2570,8 +2628,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, return CURLM_INTERNAL_ERROR; } - if(data->conn && - data->mstate >= MSTATE_CONNECT && + if(data->mstate >= MSTATE_CONNECT && data->mstate < MSTATE_DO && rc != CURLM_CALL_MULTI_PERFORM && !multi_ischanged(multi, false)) { @@ -2581,7 +2638,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, * (i.e. CURLM_CALL_MULTI_PERFORM == TRUE) then we should do that before * declaring the connection timed out as we may almost have a completed * connection. */ - multi_handle_timeout(data, nowp, &stream_error, &result, TRUE); + multi_handle_timeout(data, nowp, &stream_error, &result, FALSE); } statemachine_end: @@ -2658,8 +2715,7 @@ statemachine_end: multistate(data, MSTATE_MSGSENT); /* add this handle to the list of msgsent handles */ - Curl_llist_insert_next(&multi->msgsent, multi->msgsent.tail, data, - &data->connect_queue); + Curl_llist_append(&multi->msgsent, data, &data->connect_queue); /* unlink from the main list */ unlink_easy(multi, data); return CURLM_OK; @@ -2721,10 +2777,20 @@ CURLMcode curl_multi_perform(struct Curl_multi *multi, int *running_handles) */ do { multi->timetree = Curl_splaygetbest(now, multi->timetree, &t); - if(t) + if(t) { /* the removed may have another timeout in queue */ + data = t->payload; + if(data->mstate == MSTATE_PENDING) { + bool stream_unused; + CURLcode result_unused; + if(multi_handle_timeout(data, &now, &stream_unused, &result_unused, + FALSE)) { + infof(data, "PENDING handle timeout"); + move_pending_to_connect(multi, data); + } + } (void)add_next_timeout(now, multi, t->payload); - + } } while(t); *running_handles = multi->num_alive; @@ -3678,47 +3744,55 @@ void Curl_multiuse_state(struct Curl_easy *data, process_pending_handles(data->multi); } -/* process_pending_handles() moves all handles from PENDING - back into the main list and change state to CONNECT */ -static void process_pending_handles(struct Curl_multi *multi) +static void move_pending_to_connect(struct Curl_multi *multi, + struct Curl_easy *data) { - struct Curl_llist_element *e = multi->pending.head; - if(e) { - struct Curl_easy *data = e->ptr; + DEBUGASSERT(data->mstate == MSTATE_PENDING); + + /* put it back into the main list */ + link_easy(multi, data); - DEBUGASSERT(data->mstate == MSTATE_PENDING); + multistate(data, MSTATE_CONNECT); - /* put it back into the main list */ - link_easy(multi, data); + /* Remove this node from the pending list */ + Curl_llist_remove(&multi->pending, &data->connect_queue, NULL); - multistate(data, MSTATE_CONNECT); + /* Make sure that the handle will be processed soonish. */ + Curl_expire(data, 0, EXPIRE_RUN_NOW); +} - /* Remove this node from the list */ - Curl_llist_remove(&multi->pending, e, NULL); +/* process_pending_handles() moves a handle from PENDING back into the main + list and change state to CONNECT. - /* Make sure that the handle will be processed soonish. */ - Curl_expire(data, 0, EXPIRE_RUN_NOW); + We do not move all transfers because that can be a significant amount. + Since this is tried every now and then doing too many too often becomes a + performance problem. - /* mark this as having been in the pending queue */ - data->state.previouslypending = TRUE; + When there is a change for connection limits like max host connections etc, + this likely only allows one new transfer. When there is a pipewait change, + it can potentially allow hundreds of new transfers. + + We could consider an improvement where we store the queue reason and allow + more pipewait rechecks than others. +*/ +static void process_pending_handles(struct Curl_multi *multi) +{ + struct Curl_llist_element *e = multi->pending.head; + if(e) { + struct Curl_easy *data = e->ptr; + move_pending_to_connect(multi, data); } } void Curl_set_in_callback(struct Curl_easy *data, bool value) { - /* might get called when there is no data pointer! */ - if(data) { - if(data->multi_easy) - data->multi_easy->in_callback = value; - else if(data->multi) - data->multi->in_callback = value; - } + if(data && data->multi) + data->multi->in_callback = value; } -bool Curl_is_in_callback(struct Curl_easy *easy) +bool Curl_is_in_callback(struct Curl_easy *data) { - return ((easy->multi && easy->multi->in_callback) || - (easy->multi_easy && easy->multi_easy->in_callback)); + return (data && data->multi && data->multi->in_callback); } unsigned int Curl_multi_max_concurrent_streams(struct Curl_multi *multi) diff --git a/lib/multihandle.h b/lib/multihandle.h index 3156c83..add9a05 100644 --- a/lib/multihandle.h +++ b/lib/multihandle.h @@ -44,24 +44,25 @@ struct Curl_message { typedef enum { MSTATE_INIT, /* 0 - start in this state */ MSTATE_PENDING, /* 1 - no connections, waiting for one */ - MSTATE_CONNECT, /* 2 - resolve/connect has been sent off */ - MSTATE_RESOLVING, /* 3 - awaiting the resolve to finalize */ - MSTATE_CONNECTING, /* 4 - awaiting the TCP connect to finalize */ - MSTATE_TUNNELING, /* 5 - awaiting HTTPS proxy SSL initialization to + MSTATE_SETUP, /* 2 - start a new transfer */ + MSTATE_CONNECT, /* 3 - resolve/connect has been sent off */ + MSTATE_RESOLVING, /* 4 - awaiting the resolve to finalize */ + MSTATE_CONNECTING, /* 5 - awaiting the TCP connect to finalize */ + MSTATE_TUNNELING, /* 6 - awaiting HTTPS proxy SSL initialization to complete and/or proxy CONNECT to finalize */ - MSTATE_PROTOCONNECT, /* 6 - initiate protocol connect procedure */ - MSTATE_PROTOCONNECTING, /* 7 - completing the protocol-specific connect + MSTATE_PROTOCONNECT, /* 7 - initiate protocol connect procedure */ + MSTATE_PROTOCONNECTING, /* 8 - completing the protocol-specific connect phase */ - MSTATE_DO, /* 8 - start send off the request (part 1) */ - MSTATE_DOING, /* 9 - sending off the request (part 1) */ - MSTATE_DOING_MORE, /* 10 - send off the request (part 2) */ - MSTATE_DID, /* 11 - done sending off request */ - MSTATE_PERFORMING, /* 12 - transfer data */ - MSTATE_RATELIMITING, /* 13 - wait because limit-rate exceeded */ - MSTATE_DONE, /* 14 - post data transfer operation */ - MSTATE_COMPLETED, /* 15 - operation complete */ - MSTATE_MSGSENT, /* 16 - the operation complete message is sent */ - MSTATE_LAST /* 17 - not a true state, never use this */ + MSTATE_DO, /* 9 - start send off the request (part 1) */ + MSTATE_DOING, /* 10 - sending off the request (part 1) */ + MSTATE_DOING_MORE, /* 11 - send off the request (part 2) */ + MSTATE_DID, /* 12 - done sending off request */ + MSTATE_PERFORMING, /* 13 - transfer data */ + MSTATE_RATELIMITING, /* 14 - wait because limit-rate exceeded */ + MSTATE_DONE, /* 15 - post data transfer operation */ + MSTATE_COMPLETED, /* 16 - operation complete */ + MSTATE_MSGSENT, /* 17 - the operation complete message is sent */ + MSTATE_LAST /* 18 - not a true state, never use this */ } CURLMstate; /* we support N sockets per easy handle. Set the corresponding bit to what @@ -158,7 +159,7 @@ struct Curl_multi { WSAEVENT wsa_event; /* winsock event used for waits */ #else #ifdef ENABLE_WAKEUP - curl_socket_t wakeup_pair[2]; /* socketpair() used for wakeup + curl_socket_t wakeup_pair[2]; /* pipe()/socketpair() used for wakeup 0 is used for read, 1 is used for write */ #endif #endif @@ -179,7 +180,7 @@ struct Curl_multi { BIT(dead); /* a callback returned error, everything needs to crash and burn */ BIT(xfer_buf_borrowed); /* xfer_buf is currently being borrowed */ - BIT(xfer_ulbuf_borrowed); /* xfer_buf is currently being borrowed */ + BIT(xfer_ulbuf_borrowed); /* xfer_ulbuf is currently being borrowed */ #ifdef DEBUGBUILD BIT(warned); /* true after user warned of DEBUGBUILD */ #endif diff --git a/lib/multiif.h b/lib/multiif.h index 37f7a44..59a3064 100644 --- a/lib/multiif.h +++ b/lib/multiif.h @@ -38,15 +38,16 @@ void Curl_attach_connection(struct Curl_easy *data, void Curl_detach_connection(struct Curl_easy *data); bool Curl_multiplex_wanted(const struct Curl_multi *multi); void Curl_set_in_callback(struct Curl_easy *data, bool value); -bool Curl_is_in_callback(struct Curl_easy *easy); +bool Curl_is_in_callback(struct Curl_easy *data); CURLcode Curl_preconnect(struct Curl_easy *data); void Curl_multi_connchanged(struct Curl_multi *multi); /* Internal version of curl_multi_init() accepts size parameters for the socket, connection and dns hashes */ -struct Curl_multi *Curl_multi_handle(int hashsize, int chashsize, - int dnssize); +struct Curl_multi *Curl_multi_handle(size_t hashsize, + size_t chashsize, + size_t dnssize); /* the write bits start at bit 16 for the *getsock() bitmap */ #define GETSOCK_WRITEBITSTART 16 diff --git a/lib/noproxy.c b/lib/noproxy.c index 5241640..62299e2 100644 --- a/lib/noproxy.c +++ b/lib/noproxy.c @@ -78,7 +78,7 @@ UNITTEST bool Curl_cidr6_match(const char *ipv6, const char *network, unsigned int bits) { -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 int bytes; int rest; unsigned char address[16]; diff --git a/lib/openldap.c b/lib/openldap.c index 47266f6..0348ef5 100644 --- a/lib/openldap.c +++ b/lib/openldap.c @@ -117,7 +117,7 @@ static Curl_recv oldap_recv; */ const struct Curl_handler Curl_handler_ldap = { - "LDAP", /* scheme */ + "ldap", /* scheme */ oldap_setup_connection, /* setup_connection */ oldap_do, /* do_it */ oldap_done, /* done */ @@ -131,6 +131,7 @@ const struct Curl_handler Curl_handler_ldap = { ZERO_NULL, /* perform_getsock */ oldap_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_LDAP, /* defport */ @@ -145,7 +146,7 @@ const struct Curl_handler Curl_handler_ldap = { */ const struct Curl_handler Curl_handler_ldaps = { - "LDAPS", /* scheme */ + "ldaps", /* scheme */ oldap_setup_connection, /* setup_connection */ oldap_do, /* do_it */ oldap_done, /* done */ @@ -159,6 +160,7 @@ const struct Curl_handler Curl_handler_ldaps = { ZERO_NULL, /* perform_getsock */ oldap_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_LDAPS, /* defport */ @@ -548,9 +550,12 @@ static CURLcode oldap_connect(struct Curl_easy *data, bool *done) return result; } - hosturl = aprintf("ldap%s://%s:%d", - conn->handler->flags & PROTOPT_SSL? "s": "", - conn->host.name, conn->remote_port); + hosturl = aprintf("%s://%s%s%s:%d", + conn->handler->scheme, + conn->bits.ipv6_ip? "[": "", + conn->host.name, + conn->bits.ipv6_ip? "]": "", + conn->remote_port); if(!hosturl) return CURLE_OUT_OF_MEMORY; @@ -112,7 +112,7 @@ static CURLcode pop3_get_message(struct Curl_easy *data, struct bufref *out); */ const struct Curl_handler Curl_handler_pop3 = { - "POP3", /* scheme */ + "pop3", /* scheme */ pop3_setup_connection, /* setup_connection */ pop3_do, /* do_it */ pop3_done, /* done */ @@ -126,6 +126,7 @@ const struct Curl_handler Curl_handler_pop3 = { ZERO_NULL, /* perform_getsock */ pop3_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_POP3, /* defport */ @@ -141,7 +142,7 @@ const struct Curl_handler Curl_handler_pop3 = { */ const struct Curl_handler Curl_handler_pop3s = { - "POP3S", /* scheme */ + "pop3s", /* scheme */ pop3_setup_connection, /* setup_connection */ pop3_do, /* do_it */ pop3_done, /* done */ @@ -155,6 +156,7 @@ const struct Curl_handler Curl_handler_pop3s = { ZERO_NULL, /* perform_getsock */ pop3_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_POP3S, /* defport */ @@ -1450,7 +1452,7 @@ static CURLcode pop3_parse_custom_request(struct Curl_easy *data) * This function scans the body after the end-of-body and writes everything * until the end is found. */ -CURLcode Curl_pop3_write(struct Curl_easy *data, char *str, size_t nread) +CURLcode Curl_pop3_write(struct Curl_easy *data, const char *str, size_t nread) { /* This code could be made into a special function in the handler struct */ CURLcode result = CURLE_OK; @@ -92,6 +92,7 @@ extern const struct Curl_handler Curl_handler_pop3s; /* This function scans the body after the end-of-body and writes everything * until the end is found */ -CURLcode Curl_pop3_write(struct Curl_easy *data, char *str, size_t nread); +CURLcode Curl_pop3_write(struct Curl_easy *data, + const char *str, size_t nread); #endif /* HEADER_CURL_POP3_H */ diff --git a/lib/request.c b/lib/request.c index b3b0582..26741fa 100644 --- a/lib/request.c +++ b/lib/request.c @@ -40,10 +40,9 @@ #include "curl_memory.h" #include "memdebug.h" -CURLcode Curl_req_init(struct SingleRequest *req) +void Curl_req_init(struct SingleRequest *req) { memset(req, 0, sizeof(*req)); - return CURLE_OK; } CURLcode Curl_req_soft_reset(struct SingleRequest *req, @@ -266,7 +265,7 @@ static CURLcode req_set_upload_done(struct Curl_easy *data) else if(data->req.writebytecount) infof(data, "upload completely sent off: %" CURL_FORMAT_CURL_OFF_T " bytes", data->req.writebytecount); - else + else if(!data->req.download_done) infof(data, Curl_creader_total_length(data)? "We are completely uploaded and fine" : "Request completely sent off"); @@ -395,6 +394,7 @@ CURLcode Curl_req_send_more(struct Curl_easy *data) result = req_flush(data); if(result == CURLE_AGAIN) result = CURLE_OK; + return result; } diff --git a/lib/request.h b/lib/request.h index 488fbdd..f9be6f2 100644 --- a/lib/request.h +++ b/lib/request.h @@ -152,7 +152,7 @@ struct SingleRequest { /** * Initialize the state of the request for first use. */ -CURLcode Curl_req_init(struct SingleRequest *req); +void Curl_req_init(struct SingleRequest *req); /** * The request is about to start. Record time and do a soft reset. @@ -93,14 +93,14 @@ static int rtsp_getsock_do(struct Curl_easy *data, struct connectdata *conn, static CURLcode rtp_client_write(struct Curl_easy *data, const char *ptr, size_t len); static -CURLcode rtsp_parse_transport(struct Curl_easy *data, char *transport); +CURLcode rtsp_parse_transport(struct Curl_easy *data, const char *transport); /* * RTSP handler interface. */ const struct Curl_handler Curl_handler_rtsp = { - "RTSP", /* scheme */ + "rtsp", /* scheme */ rtsp_setup_connection, /* setup_connection */ rtsp_do, /* do_it */ rtsp_done, /* done */ @@ -114,6 +114,7 @@ const struct Curl_handler Curl_handler_rtsp = { ZERO_NULL, /* perform_getsock */ rtsp_disconnect, /* disconnect */ rtsp_rtp_write_resp, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ rtsp_conncheck, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_RTSP, /* defport */ @@ -393,7 +394,9 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) if(result) goto out; +#ifndef CURL_DISABLE_PROXY p_proxyuserpwd = data->state.aptr.proxyuserpwd; +#endif p_userpwd = data->state.aptr.userpwd; /* Referrer */ @@ -802,7 +805,7 @@ static CURLcode rtsp_rtp_write_resp(struct Curl_easy *data, DEBUGF(infof(data, "rtsp_rtp_write_resp(len=%zu, in_header=%d, eos=%d)", blen, rtspc->in_header, is_eos)); - /* If header parsing is not onging, extract RTP messages */ + /* If header parsing is not ongoing, extract RTP messages */ if(!rtspc->in_header) { result = rtsp_filter_rtp(data, buf, blen, &consumed); if(result) @@ -911,12 +914,12 @@ CURLcode rtp_client_write(struct Curl_easy *data, const char *ptr, size_t len) return CURLE_OK; } -CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, char *header) +CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, const char *header) { if(checkprefix("CSeq:", header)) { long CSeq = 0; char *endp; - char *p = &header[5]; + const char *p = &header[5]; while(ISBLANK(*p)) p++; CSeq = strtol(p, &endp, 10); @@ -931,8 +934,7 @@ CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, char *header) } } else if(checkprefix("Session:", header)) { - char *start; - char *end; + const char *start, *end; size_t idlen; /* Find the first non-space letter */ @@ -987,14 +989,13 @@ CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, char *header) } static -CURLcode rtsp_parse_transport(struct Curl_easy *data, char *transport) +CURLcode rtsp_parse_transport(struct Curl_easy *data, const char *transport) { /* If we receive multiple Transport response-headers, the linterleaved channels of each response header is recorded and used together for subsequent data validity checks.*/ /* e.g.: ' RTP/AVP/TCP;unicast;interleaved=5-6' */ - char *start; - char *end; + const char *start, *end; start = transport; while(start && *start) { while(*start && ISBLANK(*start) ) @@ -1003,7 +1004,7 @@ CURLcode rtsp_parse_transport(struct Curl_easy *data, char *transport) if(checkprefix("interleaved=", start)) { long chan1, chan2, chan; char *endp; - char *p = start + 12; + const char *p = start + 12; chan1 = strtol(p, &endp, 10); if(p != endp && chan1 >= 0 && chan1 <= 255) { unsigned char *rtp_channel_mask = data->state.rtp_channel_mask; @@ -31,7 +31,7 @@ extern const struct Curl_handler Curl_handler_rtsp; -CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, char *header); +CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, const char *header); #else /* disabled */ diff --git a/lib/sendf.c b/lib/sendf.c index 7901957..68a8bf3 100644 --- a/lib/sendf.c +++ b/lib/sendf.c @@ -88,7 +88,10 @@ CURLcode Curl_client_write(struct Curl_easy *data, DEBUGASSERT(data->req.writer_stack); } - return Curl_cwriter_write(data, data->req.writer_stack, type, buf, blen); + result = Curl_cwriter_write(data, data->req.writer_stack, type, buf, blen); + CURL_TRC_WRITE(data, "client_write(type=%x, len=%zu) -> %d", + type, blen, result); + return result; } static void cl_reset_writer(struct Curl_easy *data) @@ -115,7 +118,6 @@ static void cl_reset_reader(struct Curl_easy *data) void Curl_client_cleanup(struct Curl_easy *data) { - DEBUGF(infof(data, "Curl_client_cleanup()")); cl_reset_reader(data); cl_reset_writer(data); @@ -127,10 +129,10 @@ void Curl_client_reset(struct Curl_easy *data) { if(data->req.rewind_read) { /* already requested */ - DEBUGF(infof(data, "Curl_client_reset(), will rewind_read")); + CURL_TRC_READ(data, "client_reset, will rewind reader"); } else { - DEBUGF(infof(data, "Curl_client_reset(), clear readers")); + CURL_TRC_READ(data, "client_reset, clear readers"); cl_reset_reader(data); } cl_reset_writer(data); @@ -145,7 +147,7 @@ CURLcode Curl_client_start(struct Curl_easy *data) struct Curl_creader *r = data->req.reader_stack; CURLcode result = CURLE_OK; - DEBUGF(infof(data, "client start, rewind readers")); + CURL_TRC_READ(data, "client start, rewind readers"); while(r) { result = r->crt->rewind(data, r); if(result) { @@ -171,8 +173,7 @@ void Curl_creader_set_rewind(struct Curl_easy *data, bool enable) data->req.rewind_read = !!enable; } -/* Write data using an unencoding writer stack. "nbytes" is not - allowed to be 0. */ +/* Write data using an unencoding writer stack. */ CURLcode Curl_cwriter_write(struct Curl_easy *data, struct Curl_cwriter *writer, int type, const char *buf, size_t nbytes) @@ -249,7 +250,10 @@ static CURLcode cw_download_write(struct Curl_easy *data, if(!(type & CLIENTWRITE_BODY)) { if(is_connect && data->set.suppress_connect_headers) return CURLE_OK; - return Curl_cwriter_write(data, writer->next, type, buf, nbytes); + result = Curl_cwriter_write(data, writer->next, type, buf, nbytes); + CURL_TRC_WRITE(data, "download_write header(type=%x, blen=%zu) -> %d", + type, nbytes, result); + return result; } /* Here, we deal with REAL BODY bytes. All filtering and transfer @@ -261,8 +265,8 @@ static CURLcode cw_download_write(struct Curl_easy *data, if(data->req.no_body && nbytes > 0) { /* BODY arrives although we want none, bail out */ streamclose(data->conn, "ignoring body"); - DEBUGF(infof(data, "did not want a BODY, but seeing %zu bytes", - nbytes)); + CURL_TRC_WRITE(data, "download_write body(type=%x, blen=%zu), " + "did not want a BODY", type, nbytes); data->req.download_done = TRUE; if(data->info.header_size) /* if headers have been received, this is fine */ @@ -298,6 +302,8 @@ static CURLcode cw_download_write(struct Curl_easy *data, if(!data->req.ignorebody && (nwrite || (type & CLIENTWRITE_EOS))) { result = Curl_cwriter_write(data, writer->next, type, buf, nwrite); + CURL_TRC_WRITE(data, "download_write body(type=%x, blen=%zu) -> %d", + type, nbytes, result); if(result) return result; } @@ -333,7 +339,7 @@ static CURLcode cw_download_write(struct Curl_easy *data, } static const struct Curl_cwtype cw_download = { - "download", + "protocol", NULL, Curl_cwriter_def_init, cw_download_write, @@ -499,10 +505,22 @@ void Curl_cwriter_remove_by_name(struct Curl_easy *data, } } +bool Curl_cwriter_is_paused(struct Curl_easy *data) +{ + return Curl_cw_out_is_paused(data); +} + +CURLcode Curl_cwriter_unpause(struct Curl_easy *data) +{ + return Curl_cw_out_unpause(data); +} + CURLcode Curl_creader_read(struct Curl_easy *data, struct Curl_creader *reader, char *buf, size_t blen, size_t *nread, bool *eos) { + *nread = 0; + *eos = FALSE; if(!reader) return CURLE_READ_ERROR; return reader->crt->do_read(data, reader, buf, blen, nread, eos); @@ -649,7 +667,7 @@ static CURLcode cr_in_read(struct Curl_easy *data, switch(nread) { case 0: if((ctx->total_len >= 0) && (ctx->read_len < ctx->total_len)) { - failf(data, "client read function EOF fail, only " + failf(data, "client read function EOF fail, " "only %"CURL_FORMAT_CURL_OFF_T"/%"CURL_FORMAT_CURL_OFF_T " of needed bytes read", ctx->read_len, ctx->total_len); return CURLE_READ_ERROR; @@ -698,9 +716,10 @@ static CURLcode cr_in_read(struct Curl_easy *data, *peos = ctx->seen_eos; break; } - DEBUGF(infof(data, "cr_in_read(len=%zu, total=%"CURL_FORMAT_CURL_OFF_T - ", read=%"CURL_FORMAT_CURL_OFF_T") -> %d, %zu, %d", - blen, ctx->total_len, ctx->read_len, CURLE_OK, *pnread, *peos)); + CURL_TRC_READ(data, "cr_in_read(len=%zu, total=%"CURL_FORMAT_CURL_OFF_T + ", read=%"CURL_FORMAT_CURL_OFF_T") -> %d, nread=%zu, eos=%d", + blen, ctx->total_len, ctx->read_len, CURLE_OK, + *pnread, *peos); return CURLE_OK; } @@ -798,7 +817,7 @@ static CURLcode cr_in_rewind(struct Curl_easy *data, Curl_set_in_callback(data, true); err = (data->set.seek_func)(data->set.seek_client, 0, SEEK_SET); Curl_set_in_callback(data, false); - DEBUGF(infof(data, "cr_in, rewind via set.seek_func -> %d", err)); + CURL_TRC_READ(data, "cr_in, rewind via set.seek_func -> %d", err); if(err) { failf(data, "seek callback returned error %d", (int)err); return CURLE_SEND_FAIL_REWIND; @@ -811,7 +830,7 @@ static CURLcode cr_in_rewind(struct Curl_easy *data, err = (data->set.ioctl_func)(data, CURLIOCMD_RESTARTREAD, data->set.ioctl_client); Curl_set_in_callback(data, false); - DEBUGF(infof(data, "cr_in, rewind via set.ioctl_func -> %d", (int)err)); + CURL_TRC_READ(data, "cr_in, rewind via set.ioctl_func -> %d", (int)err); if(err) { failf(data, "ioctl callback returned error %d", (int)err); return CURLE_SEND_FAIL_REWIND; @@ -823,8 +842,8 @@ static CURLcode cr_in_rewind(struct Curl_easy *data, ourselves with fseek() */ if(data->state.fread_func == (curl_read_callback)fread) { int err = fseek(data->state.in, 0, SEEK_SET); - DEBUGF(infof(data, "cr_in, rewind via fseek -> %d(%d)", - (int)err, (int)errno)); + CURL_TRC_READ(data, "cr_in, rewind via fseek -> %d(%d)", + (int)err, (int)errno); if(-1 != err) /* successful rewind */ return CURLE_OK; @@ -945,7 +964,7 @@ static CURLcode cr_lc_read(struct Curl_easy *data, ctx->eos = TRUE; *pnread = nread; *peos = ctx->eos; - return CURLE_OK; + goto out; } /* at least one \n needs conversion to '\r\n', place into ctx->buf */ @@ -977,6 +996,10 @@ static CURLcode cr_lc_read(struct Curl_easy *data, ctx->eos = TRUE; *peos = TRUE; } + +out: + CURL_TRC_READ(data, "cr_lc_read(len=%zu) -> %d, nread=%zu, eos=%d", + blen, result, *pnread, *peos); return result; } @@ -1054,12 +1077,16 @@ CURLcode Curl_creader_set_fread(struct Curl_easy *data, curl_off_t len) result = Curl_creader_create(&r, data, &cr_in, CURL_CR_CLIENT); if(result) - return result; + goto out; ctx = r->ctx; ctx->total_len = len; cl_reset_reader(data); - return do_init_reader_stack(data, r); + result = do_init_reader_stack(data, r); +out: + CURL_TRC_READ(data, "add fread reader, len=%"CURL_FORMAT_CURL_OFF_T + " -> %d", len, result); + return result; } CURLcode Curl_creader_add(struct Curl_easy *data, @@ -1117,6 +1144,8 @@ CURLcode Curl_client_read(struct Curl_easy *data, char *buf, size_t blen, result = Curl_creader_read(data, data->req.reader_stack, buf, blen, nread, eos); + CURL_TRC_READ(data, "client_read(len=%zu) -> %d, nread=%zu, eos=%d", + blen, result, *nread, *eos); return result; } @@ -1124,8 +1153,10 @@ bool Curl_creader_needs_rewind(struct Curl_easy *data) { struct Curl_creader *reader = data->req.reader_stack; while(reader) { - if(reader->crt->needs_rewind(data, reader)) + if(reader->crt->needs_rewind(data, reader)) { + CURL_TRC_READ(data, "client reader needs rewind before next request"); return TRUE; + } reader = reader->next; } return FALSE; @@ -1209,6 +1240,8 @@ static CURLcode cr_buf_read(struct Curl_easy *data, ctx->index += nread; *peos = (ctx->index == ctx->blen); } + CURL_TRC_READ(data, "cr_buf_read(len=%zu) -> 0, nread=%zu, eos=%d", + blen, *pnread, *peos); return CURLE_OK; } @@ -1274,14 +1307,17 @@ CURLcode Curl_creader_set_buf(struct Curl_easy *data, result = Curl_creader_create(&r, data, &cr_buf, CURL_CR_CLIENT); if(result) - return result; + goto out; ctx = r->ctx; ctx->buf = buf; ctx->blen = blen; ctx->index = 0; cl_reset_reader(data); - return do_init_reader_stack(data, r); + result = do_init_reader_stack(data, r); +out: + CURL_TRC_READ(data, "add buf reader, len=%zu -> %d", blen, result); + return result; } curl_off_t Curl_creader_total_length(struct Curl_easy *data) diff --git a/lib/sendf.h b/lib/sendf.h index d736ce4..82a2902 100644 --- a/lib/sendf.h +++ b/lib/sendf.h @@ -181,6 +181,16 @@ CURLcode Curl_cwriter_write(struct Curl_easy *data, const char *buf, size_t nbytes); /** + * Return TRUE iff client writer is paused. + */ +bool Curl_cwriter_is_paused(struct Curl_easy *data); + +/** + * Unpause client writer and flush any buffered date to the client. + */ +CURLcode Curl_cwriter_unpause(struct Curl_easy *data); + +/** * Default implementations for do_init, do_write, do_close that * do nothing and pass the data through. */ @@ -302,7 +312,7 @@ CURLcode Curl_creader_set(struct Curl_easy *data, struct Curl_creader *r); /** * Read at most `blen` bytes at `buf` from the client. - * @param date the transfer to read client bytes for + * @param data the transfer to read client bytes for * @param buf the memory location to read to * @param blen the amount of memory at `buf` * @param nread on return the number of bytes read into `buf` @@ -350,8 +360,8 @@ curl_off_t Curl_creader_client_length(struct Curl_easy *data); * Ask the installed reader at phase CURL_CR_CLIENT to start * reading from the given offset. On success, this will reduce * the `total_length()` by the amount. - * @param date the transfer to read client bytes for - * param offset the offset where to start reads from, negative + * @param data the transfer to read client bytes for + * @param offset the offset where to start reads from, negative * values will be ignored. * @return CURLE_OK if offset could be set * CURLE_READ_ERROR if not supported by reader or seek/read failed diff --git a/lib/setopt.c b/lib/setopt.c index 8a5a5d7..e8b2545 100644 --- a/lib/setopt.c +++ b/lib/setopt.c @@ -52,6 +52,8 @@ #include "hsts.h" #include "tftp.h" #include "strdup.h" +#include "escape.h" + /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" @@ -109,45 +111,32 @@ CURLcode Curl_setblobopt(struct curl_blob **blobp, static CURLcode setstropt_userpwd(char *option, char **userp, char **passwdp) { - CURLcode result = CURLE_OK; char *user = NULL; char *passwd = NULL; + DEBUGASSERT(userp); + DEBUGASSERT(passwdp); + /* Parse the login details if specified. It not then we treat NULL as a hint to clear the existing data */ if(option) { size_t len = strlen(option); + CURLcode result; if(len > CURL_MAX_INPUT_LENGTH) return CURLE_BAD_FUNCTION_ARGUMENT; - result = Curl_parse_login_details(option, len, - (userp ? &user : NULL), - (passwdp ? &passwd : NULL), - NULL); + result = Curl_parse_login_details(option, len, &user, &passwd, NULL); + if(result) + return result; } - if(!result) { - /* Store the username part of option if required */ - if(userp) { - if(!user && option && option[0] == ':') { - /* Allocate an empty string instead of returning NULL as user name */ - user = strdup(""); - if(!user) - result = CURLE_OUT_OF_MEMORY; - } - - Curl_safefree(*userp); - *userp = user; - } + free(*userp); + *userp = user; - /* Store the password part of option if required */ - if(passwdp) { - Curl_safefree(*passwdp); - *passwdp = passwd; - } - } + free(*passwdp); + *passwdp = passwd; - return result; + return CURLE_OK; } #define C_SSLVERSION_VALUE(x) (x & 0xffff) @@ -518,11 +507,12 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) data. */ char *p = Curl_memdup0(argptr, (size_t)data->set.postfieldsize); - (void) Curl_setstropt(&data->set.str[STRING_COPYPOSTFIELDS], NULL); if(!p) result = CURLE_OUT_OF_MEMORY; - else + else { + free(data->set.str[STRING_COPYPOSTFIELDS]); data->set.str[STRING_COPYPOSTFIELDS] = p; + } } } @@ -536,7 +526,7 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) */ data->set.postfields = va_arg(param, void *); /* Release old copied data. */ - (void) Curl_setstropt(&data->set.str[STRING_COPYPOSTFIELDS], NULL); + Curl_safefree(data->set.str[STRING_COPYPOSTFIELDS]); data->set.method = HTTPREQ_POST; break; @@ -552,7 +542,7 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) if(data->set.postfieldsize < bigsize && data->set.postfields == data->set.str[STRING_COPYPOSTFIELDS]) { /* Previous CURLOPT_COPYPOSTFIELDS is no longer valid. */ - (void) Curl_setstropt(&data->set.str[STRING_COPYPOSTFIELDS], NULL); + Curl_safefree(data->set.str[STRING_COPYPOSTFIELDS]); data->set.postfields = NULL; } @@ -571,7 +561,7 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) if(data->set.postfieldsize < bigsize && data->set.postfields == data->set.str[STRING_COPYPOSTFIELDS]) { /* Previous CURLOPT_COPYPOSTFIELDS is no longer valid. */ - (void) Curl_setstropt(&data->set.str[STRING_COPYPOSTFIELDS], NULL); + Curl_safefree(data->set.str[STRING_COPYPOSTFIELDS]); data->set.postfields = NULL; } @@ -789,22 +779,20 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) /* * Set cookie file name to dump all cookies to when we're done. */ - { - struct CookieInfo *newcookies; result = Curl_setstropt(&data->set.str[STRING_COOKIEJAR], va_arg(param, char *)); - - /* - * Activate the cookie parser. This may or may not already - * have been made. - */ - newcookies = Curl_cookie_init(data, NULL, data->cookies, - data->set.cookiesession); - if(!newcookies) - result = CURLE_OUT_OF_MEMORY; - data->cookies = newcookies; - } - break; + if(!result) { + /* + * Activate the cookie parser. This may or may not already + * have been made. + */ + struct CookieInfo *newcookies = + Curl_cookie_init(data, NULL, data->cookies, data->set.cookiesession); + if(!newcookies) + result = CURLE_OUT_OF_MEMORY; + data->cookies = newcookies; + } + break; case CURLOPT_COOKIESESSION: /* @@ -913,7 +901,7 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) /* accepted */ break; #endif -#ifdef ENABLE_QUIC +#ifdef USE_HTTP3 case CURL_HTTP_VERSION_3: case CURL_HTTP_VERSION_3ONLY: /* accepted */ @@ -1022,9 +1010,6 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) /* switch off bits we can't support */ #ifndef USE_NTLM auth &= ~CURLAUTH_NTLM; /* no NTLM support */ - auth &= ~CURLAUTH_NTLM_WB; /* no NTLM_WB support */ -#elif !defined(NTLM_WB_ENABLED) - auth &= ~CURLAUTH_NTLM_WB; /* no NTLM_WB support */ #endif #ifndef USE_SPNEGO auth &= ~CURLAUTH_NEGOTIATE; /* no Negotiate (SPNEGO) auth without @@ -1103,9 +1088,6 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) /* switch off bits we can't support */ #ifndef USE_NTLM auth &= ~CURLAUTH_NTLM; /* no NTLM support */ - auth &= ~CURLAUTH_NTLM_WB; /* no NTLM_WB support */ -#elif !defined(NTLM_WB_ENABLED) - auth &= ~CURLAUTH_NTLM_WB; /* no NTLM_WB support */ #endif #ifndef USE_SPNEGO auth &= ~CURLAUTH_NEGOTIATE; /* no Negotiate (SPNEGO) auth without @@ -1318,6 +1300,7 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) return CURLE_BAD_FUNCTION_ARGUMENT; data->set.ftpsslauth = (unsigned char)(curl_ftpauth)arg; break; +#ifdef HAVE_GSSAPI case CURLOPT_KRBLEVEL: /* * A string that defines the kerberos security level. @@ -1327,6 +1310,7 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) data->set.krb = !!(data->set.str[STRING_KRB_LEVEL]); break; #endif +#endif #if !defined(CURL_DISABLE_FTP) || defined(USE_SSH) case CURLOPT_FTP_CREATE_MISSING_DIRS: /* @@ -1593,13 +1577,24 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) break; #ifndef CURL_DISABLE_PROXY - case CURLOPT_PROXYUSERPWD: + case CURLOPT_PROXYUSERPWD: { /* * user:password needed to use the proxy */ - result = setstropt_userpwd(va_arg(param, char *), - &data->set.str[STRING_PROXYUSERNAME], - &data->set.str[STRING_PROXYPASSWORD]); + char *u = NULL; + char *p = NULL; + result = setstropt_userpwd(va_arg(param, char *), &u, &p); + + /* URL decode the components */ + if(!result && u) + result = Curl_urldecode(u, 0, &data->set.str[STRING_PROXYUSERNAME], NULL, + REJECT_ZERO); + if(!result && p) + result = Curl_urldecode(p, 0, &data->set.str[STRING_PROXYPASSWORD], NULL, + REJECT_ZERO); + free(u); + free(p); + } break; case CURLOPT_PROXYUSERNAME: /* @@ -1851,7 +1846,7 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) /* * flag to set engine as default. */ - Curl_setstropt(&data->set.str[STRING_SSL_ENGINE], NULL); + Curl_safefree(data->set.str[STRING_SSL_ENGINE]); result = Curl_ssl_set_engine_default(data); break; case CURLOPT_CRLF: @@ -2315,7 +2310,7 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) Curl_hsts_cleanup(&data->hsts); data->hsts = data->share->hsts; } -#endif /* CURL_DISABLE_HTTP */ +#endif #ifdef USE_SSL if(data->share->sslsession) { data->set.general_ssl.max_ssl_sessions = data->share->max_ssl_sessions; @@ -2625,7 +2620,7 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) break; #endif -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 case CURLOPT_ADDRESS_SCOPE: /* * Use this scope id when using IPv6 @@ -3133,6 +3128,49 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) break; } #endif +#ifdef USE_ECH + case CURLOPT_ECH: { + size_t plen = 0; + + argptr = va_arg(param, char *); + if(!argptr) { + data->set.tls_ech = CURLECH_DISABLE; + result = CURLE_BAD_FUNCTION_ARGUMENT; + return result; + } + plen = strlen(argptr); + if(plen > CURL_MAX_INPUT_LENGTH) { + data->set.tls_ech = CURLECH_DISABLE; + result = CURLE_BAD_FUNCTION_ARGUMENT; + return result; + } + /* set tls_ech flag value, preserving CLA_CFG bit */ + if(plen == 5 && !strcmp(argptr, "false")) + data->set.tls_ech = CURLECH_DISABLE + | (data->set.tls_ech & CURLECH_CLA_CFG); + else if(plen == 6 && !strcmp(argptr, "grease")) + data->set.tls_ech = CURLECH_GREASE + | (data->set.tls_ech & CURLECH_CLA_CFG); + else if(plen == 4 && !strcmp(argptr, "true")) + data->set.tls_ech = CURLECH_ENABLE + | (data->set.tls_ech & CURLECH_CLA_CFG); + else if(plen == 4 && !strcmp(argptr, "hard")) + data->set.tls_ech = CURLECH_HARD + | (data->set.tls_ech & CURLECH_CLA_CFG); + else if(plen > 5 && !strncmp(argptr, "ecl:", 4)) { + result = Curl_setstropt(&data->set.str[STRING_ECH_CONFIG], argptr + 4); + if(result) + return result; + data->set.tls_ech |= CURLECH_CLA_CFG; + } + else if(plen > 4 && !strncmp(argptr, "pn:", 3)) { + result = Curl_setstropt(&data->set.str[STRING_ECH_PUBLIC], argptr + 3); + if(result) + return result; + } + break; + } +#endif case CURLOPT_QUICK_EXIT: data->set.quick_exit = (0 != va_arg(param, long)) ? 1L:0L; break; diff --git a/lib/setopt.h b/lib/setopt.h index 3c14a05..b023746 100644 --- a/lib/setopt.h +++ b/lib/setopt.h @@ -24,9 +24,10 @@ * ***************************************************************************/ -CURLcode Curl_setstropt(char **charp, const char *s); +CURLcode Curl_setstropt(char **charp, const char *s) WARN_UNUSED_RESULT; CURLcode Curl_setblobopt(struct curl_blob **blobp, - const struct curl_blob *blob); -CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list arg); + const struct curl_blob *blob) WARN_UNUSED_RESULT; +CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list arg) + WARN_UNUSED_RESULT; #endif /* HEADER_CURL_SETOPT_H */ diff --git a/lib/setup-vms.h b/lib/setup-vms.h index 645cc1a..ea3936c 100644 --- a/lib/setup-vms.h +++ b/lib/setup-vms.h @@ -374,8 +374,8 @@ static struct passwd *vms_getpwuid(uid_t uid) #ifdef HAVE_NETDB_H #include <netdb.h> #ifndef AI_NUMERICHOST -#ifdef ENABLE_IPV6 -#undef ENABLE_IPV6 +#ifdef USE_IPV6 +#undef USE_IPV6 #endif #endif #endif @@ -259,7 +259,7 @@ static CURLcode smb_parse_url_path(struct Curl_easy *data, * SMB handler interface */ const struct Curl_handler Curl_handler_smb = { - "SMB", /* scheme */ + "smb", /* scheme */ smb_setup_connection, /* setup_connection */ smb_do, /* do_it */ ZERO_NULL, /* done */ @@ -273,6 +273,7 @@ const struct Curl_handler Curl_handler_smb = { ZERO_NULL, /* perform_getsock */ smb_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_SMB, /* defport */ @@ -286,7 +287,7 @@ const struct Curl_handler Curl_handler_smb = { * SMBS handler interface */ const struct Curl_handler Curl_handler_smbs = { - "SMBS", /* scheme */ + "smbs", /* scheme */ smb_setup_connection, /* setup_connection */ smb_do, /* do_it */ ZERO_NULL, /* done */ @@ -300,6 +301,7 @@ const struct Curl_handler Curl_handler_smbs = { ZERO_NULL, /* perform_getsock */ smb_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_SMBS, /* defport */ @@ -118,7 +118,7 @@ static CURLcode cr_eob_add(struct Curl_easy *data); */ const struct Curl_handler Curl_handler_smtp = { - "SMTP", /* scheme */ + "smtp", /* scheme */ smtp_setup_connection, /* setup_connection */ smtp_do, /* do_it */ smtp_done, /* done */ @@ -132,6 +132,7 @@ const struct Curl_handler Curl_handler_smtp = { ZERO_NULL, /* perform_getsock */ smtp_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_SMTP, /* defport */ @@ -147,7 +148,7 @@ const struct Curl_handler Curl_handler_smtp = { */ const struct Curl_handler Curl_handler_smtps = { - "SMTPS", /* scheme */ + "smtps", /* scheme */ smtp_setup_connection, /* setup_connection */ smtp_do, /* do_it */ smtp_done, /* done */ @@ -161,6 +162,7 @@ const struct Curl_handler Curl_handler_smtps = { ZERO_NULL, /* perform_getsock */ smtp_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_SMTPS, /* defport */ @@ -1901,7 +1903,7 @@ static CURLcode cr_eob_read(struct Curl_easy *data, *peos = ctx->eos; DEBUGF(infof(data, "cr_eob_read(%zu) -> %d, %zd, %d", blen, result, *pnread, *peos)); - return CURLE_OK; + return result; } static curl_off_t cr_eob_total_length(struct Curl_easy *data, diff --git a/lib/sockaddr.h b/lib/sockaddr.h index 5a6bb20..2e2d375 100644 --- a/lib/sockaddr.h +++ b/lib/sockaddr.h @@ -30,7 +30,7 @@ struct Curl_sockaddr_storage { union { struct sockaddr sa; struct sockaddr_in sa_in; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 struct sockaddr_in6 sa_in6; #endif #ifdef HAVE_STRUCT_SOCKADDR_STORAGE diff --git a/lib/socketpair.c b/lib/socketpair.c index d01b255..d7e3afd 100644 --- a/lib/socketpair.c +++ b/lib/socketpair.c @@ -27,6 +27,27 @@ #include "urldata.h" #include "rand.h" +#if defined(HAVE_PIPE) && defined(HAVE_FCNTL) +#include <fcntl.h> + +int Curl_pipe(curl_socket_t socks[2]) +{ + if(pipe(socks)) + return -1; + + if(fcntl(socks[0], F_SETFD, FD_CLOEXEC) || + fcntl(socks[1], F_SETFD, FD_CLOEXEC) ) { + close(socks[0]); + close(socks[1]); + socks[0] = socks[1] = CURL_SOCKET_BAD; + return -1; + } + + return 0; +} +#endif + + #if !defined(HAVE_SOCKETPAIR) && !defined(CURL_DISABLE_SOCKETPAIR) #ifdef _WIN32 /* diff --git a/lib/socketpair.h b/lib/socketpair.h index bd499ab..ddd4437 100644 --- a/lib/socketpair.h +++ b/lib/socketpair.h @@ -31,17 +31,41 @@ #define wakeup_write write #define wakeup_read read #define wakeup_close close -#define wakeup_create pipe +#define wakeup_create(p) Curl_pipe(p) + +#ifdef HAVE_FCNTL +#include <curl/curl.h> +int Curl_pipe(curl_socket_t socks[2]); +#else +#define Curl_pipe(p) pipe(p) +#endif #else /* HAVE_PIPE */ #define wakeup_write swrite #define wakeup_read sread #define wakeup_close sclose -#define wakeup_create(p) Curl_socketpair(AF_UNIX, SOCK_STREAM, 0, p) + +#if defined(USE_UNIX_SOCKETS) && defined(HAVE_SOCKETPAIR) +#define SOCKETPAIR_FAMILY AF_UNIX +#elif !defined(HAVE_SOCKETPAIR) +#define SOCKETPAIR_FAMILY 0 /* not used */ +#else +#error "unsupported unix domain and socketpair build combo" +#endif + +#ifdef SOCK_CLOEXEC +#define SOCKETPAIR_TYPE (SOCK_STREAM | SOCK_CLOEXEC) +#else +#define SOCKETPAIR_TYPE SOCK_STREAM +#endif + +#define wakeup_create(p)\ +Curl_socketpair(SOCKETPAIR_FAMILY, SOCKETPAIR_TYPE, 0, p) #endif /* HAVE_PIPE */ + #ifndef HAVE_SOCKETPAIR #include <curl/curl.h> diff --git a/lib/socks.c b/lib/socks.c index bd5962a..4ade4eb 100644 --- a/lib/socks.c +++ b/lib/socks.c @@ -838,7 +838,7 @@ CONNECT_RESOLVED: struct Curl_addrinfo *hp = NULL; if(dns) hp = dns->addr; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 if(data->set.ipver != CURL_IPRESOLVE_WHATEVER) { int wanted_family = data->set.ipver == CURL_IPRESOLVE_V4 ? AF_INET : AF_INET6; @@ -872,7 +872,7 @@ CONNECT_RESOLVED: infof(data, "SOCKS5 connect to %s:%d (locally resolved)", dest, sx->remote_port); } -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 else if(hp->ai_family == AF_INET6) { int i; struct sockaddr_in6 *saddr_in6; @@ -909,7 +909,7 @@ CONNECT_RESOLVE_REMOTE: IPv6 == 4, IPv4 == 1 */ unsigned char ip4[4]; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 if(conn->bits.ipv6_ip) { char ip6[16]; if(1 != Curl_inet_pton(AF_INET6, sx->hostname, ip6)) @@ -1244,7 +1244,7 @@ static void socks_cf_get_host(struct Curl_cfilter *cf, struct Curl_cftype Curl_cft_socks_proxy = { "SOCKS-PROXYY", - CF_TYPE_IP_CONNECT, + CF_TYPE_IP_CONNECT|CF_TYPE_PROXY, 0, socks_proxy_cf_destroy, socks_proxy_cf_connect, diff --git a/lib/strcase.c b/lib/strcase.c index 7c0b4ef..14d76f7 100644 --- a/lib/strcase.c +++ b/lib/strcase.c @@ -71,7 +71,7 @@ static const unsigned char tolowermap[256] = { altered by the current locale. */ char Curl_raw_toupper(char in) { - return touppermap[(unsigned char) in]; + return (char)touppermap[(unsigned char) in]; } @@ -79,7 +79,7 @@ char Curl_raw_toupper(char in) altered by the current locale. */ char Curl_raw_tolower(char in) { - return tolowermap[(unsigned char) in]; + return (char)tolowermap[(unsigned char) in]; } /* diff --git a/lib/strerror.c b/lib/strerror.c index a900e78..f142cf1 100644 --- a/lib/strerror.c +++ b/lib/strerror.c @@ -322,6 +322,9 @@ curl_easy_strerror(CURLcode error) case CURLE_TOO_LARGE: return "A value or data field grew larger than allowed"; + case CURLE_ECH_REQUIRED: + return "ECH attempted but failed"; + /* error codes not used by current libcurl */ case CURLE_OBSOLETE20: case CURLE_OBSOLETE24: diff --git a/lib/telnet.c b/lib/telnet.c index 56ee085..227a166 100644 --- a/lib/telnet.c +++ b/lib/telnet.c @@ -173,7 +173,7 @@ struct TELNET { */ const struct Curl_handler Curl_handler_telnet = { - "TELNET", /* scheme */ + "telnet", /* scheme */ ZERO_NULL, /* setup_connection */ telnet_do, /* do_it */ telnet_done, /* done */ @@ -187,6 +187,7 @@ const struct Curl_handler Curl_handler_telnet = { ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_TELNET, /* defport */ @@ -1534,6 +1535,11 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) pfd[1].events = POLLIN; poll_cnt = 2; interval_ms = 1 * 1000; + if(pfd[1].fd < 0) { + failf(data, "cannot read input"); + result = CURLE_RECV_ERROR; + keepon = FALSE; + } } while(keepon) { @@ -168,7 +168,7 @@ static CURLcode tftp_translate_code(tftp_error_t error); */ const struct Curl_handler Curl_handler_tftp = { - "TFTP", /* scheme */ + "tftp", /* scheme */ tftp_setup_connection, /* setup_connection */ tftp_do, /* do_it */ tftp_done, /* done */ @@ -182,6 +182,7 @@ const struct Curl_handler Curl_handler_tftp = { ZERO_NULL, /* perform_getsock */ tftp_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_TFTP, /* defport */ @@ -1203,7 +1204,7 @@ static timediff_t tftp_state_timeout(struct Curl_easy *data, state->state = TFTP_STATE_FIN; return 0; } - time(¤t); + current = time(NULL); if(current > state->rx_time + state->retry_time) { if(event) *event = TFTP_EVENT_TIMEOUT; diff --git a/lib/transfer.c b/lib/transfer.c index e31d1d6..744227e 100644 --- a/lib/transfer.c +++ b/lib/transfer.c @@ -198,7 +198,6 @@ static ssize_t Curl_xfer_recv_resp(struct Curl_easy *data, if(*err) return -1; DEBUGASSERT(nread >= 0); - *err = CURLE_OK; return nread; } @@ -272,10 +271,9 @@ static CURLcode readwrite_data(struct Curl_easy *data, DEBUGF(infof(data, "nread == 0, stream closed, bailing")); else DEBUGF(infof(data, "nread <= 0, server closed connection, bailing")); - if(k->eos_written) { /* already did write this to client, leave */ - k->keepon = 0; /* stop sending as well */ + k->keepon &= ~(KEEP_RECV|KEEP_SEND); /* stop sending as well */ + if(k->eos_written) /* already did write this to client, leave */ break; - } } total_received += blen; @@ -410,6 +408,14 @@ CURLcode Curl_readwrite(struct Curl_easy *data) int didwhat = 0; int select_bits; + /* Check if client writes had been paused and can resume now. */ + if(!(k->keepon & KEEP_RECV_PAUSE) && Curl_cwriter_is_paused(data)) { + Curl_conn_ev_data_pause(data, FALSE); + result = Curl_cwriter_unpause(data); + if(result) + goto out; + } + if(data->state.select_bits) { if(select_bits_paused(data, data->state.select_bits)) { /* leave the bits unchanged, so they'll tell us what to do when @@ -706,12 +712,14 @@ CURLcode Curl_pretransfer(struct Curl_easy *data) if(!result) result = Curl_setstropt(&data->state.aptr.passwd, data->set.str[STRING_PASSWORD]); +#ifndef CURL_DISABLE_PROXY if(!result) result = Curl_setstropt(&data->state.aptr.proxyuser, data->set.str[STRING_PROXYUSERNAME]); if(!result) result = Curl_setstropt(&data->state.aptr.proxypasswd, data->set.str[STRING_PROXYPASSWORD]); +#endif data->req.headerbytecount = 0; Curl_headers_cleanup(data); @@ -1155,7 +1163,7 @@ void Curl_xfer_setup( } CURLcode Curl_xfer_write_resp(struct Curl_easy *data, - char *buf, size_t blen, + const char *buf, size_t blen, bool is_eos) { CURLcode result = CURLE_OK; @@ -1189,9 +1197,23 @@ CURLcode Curl_xfer_write_resp(struct Curl_easy *data, data->req.eos_written = TRUE; data->req.download_done = TRUE; } + CURL_TRC_WRITE(data, "xfer_write_resp(len=%zu, eos=%d) -> %d", + blen, is_eos, result); return result; } +CURLcode Curl_xfer_write_resp_hd(struct Curl_easy *data, + const char *hd0, size_t hdlen, bool is_eos) +{ + if(data->conn->handler->write_resp_hd) { + /* protocol handlers offering this function take full responsibility + * for writing all received download data to the client. */ + return data->conn->handler->write_resp_hd(data, hd0, hdlen, is_eos); + } + /* No special handling by protocol handler, write as response bytes */ + return Curl_xfer_write_resp(data, hd0, hdlen, is_eos); +} + CURLcode Curl_xfer_write_done(struct Curl_easy *data, bool premature) { (void)premature; @@ -1221,6 +1243,9 @@ CURLcode Curl_xfer_send(struct Curl_easy *data, result = CURLE_OK; *pnwritten = 0; } + else if(!result && *pnwritten) + data->info.request_size += *pnwritten; + return result; } diff --git a/lib/transfer.h b/lib/transfer.h index e65b2b1..ad0f3a2 100644 --- a/lib/transfer.h +++ b/lib/transfer.h @@ -62,12 +62,20 @@ bool Curl_meets_timecondition(struct Curl_easy *data, time_t timeofdoc); * @param blen the amount of bytes in `buf` * @param is_eos TRUE iff the connection indicates this to be the last * bytes of the response - * @param done on returnm, TRUE iff the response is complete */ CURLcode Curl_xfer_write_resp(struct Curl_easy *data, - char *buf, size_t blen, + const char *buf, size_t blen, bool is_eos); +/** + * Write a single "header" line from a server response. + * @param hd0 the 0-terminated, single header line + * @param hdlen the length of the header line + * @param is_eos TRUE iff this is the end of the response + */ +CURLcode Curl_xfer_write_resp_hd(struct Curl_easy *data, + const char *hd0, size_t hdlen, bool is_eos); + /* This sets up a forthcoming transfer */ void Curl_xfer_setup(struct Curl_easy *data, int sockindex, /* socket index to read from or -1 */ @@ -278,10 +278,12 @@ CURLcode Curl_close(struct Curl_easy **datap) up_free(data); Curl_dyn_free(&data->state.headerb); Curl_flush_cookies(data, TRUE); +#ifndef CURL_DISABLE_ALTSVC Curl_altsvc_save(data, data->asi, data->set.str[STRING_ALTSVC]); Curl_altsvc_cleanup(&data->asi); - Curl_hsts_save(data, data->hsts, data->set.str[STRING_HSTS]); +#endif #ifndef CURL_DISABLE_HSTS + Curl_hsts_save(data, data->hsts, data->set.str[STRING_HSTS]); if(!data->share || !data->share->hsts) Curl_hsts_cleanup(&data->hsts); curl_slist_free_all(data->state.hstslist); /* clean up list */ @@ -305,7 +307,9 @@ CURLcode Curl_close(struct Curl_easy **datap) Curl_share_unlock(data, CURL_LOCK_DATA_SHARE); } +#ifndef CURL_DISABLE_PROXY Curl_safefree(data->state.aptr.proxyuserpwd); +#endif Curl_safefree(data->state.aptr.uagent); Curl_safefree(data->state.aptr.userpwd); Curl_safefree(data->state.aptr.accept_encoding); @@ -313,12 +317,18 @@ CURLcode Curl_close(struct Curl_easy **datap) Curl_safefree(data->state.aptr.rangeline); Curl_safefree(data->state.aptr.ref); Curl_safefree(data->state.aptr.host); +#ifndef CURL_DISABLE_COOKIES Curl_safefree(data->state.aptr.cookiehost); +#endif +#ifndef CURL_DISABLE_RTSP Curl_safefree(data->state.aptr.rtsp_transport); +#endif Curl_safefree(data->state.aptr.user); Curl_safefree(data->state.aptr.passwd); +#ifndef CURL_DISABLE_PROXY Curl_safefree(data->state.aptr.proxyuser); Curl_safefree(data->state.aptr.proxypasswd); +#endif #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_FORM_API) Curl_mime_cleanpart(data->state.formp); @@ -429,21 +439,23 @@ CURLcode Curl_init_userdefined(struct Curl_easy *data) result = Curl_setstropt(&set->str[STRING_SSL_CAFILE], CURL_CA_BUNDLE); if(result) return result; - +#ifndef CURL_DISABLE_PROXY result = Curl_setstropt(&set->str[STRING_SSL_CAFILE_PROXY], CURL_CA_BUNDLE); if(result) return result; #endif +#endif #if defined(CURL_CA_PATH) result = Curl_setstropt(&set->str[STRING_SSL_CAPATH], CURL_CA_PATH); if(result) return result; - +#ifndef CURL_DISABLE_PROXY result = Curl_setstropt(&set->str[STRING_SSL_CAPATH_PROXY], CURL_CA_PATH); if(result) return result; #endif +#endif } #ifndef CURL_DISABLE_FTP @@ -504,12 +516,7 @@ CURLcode Curl_open(struct Curl_easy **curl) data->magic = CURLEASY_MAGIC_NUMBER; - result = Curl_req_init(&data->req); - if(result) { - DEBUGF(fprintf(stderr, "Error: request init failed\n")); - free(data); - return result; - } + Curl_req_init(&data->req); result = Curl_resolver_init(data, &data->state.async.resolver); if(result) { @@ -767,16 +774,16 @@ static bool conn_maxage(struct Curl_easy *data, } /* - * This function checks if the given connection is dead and extracts it from + * This function checks if the given connection is dead and prunes it from * the connection cache if so. * * When this is called as a Curl_conncache_foreach() callback, the connection * cache lock is held! * - * Returns TRUE if the connection was dead and extracted. + * Returns TRUE if the connection was dead and pruned. */ -static bool extract_if_dead(struct connectdata *conn, - struct Curl_easy *data) +static bool prune_if_dead(struct connectdata *conn, + struct Curl_easy *data) { if(!CONN_INUSE(conn)) { /* The check for a dead socket makes sense only if the connection isn't in @@ -803,7 +810,7 @@ static bool extract_if_dead(struct connectdata *conn, } else { - bool input_pending; + bool input_pending = FALSE; Curl_attach_connection(data, conn); dead = !Curl_conn_is_alive(data, conn, &input_pending); @@ -822,6 +829,7 @@ static bool extract_if_dead(struct connectdata *conn, } if(dead) { + /* remove connection from cache */ infof(data, "Connection %" CURL_FORMAT_CURL_OFF_T " seems to be dead", conn->connection_id); Curl_conncache_remove_conn(data, conn, FALSE); @@ -831,22 +839,17 @@ static bool extract_if_dead(struct connectdata *conn, return FALSE; } -struct prunedead { - struct Curl_easy *data; - struct connectdata *extracted; -}; - /* - * Wrapper to use extract_if_dead() function in Curl_conncache_foreach() + * Wrapper to use prune_if_dead() function in Curl_conncache_foreach() * */ -static int call_extract_if_dead(struct Curl_easy *data, - struct connectdata *conn, void *param) +static int call_prune_if_dead(struct Curl_easy *data, + struct connectdata *conn, void *param) { - struct prunedead *p = (struct prunedead *)param; - if(extract_if_dead(conn, data)) { - /* stop the iteration here, pass back the connection that was extracted */ - p->extracted = conn; + struct connectdata **pruned = (struct connectdata **)param; + if(prune_if_dead(conn, data)) { + /* stop the iteration here, pass back the connection that was pruned */ + *pruned = conn; return 1; } return 0; /* continue iteration */ @@ -870,18 +873,15 @@ static void prune_dead_connections(struct Curl_easy *data) CONNCACHE_UNLOCK(data); if(elapsed >= 1000L) { - struct prunedead prune; - prune.data = data; - prune.extracted = NULL; - while(Curl_conncache_foreach(data, data->state.conn_cache, &prune, - call_extract_if_dead)) { + struct connectdata *pruned = NULL; + while(Curl_conncache_foreach(data, data->state.conn_cache, &pruned, + call_prune_if_dead)) { /* unlocked */ - /* remove connection from cache */ - Curl_conncache_remove_conn(data, prune.extracted, TRUE); + /* connection previously removed from cache in prune_if_dead() */ /* disconnect it */ - Curl_disconnect(data, prune.extracted, TRUE); + Curl_disconnect(data, pruned, TRUE); } CONNCACHE_LOCK(data); data->state.conn_cache->last_cleanup = now; @@ -925,13 +925,12 @@ ConnectionExists(struct Curl_easy *data, struct Curl_llist_element *curr; #ifdef USE_NTLM - bool wantNTLMhttp = ((data->state.authhost.want & - (CURLAUTH_NTLM | CURLAUTH_NTLM_WB)) && + bool wantNTLMhttp = ((data->state.authhost.want & CURLAUTH_NTLM) && (needle->handler->protocol & PROTO_FAMILY_HTTP)); #ifndef CURL_DISABLE_PROXY bool wantProxyNTLMhttp = (needle->bits.proxy_user_passwd && ((data->state.authproxy.want & - (CURLAUTH_NTLM | CURLAUTH_NTLM_WB)) && + CURLAUTH_NTLM) && (needle->handler->protocol & PROTO_FAMILY_HTTP))); #else bool wantProxyNTLMhttp = FALSE; @@ -1293,7 +1292,7 @@ ConnectionExists(struct Curl_easy *data, /* When not multiplexed, we have a match here! */ infof(data, "Multiplexed connection found"); } - else if(extract_if_dead(check, data)) { + else if(prune_if_dead(check, data)) { /* disconnect it */ Curl_disconnect(data, check, TRUE); continue; @@ -1403,12 +1402,6 @@ static struct connectdata *allocate_conn(struct Curl_easy *data) conn->connect_only = data->set.connect_only; conn->transport = TRNSPRT_TCP; /* most of them are TCP streams */ -#if !defined(CURL_DISABLE_HTTP) && defined(USE_NTLM) && \ - defined(NTLM_WB_ENABLED) - conn->ntlm.ntlm_auth_hlpr_socket = CURL_SOCKET_BAD; - conn->proxyntlm.ntlm_auth_hlpr_socket = CURL_SOCKET_BAD; -#endif - /* Initialize the easy handle list */ Curl_llist_init(&conn->easyq, NULL); @@ -1710,7 +1703,7 @@ CURLcode Curl_uc_to_curlcode(CURLUcode uc) } } -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 /* * If the URL was set with an IPv6 numerical address with a zone id part, set * the scope_id based on that! @@ -1975,7 +1968,7 @@ static CURLcode parseurlandfillconn(struct Curl_easy *data, (void)curl_url_get(uh, CURLUPART_QUERY, &data->state.up.query, 0); -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 if(data->set.scope_id) /* Override any scope that was set above. */ conn->scope_id = data->set.scope_id; @@ -2083,19 +2076,13 @@ static char *detect_proxy(struct Curl_easy *data, * For compatibility, the all-uppercase versions of these variables are * checked if the lowercase versions don't exist. */ - char proxy_env[128]; - const char *protop = conn->handler->scheme; + char proxy_env[20]; char *envp = proxy_env; #ifdef CURL_DISABLE_VERBOSE_STRINGS (void)data; #endif - /* Now, build <protocol>_proxy and check for such a one to use */ - while(*protop) - *envp++ = Curl_raw_tolower(*protop++); - - /* append _proxy */ - strcpy(envp, "_proxy"); + msnprintf(proxy_env, sizeof(proxy_env), "%s_proxy", conn->handler->scheme); /* read the protocol proxy: */ proxy = curl_getenv(proxy_env); @@ -2118,7 +2105,6 @@ static char *detect_proxy(struct Curl_easy *data, proxy = curl_getenv(proxy_env); } - envp = proxy_env; if(!proxy) { #ifdef USE_WEBSOCKETS /* websocket proxy fallbacks */ @@ -2366,17 +2352,16 @@ static CURLcode parse_proxy_auth(struct Curl_easy *data, data->state.aptr.proxyuser : ""; const char *proxypasswd = data->state.aptr.proxypasswd ? data->state.aptr.proxypasswd : ""; - CURLcode result = Curl_urldecode(proxyuser, 0, &conn->http_proxy.user, NULL, - REJECT_ZERO); - if(!result) - result = Curl_setstropt(&data->state.aptr.proxyuser, - conn->http_proxy.user); - if(!result) - result = Curl_urldecode(proxypasswd, 0, &conn->http_proxy.passwd, - NULL, REJECT_ZERO); - if(!result) - result = Curl_setstropt(&data->state.aptr.proxypasswd, - conn->http_proxy.passwd); + CURLcode result = CURLE_OUT_OF_MEMORY; + + conn->http_proxy.user = strdup(proxyuser); + if(conn->http_proxy.user) { + conn->http_proxy.passwd = strdup(proxypasswd); + if(conn->http_proxy.passwd) + result = CURLE_OK; + else + Curl_safefree(conn->http_proxy.user); + } return result; } @@ -2576,14 +2561,15 @@ out: * * Parameters: * - * login [in] - The login string. - * len [in] - The length of the login string. - * userp [in/out] - The address where a pointer to newly allocated memory + * login [in] - login string. + * len [in] - length of the login string. + * userp [in/out] - address where a pointer to newly allocated memory * holding the user will be stored upon completion. - * passwdp [in/out] - The address where a pointer to newly allocated memory + * passwdp [in/out] - address where a pointer to newly allocated memory * holding the password will be stored upon completion. - * optionsp [in/out] - The address where a pointer to newly allocated memory - * holding the options will be stored upon completion. + * optionsp [in/out] - OPTIONAL address where a pointer to newly allocated + * memory holding the options will be stored upon + * completion. * * Returns CURLE_OK on success. */ @@ -2591,19 +2577,19 @@ CURLcode Curl_parse_login_details(const char *login, const size_t len, char **userp, char **passwdp, char **optionsp) { - CURLcode result = CURLE_OK; char *ubuf = NULL; char *pbuf = NULL; - char *obuf = NULL; const char *psep = NULL; const char *osep = NULL; size_t ulen; size_t plen; size_t olen; + DEBUGASSERT(userp); + DEBUGASSERT(passwdp); + /* Attempt to find the password separator */ - if(passwdp) - psep = memchr(login, ':', len); + psep = memchr(login, ':', len); /* Attempt to find the options separator */ if(optionsp) @@ -2615,64 +2601,40 @@ CURLcode Curl_parse_login_details(const char *login, const size_t len, (osep ? (size_t)(osep - login) : len)); plen = (psep ? (osep && osep > psep ? (size_t)(osep - psep) : - (size_t)(login + len - psep)) - 1 : 0); + (size_t)(login + len - psep)) - 1 : 0); olen = (osep ? (psep && psep > osep ? (size_t)(psep - osep) : - (size_t)(login + len - osep)) - 1 : 0); + (size_t)(login + len - osep)) - 1 : 0); - /* Allocate the user portion buffer, which can be zero length */ - if(userp) { - ubuf = malloc(ulen + 1); - if(!ubuf) - result = CURLE_OUT_OF_MEMORY; - } + /* Clone the user portion buffer, which can be zero length */ + ubuf = Curl_memdup0(login, ulen); + if(!ubuf) + goto error; - /* Allocate the password portion buffer */ - if(!result && passwdp && psep) { - pbuf = malloc(plen + 1); - if(!pbuf) { - free(ubuf); - result = CURLE_OUT_OF_MEMORY; - } + /* Clone the password portion buffer */ + if(psep) { + pbuf = Curl_memdup0(&psep[1], plen); + if(!pbuf) + goto error; } /* Allocate the options portion buffer */ - if(!result && optionsp && olen) { - obuf = malloc(olen + 1); - if(!obuf) { - free(pbuf); - free(ubuf); - result = CURLE_OUT_OF_MEMORY; - } - } - - if(!result) { - /* Store the user portion if necessary */ - if(ubuf) { - memcpy(ubuf, login, ulen); - ubuf[ulen] = '\0'; - Curl_safefree(*userp); - *userp = ubuf; - } - - /* Store the password portion if necessary */ - if(pbuf) { - memcpy(pbuf, psep + 1, plen); - pbuf[plen] = '\0'; - Curl_safefree(*passwdp); - *passwdp = pbuf; - } - - /* Store the options portion if necessary */ - if(obuf) { - memcpy(obuf, osep + 1, olen); - obuf[olen] = '\0'; - Curl_safefree(*optionsp); - *optionsp = obuf; + if(optionsp) { + char *obuf = NULL; + if(olen) { + obuf = Curl_memdup0(&osep[1], olen); + if(!obuf) + goto error; } + *optionsp = obuf; } - - return result; + *userp = ubuf; + *passwdp = pbuf; + return CURLE_OK; +error: + free(ubuf); + free(pbuf); + return CURLE_OUT_OF_MEMORY; } /************************************************************* @@ -2883,7 +2845,7 @@ static CURLcode parse_connect_to_host_port(struct Curl_easy *data, /* detect and extract RFC6874-style IPv6-addresses */ if(*hostptr == '[') { -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 char *ptr = ++hostptr; /* advance beyond the initial bracket */ while(*ptr && (ISXDIGIT(*ptr) || (*ptr == ':') || (*ptr == '.'))) ptr++; @@ -3081,7 +3043,7 @@ static CURLcode parse_connect_to_slist(struct Curl_easy *data, #ifdef USE_HTTP2 | ALPN_h2 #endif -#ifdef ENABLE_QUIC +#ifdef USE_HTTP3 | ALPN_h3 #endif ) & data->asi->flags; @@ -3741,14 +3703,14 @@ static CURLcode create_conn(struct Curl_easy *data, /* If NTLM is requested in a part of this connection, make sure we don't assume the state is fine as this is a fresh connection and NTLM is connection based. */ - if((data->state.authhost.picked & (CURLAUTH_NTLM | CURLAUTH_NTLM_WB)) && + if((data->state.authhost.picked & CURLAUTH_NTLM) && data->state.authhost.done) { infof(data, "NTLM picked AND auth done set, clear picked"); data->state.authhost.picked = CURLAUTH_NONE; data->state.authhost.done = FALSE; } - if((data->state.authproxy.picked & (CURLAUTH_NTLM | CURLAUTH_NTLM_WB)) && + if((data->state.authproxy.picked & CURLAUTH_NTLM) && data->state.authproxy.done) { infof(data, "NTLM-proxy picked AND auth done set, clear picked"); data->state.authproxy.picked = CURLAUTH_NONE; @@ -3884,9 +3846,7 @@ CURLcode Curl_connect(struct Curl_easy *data, CURLcode Curl_init_do(struct Curl_easy *data, struct connectdata *conn) { /* if this is a pushed stream, we need this: */ - CURLcode result = Curl_preconnect(data); - if(result) - return result; + CURLcode result; if(conn) { conn->bits.do_more = FALSE; /* by default there's no curl_do_more() to @@ -3904,14 +3864,12 @@ CURLcode Curl_init_do(struct Curl_easy *data, struct connectdata *conn) data->state.httpreq = HTTPREQ_HEAD; result = Curl_req_start(&data->req, data); - if(result) - return result; - - Curl_speedinit(data); - Curl_pgrsSetUploadCounter(data, 0); - Curl_pgrsSetDownloadCounter(data, 0); - - return CURLE_OK; + if(!result) { + Curl_speedinit(data); + Curl_pgrsSetUploadCounter(data, 0); + Curl_pgrsSetDownloadCounter(data, 0); + } + return result; } #if defined(USE_HTTP2) || defined(USE_HTTP3) diff --git a/lib/urlapi-int.h b/lib/urlapi-int.h index d6e240a..c40281a 100644 --- a/lib/urlapi-int.h +++ b/lib/urlapi-int.h @@ -28,8 +28,7 @@ size_t Curl_is_absolute_url(const char *url, char *buf, size_t buflen, bool guess_scheme); -CURLUcode Curl_url_set_authority(CURLU *u, const char *authority, - unsigned int flags); +CURLUcode Curl_url_set_authority(CURLU *u, const char *authority); #ifdef DEBUGBUILD CURLUcode Curl_parse_port(struct Curl_URL *u, struct dynbuf *host, diff --git a/lib/urlapi.c b/lib/urlapi.c index dc42489..eb03966 100644 --- a/lib/urlapi.c +++ b/lib/urlapi.c @@ -59,11 +59,11 @@ #define MAX_SCHEME_LEN 40 /* - * If ENABLE_IPV6 is disabled, we still want to parse IPv6 addresses, so make + * If USE_IPV6 is disabled, we still want to parse IPv6 addresses, so make * sure we have _some_ value for AF_INET6 without polluting our fake value * everywhere. */ -#if !defined(ENABLE_IPV6) && !defined(AF_INET6) +#if !defined(USE_IPV6) && !defined(AF_INET6) #define AF_INET6 (AF_INET + 1) #endif @@ -79,7 +79,9 @@ struct Curl_URL { char *path; char *query; char *fragment; - long portnum; /* the numerical version */ + unsigned short portnum; /* the numerical version (if 'port' is set) */ + BIT(query_present); /* to support blank */ + BIT(fragment_present); /* to support blank */ }; #define DEFAULT_SCHEME "https" @@ -232,10 +234,8 @@ size_t Curl_is_absolute_url(const char *url, char *buf, size_t buflen, /* the length of the scheme is the name part only */ size_t len = i; if(buf) { + Curl_strntolower(buf, url, i); buf[i] = 0; - while(i--) { - buf[i] = Curl_raw_tolower(url[i]); - } } return len; } @@ -264,6 +264,7 @@ static CURLcode concat_url(char *base, const char *relurl, char **newurl) const char *useurl = relurl; CURLcode result = CURLE_OK; CURLUcode uc; + bool skip_slash = FALSE; *newurl = NULL; /* protsep points to the start of the host name */ @@ -283,48 +284,50 @@ static CURLcode concat_url(char *base, const char *relurl, char **newurl) *pathsep = 0; /* we have a relative path to append to the last slash if there's one - available, or if the new URL is just a query string (starts with a - '?') we append the new one at the end of the entire currently worked - out URL */ - if(useurl[0] != '?') { + available, or the new URL is just a query string (starts with a '?') or + a fragment (starts with '#') we append the new one at the end of the + current URL */ + if((useurl[0] != '?') && (useurl[0] != '#')) { pathsep = strrchr(protsep, '/'); if(pathsep) *pathsep = 0; - } - /* Check if there's any slash after the host name, and if so, remember - that position instead */ - pathsep = strchr(protsep, '/'); - if(pathsep) - protsep = pathsep + 1; - else - protsep = NULL; + /* Check if there's any slash after the host name, and if so, remember + that position instead */ + pathsep = strchr(protsep, '/'); + if(pathsep) + protsep = pathsep + 1; + else + protsep = NULL; - /* now deal with one "./" or any amount of "../" in the newurl - and act accordingly */ + /* now deal with one "./" or any amount of "../" in the newurl + and act accordingly */ - if((useurl[0] == '.') && (useurl[1] == '/')) - useurl += 2; /* just skip the "./" */ + if((useurl[0] == '.') && (useurl[1] == '/')) + useurl += 2; /* just skip the "./" */ - while((useurl[0] == '.') && - (useurl[1] == '.') && - (useurl[2] == '/')) { - level++; - useurl += 3; /* pass the "../" */ - } + while((useurl[0] == '.') && + (useurl[1] == '.') && + (useurl[2] == '/')) { + level++; + useurl += 3; /* pass the "../" */ + } - if(protsep) { - while(level--) { - /* cut off one more level from the right of the original URL */ - pathsep = strrchr(protsep, '/'); - if(pathsep) - *pathsep = 0; - else { - *protsep = 0; - break; + if(protsep) { + while(level--) { + /* cut off one more level from the right of the original URL */ + pathsep = strrchr(protsep, '/'); + if(pathsep) + *pathsep = 0; + else { + *protsep = 0; + break; + } } } } + else + skip_slash = TRUE; } else { /* We got a new absolute path for this server */ @@ -370,7 +373,7 @@ static CURLcode concat_url(char *base, const char *relurl, char **newurl) return result; /* check if we need to append a slash */ - if(('/' == useurl[0]) || (protsep && !*protsep) || ('?' == useurl[0])) + if(('/' == useurl[0]) || (protsep && !*protsep) || skip_slash) ; else { result = Curl_dyn_addn(&newest, "/", 1); @@ -532,7 +535,7 @@ UNITTEST CURLUcode Curl_parse_port(struct Curl_URL *u, struct dynbuf *host, if(portptr) { char *rest = NULL; - long port; + unsigned long port; size_t keep = portptr - hostname; /* Browser behavior adaptation. If there's a colon with no digits after, @@ -550,15 +553,13 @@ UNITTEST CURLUcode Curl_parse_port(struct Curl_URL *u, struct dynbuf *host, if(!ISDIGIT(*portptr)) return CURLUE_BAD_PORT_NUMBER; - port = strtol(portptr, &rest, 10); /* Port number must be decimal */ + errno = 0; + port = strtoul(portptr, &rest, 10); /* Port number must be decimal */ - if(port > 0xffff) + if(errno || (port > 0xffff) || *rest) return CURLUE_BAD_PORT_NUMBER; - if(rest[0]) - return CURLUE_BAD_PORT_NUMBER; - - u->portnum = port; + u->portnum = (unsigned short) port; /* generate a new port number string to get rid of leading zeroes etc */ free(u->port); u->port = aprintf("%ld", port); @@ -680,6 +681,7 @@ static int ipv4_normalize(struct dynbuf *host) if(*c == '[') return HOST_IPV6; + errno = 0; /* for strtoul */ while(!done) { char *endp = NULL; unsigned long l; @@ -687,6 +689,13 @@ static int ipv4_normalize(struct dynbuf *host) /* most importantly this doesn't allow a leading plus or minus */ return HOST_NAME; l = strtoul(c, &endp, 0); + if(errno) + return HOST_NAME; +#if SIZEOF_LONG > 4 + /* a value larger than 32 bits */ + if(l > UINT_MAX) + return HOST_NAME; +#endif parts[n] = l; c = endp; @@ -706,16 +715,6 @@ static int ipv4_normalize(struct dynbuf *host) default: return HOST_NAME; } - - /* overflow */ - if((l == ULONG_MAX) && (errno == ERANGE)) - return HOST_NAME; - -#if SIZEOF_LONG > 4 - /* a value larger than 32 bits */ - if(l > UINT_MAX) - return HOST_NAME; -#endif } switch(n) { @@ -846,8 +845,8 @@ out: return uc; } -CURLUcode Curl_url_set_authority(CURLU *u, const char *authority, - unsigned int flags) +/* used for HTTP/2 server push */ +CURLUcode Curl_url_set_authority(CURLU *u, const char *authority) { CURLUcode result; struct dynbuf host; @@ -855,8 +854,8 @@ CURLUcode Curl_url_set_authority(CURLU *u, const char *authority, DEBUGASSERT(authority); Curl_dyn_init(&host, CURL_MAX_INPUT_LENGTH); - result = parse_authority(u, authority, strlen(authority), flags, - &host, !!u->scheme); + result = parse_authority(u, authority, strlen(authority), + CURLU_DISALLOW_USER, &host, !!u->scheme); if(result) Curl_dyn_free(&host); else { @@ -1242,6 +1241,7 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) fragment = strchr(path, '#'); if(fragment) { fraglen = pathlen - (fragment - path); + u->fragment_present = TRUE; if(fraglen > 1) { /* skip the leading '#' in the copy but include the terminating null */ if(flags & CURLU_URLENCODE) { @@ -1269,6 +1269,7 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) size_t qlen = fragment ? (size_t)(fragment - query) : pathlen - (query - path); pathlen -= qlen; + u->query_present = TRUE; if(qlen > 1) { if(flags & CURLU_URLENCODE) { struct dynbuf enc; @@ -1404,6 +1405,8 @@ CURLU *curl_url_dup(const CURLU *in) DUP(u, in, fragment); DUP(u, in, zoneid); u->portnum = in->portnum; + u->fragment_present = in->fragment_present; + u->query_present = in->query_present; } return u; fail: @@ -1488,10 +1491,16 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what, ptr = u->query; ifmissing = CURLUE_NO_QUERY; plusdecode = urldecode; + if(ptr && !ptr[0] && !(flags & CURLU_GET_EMPTY)) + /* there was a blank query and the user do not ask for it */ + ptr = NULL; break; case CURLUPART_FRAGMENT: ptr = u->fragment; ifmissing = CURLUE_NO_FRAGMENT; + if(!ptr && u->fragment_present && flags & CURLU_GET_EMPTY) + /* there was a blank fragment and the user asks for it */ + ptr = ""; break; case CURLUPART_URL: { char *url; @@ -1499,13 +1508,18 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what, char *options = u->options; char *port = u->port; char *allochost = NULL; + bool show_fragment = + u->fragment || (u->fragment_present && flags & CURLU_GET_EMPTY); + bool show_query = + (u->query && u->query[0]) || + (u->query_present && flags & CURLU_GET_EMPTY); punycode = (flags & CURLU_PUNYCODE)?1:0; depunyfy = (flags & CURLU_PUNY2IDN)?1:0; if(u->scheme && strcasecompare("file", u->scheme)) { url = aprintf("file://%s%s%s", u->path, - u->fragment? "#": "", - u->fragment? u->fragment : ""); + show_fragment ? "#": "", + u->fragment ? u->fragment : ""); } else if(!u->host) return CURLUE_NO_HOST; @@ -1593,9 +1607,9 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what, port ? ":": "", port ? port : "", u->path ? u->path : "/", - (u->query && u->query[0]) ? "?": "", - (u->query && u->query[0]) ? u->query : "", - u->fragment? "#": "", + show_query ? "?": "", + u->query ? u->query : "", + show_fragment ? "#": "", u->fragment? u->fragment : ""); free(allochost); } @@ -1687,7 +1701,6 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, const char *part, unsigned int flags) { char **storep = NULL; - long port = 0; bool urlencode = (flags & CURLU_URLENCODE)? 1 : 0; bool plusencode = FALSE; bool urlskipslash = FALSE; @@ -1730,9 +1743,11 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, break; case CURLUPART_QUERY: storep = &u->query; + u->query_present = FALSE; break; case CURLUPART_FRAGMENT: storep = &u->fragment; + u->fragment_present = FALSE; break; default: return CURLUE_UNKNOWN_PART; @@ -1794,18 +1809,26 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, storep = &u->zoneid; break; case CURLUPART_PORT: - { - char *endp; - urlencode = FALSE; /* never */ - port = strtol(part, &endp, 10); /* Port number must be decimal */ - if((port <= 0) || (port > 0xffff)) - return CURLUE_BAD_PORT_NUMBER; - if(*endp) - /* weirdly provided number, not good! */ + if(!ISDIGIT(part[0])) + /* not a number */ return CURLUE_BAD_PORT_NUMBER; - storep = &u->port; - } - break; + else { + char *tmp; + char *endp; + unsigned long port; + errno = 0; + port = strtoul(part, &endp, 10); /* must be decimal */ + if(errno || (port > 0xffff) || *endp) + /* weirdly provided number, not good! */ + return CURLUE_BAD_PORT_NUMBER; + tmp = strdup(part); + if(!tmp) + return CURLUE_OUT_OF_MEMORY; + free(u->port); + u->port = tmp; + u->portnum = (unsigned short)port; + return CURLUE_OK; + } case CURLUPART_PATH: urlskipslash = TRUE; leadingslash = TRUE; /* enforce */ @@ -1816,9 +1839,11 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, appendquery = (flags & CURLU_APPENDQUERY)?1:0; equalsencode = appendquery; storep = &u->query; + u->query_present = TRUE; break; case CURLUPART_FRAGMENT: storep = &u->fragment; + u->fragment_present = TRUE; break; case CURLUPART_URL: { /* @@ -1966,9 +1991,5 @@ nomem: free(*storep); *storep = (char *)newp; } - /* set after the string, to make it not assigned if the allocation above - fails */ - if(port) - u->portnum = port; return CURLUE_OK; } diff --git a/lib/urldata.h b/lib/urldata.h index ce28f25..8b1bd65 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -55,6 +55,15 @@ struct curl_trc_featt; +#ifdef USE_ECH +/* CURLECH_ bits for the tls_ech option */ +# define CURLECH_DISABLE (1<<0) +# define CURLECH_GREASE (1<<1) +# define CURLECH_ENABLE (1<<2) +# define CURLECH_HARD (1<<3) +# define CURLECH_CLA_CFG (1<<4) +#endif + #ifdef USE_WEBSOCKETS /* CURLPROTO_GOPHERS (29) is the highest publicly used protocol bit number, * the rest are internal information. If we use higher bits we only do this on @@ -104,7 +113,7 @@ typedef unsigned int curl_prot_t; #define PROTO_FAMILY_SSH (CURLPROTO_SCP|CURLPROTO_SFTP) #if !defined(CURL_DISABLE_FTP) || defined(USE_SSH) || \ - !defined(CURL_DISABLE_POP3) + !defined(CURL_DISABLE_POP3) || !defined(CURL_DISABLE_FILE) /* these protocols support CURLOPT_DIRLISTONLY */ #define CURL_LIST_ONLY_PROTOCOL 1 #endif @@ -232,8 +241,7 @@ typedef CURLcode (*Curl_datastream)(struct Curl_easy *data, #ifdef HAVE_GSSAPI /* Types needed for krb5-ftp connections */ struct krb5buffer { - void *data; - size_t size; + struct dynbuf buf; size_t index; BIT(eof_flag); }; @@ -279,6 +287,8 @@ struct ssl_peer { char *dispname; /* display version of hostname */ char *sni; /* SNI version of hostname or NULL if not usable */ ssl_peer_type type; /* type of the peer information */ + int port; /* port we are talking to */ + int transport; /* TCP or QUIC */ }; struct ssl_primary_config { @@ -334,6 +344,8 @@ struct ssl_general_config { int ca_cache_timeout; /* Certificate store cache timeout (seconds) */ }; +typedef void Curl_ssl_sessionid_dtor(void *sessionid, size_t idsize); + /* information stored about one single SSL session */ struct Curl_ssl_session { char *name; /* host name for which this ID was used */ @@ -341,9 +353,11 @@ struct Curl_ssl_session { const char *scheme; /* protocol scheme used */ void *sessionid; /* as returned from the SSL layer */ size_t idsize; /* if known, otherwise 0 */ + Curl_ssl_sessionid_dtor *sessionid_free; /* free `sessionid` callback */ long age; /* just a number, the higher the more recent */ int remote_port; /* remote port */ int conn_to_port; /* remote port for the connection (may be -1) */ + int transport; /* TCP or QUIC */ struct ssl_primary_config ssl_config; /* setup for this session */ }; @@ -444,14 +458,6 @@ struct ntlmdata { unsigned char nonce[8]; unsigned int target_info_len; void *target_info; /* TargetInfo received in the ntlm type-2 message */ - -#if defined(NTLM_WB_ENABLED) - /* used for communication with Samba's winbind daemon helper ntlm_auth */ - curl_socket_t ntlm_auth_hlpr_socket; - pid_t ntlm_auth_hlpr_pid; - char *challenge; /* The received base64 encoded ntlm type-2 message */ - char *response; /* The generated base64 ntlm type-1/type-3 message */ -#endif #endif }; #endif @@ -632,6 +638,9 @@ enum doh_slots { DOH_PROBE_SLOT_IPADDR_V6 = 1, /* 'V6' likewise */ /* Space here for (possibly build-specific) additional slot definitions */ +#ifdef USE_HTTPSRR + DOH_PROBE_SLOT_HTTPS = 2, /* for HTTPS RR */ +#endif /* for example */ /* #ifdef WANT_DOH_FOOBAR_TXT */ @@ -647,7 +656,7 @@ enum doh_slots { */ struct Curl_handler { - const char *scheme; /* URL scheme name. */ + const char *scheme; /* URL scheme name in lowercase */ /* Complement to setup_connection_internals(). This is done before the transfer "owns" the connection. */ @@ -706,12 +715,18 @@ struct Curl_handler { CURLcode (*disconnect)(struct Curl_easy *, struct connectdata *, bool dead_connection); - /* If used, this function gets called from transfer.c:readwrite_data() to + /* If used, this function gets called from transfer.c to allow the protocol to do extra handling in writing response to the client. */ CURLcode (*write_resp)(struct Curl_easy *data, const char *buf, size_t blen, bool is_eos); + /* If used, this function gets called from transfer.c to + allow the protocol to do extra handling in writing a single response + header line to the client. */ + CURLcode (*write_resp_hd)(struct Curl_easy *data, + const char *hd, size_t hdlen, bool is_eos); + /* This function can perform various checks on the connection. See CONNCHECK_* for more information about the checks that can be performed, and CONNRESULT_* for the results that can be returned. */ @@ -983,7 +998,7 @@ struct connectdata { int remote_port; /* the remote port, not the proxy port! */ int conn_to_port; /* the remote port to connect to. valid only if bits.conn_to_port is set */ -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 unsigned int scope_id; /* Scope id for IPv6 */ #endif unsigned short localport; @@ -1327,22 +1342,28 @@ struct UrlState { /* Dynamically allocated strings, MUST be freed before this struct is killed. */ struct dynamically_allocated_data { - char *proxyuserpwd; char *uagent; char *accept_encoding; char *userpwd; char *rangeline; char *ref; char *host; +#ifndef CURL_DISABLE_COOKIES char *cookiehost; +#endif +#ifndef CURL_DISABLE_RTSP char *rtsp_transport; +#endif char *te; /* TE: request header */ /* transfer credentials */ char *user; char *passwd; +#ifndef CURL_DISABLE_PROXY + char *proxyuserpwd; char *proxyuser; char *proxypasswd; +#endif } aptr; unsigned char httpwant; /* when non-zero, a specific HTTP version requested @@ -1379,7 +1400,6 @@ struct UrlState { BIT(done); /* set to FALSE when Curl_init_do() is called and set to TRUE when multi_done() is called, to prevent multi_done() to get invoked twice when the multi interface is used. */ - BIT(previouslypending); /* this transfer WAS in the multi->pending queue */ #ifndef CURL_DISABLE_COOKIES BIT(cookie_engine); #endif @@ -1407,95 +1427,127 @@ struct UrlState { struct Curl_multi; /* declared in multihandle.c */ -/* - * This enumeration MUST not use conditional directives (#ifdefs), new - * null terminated strings MUST be added to the enumeration immediately - * before STRING_LASTZEROTERMINATED, binary fields immediately before - * STRING_LAST. When doing so, ensure that the packages/OS400/chkstring.c - * test is updated and applicable changes for EBCDIC to ASCII conversion - * are catered for in curl_easy_setopt_ccsid() - */ enum dupstring { STRING_CERT, /* client certificate file name */ - STRING_CERT_PROXY, /* client certificate file name */ STRING_CERT_TYPE, /* format for certificate (default: PEM)*/ + STRING_KEY, /* private key file name */ + STRING_KEY_PASSWD, /* plain text private key password */ + STRING_KEY_TYPE, /* format for private key (default: PEM) */ + STRING_SSL_CAPATH, /* CA directory name (doesn't work on windows) */ + STRING_SSL_CAFILE, /* certificate file to verify peer against */ + STRING_SSL_PINNEDPUBLICKEY, /* public key file to verify peer against */ + STRING_SSL_CIPHER_LIST, /* list of ciphers to use */ + STRING_SSL_CIPHER13_LIST, /* list of TLS 1.3 ciphers to use */ + STRING_SSL_CRLFILE, /* crl file to check certificate */ + STRING_SSL_ISSUERCERT, /* issuer cert file to check certificate */ + STRING_SERVICE_NAME, /* Service name */ +#ifndef CURL_DISABLE_PROXY + STRING_CERT_PROXY, /* client certificate file name */ STRING_CERT_TYPE_PROXY, /* format for certificate (default: PEM)*/ + STRING_KEY_PROXY, /* private key file name */ + STRING_KEY_PASSWD_PROXY, /* plain text private key password */ + STRING_KEY_TYPE_PROXY, /* format for private key (default: PEM) */ + STRING_SSL_CAPATH_PROXY, /* CA directory name (doesn't work on windows) */ + STRING_SSL_CAFILE_PROXY, /* certificate file to verify peer against */ + STRING_SSL_PINNEDPUBLICKEY_PROXY, /* public key file to verify proxy */ + STRING_SSL_CIPHER_LIST_PROXY, /* list of ciphers to use */ + STRING_SSL_CIPHER13_LIST_PROXY, /* list of TLS 1.3 ciphers to use */ + STRING_SSL_CRLFILE_PROXY, /* crl file to check certificate */ + STRING_SSL_ISSUERCERT_PROXY, /* issuer cert file to check certificate */ + STRING_PROXY_SERVICE_NAME, /* Proxy service name */ +#endif +#ifndef CURL_DISABLE_COOKIES STRING_COOKIE, /* HTTP cookie string to send */ STRING_COOKIEJAR, /* dump all cookies to this file */ +#endif STRING_CUSTOMREQUEST, /* HTTP/FTP/RTSP request/method to use */ STRING_DEFAULT_PROTOCOL, /* Protocol to use when the URL doesn't specify */ STRING_DEVICE, /* local network interface/address to use */ STRING_ENCODING, /* Accept-Encoding string */ +#ifndef CURL_DISABLE_FTP STRING_FTP_ACCOUNT, /* ftp account data */ STRING_FTP_ALTERNATIVE_TO_USER, /* command to send if USER/PASS fails */ STRING_FTPPORT, /* port to send with the FTP PORT command */ - STRING_KEY, /* private key file name */ - STRING_KEY_PROXY, /* private key file name */ - STRING_KEY_PASSWD, /* plain text private key password */ - STRING_KEY_PASSWD_PROXY, /* plain text private key password */ - STRING_KEY_TYPE, /* format for private key (default: PEM) */ - STRING_KEY_TYPE_PROXY, /* format for private key (default: PEM) */ +#endif +#if defined(HAVE_GSSAPI) STRING_KRB_LEVEL, /* krb security level */ +#endif +#ifndef CURL_DISABLE_NETRC STRING_NETRC_FILE, /* if not NULL, use this instead of trying to find $HOME/.netrc */ +#endif +#ifndef CURL_DISABLE_PROXY STRING_PROXY, /* proxy to use */ STRING_PRE_PROXY, /* pre socks proxy to use */ +#endif STRING_SET_RANGE, /* range, if used */ STRING_SET_REFERER, /* custom string for the HTTP referer field */ STRING_SET_URL, /* what original URL to work on */ - STRING_SSL_CAPATH, /* CA directory name (doesn't work on windows) */ - STRING_SSL_CAPATH_PROXY, /* CA directory name (doesn't work on windows) */ - STRING_SSL_CAFILE, /* certificate file to verify peer against */ - STRING_SSL_CAFILE_PROXY, /* certificate file to verify peer against */ - STRING_SSL_PINNEDPUBLICKEY, /* public key file to verify peer against */ - STRING_SSL_PINNEDPUBLICKEY_PROXY, /* public key file to verify proxy */ - STRING_SSL_CIPHER_LIST, /* list of ciphers to use */ - STRING_SSL_CIPHER_LIST_PROXY, /* list of ciphers to use */ - STRING_SSL_CIPHER13_LIST, /* list of TLS 1.3 ciphers to use */ - STRING_SSL_CIPHER13_LIST_PROXY, /* list of TLS 1.3 ciphers to use */ STRING_USERAGENT, /* User-Agent string */ - STRING_SSL_CRLFILE, /* crl file to check certificate */ - STRING_SSL_CRLFILE_PROXY, /* crl file to check certificate */ - STRING_SSL_ISSUERCERT, /* issuer cert file to check certificate */ - STRING_SSL_ISSUERCERT_PROXY, /* issuer cert file to check certificate */ STRING_SSL_ENGINE, /* name of ssl engine */ STRING_USERNAME, /* <username>, if used */ STRING_PASSWORD, /* <password>, if used */ STRING_OPTIONS, /* <options>, if used */ +#ifndef CURL_DISABLE_PROXY STRING_PROXYUSERNAME, /* Proxy <username>, if used */ STRING_PROXYPASSWORD, /* Proxy <password>, if used */ STRING_NOPROXY, /* List of hosts which should not use the proxy, if used */ +#endif +#ifndef CURL_DISABLE_RTSP STRING_RTSP_SESSION_ID, /* Session ID to use */ STRING_RTSP_STREAM_URI, /* Stream URI for this request */ STRING_RTSP_TRANSPORT, /* Transport for this session */ +#endif +#ifdef USE_SSH STRING_SSH_PRIVATE_KEY, /* path to the private key file for auth */ STRING_SSH_PUBLIC_KEY, /* path to the public key file for auth */ STRING_SSH_HOST_PUBLIC_KEY_MD5, /* md5 of host public key in ascii hex */ STRING_SSH_HOST_PUBLIC_KEY_SHA256, /* sha256 of host public key in base64 */ STRING_SSH_KNOWNHOSTS, /* file name of knownhosts file */ - STRING_PROXY_SERVICE_NAME, /* Proxy service name */ - STRING_SERVICE_NAME, /* Service name */ +#endif +#ifndef CURL_DISABLE_SMTP STRING_MAIL_FROM, STRING_MAIL_AUTH, +#endif +#ifdef USE_TLS_SRP STRING_TLSAUTH_USERNAME, /* TLS auth <username> */ - STRING_TLSAUTH_USERNAME_PROXY, /* TLS auth <username> */ STRING_TLSAUTH_PASSWORD, /* TLS auth <password> */ +#ifndef CURL_DISABLE_PROXY + STRING_TLSAUTH_USERNAME_PROXY, /* TLS auth <username> */ STRING_TLSAUTH_PASSWORD_PROXY, /* TLS auth <password> */ +#endif +#endif STRING_BEARER, /* <bearer>, if used */ +#ifdef USE_UNIX_SOCKETS STRING_UNIX_SOCKET_PATH, /* path to Unix socket, if used */ +#endif STRING_TARGET, /* CURLOPT_REQUEST_TARGET */ +#ifndef CURL_DISABLE_DOH STRING_DOH, /* CURLOPT_DOH_URL */ +#endif +#ifndef CURL_DISABLE_ALTSVC STRING_ALTSVC, /* CURLOPT_ALTSVC */ +#endif +#ifndef CURL_DISABLE_HSTS STRING_HSTS, /* CURLOPT_HSTS */ +#endif STRING_SASL_AUTHZID, /* CURLOPT_SASL_AUTHZID */ +#ifdef USE_ARES STRING_DNS_SERVERS, STRING_DNS_INTERFACE, STRING_DNS_LOCAL_IP4, STRING_DNS_LOCAL_IP6, +#endif STRING_SSL_EC_CURVES, +#ifndef CURL_DISABLE_AWS STRING_AWS_SIGV4, /* Parameters for V4 signature */ +#endif +#ifndef CURL_DISABLE_PROXY STRING_HAPROXY_CLIENT_IP, /* CURLOPT_HAPROXY_CLIENT_IP */ +#endif + STRING_ECH_CONFIG, /* CURLOPT_ECH_CONFIG */ + STRING_ECH_PUBLIC, /* CURLOPT_ECH_PUBLIC */ /* -- end of null-terminated strings -- */ @@ -1510,13 +1562,15 @@ enum dupstring { enum dupblob { BLOB_CERT, - BLOB_CERT_PROXY, BLOB_KEY, - BLOB_KEY_PROXY, BLOB_SSL_ISSUERCERT, - BLOB_SSL_ISSUERCERT_PROXY, BLOB_CAINFO, +#ifndef CURL_DISABLE_PROXY + BLOB_CERT_PROXY, + BLOB_KEY_PROXY, + BLOB_SSL_ISSUERCERT_PROXY, BLOB_CAINFO_PROXY, +#endif BLOB_LAST }; @@ -1667,7 +1721,7 @@ struct UserDefined { unsigned int new_file_perms; /* when creating remote files */ char *str[STRING_LAST]; /* array of strings, pointing to allocated memory */ struct curl_blob *blobs[BLOB_LAST]; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 unsigned int scope_id; /* Scope id for IPv6 */ #endif curl_prot_t allowed_protocols; @@ -1735,7 +1789,9 @@ struct UserDefined { BIT(cookiesession); /* new cookie session? */ #endif BIT(crlf); /* convert crlf on ftp upload(?) */ +#ifdef USE_SSH BIT(ssh_compression); /* enable SSH compression */ +#endif /* Here follows boolean settings that define how to behave during this session. They are STATIC, set by libcurl users or at least initially @@ -1745,7 +1801,9 @@ struct UserDefined { don't want lengthy cleanups to delay termination, e.g. after a DNS timeout */ BIT(get_filetime); /* get the time and get of the remote file */ +#ifndef CURL_DISABLE_PROXY BIT(tunnel_thru_httpproxy); /* use CONNECT through an HTTP proxy */ +#endif BIT(prefer_ascii); /* ASCII rather than binary */ BIT(remote_append); /* append, not overwrite, on upload */ #ifdef CURL_LIST_ONLY_PROTOCOL @@ -1772,7 +1830,9 @@ struct UserDefined { location: */ BIT(opt_no_body); /* as set with CURLOPT_NOBODY */ BIT(verbose); /* output verbosity */ +#if defined(HAVE_GSSAPI) BIT(krb); /* Kerberos connection requested */ +#endif BIT(reuse_forbid); /* forbidden to be reused, close after use */ BIT(reuse_fresh); /* do not reuse an existing connection */ BIT(no_signal); /* do not use any signal/alarm handler */ @@ -1797,9 +1857,13 @@ struct UserDefined { BIT(suppress_connect_headers); /* suppress proxy CONNECT response headers from user callbacks */ BIT(dns_shuffle_addresses); /* whether to shuffle addresses before use */ +#ifndef CURL_DISABLE_PROXY BIT(haproxyprotocol); /* whether to send HAProxy PROXY protocol v1 header */ +#endif +#ifdef USE_UNIX_SOCKETS BIT(abstract_unix_socket); +#endif BIT(disallow_username_in_url); /* disallow username in url */ #ifndef CURL_DISABLE_DOH BIT(doh); /* DNS-over-HTTPS enabled */ @@ -1811,6 +1875,9 @@ struct UserDefined { #ifdef USE_WEBSOCKETS BIT(ws_raw_mode); #endif +#ifdef USE_ECH + int tls_ech; /* TLS ECH configuration */ +#endif }; #ifndef CURL_DISABLE_MIME diff --git a/lib/vauth/cleartext.c b/lib/vauth/cleartext.c index 972a874..29389c2 100644 --- a/lib/vauth/cleartext.c +++ b/lib/vauth/cleartext.c @@ -107,12 +107,11 @@ CURLcode Curl_auth_create_plain_message(const char *authzid, * valuep [in] - The user name or user's password. * out [out] - The result storage. * - * Returns CURLE_OK on success. + * Returns void. */ -CURLcode Curl_auth_create_login_message(const char *valuep, struct bufref *out) +void Curl_auth_create_login_message(const char *valuep, struct bufref *out) { Curl_bufref_set(out, valuep, strlen(valuep), NULL); - return CURLE_OK; } /* @@ -126,13 +125,13 @@ CURLcode Curl_auth_create_login_message(const char *valuep, struct bufref *out) * user [in] - The user name. * out [out] - The result storage. * - * Returns CURLE_OK on success. + * Returns void. */ -CURLcode Curl_auth_create_external_message(const char *user, +void Curl_auth_create_external_message(const char *user, struct bufref *out) { /* This is the same formatting as the login message */ - return Curl_auth_create_login_message(user, out); + Curl_auth_create_login_message(user, out); } #endif /* if no users */ diff --git a/lib/vauth/digest.c b/lib/vauth/digest.c index 358bfb6..a742cce 100644 --- a/lib/vauth/digest.c +++ b/lib/vauth/digest.c @@ -288,7 +288,7 @@ static CURLcode auth_decode_digest_md5_message(const struct bufref *chlgref, /* Retrieve realm string from the challenge */ if(!auth_digest_get_key_value(chlg, "realm=\"", realm, rlen, '\"')) { /* Challenge does not have a realm, set empty string [RFC2831] page 6 */ - strcpy(realm, ""); + *realm = '\0'; } /* Retrieve algorithm string from the challenge */ diff --git a/lib/vauth/vauth.h b/lib/vauth/vauth.h index 9da0540..7e82348 100644 --- a/lib/vauth/vauth.h +++ b/lib/vauth/vauth.h @@ -79,12 +79,10 @@ CURLcode Curl_auth_create_plain_message(const char *authzid, struct bufref *out); /* This is used to generate a LOGIN cleartext message */ -CURLcode Curl_auth_create_login_message(const char *value, - struct bufref *out); +void Curl_auth_create_login_message(const char *value, struct bufref *out); /* This is used to generate an EXTERNAL cleartext message */ -CURLcode Curl_auth_create_external_message(const char *user, - struct bufref *out); +void Curl_auth_create_external_message(const char *user, struct bufref *out); #ifndef CURL_DISABLE_DIGEST_AUTH /* This is used to generate a CRAM-MD5 response message */ diff --git a/lib/version.c b/lib/version.c index 88f3696..a5c8c4e 100644 --- a/lib/version.c +++ b/lib/version.c @@ -55,6 +55,7 @@ #ifdef USE_LIBRTMP #include <librtmp/rtmp.h> +#include "curl_rtmp.h" #endif #ifdef HAVE_LIBZ @@ -152,7 +153,7 @@ char *curl_version(void) #ifdef USE_NGHTTP2 char h2_version[40]; #endif -#ifdef ENABLE_QUIC +#ifdef USE_HTTP3 char h3_version[40]; #endif #ifdef USE_LIBRTMP @@ -174,8 +175,7 @@ char *curl_version(void) /* Override version string when environment variable CURL_VERSION is set */ const char *debugversion = getenv("CURL_VERSION"); if(debugversion) { - strncpy(out, debugversion, sizeof(out)-1); - out[sizeof(out)-1] = '\0'; + msnprintf(out, sizeof(out), "%s", debugversion); return out; } #endif @@ -208,6 +208,8 @@ char *curl_version(void) src[i++] = idn_version; #elif defined(USE_WIN32_IDN) src[i++] = (char *)"WinIDN"; +#elif defined(USE_APPLE_IDN) + src[i++] = (char *)"AppleIDN"; #endif #ifdef USE_LIBPSL @@ -233,25 +235,13 @@ char *curl_version(void) Curl_http2_ver(h2_version, sizeof(h2_version)); src[i++] = h2_version; #endif -#ifdef ENABLE_QUIC +#ifdef USE_HTTP3 Curl_quic_ver(h3_version, sizeof(h3_version)); src[i++] = h3_version; #endif #ifdef USE_LIBRTMP - { - char suff[2]; - if(RTMP_LIB_VERSION & 0xff) { - suff[0] = (RTMP_LIB_VERSION & 0xff) + 'a' - 1; - suff[1] = '\0'; - } - else - suff[0] = '\0'; - - msnprintf(rtmp_version, sizeof(rtmp_version), "librtmp/%d.%d%s", - RTMP_LIB_VERSION >> 16, (RTMP_LIB_VERSION >> 8) & 0xff, - suff); - src[i++] = rtmp_version; - } + Curl_rtmp_version(rtmp_version, sizeof(rtmp_version)); + src[i++] = rtmp_version; #endif #ifdef USE_HYPER msnprintf(hyper_buf, sizeof(hyper_buf), "Hyper/%s", hyper_version()); @@ -428,6 +418,14 @@ static int https_proxy_present(curl_version_info_data *info) } #endif +#if defined(USE_SSL) && defined(USE_ECH) +static int ech_present(curl_version_info_data *info) +{ + (void) info; + return Curl_ssl_supports(NULL, SSLSUPP_ECH); +} +#endif + /* * Features table. * @@ -456,6 +454,9 @@ static const struct feat features_table[] = { #ifdef DEBUGBUILD FEATURE("Debug", NULL, CURL_VERSION_DEBUG), #endif +#if defined(USE_SSL) && defined(USE_ECH) + FEATURE("ECH", ech_present, 0), +#endif #ifdef USE_GSASL FEATURE("gsasl", NULL, CURL_VERSION_GSASL), #endif @@ -468,17 +469,17 @@ static const struct feat features_table[] = { #if defined(USE_NGHTTP2) FEATURE("HTTP2", NULL, CURL_VERSION_HTTP2), #endif -#if defined(ENABLE_QUIC) +#if defined(USE_HTTP3) FEATURE("HTTP3", NULL, CURL_VERSION_HTTP3), #endif #if defined(USE_SSL) && !defined(CURL_DISABLE_PROXY) && \ !defined(CURL_DISABLE_HTTP) FEATURE("HTTPS-proxy", https_proxy_present, CURL_VERSION_HTTPS_PROXY), #endif -#if defined(USE_LIBIDN2) || defined(USE_WIN32_IDN) +#if defined(USE_LIBIDN2) || defined(USE_WIN32_IDN) || defined(USE_APPLE_IDN) FEATURE("IDN", idn_present, CURL_VERSION_IDN), #endif -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 FEATURE("IPv6", NULL, CURL_VERSION_IPV6), #endif #ifdef USE_KERBEROS5 @@ -497,10 +498,6 @@ static const struct feat features_table[] = { #ifdef USE_NTLM FEATURE("NTLM", NULL, CURL_VERSION_NTLM), #endif -#if !defined(CURL_DISABLE_HTTP) && defined(USE_NTLM) && \ - defined(NTLM_WB_ENABLED) - FEATURE("NTLM_WB", NULL, CURL_VERSION_NTLM_WB), -#endif #if defined(USE_LIBPSL) FEATURE("PSL", NULL, CURL_VERSION_PSL), #endif @@ -572,7 +569,8 @@ static curl_version_info_data version_info = { NULL, /* zstd version */ NULL, /* Hyper version */ NULL, /* gsasl version */ - feature_names + feature_names, + NULL /* rtmp version */ }; curl_version_info_data *curl_version_info(CURLversion stamp) @@ -647,7 +645,7 @@ curl_version_info_data *curl_version_info(CURLversion stamp) } #endif -#ifdef ENABLE_QUIC +#ifdef USE_HTTP3 { static char quicbuffer[80]; Curl_quic_ver(quicbuffer, sizeof(quicbuffer)); @@ -680,5 +678,13 @@ curl_version_info_data *curl_version_info(CURLversion stamp) feature_names[n] = NULL; /* Terminate array. */ version_info.features = features; +#ifdef USE_LIBRTMP + { + static char rtmp_version[30]; + Curl_rtmp_version(rtmp_version, sizeof(rtmp_version)); + version_info.rtmp_version = rtmp_version; + } +#endif + return &version_info; } diff --git a/lib/vquic/curl_msh3.c b/lib/vquic/curl_msh3.c index a52bbdd..d49af6e 100644 --- a/lib/vquic/curl_msh3.c +++ b/lib/vquic/curl_msh3.c @@ -27,6 +27,7 @@ #ifdef USE_MSH3 #include "urldata.h" +#include "hash.h" #include "timeval.h" #include "multiif.h" #include "sendf.h" @@ -118,6 +119,7 @@ struct cf_msh3_ctx { struct cf_call_data call_data; struct curltime connect_started; /* time the current attempt started */ struct curltime handshake_at; /* time connect handshake finished */ + struct Curl_hash streams; /* hash `data->id` to `stream_ctx` */ /* Flags written by msh3/msquic thread */ bool handshake_complete; bool handshake_succeeded; @@ -127,6 +129,8 @@ struct cf_msh3_ctx { BIT(active); }; +static struct cf_msh3_ctx *h3_get_msh3_ctx(struct Curl_easy *data); + /* How to access `call_data` from a cf_msh3 filter */ #undef CF_CTX_CALL_DATA #define CF_CTX_CALL_DATA(cf) \ @@ -153,18 +157,26 @@ struct stream_ctx { bool recv_header_complete; }; -#define H3_STREAM_CTX(d) ((struct stream_ctx *)(((d) && (d)->req.p.http)? \ - ((struct HTTP *)(d)->req.p.http)->h3_ctx \ - : NULL)) -#define H3_STREAM_LCTX(d) ((struct HTTP *)(d)->req.p.http)->h3_ctx -#define H3_STREAM_ID(d) (H3_STREAM_CTX(d)? \ - H3_STREAM_CTX(d)->id : -2) +#define H3_STREAM_CTX(ctx,data) ((struct stream_ctx *)((data && ctx)? \ + Curl_hash_offt_get(&(ctx)->streams, (data)->id) : NULL)) + +static void h3_stream_ctx_free(struct stream_ctx *stream) +{ + Curl_bufq_free(&stream->recvbuf); + free(stream); +} +static void h3_stream_hash_free(void *stream) +{ + DEBUGASSERT(stream); + h3_stream_ctx_free((struct stream_ctx *)stream); +} static CURLcode h3_data_setup(struct Curl_cfilter *cf, struct Curl_easy *data) { - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_msh3_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); if(stream) return CURLE_OK; @@ -173,25 +185,29 @@ static CURLcode h3_data_setup(struct Curl_cfilter *cf, if(!stream) return CURLE_OUT_OF_MEMORY; - H3_STREAM_LCTX(data) = stream; stream->req = ZERO_NULL; msh3_lock_initialize(&stream->recv_lock); Curl_bufq_init2(&stream->recvbuf, H3_STREAM_CHUNK_SIZE, H3_STREAM_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT); CURL_TRC_CF(data, cf, "data setup"); + + if(!Curl_hash_offt_set(&ctx->streams, data->id, stream)) { + h3_stream_ctx_free(stream); + return CURLE_OUT_OF_MEMORY; + } + return CURLE_OK; } static void h3_data_done(struct Curl_cfilter *cf, struct Curl_easy *data) { - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_msh3_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); (void)cf; if(stream) { CURL_TRC_CF(data, cf, "easy handle is done"); - Curl_bufq_free(&stream->recvbuf); - free(stream); - H3_STREAM_LCTX(data) = NULL; + Curl_hash_offt_remove(&ctx->streams, data->id); } } @@ -213,7 +229,8 @@ static void drain_stream_from_other_thread(struct Curl_easy *data, static void drain_stream(struct Curl_cfilter *cf, struct Curl_easy *data) { - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_msh3_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); unsigned char bits; (void)cf; @@ -311,7 +328,8 @@ static int decode_status_code(const char *value, size_t len) static CURLcode write_resp_raw(struct Curl_easy *data, const void *mem, size_t memlen) { - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_msh3_ctx *ctx = h3_get_msh3_ctx(data); + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); CURLcode result = CURLE_OK; ssize_t nwritten; @@ -337,10 +355,12 @@ static void MSH3_CALL msh3_header_received(MSH3_REQUEST *Request, const MSH3_HEADER *hd) { struct Curl_easy *data = userp; - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_msh3_ctx *ctx = h3_get_msh3_ctx(data); + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); CURLcode result; (void)Request; + DEBUGF(infof(data, "[MSH3] header received, stream=%d", !!stream)); if(!stream || stream->recv_header_complete) { return; } @@ -386,7 +406,8 @@ static bool MSH3_CALL msh3_data_received(MSH3_REQUEST *Request, const uint8_t *buf) { struct Curl_easy *data = IfContext; - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_msh3_ctx *ctx = h3_get_msh3_ctx(data); + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); CURLcode result; bool rv = FALSE; @@ -425,7 +446,8 @@ static void MSH3_CALL msh3_complete(MSH3_REQUEST *Request, void *IfContext, bool aborted, uint64_t error) { struct Curl_easy *data = IfContext; - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_msh3_ctx *ctx = h3_get_msh3_ctx(data); + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); (void)Request; if(!stream) @@ -444,7 +466,8 @@ static void MSH3_CALL msh3_shutdown_complete(MSH3_REQUEST *Request, void *IfContext) { struct Curl_easy *data = IfContext; - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_msh3_ctx *ctx = h3_get_msh3_ctx(data); + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); if(!stream) return; @@ -456,7 +479,8 @@ static void MSH3_CALL msh3_data_sent(MSH3_REQUEST *Request, void *IfContext, void *SendContext) { struct Curl_easy *data = IfContext; - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_msh3_ctx *ctx = h3_get_msh3_ctx(data); + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); if(!stream) return; (void)Request; @@ -468,7 +492,8 @@ static ssize_t recv_closed_stream(struct Curl_cfilter *cf, struct Curl_easy *data, CURLcode *err) { - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_msh3_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); ssize_t nread = -1; if(!stream) { @@ -501,7 +526,8 @@ out: static void set_quic_expire(struct Curl_cfilter *cf, struct Curl_easy *data) { - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_msh3_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); /* we have no indication from msh3 when it would be a good time * to juggle the connection again. So, we compromise by calling @@ -518,17 +544,17 @@ static void set_quic_expire(struct Curl_cfilter *cf, struct Curl_easy *data) static ssize_t cf_msh3_recv(struct Curl_cfilter *cf, struct Curl_easy *data, char *buf, size_t len, CURLcode *err) { - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_msh3_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); ssize_t nread = -1; struct cf_call_data save; - (void)cf; + CURL_TRC_CF(data, cf, "cf_recv(len=%zu), stream=%d", len, !!stream); if(!stream) { *err = CURLE_RECV_ERROR; return -1; } CF_DATA_SAVE(save, cf, data); - CURL_TRC_CF(data, cf, "req: recv with %zu byte buffer", len); msh3_lock_acquire(&stream->recv_lock); @@ -570,7 +596,7 @@ static ssize_t cf_msh3_send(struct Curl_cfilter *cf, struct Curl_easy *data, const void *buf, size_t len, CURLcode *err) { struct cf_msh3_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); struct h1_req_parser h1; struct dynhds h2_headers; MSH3_HEADER *nva = NULL; @@ -682,7 +708,7 @@ static void cf_msh3_adjust_pollset(struct Curl_cfilter *cf, struct easy_pollset *ps) { struct cf_msh3_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); struct cf_call_data save; CF_DATA_SAVE(save, cf, data); @@ -701,7 +727,8 @@ static void cf_msh3_adjust_pollset(struct Curl_cfilter *cf, static bool cf_msh3_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data) { - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_msh3_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); struct cf_call_data save; bool pending = FALSE; @@ -737,7 +764,8 @@ static CURLcode cf_msh3_data_event(struct Curl_cfilter *cf, struct Curl_easy *data, int event, int arg1, void *arg2) { - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_msh3_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); struct cf_call_data save; CURLcode result = CURLE_OK; @@ -785,6 +813,7 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, CURLcode result; bool verify; + Curl_hash_offt_init(&ctx->streams, 63, h3_stream_hash_free); conn_config = Curl_ssl_cf_get_primary_config(cf); if(!conn_config) return CURLE_FAILED_INIT; @@ -911,6 +940,7 @@ static void cf_msh3_close(struct Curl_cfilter *cf, struct Curl_easy *data) MsH3ApiClose(ctx->api); ctx->api = NULL; } + Curl_hash_destroy(&ctx->streams); if(ctx->active) { /* We share our socket at cf->conn->sock[cf->sockindex] when active. @@ -1019,6 +1049,20 @@ struct Curl_cftype Curl_cft_http3 = { cf_msh3_query, }; +static struct cf_msh3_ctx *h3_get_msh3_ctx(struct Curl_easy *data) +{ + if(data && data->conn) { + struct Curl_cfilter *cf = data->conn->cfilter[FIRSTSOCKET]; + while(cf) { + if(cf->cft == &Curl_cft_http3) + return cf->ctx; + cf = cf->next; + } + } + DEBUGF(infof(data, "no filter context found")); + return NULL; +} + CURLcode Curl_cf_msh3_create(struct Curl_cfilter **pcf, struct Curl_easy *data, struct connectdata *conn, diff --git a/lib/vquic/curl_ngtcp2.c b/lib/vquic/curl_ngtcp2.c index 6b6b887..0d9d87f 100644 --- a/lib/vquic/curl_ngtcp2.c +++ b/lib/vquic/curl_ngtcp2.c @@ -44,6 +44,7 @@ #endif #include "urldata.h" +#include "hash.h" #include "sendf.h" #include "strdup.h" #include "rand.h" @@ -113,7 +114,7 @@ void Curl_ngtcp2_ver(char *p, size_t len) struct cf_ngtcp2_ctx { struct cf_quic_ctx q; struct ssl_peer peer; - struct quic_tls_ctx tls; + struct curl_tls_ctx tls; ngtcp2_path connected_path; ngtcp2_conn *qconn; ngtcp2_cid dcid; @@ -130,9 +131,14 @@ struct cf_ngtcp2_ctx { struct curltime handshake_at; /* time connect handshake finished */ struct curltime reconnect_at; /* time the next attempt should start */ struct bufc_pool stream_bufcp; /* chunk pool for streams */ + struct dynbuf scratch; /* temp buffer for header construction */ + struct Curl_hash streams; /* hash `data->id` to `h3_stream_ctx` */ size_t max_stream_window; /* max flow window for one stream */ uint64_t max_idle_ms; /* max idle time for QUIC connection */ + uint64_t used_bidi_streams; /* bidi streams we have opened */ + uint64_t max_bidi_streams; /* max bidi streams we can open */ int qlogfd; + BIT(conn_closed); /* connection is closed */ }; /* How to access `call_data` from a cf_ngtcp2 filter */ @@ -140,18 +146,27 @@ struct cf_ngtcp2_ctx { #define CF_CTX_CALL_DATA(cf) \ ((struct cf_ngtcp2_ctx *)(cf)->ctx)->call_data +struct pkt_io_ctx; +static CURLcode cf_progress_ingress(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct pkt_io_ctx *pktx); +static CURLcode cf_progress_egress(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct pkt_io_ctx *pktx); + /** * All about the H3 internals of a stream */ struct h3_stream_ctx { - int64_t id; /* HTTP/3 protocol identifier */ + curl_int64_t id; /* HTTP/3 protocol identifier */ struct bufq sendbuf; /* h3 request body */ struct h1_req_parser h1; /* h1 request parsing */ size_t sendbuf_len_in_flight; /* sendbuf amount "in flight" */ size_t upload_blocked_len; /* the amount written last and EGAINed */ - uint64_t error3; /* HTTP/3 stream error code */ + curl_uint64_t error3; /* HTTP/3 stream error code */ curl_off_t upload_left; /* number of request bytes left to upload */ int status_code; /* HTTP status code */ + CURLcode xfer_result; /* result from xfer_resp_write(_hd) */ bool resp_hds_complete; /* we have a complete, final response */ bool closed; /* TRUE on stream close */ bool reset; /* TRUE on stream reset */ @@ -159,18 +174,29 @@ struct h3_stream_ctx { BIT(quic_flow_blocked); /* stream is blocked by QUIC flow control */ }; -#define H3_STREAM_CTX(d) ((struct h3_stream_ctx *)(((d) && (d)->req.p.http)? \ - ((struct HTTP *)(d)->req.p.http)->h3_ctx \ - : NULL)) -#define H3_STREAM_LCTX(d) ((struct HTTP *)(d)->req.p.http)->h3_ctx -#define H3_STREAM_ID(d) (H3_STREAM_CTX(d)? \ - H3_STREAM_CTX(d)->id : -2) +#define H3_STREAM_CTX(ctx,data) ((struct h3_stream_ctx *)(\ + data? Curl_hash_offt_get(&(ctx)->streams, (data)->id) : NULL)) +#define H3_STREAM_CTX_ID(ctx,id) ((struct h3_stream_ctx *)(\ + Curl_hash_offt_get(&(ctx)->streams, (id)))) + +static void h3_stream_ctx_free(struct h3_stream_ctx *stream) +{ + Curl_bufq_free(&stream->sendbuf); + Curl_h1_req_parse_free(&stream->h1); + free(stream); +} + +static void h3_stream_hash_free(void *stream) +{ + DEBUGASSERT(stream); + h3_stream_ctx_free((struct h3_stream_ctx *)stream); +} static CURLcode h3_data_setup(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); if(!data || !data->req.p.http) { failf(data, "initialization failure, transfer not http initialized"); @@ -191,59 +217,85 @@ static CURLcode h3_data_setup(struct Curl_cfilter *cf, stream->sendbuf_len_in_flight = 0; Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN); - H3_STREAM_LCTX(data) = stream; + if(!Curl_hash_offt_set(&ctx->streams, data->id, stream)) { + h3_stream_ctx_free(stream); + return CURLE_OUT_OF_MEMORY; + } + return CURLE_OK; } -static void h3_data_done(struct Curl_cfilter *cf, struct Curl_easy *data) +static void cf_ngtcp2_stream_close(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h3_stream_ctx *stream) { struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + DEBUGASSERT(data); + DEBUGASSERT(stream); + if(!stream->closed && ctx->qconn && ctx->h3conn) { + CURLcode result; + + nghttp3_conn_set_stream_user_data(ctx->h3conn, stream->id, NULL); + ngtcp2_conn_set_stream_user_data(ctx->qconn, stream->id, NULL); + stream->closed = TRUE; + (void)ngtcp2_conn_shutdown_stream(ctx->qconn, 0, stream->id, + NGHTTP3_H3_REQUEST_CANCELLED); + result = cf_progress_egress(cf, data, NULL); + if(result) + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] cancel stream -> %d", + stream->id, result); + } +} +static void h3_data_done(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); (void)cf; if(stream) { - CURL_TRC_CF(data, cf, "[%"PRId64"] easy handle is done", stream->id); - if(ctx->h3conn && !stream->closed) { - nghttp3_conn_shutdown_stream_read(ctx->h3conn, stream->id); - nghttp3_conn_close_stream(ctx->h3conn, stream->id, - NGHTTP3_H3_REQUEST_CANCELLED); - nghttp3_conn_set_stream_user_data(ctx->h3conn, stream->id, NULL); - ngtcp2_conn_set_stream_user_data(ctx->qconn, stream->id, NULL); - stream->closed = TRUE; - } - - Curl_bufq_free(&stream->sendbuf); - Curl_h1_req_parse_free(&stream->h1); - free(stream); - H3_STREAM_LCTX(data) = NULL; + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] easy handle is done", + stream->id); + cf_ngtcp2_stream_close(cf, data, stream); + Curl_hash_offt_remove(&ctx->streams, data->id); } } static struct Curl_easy *get_stream_easy(struct Curl_cfilter *cf, struct Curl_easy *data, - int64_t stream_id) + int64_t stream_id, + struct h3_stream_ctx **pstream) { + struct cf_ngtcp2_ctx *ctx = cf->ctx; struct Curl_easy *sdata; + struct h3_stream_ctx *stream; (void)cf; - if(H3_STREAM_ID(data) == stream_id) { + stream = H3_STREAM_CTX(ctx, data); + if(stream && stream->id == stream_id) { + *pstream = stream; return data; } else { DEBUGASSERT(data->multi); for(sdata = data->multi->easyp; sdata; sdata = sdata->next) { - if((sdata->conn == data->conn) && H3_STREAM_ID(sdata) == stream_id) { + if(sdata->conn != data->conn) + continue; + stream = H3_STREAM_CTX(ctx, sdata); + if(stream && stream->id == stream_id) { + *pstream = stream; return sdata; } } } + *pstream = NULL; return NULL; } static void h3_drain_stream(struct Curl_cfilter *cf, struct Curl_easy *data) { - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); unsigned char bits; (void)cf; @@ -289,12 +341,6 @@ static void pktx_init(struct pkt_io_ctx *pktx, pktx_update_time(pktx, cf); } -static CURLcode cf_progress_ingress(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct pkt_io_ctx *pktx); -static CURLcode cf_progress_egress(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct pkt_io_ctx *pktx); static int cb_h3_acked_req_body(nghttp3_conn *conn, int64_t stream_id, uint64_t datalen, void *user_data, void *stream_user_data); @@ -380,13 +426,60 @@ static int cb_handshake_completed(ngtcp2_conn *tconn, void *user_data) return 0; } +static void cf_ngtcp2_conn_close(struct Curl_cfilter *cf, + struct Curl_easy *data); + +static bool cf_ngtcp2_err_is_fatal(int code) +{ + return (NGTCP2_ERR_FATAL >= code) || + (NGTCP2_ERR_DROP_CONN == code) || + (NGTCP2_ERR_IDLE_CLOSE == code); +} + +static void cf_ngtcp2_err_set(struct Curl_cfilter *cf, + struct Curl_easy *data, int code) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + if(!ctx->last_error.error_code) { + if(NGTCP2_ERR_CRYPTO == code) { + ngtcp2_ccerr_set_tls_alert(&ctx->last_error, + ngtcp2_conn_get_tls_alert(ctx->qconn), + NULL, 0); + } + else { + ngtcp2_ccerr_set_liberr(&ctx->last_error, code, NULL, 0); + } + } + if(cf_ngtcp2_err_is_fatal(code)) + cf_ngtcp2_conn_close(cf, data); +} + +static bool cf_ngtcp2_h3_err_is_fatal(int code) +{ + return (NGHTTP3_ERR_FATAL >= code) || + (NGHTTP3_ERR_H3_CLOSED_CRITICAL_STREAM == code); +} + +static void cf_ngtcp2_h3_err_set(struct Curl_cfilter *cf, + struct Curl_easy *data, int code) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + if(!ctx->last_error.error_code) { + ngtcp2_ccerr_set_application_error(&ctx->last_error, + nghttp3_err_infer_quic_app_error_code(code), NULL, 0); + } + if(cf_ngtcp2_h3_err_is_fatal(code)) + cf_ngtcp2_conn_close(cf, data); +} + static int cb_recv_stream_data(ngtcp2_conn *tconn, uint32_t flags, - int64_t stream_id, uint64_t offset, + int64_t sid, uint64_t offset, const uint8_t *buf, size_t buflen, void *user_data, void *stream_user_data) { struct Curl_cfilter *cf = user_data; struct cf_ngtcp2_ctx *ctx = cf->ctx; + curl_int64_t stream_id = (curl_int64_t)sid; nghttp3_ssize nconsumed; int fin = (flags & NGTCP2_STREAM_DATA_FLAG_FIN) ? 1 : 0; struct Curl_easy *data = stream_user_data; @@ -395,18 +488,18 @@ static int cb_recv_stream_data(ngtcp2_conn *tconn, uint32_t flags, nconsumed = nghttp3_conn_read_stream(ctx->h3conn, stream_id, buf, buflen, fin); - CURL_TRC_CF(data, cf, "[%" PRId64 "] read_stream(len=%zu) -> %zd", - stream_id, buflen, nconsumed); + if(!data) + data = CF_DATA_CURRENT(cf); + if(data) + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] read_stream(len=%zu) -> %zd", + stream_id, buflen, nconsumed); if(nconsumed < 0) { - if(!data) { - struct Curl_easy *cdata = CF_DATA_CURRENT(cf); - CURL_TRC_CF(cdata, cf, "[%" PRId64 "] nghttp3 error on stream not " - "used by us, ignored", stream_id); - return 0; + struct h3_stream_ctx *stream = H3_STREAM_CTX_ID(ctx, stream_id); + if(data && stream) { + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] error on known stream, " + "reset=%d, closed=%d", + stream_id, stream->reset, stream->closed); } - ngtcp2_ccerr_set_application_error( - &ctx->last_error, - nghttp3_err_infer_quic_app_error_code((int)nconsumed), NULL, 0); return NGTCP2_ERR_CALLBACK_FAILURE; } @@ -442,41 +535,45 @@ cb_acked_stream_data_offset(ngtcp2_conn *tconn, int64_t stream_id, } static int cb_stream_close(ngtcp2_conn *tconn, uint32_t flags, - int64_t stream3_id, uint64_t app_error_code, + int64_t sid, uint64_t app_error_code, void *user_data, void *stream_user_data) { struct Curl_cfilter *cf = user_data; - struct Curl_easy *data = stream_user_data; struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct Curl_easy *data = stream_user_data; + curl_int64_t stream_id = (curl_int64_t)sid; int rv; (void)tconn; - (void)data; /* stream is closed... */ + if(!data) + data = CF_DATA_CURRENT(cf); + if(!data) + return NGTCP2_ERR_CALLBACK_FAILURE; if(!(flags & NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET)) { app_error_code = NGHTTP3_H3_NO_ERROR; } - rv = nghttp3_conn_close_stream(ctx->h3conn, stream3_id, - app_error_code); - CURL_TRC_CF(data, cf, "[%" PRId64 "] quic close(err=%" - PRIu64 ") -> %d", stream3_id, app_error_code, rv); + rv = nghttp3_conn_close_stream(ctx->h3conn, stream_id, app_error_code); + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] quic close(app_error=%" + CURL_PRIu64 ") -> %d", stream_id, (curl_uint64_t)app_error_code, + rv); if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { - ngtcp2_ccerr_set_application_error( - &ctx->last_error, nghttp3_err_infer_quic_app_error_code(rv), NULL, 0); + cf_ngtcp2_h3_err_set(cf, data, rv); return NGTCP2_ERR_CALLBACK_FAILURE; } return 0; } -static int cb_stream_reset(ngtcp2_conn *tconn, int64_t stream_id, +static int cb_stream_reset(ngtcp2_conn *tconn, int64_t sid, uint64_t final_size, uint64_t app_error_code, void *user_data, void *stream_user_data) { struct Curl_cfilter *cf = user_data; struct cf_ngtcp2_ctx *ctx = cf->ctx; + curl_int64_t stream_id = (curl_int64_t)sid; struct Curl_easy *data = stream_user_data; int rv; (void)tconn; @@ -485,7 +582,7 @@ static int cb_stream_reset(ngtcp2_conn *tconn, int64_t stream_id, (void)data; rv = nghttp3_conn_shutdown_stream_read(ctx->h3conn, stream_id); - CURL_TRC_CF(data, cf, "[%" PRId64 "] reset -> %d", stream_id, rv); + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] reset -> %d", stream_id, rv); if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { return NGTCP2_ERR_CALLBACK_FAILURE; } @@ -516,19 +613,26 @@ static int cb_extend_max_local_streams_bidi(ngtcp2_conn *tconn, uint64_t max_streams, void *user_data) { - (void)tconn; - (void)max_streams; - (void)user_data; + struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + (void)tconn; + ctx->max_bidi_streams = max_streams; + if(data) + CURL_TRC_CF(data, cf, "max bidi streams now %" CURL_PRIu64 + ", used %" CURL_PRIu64, (curl_uint64_t)ctx->max_bidi_streams, + (curl_uint64_t)ctx->used_bidi_streams); return 0; } -static int cb_extend_max_stream_data(ngtcp2_conn *tconn, int64_t stream_id, +static int cb_extend_max_stream_data(ngtcp2_conn *tconn, int64_t sid, uint64_t max_data, void *user_data, void *stream_user_data) { struct Curl_cfilter *cf = user_data; struct cf_ngtcp2_ctx *ctx = cf->ctx; + curl_int64_t stream_id = (curl_int64_t)sid; struct Curl_easy *data = CF_DATA_CURRENT(cf); struct Curl_easy *s_data; struct h3_stream_ctx *stream; @@ -541,12 +645,12 @@ static int cb_extend_max_stream_data(ngtcp2_conn *tconn, int64_t stream_id, if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { return NGTCP2_ERR_CALLBACK_FAILURE; } - s_data = get_stream_easy(cf, data, stream_id); - stream = H3_STREAM_CTX(s_data); - if(stream && stream->quic_flow_blocked) { - CURL_TRC_CF(data, cf, "[%" PRId64 "] unblock quic flow", stream_id); + s_data = get_stream_easy(cf, data, stream_id, &stream); + if(s_data && stream && stream->quic_flow_blocked) { + CURL_TRC_CF(s_data, cf, "[%" CURL_PRId64 "] unblock quic flow", + stream_id); stream->quic_flow_blocked = FALSE; - h3_drain_stream(cf, data); + h3_drain_stream(cf, s_data); } return 0; } @@ -676,7 +780,7 @@ static CURLcode check_and_set_expiry(struct Curl_cfilter *cf, if(rv) { failf(data, "ngtcp2_conn_handle_expiry returned error: %s", ngtcp2_strerror(rv)); - ngtcp2_ccerr_set_liberr(&ctx->last_error, rv, NULL, 0); + cf_ngtcp2_err_set(cf, data, rv); return CURLE_SEND_ERROR; } result = cf_progress_ingress(cf, data, pktx); @@ -712,7 +816,7 @@ static void cf_ngtcp2_adjust_pollset(struct Curl_cfilter *cf, Curl_pollset_check(data, ps, ctx->q.sockfd, &want_recv, &want_send); if(want_recv || want_send) { - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); struct cf_call_data save; bool c_exhaust, s_exhaust; @@ -730,13 +834,15 @@ static void cf_ngtcp2_adjust_pollset(struct Curl_cfilter *cf, } } -static int cb_h3_stream_close(nghttp3_conn *conn, int64_t stream_id, +static int cb_h3_stream_close(nghttp3_conn *conn, int64_t sid, uint64_t app_error_code, void *user_data, void *stream_user_data) { struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + curl_int64_t stream_id = (curl_int64_t)sid; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); (void)conn; (void)stream_id; @@ -745,24 +851,50 @@ static int cb_h3_stream_close(nghttp3_conn *conn, int64_t stream_id, return 0; stream->closed = TRUE; - stream->error3 = app_error_code; + stream->error3 = (curl_uint64_t)app_error_code; if(stream->error3 != NGHTTP3_H3_NO_ERROR) { stream->reset = TRUE; stream->send_closed = TRUE; - CURL_TRC_CF(data, cf, "[%" PRId64 "] RESET: error %" PRId64, + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] RESET: error %" CURL_PRIu64, stream->id, stream->error3); } else { - CURL_TRC_CF(data, cf, "[%" PRId64 "] CLOSED", stream->id); + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] CLOSED", stream->id); } h3_drain_stream(cf, data); return 0; } -static CURLcode write_resp_hds(struct Curl_easy *data, - const char *buf, size_t blen) +static void h3_xfer_write_resp_hd(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h3_stream_ctx *stream, + const char *buf, size_t blen, bool eos) { - return Curl_xfer_write_resp(data, (char *)buf, blen, FALSE); + + /* If we already encountered an error, skip further writes */ + if(!stream->xfer_result) { + stream->xfer_result = Curl_xfer_write_resp_hd(data, buf, blen, eos); + if(stream->xfer_result) + CURL_TRC_CF(data, cf, "[%"CURL_PRId64"] error %d writing %zu " + "bytes of headers", stream->id, stream->xfer_result, blen); + } +} + +static void h3_xfer_write_resp(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h3_stream_ctx *stream, + const char *buf, size_t blen, bool eos) +{ + + /* If we already encountered an error, skip further writes */ + if(!stream->xfer_result) { + stream->xfer_result = Curl_xfer_write_resp(data, buf, blen, eos); + /* If the transfer write is errored, we do not want any more data */ + if(stream->xfer_result) { + CURL_TRC_CF(data, cf, "[%"CURL_PRId64"] error %d writing %zu bytes " + "of data", stream->id, stream->xfer_result, blen); + } + } } static int cb_h3_recv_data(nghttp3_conn *conn, int64_t stream3_id, @@ -772,8 +904,7 @@ static int cb_h3_recv_data(nghttp3_conn *conn, int64_t stream3_id, struct Curl_cfilter *cf = user_data; struct cf_ngtcp2_ctx *ctx = cf->ctx; struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); - CURLcode result; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); (void)conn; (void)stream3_id; @@ -781,19 +912,14 @@ static int cb_h3_recv_data(nghttp3_conn *conn, int64_t stream3_id, if(!stream) return NGHTTP3_ERR_CALLBACK_FAILURE; - result = Curl_xfer_write_resp(data, (char *)buf, blen, FALSE); - if(result) { - CURL_TRC_CF(data, cf, "[%" PRId64 "] DATA len=%zu, ERROR receiving %d", - stream->id, blen, result); - return NGHTTP3_ERR_CALLBACK_FAILURE; - } + h3_xfer_write_resp(cf, data, stream, (char *)buf, blen, FALSE); if(blen) { - CURL_TRC_CF(data, cf, "[%" PRId64 "] ACK %zu bytes of DATA", + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] ACK %zu bytes of DATA", stream->id, blen); ngtcp2_conn_extend_max_stream_offset(ctx->qconn, stream->id, blen); ngtcp2_conn_extend_max_offset(ctx->qconn, blen); } - CURL_TRC_CF(data, cf, "[%" PRId64 "] DATA len=%zu", stream->id, blen); + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] DATA len=%zu", stream->id, blen); return 0; } @@ -813,13 +939,14 @@ static int cb_h3_deferred_consume(nghttp3_conn *conn, int64_t stream3_id, return 0; } -static int cb_h3_end_headers(nghttp3_conn *conn, int64_t stream_id, +static int cb_h3_end_headers(nghttp3_conn *conn, int64_t sid, int fin, void *user_data, void *stream_user_data) { struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); - CURLcode result = CURLE_OK; + curl_int64_t stream_id = (curl_int64_t)sid; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); (void)conn; (void)stream_id; (void)fin; @@ -828,12 +955,9 @@ static int cb_h3_end_headers(nghttp3_conn *conn, int64_t stream_id, if(!stream) return 0; /* add a CRLF only if we've received some headers */ - result = write_resp_hds(data, "\r\n", 2); - if(result) { - return -1; - } + h3_xfer_write_resp_hd(cf, data, stream, STRCONST("\r\n"), stream->closed); - CURL_TRC_CF(data, cf, "[%" PRId64 "] end_headers, status=%d", + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] end_headers, status=%d", stream_id, stream->status_code); if(stream->status_code / 100 != 1) { stream->resp_hds_complete = TRUE; @@ -842,16 +966,18 @@ static int cb_h3_end_headers(nghttp3_conn *conn, int64_t stream_id, return 0; } -static int cb_h3_recv_header(nghttp3_conn *conn, int64_t stream_id, +static int cb_h3_recv_header(nghttp3_conn *conn, int64_t sid, int32_t token, nghttp3_rcbuf *name, nghttp3_rcbuf *value, uint8_t flags, void *user_data, void *stream_user_data) { struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + curl_int64_t stream_id = (curl_int64_t)sid; nghttp3_vec h3name = nghttp3_rcbuf_get_buf(name); nghttp3_vec h3val = nghttp3_rcbuf_get_buf(value); struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); CURLcode result = CURLE_OK; (void)conn; (void)stream_id; @@ -864,42 +990,45 @@ static int cb_h3_recv_header(nghttp3_conn *conn, int64_t stream_id, return 0; if(token == NGHTTP3_QPACK_TOKEN__STATUS) { - char line[14]; /* status line is always 13 characters long */ - size_t ncopy; result = Curl_http_decode_status(&stream->status_code, (const char *)h3val.base, h3val.len); if(result) return -1; - ncopy = msnprintf(line, sizeof(line), "HTTP/3 %03d \r\n", - stream->status_code); - CURL_TRC_CF(data, cf, "[%" PRId64 "] status: %s", stream_id, line); - result = write_resp_hds(data, line, ncopy); + Curl_dyn_reset(&ctx->scratch); + result = Curl_dyn_addn(&ctx->scratch, STRCONST("HTTP/3 ")); + if(!result) + result = Curl_dyn_addn(&ctx->scratch, + (const char *)h3val.base, h3val.len); + if(!result) + result = Curl_dyn_addn(&ctx->scratch, STRCONST(" \r\n")); + if(!result) + h3_xfer_write_resp_hd(cf, data, stream, Curl_dyn_ptr(&ctx->scratch), + Curl_dyn_len(&ctx->scratch), FALSE); + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] status: %s", + stream_id, Curl_dyn_ptr(&ctx->scratch)); if(result) { return -1; } } else { /* store as an HTTP1-style header */ - CURL_TRC_CF(data, cf, "[%" PRId64 "] header: %.*s: %.*s", + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] header: %.*s: %.*s", stream_id, (int)h3name.len, h3name.base, (int)h3val.len, h3val.base); - result = write_resp_hds(data, (const char *)h3name.base, h3name.len); - if(result) { - return -1; - } - result = write_resp_hds(data, ": ", 2); - if(result) { - return -1; - } - result = write_resp_hds(data, (const char *)h3val.base, h3val.len); - if(result) { - return -1; - } - result = write_resp_hds(data, "\r\n", 2); - if(result) { - return -1; - } + Curl_dyn_reset(&ctx->scratch); + result = Curl_dyn_addn(&ctx->scratch, + (const char *)h3name.base, h3name.len); + if(!result) + result = Curl_dyn_addn(&ctx->scratch, STRCONST(": ")); + if(!result) + result = Curl_dyn_addn(&ctx->scratch, + (const char *)h3val.base, h3val.len); + if(!result) + result = Curl_dyn_addn(&ctx->scratch, STRCONST("\r\n")); + if(!result) + h3_xfer_write_resp_hd(cf, data, stream, Curl_dyn_ptr(&ctx->scratch), + Curl_dyn_len(&ctx->scratch), FALSE); } return 0; } @@ -923,11 +1052,12 @@ static int cb_h3_stop_sending(nghttp3_conn *conn, int64_t stream_id, return 0; } -static int cb_h3_reset_stream(nghttp3_conn *conn, int64_t stream_id, +static int cb_h3_reset_stream(nghttp3_conn *conn, int64_t sid, uint64_t app_error_code, void *user_data, void *stream_user_data) { struct Curl_cfilter *cf = user_data; struct cf_ngtcp2_ctx *ctx = cf->ctx; + curl_int64_t stream_id = (curl_int64_t)sid; struct Curl_easy *data = stream_user_data; int rv; (void)conn; @@ -935,7 +1065,7 @@ static int cb_h3_reset_stream(nghttp3_conn *conn, int64_t stream_id, rv = ngtcp2_conn_shutdown_stream_write(ctx->qconn, 0, stream_id, app_error_code); - CURL_TRC_CF(data, cf, "[%" PRId64 "] reset -> %d", stream_id, rv); + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] reset -> %d", stream_id, rv); if(rv && rv != NGTCP2_ERR_STREAM_NOT_FOUND) { return NGTCP2_ERR_CALLBACK_FAILURE; } @@ -1031,14 +1161,14 @@ static ssize_t recv_closed_stream(struct Curl_cfilter *cf, (void)cf; if(stream->reset) { failf(data, - "HTTP/3 stream %" PRId64 " reset by server", stream->id); + "HTTP/3 stream %" CURL_PRId64 " reset by server", stream->id); *err = data->req.bytecount? CURLE_PARTIAL_FILE : CURLE_HTTP3; goto out; } else if(!stream->resp_hds_complete) { failf(data, - "HTTP/3 stream %" PRId64 " was closed cleanly, but before getting" - " all response header fields, treated as error", + "HTTP/3 stream %" CURL_PRId64 " was closed cleanly, but before " + "getting all response header fields, treated as error", stream->id); *err = CURLE_HTTP3; goto out; @@ -1055,7 +1185,7 @@ static ssize_t cf_ngtcp2_recv(struct Curl_cfilter *cf, struct Curl_easy *data, char *buf, size_t blen, CURLcode *err) { struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); ssize_t nread = -1; struct cf_call_data save; struct pkt_io_ctx pktx; @@ -1072,7 +1202,7 @@ static ssize_t cf_ngtcp2_recv(struct Curl_cfilter *cf, struct Curl_easy *data, pktx_init(&pktx, cf, data); - if(!stream) { + if(!stream || ctx->conn_closed) { *err = CURLE_RECV_ERROR; goto out; } @@ -1083,7 +1213,14 @@ static ssize_t cf_ngtcp2_recv(struct Curl_cfilter *cf, struct Curl_easy *data, goto out; } - if(stream->closed) { + if(stream->xfer_result) { + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] xfer write failed", stream->id); + cf_ngtcp2_stream_close(cf, data, stream); + *err = stream->xfer_result; + nread = -1; + goto out; + } + else if(stream->closed) { nread = recv_closed_stream(cf, data, stream, err); goto out; } @@ -1102,7 +1239,7 @@ out: nread = -1; } } - CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_recv(blen=%zu) -> %zd, %d", + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] cf_recv(blen=%zu) -> %zd, %d", stream? stream->id : -1, blen, nread, *err); CF_DATA_RESTORE(cf, save); return nread; @@ -1113,8 +1250,9 @@ static int cb_h3_acked_req_body(nghttp3_conn *conn, int64_t stream_id, void *stream_user_data) { struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); size_t skiplen; (void)cf; @@ -1147,8 +1285,9 @@ cb_h3_read_req_body(nghttp3_conn *conn, int64_t stream_id, void *stream_user_data) { struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); ssize_t nwritten = 0; size_t nvecs = 0; (void)cf; @@ -1191,12 +1330,12 @@ cb_h3_read_req_body(nghttp3_conn *conn, int64_t stream_id, } else if(!nwritten) { /* Not EOF, and nothing to give, we signal WOULDBLOCK. */ - CURL_TRC_CF(data, cf, "[%" PRId64 "] read req body -> AGAIN", + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] read req body -> AGAIN", stream->id); return NGHTTP3_ERR_WOULDBLOCK; } - CURL_TRC_CF(data, cf, "[%" PRId64 "] read req body -> " + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] read req body -> " "%d vecs%s with %zu (buffered=%zu, left=%" CURL_FORMAT_CURL_OFF_T ")", stream->id, (int)nvecs, @@ -1217,6 +1356,7 @@ static ssize_t h3_stream_open(struct Curl_cfilter *cf, { struct cf_ngtcp2_ctx *ctx = cf->ctx; struct h3_stream_ctx *stream = NULL; + int64_t sid; struct dynhds h2_headers; size_t nheader; nghttp3_nv *nva = NULL; @@ -1231,7 +1371,7 @@ static ssize_t h3_stream_open(struct Curl_cfilter *cf, *err = h3_data_setup(cf, data); if(*err) goto out; - stream = H3_STREAM_CTX(data); + stream = H3_STREAM_CTX(ctx, data); DEBUGASSERT(stream); if(!stream) { *err = CURLE_FAILED_INIT; @@ -1272,12 +1412,15 @@ static ssize_t h3_stream_open(struct Curl_cfilter *cf, nva[i].flags = NGHTTP3_NV_FLAG_NONE; } - rc = ngtcp2_conn_open_bidi_stream(ctx->qconn, &stream->id, data); + rc = ngtcp2_conn_open_bidi_stream(ctx->qconn, &sid, data); if(rc) { failf(data, "can get bidi streams"); *err = CURLE_SEND_ERROR; + nwritten = -1; goto out; } + stream->id = (curl_int64_t)sid; + ++ctx->used_bidi_streams; switch(data->state.httpreq) { case HTTPREQ_POST: @@ -1308,12 +1451,12 @@ static ssize_t h3_stream_open(struct Curl_cfilter *cf, if(rc) { switch(rc) { case NGHTTP3_ERR_CONN_CLOSING: - CURL_TRC_CF(data, cf, "h3sid[%"PRId64"] failed to send, " + CURL_TRC_CF(data, cf, "h3sid[%" CURL_PRId64 "] failed to send, " "connection is closing", stream->id); break; default: - CURL_TRC_CF(data, cf, "h3sid[%"PRId64"] failed to send -> %d (%s)", - stream->id, rc, ngtcp2_strerror(rc)); + CURL_TRC_CF(data, cf, "h3sid[%" CURL_PRId64 "] failed to send -> " + "%d (%s)", stream->id, rc, ngtcp2_strerror(rc)); break; } *err = CURLE_SEND_ERROR; @@ -1322,10 +1465,10 @@ static ssize_t h3_stream_open(struct Curl_cfilter *cf, } if(Curl_trc_is_verbose(data)) { - infof(data, "[HTTP/3] [%" PRId64 "] OPENED stream for %s", + infof(data, "[HTTP/3] [%" CURL_PRId64 "] OPENED stream for %s", stream->id, data->state.url); for(i = 0; i < nheader; ++i) { - infof(data, "[HTTP/3] [%" PRId64 "] [%.*s: %.*s]", stream->id, + infof(data, "[HTTP/3] [%" CURL_PRId64 "] [%.*s: %.*s]", stream->id, (int)nva[i].namelen, nva[i].name, (int)nva[i].valuelen, nva[i].value); } @@ -1341,7 +1484,7 @@ static ssize_t cf_ngtcp2_send(struct Curl_cfilter *cf, struct Curl_easy *data, const void *buf, size_t len, CURLcode *err) { struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); ssize_t sent = 0; struct cf_call_data save; struct pkt_io_ctx pktx; @@ -1361,12 +1504,25 @@ static ssize_t cf_ngtcp2_send(struct Curl_cfilter *cf, struct Curl_easy *data, } if(!stream || stream->id < 0) { + if(ctx->conn_closed) { + CURL_TRC_CF(data, cf, "cannot open stream on closed connection"); + *err = CURLE_SEND_ERROR; + sent = -1; + goto out; + } sent = h3_stream_open(cf, data, buf, len, err); if(sent < 0) { CURL_TRC_CF(data, cf, "failed to open stream -> %d", *err); goto out; } - stream = H3_STREAM_CTX(data); + stream = H3_STREAM_CTX(ctx, data); + } + else if(stream->xfer_result) { + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] xfer write failed", stream->id); + cf_ngtcp2_stream_close(cf, data, stream); + *err = stream->xfer_result; + sent = -1; + goto out; } else if(stream->upload_blocked_len) { /* the data in `buf` has already been submitted or added to the @@ -1390,21 +1546,27 @@ static ssize_t cf_ngtcp2_send(struct Curl_cfilter *cf, struct Curl_easy *data, * body. This happens on 30x or 40x responses. * We silently discard the data sent, since this is not a transport * error situation. */ - CURL_TRC_CF(data, cf, "[%" PRId64 "] discarding data" + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] discarding data" "on closed stream with response", stream->id); *err = CURLE_OK; sent = (ssize_t)len; goto out; } - CURL_TRC_CF(data, cf, "[%" PRId64 "] send_body(len=%zu) " + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] send_body(len=%zu) " "-> stream closed", stream->id, len); *err = CURLE_HTTP3; sent = -1; goto out; } + else if(ctx->conn_closed) { + CURL_TRC_CF(data, cf, "cannot send on closed connection"); + *err = CURLE_SEND_ERROR; + sent = -1; + goto out; + } else { sent = Curl_bufq_write(&stream->sendbuf, buf, len, err); - CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_send, add to " + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] cf_send, add to " "sendbuf(len=%zu) -> %zd, %d", stream->id, len, sent, *err); if(sent < 0) { @@ -1425,7 +1587,7 @@ static ssize_t cf_ngtcp2_send(struct Curl_cfilter *cf, struct Curl_easy *data, * caller. Instead we EAGAIN and remember how much we have already * "written" into our various internal connection buffers. */ stream->upload_blocked_len = sent; - CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_send(len=%zu), " + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] cf_send(len=%zu), " "%zu bytes in flight -> EGAIN", stream->id, len, stream->sendbuf_len_in_flight); *err = CURLE_AGAIN; @@ -1438,7 +1600,7 @@ out: *err = result; sent = -1; } - CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_send(len=%zu) -> %zd, %d", + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] cf_send(len=%zu) -> %zd, %d", stream? stream->id : -1, len, sent, *err); CF_DATA_RESTORE(cf, save); return sent; @@ -1478,16 +1640,7 @@ static CURLcode recv_pkt(const unsigned char *pkt, size_t pktlen, if(rv) { CURL_TRC_CF(pktx->data, pktx->cf, "ingress, read_pkt -> %s (%d)", ngtcp2_strerror(rv), rv); - if(!ctx->last_error.error_code) { - if(rv == NGTCP2_ERR_CRYPTO) { - ngtcp2_ccerr_set_tls_alert(&ctx->last_error, - ngtcp2_conn_get_tls_alert(ctx->qconn), - NULL, 0); - } - else { - ngtcp2_ccerr_set_liberr(&ctx->last_error, rv, NULL, 0); - } - } + cf_ngtcp2_err_set(pktx->cf, pktx->data, rv); if(rv == NGTCP2_ERR_CRYPTO) /* this is a "TLS problem", but a failed certificate verification @@ -1570,9 +1723,7 @@ static ssize_t read_pkt_to_send(void *userp, if(veccnt < 0) { failf(x->data, "nghttp3_conn_writev_stream returned error: %s", nghttp3_strerror((int)veccnt)); - ngtcp2_ccerr_set_application_error( - &ctx->last_error, - nghttp3_err_infer_quic_app_error_code((int)veccnt), NULL, 0); + cf_ngtcp2_h3_err_set(x->cf, x->data, (int)veccnt); *err = CURLE_SEND_ERROR; return -1; } @@ -1593,11 +1744,11 @@ static ssize_t read_pkt_to_send(void *userp, else if(n < 0) { switch(n) { case NGTCP2_ERR_STREAM_DATA_BLOCKED: { - struct h3_stream_ctx *stream = H3_STREAM_CTX(x->data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, x->data); DEBUGASSERT(ndatalen == -1); nghttp3_conn_block_stream(ctx->h3conn, stream_id); - CURL_TRC_CF(x->data, x->cf, "[%" PRId64 "] block quic flow", - stream_id); + CURL_TRC_CF(x->data, x->cf, "[%" CURL_PRId64 "] block quic flow", + (curl_int64_t)stream_id); DEBUGASSERT(stream); if(stream) stream->quic_flow_blocked = TRUE; @@ -1619,7 +1770,7 @@ static ssize_t read_pkt_to_send(void *userp, DEBUGASSERT(ndatalen == -1); failf(x->data, "ngtcp2_conn_writev_stream returned error: %s", ngtcp2_strerror((int)n)); - ngtcp2_ccerr_set_liberr(&ctx->last_error, (int)n, NULL, 0); + cf_ngtcp2_err_set(x->cf, x->data, (int)n); *err = CURLE_SEND_ERROR; nwritten = -1; goto out; @@ -1807,7 +1958,7 @@ static CURLcode cf_ngtcp2_data_event(struct Curl_cfilter *cf, h3_data_done(cf, data); break; case CF_CTRL_DATA_DONE_SEND: { - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); if(stream && !stream->send_closed) { stream->send_closed = TRUE; stream->upload_left = Curl_bufq_len(&stream->sendbuf); @@ -1816,7 +1967,7 @@ static CURLcode cf_ngtcp2_data_event(struct Curl_cfilter *cf, break; } case CF_CTRL_DATA_IDLE: { - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); CURL_TRC_CF(data, cf, "data idle"); if(stream && !stream->closed) { result = check_and_set_expiry(cf, data, NULL); @@ -1846,6 +1997,9 @@ static void cf_ngtcp2_ctx_clear(struct cf_ngtcp2_ctx *ctx) if(ctx->qconn) ngtcp2_conn_del(ctx->qconn); Curl_bufcp_free(&ctx->stream_bufcp); + Curl_dyn_free(&ctx->scratch); + Curl_hash_clean(&ctx->streams); + Curl_hash_destroy(&ctx->streams); Curl_ssl_peer_cleanup(&ctx->peer); memset(ctx, 0, sizeof(*ctx)); @@ -1853,31 +2007,42 @@ static void cf_ngtcp2_ctx_clear(struct cf_ngtcp2_ctx *ctx) ctx->call_data = save; } -static void cf_ngtcp2_close(struct Curl_cfilter *cf, struct Curl_easy *data) +static void cf_ngtcp2_conn_close(struct Curl_cfilter *cf, + struct Curl_easy *data) { struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct cf_call_data save; - - CF_DATA_SAVE(save, cf, data); - if(ctx && ctx->qconn) { + if(ctx && ctx->qconn && !ctx->conn_closed) { char buffer[NGTCP2_MAX_UDP_PAYLOAD_SIZE]; struct pkt_io_ctx pktx; ngtcp2_ssize rc; - CURL_TRC_CF(data, cf, "close"); + ctx->conn_closed = TRUE; pktx_init(&pktx, cf, data); rc = ngtcp2_conn_write_connection_close(ctx->qconn, NULL, /* path */ NULL, /* pkt_info */ (uint8_t *)buffer, sizeof(buffer), &ctx->last_error, pktx.ts); + CURL_TRC_CF(data, cf, "closing connection(err_type=%d, err_code=%" + CURL_PRIu64 ") -> %d", ctx->last_error.type, + (curl_uint64_t)ctx->last_error.error_code, (int)rc); if(rc > 0) { while((send(ctx->q.sockfd, buffer, (SEND_TYPE_ARG3)rc, 0) == -1) && SOCKERRNO == EINTR); } + } +} +static void cf_ngtcp2_close(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct cf_call_data save; + + CF_DATA_SAVE(save, cf, data); + if(ctx && ctx->qconn) { + cf_ngtcp2_conn_close(cf, data); cf_ngtcp2_ctx_clear(ctx); + CURL_TRC_CF(data, cf, "close"); } - cf->connected = FALSE; CF_DATA_RESTORE(cf, save); } @@ -1898,25 +2063,59 @@ static void cf_ngtcp2_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) (void)save; } -static CURLcode tls_ctx_setup(struct quic_tls_ctx *ctx, - struct Curl_cfilter *cf, - struct Curl_easy *data) +#ifdef USE_OPENSSL +/* The "new session" callback must return zero if the session can be removed + * or non-zero if the session has been put into the session cache. + */ +static int quic_ossl_new_session_cb(SSL *ssl, SSL_SESSION *ssl_sessionid) { + struct Curl_cfilter *cf; + struct cf_ngtcp2_ctx *ctx; + struct Curl_easy *data; + ngtcp2_crypto_conn_ref *cref; + + cref = (ngtcp2_crypto_conn_ref *)SSL_get_app_data(ssl); + cf = cref? cref->user_data : NULL; + ctx = cf? cf->ctx : NULL; + data = cf? CF_DATA_CURRENT(cf) : NULL; + if(cf && data && ctx) { + Curl_ossl_add_session(cf, data, &ctx->peer, ssl_sessionid); + return 1; + } + return 0; +} +#endif /* USE_OPENSSL */ + +static CURLcode tls_ctx_setup(struct Curl_cfilter *cf, + struct Curl_easy *data, + void *user_data) +{ + struct curl_tls_ctx *ctx = user_data; (void)cf; #ifdef USE_OPENSSL #if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) - if(ngtcp2_crypto_boringssl_configure_client_context(ctx->ssl_ctx) != 0) { + if(ngtcp2_crypto_boringssl_configure_client_context(ctx->ossl.ssl_ctx) + != 0) { failf(data, "ngtcp2_crypto_boringssl_configure_client_context failed"); return CURLE_FAILED_INIT; } #else - if(ngtcp2_crypto_quictls_configure_client_context(ctx->ssl_ctx) != 0) { + if(ngtcp2_crypto_quictls_configure_client_context(ctx->ossl.ssl_ctx) != 0) { failf(data, "ngtcp2_crypto_quictls_configure_client_context failed"); return CURLE_FAILED_INIT; } #endif /* !OPENSSL_IS_BORINGSSL && !OPENSSL_IS_AWSLC */ + /* Enable the session cache because it's a prerequisite for the + * "new session" callback. Use the "external storage" mode to prevent + * OpenSSL from creating an internal session cache. + */ + SSL_CTX_set_session_cache_mode(ctx->ossl.ssl_ctx, + SSL_SESS_CACHE_CLIENT | + SSL_SESS_CACHE_NO_INTERNAL); + SSL_CTX_sess_set_new_cb(ctx->ossl.ssl_ctx, quic_ossl_new_session_cb); + #elif defined(USE_GNUTLS) - if(ngtcp2_crypto_gnutls_configure_client_session(ctx->gtls->session) != 0) { + if(ngtcp2_crypto_gnutls_configure_client_session(ctx->gtls.session) != 0) { failf(data, "ngtcp2_crypto_gnutls_configure_client_session failed"); return CURLE_FAILED_INIT; } @@ -1948,18 +2147,24 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, ctx->max_idle_ms = CURL_QUIC_MAX_IDLE_MS; Curl_bufcp_init(&ctx->stream_bufcp, H3_STREAM_CHUNK_SIZE, H3_STREAM_POOL_SPARES); + Curl_dyn_init(&ctx->scratch, CURL_MAX_HTTP_HEADER); + Curl_hash_offt_init(&ctx->streams, 63, h3_stream_hash_free); - result = Curl_ssl_peer_init(&ctx->peer, cf); + result = Curl_ssl_peer_init(&ctx->peer, cf, TRNSPRT_QUIC); if(result) return result; #define H3_ALPN "\x2h3\x5h3-29" result = Curl_vquic_tls_init(&ctx->tls, cf, data, &ctx->peer, H3_ALPN, sizeof(H3_ALPN) - 1, - tls_ctx_setup, &ctx->conn_ref); + tls_ctx_setup, &ctx->tls, &ctx->conn_ref); if(result) return result; +#ifdef USE_OPENSSL + SSL_set_quic_use_legacy_codepoint(ctx->tls.ossl.ssl, 0); +#endif + ctx->dcid.datalen = NGTCP2_MAX_CIDLEN; result = Curl_rand(data, ctx->dcid.data, NGTCP2_MAX_CIDLEN); if(result) @@ -2001,8 +2206,10 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, if(rc) return CURLE_QUIC_CONNECT_ERROR; -#ifdef USE_GNUTLS - ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->tls.gtls->session); +#ifdef USE_OPENSSL + ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->tls.ossl.ssl); +#elif defined(USE_GNUTLS) + ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->tls.gtls.session); #else ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->tls.ssl); #endif @@ -2118,17 +2325,28 @@ static CURLcode cf_ngtcp2_query(struct Curl_cfilter *cf, switch(query) { case CF_QUERY_MAX_CONCURRENT: { - const ngtcp2_transport_params *rp; DEBUGASSERT(pres1); - CF_DATA_SAVE(save, cf, data); - rp = ngtcp2_conn_get_remote_transport_params(ctx->qconn); - if(rp) - *pres1 = (rp->initial_max_streams_bidi > INT_MAX)? - INT_MAX : (int)rp->initial_max_streams_bidi; - else /* not arrived yet? */ + /* Set after transport params arrived and continually updated + * by callback. QUIC counts the number over the lifetime of the + * connection, ever increasing. + * We count the *open* transfers plus the budget for new ones. */ + if(!ctx->qconn || ctx->conn_closed) { + *pres1 = 0; + } + else if(ctx->max_bidi_streams) { + uint64_t avail_bidi_streams = 0; + uint64_t max_streams = CONN_INUSE(cf->conn); + if(ctx->max_bidi_streams > ctx->used_bidi_streams) + avail_bidi_streams = ctx->max_bidi_streams - ctx->used_bidi_streams; + max_streams += avail_bidi_streams; + *pres1 = (max_streams > INT_MAX)? INT_MAX : (int)max_streams; + } + else /* transport params not arrived yet? take our default. */ *pres1 = Curl_multi_max_concurrent_streams(data->multi); - CURL_TRC_CF(data, cf, "query max_conncurrent -> %d", *pres1); + CURL_TRC_CF(data, cf, "query conn[%" CURL_FORMAT_CURL_OFF_T "]: " + "MAX_CONCURRENT -> %d (%zu in use)", + cf->conn->connection_id, *pres1, CONN_INUSE(cf->conn)); CF_DATA_RESTORE(cf, save); return CURLE_OK; } @@ -2169,9 +2387,9 @@ static bool cf_ngtcp2_conn_is_alive(struct Curl_cfilter *cf, const ngtcp2_transport_params *rp; struct cf_call_data save; - CF_DATA_SAVE(save, cf, data); + CF_DATA_SAVE(save, cf, data); *input_pending = FALSE; - if(!ctx->qconn) + if(!ctx->qconn || ctx->conn_closed) goto out; /* Both sides of the QUIC connection announce they max idle times in diff --git a/lib/vquic/curl_osslq.c b/lib/vquic/curl_osslq.c index 1d53e2c..8b9e889 100644 --- a/lib/vquic/curl_osslq.c +++ b/lib/vquic/curl_osslq.c @@ -32,6 +32,7 @@ #include <nghttp3/nghttp3.h> #include "urldata.h" +#include "hash.h" #include "sendf.h" #include "strdup.h" #include "rand.h" @@ -182,7 +183,7 @@ static CURLcode make_bio_addr(BIO_ADDR **pbio_addr, result = CURLE_OK; break; } -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 case AF_INET6: { struct sockaddr_in6 * const sin = (struct sockaddr_in6 * const)(void *)&addr->sa_addr; @@ -192,7 +193,7 @@ static CURLcode make_bio_addr(BIO_ADDR **pbio_addr, result = CURLE_OK; break; } -#endif /* ENABLE_IPV6 */ +#endif /* USE_IPV6 */ default: /* sunsupported */ DEBUGASSERT(0); @@ -210,7 +211,7 @@ out: /* QUIC stream (not necessarily H3) */ struct cf_osslq_stream { - int64_t id; + curl_int64_t id; SSL *ssl; struct bufq recvbuf; /* QUIC war data recv buffer */ BIT(recvd_eos); @@ -281,7 +282,7 @@ static void cf_osslq_h3conn_cleanup(struct cf_osslq_h3conn *h3) struct cf_osslq_ctx { struct cf_quic_ctx q; struct ssl_peer peer; - struct quic_tls_ctx tls; + struct curl_tls_ctx tls; struct cf_call_data call_data; struct cf_osslq_h3conn h3; struct curltime started_at; /* time the current attempt started */ @@ -289,6 +290,7 @@ struct cf_osslq_ctx { struct curltime first_byte_at; /* when first byte was recvd */ struct curltime reconnect_at; /* time the next attempt should start */ struct bufc_pool stream_bufcp; /* chunk pool for streams */ + struct Curl_hash streams; /* hash `data->id` to `h3_stream_ctx` */ size_t max_stream_window; /* max flow window for one stream */ uint64_t max_idle_ms; /* max idle time for QUIC connection */ BIT(got_first_byte); /* if first byte was received */ @@ -306,6 +308,8 @@ static void cf_osslq_ctx_clear(struct cf_osslq_ctx *ctx) Curl_vquic_tls_cleanup(&ctx->tls); vquic_ctx_free(&ctx->q); Curl_bufcp_free(&ctx->stream_bufcp); + Curl_hash_clean(&ctx->streams); + Curl_hash_destroy(&ctx->streams); Curl_ssl_peer_cleanup(&ctx->peer); memset(ctx, 0, sizeof(*ctx)); @@ -318,7 +322,7 @@ static void cf_osslq_close(struct Curl_cfilter *cf, struct Curl_easy *data) struct cf_call_data save; CF_DATA_SAVE(save, cf, data); - if(ctx && ctx->tls.ssl) { + if(ctx && ctx->tls.ossl.ssl) { /* TODO: send connection close */ CURL_TRC_CF(data, cf, "cf_osslq_close()"); cf_osslq_ctx_clear(ctx); @@ -355,8 +359,8 @@ static CURLcode cf_osslq_h3conn_add_stream(struct cf_osslq_h3conn *h3, if(h3->remote_ctrl_n >= ARRAYSIZE(h3->remote_ctrl)) { /* rejected, we are full */ - CURL_TRC_CF(data, cf, "[%" PRId64 "] rejecting additional remote stream", - stream_id); + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] rejecting remote stream", + (curl_int64_t)stream_id); SSL_free(stream_ssl); return CURLE_FAILED_INIT; } @@ -366,13 +370,13 @@ static CURLcode cf_osslq_h3conn_add_stream(struct cf_osslq_h3conn *h3, nstream->id = stream_id; nstream->ssl = stream_ssl; Curl_bufq_initp(&nstream->recvbuf, &ctx->stream_bufcp, 1, BUFQ_OPT_NONE); - CURL_TRC_CF(data, cf, "[%" PRId64 "] accepted new remote uni stream", - stream_id); + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] accepted remote uni stream", + (curl_int64_t)stream_id); break; } default: - CURL_TRC_CF(data, cf, "[%" PRId64 "] rejecting remote non-uni-read" - " stream", stream_id); + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] reject remote non-uni-read" + " stream", (curl_int64_t)stream_id); SSL_free(stream_ssl); return CURLE_FAILED_INIT; } @@ -403,7 +407,7 @@ static CURLcode cf_osslq_ssl_err(struct Curl_cfilter *cf, (reason == SSL_R_SSLV3_ALERT_CERTIFICATE_EXPIRED))) { result = CURLE_PEER_FAILED_VERIFICATION; - lerr = SSL_get_verify_result(ctx->tls.ssl); + lerr = SSL_get_verify_result(ctx->tls.ossl.ssl); if(lerr != X509_V_OK) { ssl_config->certverifyresult = lerr; msnprintf(ebuf, sizeof(ebuf), @@ -482,7 +486,7 @@ struct h3_stream_ctx { size_t sendbuf_len_in_flight; /* sendbuf amount "in flight" */ size_t upload_blocked_len; /* the amount written last and EGAINed */ size_t recv_buf_nonflow; /* buffered bytes, not counting for flow control */ - uint64_t error3; /* HTTP/3 stream error code */ + curl_uint64_t error3; /* HTTP/3 stream error code */ curl_off_t upload_left; /* number of request bytes left to upload */ curl_off_t download_recvd; /* number of response DATA bytes received */ int status_code; /* HTTP status code */ @@ -493,18 +497,29 @@ struct h3_stream_ctx { BIT(quic_flow_blocked); /* stream is blocked by QUIC flow control */ }; -#define H3_STREAM_CTX(d) ((struct h3_stream_ctx *)(((d) && (d)->req.p.http)? \ - ((struct HTTP *)(d)->req.p.http)->h3_ctx \ - : NULL)) -#define H3_STREAM_LCTX(d) ((struct HTTP *)(d)->req.p.http)->h3_ctx -#define H3_STREAM_ID(d) (H3_STREAM_CTX(d)? \ - H3_STREAM_CTX(d)->s.id : -2) +#define H3_STREAM_CTX(ctx,data) ((struct h3_stream_ctx *)(\ + data? Curl_hash_offt_get(&(ctx)->streams, (data)->id) : NULL)) + +static void h3_stream_ctx_free(struct h3_stream_ctx *stream) +{ + cf_osslq_stream_cleanup(&stream->s); + Curl_bufq_free(&stream->sendbuf); + Curl_bufq_free(&stream->recvbuf); + Curl_h1_req_parse_free(&stream->h1); + free(stream); +} + +static void h3_stream_hash_free(void *stream) +{ + DEBUGASSERT(stream); + h3_stream_ctx_free((struct h3_stream_ctx *)stream); +} static CURLcode h3_data_setup(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_osslq_ctx *ctx = cf->ctx; - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); if(!data || !data->req.p.http) { failf(data, "initialization failure, transfer not http initialized"); @@ -530,18 +545,23 @@ static CURLcode h3_data_setup(struct Curl_cfilter *cf, stream->recv_buf_nonflow = 0; Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN); - H3_STREAM_LCTX(data) = stream; + if(!Curl_hash_offt_set(&ctx->streams, data->id, stream)) { + h3_stream_ctx_free(stream); + return CURLE_OUT_OF_MEMORY; + } + return CURLE_OK; } static void h3_data_done(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_osslq_ctx *ctx = cf->ctx; - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); (void)cf; if(stream) { - CURL_TRC_CF(data, cf, "[%"PRId64"] easy handle is done", stream->s.id); + CURL_TRC_CF(data, cf, "[%"CURL_PRId64"] easy handle is done", + stream->s.id); if(ctx->h3.conn && !stream->closed) { nghttp3_conn_shutdown_stream_read(ctx->h3.conn, stream->s.id); nghttp3_conn_close_stream(ctx->h3.conn, stream->s.id, @@ -550,12 +570,7 @@ static void h3_data_done(struct Curl_cfilter *cf, struct Curl_easy *data) stream->closed = TRUE; } - cf_osslq_stream_cleanup(&stream->s); - Curl_bufq_free(&stream->sendbuf); - Curl_bufq_free(&stream->recvbuf); - Curl_h1_req_parse_free(&stream->h1); - free(stream); - H3_STREAM_LCTX(data) = NULL; + Curl_hash_offt_remove(&ctx->streams, data->id); } } @@ -564,7 +579,7 @@ static struct cf_osslq_stream *cf_osslq_get_qstream(struct Curl_cfilter *cf, int64_t stream_id) { struct cf_osslq_ctx *ctx = cf->ctx; - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); struct Curl_easy *sdata; if(stream && stream->s.id == stream_id) { @@ -582,9 +597,11 @@ static struct cf_osslq_stream *cf_osslq_get_qstream(struct Curl_cfilter *cf, else { DEBUGASSERT(data->multi); for(sdata = data->multi->easyp; sdata; sdata = sdata->next) { - if((sdata->conn == data->conn) && H3_STREAM_ID(sdata) == stream_id) { - stream = H3_STREAM_CTX(sdata); - return stream? &stream->s : NULL; + if(sdata->conn != data->conn) + continue; + stream = H3_STREAM_CTX(ctx, sdata); + if(stream && stream->s.id == stream_id) { + return &stream->s; } } } @@ -594,7 +611,8 @@ static struct cf_osslq_stream *cf_osslq_get_qstream(struct Curl_cfilter *cf, static void h3_drain_stream(struct Curl_cfilter *cf, struct Curl_easy *data) { - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_osslq_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); unsigned char bits; (void)cf; @@ -624,8 +642,9 @@ static int cb_h3_stream_close(nghttp3_conn *conn, int64_t stream_id, void *stream_user_data) { struct Curl_cfilter *cf = user_data; + struct cf_osslq_ctx *ctx = cf->ctx; struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); (void)conn; (void)stream_id; @@ -638,11 +657,11 @@ static int cb_h3_stream_close(nghttp3_conn *conn, int64_t stream_id, if(stream->error3 != NGHTTP3_H3_NO_ERROR) { stream->reset = TRUE; stream->send_closed = TRUE; - CURL_TRC_CF(data, cf, "[%" PRId64 "] RESET: error %" PRId64, + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] RESET: error %" CURL_PRIu64, stream->s.id, stream->error3); } else { - CURL_TRC_CF(data, cf, "[%" PRId64 "] CLOSED", stream->s.id); + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] CLOSED", stream->s.id); } h3_drain_stream(cf, data); return 0; @@ -658,7 +677,8 @@ static CURLcode write_resp_raw(struct Curl_cfilter *cf, const void *mem, size_t memlen, bool flow) { - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_osslq_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); CURLcode result = CURLE_OK; ssize_t nwritten; @@ -688,8 +708,9 @@ static int cb_h3_recv_data(nghttp3_conn *conn, int64_t stream3_id, void *user_data, void *stream_user_data) { struct Curl_cfilter *cf = user_data; + struct cf_osslq_ctx *ctx = cf->ctx; struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); CURLcode result; (void)conn; @@ -700,12 +721,12 @@ static int cb_h3_recv_data(nghttp3_conn *conn, int64_t stream3_id, result = write_resp_raw(cf, data, buf, buflen, TRUE); if(result) { - CURL_TRC_CF(data, cf, "[%" PRId64 "] DATA len=%zu, ERROR receiving %d", + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] DATA len=%zu, ERROR %d", stream->s.id, buflen, result); return NGHTTP3_ERR_CALLBACK_FAILURE; } stream->download_recvd += (curl_off_t)buflen; - CURL_TRC_CF(data, cf, "[%" PRId64 "] DATA len=%zu, total=%zd", + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] DATA len=%zu, total=%zd", stream->s.id, buflen, stream->download_recvd); h3_drain_stream(cf, data); return 0; @@ -716,27 +737,30 @@ static int cb_h3_deferred_consume(nghttp3_conn *conn, int64_t stream_id, void *stream_user_data) { struct Curl_cfilter *cf = user_data; + struct cf_osslq_ctx *ctx = cf->ctx; struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); (void)conn; (void)stream_id; if(stream) - CURL_TRC_CF(data, cf, "[%" PRId64 "] deferred consume %zu bytes", + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] deferred consume %zu bytes", stream->s.id, consumed); return 0; } -static int cb_h3_recv_header(nghttp3_conn *conn, int64_t stream_id, +static int cb_h3_recv_header(nghttp3_conn *conn, int64_t sid, int32_t token, nghttp3_rcbuf *name, nghttp3_rcbuf *value, uint8_t flags, void *user_data, void *stream_user_data) { struct Curl_cfilter *cf = user_data; + curl_int64_t stream_id = sid; + struct cf_osslq_ctx *ctx = cf->ctx; nghttp3_vec h3name = nghttp3_rcbuf_get_buf(name); nghttp3_vec h3val = nghttp3_rcbuf_get_buf(value); struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); CURLcode result = CURLE_OK; (void)conn; (void)stream_id; @@ -758,7 +782,7 @@ static int cb_h3_recv_header(nghttp3_conn *conn, int64_t stream_id, return -1; ncopy = msnprintf(line, sizeof(line), "HTTP/3 %03d \r\n", stream->status_code); - CURL_TRC_CF(data, cf, "[%" PRId64 "] status: %s", stream_id, line); + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] status: %s", stream_id, line); result = write_resp_raw(cf, data, line, ncopy, FALSE); if(result) { return -1; @@ -766,7 +790,7 @@ static int cb_h3_recv_header(nghttp3_conn *conn, int64_t stream_id, } else { /* store as an HTTP1-style header */ - CURL_TRC_CF(data, cf, "[%" PRId64 "] header: %.*s: %.*s", + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] header: %.*s: %.*s", stream_id, (int)h3name.len, h3name.base, (int)h3val.len, h3val.base); result = write_resp_raw(cf, data, h3name.base, h3name.len, FALSE); @@ -789,12 +813,14 @@ static int cb_h3_recv_header(nghttp3_conn *conn, int64_t stream_id, return 0; } -static int cb_h3_end_headers(nghttp3_conn *conn, int64_t stream_id, +static int cb_h3_end_headers(nghttp3_conn *conn, int64_t sid, int fin, void *user_data, void *stream_user_data) { struct Curl_cfilter *cf = user_data; + struct cf_osslq_ctx *ctx = cf->ctx; struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + curl_int64_t stream_id = sid; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); CURLcode result = CURLE_OK; (void)conn; (void)stream_id; @@ -809,7 +835,7 @@ static int cb_h3_end_headers(nghttp3_conn *conn, int64_t stream_id, return -1; } - CURL_TRC_CF(data, cf, "[%" PRId64 "] end_headers, status=%d", + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] end_headers, status=%d", stream_id, stream->status_code); if(stream->status_code / 100 != 1) { stream->resp_hds_complete = TRUE; @@ -818,30 +844,34 @@ static int cb_h3_end_headers(nghttp3_conn *conn, int64_t stream_id, return 0; } -static int cb_h3_stop_sending(nghttp3_conn *conn, int64_t stream_id, +static int cb_h3_stop_sending(nghttp3_conn *conn, int64_t sid, uint64_t app_error_code, void *user_data, void *stream_user_data) { struct Curl_cfilter *cf = user_data; + struct cf_osslq_ctx *ctx = cf->ctx; struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + curl_int64_t stream_id = sid; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); (void)conn; (void)app_error_code; if(!stream || !stream->s.ssl) return 0; - CURL_TRC_CF(data, cf, "[%" PRId64 "] stop_sending", stream_id); + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] stop_sending", stream_id); cf_osslq_stream_close(&stream->s); return 0; } -static int cb_h3_reset_stream(nghttp3_conn *conn, int64_t stream_id, +static int cb_h3_reset_stream(nghttp3_conn *conn, int64_t sid, uint64_t app_error_code, void *user_data, void *stream_user_data) { struct Curl_cfilter *cf = user_data; + struct cf_osslq_ctx *ctx = cf->ctx; struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + curl_int64_t stream_id = sid; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); int rv; (void)conn; @@ -849,7 +879,7 @@ static int cb_h3_reset_stream(nghttp3_conn *conn, int64_t stream_id, SSL_STREAM_RESET_ARGS args = {0}; args.quic_error_code = app_error_code; rv = !SSL_stream_reset(stream->s.ssl, &args, sizeof(args)); - CURL_TRC_CF(data, cf, "[%" PRId64 "] reset -> %d", stream_id, rv); + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] reset -> %d", stream_id, rv); if(!rv) { return NGHTTP3_ERR_CALLBACK_FAILURE; } @@ -864,8 +894,9 @@ cb_h3_read_req_body(nghttp3_conn *conn, int64_t stream_id, void *stream_user_data) { struct Curl_cfilter *cf = user_data; + struct cf_osslq_ctx *ctx = cf->ctx; struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); ssize_t nwritten = 0; size_t nvecs = 0; (void)cf; @@ -908,12 +939,12 @@ cb_h3_read_req_body(nghttp3_conn *conn, int64_t stream_id, } else if(!nwritten) { /* Not EOF, and nothing to give, we signal WOULDBLOCK. */ - CURL_TRC_CF(data, cf, "[%" PRId64 "] read req body -> AGAIN", + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] read req body -> AGAIN", stream->s.id); return NGHTTP3_ERR_WOULDBLOCK; } - CURL_TRC_CF(data, cf, "[%" PRId64 "] read req body -> " + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] read req body -> " "%d vecs%s with %zu (buffered=%zu, left=%" CURL_FORMAT_CURL_OFF_T ")", stream->s.id, (int)nvecs, @@ -928,8 +959,9 @@ static int cb_h3_acked_stream_data(nghttp3_conn *conn, int64_t stream_id, void *stream_user_data) { struct Curl_cfilter *cf = user_data; + struct cf_osslq_ctx *ctx = cf->ctx; struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); size_t skiplen; (void)cf; @@ -1042,14 +1074,15 @@ static CURLcode cf_osslq_ctx_start(struct Curl_cfilter *cf, Curl_bufcp_init(&ctx->stream_bufcp, H3_STREAM_CHUNK_SIZE, H3_STREAM_POOL_SPARES); - result = Curl_ssl_peer_init(&ctx->peer, cf); + Curl_hash_offt_init(&ctx->streams, 63, h3_stream_hash_free); + result = Curl_ssl_peer_init(&ctx->peer, cf, TRNSPRT_QUIC); if(result) goto out; #define H3_ALPN "\x2h3" result = Curl_vquic_tls_init(&ctx->tls, cf, data, &ctx->peer, H3_ALPN, sizeof(H3_ALPN) - 1, - NULL, NULL); + NULL, NULL, NULL); if(result) goto out; @@ -1093,12 +1126,12 @@ static CURLcode cf_osslq_ctx_start(struct Curl_cfilter *cf, goto out; } - if(!SSL_set1_initial_peer_addr(ctx->tls.ssl, baddr)) { + if(!SSL_set1_initial_peer_addr(ctx->tls.ossl.ssl, baddr)) { failf(data, "failed to set the initial peer address"); result = CURLE_FAILED_INIT; goto out; } - if(!SSL_set_blocking_mode(ctx->tls.ssl, 0)) { + if(!SSL_set_blocking_mode(ctx->tls.ossl.ssl, 0)) { failf(data, "failed to turn off blocking mode"); result = CURLE_FAILED_INIT; goto out; @@ -1106,7 +1139,8 @@ static CURLcode cf_osslq_ctx_start(struct Curl_cfilter *cf, #ifdef SSL_VALUE_QUIC_IDLE_TIMEOUT /* Added in OpenSSL v3.3.x */ - if(!SSL_set_feature_request_uint(ctx->tls.ssl, SSL_VALUE_QUIC_IDLE_TIMEOUT, + if(!SSL_set_feature_request_uint(ctx->tls.ossl.ssl, + SSL_VALUE_QUIC_IDLE_TIMEOUT, CURL_QUIC_MAX_IDLE_MS)) { CURL_TRC_CF(data, cf, "error setting idle timeout, "); result = CURLE_FAILED_INIT; @@ -1114,13 +1148,13 @@ static CURLcode cf_osslq_ctx_start(struct Curl_cfilter *cf, } #endif - SSL_set_bio(ctx->tls.ssl, bio, bio); + SSL_set_bio(ctx->tls.ossl.ssl, bio, bio); bio = NULL; - SSL_set_connect_state(ctx->tls.ssl); - SSL_set_incoming_stream_policy(ctx->tls.ssl, + SSL_set_connect_state(ctx->tls.ossl.ssl); + SSL_set_incoming_stream_policy(ctx->tls.ossl.ssl, SSL_INCOMING_STREAM_POLICY_ACCEPT, 0); /* setup the H3 things on top of the QUIC connection */ - result = cf_osslq_h3conn_init(ctx, ctx->tls.ssl, cf); + result = cf_osslq_h3conn_init(ctx, ctx->tls.ossl.ssl, cf); out: if(bio) @@ -1154,7 +1188,7 @@ static ssize_t h3_quic_recv(void *reader_ctx, return -1; } else if(detail == SSL_ERROR_ZERO_RETURN) { - CURL_TRC_CF(x->data, x->cf, "[%" PRId64 "] h3_quic_recv -> EOS", + CURL_TRC_CF(x->data, x->cf, "[%" CURL_PRId64 "] h3_quic_recv -> EOS", x->s->id); x->s->recvd_eos = TRUE; return 0; @@ -1163,9 +1197,9 @@ static ssize_t h3_quic_recv(void *reader_ctx, SSL_STREAM_STATE_RESET_REMOTE) { uint64_t app_error_code = NGHTTP3_H3_NO_ERROR; SSL_get_stream_read_error_code(x->s->ssl, &app_error_code); - CURL_TRC_CF(x->data, x->cf, "[%" PRId64 "] h3_quic_recv -> RESET, " - "rv=%d, app_err=%" PRIu64, - x->s->id, rv, app_error_code); + CURL_TRC_CF(x->data, x->cf, "[%" CURL_PRId64 "] h3_quic_recv -> RESET, " + "rv=%d, app_err=%" CURL_PRIu64, + x->s->id, rv, (curl_uint64_t)app_error_code); if(app_error_code != NGHTTP3_H3_NO_ERROR) { x->s->reset = TRUE; } @@ -1177,10 +1211,6 @@ static ssize_t h3_quic_recv(void *reader_ctx, return -1; } } - else { - /* CURL_TRC_CF(x->data, x->cf, "[%" PRId64 "] h3_quic_recv -> %zu bytes", - x->s->id, nread); */ - } return (ssize_t)nread; } @@ -1224,7 +1254,7 @@ static CURLcode cf_osslq_stream_recv(struct cf_osslq_stream *s, while(Curl_bufq_peek(&s->recvbuf, &buf, &blen)) { nread = nghttp3_conn_read_stream(ctx->h3.conn, s->id, buf, blen, 0); - CURL_TRC_CF(data, cf, "[%" PRId64 "] forward %zu bytes " + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] forward %zu bytes " "to nghttp3 -> %zd", s->id, blen, nread); if(nread < 0) { failf(data, "nghttp3_conn_read_stream(len=%zu) error: %s", @@ -1263,7 +1293,7 @@ static CURLcode cf_osslq_stream_recv(struct cf_osslq_stream *s, rv = nghttp3_conn_close_stream(ctx->h3.conn, s->id, NGHTTP3_H3_NO_ERROR); s->closed = TRUE; - CURL_TRC_CF(data, cf, "[%" PRId64 "] close nghttp3 stream -> %d", + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] close nghttp3 stream -> %d", s->id, rv); if(rv < 0 && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { failf(data, "nghttp3_conn_close_stream returned error: %s", @@ -1276,7 +1306,7 @@ static CURLcode cf_osslq_stream_recv(struct cf_osslq_stream *s, } out: if(result) - CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_osslq_stream_recv -> %d", + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] cf_osslq_stream_recv -> %d", s->id, result); return result; } @@ -1287,22 +1317,23 @@ static CURLcode cf_progress_ingress(struct Curl_cfilter *cf, struct cf_osslq_ctx *ctx = cf->ctx; CURLcode result = CURLE_OK; - if(!ctx->tls.ssl) + if(!ctx->tls.ossl.ssl) goto out; ERR_clear_error(); /* 1. Check for new incoming streams */ while(1) { - SSL *snew = SSL_accept_stream(ctx->tls.ssl, SSL_ACCEPT_STREAM_NO_BLOCK); + SSL *snew = SSL_accept_stream(ctx->tls.ossl.ssl, + SSL_ACCEPT_STREAM_NO_BLOCK); if(!snew) break; (void)cf_osslq_h3conn_add_stream(&ctx->h3, snew, cf, data); } - if(!SSL_handle_events(ctx->tls.ssl)) { - int detail = SSL_get_error(ctx->tls.ssl, 0); + if(!SSL_handle_events(ctx->tls.ossl.ssl)) { + int detail = SSL_get_error(ctx->tls.ossl.ssl, 0); result = cf_osslq_ssl_err(cf, data, detail, CURLE_RECV_ERROR); } @@ -1322,7 +1353,7 @@ static CURLcode cf_progress_ingress(struct Curl_cfilter *cf, DEBUGASSERT(data->multi); for(sdata = data->multi->easyp; sdata; sdata = sdata->next) { if(sdata->conn == data->conn && CURL_WANT_RECV(sdata)) { - stream = H3_STREAM_CTX(sdata); + stream = H3_STREAM_CTX(ctx, sdata); if(stream && !stream->closed && !Curl_bufq_is_full(&stream->recvbuf)) { result = cf_osslq_stream_recv(&stream->s, cf, sdata); @@ -1349,7 +1380,7 @@ static CURLcode cf_osslq_check_and_unblock(struct Curl_cfilter *cf, if(ctx->h3.conn) { for(sdata = data->multi->easyp; sdata; sdata = sdata->next) { if(sdata->conn == data->conn) { - stream = H3_STREAM_CTX(sdata); + stream = H3_STREAM_CTX(ctx, sdata); if(stream && stream->s.ssl && stream->s.send_blocked && !SSL_want_write(stream->s.ssl)) { nghttp3_conn_unblock_stream(ctx->h3.conn, stream->s.id); @@ -1369,7 +1400,7 @@ static CURLcode h3_send_streams(struct Curl_cfilter *cf, struct cf_osslq_ctx *ctx = cf->ctx; CURLcode result = CURLE_OK; - if(!ctx->tls.ssl || !ctx->h3.conn) + if(!ctx->tls.ossl.ssl || !ctx->h3.conn) goto out; for(;;) { @@ -1398,8 +1429,8 @@ static CURLcode h3_send_streams(struct Curl_cfilter *cf, /* Get the stream for this data */ s = cf_osslq_get_qstream(cf, data, stream_id); if(!s) { - failf(data, "nghttp3_conn_writev_stream gave unknown stream %" PRId64, - stream_id); + failf(data, "nghttp3_conn_writev_stream gave unknown stream %" + CURL_PRId64, (curl_int64_t)stream_id); result = CURLE_SEND_ERROR; goto out; } @@ -1427,8 +1458,8 @@ static CURLcode h3_send_streams(struct Curl_cfilter *cf, if(ok) { /* As OpenSSL buffers the data, we count this as acknowledged * from nghttp3's point of view */ - CURL_TRC_CF(data, cf, "[%"PRId64"] send %zu bytes to QUIC ok", - s->id, vec[i].len); + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] send %zu bytes to QUIC ok", + s->id, vec[i].len); acked_len += vec[i].len; } else { @@ -1437,16 +1468,16 @@ static CURLcode h3_send_streams(struct Curl_cfilter *cf, case SSL_ERROR_WANT_WRITE: case SSL_ERROR_WANT_READ: /* QUIC blocked us from writing more */ - CURL_TRC_CF(data, cf, "[%"PRId64"] send %zu bytes to QUIC blocked", - s->id, vec[i].len); + CURL_TRC_CF(data, cf, "[%"CURL_PRId64 "] send %zu bytes to " + "QUIC blocked", s->id, vec[i].len); written = 0; nghttp3_conn_block_stream(ctx->h3.conn, s->id); s->send_blocked = blocked = TRUE; break; default: - failf(data, "[%"PRId64"] send %zu bytes to QUIC, SSL error %d", + failf(data, "[%"CURL_PRId64 "] send %zu bytes to QUIC, SSL error %d", s->id, vec[i].len, detail); - result = cf_osslq_ssl_err(cf, data, detail, CURLE_SEND_ERROR); + result = cf_osslq_ssl_err(cf, data, detail, CURLE_HTTP3); goto out; } } @@ -1470,13 +1501,13 @@ static CURLcode h3_send_streams(struct Curl_cfilter *cf, result = CURLE_SEND_ERROR; goto out; } - CURL_TRC_CF(data, cf, "[%" PRId64 "] forwarded %zu/%zu h3 bytes " + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] forwarded %zu/%zu h3 bytes " "to QUIC, eos=%d", s->id, acked_len, total_len, eos); } if(eos && !s->send_blocked && !eos_written) { /* wrote everything and H3 indicates end of stream */ - CURL_TRC_CF(data, cf, "[%" PRId64 "] closing QUIC stream", s->id); + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] closing QUIC stream", s->id); SSL_stream_conclude(s->ssl, 0); } } @@ -1492,7 +1523,7 @@ static CURLcode cf_progress_egress(struct Curl_cfilter *cf, struct cf_osslq_ctx *ctx = cf->ctx; CURLcode result = CURLE_OK; - if(!ctx->tls.ssl) + if(!ctx->tls.ossl.ssl) goto out; ERR_clear_error(); @@ -1500,8 +1531,8 @@ static CURLcode cf_progress_egress(struct Curl_cfilter *cf, if(result) goto out; - if(!SSL_handle_events(ctx->tls.ssl)) { - int detail = SSL_get_error(ctx->tls.ssl, 0); + if(!SSL_handle_events(ctx->tls.ossl.ssl)) { + int detail = SSL_get_error(ctx->tls.ossl.ssl, 0); result = cf_osslq_ssl_err(cf, data, detail, CURLE_SEND_ERROR); } @@ -1521,8 +1552,8 @@ static CURLcode check_and_set_expiry(struct Curl_cfilter *cf, timediff_t timeoutms; int is_infinite = TRUE; - if(ctx->tls.ssl && - SSL_get_event_timeout(ctx->tls.ssl, &tv, &is_infinite) && + if(ctx->tls.ossl.ssl && + SSL_get_event_timeout(ctx->tls.ossl.ssl, &tv, &is_infinite) && !is_infinite) { timeoutms = curlx_tvtoms(&tv); /* QUIC want to be called again latest at the returned timeout */ @@ -1533,7 +1564,7 @@ static CURLcode check_and_set_expiry(struct Curl_cfilter *cf, result = cf_progress_egress(cf, data); if(result) goto out; - if(SSL_get_event_timeout(ctx->tls.ssl, &tv, &is_infinite)) { + if(SSL_get_event_timeout(ctx->tls.ossl.ssl, &tv, &is_infinite)) { timeoutms = curlx_tvtoms(&tv); } } @@ -1578,7 +1609,7 @@ static CURLcode cf_osslq_connect(struct Curl_cfilter *cf, goto out; } - if(!ctx->tls.ssl) { + if(!ctx->tls.ossl.ssl) { ctx->started_at = now; result = cf_osslq_ctx_start(cf, data); if(result) @@ -1594,7 +1625,7 @@ static CURLcode cf_osslq_connect(struct Curl_cfilter *cf, } ERR_clear_error(); - err = SSL_do_handshake(ctx->tls.ssl); + err = SSL_do_handshake(ctx->tls.ossl.ssl); if(err == 1) { /* connected */ @@ -1612,7 +1643,7 @@ static CURLcode cf_osslq_connect(struct Curl_cfilter *cf, } } else { - int detail = SSL_get_error(ctx->tls.ssl, err); + int detail = SSL_get_error(ctx->tls.ossl.ssl, err); switch(detail) { case SSL_ERROR_WANT_READ: ctx->q.last_io = now; @@ -1643,7 +1674,8 @@ static CURLcode cf_osslq_connect(struct Curl_cfilter *cf, } out: - if(result == CURLE_RECV_ERROR && ctx->tls.ssl && ctx->protocol_shutdown) { + if(result == CURLE_RECV_ERROR && ctx->tls.ossl.ssl && + ctx->protocol_shutdown) { /* When a QUIC server instance is shutting down, it may send us a * CONNECTION_CLOSE right away. Our connection then enters the DRAINING * state. The CONNECT may work in the near future again. Indicate @@ -1689,7 +1721,7 @@ static ssize_t h3_stream_open(struct Curl_cfilter *cf, *err = h3_data_setup(cf, data); if(*err) goto out; - stream = H3_STREAM_CTX(data); + stream = H3_STREAM_CTX(ctx, data); DEBUGASSERT(stream); if(!stream) { *err = CURLE_FAILED_INIT; @@ -1731,7 +1763,7 @@ static ssize_t h3_stream_open(struct Curl_cfilter *cf, } DEBUGASSERT(stream->s.id == -1); - *err = cf_osslq_stream_open(&stream->s, ctx->tls.ssl, 0, + *err = cf_osslq_stream_open(&stream->s, ctx->tls.ossl.ssl, 0, &ctx->stream_bufcp, data); if(*err) { failf(data, "can't get bidi streams"); @@ -1768,11 +1800,11 @@ static ssize_t h3_stream_open(struct Curl_cfilter *cf, if(rc) { switch(rc) { case NGHTTP3_ERR_CONN_CLOSING: - CURL_TRC_CF(data, cf, "h3sid[%"PRId64"] failed to send, " + CURL_TRC_CF(data, cf, "h3sid[%"CURL_PRId64"] failed to send, " "connection is closing", stream->s.id); break; default: - CURL_TRC_CF(data, cf, "h3sid[%"PRId64"] failed to send -> %d (%s)", + CURL_TRC_CF(data, cf, "h3sid[%"CURL_PRId64 "] failed to send -> %d (%s)", stream->s.id, rc, nghttp3_strerror(rc)); break; } @@ -1782,10 +1814,11 @@ static ssize_t h3_stream_open(struct Curl_cfilter *cf, } if(Curl_trc_is_verbose(data)) { - infof(data, "[HTTP/3] [%" PRId64 "] OPENED stream for %s", + infof(data, "[HTTP/3] [%" CURL_PRId64 "] OPENED stream for %s", stream->s.id, data->state.url); for(i = 0; i < nheader; ++i) { - infof(data, "[HTTP/3] [%" PRId64 "] [%.*s: %.*s]", stream->s.id, + infof(data, "[HTTP/3] [%" CURL_PRId64 "] [%.*s: %.*s]", + stream->s.id, (int)nva[i].namelen, nva[i].name, (int)nva[i].valuelen, nva[i].value); } @@ -1801,14 +1834,14 @@ static ssize_t cf_osslq_send(struct Curl_cfilter *cf, struct Curl_easy *data, const void *buf, size_t len, CURLcode *err) { struct cf_osslq_ctx *ctx = cf->ctx; - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); struct cf_call_data save; ssize_t nwritten; CURLcode result; CF_DATA_SAVE(save, cf, data); DEBUGASSERT(cf->connected); - DEBUGASSERT(ctx->tls.ssl); + DEBUGASSERT(ctx->tls.ossl.ssl); DEBUGASSERT(ctx->h3.conn); *err = CURLE_OK; @@ -1832,7 +1865,7 @@ static ssize_t cf_osslq_send(struct Curl_cfilter *cf, struct Curl_easy *data, CURL_TRC_CF(data, cf, "failed to open stream -> %d", *err); goto out; } - stream = H3_STREAM_CTX(data); + stream = H3_STREAM_CTX(ctx, data); } else if(stream->upload_blocked_len) { /* the data in `buf` has already been submitted or added to the @@ -1856,13 +1889,13 @@ static ssize_t cf_osslq_send(struct Curl_cfilter *cf, struct Curl_easy *data, * body. This happens on 30x or 40x responses. * We silently discard the data sent, since this is not a transport * error situation. */ - CURL_TRC_CF(data, cf, "[%" PRId64 "] discarding data" + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] discarding data" "on closed stream with response", stream->s.id); *err = CURLE_OK; nwritten = (ssize_t)len; goto out; } - CURL_TRC_CF(data, cf, "[%" PRId64 "] send_body(len=%zu) " + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] send_body(len=%zu) " "-> stream closed", stream->s.id, len); *err = CURLE_HTTP3; nwritten = -1; @@ -1870,7 +1903,7 @@ static ssize_t cf_osslq_send(struct Curl_cfilter *cf, struct Curl_easy *data, } else { nwritten = Curl_bufq_write(&stream->sendbuf, buf, len, err); - CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_send, add to " + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] cf_send, add to " "sendbuf(len=%zu) -> %zd, %d", stream->s.id, len, nwritten, *err); if(nwritten < 0) { @@ -1891,7 +1924,7 @@ static ssize_t cf_osslq_send(struct Curl_cfilter *cf, struct Curl_easy *data, * caller. Instead we EAGAIN and remember how much we have already * "written" into our various internal connection buffers. */ stream->upload_blocked_len = nwritten; - CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_send(len=%zu), " + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] cf_send(len=%zu), " "%zu bytes in flight -> EGAIN", stream->s.id, len, stream->sendbuf_len_in_flight); *err = CURLE_AGAIN; @@ -1900,7 +1933,7 @@ static ssize_t cf_osslq_send(struct Curl_cfilter *cf, struct Curl_easy *data, out: result = check_and_set_expiry(cf, data); - CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_send(len=%zu) -> %zd, %d", + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] cf_send(len=%zu) -> %zd, %d", stream? stream->s.id : -1, len, nwritten, *err); CF_DATA_RESTORE(cf, save); return nwritten; @@ -1916,13 +1949,15 @@ static ssize_t recv_closed_stream(struct Curl_cfilter *cf, (void)cf; if(stream->reset) { failf(data, - "HTTP/3 stream %" PRId64 " reset by server", stream->s.id); + "HTTP/3 stream %" CURL_PRId64 " reset by server", + stream->s.id); *err = data->req.bytecount? CURLE_PARTIAL_FILE : CURLE_HTTP3; goto out; } else if(!stream->resp_hds_complete) { failf(data, - "HTTP/3 stream %" PRId64 " was closed cleanly, but before getting" + "HTTP/3 stream %" CURL_PRId64 + " was closed cleanly, but before getting" " all response header fields, treated as error", stream->s.id); *err = CURLE_HTTP3; @@ -1939,7 +1974,7 @@ static ssize_t cf_osslq_recv(struct Curl_cfilter *cf, struct Curl_easy *data, char *buf, size_t len, CURLcode *err) { struct cf_osslq_ctx *ctx = cf->ctx; - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); ssize_t nread = -1; struct cf_call_data save; CURLcode result; @@ -1948,7 +1983,7 @@ static ssize_t cf_osslq_recv(struct Curl_cfilter *cf, struct Curl_easy *data, CF_DATA_SAVE(save, cf, data); DEBUGASSERT(cf->connected); DEBUGASSERT(ctx); - DEBUGASSERT(ctx->tls.ssl); + DEBUGASSERT(ctx->tls.ossl.ssl); DEBUGASSERT(ctx->h3.conn); *err = CURLE_OK; @@ -1961,7 +1996,7 @@ static ssize_t cf_osslq_recv(struct Curl_cfilter *cf, struct Curl_easy *data, nread = Curl_bufq_read(&stream->recvbuf, (unsigned char *)buf, len, err); if(nread < 0) { - CURL_TRC_CF(data, cf, "[%" PRId64 "] read recvbuf(len=%zu) " + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] read recvbuf(len=%zu) " "-> %zd, %d", stream->s.id, len, nread, *err); goto out; } @@ -1979,7 +2014,7 @@ static ssize_t cf_osslq_recv(struct Curl_cfilter *cf, struct Curl_easy *data, nread = Curl_bufq_read(&stream->recvbuf, (unsigned char *)buf, len, err); if(nread < 0) { - CURL_TRC_CF(data, cf, "[%" PRId64 "] read recvbuf(len=%zu) " + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] read recvbuf(len=%zu) " "-> %zd, %d", stream->s.id, len, nread, *err); goto out; } @@ -2009,7 +2044,7 @@ out: nread = -1; } } - CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_recv(len=%zu) -> %zd, %d", + CURL_TRC_CF(data, cf, "[%" CURL_PRId64 "] cf_recv(len=%zu) -> %zd, %d", stream? stream->s.id : -1, len, nread, *err); CF_DATA_RESTORE(cf, save); return nread; @@ -2022,7 +2057,8 @@ out: static bool cf_osslq_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data) { - const struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_osslq_ctx *ctx = cf->ctx; + const struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); (void)cf; return stream && !Curl_bufq_is_empty(&stream->recvbuf); } @@ -2051,7 +2087,7 @@ static CURLcode cf_osslq_data_event(struct Curl_cfilter *cf, h3_data_done(cf, data); break; case CF_CTRL_DATA_DONE_SEND: { - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); if(stream && !stream->send_closed) { stream->send_closed = TRUE; stream->upload_left = Curl_bufq_len(&stream->sendbuf); @@ -2060,7 +2096,7 @@ static CURLcode cf_osslq_data_event(struct Curl_cfilter *cf, break; } case CF_CTRL_DATA_IDLE: { - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); CURL_TRC_CF(data, cf, "data idle"); if(stream && !stream->closed) { result = check_and_set_expiry(cf, data); @@ -2084,7 +2120,7 @@ static bool cf_osslq_conn_is_alive(struct Curl_cfilter *cf, CF_DATA_SAVE(save, cf, data); *input_pending = FALSE; - if(!ctx->tls.ssl) + if(!ctx->tls.ossl.ssl) goto out; #ifdef SSL_VALUE_QUIC_IDLE_TIMEOUT @@ -2092,7 +2128,8 @@ static bool cf_osslq_conn_is_alive(struct Curl_cfilter *cf, { timediff_t idletime; uint64_t idle_ms = ctx->max_idle_ms; - if(!SSL_get_value_uint(ctx->tls.ssl, SSL_VALUE_CLASS_FEATURE_NEGOTIATED, + if(!SSL_get_value_uint(ctx->tls.ossl.ssl, + SSL_VALUE_CLASS_FEATURE_NEGOTIATED, SSL_VALUE_QUIC_IDLE_TIMEOUT, &idle_ms)) { CURL_TRC_CF(data, cf, "error getting negotiated idle timeout, " "assume connection is dead."); @@ -2132,15 +2169,15 @@ static void cf_osslq_adjust_pollset(struct Curl_cfilter *cf, { struct cf_osslq_ctx *ctx = cf->ctx; - if(!ctx->tls.ssl) { + if(!ctx->tls.ossl.ssl) { /* NOP */ } else if(!cf->connected) { /* during handshake, transfer has not started yet. we always * add our socket for polling if SSL wants to send/recv */ Curl_pollset_set(data, ps, ctx->q.sockfd, - SSL_net_read_desired(ctx->tls.ssl), - SSL_net_write_desired(ctx->tls.ssl)); + SSL_net_read_desired(ctx->tls.ossl.ssl), + SSL_net_write_desired(ctx->tls.ossl.ssl)); } else { /* once connected, we only modify the socket if it is present. @@ -2149,8 +2186,8 @@ static void cf_osslq_adjust_pollset(struct Curl_cfilter *cf, Curl_pollset_check(data, ps, ctx->q.sockfd, &want_recv, &want_send); if(want_recv || want_send) { Curl_pollset_set(data, ps, ctx->q.sockfd, - SSL_net_read_desired(ctx->tls.ssl), - SSL_net_write_desired(ctx->tls.ssl)); + SSL_net_read_desired(ctx->tls.ossl.ssl), + SSL_net_write_desired(ctx->tls.ossl.ssl)); } } } @@ -2166,7 +2203,7 @@ static CURLcode cf_osslq_query(struct Curl_cfilter *cf, #ifdef SSL_VALUE_QUIC_STREAM_BIDI_LOCAL_AVAIL /* Added in OpenSSL v3.3.x */ uint64_t v; - if(!SSL_get_value_uint(ctx->tls.ssl, SSL_VALUE_CLASS_GENERIC, + if(!SSL_get_value_uint(ctx->tls.ossl.ssl, SSL_VALUE_CLASS_GENERIC, SSL_VALUE_QUIC_STREAM_BIDI_LOCAL_AVAIL, &v)) { CURL_TRC_CF(data, cf, "error getting available local bidi streams"); return CURLE_HTTP3; diff --git a/lib/vquic/curl_quiche.c b/lib/vquic/curl_quiche.c index 2ebc50c..a68fc64 100644 --- a/lib/vquic/curl_quiche.c +++ b/lib/vquic/curl_quiche.c @@ -29,6 +29,7 @@ #include <openssl/err.h> #include <openssl/ssl.h> #include "bufq.h" +#include "hash.h" #include "urldata.h" #include "cfilters.h" #include "cf-socket.h" @@ -88,7 +89,7 @@ void Curl_quiche_ver(char *p, size_t len) struct cf_quiche_ctx { struct cf_quic_ctx q; struct ssl_peer peer; - struct quic_tls_ctx tls; + struct curl_tls_ctx tls; quiche_conn *qconn; quiche_config *cfg; quiche_h3_conn *h3c; @@ -98,8 +99,8 @@ struct cf_quiche_ctx { struct curltime handshake_at; /* time connect handshake finished */ struct curltime reconnect_at; /* time the next attempt should start */ struct bufc_pool stream_bufcp; /* chunk pool for streams */ + struct Curl_hash streams; /* hash `data->id` to `stream_ctx` */ curl_off_t data_recvd; - uint64_t max_idle_ms; /* max idle time for QUIC conn */ BIT(goaway); /* got GOAWAY from server */ BIT(x509_store_setup); /* if x509 store has been set up */ }; @@ -123,55 +124,71 @@ static void cf_quiche_ctx_clear(struct cf_quiche_ctx *ctx) quiche_conn_free(ctx->qconn); if(ctx->cfg) quiche_config_free(ctx->cfg); - /* quiche just freed ctx->tls.ssl */ - ctx->tls.ssl = NULL; + /* quiche just freed it */ + ctx->tls.ossl.ssl = NULL; Curl_vquic_tls_cleanup(&ctx->tls); Curl_ssl_peer_cleanup(&ctx->peer); vquic_ctx_free(&ctx->q); Curl_bufcp_free(&ctx->stream_bufcp); + Curl_hash_clean(&ctx->streams); + Curl_hash_destroy(&ctx->streams); memset(ctx, 0, sizeof(*ctx)); } } +static CURLcode cf_flush_egress(struct Curl_cfilter *cf, + struct Curl_easy *data); + /** * All about the H3 internals of a stream */ struct stream_ctx { - int64_t id; /* HTTP/3 protocol stream identifier */ + curl_uint64_t id; /* HTTP/3 protocol stream identifier */ struct bufq recvbuf; /* h3 response */ struct h1_req_parser h1; /* h1 request parsing */ - uint64_t error3; /* HTTP/3 stream error code */ + curl_uint64_t error3; /* HTTP/3 stream error code */ curl_off_t upload_left; /* number of request bytes left to upload */ - bool closed; /* TRUE on stream close */ - bool reset; /* TRUE on stream reset */ - bool send_closed; /* stream is locally closed */ - bool resp_hds_complete; /* complete, final response has been received */ - bool resp_got_header; /* TRUE when h3 stream has recvd some HEADER */ + BIT(opened); /* TRUE after stream has been opened */ + BIT(closed); /* TRUE on stream close */ + BIT(reset); /* TRUE on stream reset */ + BIT(send_closed); /* stream is locally closed */ + BIT(resp_hds_complete); /* final response has been received */ + BIT(resp_got_header); /* TRUE when h3 stream has recvd some HEADER */ BIT(quic_flow_blocked); /* stream is blocked by QUIC flow control */ }; -#define H3_STREAM_CTX(d) ((struct stream_ctx *)(((d) && (d)->req.p.http)? \ - ((struct HTTP *)(d)->req.p.http)->h3_ctx \ - : NULL)) -#define H3_STREAM_LCTX(d) ((struct HTTP *)(d)->req.p.http)->h3_ctx -#define H3_STREAM_ID(d) (H3_STREAM_CTX(d)? \ - H3_STREAM_CTX(d)->id : -2) +#define H3_STREAM_CTX(ctx,data) ((struct stream_ctx *)(\ + data? Curl_hash_offt_get(&(ctx)->streams, (data)->id) : NULL)) + +static void h3_stream_ctx_free(struct stream_ctx *stream) +{ + Curl_bufq_free(&stream->recvbuf); + Curl_h1_req_parse_free(&stream->h1); + free(stream); +} + +static void h3_stream_hash_free(void *stream) +{ + DEBUGASSERT(stream); + h3_stream_ctx_free((struct stream_ctx *)stream); +} static void check_resumes(struct Curl_cfilter *cf, struct Curl_easy *data) { + struct cf_quiche_ctx *ctx = cf->ctx; struct Curl_easy *sdata; struct stream_ctx *stream; DEBUGASSERT(data->multi); for(sdata = data->multi->easyp; sdata; sdata = sdata->next) { if(sdata->conn == data->conn) { - stream = H3_STREAM_CTX(sdata); + stream = H3_STREAM_CTX(ctx, sdata); if(stream && stream->quic_flow_blocked) { stream->quic_flow_blocked = FALSE; Curl_expire(data, 0, EXPIRE_RUN_NOW); - CURL_TRC_CF(data, cf, "[%"PRId64"] unblock", stream->id); + CURL_TRC_CF(data, cf, "[%"CURL_PRIu64"] unblock", stream->id); } } } @@ -181,7 +198,7 @@ static CURLcode h3_data_setup(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_quiche_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); if(stream) return CURLE_OK; @@ -190,22 +207,28 @@ static CURLcode h3_data_setup(struct Curl_cfilter *cf, if(!stream) return CURLE_OUT_OF_MEMORY; - H3_STREAM_LCTX(data) = stream; stream->id = -1; Curl_bufq_initp(&stream->recvbuf, &ctx->stream_bufcp, H3_STREAM_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT); Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN); + + if(!Curl_hash_offt_set(&ctx->streams, data->id, stream)) { + h3_stream_ctx_free(stream); + return CURLE_OUT_OF_MEMORY; + } + return CURLE_OK; } static void h3_data_done(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_quiche_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); + CURLcode result; (void)cf; if(stream) { - CURL_TRC_CF(data, cf, "[%"PRId64"] easy handle is done", stream->id); + CURL_TRC_CF(data, cf, "[%"CURL_PRIu64"] easy handle is done", stream->id); if(ctx->qconn && !stream->closed) { quiche_conn_stream_shutdown(ctx->qconn, stream->id, QUICHE_SHUTDOWN_READ, CURL_H3_NO_ERROR); @@ -215,18 +238,19 @@ static void h3_data_done(struct Curl_cfilter *cf, struct Curl_easy *data) stream->send_closed = TRUE; } stream->closed = TRUE; + result = cf_flush_egress(cf, data); + if(result) + CURL_TRC_CF(data, cf, "data_done, flush egress -> %d", result); } - Curl_bufq_free(&stream->recvbuf); - Curl_h1_req_parse_free(&stream->h1); - free(stream); - H3_STREAM_LCTX(data) = NULL; + Curl_hash_offt_remove(&ctx->streams, data->id); } } static void drain_stream(struct Curl_cfilter *cf, struct Curl_easy *data) { - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_quiche_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); unsigned char bits; (void)cf; @@ -241,25 +265,50 @@ static void drain_stream(struct Curl_cfilter *cf, static struct Curl_easy *get_stream_easy(struct Curl_cfilter *cf, struct Curl_easy *data, - int64_t stream3_id) + curl_uint64_t stream_id, + struct stream_ctx **pstream) { + struct cf_quiche_ctx *ctx = cf->ctx; struct Curl_easy *sdata; + struct stream_ctx *stream; (void)cf; - if(H3_STREAM_ID(data) == stream3_id) { + stream = H3_STREAM_CTX(ctx, data); + if(stream && stream->id == stream_id) { + *pstream = stream; return data; } else { DEBUGASSERT(data->multi); for(sdata = data->multi->easyp; sdata; sdata = sdata->next) { - if((sdata->conn == data->conn) && H3_STREAM_ID(sdata) == stream3_id) { + if(sdata->conn != data->conn) + continue; + stream = H3_STREAM_CTX(ctx, sdata); + if(stream && stream->id == stream_id) { + *pstream = stream; return sdata; } } } + *pstream = NULL; return NULL; } +static void cf_quiche_expire_conn_closed(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct Curl_easy *sdata; + + DEBUGASSERT(data->multi); + CURL_TRC_CF(data, cf, "conn closed, expire all transfers"); + for(sdata = data->multi->easyp; sdata; sdata = sdata->next) { + if(sdata == data || sdata->conn != data->conn) + continue; + CURL_TRC_CF(sdata, cf, "conn closed, expire transfer"); + Curl_expire(sdata, 0, EXPIRE_RUN_NOW); + } +} + /* * write_resp_raw() copies response data in raw format to the `data`'s * receive buffer. If not enough space is available, it appends to the @@ -269,7 +318,8 @@ static CURLcode write_resp_raw(struct Curl_cfilter *cf, struct Curl_easy *data, const void *mem, size_t memlen) { - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_quiche_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); CURLcode result = CURLE_OK; ssize_t nwritten; @@ -299,14 +349,15 @@ static int cb_each_header(uint8_t *name, size_t name_len, void *argp) { struct cb_ctx *x = argp; - struct stream_ctx *stream = H3_STREAM_CTX(x->data); + struct cf_quiche_ctx *ctx = x->cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(ctx, x->data); CURLcode result; if(!stream) return CURLE_OK; if((name_len == 7) && !strncmp(HTTP_PSEUDO_STATUS, (char *)name, 7)) { - CURL_TRC_CF(x->data, x->cf, "[%" PRId64 "] status: %.*s", + CURL_TRC_CF(x->data, x->cf, "[%" CURL_PRIu64 "] status: %.*s", stream->id, (int)value_len, value); result = write_resp_raw(x->cf, x->data, "HTTP/3 ", sizeof("HTTP/3 ") - 1); if(!result) @@ -315,7 +366,7 @@ static int cb_each_header(uint8_t *name, size_t name_len, result = write_resp_raw(x->cf, x->data, " \r\n", 3); } else { - CURL_TRC_CF(x->data, x->cf, "[%" PRId64 "] header: %.*s: %.*s", + CURL_TRC_CF(x->data, x->cf, "[%" CURL_PRIu64 "] header: %.*s: %.*s", stream->id, (int)name_len, name, (int)value_len, value); result = write_resp_raw(x->cf, x->data, name, name_len); @@ -327,7 +378,7 @@ static int cb_each_header(uint8_t *name, size_t name_len, result = write_resp_raw(x->cf, x->data, "\r\n", 2); } if(result) { - CURL_TRC_CF(x->data, x->cf, "[%"PRId64"] on header error %d", + CURL_TRC_CF(x->data, x->cf, "[%"CURL_PRIu64"] on header error %d", stream->id, result); } return result; @@ -339,7 +390,7 @@ static ssize_t stream_resp_read(void *reader_ctx, { struct cb_ctx *x = reader_ctx; struct cf_quiche_ctx *ctx = x->cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(x->data); + struct stream_ctx *stream = H3_STREAM_CTX(ctx, x->data); ssize_t nread; if(!stream) { @@ -362,7 +413,8 @@ static ssize_t stream_resp_read(void *reader_ctx, static CURLcode cf_recv_body(struct Curl_cfilter *cf, struct Curl_easy *data) { - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_quiche_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); ssize_t nwritten; struct cb_ctx cb_ctx; CURLcode result = CURLE_OK; @@ -383,9 +435,9 @@ static CURLcode cf_recv_body(struct Curl_cfilter *cf, stream_resp_read, &cb_ctx, &result); if(nwritten < 0 && result != CURLE_AGAIN) { - CURL_TRC_CF(data, cf, "[%"PRId64"] recv_body error %zd", + CURL_TRC_CF(data, cf, "[%"CURL_PRIu64"] recv_body error %zd", stream->id, nwritten); - failf(data, "Error %d in HTTP/3 response body for stream[%"PRId64"]", + failf(data, "Error %d in HTTP/3 response body for stream[%"CURL_PRIu64"]", result, stream->id); stream->closed = TRUE; stream->reset = TRUE; @@ -420,17 +472,15 @@ static const char *cf_ev_name(quiche_h3_event *ev) static CURLcode h3_process_event(struct Curl_cfilter *cf, struct Curl_easy *data, - int64_t stream3_id, + struct stream_ctx *stream, quiche_h3_event *ev) { - struct stream_ctx *stream = H3_STREAM_CTX(data); struct cb_ctx cb_ctx; CURLcode result = CURLE_OK; int rc; if(!stream) return CURLE_OK; - DEBUGASSERT(stream3_id == stream->id); switch(quiche_h3_event_type(ev)) { case QUICHE_H3_EVENT_HEADERS: stream->resp_got_header = TRUE; @@ -438,11 +488,11 @@ static CURLcode h3_process_event(struct Curl_cfilter *cf, cb_ctx.data = data; rc = quiche_h3_event_for_each_header(ev, cb_each_header, &cb_ctx); if(rc) { - failf(data, "Error %d in HTTP/3 response header for stream[%"PRId64"]", - rc, stream3_id); + failf(data, "Error %d in HTTP/3 response header for stream[%" + CURL_PRIu64"]", rc, stream->id); return CURLE_RECV_ERROR; } - CURL_TRC_CF(data, cf, "[%"PRId64"] <- [HEADERS]", stream3_id); + CURL_TRC_CF(data, cf, "[%"CURL_PRIu64"] <- [HEADERS]", stream->id); break; case QUICHE_H3_EVENT_DATA: @@ -452,7 +502,7 @@ static CURLcode h3_process_event(struct Curl_cfilter *cf, break; case QUICHE_H3_EVENT_RESET: - CURL_TRC_CF(data, cf, "[%"PRId64"] RESET", stream3_id); + CURL_TRC_CF(data, cf, "[%"CURL_PRIu64"] RESET", stream->id); stream->closed = TRUE; stream->reset = TRUE; stream->send_closed = TRUE; @@ -460,7 +510,7 @@ static CURLcode h3_process_event(struct Curl_cfilter *cf, break; case QUICHE_H3_EVENT_FINISHED: - CURL_TRC_CF(data, cf, "[%"PRId64"] CLOSED", stream3_id); + CURL_TRC_CF(data, cf, "[%"CURL_PRIu64"] CLOSED", stream->id); if(!stream->resp_hds_complete) { result = write_resp_raw(cf, data, "\r\n", 2); if(result) @@ -472,12 +522,12 @@ static CURLcode h3_process_event(struct Curl_cfilter *cf, break; case QUICHE_H3_EVENT_GOAWAY: - CURL_TRC_CF(data, cf, "[%"PRId64"] <- [GOAWAY]", stream3_id); + CURL_TRC_CF(data, cf, "[%"CURL_PRIu64"] <- [GOAWAY]", stream->id); break; default: - CURL_TRC_CF(data, cf, "[%"PRId64"] recv, unhandled event %d", - stream3_id, quiche_h3_event_type(ev)); + CURL_TRC_CF(data, cf, "[%"CURL_PRIu64"] recv, unhandled event %d", + stream->id, quiche_h3_event_type(ev)); break; } return result; @@ -487,36 +537,33 @@ static CURLcode cf_poll_events(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_quiche_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct stream_ctx *stream = NULL; struct Curl_easy *sdata; quiche_h3_event *ev; CURLcode result; /* Take in the events and distribute them to the transfers. */ while(ctx->h3c) { - int64_t stream3_id = quiche_h3_conn_poll(ctx->h3c, ctx->qconn, &ev); + curl_int64_t stream3_id = quiche_h3_conn_poll(ctx->h3c, ctx->qconn, &ev); if(stream3_id == QUICHE_H3_ERR_DONE) { break; } else if(stream3_id < 0) { - CURL_TRC_CF(data, cf, "[%"PRId64"] error poll: %"PRId64, - stream? stream->id : -1, stream3_id); + CURL_TRC_CF(data, cf, "error poll: %"CURL_PRId64, stream3_id); return CURLE_HTTP3; } - sdata = get_stream_easy(cf, data, stream3_id); - if(!sdata) { - CURL_TRC_CF(data, cf, "[%"PRId64"] discard event %s for " - "unknown [%"PRId64"]", - stream? stream->id : -1, cf_ev_name(ev), stream3_id); + sdata = get_stream_easy(cf, data, stream3_id, &stream); + if(!sdata || !stream) { + CURL_TRC_CF(data, cf, "discard event %s for unknown [%"CURL_PRId64"]", + cf_ev_name(ev), stream3_id); } else { - result = h3_process_event(cf, sdata, stream3_id, ev); + result = h3_process_event(cf, sdata, stream, ev); drain_stream(cf, sdata); if(result) { - CURL_TRC_CF(data, cf, "[%"PRId64"] error processing event %s " - "for [%"PRId64"] -> %d", - stream? stream->id : -1, cf_ev_name(ev), + CURL_TRC_CF(data, cf, "error processing event %s " + "for [%"CURL_PRIu64"] -> %d", cf_ev_name(ev), stream3_id, result); if(data == sdata) { /* Only report this error to the caller if it is about the @@ -560,11 +607,19 @@ static CURLcode recv_pkt(const unsigned char *pkt, size_t pktlen, &recv_info); if(nread < 0) { if(QUICHE_ERR_DONE == nread) { + if(quiche_conn_is_draining(ctx->qconn)) { + CURL_TRC_CF(r->data, r->cf, "ingress, connection is draining"); + return CURLE_RECV_ERROR; + } + if(quiche_conn_is_closed(ctx->qconn)) { + CURL_TRC_CF(r->data, r->cf, "ingress, connection is closed"); + return CURLE_RECV_ERROR; + } CURL_TRC_CF(r->data, r->cf, "ingress, quiche is DONE"); return CURLE_OK; } else if(QUICHE_ERR_TLS_FAIL == nread) { - long verify_ok = SSL_get_verify_result(ctx->tls.ssl); + long verify_ok = SSL_get_verify_result(ctx->tls.ossl.ssl); if(verify_ok != X509_V_OK) { failf(r->data, "SSL certificate problem: %s", X509_verify_cert_error_string(verify_ok)); @@ -651,8 +706,8 @@ static CURLcode cf_flush_egress(struct Curl_cfilter *cf, struct cf_quiche_ctx *ctx = cf->ctx; ssize_t nread; CURLcode result; - int64_t expiry_ns; - int64_t timeout_ns; + curl_int64_t expiry_ns; + curl_int64_t timeout_ns; struct read_ctx readx; size_t pkt_count, gsolen; @@ -660,7 +715,13 @@ static CURLcode cf_flush_egress(struct Curl_cfilter *cf, if(!expiry_ns) { quiche_conn_on_timeout(ctx->qconn); if(quiche_conn_is_closed(ctx->qconn)) { - failf(data, "quiche_conn_on_timeout closed the connection"); + if(quiche_conn_is_timed_out(ctx->qconn)) + failf(data, "connection closed by idle timeout"); + else + failf(data, "connection closed by server"); + /* Connection timed out, expire all transfers belonging to it + * as will not get any more POLL events here. */ + cf_quiche_expire_conn_closed(cf, data); return CURLE_SEND_ERROR; } } @@ -725,25 +786,26 @@ static ssize_t recv_closed_stream(struct Curl_cfilter *cf, struct Curl_easy *data, CURLcode *err) { - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_quiche_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); ssize_t nread = -1; DEBUGASSERT(stream); if(stream->reset) { failf(data, - "HTTP/3 stream %" PRId64 " reset by server", stream->id); + "HTTP/3 stream %" CURL_PRIu64 " reset by server", stream->id); *err = data->req.bytecount? CURLE_PARTIAL_FILE : CURLE_HTTP3; - CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_recv, was reset -> %d", + CURL_TRC_CF(data, cf, "[%" CURL_PRIu64 "] cf_recv, was reset -> %d", stream->id, *err); } else if(!stream->resp_got_header) { failf(data, - "HTTP/3 stream %" PRId64 " was closed cleanly, but before getting" - " all response header fields, treated as error", + "HTTP/3 stream %" CURL_PRIu64 " was closed cleanly, but before " + "getting all response header fields, treated as error", stream->id); /* *err = CURLE_PARTIAL_FILE; */ *err = CURLE_HTTP3; - CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_recv, closed incomplete" + CURL_TRC_CF(data, cf, "[%" CURL_PRIu64 "] cf_recv, closed incomplete" " -> %d", stream->id, *err); } else { @@ -757,7 +819,7 @@ static ssize_t cf_quiche_recv(struct Curl_cfilter *cf, struct Curl_easy *data, char *buf, size_t len, CURLcode *err) { struct cf_quiche_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); ssize_t nread = -1; CURLcode result; @@ -771,7 +833,7 @@ static ssize_t cf_quiche_recv(struct Curl_cfilter *cf, struct Curl_easy *data, if(!Curl_bufq_is_empty(&stream->recvbuf)) { nread = Curl_bufq_read(&stream->recvbuf, (unsigned char *)buf, len, err); - CURL_TRC_CF(data, cf, "[%" PRId64 "] read recvbuf(len=%zu) " + CURL_TRC_CF(data, cf, "[%" CURL_PRIu64 "] read recvbuf(len=%zu) " "-> %zd, %d", stream->id, len, nread, *err); if(nread < 0) goto out; @@ -788,7 +850,7 @@ static ssize_t cf_quiche_recv(struct Curl_cfilter *cf, struct Curl_easy *data, if(nread < 0 && !Curl_bufq_is_empty(&stream->recvbuf)) { nread = Curl_bufq_read(&stream->recvbuf, (unsigned char *)buf, len, err); - CURL_TRC_CF(data, cf, "[%" PRId64 "] read recvbuf(len=%zu) " + CURL_TRC_CF(data, cf, "[%" CURL_PRIu64 "] read recvbuf(len=%zu) " "-> %zd, %d", stream->id, len, nread, *err); if(nread < 0) goto out; @@ -822,7 +884,7 @@ out: } if(nread > 0) ctx->data_recvd += nread; - CURL_TRC_CF(data, cf, "[%"PRId64"] cf_recv(total=%" + CURL_TRC_CF(data, cf, "[%"CURL_PRIu64"] cf_recv(total=%" CURL_FORMAT_CURL_OFF_T ") -> %zd, %d", stream->id, ctx->data_recvd, nread, *err); return nread; @@ -838,9 +900,9 @@ static ssize_t h3_open_stream(struct Curl_cfilter *cf, CURLcode *err) { struct cf_quiche_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); size_t nheader, i; - int64_t stream3_id; + curl_int64_t stream3_id; struct dynhds h2_headers; quiche_h3_header *nva = NULL; ssize_t nwritten; @@ -850,7 +912,7 @@ static ssize_t h3_open_stream(struct Curl_cfilter *cf, if(*err) { return -1; } - stream = H3_STREAM_CTX(data); + stream = H3_STREAM_CTX(ctx, data); DEBUGASSERT(stream); } @@ -915,14 +977,14 @@ static ssize_t h3_open_stream(struct Curl_cfilter *cf, if(QUICHE_H3_ERR_STREAM_BLOCKED == stream3_id) { /* quiche seems to report this error if the connection window is * exhausted. Which happens frequently and intermittent. */ - CURL_TRC_CF(data, cf, "[%"PRId64"] blocked", stream->id); + CURL_TRC_CF(data, cf, "[%"CURL_PRIu64"] blocked", stream->id); stream->quic_flow_blocked = TRUE; *err = CURLE_AGAIN; nwritten = -1; goto out; } else { - CURL_TRC_CF(data, cf, "send_request(%s) -> %" PRId64, + CURL_TRC_CF(data, cf, "send_request(%s) -> %" CURL_PRIu64, data->state.url, stream3_id); } *err = CURLE_SEND_ERROR; @@ -930,17 +992,18 @@ static ssize_t h3_open_stream(struct Curl_cfilter *cf, goto out; } - DEBUGASSERT(stream->id == -1); + DEBUGASSERT(!stream->opened); *err = CURLE_OK; stream->id = stream3_id; + stream->opened = TRUE; stream->closed = FALSE; stream->reset = FALSE; if(Curl_trc_is_verbose(data)) { - infof(data, "[HTTP/3] [%" PRId64 "] OPENED stream for %s", + infof(data, "[HTTP/3] [%" CURL_PRIu64 "] OPENED stream for %s", stream->id, data->state.url); for(i = 0; i < nheader; ++i) { - infof(data, "[HTTP/3] [%" PRId64 "] [%.*s: %.*s]", stream->id, + infof(data, "[HTTP/3] [%" CURL_PRIu64 "] [%.*s: %.*s]", stream->id, (int)nva[i].name_len, nva[i].name, (int)nva[i].value_len, nva[i].value); } @@ -956,7 +1019,7 @@ static ssize_t cf_quiche_send(struct Curl_cfilter *cf, struct Curl_easy *data, const void *buf, size_t len, CURLcode *err) { struct cf_quiche_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); CURLcode result; ssize_t nwritten; @@ -968,11 +1031,11 @@ static ssize_t cf_quiche_send(struct Curl_cfilter *cf, struct Curl_easy *data, goto out; } - if(!stream || stream->id < 0) { + if(!stream || !stream->opened) { nwritten = h3_open_stream(cf, data, buf, len, err); if(nwritten < 0) goto out; - stream = H3_STREAM_CTX(data); + stream = H3_STREAM_CTX(ctx, data); } else if(stream->closed) { if(stream->resp_hds_complete) { @@ -984,13 +1047,13 @@ static ssize_t cf_quiche_send(struct Curl_cfilter *cf, struct Curl_easy *data, * sending the 30x response. * This is sort of a race: had the transfer loop called recv first, * it would see the response and stop/discard sending on its own- */ - CURL_TRC_CF(data, cf, "[%" PRId64 "] discarding data" + CURL_TRC_CF(data, cf, "[%" CURL_PRIu64 "] discarding data" "on closed stream with response", stream->id); *err = CURLE_OK; nwritten = (ssize_t)len; goto out; } - CURL_TRC_CF(data, cf, "[%" PRId64 "] send_body(len=%zu) " + CURL_TRC_CF(data, cf, "[%" CURL_PRIu64 "] send_body(len=%zu) " "-> stream closed", stream->id, len); *err = CURLE_HTTP3; nwritten = -1; @@ -1005,7 +1068,7 @@ static ssize_t cf_quiche_send(struct Curl_cfilter *cf, struct Curl_easy *data, /* TODO: we seem to be blocked on flow control and should HOLD * sending. But when do we open again? */ if(!quiche_conn_stream_writable(ctx->qconn, stream->id, len)) { - CURL_TRC_CF(data, cf, "[%" PRId64 "] send_body(len=%zu) " + CURL_TRC_CF(data, cf, "[%" CURL_PRIu64 "] send_body(len=%zu) " "-> window exhausted", stream->id, len); stream->quic_flow_blocked = TRUE; } @@ -1014,21 +1077,21 @@ static ssize_t cf_quiche_send(struct Curl_cfilter *cf, struct Curl_easy *data, goto out; } else if(nwritten == QUICHE_H3_TRANSPORT_ERR_INVALID_STREAM_STATE) { - CURL_TRC_CF(data, cf, "[%" PRId64 "] send_body(len=%zu) " + CURL_TRC_CF(data, cf, "[%" CURL_PRIu64 "] send_body(len=%zu) " "-> invalid stream state", stream->id, len); *err = CURLE_HTTP3; nwritten = -1; goto out; } else if(nwritten == QUICHE_H3_TRANSPORT_ERR_FINAL_SIZE) { - CURL_TRC_CF(data, cf, "[%" PRId64 "] send_body(len=%zu) " + CURL_TRC_CF(data, cf, "[%" CURL_PRIu64 "] send_body(len=%zu) " "-> exceeds size", stream->id, len); *err = CURLE_SEND_ERROR; nwritten = -1; goto out; } else if(nwritten < 0) { - CURL_TRC_CF(data, cf, "[%" PRId64 "] send_body(len=%zu) " + CURL_TRC_CF(data, cf, "[%" CURL_PRIu64 "] send_body(len=%zu) " "-> quiche err %zd", stream->id, len, nwritten); *err = CURLE_SEND_ERROR; nwritten = -1; @@ -1043,7 +1106,7 @@ static ssize_t cf_quiche_send(struct Curl_cfilter *cf, struct Curl_easy *data, if(stream->upload_left == 0) stream->send_closed = TRUE; - CURL_TRC_CF(data, cf, "[%" PRId64 "] send body(len=%zu, " + CURL_TRC_CF(data, cf, "[%" CURL_PRIu64 "] send body(len=%zu, " "left=%" CURL_FORMAT_CURL_OFF_T ") -> %zd", stream->id, len, stream->upload_left, nwritten); *err = CURLE_OK; @@ -1056,7 +1119,7 @@ out: *err = result; nwritten = -1; } - CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_send(len=%zu) -> %zd, %d", + CURL_TRC_CF(data, cf, "[%" CURL_PRIu64 "] cf_send(len=%zu) -> %zd, %d", stream? stream->id : -1, len, nwritten, *err); return nwritten; } @@ -1065,10 +1128,10 @@ static bool stream_is_writeable(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_quiche_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); - return stream && (quiche_conn_stream_writable(ctx->qconn, - (uint64_t)stream->id, 1) > 0); + return stream && (quiche_conn_stream_writable( + ctx->qconn, (curl_uint64_t)stream->id, 1) > 0); } static void cf_quiche_adjust_pollset(struct Curl_cfilter *cf, @@ -1083,12 +1146,12 @@ static void cf_quiche_adjust_pollset(struct Curl_cfilter *cf, Curl_pollset_check(data, ps, ctx->q.sockfd, &want_recv, &want_send); if(want_recv || want_send) { - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); bool c_exhaust, s_exhaust; c_exhaust = FALSE; /* Have not found any call in quiche that tells us if the connection itself is blocked */ - s_exhaust = want_send && stream && stream->id >= 0 && + s_exhaust = want_send && stream && stream->opened && (stream->quic_flow_blocked || !stream_is_writeable(cf, data)); want_recv = (want_recv || c_exhaust || s_exhaust); want_send = (!s_exhaust && want_send) || @@ -1105,7 +1168,8 @@ static void cf_quiche_adjust_pollset(struct Curl_cfilter *cf, static bool cf_quiche_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data) { - const struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_quiche_ctx *ctx = cf->ctx; + const struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); (void)cf; return stream && !Curl_bufq_is_empty(&stream->recvbuf); } @@ -1127,6 +1191,7 @@ static CURLcode cf_quiche_data_event(struct Curl_cfilter *cf, struct Curl_easy *data, int event, int arg1, void *arg2) { + struct cf_quiche_ctx *ctx = cf->ctx; CURLcode result = CURLE_OK; (void)arg1; @@ -1144,7 +1209,7 @@ static CURLcode cf_quiche_data_event(struct Curl_cfilter *cf, h3_data_done(cf, data); break; case CF_CTRL_DATA_DONE_SEND: { - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); if(stream && !stream->send_closed) { unsigned char body[1]; ssize_t sent; @@ -1153,13 +1218,13 @@ static CURLcode cf_quiche_data_event(struct Curl_cfilter *cf, stream->upload_left = 0; body[0] = 'X'; sent = cf_quiche_send(cf, data, body, 0, &result); - CURL_TRC_CF(data, cf, "[%"PRId64"] DONE_SEND -> %zd, %d", + CURL_TRC_CF(data, cf, "[%"CURL_PRIu64"] DONE_SEND -> %zd, %d", stream->id, sent, result); } break; } case CF_CTRL_DATA_IDLE: { - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); if(stream && !stream->closed) { result = cf_flush_egress(cf, data); if(result) @@ -1191,16 +1256,16 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, debug_log_init = 1; } #endif - ctx->max_idle_ms = CURL_QUIC_MAX_IDLE_MS; Curl_bufcp_init(&ctx->stream_bufcp, H3_STREAM_CHUNK_SIZE, H3_STREAM_POOL_SPARES); + Curl_hash_offt_init(&ctx->streams, 63, h3_stream_hash_free); ctx->data_recvd = 0; result = vquic_ctx_init(&ctx->q); if(result) return result; - result = Curl_ssl_peer_init(&ctx->peer, cf); + result = Curl_ssl_peer_init(&ctx->peer, cf, TRNSPRT_QUIC); if(result) return result; @@ -1210,7 +1275,7 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, return CURLE_FAILED_INIT; } quiche_config_enable_pacing(ctx->cfg, false); - quiche_config_set_max_idle_timeout(ctx->cfg, ctx->max_idle_ms * 1000); + quiche_config_set_max_idle_timeout(ctx->cfg, CURL_QUIC_MAX_IDLE_MS); quiche_config_set_initial_max_data(ctx->cfg, (1 * 1024 * 1024) /* (QUIC_MAX_STREAMS/2) * H3_STREAM_WINDOW_SIZE */); quiche_config_set_initial_max_streams_bidi(ctx->cfg, QUIC_MAX_STREAMS); @@ -1235,7 +1300,7 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, result = Curl_vquic_tls_init(&ctx->tls, cf, data, &ctx->peer, QUICHE_H3_APPLICATION_PROTOCOL, sizeof(QUICHE_H3_APPLICATION_PROTOCOL) - 1, - NULL, cf); + NULL, NULL, cf); if(result) return result; @@ -1255,7 +1320,7 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, (struct sockaddr *)&ctx->q.local_addr, ctx->q.local_addrlen, &sockaddr->sa_addr, sockaddr->addrlen, - ctx->cfg, ctx->tls.ssl, false); + ctx->cfg, ctx->tls.ossl.ssl, false); if(!ctx->qconn) { failf(data, "can't create quiche connection"); return CURLE_OUT_OF_MEMORY; @@ -1433,12 +1498,14 @@ static CURLcode cf_quiche_query(struct Curl_cfilter *cf, switch(query) { case CF_QUERY_MAX_CONCURRENT: { - uint64_t max_streams = CONN_INUSE(cf->conn); + curl_uint64_t max_streams = CONN_INUSE(cf->conn); if(!ctx->goaway) { max_streams += quiche_conn_peer_streams_left_bidi(ctx->qconn); } *pres1 = (max_streams > INT_MAX)? INT_MAX : (int)max_streams; - CURL_TRC_CF(data, cf, "query: MAX_CONCURRENT -> %d", *pres1); + CURL_TRC_CF(data, cf, "query conn[%" CURL_FORMAT_CURL_OFF_T "]: " + "MAX_CONCURRENT -> %d (%zu in use)", + cf->conn->connection_id, *pres1, CONN_INUSE(cf->conn)); return CURLE_OK; } case CF_QUERY_CONNECT_REPLY_MS: @@ -1480,23 +1547,12 @@ static bool cf_quiche_conn_is_alive(struct Curl_cfilter *cf, if(!ctx->qconn) return FALSE; - /* Both sides of the QUIC connection announce they max idle times in - * the transport parameters. Look at the minimum of both and if - * we exceed this, regard the connection as dead. The other side - * may have completely purged it and will no longer respond - * to any packets from us. */ - { - quiche_transport_params qpeerparams; - timediff_t idletime; - uint64_t idle_ms = ctx->max_idle_ms; - - if(quiche_conn_peer_transport_params(ctx->qconn, &qpeerparams) && - qpeerparams.peer_max_idle_timeout && - qpeerparams.peer_max_idle_timeout < idle_ms) - idle_ms = qpeerparams.peer_max_idle_timeout; - idletime = Curl_timediff(Curl_now(), cf->conn->lastused); - if(idletime > 0 && (uint64_t)idletime > idle_ms) - return FALSE; + if(quiche_conn_is_closed(ctx->qconn)) { + if(quiche_conn_is_timed_out(ctx->qconn)) + CURL_TRC_CF(data, cf, "connection was closed due to idle timeout"); + else + CURL_TRC_CF(data, cf, "connection is closed"); + return FALSE; } if(!cf->next || !cf->next->cft->is_alive(cf->next, data, input_pending)) diff --git a/lib/vquic/vquic-tls.c b/lib/vquic/vquic-tls.c index ecba607..aca18b4 100644 --- a/lib/vquic/vquic-tls.c +++ b/lib/vquic/vquic-tls.c @@ -24,7 +24,7 @@ #include "curl_setup.h" -#if defined(ENABLE_QUIC) && \ +#if defined(USE_HTTP3) && \ (defined(USE_OPENSSL) || defined(USE_GNUTLS) || defined(USE_WOLFSSL)) #ifdef USE_OPENSSL @@ -61,277 +61,12 @@ #define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) #endif -#ifdef USE_OPENSSL -#define QUIC_CIPHERS \ - "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_" \ - "POLY1305_SHA256:TLS_AES_128_CCM_SHA256" -#define QUIC_GROUPS "P-256:X25519:P-384:P-521" -#elif defined(USE_GNUTLS) -#define QUIC_PRIORITY \ - "NORMAL:-VERS-ALL:+VERS-TLS1.3:-CIPHER-ALL:+AES-128-GCM:+AES-256-GCM:" \ - "+CHACHA20-POLY1305:+AES-128-CCM:-GROUP-ALL:+GROUP-SECP256R1:" \ - "+GROUP-X25519:+GROUP-SECP384R1:+GROUP-SECP521R1:" \ - "%DISABLE_TLS13_COMPAT_MODE" -#elif defined(USE_WOLFSSL) +#if defined(USE_WOLFSSL) + #define QUIC_CIPHERS \ "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_" \ "POLY1305_SHA256:TLS_AES_128_CCM_SHA256" #define QUIC_GROUPS "P-256:P-384:P-521" -#endif - - -#ifdef USE_OPENSSL - -static void keylog_callback(const SSL *ssl, const char *line) -{ - (void)ssl; - Curl_tls_keylog_write_line(line); -} - -static CURLcode curl_ossl_init_ctx(struct quic_tls_ctx *ctx, - struct Curl_cfilter *cf, - struct Curl_easy *data, - Curl_vquic_tls_ctx_setup *ctx_setup) -{ - struct ssl_primary_config *conn_config; - CURLcode result = CURLE_FAILED_INIT; - - DEBUGASSERT(!ctx->ssl_ctx); -#ifdef USE_OPENSSL_QUIC - ctx->ssl_ctx = SSL_CTX_new(OSSL_QUIC_client_method()); -#else - ctx->ssl_ctx = SSL_CTX_new(TLS_method()); -#endif - if(!ctx->ssl_ctx) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - conn_config = Curl_ssl_cf_get_primary_config(cf); - if(!conn_config) { - result = CURLE_FAILED_INIT; - goto out; - } - - if(ctx_setup) { - result = ctx_setup(ctx, cf, data); - if(result) - goto out; - } - - SSL_CTX_set_default_verify_paths(ctx->ssl_ctx); - - { - const char *curves = conn_config->curves ? - conn_config->curves : QUIC_GROUPS; - if(!SSL_CTX_set1_curves_list(ctx->ssl_ctx, curves)) { - failf(data, "failed setting curves list for QUIC: '%s'", curves); - return CURLE_SSL_CIPHER; - } - } - -#ifndef OPENSSL_IS_BORINGSSL - { - const char *ciphers13 = conn_config->cipher_list13 ? - conn_config->cipher_list13 : QUIC_CIPHERS; - if(SSL_CTX_set_ciphersuites(ctx->ssl_ctx, ciphers13) != 1) { - failf(data, "failed setting QUIC cipher suite: %s", ciphers13); - return CURLE_SSL_CIPHER; - } - infof(data, "QUIC cipher selection: %s", ciphers13); - } -#endif - - /* Open the file if a TLS or QUIC backend has not done this before. */ - Curl_tls_keylog_open(); - if(Curl_tls_keylog_enabled()) { - SSL_CTX_set_keylog_callback(ctx->ssl_ctx, keylog_callback); - } - - /* OpenSSL always tries to verify the peer, this only says whether it should - * fail to connect if the verification fails, or if it should continue - * anyway. In the latter case the result of the verification is checked with - * SSL_get_verify_result() below. */ - SSL_CTX_set_verify(ctx->ssl_ctx, conn_config->verifypeer ? - SSL_VERIFY_PEER : SSL_VERIFY_NONE, NULL); - - /* give application a chance to interfere with SSL set up. */ - if(data->set.ssl.fsslctx) { - /* When a user callback is installed to modify the SSL_CTX, - * we need to do the full initialization before calling it. - * See: #11800 */ - if(!ctx->x509_store_setup) { - result = Curl_ssl_setup_x509_store(cf, data, ctx->ssl_ctx); - if(result) - goto out; - ctx->x509_store_setup = TRUE; - } - Curl_set_in_callback(data, true); - result = (*data->set.ssl.fsslctx)(data, ctx->ssl_ctx, - data->set.ssl.fsslctxp); - Curl_set_in_callback(data, false); - if(result) { - failf(data, "error signaled by ssl ctx callback"); - goto out; - } - } - result = CURLE_OK; - -out: - if(result && ctx->ssl_ctx) { - SSL_CTX_free(ctx->ssl_ctx); - ctx->ssl_ctx = NULL; - } - return result; -} - -static CURLcode curl_ossl_set_client_cert(struct quic_tls_ctx *ctx, - struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - SSL_CTX *ssl_ctx = ctx->ssl_ctx; - const struct ssl_config_data *ssl_config; - - ssl_config = Curl_ssl_cf_get_config(cf, data); - DEBUGASSERT(ssl_config); - - if(ssl_config->primary.clientcert || - ssl_config->primary.cert_blob || - ssl_config->cert_type) { - return Curl_ossl_set_client_cert( - data, ssl_ctx, ssl_config->primary.clientcert, - ssl_config->primary.cert_blob, ssl_config->cert_type, - ssl_config->key, ssl_config->key_blob, - ssl_config->key_type, ssl_config->key_passwd); - } - - return CURLE_OK; -} - -/** SSL callbacks ***/ - -static CURLcode curl_ossl_init_ssl(struct quic_tls_ctx *ctx, - struct Curl_easy *data, - struct ssl_peer *peer, - const char *alpn, size_t alpn_len, - void *user_data) -{ - DEBUGASSERT(!ctx->ssl); - ctx->ssl = SSL_new(ctx->ssl_ctx); - - SSL_set_app_data(ctx->ssl, user_data); - SSL_set_connect_state(ctx->ssl); -#ifndef USE_OPENSSL_QUIC - SSL_set_quic_use_legacy_codepoint(ctx->ssl, 0); -#endif - - if(alpn) - SSL_set_alpn_protos(ctx->ssl, (const uint8_t *)alpn, (int)alpn_len); - - if(peer->sni) { - if(!SSL_set_tlsext_host_name(ctx->ssl, peer->sni)) { - failf(data, "Failed set SNI"); - SSL_free(ctx->ssl); - ctx->ssl = NULL; - return CURLE_QUIC_CONNECT_ERROR; - } - } - return CURLE_OK; -} - -#elif defined(USE_GNUTLS) -static int keylog_callback(gnutls_session_t session, const char *label, - const gnutls_datum_t *secret) -{ - gnutls_datum_t crandom; - gnutls_datum_t srandom; - - gnutls_session_get_random(session, &crandom, &srandom); - if(crandom.size != 32) { - return -1; - } - - Curl_tls_keylog_write(label, crandom.data, secret->data, secret->size); - return 0; -} - -static CURLcode curl_gtls_init_ctx(struct quic_tls_ctx *ctx, - struct Curl_cfilter *cf, - struct Curl_easy *data, - struct ssl_peer *peer, - const char *alpn, size_t alpn_len, - Curl_vquic_tls_ctx_setup *ctx_setup, - void *user_data) -{ - struct ssl_primary_config *conn_config; - CURLcode result; - gnutls_datum_t alpns[5]; - /* this will need some attention when HTTPS proxy over QUIC get fixed */ - long * const pverifyresult = &data->set.ssl.certverifyresult; - int rc; - - conn_config = Curl_ssl_cf_get_primary_config(cf); - if(!conn_config) - return CURLE_FAILED_INIT; - - DEBUGASSERT(ctx->gtls == NULL); - ctx->gtls = calloc(1, sizeof(*(ctx->gtls))); - if(!ctx->gtls) - return CURLE_OUT_OF_MEMORY; - - result = gtls_client_init(data, conn_config, &data->set.ssl, - peer, ctx->gtls, pverifyresult); - if(result) - return result; - - gnutls_session_set_ptr(ctx->gtls->session, user_data); - - if(ctx_setup) { - result = ctx_setup(ctx, cf, data); - if(result) - return result; - } - - rc = gnutls_priority_set_direct(ctx->gtls->session, QUIC_PRIORITY, NULL); - if(rc < 0) { - CURL_TRC_CF(data, cf, "gnutls_priority_set_direct failed: %s\n", - gnutls_strerror(rc)); - return CURLE_QUIC_CONNECT_ERROR; - } - - /* Open the file if a TLS or QUIC backend has not done this before. */ - Curl_tls_keylog_open(); - if(Curl_tls_keylog_enabled()) { - gnutls_session_set_keylog_function(ctx->gtls->session, keylog_callback); - } - - /* convert the ALPN string from our arguments to a list of strings - * that gnutls wants and will convert internally back to this very - * string for sending to the server. nice. */ - if(alpn) { - size_t i, alen = alpn_len; - unsigned char *s = (unsigned char *)alpn; - unsigned char slen; - for(i = 0; (i < ARRAYSIZE(alpns)) && alen; ++i) { - slen = s[0]; - if(slen >= alen) - return CURLE_FAILED_INIT; - alpns[i].data = s + 1; - alpns[i].size = slen; - s += slen + 1; - alen -= (size_t)slen + 1; - } - if(alen) /* not all alpn chars used, wrong format or too many */ - return CURLE_FAILED_INIT; - if(i) { - gnutls_alpn_set_protocols(ctx->gtls->session, - alpns, (unsigned int)i, - GNUTLS_ALPN_MANDATORY); - } - } - - return CURLE_OK; -} -#elif defined(USE_WOLFSSL) #if defined(HAVE_SECRET_CALLBACK) static void keylog_callback(const WOLFSSL *ssl, const char *line) @@ -341,10 +76,11 @@ static void keylog_callback(const WOLFSSL *ssl, const char *line) } #endif -static CURLcode curl_wssl_init_ctx(struct quic_tls_ctx *ctx, +static CURLcode curl_wssl_init_ctx(struct curl_tls_ctx *ctx, struct Curl_cfilter *cf, struct Curl_easy *data, - Curl_vquic_tls_ctx_setup *ctx_setup) + Curl_vquic_tls_ctx_setup *cb_setup, + void *cb_user_data) { struct ssl_primary_config *conn_config; CURLcode result = CURLE_FAILED_INIT; @@ -361,8 +97,8 @@ static CURLcode curl_wssl_init_ctx(struct quic_tls_ctx *ctx, goto out; } - if(ctx_setup) { - result = ctx_setup(ctx, cf, data); + if(cb_setup) { + result = cb_setup(cf, data, cb_user_data); if(result) goto out; } @@ -458,7 +194,7 @@ out: /** SSL callbacks ***/ -static CURLcode curl_wssl_init_ssl(struct quic_tls_ctx *ctx, +static CURLcode curl_wssl_init_ssl(struct curl_tls_ctx *ctx, struct Curl_easy *data, struct ssl_peer *peer, const char *alpn, size_t alpn_len, @@ -486,57 +222,50 @@ static CURLcode curl_wssl_init_ssl(struct quic_tls_ctx *ctx, } #endif /* defined(USE_WOLFSSL) */ -CURLcode Curl_vquic_tls_init(struct quic_tls_ctx *ctx, +CURLcode Curl_vquic_tls_init(struct curl_tls_ctx *ctx, struct Curl_cfilter *cf, struct Curl_easy *data, struct ssl_peer *peer, const char *alpn, size_t alpn_len, - Curl_vquic_tls_ctx_setup *ctx_setup, - void *user_data) + Curl_vquic_tls_ctx_setup *cb_setup, + void *cb_user_data, void *ssl_user_data) { CURLcode result; #ifdef USE_OPENSSL - result = curl_ossl_init_ctx(ctx, cf, data, ctx_setup); - if(result) - return result; - - result = curl_ossl_set_client_cert(ctx, cf, data); - if(result) - return result; - - return curl_ossl_init_ssl(ctx, data, peer, alpn, alpn_len, user_data); + (void)result; + return Curl_ossl_ctx_init(&ctx->ossl, cf, data, peer, TRNSPRT_QUIC, + (const unsigned char *)alpn, alpn_len, + cb_setup, cb_user_data, NULL, ssl_user_data); #elif defined(USE_GNUTLS) (void)result; - return curl_gtls_init_ctx(ctx, cf, data, peer, alpn, alpn_len, - ctx_setup, user_data); + return Curl_gtls_ctx_init(&ctx->gtls, cf, data, peer, + (const unsigned char *)alpn, alpn_len, + cb_setup, cb_user_data, ssl_user_data); #elif defined(USE_WOLFSSL) - result = curl_wssl_init_ctx(ctx, cf, data, ctx_setup); + result = curl_wssl_init_ctx(ctx, cf, data, cb_setup, cb_user_data); if(result) return result; - return curl_wssl_init_ssl(ctx, data, peer, alpn, alpn_len, user_data); + return curl_wssl_init_ssl(ctx, data, peer, alpn, alpn_len, ssl_user_data); #else #error "no TLS lib in used, should not happen" return CURLE_FAILED_INIT; #endif } -void Curl_vquic_tls_cleanup(struct quic_tls_ctx *ctx) +void Curl_vquic_tls_cleanup(struct curl_tls_ctx *ctx) { #ifdef USE_OPENSSL - if(ctx->ssl) - SSL_free(ctx->ssl); - if(ctx->ssl_ctx) - SSL_CTX_free(ctx->ssl_ctx); + if(ctx->ossl.ssl) + SSL_free(ctx->ossl.ssl); + if(ctx->ossl.ssl_ctx) + SSL_CTX_free(ctx->ossl.ssl_ctx); #elif defined(USE_GNUTLS) - if(ctx->gtls) { - if(ctx->gtls->cred) - gnutls_certificate_free_credentials(ctx->gtls->cred); - if(ctx->gtls->session) - gnutls_deinit(ctx->gtls->session); - free(ctx->gtls); - } + if(ctx->gtls.cred) + gnutls_certificate_free_credentials(ctx->gtls.cred); + if(ctx->gtls.session) + gnutls_deinit(ctx->gtls.session); #elif defined(USE_WOLFSSL) if(ctx->ssl) wolfSSL_free(ctx->ssl); @@ -546,16 +275,22 @@ void Curl_vquic_tls_cleanup(struct quic_tls_ctx *ctx) memset(ctx, 0, sizeof(*ctx)); } -CURLcode Curl_vquic_tls_before_recv(struct quic_tls_ctx *ctx, +CURLcode Curl_vquic_tls_before_recv(struct curl_tls_ctx *ctx, struct Curl_cfilter *cf, struct Curl_easy *data) { #ifdef USE_OPENSSL - if(!ctx->x509_store_setup) { - CURLcode result = Curl_ssl_setup_x509_store(cf, data, ctx->ssl_ctx); + if(!ctx->ossl.x509_store_setup) { + CURLcode result = Curl_ssl_setup_x509_store(cf, data, ctx->ossl.ssl_ctx); + if(result) + return result; + ctx->ossl.x509_store_setup = TRUE; + } +#elif defined(USE_GNUTLS) + if(!ctx->gtls.trust_setup) { + CURLcode result = Curl_gtls_client_trust_setup(cf, data, &ctx->gtls); if(result) return result; - ctx->x509_store_setup = TRUE; } #else (void)ctx; (void)cf; (void)data; @@ -563,7 +298,7 @@ CURLcode Curl_vquic_tls_before_recv(struct quic_tls_ctx *ctx, return CURLE_OK; } -CURLcode Curl_vquic_tls_verify_peer(struct quic_tls_ctx *ctx, +CURLcode Curl_vquic_tls_verify_peer(struct curl_tls_ctx *ctx, struct Curl_cfilter *cf, struct Curl_easy *data, struct ssl_peer *peer) @@ -575,39 +310,33 @@ CURLcode Curl_vquic_tls_verify_peer(struct quic_tls_ctx *ctx, if(!conn_config) return CURLE_FAILED_INIT; - if(conn_config->verifyhost) { #ifdef USE_OPENSSL - X509 *server_cert; - server_cert = SSL_get1_peer_certificate(ctx->ssl); - if(!server_cert) { - return CURLE_PEER_FAILED_VERIFICATION; - } - result = Curl_ossl_verifyhost(data, cf->conn, peer, server_cert); - X509_free(server_cert); - if(result) - return result; + (void)conn_config; + result = Curl_oss_check_peer_cert(cf, data, &ctx->ossl, peer); #elif defined(USE_GNUTLS) - result = Curl_gtls_verifyserver(data, ctx->gtls->session, + if(conn_config->verifyhost) { + result = Curl_gtls_verifyserver(data, ctx->gtls.session, conn_config, &data->set.ssl, peer, data->set.str[STRING_SSL_PINNEDPUBLICKEY]); if(result) return result; + } #elif defined(USE_WOLFSSL) - if(!peer->sni || - wolfSSL_check_domain_name(ctx->ssl, peer->sni) == SSL_FAILURE) - return CURLE_PEER_FAILED_VERIFICATION; -#endif - infof(data, "Verified certificate just fine"); + (void)data; + if(conn_config->verifyhost) { + if(peer->sni) { + WOLFSSL_X509* cert = wolfSSL_get_peer_certificate(ctx->ssl); + if(wolfSSL_X509_check_host(cert, peer->sni, strlen(peer->sni), 0, NULL) + == WOLFSSL_FAILURE) { + result = CURLE_PEER_FAILED_VERIFICATION; + } + wolfSSL_X509_free(cert); + } + } - else - infof(data, "Skipped certificate verification"); -#ifdef USE_OPENSSL - if(data->set.ssl.certinfo) - /* asked to gather certificate info */ - (void)Curl_ossl_certchain(data, ctx->ssl); #endif return result; } -#endif /* !ENABLE_QUIC && (USE_OPENSSL || USE_GNUTLS || USE_WOLFSSL) */ +#endif /* !USE_HTTP3 && (USE_OPENSSL || USE_GNUTLS || USE_WOLFSSL) */ diff --git a/lib/vquic/vquic-tls.h b/lib/vquic/vquic-tls.h index 9c0dfd8..1d35fd0 100644 --- a/lib/vquic/vquic-tls.h +++ b/lib/vquic/vquic-tls.h @@ -26,21 +26,20 @@ #include "curl_setup.h" #include "bufq.h" +#include "vtls/openssl.h" -#if defined(ENABLE_QUIC) && \ +#if defined(USE_HTTP3) && \ (defined(USE_OPENSSL) || defined(USE_GNUTLS) || defined(USE_WOLFSSL)) -struct quic_tls_ctx { +struct curl_tls_ctx { #ifdef USE_OPENSSL - SSL_CTX *ssl_ctx; - SSL *ssl; + struct ossl_ctx ossl; #elif defined(USE_GNUTLS) - struct gtls_instance *gtls; + struct gtls_ctx gtls; #elif defined(USE_WOLFSSL) WOLFSSL_CTX *ssl_ctx; WOLFSSL *ssl; #endif - BIT(x509_store_setup); /* if x509 store has been set up */ }; /** @@ -50,9 +49,9 @@ struct quic_tls_ctx { * - openssl/wolfssl: SSL_CTX* has just been created * - gnutls: gtls_client_init() has run */ -typedef CURLcode Curl_vquic_tls_ctx_setup(struct quic_tls_ctx *ctx, - struct Curl_cfilter *cf, - struct Curl_easy *data); +typedef CURLcode Curl_vquic_tls_ctx_setup(struct Curl_cfilter *cf, + struct Curl_easy *data, + void *cb_user_data); /** * Initialize the QUIC TLS instances based of the SSL configurations @@ -64,23 +63,25 @@ typedef CURLcode Curl_vquic_tls_ctx_setup(struct quic_tls_ctx *ctx, * @param alpn the ALPN string in protocol format ((len+bytes+)+), * may be NULL * @param alpn_len the overall number of bytes in `alpn` - * @param ctx_setup optional callback for very early TLS config - * @param user_data optional pointer to set in TLS application context + * @param cb_setup optional callback for very early TLS config + ± @param cb_user_data user_data param for callback + * @param ssl_user_data optional pointer to set in TLS application context */ -CURLcode Curl_vquic_tls_init(struct quic_tls_ctx *ctx, +CURLcode Curl_vquic_tls_init(struct curl_tls_ctx *ctx, struct Curl_cfilter *cf, struct Curl_easy *data, struct ssl_peer *peer, const char *alpn, size_t alpn_len, - Curl_vquic_tls_ctx_setup *ctx_setup, - void *user_data); + Curl_vquic_tls_ctx_setup *cb_setup, + void *cb_user_data, + void *ssl_user_data); /** * Cleanup all data that has been initialized. */ -void Curl_vquic_tls_cleanup(struct quic_tls_ctx *ctx); +void Curl_vquic_tls_cleanup(struct curl_tls_ctx *ctx); -CURLcode Curl_vquic_tls_before_recv(struct quic_tls_ctx *ctx, +CURLcode Curl_vquic_tls_before_recv(struct curl_tls_ctx *ctx, struct Curl_cfilter *cf, struct Curl_easy *data); @@ -88,11 +89,11 @@ CURLcode Curl_vquic_tls_before_recv(struct quic_tls_ctx *ctx, * After the QUIC basic handshake has been, verify that the peer * (and its certificate) fulfill our requirements. */ -CURLcode Curl_vquic_tls_verify_peer(struct quic_tls_ctx *ctx, +CURLcode Curl_vquic_tls_verify_peer(struct curl_tls_ctx *ctx, struct Curl_cfilter *cf, struct Curl_easy *data, struct ssl_peer *peer); -#endif /* !ENABLE_QUIC && (USE_OPENSSL || USE_GNUTLS || USE_WOLFSSL) */ +#endif /* !USE_HTTP3 && (USE_OPENSSL || USE_GNUTLS || USE_WOLFSSL) */ #endif /* HEADER_CURL_VQUIC_TLS_H */ diff --git a/lib/vquic/vquic.c b/lib/vquic/vquic.c index 066caf9..9ce1e46 100644 --- a/lib/vquic/vquic.c +++ b/lib/vquic/vquic.c @@ -59,7 +59,7 @@ #include "memdebug.h" -#ifdef ENABLE_QUIC +#ifdef USE_HTTP3 #ifdef O_BINARY #define QLOGMODE O_WRONLY|O_CREAT|O_BINARY @@ -663,7 +663,7 @@ CURLcode Curl_conn_may_http3(struct Curl_easy *data, return CURLE_OK; } -#else /* ENABLE_QUIC */ +#else /* USE_HTTP3 */ CURLcode Curl_conn_may_http3(struct Curl_easy *data, const struct connectdata *conn) @@ -674,4 +674,4 @@ CURLcode Curl_conn_may_http3(struct Curl_easy *data, return CURLE_NOT_BUILT_IN; } -#endif /* !ENABLE_QUIC */ +#endif /* !USE_HTTP3 */ diff --git a/lib/vquic/vquic.h b/lib/vquic/vquic.h index dc73957..c1ca1df 100644 --- a/lib/vquic/vquic.h +++ b/lib/vquic/vquic.h @@ -26,7 +26,7 @@ #include "curl_setup.h" -#ifdef ENABLE_QUIC +#ifdef USE_HTTP3 struct Curl_cfilter; struct Curl_easy; struct connectdata; @@ -52,11 +52,11 @@ bool Curl_conn_is_http3(const struct Curl_easy *data, extern struct Curl_cftype Curl_cft_http3; -#else /* ENABLE_QUIC */ +#else /* USE_HTTP3 */ #define Curl_conn_is_http3(a,b,c) FALSE -#endif /* !ENABLE_QUIC */ +#endif /* !USE_HTTP3 */ CURLcode Curl_conn_may_http3(struct Curl_easy *data, const struct connectdata *conn); diff --git a/lib/vquic/vquic_int.h b/lib/vquic/vquic_int.h index c218a94..754e1f5 100644 --- a/lib/vquic/vquic_int.h +++ b/lib/vquic/vquic_int.h @@ -27,7 +27,7 @@ #include "curl_setup.h" #include "bufq.h" -#ifdef ENABLE_QUIC +#ifdef USE_HTTP3 #define MAX_PKT_BURST 10 #define MAX_UDP_PAYLOAD_SIZE 1452 @@ -88,6 +88,6 @@ CURLcode vquic_recv_packets(struct Curl_cfilter *cf, size_t max_pkts, vquic_recv_pkt_cb *recv_cb, void *userp); -#endif /* !ENABLE_QUIC */ +#endif /* !USE_HTTP3 */ #endif /* HEADER_CURL_VQUIC_QUIC_INT_H */ diff --git a/lib/vssh/libssh.c b/lib/vssh/libssh.c index da7ce6a..d6ba987 100644 --- a/lib/vssh/libssh.c +++ b/lib/vssh/libssh.c @@ -162,6 +162,7 @@ const struct Curl_handler Curl_handler_scp = { myssh_getsock, /* perform_getsock */ scp_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_SSH, /* defport */ @@ -189,6 +190,7 @@ const struct Curl_handler Curl_handler_sftp = { myssh_getsock, /* perform_getsock */ sftp_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_SSH, /* defport */ diff --git a/lib/vssh/libssh2.c b/lib/vssh/libssh2.c index 3cfbe12..abdf42e 100644 --- a/lib/vssh/libssh2.c +++ b/lib/vssh/libssh2.c @@ -139,6 +139,7 @@ const struct Curl_handler Curl_handler_scp = { ssh_getsock, /* perform_getsock */ scp_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ssh_attach, /* attach */ PORT_SSH, /* defport */ @@ -168,6 +169,7 @@ const struct Curl_handler Curl_handler_sftp = { ssh_getsock, /* perform_getsock */ sftp_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ssh_attach, /* attach */ PORT_SSH, /* defport */ @@ -201,7 +203,8 @@ kbd_callback(const char *name, int name_len, const char *instruction, if(num_prompts == 1) { struct connectdata *conn = data->conn; responses[0].text = strdup(conn->passwd); - responses[0].length = curlx_uztoui(strlen(conn->passwd)); + responses[0].length = + responses[0].text == NULL ? 0 : curlx_uztoui(strlen(conn->passwd)); } (void)prompts; } /* kbd_callback */ @@ -1083,6 +1086,7 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) /* To ponder about: should really the lib be messing about with the HOME environment variable etc? */ char *home = curl_getenv("HOME"); + struct_stat sbuf; /* If no private key file is specified, try some common paths. */ if(home) { @@ -1090,12 +1094,12 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) sshc->rsa = aprintf("%s/.ssh/id_rsa", home); if(!sshc->rsa) out_of_memory = TRUE; - else if(access(sshc->rsa, R_OK) != 0) { + else if(stat(sshc->rsa, &sbuf)) { Curl_safefree(sshc->rsa); sshc->rsa = aprintf("%s/.ssh/id_dsa", home); if(!sshc->rsa) out_of_memory = TRUE; - else if(access(sshc->rsa, R_OK) != 0) { + else if(stat(sshc->rsa, &sbuf)) { Curl_safefree(sshc->rsa); } } @@ -1104,10 +1108,10 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) if(!out_of_memory && !sshc->rsa) { /* Nothing found; try the current dir. */ sshc->rsa = strdup("id_rsa"); - if(sshc->rsa && access(sshc->rsa, R_OK) != 0) { + if(sshc->rsa && stat(sshc->rsa, &sbuf)) { Curl_safefree(sshc->rsa); sshc->rsa = strdup("id_dsa"); - if(sshc->rsa && access(sshc->rsa, R_OK) != 0) { + if(sshc->rsa && stat(sshc->rsa, &sbuf)) { Curl_safefree(sshc->rsa); /* Out of guesses. Set to the empty string to avoid * surprising info messages. */ @@ -3282,7 +3286,6 @@ static CURLcode ssh_connect(struct Curl_easy *data, bool *done) return CURLE_FAILED_INIT; } -#ifdef HAVE_LIBSSH2_VERSION /* Set the packet read timeout if the libssh2 version supports it */ #if LIBSSH2_VERSION_NUM >= 0x010B00 if(data->set.server_response_timeout > 0) { @@ -3290,7 +3293,6 @@ static CURLcode ssh_connect(struct Curl_easy *data, bool *done) data->set.server_response_timeout / 1000); } #endif -#endif #ifndef CURL_DISABLE_PROXY if(conn->http_proxy.proxytype == CURLPROXY_HTTPS) { diff --git a/lib/vssh/wolfssh.c b/lib/vssh/wolfssh.c index 1127591..6a5aed8 100644 --- a/lib/vssh/wolfssh.c +++ b/lib/vssh/wolfssh.c @@ -94,6 +94,7 @@ const struct Curl_handler Curl_handler_scp = { wssh_getsock, /* perform_getsock */ wscp_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_SSH, /* defport */ @@ -123,6 +124,7 @@ const struct Curl_handler Curl_handler_sftp = { wssh_getsock, /* perform_getsock */ wsftp_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_SSH, /* defport */ diff --git a/lib/vtls/bearssl.c b/lib/vtls/bearssl.c index 16f9c4e..a595f54 100644 --- a/lib/vtls/bearssl.c +++ b/lib/vtls/bearssl.c @@ -28,6 +28,7 @@ #include <bearssl.h> #include "bearssl.h" +#include "cipher_suite.h" #include "urldata.h" #include "sendf.h" #include "inet_pton.h" @@ -37,7 +38,6 @@ #include "select.h" #include "multiif.h" #include "curl_printf.h" -#include "strcase.h" /* The last #include files should be: */ #include "curl_memory.h" @@ -120,9 +120,9 @@ static CURLcode load_cafile(struct cafile_source *source, br_x509_pkey *pkey; FILE *fp = 0; unsigned char buf[BUFSIZ]; - const unsigned char *p; + const unsigned char *p = NULL; const char *name; - size_t n, i, pushed; + size_t n = 0, i, pushed; DEBUGASSERT(source->type == CAFILE_SOURCE_PATH || source->type == CAFILE_SOURCE_BLOB); @@ -360,219 +360,121 @@ static const br_x509_class x509_vtable = { x509_get_pkey }; -struct st_cipher { - const char *name; /* Cipher suite IANA name. It starts with "TLS_" prefix */ - const char *alias_name; /* Alias name is the same as OpenSSL cipher name */ - uint16_t num; /* BearSSL cipher suite */ -}; - -/* Macro to initialize st_cipher data structure */ -#define CIPHER_DEF(num, alias) { #num, alias, BR_##num } - -static const struct st_cipher ciphertable[] = { +static const uint16_t ciphertable[] = { /* RFC 2246 TLS 1.0 */ - CIPHER_DEF(TLS_RSA_WITH_3DES_EDE_CBC_SHA, /* 0x000A */ - "DES-CBC3-SHA"), + BR_TLS_RSA_WITH_3DES_EDE_CBC_SHA, /* 0x000A */ /* RFC 3268 TLS 1.0 AES */ - CIPHER_DEF(TLS_RSA_WITH_AES_128_CBC_SHA, /* 0x002F */ - "AES128-SHA"), - CIPHER_DEF(TLS_RSA_WITH_AES_256_CBC_SHA, /* 0x0035 */ - "AES256-SHA"), + BR_TLS_RSA_WITH_AES_128_CBC_SHA, /* 0x002F */ + BR_TLS_RSA_WITH_AES_256_CBC_SHA, /* 0x0035 */ /* RFC 5246 TLS 1.2 */ - CIPHER_DEF(TLS_RSA_WITH_AES_128_CBC_SHA256, /* 0x003C */ - "AES128-SHA256"), - CIPHER_DEF(TLS_RSA_WITH_AES_256_CBC_SHA256, /* 0x003D */ - "AES256-SHA256"), + BR_TLS_RSA_WITH_AES_128_CBC_SHA256, /* 0x003C */ + BR_TLS_RSA_WITH_AES_256_CBC_SHA256, /* 0x003D */ /* RFC 5288 TLS 1.2 AES GCM */ - CIPHER_DEF(TLS_RSA_WITH_AES_128_GCM_SHA256, /* 0x009C */ - "AES128-GCM-SHA256"), - CIPHER_DEF(TLS_RSA_WITH_AES_256_GCM_SHA384, /* 0x009D */ - "AES256-GCM-SHA384"), + BR_TLS_RSA_WITH_AES_128_GCM_SHA256, /* 0x009C */ + BR_TLS_RSA_WITH_AES_256_GCM_SHA384, /* 0x009D */ /* RFC 4492 TLS 1.0 ECC */ - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA, /* 0xC003 */ - "ECDH-ECDSA-DES-CBC3-SHA"), - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, /* 0xC004 */ - "ECDH-ECDSA-AES128-SHA"), - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, /* 0xC005 */ - "ECDH-ECDSA-AES256-SHA"), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, /* 0xC008 */ - "ECDHE-ECDSA-DES-CBC3-SHA"), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, /* 0xC009 */ - "ECDHE-ECDSA-AES128-SHA"), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, /* 0xC00A */ - "ECDHE-ECDSA-AES256-SHA"), - CIPHER_DEF(TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA, /* 0xC00D */ - "ECDH-RSA-DES-CBC3-SHA"), - CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, /* 0xC00E */ - "ECDH-RSA-AES128-SHA"), - CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, /* 0xC00F */ - "ECDH-RSA-AES256-SHA"), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, /* 0xC012 */ - "ECDHE-RSA-DES-CBC3-SHA"), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, /* 0xC013 */ - "ECDHE-RSA-AES128-SHA"), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, /* 0xC014 */ - "ECDHE-RSA-AES256-SHA"), + BR_TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA, /* 0xC003 */ + BR_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, /* 0xC004 */ + BR_TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, /* 0xC005 */ + BR_TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, /* 0xC008 */ + BR_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, /* 0xC009 */ + BR_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, /* 0xC00A */ + BR_TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA, /* 0xC00D */ + BR_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, /* 0xC00E */ + BR_TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, /* 0xC00F */ + BR_TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, /* 0xC012 */ + BR_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, /* 0xC013 */ + BR_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, /* 0xC014 */ /* RFC 5289 TLS 1.2 ECC HMAC SHA256/384 */ - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, /* 0xC023 */ - "ECDHE-ECDSA-AES128-SHA256"), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, /* 0xC024 */ - "ECDHE-ECDSA-AES256-SHA384"), - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, /* 0xC025 */ - "ECDH-ECDSA-AES128-SHA256"), - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384, /* 0xC026 */ - "ECDH-ECDSA-AES256-SHA384"), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, /* 0xC027 */ - "ECDHE-RSA-AES128-SHA256"), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, /* 0xC028 */ - "ECDHE-RSA-AES256-SHA384"), - CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, /* 0xC029 */ - "ECDH-RSA-AES128-SHA256"), - CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384, /* 0xC02A */ - "ECDH-RSA-AES256-SHA384"), + BR_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, /* 0xC023 */ + BR_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, /* 0xC024 */ + BR_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, /* 0xC025 */ + BR_TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384, /* 0xC026 */ + BR_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, /* 0xC027 */ + BR_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, /* 0xC028 */ + BR_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, /* 0xC029 */ + BR_TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384, /* 0xC02A */ /* RFC 5289 TLS 1.2 GCM */ - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, /* 0xC02B */ - "ECDHE-ECDSA-AES128-GCM-SHA256"), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, /* 0xC02C */ - "ECDHE-ECDSA-AES256-GCM-SHA384"), - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, /* 0xC02D */ - "ECDH-ECDSA-AES128-GCM-SHA256"), - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384, /* 0xC02E */ - "ECDH-ECDSA-AES256-GCM-SHA384"), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, /* 0xC02F */ - "ECDHE-RSA-AES128-GCM-SHA256"), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, /* 0xC030 */ - "ECDHE-RSA-AES256-GCM-SHA384"), - CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, /* 0xC031 */ - "ECDH-RSA-AES128-GCM-SHA256"), - CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384, /* 0xC032 */ - "ECDH-RSA-AES256-GCM-SHA384"), -#ifdef BR_TLS_RSA_WITH_AES_128_CCM + BR_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, /* 0xC02B */ + BR_TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, /* 0xC02C */ + BR_TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, /* 0xC02D */ + BR_TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384, /* 0xC02E */ + BR_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, /* 0xC02F */ + BR_TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, /* 0xC030 */ + BR_TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, /* 0xC031 */ + BR_TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384, /* 0xC032 */ +#ifdef BR_TLS_RSA_WITH_AES_128_CCM /* RFC 6655 TLS 1.2 CCM Supported since BearSSL 0.6 */ - CIPHER_DEF(TLS_RSA_WITH_AES_128_CCM, /* 0xC09C */ - "AES128-CCM"), - CIPHER_DEF(TLS_RSA_WITH_AES_256_CCM, /* 0xC09D */ - "AES256-CCM"), - CIPHER_DEF(TLS_RSA_WITH_AES_128_CCM_8, /* 0xC0A0 */ - "AES128-CCM8"), - CIPHER_DEF(TLS_RSA_WITH_AES_256_CCM_8, /* 0xC0A1 */ - "AES256-CCM8"), + BR_TLS_RSA_WITH_AES_128_CCM, /* 0xC09C */ + BR_TLS_RSA_WITH_AES_256_CCM, /* 0xC09D */ + BR_TLS_RSA_WITH_AES_128_CCM_8, /* 0xC0A0 */ + BR_TLS_RSA_WITH_AES_256_CCM_8, /* 0xC0A1 */ /* RFC 7251 TLS 1.2 ECC CCM Supported since BearSSL 0.6 */ - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_128_CCM, /* 0xC0AC */ - "ECDHE-ECDSA-AES128-CCM"), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_256_CCM, /* 0xC0AD */ - "ECDHE-ECDSA-AES256-CCM"), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8, /* 0xC0AE */ - "ECDHE-ECDSA-AES128-CCM8"), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8, /* 0xC0AF */ - "ECDHE-ECDSA-AES256-CCM8"), + BR_TLS_ECDHE_ECDSA_WITH_AES_128_CCM, /* 0xC0AC */ + BR_TLS_ECDHE_ECDSA_WITH_AES_256_CCM, /* 0xC0AD */ + BR_TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8, /* 0xC0AE */ + BR_TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8, /* 0xC0AF */ #endif /* RFC 7905 TLS 1.2 ChaCha20-Poly1305 Supported since BearSSL 0.2 */ - CIPHER_DEF(TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, /* 0xCCA8 */ - "ECDHE-RSA-CHACHA20-POLY1305"), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, /* 0xCCA9 */ - "ECDHE-ECDSA-CHACHA20-POLY1305"), + BR_TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, /* 0xCCA8 */ + BR_TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, /* 0xCCA9 */ }; #define NUM_OF_CIPHERS (sizeof(ciphertable) / sizeof(ciphertable[0])) -#define CIPHER_NAME_BUF_LEN 64 - -static bool is_separator(char c) -{ - /* Return whether character is a cipher list separator. */ - switch(c) { - case ' ': - case '\t': - case ':': - case ',': - case ';': - return true; - } - return false; -} static CURLcode bearssl_set_selected_ciphers(struct Curl_easy *data, br_ssl_engine_context *ssl_eng, const char *ciphers) { - uint16_t selected_ciphers[NUM_OF_CIPHERS]; - size_t selected_count = 0; - const char *cipher_start = ciphers; - const char *cipher_end; - size_t i, j; - - if(!cipher_start) - return CURLE_SSL_CIPHER; - - while(true) { - const char *cipher; - size_t clen; - - /* Extract the next cipher name from the ciphers string */ - while(is_separator(*cipher_start)) - ++cipher_start; - if(!*cipher_start) - break; - cipher_end = cipher_start; - while(*cipher_end && !is_separator(*cipher_end)) - ++cipher_end; - - clen = cipher_end - cipher_start; - cipher = cipher_start; - - cipher_start = cipher_end; - - /* Lookup the cipher name in the table of available ciphers. If the cipher - name starts with "TLS_" we do the lookup by IANA name. Otherwise, we try - to match cipher name by an (OpenSSL) alias. */ - if(strncasecompare(cipher, "TLS_", 4)) { - for(i = 0; i < NUM_OF_CIPHERS && - (strlen(ciphertable[i].name) == clen) && - !strncasecompare(cipher, ciphertable[i].name, clen); ++i); + uint16_t selected[NUM_OF_CIPHERS]; + size_t count = 0, i; + const char *ptr, *end; + + for(ptr = ciphers; ptr[0] != '\0' && count < NUM_OF_CIPHERS; ptr = end) { + uint16_t id = Curl_cipher_suite_walk_str(&ptr, &end); + + /* Check if cipher is supported */ + if(id) { + for(i = 0; i < NUM_OF_CIPHERS && ciphertable[i] != id; i++); + if(i == NUM_OF_CIPHERS) + id = 0; } - else { - for(i = 0; i < NUM_OF_CIPHERS && - (strlen(ciphertable[i].alias_name) == clen) && - !strncasecompare(cipher, ciphertable[i].alias_name, clen); ++i); - } - if(i == NUM_OF_CIPHERS) { - infof(data, "BearSSL: unknown cipher in list: %.*s", - (int)clen, cipher); + if(!id) { + if(ptr[0] != '\0') + infof(data, "BearSSL: unknown cipher in list: \"%.*s\"", + (int) (end - ptr), ptr); continue; } /* No duplicates allowed */ - for(j = 0; j < selected_count && - selected_ciphers[j] != ciphertable[i].num; j++); - if(j < selected_count) { - infof(data, "BearSSL: duplicate cipher in list: %.*s", - (int)clen, cipher); + for(i = 0; i < count && selected[i] != id; i++); + if(i < count) { + infof(data, "BearSSL: duplicate cipher in list: \"%.*s\"", + (int) (end - ptr), ptr); continue; } - DEBUGASSERT(selected_count < NUM_OF_CIPHERS); - selected_ciphers[selected_count] = ciphertable[i].num; - ++selected_count; + selected[count++] = id; } - if(selected_count == 0) { + if(count == 0) { failf(data, "BearSSL: no supported cipher in list"); return CURLE_SSL_CIPHER; } - br_ssl_engine_set_suites(ssl_eng, selected_ciphers, selected_count); + br_ssl_engine_set_suites(ssl_eng, selected, count); return CURLE_OK; } @@ -686,7 +588,7 @@ static CURLcode bearssl_connect_step1(struct Curl_cfilter *cf, CURL_TRC_CF(data, cf, "connect_step1, check session cache"); Curl_ssl_sessionid_lock(data); - if(!Curl_ssl_getsessionid(cf, data, &session, NULL)) { + if(!Curl_ssl_getsessionid(cf, data, &connssl->peer, &session, NULL)) { br_ssl_engine_set_session_parameters(&backend->ctx.eng, session); session_set = 1; infof(data, "BearSSL: reusing session ID"); @@ -830,7 +732,7 @@ static CURLcode bearssl_run_until(struct Curl_cfilter *cf, CURL_TRC_CF(data, cf, "ssl_recv(len=%zu) -> %zd, %d", len, ret, result); if(ret == 0) { failf(data, "SSL: EOF without close notify"); - return CURLE_READ_ERROR; + return CURLE_RECV_ERROR; } if(ret <= 0) { return result; @@ -846,6 +748,9 @@ static CURLcode bearssl_connect_step2(struct Curl_cfilter *cf, struct ssl_connect_data *connssl = cf->ctx; struct bearssl_ssl_backend_data *backend = (struct bearssl_ssl_backend_data *)connssl->backend; + br_ssl_session_parameters session; + char cipher_str[64]; + char ver_str[16]; CURLcode ret; DEBUGASSERT(backend); @@ -856,6 +761,7 @@ static CURLcode bearssl_connect_step2(struct Curl_cfilter *cf, return CURLE_OK; if(ret == CURLE_OK) { unsigned int tver; + if(br_ssl_engine_current_state(&backend->ctx.eng) == BR_SSL_CLOSED) { failf(data, "SSL: connection closed during handshake"); return CURLE_SSL_CONNECT_ERROR; @@ -863,16 +769,29 @@ static CURLcode bearssl_connect_step2(struct Curl_cfilter *cf, connssl->connecting_state = ssl_connect_3; /* Informational message */ tver = br_ssl_engine_get_version(&backend->ctx.eng); - if(tver == 0x0303) - infof(data, "SSL connection using TLSv1.2"); - else if(tver == 0x0304) - infof(data, "SSL connection using TLSv1.3"); - else - infof(data, "SSL connection using TLS 0x%x", tver); + if(tver == BR_TLS12) + strcpy(ver_str, "TLSv1.2"); + else if(tver == BR_TLS11) + strcpy(ver_str, "TLSv1.1"); + else if(tver == BR_TLS10) + strcpy(ver_str, "TLSv1.0"); + else { + msnprintf(ver_str, sizeof(ver_str), "TLS 0x%04x", tver); + } + br_ssl_engine_get_session_parameters(&backend->ctx.eng, &session); + Curl_cipher_suite_get_str(session.cipher_suite, cipher_str, + sizeof(cipher_str), true); + infof(data, "BearSSL: %s connection using %s", ver_str, cipher_str); } return ret; } +static void bearssl_session_free(void *sessionid, size_t idsize) +{ + (void)idsize; + free(sessionid); +} + static CURLcode bearssl_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data) { @@ -896,7 +815,6 @@ static CURLcode bearssl_connect_step3(struct Curl_cfilter *cf, if(ssl_config->primary.sessionid) { bool incache; - bool added = FALSE; void *oldsession; br_ssl_session_parameters *session; @@ -905,16 +823,16 @@ static CURLcode bearssl_connect_step3(struct Curl_cfilter *cf, return CURLE_OUT_OF_MEMORY; br_ssl_engine_get_session_parameters(&backend->ctx.eng, session); Curl_ssl_sessionid_lock(data); - incache = !(Curl_ssl_getsessionid(cf, data, &oldsession, NULL)); + incache = !(Curl_ssl_getsessionid(cf, data, &connssl->peer, + &oldsession, NULL)); if(incache) Curl_ssl_delsessionid(data, oldsession); - ret = Curl_ssl_addsessionid(cf, data, session, 0, &added); + + ret = Curl_ssl_addsessionid(cf, data, &connssl->peer, session, 0, + bearssl_session_free); Curl_ssl_sessionid_unlock(data); - if(!added) - free(session); - if(ret) { - return CURLE_OUT_OF_MEMORY; - } + if(ret) + return ret; } connssl->connecting_state = ssl_connect_done; @@ -1173,11 +1091,6 @@ static void bearssl_close(struct Curl_cfilter *cf, struct Curl_easy *data) } } -static void bearssl_session_free(void *ptr) -{ - free(ptr); -} - static CURLcode bearssl_sha256sum(const unsigned char *input, size_t inputlen, unsigned char *sha256sum, @@ -1210,7 +1123,6 @@ const struct Curl_ssl Curl_ssl_bearssl = { bearssl_get_internals, /* get_internals */ bearssl_close, /* close_one */ Curl_none_close_all, /* close_all */ - bearssl_session_free, /* session_free */ Curl_none_set_engine, /* set_engine */ Curl_none_set_engine_default, /* set_engine_default */ Curl_none_engines_list, /* engines_list */ diff --git a/lib/vtls/cipher_suite.c b/lib/vtls/cipher_suite.c new file mode 100644 index 0000000..a78838d --- /dev/null +++ b/lib/vtls/cipher_suite.c @@ -0,0 +1,716 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Jan Venekamp, <jan@venekamp.net> + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +#if defined(USE_MBEDTLS) || defined(USE_BEARSSL) +#include "cipher_suite.h" +#include "curl_printf.h" +#include "strcase.h" +#include <string.h> + +/* + * To support the CURLOPT_SSL_CIPHER_LIST option on SSL backends + * that do not support it natively, but do support setting a list of + * IANA ids, we need a list of all supported cipher suite names + * (openssl and IANA) to be able to look up the IANA ids. + * + * To keep the binary size of this list down we compress each entry + * down to 2 + 6 bytes using the C preprocessor. + */ + +/* + * mbedTLS NOTE: mbedTLS has mbedtls_ssl_get_ciphersuite_id() to + * convert a string representation to an IANA id, we do not use that + * because it does not support "standard" openssl cipher suite + * names, nor IANA names. + */ + +/* NOTE: also see tests/unit/unit3205.c */ + +/* Text for cipher suite parts (max 64 entries), + keep indexes below in sync with this! */ +static const char *cs_txt = + "\0" + "TLS" "\0" + "WITH" "\0" + "128" "\0" + "256" "\0" + "3DES" "\0" + "8" "\0" + "AES" "\0" + "AES128" "\0" + "AES256" "\0" + "CBC" "\0" + "CBC3" "\0" + "CCM" "\0" + "CCM8" "\0" + "CHACHA20" "\0" + "DES" "\0" + "DHE" "\0" + "ECDH" "\0" + "ECDHE" "\0" + "ECDSA" "\0" + "EDE" "\0" + "GCM" "\0" + "MD5" "\0" + "NULL" "\0" + "POLY1305" "\0" + "PSK" "\0" + "RSA" "\0" + "SHA" "\0" + "SHA256" "\0" + "SHA384" "\0" +#if defined(USE_MBEDTLS) + "ARIA" "\0" + "ARIA128" "\0" + "ARIA256" "\0" + "CAMELLIA" "\0" + "CAMELLIA128" "\0" + "CAMELLIA256" "\0" +#endif +; +/* Indexes of above cs_txt */ +enum { + CS_TXT_IDX_, + CS_TXT_IDX_TLS, + CS_TXT_IDX_WITH, + CS_TXT_IDX_128, + CS_TXT_IDX_256, + CS_TXT_IDX_3DES, + CS_TXT_IDX_8, + CS_TXT_IDX_AES, + CS_TXT_IDX_AES128, + CS_TXT_IDX_AES256, + CS_TXT_IDX_CBC, + CS_TXT_IDX_CBC3, + CS_TXT_IDX_CCM, + CS_TXT_IDX_CCM8, + CS_TXT_IDX_CHACHA20, + CS_TXT_IDX_DES, + CS_TXT_IDX_DHE, + CS_TXT_IDX_ECDH, + CS_TXT_IDX_ECDHE, + CS_TXT_IDX_ECDSA, + CS_TXT_IDX_EDE, + CS_TXT_IDX_GCM, + CS_TXT_IDX_MD5, + CS_TXT_IDX_NULL, + CS_TXT_IDX_POLY1305, + CS_TXT_IDX_PSK, + CS_TXT_IDX_RSA, + CS_TXT_IDX_SHA, + CS_TXT_IDX_SHA256, + CS_TXT_IDX_SHA384, +#if defined(USE_MBEDTLS) + CS_TXT_IDX_ARIA, + CS_TXT_IDX_ARIA128, + CS_TXT_IDX_ARIA256, + CS_TXT_IDX_CAMELLIA, + CS_TXT_IDX_CAMELLIA128, + CS_TXT_IDX_CAMELLIA256, +#endif + CS_TXT_LEN, +}; + +#define CS_ZIP_IDX(a, b, c, d, e, f, g, h) \ +{ \ + (uint8_t) ((a) << 2 | ((b) & 0x3F) >> 4), \ + (uint8_t) ((b) << 4 | ((c) & 0x3F) >> 2), \ + (uint8_t) ((c) << 6 | ((d) & 0x3F)), \ + (uint8_t) ((e) << 2 | ((f) & 0x3F) >> 4), \ + (uint8_t) ((f) << 4 | ((g) & 0x3F) >> 2), \ + (uint8_t) ((g) << 6 | ((h) & 0x3F)) \ +} +#define CS_ENTRY(id, a, b, c, d, e, f, g, h) \ +{ \ + id, \ + CS_ZIP_IDX( \ + CS_TXT_IDX_ ## a, CS_TXT_IDX_ ## b, \ + CS_TXT_IDX_ ## c, CS_TXT_IDX_ ## d, \ + CS_TXT_IDX_ ## e, CS_TXT_IDX_ ## f, \ + CS_TXT_IDX_ ## g, CS_TXT_IDX_ ## h \ + ) \ +} + +struct cs_entry { + uint16_t id; + uint8_t zip[6]; +}; + +/* !checksrc! disable COMMANOSPACE all */ +static const struct cs_entry cs_list [] = { + CS_ENTRY(0x002F, TLS,RSA,WITH,AES,128,CBC,SHA,), + CS_ENTRY(0x002F, AES128,SHA,,,,,,), + CS_ENTRY(0x0035, TLS,RSA,WITH,AES,256,CBC,SHA,), + CS_ENTRY(0x0035, AES256,SHA,,,,,,), + CS_ENTRY(0x003C, TLS,RSA,WITH,AES,128,CBC,SHA256,), + CS_ENTRY(0x003C, AES128,SHA256,,,,,,), + CS_ENTRY(0x003D, TLS,RSA,WITH,AES,256,CBC,SHA256,), + CS_ENTRY(0x003D, AES256,SHA256,,,,,,), + CS_ENTRY(0x009C, TLS,RSA,WITH,AES,128,GCM,SHA256,), + CS_ENTRY(0x009C, AES128,GCM,SHA256,,,,,), + CS_ENTRY(0x009D, TLS,RSA,WITH,AES,256,GCM,SHA384,), + CS_ENTRY(0x009D, AES256,GCM,SHA384,,,,,), + CS_ENTRY(0xC004, TLS,ECDH,ECDSA,WITH,AES,128,CBC,SHA), + CS_ENTRY(0xC004, ECDH,ECDSA,AES128,SHA,,,,), + CS_ENTRY(0xC005, TLS,ECDH,ECDSA,WITH,AES,256,CBC,SHA), + CS_ENTRY(0xC005, ECDH,ECDSA,AES256,SHA,,,,), + CS_ENTRY(0xC009, TLS,ECDHE,ECDSA,WITH,AES,128,CBC,SHA), + CS_ENTRY(0xC009, ECDHE,ECDSA,AES128,SHA,,,,), + CS_ENTRY(0xC00A, TLS,ECDHE,ECDSA,WITH,AES,256,CBC,SHA), + CS_ENTRY(0xC00A, ECDHE,ECDSA,AES256,SHA,,,,), + CS_ENTRY(0xC00E, TLS,ECDH,RSA,WITH,AES,128,CBC,SHA), + CS_ENTRY(0xC00E, ECDH,RSA,AES128,SHA,,,,), + CS_ENTRY(0xC00F, TLS,ECDH,RSA,WITH,AES,256,CBC,SHA), + CS_ENTRY(0xC00F, ECDH,RSA,AES256,SHA,,,,), + CS_ENTRY(0xC013, TLS,ECDHE,RSA,WITH,AES,128,CBC,SHA), + CS_ENTRY(0xC013, ECDHE,RSA,AES128,SHA,,,,), + CS_ENTRY(0xC014, TLS,ECDHE,RSA,WITH,AES,256,CBC,SHA), + CS_ENTRY(0xC014, ECDHE,RSA,AES256,SHA,,,,), + CS_ENTRY(0xC023, TLS,ECDHE,ECDSA,WITH,AES,128,CBC,SHA256), + CS_ENTRY(0xC023, ECDHE,ECDSA,AES128,SHA256,,,,), + CS_ENTRY(0xC024, TLS,ECDHE,ECDSA,WITH,AES,256,CBC,SHA384), + CS_ENTRY(0xC024, ECDHE,ECDSA,AES256,SHA384,,,,), + CS_ENTRY(0xC025, TLS,ECDH,ECDSA,WITH,AES,128,CBC,SHA256), + CS_ENTRY(0xC025, ECDH,ECDSA,AES128,SHA256,,,,), + CS_ENTRY(0xC026, TLS,ECDH,ECDSA,WITH,AES,256,CBC,SHA384), + CS_ENTRY(0xC026, ECDH,ECDSA,AES256,SHA384,,,,), + CS_ENTRY(0xC027, TLS,ECDHE,RSA,WITH,AES,128,CBC,SHA256), + CS_ENTRY(0xC027, ECDHE,RSA,AES128,SHA256,,,,), + CS_ENTRY(0xC028, TLS,ECDHE,RSA,WITH,AES,256,CBC,SHA384), + CS_ENTRY(0xC028, ECDHE,RSA,AES256,SHA384,,,,), + CS_ENTRY(0xC029, TLS,ECDH,RSA,WITH,AES,128,CBC,SHA256), + CS_ENTRY(0xC029, ECDH,RSA,AES128,SHA256,,,,), + CS_ENTRY(0xC02A, TLS,ECDH,RSA,WITH,AES,256,CBC,SHA384), + CS_ENTRY(0xC02A, ECDH,RSA,AES256,SHA384,,,,), + CS_ENTRY(0xC02B, TLS,ECDHE,ECDSA,WITH,AES,128,GCM,SHA256), + CS_ENTRY(0xC02B, ECDHE,ECDSA,AES128,GCM,SHA256,,,), + CS_ENTRY(0xC02C, TLS,ECDHE,ECDSA,WITH,AES,256,GCM,SHA384), + CS_ENTRY(0xC02C, ECDHE,ECDSA,AES256,GCM,SHA384,,,), + CS_ENTRY(0xC02D, TLS,ECDH,ECDSA,WITH,AES,128,GCM,SHA256), + CS_ENTRY(0xC02D, ECDH,ECDSA,AES128,GCM,SHA256,,,), + CS_ENTRY(0xC02E, TLS,ECDH,ECDSA,WITH,AES,256,GCM,SHA384), + CS_ENTRY(0xC02E, ECDH,ECDSA,AES256,GCM,SHA384,,,), + CS_ENTRY(0xC02F, TLS,ECDHE,RSA,WITH,AES,128,GCM,SHA256), + CS_ENTRY(0xC02F, ECDHE,RSA,AES128,GCM,SHA256,,,), + CS_ENTRY(0xC030, TLS,ECDHE,RSA,WITH,AES,256,GCM,SHA384), + CS_ENTRY(0xC030, ECDHE,RSA,AES256,GCM,SHA384,,,), + CS_ENTRY(0xC031, TLS,ECDH,RSA,WITH,AES,128,GCM,SHA256), + CS_ENTRY(0xC031, ECDH,RSA,AES128,GCM,SHA256,,,), + CS_ENTRY(0xC032, TLS,ECDH,RSA,WITH,AES,256,GCM,SHA384), + CS_ENTRY(0xC032, ECDH,RSA,AES256,GCM,SHA384,,,), + CS_ENTRY(0xCCA8, TLS,ECDHE,RSA,WITH,CHACHA20,POLY1305,SHA256,), + CS_ENTRY(0xCCA8, ECDHE,RSA,CHACHA20,POLY1305,,,,), + CS_ENTRY(0xCCA9, TLS,ECDHE,ECDSA,WITH,CHACHA20,POLY1305,SHA256,), + CS_ENTRY(0xCCA9, ECDHE,ECDSA,CHACHA20,POLY1305,,,,), +#if defined(USE_MBEDTLS) + CS_ENTRY(0x0001, TLS,RSA,WITH,NULL,MD5,,,), + CS_ENTRY(0x0001, NULL,MD5,,,,,,), + CS_ENTRY(0x0002, TLS,RSA,WITH,NULL,SHA,,,), + CS_ENTRY(0x0002, NULL,SHA,,,,,,), + CS_ENTRY(0x002C, TLS,PSK,WITH,NULL,SHA,,,), + CS_ENTRY(0x002C, PSK,NULL,SHA,,,,,), + CS_ENTRY(0x002D, TLS,DHE,PSK,WITH,NULL,SHA,,), + CS_ENTRY(0x002D, DHE,PSK,NULL,SHA,,,,), + CS_ENTRY(0x002E, TLS,RSA,PSK,WITH,NULL,SHA,,), + CS_ENTRY(0x002E, RSA,PSK,NULL,SHA,,,,), + CS_ENTRY(0x0033, TLS,DHE,RSA,WITH,AES,128,CBC,SHA), + CS_ENTRY(0x0033, DHE,RSA,AES128,SHA,,,,), + CS_ENTRY(0x0039, TLS,DHE,RSA,WITH,AES,256,CBC,SHA), + CS_ENTRY(0x0039, DHE,RSA,AES256,SHA,,,,), + CS_ENTRY(0x003B, TLS,RSA,WITH,NULL,SHA256,,,), + CS_ENTRY(0x003B, NULL,SHA256,,,,,,), + CS_ENTRY(0x0067, TLS,DHE,RSA,WITH,AES,128,CBC,SHA256), + CS_ENTRY(0x0067, DHE,RSA,AES128,SHA256,,,,), + CS_ENTRY(0x006B, TLS,DHE,RSA,WITH,AES,256,CBC,SHA256), + CS_ENTRY(0x006B, DHE,RSA,AES256,SHA256,,,,), + CS_ENTRY(0x008C, TLS,PSK,WITH,AES,128,CBC,SHA,), + CS_ENTRY(0x008C, PSK,AES128,CBC,SHA,,,,), + CS_ENTRY(0x008D, TLS,PSK,WITH,AES,256,CBC,SHA,), + CS_ENTRY(0x008D, PSK,AES256,CBC,SHA,,,,), + CS_ENTRY(0x0090, TLS,DHE,PSK,WITH,AES,128,CBC,SHA), + CS_ENTRY(0x0090, DHE,PSK,AES128,CBC,SHA,,,), + CS_ENTRY(0x0091, TLS,DHE,PSK,WITH,AES,256,CBC,SHA), + CS_ENTRY(0x0091, DHE,PSK,AES256,CBC,SHA,,,), + CS_ENTRY(0x0094, TLS,RSA,PSK,WITH,AES,128,CBC,SHA), + CS_ENTRY(0x0094, RSA,PSK,AES128,CBC,SHA,,,), + CS_ENTRY(0x0095, TLS,RSA,PSK,WITH,AES,256,CBC,SHA), + CS_ENTRY(0x0095, RSA,PSK,AES256,CBC,SHA,,,), + CS_ENTRY(0x009E, TLS,DHE,RSA,WITH,AES,128,GCM,SHA256), + CS_ENTRY(0x009E, DHE,RSA,AES128,GCM,SHA256,,,), + CS_ENTRY(0x009F, TLS,DHE,RSA,WITH,AES,256,GCM,SHA384), + CS_ENTRY(0x009F, DHE,RSA,AES256,GCM,SHA384,,,), + CS_ENTRY(0x00A8, TLS,PSK,WITH,AES,128,GCM,SHA256,), + CS_ENTRY(0x00A8, PSK,AES128,GCM,SHA256,,,,), + CS_ENTRY(0x00A9, TLS,PSK,WITH,AES,256,GCM,SHA384,), + CS_ENTRY(0x00A9, PSK,AES256,GCM,SHA384,,,,), + CS_ENTRY(0x00AA, TLS,DHE,PSK,WITH,AES,128,GCM,SHA256), + CS_ENTRY(0x00AA, DHE,PSK,AES128,GCM,SHA256,,,), + CS_ENTRY(0x00AB, TLS,DHE,PSK,WITH,AES,256,GCM,SHA384), + CS_ENTRY(0x00AB, DHE,PSK,AES256,GCM,SHA384,,,), + CS_ENTRY(0x00AC, TLS,RSA,PSK,WITH,AES,128,GCM,SHA256), + CS_ENTRY(0x00AC, RSA,PSK,AES128,GCM,SHA256,,,), + CS_ENTRY(0x00AD, TLS,RSA,PSK,WITH,AES,256,GCM,SHA384), + CS_ENTRY(0x00AD, RSA,PSK,AES256,GCM,SHA384,,,), + CS_ENTRY(0x00AE, TLS,PSK,WITH,AES,128,CBC,SHA256,), + CS_ENTRY(0x00AE, PSK,AES128,CBC,SHA256,,,,), + CS_ENTRY(0x00AF, TLS,PSK,WITH,AES,256,CBC,SHA384,), + CS_ENTRY(0x00AF, PSK,AES256,CBC,SHA384,,,,), + CS_ENTRY(0x00B0, TLS,PSK,WITH,NULL,SHA256,,,), + CS_ENTRY(0x00B0, PSK,NULL,SHA256,,,,,), + CS_ENTRY(0x00B1, TLS,PSK,WITH,NULL,SHA384,,,), + CS_ENTRY(0x00B1, PSK,NULL,SHA384,,,,,), + CS_ENTRY(0x00B2, TLS,DHE,PSK,WITH,AES,128,CBC,SHA256), + CS_ENTRY(0x00B2, DHE,PSK,AES128,CBC,SHA256,,,), + CS_ENTRY(0x00B3, TLS,DHE,PSK,WITH,AES,256,CBC,SHA384), + CS_ENTRY(0x00B3, DHE,PSK,AES256,CBC,SHA384,,,), + CS_ENTRY(0x00B4, TLS,DHE,PSK,WITH,NULL,SHA256,,), + CS_ENTRY(0x00B4, DHE,PSK,NULL,SHA256,,,,), + CS_ENTRY(0x00B5, TLS,DHE,PSK,WITH,NULL,SHA384,,), + CS_ENTRY(0x00B5, DHE,PSK,NULL,SHA384,,,,), + CS_ENTRY(0x00B6, TLS,RSA,PSK,WITH,AES,128,CBC,SHA256), + CS_ENTRY(0x00B6, RSA,PSK,AES128,CBC,SHA256,,,), + CS_ENTRY(0x00B7, TLS,RSA,PSK,WITH,AES,256,CBC,SHA384), + CS_ENTRY(0x00B7, RSA,PSK,AES256,CBC,SHA384,,,), + CS_ENTRY(0x00B8, TLS,RSA,PSK,WITH,NULL,SHA256,,), + CS_ENTRY(0x00B8, RSA,PSK,NULL,SHA256,,,,), + CS_ENTRY(0x00B9, TLS,RSA,PSK,WITH,NULL,SHA384,,), + CS_ENTRY(0x00B9, RSA,PSK,NULL,SHA384,,,,), + CS_ENTRY(0x1301, TLS,AES,128,GCM,SHA256,,,), + CS_ENTRY(0x1302, TLS,AES,256,GCM,SHA384,,,), + CS_ENTRY(0x1303, TLS,CHACHA20,POLY1305,SHA256,,,,), + CS_ENTRY(0x1304, TLS,AES,128,CCM,SHA256,,,), + CS_ENTRY(0x1305, TLS,AES,128,CCM,8,SHA256,,), + CS_ENTRY(0xC001, TLS,ECDH,ECDSA,WITH,NULL,SHA,,), + CS_ENTRY(0xC001, ECDH,ECDSA,NULL,SHA,,,,), + CS_ENTRY(0xC006, TLS,ECDHE,ECDSA,WITH,NULL,SHA,,), + CS_ENTRY(0xC006, ECDHE,ECDSA,NULL,SHA,,,,), + CS_ENTRY(0xC00B, TLS,ECDH,RSA,WITH,NULL,SHA,,), + CS_ENTRY(0xC00B, ECDH,RSA,NULL,SHA,,,,), + CS_ENTRY(0xC010, TLS,ECDHE,RSA,WITH,NULL,SHA,,), + CS_ENTRY(0xC010, ECDHE,RSA,NULL,SHA,,,,), + CS_ENTRY(0xC035, TLS,ECDHE,PSK,WITH,AES,128,CBC,SHA), + CS_ENTRY(0xC035, ECDHE,PSK,AES128,CBC,SHA,,,), + CS_ENTRY(0xC036, TLS,ECDHE,PSK,WITH,AES,256,CBC,SHA), + CS_ENTRY(0xC036, ECDHE,PSK,AES256,CBC,SHA,,,), + CS_ENTRY(0xCCAB, TLS,PSK,WITH,CHACHA20,POLY1305,SHA256,,), + CS_ENTRY(0xCCAB, PSK,CHACHA20,POLY1305,,,,,), +#endif +#if defined(USE_BEARSSL) + CS_ENTRY(0x000A, TLS,RSA,WITH,3DES,EDE,CBC,SHA,), + CS_ENTRY(0x000A, DES,CBC3,SHA,,,,,), + CS_ENTRY(0xC003, TLS,ECDH,ECDSA,WITH,3DES,EDE,CBC,SHA), + CS_ENTRY(0xC003, ECDH,ECDSA,DES,CBC3,SHA,,,), + CS_ENTRY(0xC008, TLS,ECDHE,ECDSA,WITH,3DES,EDE,CBC,SHA), + CS_ENTRY(0xC008, ECDHE,ECDSA,DES,CBC3,SHA,,,), + CS_ENTRY(0xC00D, TLS,ECDH,RSA,WITH,3DES,EDE,CBC,SHA), + CS_ENTRY(0xC00D, ECDH,RSA,DES,CBC3,SHA,,,), + CS_ENTRY(0xC012, TLS,ECDHE,RSA,WITH,3DES,EDE,CBC,SHA), + CS_ENTRY(0xC012, ECDHE,RSA,DES,CBC3,SHA,,,), +#endif + CS_ENTRY(0xC09C, TLS,RSA,WITH,AES,128,CCM,,), + CS_ENTRY(0xC09C, AES128,CCM,,,,,,), + CS_ENTRY(0xC09D, TLS,RSA,WITH,AES,256,CCM,,), + CS_ENTRY(0xC09D, AES256,CCM,,,,,,), + CS_ENTRY(0xC0A0, TLS,RSA,WITH,AES,128,CCM,8,), + CS_ENTRY(0xC0A0, AES128,CCM8,,,,,,), + CS_ENTRY(0xC0A1, TLS,RSA,WITH,AES,256,CCM,8,), + CS_ENTRY(0xC0A1, AES256,CCM8,,,,,,), + CS_ENTRY(0xC0AC, TLS,ECDHE,ECDSA,WITH,AES,128,CCM,), + CS_ENTRY(0xC0AC, ECDHE,ECDSA,AES128,CCM,,,,), + CS_ENTRY(0xC0AD, TLS,ECDHE,ECDSA,WITH,AES,256,CCM,), + CS_ENTRY(0xC0AD, ECDHE,ECDSA,AES256,CCM,,,,), + CS_ENTRY(0xC0AE, TLS,ECDHE,ECDSA,WITH,AES,128,CCM,8), + CS_ENTRY(0xC0AE, ECDHE,ECDSA,AES128,CCM8,,,,), + CS_ENTRY(0xC0AF, TLS,ECDHE,ECDSA,WITH,AES,256,CCM,8), + CS_ENTRY(0xC0AF, ECDHE,ECDSA,AES256,CCM8,,,,), +#if defined(USE_MBEDTLS) + /* entries marked ns are "non-standard", they are not in openssl */ + CS_ENTRY(0x0041, TLS,RSA,WITH,CAMELLIA,128,CBC,SHA,), + CS_ENTRY(0x0041, CAMELLIA128,SHA,,,,,,), + CS_ENTRY(0x0045, TLS,DHE,RSA,WITH,CAMELLIA,128,CBC,SHA), + CS_ENTRY(0x0045, DHE,RSA,CAMELLIA128,SHA,,,,), + CS_ENTRY(0x0084, TLS,RSA,WITH,CAMELLIA,256,CBC,SHA,), + CS_ENTRY(0x0084, CAMELLIA256,SHA,,,,,,), + CS_ENTRY(0x0088, TLS,DHE,RSA,WITH,CAMELLIA,256,CBC,SHA), + CS_ENTRY(0x0088, DHE,RSA,CAMELLIA256,SHA,,,,), + CS_ENTRY(0x00BA, TLS,RSA,WITH,CAMELLIA,128,CBC,SHA256,), + CS_ENTRY(0x00BA, CAMELLIA128,SHA256,,,,,,), + CS_ENTRY(0x00BE, TLS,DHE,RSA,WITH,CAMELLIA,128,CBC,SHA256), + CS_ENTRY(0x00BE, DHE,RSA,CAMELLIA128,SHA256,,,,), + CS_ENTRY(0x00C0, TLS,RSA,WITH,CAMELLIA,256,CBC,SHA256,), + CS_ENTRY(0x00C0, CAMELLIA256,SHA256,,,,,,), + CS_ENTRY(0x00C4, TLS,DHE,RSA,WITH,CAMELLIA,256,CBC,SHA256), + CS_ENTRY(0x00C4, DHE,RSA,CAMELLIA256,SHA256,,,,), + CS_ENTRY(0xC037, TLS,ECDHE,PSK,WITH,AES,128,CBC,SHA256), + CS_ENTRY(0xC037, ECDHE,PSK,AES128,CBC,SHA256,,,), + CS_ENTRY(0xC038, TLS,ECDHE,PSK,WITH,AES,256,CBC,SHA384), + CS_ENTRY(0xC038, ECDHE,PSK,AES256,CBC,SHA384,,,), + CS_ENTRY(0xC039, TLS,ECDHE,PSK,WITH,NULL,SHA,,), + CS_ENTRY(0xC039, ECDHE,PSK,NULL,SHA,,,,), + CS_ENTRY(0xC03A, TLS,ECDHE,PSK,WITH,NULL,SHA256,,), + CS_ENTRY(0xC03A, ECDHE,PSK,NULL,SHA256,,,,), + CS_ENTRY(0xC03B, TLS,ECDHE,PSK,WITH,NULL,SHA384,,), + CS_ENTRY(0xC03B, ECDHE,PSK,NULL,SHA384,,,,), + CS_ENTRY(0xC03C, TLS,RSA,WITH,ARIA,128,CBC,SHA256,), + CS_ENTRY(0xC03C, ARIA128,SHA256,,,,,,), /* ns */ + CS_ENTRY(0xC03D, TLS,RSA,WITH,ARIA,256,CBC,SHA384,), + CS_ENTRY(0xC03D, ARIA256,SHA384,,,,,,), /* ns */ + CS_ENTRY(0xC044, TLS,DHE,RSA,WITH,ARIA,128,CBC,SHA256), + CS_ENTRY(0xC044, DHE,RSA,ARIA128,SHA256,,,,), /* ns */ + CS_ENTRY(0xC045, TLS,DHE,RSA,WITH,ARIA,256,CBC,SHA384), + CS_ENTRY(0xC045, DHE,RSA,ARIA256,SHA384,,,,), /* ns */ + CS_ENTRY(0xC048, TLS,ECDHE,ECDSA,WITH,ARIA,128,CBC,SHA256), + CS_ENTRY(0xC048, ECDHE,ECDSA,ARIA128,SHA256,,,,), /* ns */ + CS_ENTRY(0xC049, TLS,ECDHE,ECDSA,WITH,ARIA,256,CBC,SHA384), + CS_ENTRY(0xC049, ECDHE,ECDSA,ARIA256,SHA384,,,,), /* ns */ + CS_ENTRY(0xC04A, TLS,ECDH,ECDSA,WITH,ARIA,128,CBC,SHA256), + CS_ENTRY(0xC04A, ECDH,ECDSA,ARIA128,SHA256,,,,), /* ns */ + CS_ENTRY(0xC04B, TLS,ECDH,ECDSA,WITH,ARIA,256,CBC,SHA384), + CS_ENTRY(0xC04B, ECDH,ECDSA,ARIA256,SHA384,,,,), /* ns */ + CS_ENTRY(0xC04C, TLS,ECDHE,RSA,WITH,ARIA,128,CBC,SHA256), + CS_ENTRY(0xC04C, ECDHE,ARIA128,SHA256,,,,,), /* ns */ + CS_ENTRY(0xC04D, TLS,ECDHE,RSA,WITH,ARIA,256,CBC,SHA384), + CS_ENTRY(0xC04D, ECDHE,ARIA256,SHA384,,,,,), /* ns */ + CS_ENTRY(0xC04E, TLS,ECDH,RSA,WITH,ARIA,128,CBC,SHA256), + CS_ENTRY(0xC04E, ECDH,ARIA128,SHA256,,,,,), /* ns */ + CS_ENTRY(0xC04F, TLS,ECDH,RSA,WITH,ARIA,256,CBC,SHA384), + CS_ENTRY(0xC04F, ECDH,ARIA256,SHA384,,,,,), /* ns */ + CS_ENTRY(0xC050, TLS,RSA,WITH,ARIA,128,GCM,SHA256,), + CS_ENTRY(0xC050, ARIA128,GCM,SHA256,,,,,), + CS_ENTRY(0xC051, TLS,RSA,WITH,ARIA,256,GCM,SHA384,), + CS_ENTRY(0xC051, ARIA256,GCM,SHA384,,,,,), + CS_ENTRY(0xC052, TLS,DHE,RSA,WITH,ARIA,128,GCM,SHA256), + CS_ENTRY(0xC052, DHE,RSA,ARIA128,GCM,SHA256,,,), + CS_ENTRY(0xC053, TLS,DHE,RSA,WITH,ARIA,256,GCM,SHA384), + CS_ENTRY(0xC053, DHE,RSA,ARIA256,GCM,SHA384,,,), + CS_ENTRY(0xC05C, TLS,ECDHE,ECDSA,WITH,ARIA,128,GCM,SHA256), + CS_ENTRY(0xC05C, ECDHE,ECDSA,ARIA128,GCM,SHA256,,,), + CS_ENTRY(0xC05D, TLS,ECDHE,ECDSA,WITH,ARIA,256,GCM,SHA384), + CS_ENTRY(0xC05D, ECDHE,ECDSA,ARIA256,GCM,SHA384,,,), + CS_ENTRY(0xC05E, TLS,ECDH,ECDSA,WITH,ARIA,128,GCM,SHA256), + CS_ENTRY(0xC05E, ECDH,ECDSA,ARIA128,GCM,SHA256,,,), /* ns */ + CS_ENTRY(0xC05F, TLS,ECDH,ECDSA,WITH,ARIA,256,GCM,SHA384), + CS_ENTRY(0xC05F, ECDH,ECDSA,ARIA256,GCM,SHA384,,,), /* ns */ + CS_ENTRY(0xC060, TLS,ECDHE,RSA,WITH,ARIA,128,GCM,SHA256), + CS_ENTRY(0xC060, ECDHE,ARIA128,GCM,SHA256,,,,), + CS_ENTRY(0xC061, TLS,ECDHE,RSA,WITH,ARIA,256,GCM,SHA384), + CS_ENTRY(0xC061, ECDHE,ARIA256,GCM,SHA384,,,,), + CS_ENTRY(0xC062, TLS,ECDH,RSA,WITH,ARIA,128,GCM,SHA256), + CS_ENTRY(0xC062, ECDH,ARIA128,GCM,SHA256,,,,), /* ns */ + CS_ENTRY(0xC063, TLS,ECDH,RSA,WITH,ARIA,256,GCM,SHA384), + CS_ENTRY(0xC063, ECDH,ARIA256,GCM,SHA384,,,,), /* ns */ + CS_ENTRY(0xC064, TLS,PSK,WITH,ARIA,128,CBC,SHA256,), + CS_ENTRY(0xC064, PSK,ARIA128,SHA256,,,,,), /* ns */ + CS_ENTRY(0xC065, TLS,PSK,WITH,ARIA,256,CBC,SHA384,), + CS_ENTRY(0xC065, PSK,ARIA256,SHA384,,,,,), /* ns */ + CS_ENTRY(0xC066, TLS,DHE,PSK,WITH,ARIA,128,CBC,SHA256), + CS_ENTRY(0xC066, DHE,PSK,ARIA128,SHA256,,,,), /* ns */ + CS_ENTRY(0xC067, TLS,DHE,PSK,WITH,ARIA,256,CBC,SHA384), + CS_ENTRY(0xC067, DHE,PSK,ARIA256,SHA384,,,,), /* ns */ + CS_ENTRY(0xC068, TLS,RSA,PSK,WITH,ARIA,128,CBC,SHA256), + CS_ENTRY(0xC068, RSA,PSK,ARIA128,SHA256,,,,), /* ns */ + CS_ENTRY(0xC069, TLS,RSA,PSK,WITH,ARIA,256,CBC,SHA384), + CS_ENTRY(0xC069, RSA,PSK,ARIA256,SHA384,,,,), /* ns */ + CS_ENTRY(0xC06A, TLS,PSK,WITH,ARIA,128,GCM,SHA256,), + CS_ENTRY(0xC06A, PSK,ARIA128,GCM,SHA256,,,,), + CS_ENTRY(0xC06B, TLS,PSK,WITH,ARIA,256,GCM,SHA384,), + CS_ENTRY(0xC06B, PSK,ARIA256,GCM,SHA384,,,,), + CS_ENTRY(0xC06C, TLS,DHE,PSK,WITH,ARIA,128,GCM,SHA256), + CS_ENTRY(0xC06C, DHE,PSK,ARIA128,GCM,SHA256,,,), + CS_ENTRY(0xC06D, TLS,DHE,PSK,WITH,ARIA,256,GCM,SHA384), + CS_ENTRY(0xC06D, DHE,PSK,ARIA256,GCM,SHA384,,,), + CS_ENTRY(0xC06E, TLS,RSA,PSK,WITH,ARIA,128,GCM,SHA256), + CS_ENTRY(0xC06E, RSA,PSK,ARIA128,GCM,SHA256,,,), + CS_ENTRY(0xC06F, TLS,RSA,PSK,WITH,ARIA,256,GCM,SHA384), + CS_ENTRY(0xC06F, RSA,PSK,ARIA256,GCM,SHA384,,,), + CS_ENTRY(0xC070, TLS,ECDHE,PSK,WITH,ARIA,128,CBC,SHA256), + CS_ENTRY(0xC070, ECDHE,PSK,ARIA128,SHA256,,,,), /* ns */ + CS_ENTRY(0xC071, TLS,ECDHE,PSK,WITH,ARIA,256,CBC,SHA384), + CS_ENTRY(0xC071, ECDHE,PSK,ARIA256,SHA384,,,,), /* ns */ + CS_ENTRY(0xC072, TLS,ECDHE,ECDSA,WITH,CAMELLIA,128,CBC,SHA256), + CS_ENTRY(0xC072, ECDHE,ECDSA,CAMELLIA128,SHA256,,,,), + CS_ENTRY(0xC073, TLS,ECDHE,ECDSA,WITH,CAMELLIA,256,CBC,SHA384), + CS_ENTRY(0xC073, ECDHE,ECDSA,CAMELLIA256,SHA384,,,,), + CS_ENTRY(0xC074, TLS,ECDH,ECDSA,WITH,CAMELLIA,128,CBC,SHA256), + CS_ENTRY(0xC074, ECDH,ECDSA,CAMELLIA128,SHA256,,,,), /* ns */ + CS_ENTRY(0xC075, TLS,ECDH,ECDSA,WITH,CAMELLIA,256,CBC,SHA384), + CS_ENTRY(0xC075, ECDH,ECDSA,CAMELLIA256,SHA384,,,,), /* ns */ + CS_ENTRY(0xC076, TLS,ECDHE,RSA,WITH,CAMELLIA,128,CBC,SHA256), + CS_ENTRY(0xC076, ECDHE,RSA,CAMELLIA128,SHA256,,,,), + CS_ENTRY(0xC077, TLS,ECDHE,RSA,WITH,CAMELLIA,256,CBC,SHA384), + CS_ENTRY(0xC077, ECDHE,RSA,CAMELLIA256,SHA384,,,,), + CS_ENTRY(0xC078, TLS,ECDH,RSA,WITH,CAMELLIA,128,CBC,SHA256), + CS_ENTRY(0xC078, ECDH,CAMELLIA128,SHA256,,,,,), /* ns */ + CS_ENTRY(0xC079, TLS,ECDH,RSA,WITH,CAMELLIA,256,CBC,SHA384), + CS_ENTRY(0xC079, ECDH,CAMELLIA256,SHA384,,,,,), /* ns */ + CS_ENTRY(0xC07A, TLS,RSA,WITH,CAMELLIA,128,GCM,SHA256,), + CS_ENTRY(0xC07A, CAMELLIA128,GCM,SHA256,,,,,), /* ns */ + CS_ENTRY(0xC07B, TLS,RSA,WITH,CAMELLIA,256,GCM,SHA384,), + CS_ENTRY(0xC07B, CAMELLIA256,GCM,SHA384,,,,,), /* ns */ + CS_ENTRY(0xC07C, TLS,DHE,RSA,WITH,CAMELLIA,128,GCM,SHA256), + CS_ENTRY(0xC07C, DHE,RSA,CAMELLIA128,GCM,SHA256,,,), /* ns */ + CS_ENTRY(0xC07D, TLS,DHE,RSA,WITH,CAMELLIA,256,GCM,SHA384), + CS_ENTRY(0xC07D, DHE,RSA,CAMELLIA256,GCM,SHA384,,,), /* ns */ + CS_ENTRY(0xC086, TLS,ECDHE,ECDSA,WITH,CAMELLIA,128,GCM,SHA256), + CS_ENTRY(0xC086, ECDHE,ECDSA,CAMELLIA128,GCM,SHA256,,,), /* ns */ + CS_ENTRY(0xC087, TLS,ECDHE,ECDSA,WITH,CAMELLIA,256,GCM,SHA384), + CS_ENTRY(0xC087, ECDHE,ECDSA,CAMELLIA256,GCM,SHA384,,,), /* ns */ + CS_ENTRY(0xC088, TLS,ECDH,ECDSA,WITH,CAMELLIA,128,GCM,SHA256), + CS_ENTRY(0xC088, ECDH,ECDSA,CAMELLIA128,GCM,SHA256,,,), /* ns */ + CS_ENTRY(0xC089, TLS,ECDH,ECDSA,WITH,CAMELLIA,256,GCM,SHA384), + CS_ENTRY(0xC089, ECDH,ECDSA,CAMELLIA256,GCM,SHA384,,,), /* ns */ + CS_ENTRY(0xC08A, TLS,ECDHE,RSA,WITH,CAMELLIA,128,GCM,SHA256), + CS_ENTRY(0xC08A, ECDHE,CAMELLIA128,GCM,SHA256,,,,), /* ns */ + CS_ENTRY(0xC08B, TLS,ECDHE,RSA,WITH,CAMELLIA,256,GCM,SHA384), + CS_ENTRY(0xC08B, ECDHE,CAMELLIA256,GCM,SHA384,,,,), /* ns */ + CS_ENTRY(0xC08C, TLS,ECDH,RSA,WITH,CAMELLIA,128,GCM,SHA256), + CS_ENTRY(0xC08C, ECDH,CAMELLIA128,GCM,SHA256,,,,), /* ns */ + CS_ENTRY(0xC08D, TLS,ECDH,RSA,WITH,CAMELLIA,256,GCM,SHA384), + CS_ENTRY(0xC08D, ECDH,CAMELLIA256,GCM,SHA384,,,,), /* ns */ + CS_ENTRY(0xC08E, TLS,PSK,WITH,CAMELLIA,128,GCM,SHA256,), + CS_ENTRY(0xC08E, PSK,CAMELLIA128,GCM,SHA256,,,,), /* ns */ + CS_ENTRY(0xC08F, TLS,PSK,WITH,CAMELLIA,256,GCM,SHA384,), + CS_ENTRY(0xC08F, PSK,CAMELLIA256,GCM,SHA384,,,,), /* ns */ + CS_ENTRY(0xC090, TLS,DHE,PSK,WITH,CAMELLIA,128,GCM,SHA256), + CS_ENTRY(0xC090, DHE,PSK,CAMELLIA128,GCM,SHA256,,,), /* ns */ + CS_ENTRY(0xC091, TLS,DHE,PSK,WITH,CAMELLIA,256,GCM,SHA384), + CS_ENTRY(0xC091, DHE,PSK,CAMELLIA256,GCM,SHA384,,,), /* ns */ + CS_ENTRY(0xC092, TLS,RSA,PSK,WITH,CAMELLIA,128,GCM,SHA256), + CS_ENTRY(0xC092, RSA,PSK,CAMELLIA128,GCM,SHA256,,,), /* ns */ + CS_ENTRY(0xC093, TLS,RSA,PSK,WITH,CAMELLIA,256,GCM,SHA384), + CS_ENTRY(0xC093, RSA,PSK,CAMELLIA256,GCM,SHA384,,,), /* ns */ + CS_ENTRY(0xC094, TLS,PSK,WITH,CAMELLIA,128,CBC,SHA256,), + CS_ENTRY(0xC094, PSK,CAMELLIA128,SHA256,,,,,), + CS_ENTRY(0xC095, TLS,PSK,WITH,CAMELLIA,256,CBC,SHA384,), + CS_ENTRY(0xC095, PSK,CAMELLIA256,SHA384,,,,,), + CS_ENTRY(0xC096, TLS,DHE,PSK,WITH,CAMELLIA,128,CBC,SHA256), + CS_ENTRY(0xC096, DHE,PSK,CAMELLIA128,SHA256,,,,), + CS_ENTRY(0xC097, TLS,DHE,PSK,WITH,CAMELLIA,256,CBC,SHA384), + CS_ENTRY(0xC097, DHE,PSK,CAMELLIA256,SHA384,,,,), + CS_ENTRY(0xC098, TLS,RSA,PSK,WITH,CAMELLIA,128,CBC,SHA256), + CS_ENTRY(0xC098, RSA,PSK,CAMELLIA128,SHA256,,,,), + CS_ENTRY(0xC099, TLS,RSA,PSK,WITH,CAMELLIA,256,CBC,SHA384), + CS_ENTRY(0xC099, RSA,PSK,CAMELLIA256,SHA384,,,,), + CS_ENTRY(0xC09A, TLS,ECDHE,PSK,WITH,CAMELLIA,128,CBC,SHA256), + CS_ENTRY(0xC09A, ECDHE,PSK,CAMELLIA128,SHA256,,,,), + CS_ENTRY(0xC09B, TLS,ECDHE,PSK,WITH,CAMELLIA,256,CBC,SHA384), + CS_ENTRY(0xC09B, ECDHE,PSK,CAMELLIA256,SHA384,,,,), + CS_ENTRY(0xC09E, TLS,DHE,RSA,WITH,AES,128,CCM,), + CS_ENTRY(0xC09E, DHE,RSA,AES128,CCM,,,,), + CS_ENTRY(0xC09F, TLS,DHE,RSA,WITH,AES,256,CCM,), + CS_ENTRY(0xC09F, DHE,RSA,AES256,CCM,,,,), + CS_ENTRY(0xC0A2, TLS,DHE,RSA,WITH,AES,128,CCM,8), + CS_ENTRY(0xC0A2, DHE,RSA,AES128,CCM8,,,,), + CS_ENTRY(0xC0A3, TLS,DHE,RSA,WITH,AES,256,CCM,8), + CS_ENTRY(0xC0A3, DHE,RSA,AES256,CCM8,,,,), + CS_ENTRY(0xC0A4, TLS,PSK,WITH,AES,128,CCM,,), + CS_ENTRY(0xC0A4, PSK,AES128,CCM,,,,,), + CS_ENTRY(0xC0A5, TLS,PSK,WITH,AES,256,CCM,,), + CS_ENTRY(0xC0A5, PSK,AES256,CCM,,,,,), + CS_ENTRY(0xC0A6, TLS,DHE,PSK,WITH,AES,128,CCM,), + CS_ENTRY(0xC0A6, DHE,PSK,AES128,CCM,,,,), + CS_ENTRY(0xC0A7, TLS,DHE,PSK,WITH,AES,256,CCM,), + CS_ENTRY(0xC0A7, DHE,PSK,AES256,CCM,,,,), + CS_ENTRY(0xC0A8, TLS,PSK,WITH,AES,128,CCM,8,), + CS_ENTRY(0xC0A8, PSK,AES128,CCM8,,,,,), + CS_ENTRY(0xC0A9, TLS,PSK,WITH,AES,256,CCM,8,), + CS_ENTRY(0xC0A9, PSK,AES256,CCM8,,,,,), + CS_ENTRY(0xC0AA, TLS,PSK,DHE,WITH,AES,128,CCM,8), + CS_ENTRY(0xC0AA, DHE,PSK,AES128,CCM8,,,,), + CS_ENTRY(0xC0AB, TLS,PSK,DHE,WITH,AES,256,CCM,8), + CS_ENTRY(0xC0AB, DHE,PSK,AES256,CCM8,,,,), + CS_ENTRY(0xCCAA, TLS,DHE,RSA,WITH,CHACHA20,POLY1305,SHA256,), + CS_ENTRY(0xCCAA, DHE,RSA,CHACHA20,POLY1305,,,,), + CS_ENTRY(0xCCAC, TLS,ECDHE,PSK,WITH,CHACHA20,POLY1305,SHA256,), + CS_ENTRY(0xCCAC, ECDHE,PSK,CHACHA20,POLY1305,,,,), + CS_ENTRY(0xCCAD, TLS,DHE,PSK,WITH,CHACHA20,POLY1305,SHA256,), + CS_ENTRY(0xCCAD, DHE,PSK,CHACHA20,POLY1305,,,,), + CS_ENTRY(0xCCAE, TLS,RSA,PSK,WITH,CHACHA20,POLY1305,SHA256,), + CS_ENTRY(0xCCAE, RSA,PSK,CHACHA20,POLY1305,,,,), +#endif +}; +#define CS_LIST_LEN (sizeof(cs_list) / sizeof(cs_list[0])) + +static int cs_str_to_zip(const char *cs_str, size_t cs_len, + uint8_t zip[6]) +{ + uint8_t indexes[8] = {0}; + const char *entry, *cur; + const char *nxt = cs_str; + const char *end = cs_str + cs_len; + char separator = '-'; + int idx, i = 0; + size_t len; + + /* split the cipher string by '-' or '_' */ + if(strncasecompare(cs_str, "TLS", 3)) + separator = '_'; + + do { + if(i == 8) + return -1; + + /* determine the length of the part */ + cur = nxt; + for(; nxt < end && *nxt != '\0' && *nxt != separator; nxt++); + len = nxt - cur; + + /* lookup index for the part (skip empty string at 0) */ + for(idx = 1, entry = cs_txt + 1; idx < CS_TXT_LEN; idx++) { + size_t elen = strlen(entry); + if(elen == len && strncasecompare(entry, cur, len)) + break; + entry += elen + 1; + } + if(idx == CS_TXT_LEN) + return -1; + + indexes[i++] = (uint8_t) idx; + } while(nxt < end && *(nxt++) != '\0'); + + /* zip the 8 indexes into 48 bits */ + zip[0] = (uint8_t) (indexes[0] << 2 | (indexes[1] & 0x3F) >> 4); + zip[1] = (uint8_t) (indexes[1] << 4 | (indexes[2] & 0x3F) >> 2); + zip[2] = (uint8_t) (indexes[2] << 6 | (indexes[3] & 0x3F)); + zip[3] = (uint8_t) (indexes[4] << 2 | (indexes[5] & 0x3F) >> 4); + zip[4] = (uint8_t) (indexes[5] << 4 | (indexes[6] & 0x3F) >> 2); + zip[5] = (uint8_t) (indexes[6] << 6 | (indexes[7] & 0x3F)); + + return 0; +} + +static int cs_zip_to_str(const uint8_t zip[6], + char *buf, size_t buf_size) +{ + uint8_t indexes[8] = {0}; + const char *entry; + char separator = '-'; + int idx, i, r; + size_t len = 0; + + /* unzip the 8 indexes */ + indexes[0] = zip[0] >> 2; + indexes[1] = ((zip[0] << 4) & 0x3F) | zip[1] >> 4; + indexes[2] = ((zip[1] << 2) & 0x3F) | zip[2] >> 6; + indexes[3] = ((zip[2] << 0) & 0x3F); + indexes[4] = zip[3] >> 2; + indexes[5] = ((zip[3] << 4) & 0x3F) | zip[4] >> 4; + indexes[6] = ((zip[4] << 2) & 0x3F) | zip[5] >> 6; + indexes[7] = ((zip[5] << 0) & 0x3F); + + if(indexes[0] == CS_TXT_IDX_TLS) + separator = '_'; + + for(i = 0; i < 8 && indexes[i] != 0 && len < buf_size; i++) { + if(indexes[i] >= CS_TXT_LEN) + return -1; + + /* lookup the part string for the index (skip empty string at 0) */ + for(idx = 1, entry = cs_txt + 1; idx < indexes[i]; idx++) { + size_t elen = strlen(entry); + entry += elen + 1; + } + + /* append the part string to the buffer */ + if(i > 0) + r = msnprintf(&buf[len], buf_size - len, "%c%s", separator, entry); + else + r = msnprintf(&buf[len], buf_size - len, "%s", entry); + + if(r < 0) + return -1; + len += r; + } + + return 0; +} + +uint16_t Curl_cipher_suite_lookup_id(const char *cs_str, size_t cs_len) +{ + size_t i; + uint8_t zip[6]; + + if(cs_len > 0 && cs_str_to_zip(cs_str, cs_len, zip) == 0) { + for(i = 0; i < CS_LIST_LEN; i++) { + if(memcmp(cs_list[i].zip, zip, sizeof(zip)) == 0) + return cs_list[i].id; + } + } + + return 0; +} + +static bool cs_is_separator(char c) +{ + switch(c) { + case ' ': + case '\t': + case ':': + case ',': + case ';': + return true; + default:; + } + return false; +} + +uint16_t Curl_cipher_suite_walk_str(const char **str, const char **end) +{ + /* move string pointer to first non-separator or end of string */ + for(; cs_is_separator(*str[0]); (*str)++); + + /* move end pointer to next separator or end of string */ + for(*end = *str; *end[0] != '\0' && !cs_is_separator(*end[0]); (*end)++); + + return Curl_cipher_suite_lookup_id(*str, *end - *str); +} + +int Curl_cipher_suite_get_str(uint16_t id, char *buf, size_t buf_size, + bool prefer_rfc) +{ + size_t i, j = CS_LIST_LEN; + int r = -1; + + for(i = 0; i < CS_LIST_LEN; i++) { + if(cs_list[i].id != id) + continue; + if((cs_list[i].zip[0] >> 2 != CS_TXT_IDX_TLS) == !prefer_rfc) { + j = i; + break; + } + if(j == CS_LIST_LEN) + j = i; + } + + if(j < CS_LIST_LEN) + r = cs_zip_to_str(cs_list[j].zip, buf, buf_size); + + if(r < 0) + msnprintf(buf, buf_size, "TLS_UNKNOWN_0x%04x", id); + + return r; +} + +#endif /* defined(USE_MBEDTLS) || defined(USE_BEARSSL) */ diff --git a/lib/curl_ntlm_wb.h b/lib/vtls/cipher_suite.h index 37704c0..c139979 100644 --- a/lib/curl_ntlm_wb.h +++ b/lib/vtls/cipher_suite.h @@ -1,5 +1,5 @@ -#ifndef HEADER_CURL_NTLM_WB_H -#define HEADER_CURL_NTLM_WB_H +#ifndef HEADER_CURL_CIPHER_SUITE_H +#define HEADER_CURL_CIPHER_SUITE_H /*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) Jan Venekamp, <jan@venekamp.net> * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -26,20 +26,21 @@ #include "curl_setup.h" -#if !defined(CURL_DISABLE_HTTP) && defined(USE_NTLM) && \ - defined(NTLM_WB_ENABLED) +#if defined(USE_MBEDTLS) || defined(USE_BEARSSL) +#include <stdint.h> -/* this is for ntlm header input */ -CURLcode Curl_input_ntlm_wb(struct Curl_easy *data, - struct connectdata *conn, bool proxy, - const char *header); +/* Lookup IANA id for cipher suite string, returns 0 if not recognized */ +uint16_t Curl_cipher_suite_lookup_id(const char *cs_str, size_t cs_len); -/* this is for creating ntlm header output */ -CURLcode Curl_output_ntlm_wb(struct Curl_easy *data, struct connectdata *conn, - bool proxy); +/* Walk over cipher suite string, update str and end pointers to next + cipher suite in string, returns IANA id of that suite if recognized */ +uint16_t Curl_cipher_suite_walk_str(const char **str, const char **end); -void Curl_http_auth_cleanup_ntlm_wb(struct connectdata *conn); +/* Copy openssl or RFC name for cipher suite in supplied buffer. + Caller is responsible to supply sufficiently large buffer (size + of 64 should suffice), excess bytes are silently truncated. */ +int Curl_cipher_suite_get_str(uint16_t id, char *buf, size_t buf_size, + bool prefer_rfc); -#endif /* !CURL_DISABLE_HTTP && USE_NTLM && NTLM_WB_ENABLED */ - -#endif /* HEADER_CURL_NTLM_WB_H */ +#endif /* defined(USE_MBEDTLS) || defined(USE_BEARSSL) */ +#endif /* HEADER_CURL_CIPHER_SUITE_H */ diff --git a/lib/vtls/gtls.c b/lib/vtls/gtls.c index 6eaa6a8..5cf3bf9 100644 --- a/lib/vtls/gtls.c +++ b/lib/vtls/gtls.c @@ -43,6 +43,7 @@ #include "urldata.h" #include "sendf.h" #include "inet_pton.h" +#include "keylog.h" #include "gtls.h" #include "vtls.h" #include "vtls_int.h" @@ -59,6 +60,16 @@ /* The last #include file should be: */ #include "memdebug.h" +#ifndef ARRAYSIZE +#define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) +#endif + +#define QUIC_PRIORITY \ + "NORMAL:-VERS-ALL:+VERS-TLS1.3:-CIPHER-ALL:+AES-128-GCM:+AES-256-GCM:" \ + "+CHACHA20-POLY1305:+AES-128-CCM:-GROUP-ALL:+GROUP-SECP256R1:" \ + "+GROUP-X25519:+GROUP-SECP384R1:+GROUP-SECP521R1:" \ + "%DISABLE_TLS13_COMPAT_MODE" + /* Enable GnuTLS debugging by defining GTLSDEBUG */ /*#define GTLSDEBUG */ @@ -77,22 +88,25 @@ static bool gtls_inited = FALSE; # include <gnutls/ocsp.h> struct gtls_ssl_backend_data { - struct gtls_instance gtls; + struct gtls_ctx gtls; }; static ssize_t gtls_push(void *s, const void *buf, size_t blen) { struct Curl_cfilter *cf = s; struct ssl_connect_data *connssl = cf->ctx; + struct gtls_ssl_backend_data *backend = + (struct gtls_ssl_backend_data *)connssl->backend; struct Curl_easy *data = CF_DATA_CURRENT(cf); ssize_t nwritten; CURLcode result; DEBUGASSERT(data); nwritten = Curl_conn_cf_send(cf->next, data, buf, blen, &result); + CURL_TRC_CF(data, cf, "gtls_push(len=%zu) -> %zd, err=%d", + blen, nwritten, result); + backend->gtls.io_result = result; if(nwritten < 0) { - struct gtls_ssl_backend_data *backend = - (struct gtls_ssl_backend_data *)connssl->backend; gnutls_transport_set_errno(backend->gtls.session, (CURLE_AGAIN == result)? EAGAIN : EINVAL); nwritten = -1; @@ -104,15 +118,27 @@ static ssize_t gtls_pull(void *s, void *buf, size_t blen) { struct Curl_cfilter *cf = s; struct ssl_connect_data *connssl = cf->ctx; + struct gtls_ssl_backend_data *backend = + (struct gtls_ssl_backend_data *)connssl->backend; struct Curl_easy *data = CF_DATA_CURRENT(cf); ssize_t nread; CURLcode result; DEBUGASSERT(data); + if(!backend->gtls.trust_setup) { + result = Curl_gtls_client_trust_setup(cf, data, &backend->gtls); + if(result) { + gnutls_transport_set_errno(backend->gtls.session, EINVAL); + backend->gtls.io_result = result; + return -1; + } + } + nread = Curl_conn_cf_recv(cf->next, data, buf, blen, &result); + CURL_TRC_CF(data, cf, "glts_pull(len=%zu) -> %zd, err=%d", + blen, nread, result); + backend->gtls.io_result = result; if(nread < 0) { - struct gtls_ssl_backend_data *backend = - (struct gtls_ssl_backend_data *)connssl->backend; gnutls_transport_set_errno(backend->gtls.session, (CURLE_AGAIN == result)? EAGAIN : EINVAL); nread = -1; @@ -268,8 +294,17 @@ static CURLcode handshake(struct Curl_cfilter *cf, /* socket is readable or writable */ } + backend->gtls.io_result = CURLE_OK; rc = gnutls_handshake(session); + if(!backend->gtls.trust_setup) { + /* After having send off the ClientHello, we prepare the trust + * store to verify the coming certificate from the server */ + CURLcode result = Curl_gtls_client_trust_setup(cf, data, &backend->gtls); + if(result) + return result; + } + if((rc == GNUTLS_E_AGAIN) || (rc == GNUTLS_E_INTERRUPTED)) { connssl->connecting_state = gnutls_record_get_direction(session)? @@ -290,6 +325,9 @@ static CURLcode handshake(struct Curl_cfilter *cf, infof(data, "gnutls_handshake() warning: %s", strerr); continue; } + else if((rc < 0) && backend->gtls.io_result) { + return backend->gtls.io_result; + } else if(rc < 0) { const char *strerr = NULL; @@ -330,6 +368,7 @@ static gnutls_x509_crt_fmt_t do_file_type(const char *type) static CURLcode set_ssl_version_min_max(struct Curl_easy *data, + struct ssl_peer *peer, struct ssl_primary_config *conn_config, const char **prioritylist, const char *tls13support) @@ -337,6 +376,16 @@ set_ssl_version_min_max(struct Curl_easy *data, long ssl_version = conn_config->version; long ssl_version_max = conn_config->version_max; + if(peer->transport == TRNSPRT_QUIC) { + if((ssl_version != CURL_SSLVERSION_DEFAULT) && + (ssl_version < CURL_SSLVERSION_TLSv1_3)) { + failf(data, "QUIC needs at least TLS version 1.3"); + return CURLE_SSL_CONNECT_ERROR; + } + *prioritylist = QUIC_PRIORITY; + return CURLE_OK; + } + if((ssl_version == CURL_SSLVERSION_DEFAULT) || (ssl_version == CURL_SSLVERSION_TLSv1)) ssl_version = CURL_SSLVERSION_TLSv1_0; @@ -401,62 +450,15 @@ set_ssl_version_min_max(struct Curl_easy *data, return CURLE_SSL_CONNECT_ERROR; } -CURLcode gtls_client_init(struct Curl_easy *data, - struct ssl_primary_config *config, - struct ssl_config_data *ssl_config, - struct ssl_peer *peer, - struct gtls_instance *gtls, - long *pverifyresult) +CURLcode Curl_gtls_client_trust_setup(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct gtls_ctx *gtls) { - unsigned int init_flags; + struct ssl_primary_config *config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); int rc; - bool sni = TRUE; /* default is SNI enabled */ - const char *prioritylist; - const char *err = NULL; - const char *tls13support; - CURLcode result; - - if(!gtls_inited) - gtls_init(); - - *pverifyresult = 0; - - if(config->version == CURL_SSLVERSION_SSLv2) { - failf(data, "GnuTLS does not support SSLv2"); - return CURLE_SSL_CONNECT_ERROR; - } - else if(config->version == CURL_SSLVERSION_SSLv3) - sni = FALSE; /* SSLv3 has no SNI */ - - /* allocate a cred struct */ - rc = gnutls_certificate_allocate_credentials(>ls->cred); - if(rc != GNUTLS_E_SUCCESS) { - failf(data, "gnutls_cert_all_cred() failed: %s", gnutls_strerror(rc)); - return CURLE_SSL_CONNECT_ERROR; - } - -#ifdef USE_GNUTLS_SRP - if(config->username && Curl_auth_allowed_to_host(data)) { - infof(data, "Using TLS-SRP username: %s", config->username); - - rc = gnutls_srp_allocate_client_credentials(>ls->srp_client_cred); - if(rc != GNUTLS_E_SUCCESS) { - failf(data, "gnutls_srp_allocate_client_cred() failed: %s", - gnutls_strerror(rc)); - return CURLE_OUT_OF_MEMORY; - } - - rc = gnutls_srp_set_client_credentials(gtls->srp_client_cred, - config->username, - config->password); - if(rc != GNUTLS_E_SUCCESS) { - failf(data, "gnutls_srp_set_client_cred() failed: %s", - gnutls_strerror(rc)); - return CURLE_BAD_FUNCTION_ARGUMENT; - } - } -#endif + CURL_TRC_CF(data, cf, "setup trust anchors and CRLs"); if(config->verifypeer) { bool imported_native_ca = false; @@ -485,7 +487,7 @@ CURLcode gtls_client_init(struct Curl_easy *data, config->CAfile, gnutls_strerror(rc), (imported_native_ca ? ", continuing anyway" : "")); if(!imported_native_ca) { - *pverifyresult = rc; + ssl_config->certverifyresult = rc; return CURLE_SSL_CACERT_BADFILE; } } @@ -503,7 +505,7 @@ CURLcode gtls_client_init(struct Curl_easy *data, config->CApath, gnutls_strerror(rc), (imported_native_ca ? ", continuing anyway" : "")); if(!imported_native_ca) { - *pverifyresult = rc; + ssl_config->certverifyresult = rc; return CURLE_SSL_CACERT_BADFILE; } } @@ -526,6 +528,148 @@ CURLcode gtls_client_init(struct Curl_easy *data, infof(data, "found %d CRL in %s", rc, config->CRLfile); } + gtls->trust_setup = TRUE; + return CURLE_OK; +} + +static void gtls_sessionid_free(void *sessionid, size_t idsize) +{ + (void)idsize; + free(sessionid); +} + +static CURLcode gtls_update_session_id(struct Curl_cfilter *cf, + struct Curl_easy *data, + gnutls_session_t session) +{ + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + struct ssl_connect_data *connssl = cf->ctx; + CURLcode result = CURLE_OK; + + if(ssl_config->primary.sessionid) { + /* we always unconditionally get the session id here, as even if we + already got it from the cache and asked to use it in the connection, it + might've been rejected and then a new one is in use now and we need to + detect that. */ + void *connect_sessionid; + size_t connect_idsize = 0; + + /* get the session ID data size */ + gnutls_session_get_data(session, NULL, &connect_idsize); + connect_sessionid = malloc(connect_idsize); /* get a buffer for it */ + if(!connect_sessionid) { + return CURLE_OUT_OF_MEMORY; + } + else { + bool incache; + void *ssl_sessionid; + + /* extract session ID to the allocated buffer */ + gnutls_session_get_data(session, connect_sessionid, &connect_idsize); + + DEBUGF(infof(data, "get session id (len=%zu) and store in cache", + connect_idsize)); + Curl_ssl_sessionid_lock(data); + incache = !(Curl_ssl_getsessionid(cf, data, &connssl->peer, + &ssl_sessionid, NULL)); + if(incache) { + /* there was one before in the cache, so instead of risking that the + previous one was rejected, we just kill that and store the new */ + Curl_ssl_delsessionid(data, ssl_sessionid); + } + + /* store this session id, takes ownership */ + result = Curl_ssl_addsessionid(cf, data, &connssl->peer, + connect_sessionid, connect_idsize, + gtls_sessionid_free); + Curl_ssl_sessionid_unlock(data); + } + } + return result; +} + +static int gtls_handshake_cb(gnutls_session_t session, unsigned int htype, + unsigned when, unsigned int incoming, + const gnutls_datum_t *msg) +{ + struct Curl_cfilter *cf = gnutls_session_get_ptr(session); + + (void)msg; + (void)incoming; + if(when) { /* after message has been processed */ + struct Curl_easy *data = CF_DATA_CURRENT(cf); + if(data) { + DEBUGF(infof(data, "handshake: %s message type %d", + incoming? "incoming" : "outgoing", htype)); + switch(htype) { + case GNUTLS_HANDSHAKE_NEW_SESSION_TICKET: { + gtls_update_session_id(cf, data, session); + break; + } + default: + break; + } + } + } + return 0; +} + +static CURLcode gtls_client_init(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + struct gtls_ctx *gtls) +{ + struct ssl_primary_config *config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + unsigned int init_flags; + int rc; + bool sni = TRUE; /* default is SNI enabled */ + const char *prioritylist; + const char *err = NULL; + const char *tls13support; + CURLcode result; + + if(!gtls_inited) + gtls_init(); + + if(config->version == CURL_SSLVERSION_SSLv2) { + failf(data, "GnuTLS does not support SSLv2"); + return CURLE_SSL_CONNECT_ERROR; + } + else if(config->version == CURL_SSLVERSION_SSLv3) + sni = FALSE; /* SSLv3 has no SNI */ + + /* allocate a cred struct */ + rc = gnutls_certificate_allocate_credentials(>ls->cred); + if(rc != GNUTLS_E_SUCCESS) { + failf(data, "gnutls_cert_all_cred() failed: %s", gnutls_strerror(rc)); + return CURLE_SSL_CONNECT_ERROR; + } + +#ifdef USE_GNUTLS_SRP + if(config->username && Curl_auth_allowed_to_host(data)) { + infof(data, "Using TLS-SRP username: %s", config->username); + + rc = gnutls_srp_allocate_client_credentials(>ls->srp_client_cred); + if(rc != GNUTLS_E_SUCCESS) { + failf(data, "gnutls_srp_allocate_client_cred() failed: %s", + gnutls_strerror(rc)); + return CURLE_OUT_OF_MEMORY; + } + + rc = gnutls_srp_set_client_credentials(gtls->srp_client_cred, + config->username, + config->password); + if(rc != GNUTLS_E_SUCCESS) { + failf(data, "gnutls_srp_set_client_cred() failed: %s", + gnutls_strerror(rc)); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + } +#endif + + ssl_config->certverifyresult = 0; + /* Initialize TLS session as a client */ init_flags = GNUTLS_CLIENT; @@ -578,7 +722,8 @@ CURLcode gtls_client_init(struct Curl_easy *data, } /* At this point we know we have a supported TLS version, so set it */ - result = set_ssl_version_min_max(data, config, &prioritylist, tls13support); + result = set_ssl_version_min_max(data, peer, + config, &prioritylist, tls13support); if(result) return result; @@ -611,6 +756,11 @@ CURLcode gtls_client_init(struct Curl_easy *data, } if(config->clientcert) { + if(!gtls->trust_setup) { + result = Curl_gtls_client_trust_setup(cf, data, gtls); + if(result) + return result; + } if(ssl_config->key_passwd) { const unsigned int supported_key_encryption_algorithms = GNUTLS_PKCS_USE_PKCS12_3DES | GNUTLS_PKCS_USE_PKCS12_ARCFOUR | @@ -677,46 +827,78 @@ CURLcode gtls_client_init(struct Curl_easy *data, return CURLE_OK; } -static CURLcode -gtls_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) +static int keylog_callback(gnutls_session_t session, const char *label, + const gnutls_datum_t *secret) +{ + gnutls_datum_t crandom; + gnutls_datum_t srandom; + + gnutls_session_get_random(session, &crandom, &srandom); + if(crandom.size != 32) { + return -1; + } + + Curl_tls_keylog_write(label, crandom.data, secret->data, secret->size); + return 0; +} + +CURLcode Curl_gtls_ctx_init(struct gtls_ctx *gctx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + const unsigned char *alpn, size_t alpn_len, + Curl_gtls_ctx_setup_cb *cb_setup, + void *cb_user_data, + void *ssl_user_data) { - struct ssl_connect_data *connssl = cf->ctx; - struct gtls_ssl_backend_data *backend = - (struct gtls_ssl_backend_data *)connssl->backend; struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); - long * const pverifyresult = &ssl_config->certverifyresult; CURLcode result; - DEBUGASSERT(backend); - - if(connssl->state == ssl_connection_complete) - /* to make us tolerant against being called more than once for the - same connection */ - return CURLE_OK; + DEBUGASSERT(gctx); - result = gtls_client_init(data, conn_config, ssl_config, - &connssl->peer, - &backend->gtls, pverifyresult); + result = gtls_client_init(cf, data, peer, gctx); if(result) return result; - if(connssl->alpn) { - struct alpn_proto_buf proto; - gnutls_datum_t alpn[ALPN_ENTRIES_MAX]; - size_t i; + gnutls_session_set_ptr(gctx->session, ssl_user_data); + + if(cb_setup) { + result = cb_setup(cf, data, cb_user_data); + if(result) + return result; + } + + /* Open the file if a TLS or QUIC backend has not done this before. */ + Curl_tls_keylog_open(); + if(Curl_tls_keylog_enabled()) { + gnutls_session_set_keylog_function(gctx->session, keylog_callback); + } - for(i = 0; i < connssl->alpn->count; ++i) { - alpn[i].data = (unsigned char *)connssl->alpn->entries[i]; - alpn[i].size = (unsigned)strlen(connssl->alpn->entries[i]); + /* convert the ALPN string from our arguments to a list of strings + * that gnutls wants and will convert internally back to this very + * string for sending to the server. nice. */ + if(alpn && alpn_len) { + gnutls_datum_t alpns[5]; + size_t i, alen = alpn_len; + unsigned char *s = (unsigned char *)alpn; + unsigned char slen; + for(i = 0; (i < ARRAYSIZE(alpns)) && alen; ++i) { + slen = s[0]; + if(slen >= alen) + return CURLE_FAILED_INIT; + alpns[i].data = s + 1; + alpns[i].size = slen; + s += slen + 1; + alen -= (size_t)slen + 1; } - if(gnutls_alpn_set_protocols(backend->gtls.session, alpn, - (unsigned)connssl->alpn->count, 0)) { + if(alen) /* not all alpn chars used, wrong format or too many */ + return CURLE_FAILED_INIT; + if(i && gnutls_alpn_set_protocols(gctx->session, + alpns, (unsigned int)i, + GNUTLS_ALPN_MANDATORY)) { failf(data, "failed setting ALPN"); return CURLE_SSL_CONNECT_ERROR; } - Curl_alpn_to_proto_str(&proto, connssl->alpn); - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); } /* This might be a reconnect, so we check for a session ID in the cache @@ -726,16 +908,55 @@ gtls_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) size_t ssl_idsize; Curl_ssl_sessionid_lock(data); - if(!Curl_ssl_getsessionid(cf, data, &ssl_sessionid, &ssl_idsize)) { + if(!Curl_ssl_getsessionid(cf, data, peer, &ssl_sessionid, &ssl_idsize)) { /* we got a session id, use it! */ - gnutls_session_set_data(backend->gtls.session, - ssl_sessionid, ssl_idsize); + int rc; - /* Informational message */ - infof(data, "SSL reusing session ID"); + rc = gnutls_session_set_data(gctx->session, ssl_sessionid, ssl_idsize); + if(rc < 0) + infof(data, "SSL failed to set session ID"); + else + infof(data, "SSL reusing session ID (size=%zu)", ssl_idsize); } Curl_ssl_sessionid_unlock(data); } + return CURLE_OK; +} + +static CURLcode +gtls_connect_step1(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; + struct alpn_proto_buf proto; + CURLcode result; + + DEBUGASSERT(backend); + DEBUGASSERT(ssl_connect_1 == connssl->connecting_state); + + if(connssl->state == ssl_connection_complete) + /* to make us tolerant against being called more than once for the + same connection */ + return CURLE_OK; + + memset(&proto, 0, sizeof(proto)); + if(connssl->alpn) { + result = Curl_alpn_to_proto_buf(&proto, connssl->alpn); + if(result) { + failf(data, "Error determining ALPN"); + return CURLE_SSL_CONNECT_ERROR; + } + } + + result = Curl_gtls_ctx_init(&backend->gtls, cf, data, &connssl->peer, + proto.data, proto.len, NULL, NULL, cf); + if(result) + return result; + + gnutls_handshake_set_hook_function(backend->gtls.session, + GNUTLS_HANDSHAKE_ANY, GNUTLS_HOOK_POST, + gtls_handshake_cb); /* register callback functions and handle to send and receive data. */ gnutls_transport_set_ptr(backend->gtls.session, cf); @@ -1071,7 +1292,7 @@ Curl_gtls_verifyserver(struct Curl_easy *data, /* Before 3.3.6, gnutls_x509_crt_check_hostname() didn't check IP addresses. */ if(!rc) { -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 #define use_addr in6_addr #else #define use_addr in_addr @@ -1081,7 +1302,7 @@ Curl_gtls_verifyserver(struct Curl_easy *data, if(Curl_inet_pton(AF_INET, peer->hostname, addrbuf) > 0) addrlen = 4; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 else if(Curl_inet_pton(AF_INET6, peer->hostname, addrbuf) > 0) addrlen = 16; #endif @@ -1245,9 +1466,13 @@ static CURLcode gtls_verifyserver(struct Curl_cfilter *cf, struct ssl_connect_data *connssl = cf->ctx; struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); +#ifndef CURL_DISABLE_PROXY const char *pinned_key = Curl_ssl_cf_is_proxy(cf)? data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY]: data->set.str[STRING_SSL_PINNEDPUBLICKEY]; +#else + const char *pinned_key = data->set.str[STRING_SSL_PINNEDPUBLICKEY]; +#endif CURLcode result; result = Curl_gtls_verifyserver(data, session, conn_config, ssl_config, @@ -1266,47 +1491,10 @@ static CURLcode gtls_verifyserver(struct Curl_cfilter *cf, Curl_alpn_set_negotiated(cf, data, NULL, 0); } - if(ssl_config->primary.sessionid) { - /* we always unconditionally get the session id here, as even if we - already got it from the cache and asked to use it in the connection, it - might've been rejected and then a new one is in use now and we need to - detect that. */ - void *connect_sessionid; - size_t connect_idsize = 0; - - /* get the session ID data size */ - gnutls_session_get_data(session, NULL, &connect_idsize); - connect_sessionid = malloc(connect_idsize); /* get a buffer for it */ - - if(connect_sessionid) { - bool incache; - bool added = FALSE; - void *ssl_sessionid; - - /* extract session ID to the allocated buffer */ - gnutls_session_get_data(session, connect_sessionid, &connect_idsize); - - Curl_ssl_sessionid_lock(data); - incache = !(Curl_ssl_getsessionid(cf, data, &ssl_sessionid, NULL)); - if(incache) { - /* there was one before in the cache, so instead of risking that the - previous one was rejected, we just kill that and store the new */ - Curl_ssl_delsessionid(data, ssl_sessionid); - } - - /* store this session id */ - result = Curl_ssl_addsessionid(cf, data, connect_sessionid, - connect_idsize, &added); - Curl_ssl_sessionid_unlock(data); - if(!added) - free(connect_sessionid); - if(result) { - result = CURLE_OUT_OF_MEMORY; - } - } - else - result = CURLE_OUT_OF_MEMORY; - } + /* Only on TLSv1.2 or lower do we have the session id now. For + * TLSv1.3 we get it via a SESSION_TICKET message that arrives later. */ + if(gnutls_protocol_get_version(session) < GNUTLS_TLS1_3) + result = gtls_update_session_id(cf, data, session); out: return result; @@ -1418,12 +1606,13 @@ static ssize_t gtls_send(struct Curl_cfilter *cf, (void)data; DEBUGASSERT(backend); + backend->gtls.io_result = CURLE_OK; rc = gnutls_record_send(backend->gtls.session, mem, len); if(rc < 0) { - *curlcode = (rc == GNUTLS_E_AGAIN) - ? CURLE_AGAIN - : CURLE_SEND_ERROR; + *curlcode = (rc == GNUTLS_E_AGAIN)? + CURLE_AGAIN : + (backend->gtls.io_result? backend->gtls.io_result : CURLE_SEND_ERROR); rc = -1; } @@ -1559,6 +1748,7 @@ static ssize_t gtls_recv(struct Curl_cfilter *cf, (void)data; DEBUGASSERT(backend); + backend->gtls.io_result = CURLE_OK; ret = gnutls_record_recv(backend->gtls.session, buf, buffersize); if((ret == GNUTLS_E_AGAIN) || (ret == GNUTLS_E_INTERRUPTED)) { *curlcode = CURLE_AGAIN; @@ -1583,7 +1773,8 @@ static ssize_t gtls_recv(struct Curl_cfilter *cf, failf(data, "GnuTLS recv error (%d): %s", (int)ret, gnutls_strerror((int)ret)); - *curlcode = CURLE_RECV_ERROR; + *curlcode = backend->gtls.io_result? + backend->gtls.io_result : CURLE_RECV_ERROR; ret = -1; goto out; } @@ -1592,11 +1783,6 @@ out: return ret; } -static void gtls_session_free(void *ptr) -{ - free(ptr); -} - static size_t gtls_version(char *buffer, size_t size) { return msnprintf(buffer, size, "GnuTLS/%s", gnutls_check_version(NULL)); @@ -1663,7 +1849,6 @@ const struct Curl_ssl Curl_ssl_gnutls = { gtls_get_internals, /* get_internals */ gtls_close, /* close_one */ Curl_none_close_all, /* close_all */ - gtls_session_free, /* session_free */ Curl_none_set_engine, /* set_engine */ Curl_none_set_engine_default, /* set_engine_default */ Curl_none_engines_list, /* engines_list */ diff --git a/lib/vtls/gtls.h b/lib/vtls/gtls.h index 1a81c01..f8388b3 100644 --- a/lib/vtls/gtls.h +++ b/lib/vtls/gtls.h @@ -45,29 +45,39 @@ struct ssl_primary_config; struct ssl_config_data; struct ssl_peer; -struct gtls_instance { +struct gtls_ctx { gnutls_session_t session; gnutls_certificate_credentials_t cred; #ifdef USE_GNUTLS_SRP gnutls_srp_client_credentials_t srp_client_cred; #endif + CURLcode io_result; /* result of last IO cfilter operation */ + BIT(trust_setup); /* x509 anchors + CRLs have been set up */ }; -CURLcode -gtls_client_init(struct Curl_easy *data, - struct ssl_primary_config *config, - struct ssl_config_data *ssl_config, - struct ssl_peer *peer, - struct gtls_instance *gtls, - long *pverifyresult); +typedef CURLcode Curl_gtls_ctx_setup_cb(struct Curl_cfilter *cf, + struct Curl_easy *data, + void *user_data); -CURLcode -Curl_gtls_verifyserver(struct Curl_easy *data, - gnutls_session_t session, - struct ssl_primary_config *config, - struct ssl_config_data *ssl_config, - struct ssl_peer *peer, - const char *pinned_key); +CURLcode Curl_gtls_ctx_init(struct gtls_ctx *gctx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + const unsigned char *alpn, size_t alpn_len, + Curl_gtls_ctx_setup_cb *cb_setup, + void *cb_user_data, + void *ssl_user_data); + +CURLcode Curl_gtls_client_trust_setup(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct gtls_ctx *gtls); + +CURLcode Curl_gtls_verifyserver(struct Curl_easy *data, + gnutls_session_t session, + struct ssl_primary_config *config, + struct ssl_config_data *ssl_config, + struct ssl_peer *peer, + const char *pinned_key); extern const struct Curl_ssl Curl_ssl_gnutls; diff --git a/lib/vtls/keylog.c b/lib/vtls/keylog.c index fbcb25c..ab7baaa 100644 --- a/lib/vtls/keylog.c +++ b/lib/vtls/keylog.c @@ -24,6 +24,7 @@ #include "curl_setup.h" #if defined(USE_OPENSSL) || \ + defined(USE_GNUTLS) || \ defined(USE_WOLFSSL) || \ (defined(USE_NGTCP2) && defined(USE_NGHTTP3)) || \ defined(USE_QUICHE) diff --git a/lib/vtls/mbedtls.c b/lib/vtls/mbedtls.c index 5f07e78..ec0b10d 100644 --- a/lib/vtls/mbedtls.c +++ b/lib/vtls/mbedtls.c @@ -67,6 +67,8 @@ #pragma GCC diagnostic pop #endif +#include "cipher_suite.h" +#include "strcase.h" #include "urldata.h" #include "sendf.h" #include "inet_pton.h" @@ -107,6 +109,7 @@ struct mbed_ssl_backend_data { #ifdef HAS_ALPN const char *protocols[3]; #endif + int *ciphersuites; }; /* apply threading? */ @@ -119,6 +122,10 @@ struct mbed_ssl_backend_data { #define mbedtls_strerror(a,b,c) b[0] = 0 #endif +#if defined(MBEDTLS_SSL_PROTO_TLS1_3) && MBEDTLS_VERSION_NUMBER >= 0x03060000 +#define TLS13_SUPPORT +#endif + #if defined(THREADING_SUPPORT) static mbedtls_entropy_context ts_entropy; @@ -163,15 +170,18 @@ static int entropy_func_mutex(void *data, unsigned char *output, size_t len) static void mbed_debug(void *context, int level, const char *f_name, int line_nb, const char *line) { - struct Curl_easy *data = NULL; - - if(!context) - return; - - data = (struct Curl_easy *)context; - - infof(data, "%s", line); + struct Curl_easy *data = (struct Curl_easy *)context; (void) level; + (void) line_nb; + (void) f_name; + + if(data) { + size_t len = strlen(line); + if(len && (line[len - 1] == '\n')) + /* discount any trailing newline */ + len--; + infof(data, "%.*s", (int)len, line); + } } #endif @@ -256,7 +266,12 @@ static CURLcode mbedtls_version_from_curl( *mbedver = MBEDTLS_SSL_VERSION_TLS1_2; return CURLE_OK; case CURL_SSLVERSION_TLSv1_3: +#ifdef TLS13_SUPPORT + *mbedver = MBEDTLS_SSL_VERSION_TLS1_3; + return CURLE_OK; +#else break; +#endif } return CURLE_SSL_CONNECT_ERROR; @@ -303,7 +318,11 @@ set_ssl_version_min_max(struct Curl_cfilter *cf, struct Curl_easy *data) struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); #if MBEDTLS_VERSION_NUMBER >= 0x03020000 mbedtls_ssl_protocol_version mbedtls_ver_min = MBEDTLS_SSL_VERSION_TLS1_2; +#ifdef TLS13_SUPPORT + mbedtls_ssl_protocol_version mbedtls_ver_max = MBEDTLS_SSL_VERSION_TLS1_3; +#else mbedtls_ssl_protocol_version mbedtls_ver_max = MBEDTLS_SSL_VERSION_TLS1_2; +#endif #elif MBEDTLS_VERSION_NUMBER >= 0x03000000 int mbedtls_ver_min = MBEDTLS_SSL_MINOR_VERSION_3; int mbedtls_ver_max = MBEDTLS_SSL_MINOR_VERSION_3; @@ -327,7 +346,11 @@ set_ssl_version_min_max(struct Curl_cfilter *cf, struct Curl_easy *data) switch(ssl_version_max) { case CURL_SSLVERSION_MAX_NONE: case CURL_SSLVERSION_MAX_DEFAULT: +#ifdef TLS13_SUPPORT + ssl_version_max = CURL_SSLVERSION_MAX_TLSv1_3; +#else ssl_version_max = CURL_SSLVERSION_MAX_TLSv1_2; +#endif break; } @@ -352,9 +375,113 @@ set_ssl_version_min_max(struct Curl_cfilter *cf, struct Curl_easy *data) mbedtls_ver_max); #endif +#ifdef TLS13_SUPPORT + if(mbedtls_ver_min == MBEDTLS_SSL_VERSION_TLS1_3) { + mbedtls_ssl_conf_authmode(&backend->config, MBEDTLS_SSL_VERIFY_REQUIRED); + } + else { + mbedtls_ssl_conf_authmode(&backend->config, MBEDTLS_SSL_VERIFY_OPTIONAL); + } +#else + mbedtls_ssl_conf_authmode(&backend->config, MBEDTLS_SSL_VERIFY_OPTIONAL); +#endif + return result; } +/* TLS_ECJPAKE_WITH_AES_128_CCM_8 (0xC0FF) is marked experimental + in mbedTLS. The number is not reserved by IANA nor is the + cipher suite present in other SSL implementations. Provide + provisional support for specifying the cipher suite here. */ +#ifdef MBEDTLS_TLS_ECJPAKE_WITH_AES_128_CCM_8 +static int +mbed_cipher_suite_get_str(uint16_t id, char *buf, size_t buf_size, + bool prefer_rfc) +{ + if(id == MBEDTLS_TLS_ECJPAKE_WITH_AES_128_CCM_8) + msnprintf(buf, buf_size, "%s", "TLS_ECJPAKE_WITH_AES_128_CCM_8"); + else + return Curl_cipher_suite_get_str(id, buf, buf_size, prefer_rfc); + return 0; +} + +static uint16_t +mbed_cipher_suite_walk_str(const char **str, const char **end) +{ + uint16_t id = Curl_cipher_suite_walk_str(str, end); + size_t len = *end - *str; + + if(!id) { + if(strncasecompare("TLS_ECJPAKE_WITH_AES_128_CCM_8", *str, len)) + id = MBEDTLS_TLS_ECJPAKE_WITH_AES_128_CCM_8; + } + return id; +} +#else +#define mbed_cipher_suite_get_str Curl_cipher_suite_get_str +#define mbed_cipher_suite_walk_str Curl_cipher_suite_walk_str +#endif + +static CURLcode +mbed_set_selected_ciphers(struct Curl_easy *data, + struct mbed_ssl_backend_data *backend, + const char *ciphers) +{ + const int *supported; + int *selected; + size_t supported_len, count = 0, i; + const char *ptr, *end; + + supported = mbedtls_ssl_list_ciphersuites(); + for(i = 0; supported[i] != 0; i++); + supported_len = i; + + selected = malloc(sizeof(int) * (supported_len + 1)); + if(!selected) + return CURLE_OUT_OF_MEMORY; + + for(ptr = ciphers; ptr[0] != '\0' && count < supported_len; ptr = end) { + uint16_t id = mbed_cipher_suite_walk_str(&ptr, &end); + + /* Check if cipher is supported */ + if(id) { + for(i = 0; i < supported_len && supported[i] != id; i++); + if(i == supported_len) + id = 0; + } + if(!id) { + if(ptr[0] != '\0') + infof(data, "mbedTLS: unknown cipher in list: \"%.*s\"", + (int) (end - ptr), ptr); + continue; + } + + /* No duplicates allowed (so selected cannot overflow) */ + for(i = 0; i < count && selected[i] != id; i++); + if(i < count) { + infof(data, "mbedTLS: duplicate cipher in list: \"%.*s\"", + (int) (end - ptr), ptr); + continue; + } + + selected[count++] = id; + } + + selected[count] = 0; + + if(count == 0) { + free(selected); + failf(data, "mbedTLS: no supported cipher in list"); + return CURLE_SSL_CIPHER; + } + + /* mbedtls_ssl_conf_ciphersuites(): The ciphersuites array is not copied. + It must remain valid for the lifetime of the SSL configuration */ + backend->ciphersuites = selected; + mbedtls_ssl_conf_ciphersuites(&backend->config, backend->ciphersuites); + return CURLE_OK; +} + static CURLcode mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) { @@ -384,6 +511,16 @@ mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) return CURLE_NOT_BUILT_IN; } +#ifdef TLS13_SUPPORT + ret = psa_crypto_init(); + if(ret != PSA_SUCCESS) { + mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); + failf(data, "mbedTLS psa_crypto_init returned (-0x%04X) %s", + -ret, errorbuf); + return CURLE_SSL_CONNECT_ERROR; + } +#endif /* TLS13_SUPPORT */ + #ifdef THREADING_SUPPORT mbedtls_ctr_drbg_init(&backend->ctr_drbg); @@ -589,7 +726,7 @@ mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) } #endif - infof(data, "mbedTLS: Connecting to %s:%d", hostname, connssl->port); + infof(data, "mbedTLS: Connecting to %s:%d", hostname, connssl->peer.port); mbedtls_ssl_config_init(&backend->config); ret = mbedtls_ssl_config_defaults(&backend->config, @@ -602,10 +739,6 @@ mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) } mbedtls_ssl_init(&backend->ssl); - if(mbedtls_ssl_setup(&backend->ssl, &backend->config)) { - failf(data, "mbedTLS: ssl_init failed"); - return CURLE_SSL_CONNECT_ERROR; - } /* new profile with RSA min key len = 1024 ... */ mbedtls_ssl_conf_cert_profile(&backend->config, @@ -635,17 +768,34 @@ mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) return CURLE_SSL_CONNECT_ERROR; } - mbedtls_ssl_conf_authmode(&backend->config, MBEDTLS_SSL_VERIFY_OPTIONAL); - mbedtls_ssl_conf_rng(&backend->config, mbedtls_ctr_drbg_random, &backend->ctr_drbg); + + ret = mbedtls_ssl_setup(&backend->ssl, &backend->config); + if(ret) { + mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); + failf(data, "ssl_setup failed - mbedTLS: (-0x%04X) %s", + -ret, errorbuf); + return CURLE_SSL_CONNECT_ERROR; + } + mbedtls_ssl_set_bio(&backend->ssl, cf, mbedtls_bio_cf_write, mbedtls_bio_cf_read, NULL /* rev_timeout() */); - mbedtls_ssl_conf_ciphersuites(&backend->config, - mbedtls_ssl_list_ciphersuites()); + if(conn_config->cipher_list) { + ret = mbed_set_selected_ciphers(data, backend, conn_config->cipher_list); + if(ret) { + failf(data, "mbedTLS: failed to set cipher suites"); + return ret; + } + } + else { + mbedtls_ssl_conf_ciphersuites(&backend->config, + mbedtls_ssl_list_ciphersuites()); + } + #if defined(MBEDTLS_SSL_RENEGOTIATION) mbedtls_ssl_conf_renegotiation(&backend->config, @@ -662,7 +812,7 @@ mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) void *old_session = NULL; Curl_ssl_sessionid_lock(data); - if(!Curl_ssl_getsessionid(cf, data, &old_session, NULL)) { + if(!Curl_ssl_getsessionid(cf, data, &connssl->peer, &old_session, NULL)) { ret = mbedtls_ssl_set_session(&backend->ssl, old_session); if(ret) { Curl_ssl_sessionid_unlock(data); @@ -752,9 +902,15 @@ mbed_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) (struct mbed_ssl_backend_data *)connssl->backend; struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); const mbedtls_x509_crt *peercert; + char cipher_str[64]; + uint16_t cipher_id; +#ifndef CURL_DISABLE_PROXY const char * const pinnedpubkey = Curl_ssl_cf_is_proxy(cf)? data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY]: data->set.str[STRING_SSL_PINNEDPUBLICKEY]; +#else + const char * const pinnedpubkey = data->set.str[STRING_SSL_PINNEDPUBLICKEY]; +#endif DEBUGASSERT(backend); @@ -776,8 +932,10 @@ mbed_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) return CURLE_SSL_CONNECT_ERROR; } - infof(data, "mbedTLS: Handshake complete, cipher is %s", - mbedtls_ssl_get_ciphersuite(&backend->ssl)); + cipher_id = (uint16_t) + mbedtls_ssl_get_ciphersuite_id_from_ssl(&backend->ssl); + mbed_cipher_suite_get_str(cipher_id, cipher_str, sizeof(cipher_str), true); + infof(data, "mbedTLS: Handshake complete, cipher is %s", cipher_str); ret = mbedtls_ssl_get_verify_result(&backend->ssl); @@ -911,6 +1069,13 @@ pinnedpubkey_error: return CURLE_OK; } +static void mbedtls_session_free(void *sessionid, size_t idsize) +{ + (void)idsize; + mbedtls_ssl_session_free(sessionid); + free(sessionid); +} + static CURLcode mbed_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data) { @@ -927,7 +1092,6 @@ mbed_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data) int ret; mbedtls_ssl_session *our_ssl_sessionid; void *old_ssl_sessionid = NULL; - bool added = FALSE; our_ssl_sessionid = malloc(sizeof(mbedtls_ssl_session)); if(!our_ssl_sessionid) @@ -946,20 +1110,16 @@ mbed_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data) /* If there's already a matching session in the cache, delete it */ Curl_ssl_sessionid_lock(data); - if(!Curl_ssl_getsessionid(cf, data, &old_ssl_sessionid, NULL)) + if(!Curl_ssl_getsessionid(cf, data, &connssl->peer, + &old_ssl_sessionid, NULL)) Curl_ssl_delsessionid(data, old_ssl_sessionid); - retcode = Curl_ssl_addsessionid(cf, data, our_ssl_sessionid, - 0, &added); + retcode = Curl_ssl_addsessionid(cf, data, &connssl->peer, + our_ssl_sessionid, 0, + mbedtls_session_free); Curl_ssl_sessionid_unlock(data); - if(!added) { - mbedtls_ssl_session_free(our_ssl_sessionid); - free(our_ssl_sessionid); - } - if(retcode) { - failf(data, "failed to store ssl session"); + if(retcode) return retcode; - } } connssl->connecting_state = ssl_connect_done; @@ -1014,6 +1174,7 @@ static void mbedtls_close(struct Curl_cfilter *cf, struct Curl_easy *data) #ifdef MBEDTLS_X509_CRL_PARSE_C mbedtls_x509_crl_free(&backend->crl); #endif + Curl_safefree(backend->ciphersuites); mbedtls_ssl_config_free(&backend->config); mbedtls_ssl_free(&backend->ssl); mbedtls_ctr_drbg_free(&backend->ctr_drbg); @@ -1042,8 +1203,11 @@ static ssize_t mbed_recv(struct Curl_cfilter *cf, struct Curl_easy *data, if(ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) return 0; - *curlcode = (ret == MBEDTLS_ERR_SSL_WANT_READ) ? - CURLE_AGAIN : CURLE_RECV_ERROR; + *curlcode = ((ret == MBEDTLS_ERR_SSL_WANT_READ) +#ifdef TLS13_SUPPORT + || (ret == MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET) +#endif + ) ? CURLE_AGAIN : CURLE_RECV_ERROR; return -1; } @@ -1052,12 +1216,6 @@ static ssize_t mbed_recv(struct Curl_cfilter *cf, struct Curl_easy *data, return len; } -static void mbedtls_session_free(void *ptr) -{ - mbedtls_ssl_session_free(ptr); - free(ptr); -} - static size_t mbedtls_version(char *buffer, size_t size) { #ifdef MBEDTLS_VERSION_C @@ -1336,7 +1494,6 @@ const struct Curl_ssl Curl_ssl_mbedtls = { mbedtls_get_internals, /* get_internals */ mbedtls_close, /* close_one */ mbedtls_close_all, /* close_all */ - mbedtls_session_free, /* session_free */ Curl_none_set_engine, /* set_engine */ Curl_none_set_engine_default, /* set_engine_default */ Curl_none_engines_list, /* engines_list */ diff --git a/lib/vtls/openssl.c b/lib/vtls/openssl.c index a3953f6..298a488 100644 --- a/lib/vtls/openssl.c +++ b/lib/vtls/openssl.c @@ -82,6 +82,17 @@ #include <openssl/tls1.h> #include <openssl/evp.h> +#ifdef USE_ECH +# ifndef OPENSSL_IS_BORINGSSL +# include <openssl/ech.h> +# endif +# include "curl_base64.h" +# define ECH_ENABLED(__data__) \ + (__data__->set.tls_ech && \ + !(__data__->set.tls_ech & CURLECH_DISABLE)\ + ) +#endif /* USE_ECH */ + #if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_OCSP) #include <openssl/ocsp.h> #endif @@ -193,12 +204,10 @@ * Whether SSL_CTX_set_keylog_callback is available. * OpenSSL: supported since 1.1.1 https://github.com/openssl/openssl/pull/2287 * BoringSSL: supported since d28f59c27bac (committed 2015-11-19) - * LibreSSL: supported since 3.5.0 (released 2022-02-24) + * LibreSSL: not supported. 3.5.0+ has a stub function that does nothing. */ #if (OPENSSL_VERSION_NUMBER >= 0x10101000L && \ !defined(LIBRESSL_VERSION_NUMBER)) || \ - (defined(LIBRESSL_VERSION_NUMBER) && \ - LIBRESSL_VERSION_NUMBER >= 0x3050000fL) || \ defined(OPENSSL_IS_BORINGSSL) #define HAVE_KEYLOG_CALLBACK #endif @@ -298,20 +307,6 @@ typedef unsigned long sslerr_t; #define USE_PRE_1_1_API (OPENSSL_VERSION_NUMBER < 0x10100000L) #endif /* !LIBRESSL_VERSION_NUMBER */ -struct ossl_ssl_backend_data { - /* these ones requires specific SSL-types */ - SSL_CTX* ctx; - SSL* handle; - X509* server_cert; - BIO_METHOD *bio_method; - CURLcode io_result; /* result of last BIO cfilter operation */ -#ifndef HAVE_KEYLOG_CALLBACK - /* Set to true once a valid keylog entry has been created to avoid dupes. */ - bool keylog_done; -#endif - bool x509_store_setup; /* x509 store has been set up */ -}; - #if defined(HAVE_SSL_X509_STORE_SHARE) struct multi_ssl_backend_data { char *CAfile; /* CAfile path used to generate X509 store */ @@ -726,8 +721,7 @@ static int ossl_bio_cf_out_write(BIO *bio, const char *buf, int blen) { struct Curl_cfilter *cf = BIO_get_data(bio); struct ssl_connect_data *connssl = cf->ctx; - struct ossl_ssl_backend_data *backend = - (struct ossl_ssl_backend_data *)connssl->backend; + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; struct Curl_easy *data = CF_DATA_CURRENT(cf); ssize_t nwritten; CURLcode result = CURLE_SEND_ERROR; @@ -737,7 +731,7 @@ static int ossl_bio_cf_out_write(BIO *bio, const char *buf, int blen) CURL_TRC_CF(data, cf, "ossl_bio_cf_out_write(len=%d) -> %d, err=%d", blen, (int)nwritten, result); BIO_clear_retry_flags(bio); - backend->io_result = result; + octx->io_result = result; if(nwritten < 0) { if(CURLE_AGAIN == result) BIO_set_retry_write(bio); @@ -749,8 +743,7 @@ static int ossl_bio_cf_in_read(BIO *bio, char *buf, int blen) { struct Curl_cfilter *cf = BIO_get_data(bio); struct ssl_connect_data *connssl = cf->ctx; - struct ossl_ssl_backend_data *backend = - (struct ossl_ssl_backend_data *)connssl->backend; + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; struct Curl_easy *data = CF_DATA_CURRENT(cf); ssize_t nread; CURLcode result = CURLE_RECV_ERROR; @@ -764,7 +757,7 @@ static int ossl_bio_cf_in_read(BIO *bio, char *buf, int blen) CURL_TRC_CF(data, cf, "ossl_bio_cf_in_read(len=%d) -> %d, err=%d", blen, (int)nread, result); BIO_clear_retry_flags(bio); - backend->io_result = result; + octx->io_result = result; if(nread < 0) { if(CURLE_AGAIN == result) BIO_set_retry_read(bio); @@ -775,13 +768,13 @@ static int ossl_bio_cf_in_read(BIO *bio, char *buf, int blen) /* Before returning server replies to the SSL instance, we need * to have setup the x509 store or verification will fail. */ - if(!backend->x509_store_setup) { - result = Curl_ssl_setup_x509_store(cf, data, backend->ctx); + if(!octx->x509_store_setup) { + result = Curl_ssl_setup_x509_store(cf, data, octx->ssl_ctx); if(result) { - backend->io_result = result; + octx->io_result = result; return -1; } - backend->x509_store_setup = TRUE; + octx->x509_store_setup = TRUE; } return (int)nread; @@ -1883,13 +1876,12 @@ static struct curl_slist *ossl_engines_list(struct Curl_easy *data) static void ossl_close(struct Curl_cfilter *cf, struct Curl_easy *data) { struct ssl_connect_data *connssl = cf->ctx; - struct ossl_ssl_backend_data *backend = - (struct ossl_ssl_backend_data *)connssl->backend; + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; (void)data; - DEBUGASSERT(backend); + DEBUGASSERT(octx); - if(backend->handle) { + 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) { @@ -1900,8 +1892,8 @@ static void ossl_close(struct Curl_cfilter *cf, struct Curl_easy *data) /* 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(backend->handle, buf, (int)sizeof(buf)); - err = SSL_get_error(backend->handle, nread); + 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; @@ -1924,12 +1916,12 @@ static void ossl_close(struct Curl_cfilter *cf, struct Curl_easy *data) CURL_TRC_CF(data, cf, "not from sending TLS shutdown on " "connection closed by peer"); } - else if(SSL_shutdown(backend->handle) == 1) { + else if(SSL_shutdown(octx->ssl) == 1) { CURL_TRC_CF(data, cf, "SSL shutdown finished"); } else { - nread = SSL_read(backend->handle, buf, (int)sizeof(buf)); - err = SSL_get_error(backend->handle, nread); + 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 */ @@ -1955,20 +1947,20 @@ static void ossl_close(struct Curl_cfilter *cf, struct Curl_easy *data) } ERR_clear_error(); - SSL_set_connect_state(backend->handle); + SSL_set_connect_state(octx->ssl); } - SSL_free(backend->handle); - backend->handle = NULL; + SSL_free(octx->ssl); + octx->ssl = NULL; } - if(backend->ctx) { - SSL_CTX_free(backend->ctx); - backend->ctx = NULL; - backend->x509_store_setup = FALSE; + if(octx->ssl_ctx) { + SSL_CTX_free(octx->ssl_ctx); + octx->ssl_ctx = NULL; + octx->x509_store_setup = FALSE; } - if(backend->bio_method) { - ossl_bio_cf_method_free(backend->bio_method); - backend->bio_method = NULL; + if(octx->bio_method) { + ossl_bio_cf_method_free(octx->bio_method); + octx->bio_method = NULL; } } @@ -1988,11 +1980,10 @@ static int ossl_shutdown(struct Curl_cfilter *cf, int buffsize; int err; bool done = FALSE; - struct ossl_ssl_backend_data *backend = - (struct ossl_ssl_backend_data *)connssl->backend; + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; int loop = 10; - DEBUGASSERT(backend); + DEBUGASSERT(octx); #ifndef CURL_DISABLE_FTP /* This has only been tested on the proftpd server, and the mod_tls code @@ -2001,10 +1992,10 @@ static int ossl_shutdown(struct Curl_cfilter *cf, we do not send one. Let's hope other servers do the same... */ if(data->set.ftp_ccc == CURLFTPSSL_CCC_ACTIVE) - (void)SSL_shutdown(backend->handle); + (void)SSL_shutdown(octx->ssl); #endif - if(backend->handle) { + if(octx->ssl) { buffsize = (int)sizeof(buf); while(!done && loop--) { int what = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data), @@ -2014,8 +2005,8 @@ static int ossl_shutdown(struct Curl_cfilter *cf, /* Something to read, let's do it and hope that it is the close notify alert from the server */ - nread = SSL_read(backend->handle, buf, buffsize); - err = SSL_get_error(backend->handle, nread); + 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 */ @@ -2060,7 +2051,7 @@ static int ossl_shutdown(struct Curl_cfilter *cf, if(data->set.verbose) { #ifdef HAVE_SSL_GET_SHUTDOWN - switch(SSL_get_shutdown(backend->handle)) { + switch(SSL_get_shutdown(octx->ssl)) { case SSL_SENT_SHUTDOWN: infof(data, "SSL_get_shutdown() returned SSL_SENT_SHUTDOWN"); break; @@ -2075,16 +2066,17 @@ static int ossl_shutdown(struct Curl_cfilter *cf, #endif } - SSL_free(backend->handle); - backend->handle = NULL; + SSL_free(octx->ssl); + octx->ssl = NULL; } return retval; } -static void ossl_session_free(void *ptr) +static void ossl_session_free(void *sessionid, size_t idsize) { /* free the ID */ - SSL_SESSION_free(ptr); + (void)idsize; + SSL_SESSION_free(sessionid); } /* @@ -2165,14 +2157,14 @@ CURLcode Curl_ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn, int target; /* target type, GEN_DNS or GEN_IPADD */ size_t addrlen = 0; STACK_OF(GENERAL_NAME) *altnames; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 struct in6_addr addr; #else struct in_addr addr; #endif CURLcode result = CURLE_OK; bool dNSName = FALSE; /* if a dNSName field exists in the cert */ - bool iPAddress = FALSE; /* if a iPAddress field exists in the cert */ + bool iPAddress = FALSE; /* if an iPAddress field exists in the cert */ size_t hostlen; (void)conn; @@ -2184,7 +2176,7 @@ CURLcode Curl_ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn, target = GEN_IPADD; addrlen = sizeof(struct in_addr); break; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 case CURL_SSL_PEER_IPV6: if(!Curl_inet_pton(AF_INET6, peer->hostname, &addr)) return CURLE_PEER_FAILED_VERIFICATION; @@ -2381,8 +2373,7 @@ static CURLcode verifystatus(struct Curl_cfilter *cf, OCSP_BASICRESP *br = NULL; X509_STORE *st = NULL; STACK_OF(X509) *ch = NULL; - struct ossl_ssl_backend_data *backend = - (struct ossl_ssl_backend_data *)connssl->backend; + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; X509 *cert; OCSP_CERTID *id = NULL; int cert_status, crl_reason; @@ -2390,9 +2381,9 @@ static CURLcode verifystatus(struct Curl_cfilter *cf, int ret; long len; - DEBUGASSERT(backend); + DEBUGASSERT(octx); - len = SSL_get_tlsext_status_ocsp_resp(backend->handle, &status); + len = SSL_get_tlsext_status_ocsp_resp(octx->ssl, &status); if(!status) { failf(data, "No OCSP response received"); @@ -2422,13 +2413,13 @@ static CURLcode verifystatus(struct Curl_cfilter *cf, goto end; } - ch = SSL_get_peer_cert_chain(backend->handle); + ch = SSL_get_peer_cert_chain(octx->ssl); if(!ch) { failf(data, "Could not get peer certificate chain"); result = CURLE_SSL_INVALIDCERTSTATUS; goto end; } - st = SSL_CTX_get_cert_store(backend->ctx); + st = SSL_CTX_get_cert_store(octx->ssl_ctx); #if ((OPENSSL_VERSION_NUMBER <= 0x1000201fL) /* Fixed after 1.0.2a */ || \ (defined(LIBRESSL_VERSION_NUMBER) && \ @@ -2465,7 +2456,7 @@ static CURLcode verifystatus(struct Curl_cfilter *cf, } /* Compute the certificate's ID */ - cert = SSL_get1_peer_certificate(backend->handle); + cert = SSL_get1_peer_certificate(octx->ssl); if(!cert) { failf(data, "Error getting peer certificate"); result = CURLE_SSL_INVALIDCERTSTATUS; @@ -2880,10 +2871,9 @@ ossl_set_ssl_version_min_max_legacy(ctx_option_t *ctx_options, #ifdef TLS1_3_VERSION { struct ssl_connect_data *connssl = cf->ctx; - struct ossl_ssl_backend_data *backend = - (struct ossl_ssl_backend_data *)connssl->backend; - DEBUGASSERT(backend); - SSL_CTX_set_max_proto_version(backend->ctx, TLS1_3_VERSION); + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; + DEBUGASSERT(octx); + SSL_CTX_set_max_proto_version(octx->ssl_ctx, TLS1_3_VERSION); *ctx_options |= SSL_OP_NO_TLSv1_2; } #else @@ -2941,61 +2931,64 @@ ossl_set_ssl_version_min_max_legacy(ctx_option_t *ctx_options, } #endif -/* The "new session" callback must return zero if the session can be removed - * or non-zero if the session has been put into the session cache. - */ -static int ossl_new_session_cb(SSL *ssl, SSL_SESSION *ssl_sessionid) +CURLcode Curl_ossl_add_session(struct Curl_cfilter *cf, + struct Curl_easy *data, + const struct ssl_peer *peer, + SSL_SESSION *session) { - int res = 0; - struct Curl_easy *data; - struct Curl_cfilter *cf; const struct ssl_config_data *config; - struct ssl_connect_data *connssl; bool isproxy; + bool added = FALSE; - cf = (struct Curl_cfilter*) SSL_get_app_data(ssl); - connssl = cf? cf->ctx : NULL; - data = connssl? CF_DATA_CURRENT(cf) : NULL; - /* The sockindex has been stored as a pointer to an array element */ if(!cf || !data) - return 0; + goto out; isproxy = Curl_ssl_cf_is_proxy(cf); config = Curl_ssl_cf_get_config(cf, data); if(config->primary.sessionid) { bool incache; - bool added = FALSE; - void *old_ssl_sessionid = NULL; + void *old_session = NULL; Curl_ssl_sessionid_lock(data); if(isproxy) incache = FALSE; else - incache = !(Curl_ssl_getsessionid(cf, data, &old_ssl_sessionid, NULL)); - if(incache) { - if(old_ssl_sessionid != ssl_sessionid) { - infof(data, "old SSL session ID is stale, removing"); - Curl_ssl_delsessionid(data, old_ssl_sessionid); - incache = FALSE; - } + incache = !(Curl_ssl_getsessionid(cf, data, peer, + &old_session, NULL)); + if(incache && (old_session != session)) { + infof(data, "old SSL session ID is stale, removing"); + Curl_ssl_delsessionid(data, old_session); + incache = FALSE; } if(!incache) { - if(!Curl_ssl_addsessionid(cf, data, ssl_sessionid, - 0 /* unknown size */, &added)) { - if(added) { - /* the session has been put into the session cache */ - res = 1; - } - } - else - failf(data, "failed to store ssl session"); + added = TRUE; + Curl_ssl_addsessionid(cf, data, peer, session, 0, ossl_session_free); } Curl_ssl_sessionid_unlock(data); } - return res; +out: + if(!added) + ossl_session_free(session, 0); + return CURLE_OK; +} + +/* The "new session" callback must return zero if the session can be removed + * or non-zero if the session has been put into the session cache. + */ +static int ossl_new_session_cb(SSL *ssl, SSL_SESSION *ssl_sessionid) +{ + struct Curl_cfilter *cf; + struct Curl_easy *data; + struct ssl_connect_data *connssl; + + cf = (struct Curl_cfilter*) SSL_get_app_data(ssl); + connssl = cf? cf->ctx : NULL; + data = connssl? CF_DATA_CURRENT(cf) : NULL; + Curl_ossl_add_session(cf, data, &connssl->peer, ssl_sessionid); + return 1; } static CURLcode load_cacert_from_memory(X509_STORE *store, @@ -3390,7 +3383,7 @@ static bool cached_x509_store_different( static X509_STORE *get_cached_x509_store(struct Curl_cfilter *cf, const struct Curl_easy *data) { - struct Curl_multi *multi = data->multi_easy ? data->multi_easy : data->multi; + struct Curl_multi *multi = data->multi; X509_STORE *store = NULL; DEBUGASSERT(multi); @@ -3410,7 +3403,7 @@ static void set_cached_x509_store(struct Curl_cfilter *cf, X509_STORE *store) { struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); - struct Curl_multi *multi = data->multi_easy ? data->multi_easy : data->multi; + struct Curl_multi *multi = data->multi; struct multi_ssl_backend_data *mbackend; DEBUGASSERT(multi); @@ -3493,29 +3486,33 @@ CURLcode Curl_ssl_setup_x509_store(struct Curl_cfilter *cf, } #endif /* HAVE_SSL_X509_STORE_SHARE */ -static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, - struct Curl_easy *data) +CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + int transport, /* TCP or QUIC */ + const unsigned char *alpn, size_t alpn_len, + Curl_ossl_ctx_setup_cb *cb_setup, + void *cb_user_data, + Curl_ossl_new_session_cb *cb_new_session, + void *ssl_user_data) { CURLcode result = CURLE_OK; - char *ciphers; + const char *ciphers; SSL_METHOD_QUAL SSL_METHOD *req_method = NULL; - struct ssl_connect_data *connssl = cf->ctx; ctx_option_t ctx_options = 0; void *ssl_sessionid = NULL; struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); - BIO *bio; const long int ssl_version = conn_config->version; char * const ssl_cert = ssl_config->primary.clientcert; const struct curl_blob *ssl_cert_blob = ssl_config->primary.cert_blob; const char * const ssl_cert_type = ssl_config->cert_type; const bool verifypeer = conn_config->verifypeer; char error_buffer[256]; - struct ossl_ssl_backend_data *backend = - (struct ossl_ssl_backend_data *)connssl->backend; - - DEBUGASSERT(ssl_connect_1 == connssl->connecting_state); - DEBUGASSERT(backend); +#ifdef USE_ECH + struct ssl_connect_data *connssl = cf->ctx; +#endif /* Make funny stuff to get random input */ result = ossl_seed(data); @@ -3524,56 +3521,74 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, ssl_config->certverifyresult = !X509_V_OK; - /* check to see if we've been told to use an explicit SSL/TLS version */ - - switch(ssl_version) { - case CURL_SSLVERSION_DEFAULT: - case CURL_SSLVERSION_TLSv1: - case CURL_SSLVERSION_TLSv1_0: - case CURL_SSLVERSION_TLSv1_1: - case CURL_SSLVERSION_TLSv1_2: - case CURL_SSLVERSION_TLSv1_3: - /* it will be handled later with the context options */ -#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) - req_method = TLS_client_method(); + switch(transport) { + case TRNSPRT_TCP: + /* check to see if we've been told to use an explicit SSL/TLS version */ + switch(ssl_version) { + case CURL_SSLVERSION_DEFAULT: + case CURL_SSLVERSION_TLSv1: + case CURL_SSLVERSION_TLSv1_0: + case CURL_SSLVERSION_TLSv1_1: + case CURL_SSLVERSION_TLSv1_2: + case CURL_SSLVERSION_TLSv1_3: + /* it will be handled later with the context options */ + #if (OPENSSL_VERSION_NUMBER >= 0x10100000L) + req_method = TLS_client_method(); + #else + req_method = SSLv23_client_method(); + #endif + break; + case CURL_SSLVERSION_SSLv2: + failf(data, "No SSLv2 support"); + return CURLE_NOT_BUILT_IN; + case CURL_SSLVERSION_SSLv3: + failf(data, "No SSLv3 support"); + return CURLE_NOT_BUILT_IN; + default: + failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); + return CURLE_SSL_CONNECT_ERROR; + } + break; + case TRNSPRT_QUIC: + if((ssl_version != CURL_SSLVERSION_DEFAULT) && + (ssl_version < CURL_SSLVERSION_TLSv1_3)) { + failf(data, "QUIC needs at least TLS version 1.3"); + return CURLE_SSL_CONNECT_ERROR; + } +#ifdef USE_OPENSSL_QUIC + req_method = OSSL_QUIC_client_method(); +#elif (OPENSSL_VERSION_NUMBER >= 0x10100000L) + req_method = TLS_method(); #else req_method = SSLv23_client_method(); #endif break; - case CURL_SSLVERSION_SSLv2: - failf(data, "No SSLv2 support"); - return CURLE_NOT_BUILT_IN; - case CURL_SSLVERSION_SSLv3: - failf(data, "No SSLv3 support"); - return CURLE_NOT_BUILT_IN; default: - failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); + failf(data, "unsupported transport %d in SSL init", transport); return CURLE_SSL_CONNECT_ERROR; } - if(backend->ctx) { - /* This happens when an error was encountered before in this - * step and we are called to do it again. Get rid of any leftover - * from the previous call. */ - ossl_close(cf, data); - } - backend->ctx = SSL_CTX_new(req_method); - if(!backend->ctx) { + DEBUGASSERT(!octx->ssl_ctx); + octx->ssl_ctx = SSL_CTX_new(req_method); + + if(!octx->ssl_ctx) { failf(data, "SSL: couldn't create a context: %s", ossl_strerror(ERR_peek_error(), error_buffer, sizeof(error_buffer))); return CURLE_OUT_OF_MEMORY; } -#ifdef SSL_MODE_RELEASE_BUFFERS - SSL_CTX_set_mode(backend->ctx, SSL_MODE_RELEASE_BUFFERS); -#endif + if(cb_setup) { + result = cb_setup(cf, data, cb_user_data); + if(result) + return result; + } #ifdef SSL_CTRL_SET_MSG_CALLBACK if(data->set.fdebug && data->set.verbose) { /* the SSL trace callback is only used for verbose logging */ - SSL_CTX_set_msg_callback(backend->ctx, ossl_trace); - SSL_CTX_set_msg_callback_arg(backend->ctx, cf); + SSL_CTX_set_msg_callback(octx->ssl_ctx, ossl_trace); + SSL_CTX_set_msg_callback_arg(octx->ssl_ctx, cf); } #endif @@ -3653,7 +3668,7 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, ctx_options |= SSL_OP_NO_SSLv3; #if (OPENSSL_VERSION_NUMBER >= 0x10100000L) /* 1.1.0 */ - result = ossl_set_ssl_version_min_max(cf, backend->ctx); + result = ossl_set_ssl_version_min_max(cf, octx->ssl_ctx); #else result = ossl_set_ssl_version_min_max_legacy(&ctx_options, cf, data); #endif @@ -3666,26 +3681,20 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, return CURLE_SSL_CONNECT_ERROR; } - SSL_CTX_set_options(backend->ctx, ctx_options); + SSL_CTX_set_options(octx->ssl_ctx, ctx_options); #ifdef HAS_ALPN - if(connssl->alpn) { - struct alpn_proto_buf proto; - - result = Curl_alpn_to_proto_buf(&proto, connssl->alpn); - if(result || - SSL_CTX_set_alpn_protos(backend->ctx, proto.data, proto.len)) { + if(alpn && alpn_len) { + if(SSL_CTX_set_alpn_protos(octx->ssl_ctx, alpn, (int)alpn_len)) { failf(data, "Error setting ALPN"); return CURLE_SSL_CONNECT_ERROR; } - Curl_alpn_to_proto_str(&proto, connssl->alpn); - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); } #endif if(ssl_cert || ssl_cert_blob || ssl_cert_type) { if(!result && - !cert_stuff(data, backend->ctx, + !cert_stuff(data, octx->ssl_ctx, ssl_cert, ssl_cert_blob, ssl_cert_type, ssl_config->key, ssl_config->key_blob, ssl_config->key_type, ssl_config->key_passwd)) @@ -3696,10 +3705,10 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, } ciphers = conn_config->cipher_list; - if(!ciphers) - ciphers = (char *)DEFAULT_CIPHER_SELECTION; + if(!ciphers && (peer->transport != TRNSPRT_QUIC)) + ciphers = DEFAULT_CIPHER_SELECTION; if(ciphers) { - if(!SSL_CTX_set_cipher_list(backend->ctx, ciphers)) { + if(!SSL_CTX_set_cipher_list(octx->ssl_ctx, ciphers)) { failf(data, "failed setting cipher list: %s", ciphers); return CURLE_SSL_CIPHER; } @@ -3708,9 +3717,9 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, #ifdef HAVE_SSL_CTX_SET_CIPHERSUITES { - char *ciphers13 = conn_config->cipher_list13; + const char *ciphers13 = conn_config->cipher_list13; if(ciphers13) { - if(!SSL_CTX_set_ciphersuites(backend->ctx, ciphers13)) { + if(!SSL_CTX_set_ciphersuites(octx->ssl_ctx, ciphers13)) { failf(data, "failed setting TLS 1.3 cipher suite: %s", ciphers13); return CURLE_SSL_CIPHER; } @@ -3721,14 +3730,14 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, #ifdef HAVE_SSL_CTX_SET_POST_HANDSHAKE_AUTH /* OpenSSL 1.1.1 requires clients to opt-in for PHA */ - SSL_CTX_set_post_handshake_auth(backend->ctx, 1); + SSL_CTX_set_post_handshake_auth(octx->ssl_ctx, 1); #endif #ifdef HAVE_SSL_CTX_SET_EC_CURVES { - char *curves = conn_config->curves; + const char *curves = conn_config->curves; if(curves) { - if(!SSL_CTX_set1_curves_list(backend->ctx, curves)) { + if(!SSL_CTX_set1_curves_list(octx->ssl_ctx, curves)) { failf(data, "failed setting curves list: '%s'", curves); return CURLE_SSL_CIPHER; } @@ -3742,18 +3751,18 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, char * const ssl_password = ssl_config->primary.password; infof(data, "Using TLS-SRP username: %s", ssl_username); - if(!SSL_CTX_set_srp_username(backend->ctx, ssl_username)) { + if(!SSL_CTX_set_srp_username(octx->ssl_ctx, ssl_username)) { failf(data, "Unable to set SRP user name"); return CURLE_BAD_FUNCTION_ARGUMENT; } - if(!SSL_CTX_set_srp_password(backend->ctx, ssl_password)) { + if(!SSL_CTX_set_srp_password(octx->ssl_ctx, ssl_password)) { failf(data, "failed setting SRP password"); return CURLE_BAD_FUNCTION_ARGUMENT; } if(!conn_config->cipher_list) { infof(data, "Setting cipher list SRP"); - if(!SSL_CTX_set_cipher_list(backend->ctx, "SRP")) { + if(!SSL_CTX_set_cipher_list(octx->ssl_ctx, "SRP")) { failf(data, "failed setting SRP cipher list"); return CURLE_SSL_CIPHER; } @@ -3765,38 +3774,40 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, * fail to connect if the verification fails, or if it should continue * anyway. In the latter case the result of the verification is checked with * SSL_get_verify_result() below. */ - SSL_CTX_set_verify(backend->ctx, + SSL_CTX_set_verify(octx->ssl_ctx, verifypeer ? SSL_VERIFY_PEER : SSL_VERIFY_NONE, NULL); /* Enable logging of secrets to the file specified in env SSLKEYLOGFILE. */ #ifdef HAVE_KEYLOG_CALLBACK if(Curl_tls_keylog_enabled()) { - SSL_CTX_set_keylog_callback(backend->ctx, ossl_keylog_callback); + SSL_CTX_set_keylog_callback(octx->ssl_ctx, ossl_keylog_callback); } #endif - /* Enable the session cache because it's a prerequisite for the "new session" - * callback. Use the "external storage" mode to prevent OpenSSL from creating - * an internal session cache. - */ - SSL_CTX_set_session_cache_mode(backend->ctx, - SSL_SESS_CACHE_CLIENT | - SSL_SESS_CACHE_NO_INTERNAL); - SSL_CTX_sess_set_new_cb(backend->ctx, ossl_new_session_cb); + if(cb_new_session) { + /* Enable the session cache because it's a prerequisite for the + * "new session" callback. Use the "external storage" mode to prevent + * OpenSSL from creating an internal session cache. + */ + SSL_CTX_set_session_cache_mode(octx->ssl_ctx, + SSL_SESS_CACHE_CLIENT | + SSL_SESS_CACHE_NO_INTERNAL); + SSL_CTX_sess_set_new_cb(octx->ssl_ctx, cb_new_session); + } /* give application a chance to interfere with SSL set up. */ if(data->set.ssl.fsslctx) { /* When a user callback is installed to modify the SSL_CTX, * we need to do the full initialization before calling it. * See: #11800 */ - if(!backend->x509_store_setup) { - result = Curl_ssl_setup_x509_store(cf, data, backend->ctx); + if(!octx->x509_store_setup) { + result = Curl_ssl_setup_x509_store(cf, data, octx->ssl_ctx); if(result) return result; - backend->x509_store_setup = TRUE; + octx->x509_store_setup = TRUE; } Curl_set_in_callback(data, true); - result = (*data->set.ssl.fsslctx)(data, backend->ctx, + result = (*data->set.ssl.fsslctx)(data, octx->ssl_ctx, data->set.ssl.fsslctxp); Curl_set_in_callback(data, false); if(result) { @@ -3806,47 +3817,174 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, } /* Let's make an SSL structure */ - if(backend->handle) - SSL_free(backend->handle); - backend->handle = SSL_new(backend->ctx); - if(!backend->handle) { + if(octx->ssl) + SSL_free(octx->ssl); + octx->ssl = SSL_new(octx->ssl_ctx); + if(!octx->ssl) { failf(data, "SSL: couldn't create a context (handle)"); return CURLE_OUT_OF_MEMORY; } - SSL_set_app_data(backend->handle, cf); + SSL_set_app_data(octx->ssl, ssl_user_data); #if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_TLSEXT) && \ !defined(OPENSSL_NO_OCSP) if(conn_config->verifystatus) - SSL_set_tlsext_status_type(backend->handle, TLSEXT_STATUSTYPE_ocsp); + SSL_set_tlsext_status_type(octx->ssl, TLSEXT_STATUSTYPE_ocsp); #endif #if (defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC)) && \ defined(ALLOW_RENEG) - SSL_set_renegotiate_mode(backend->handle, ssl_renegotiate_freely); + SSL_set_renegotiate_mode(octx->ssl, ssl_renegotiate_freely); #endif - SSL_set_connect_state(backend->handle); + SSL_set_connect_state(octx->ssl); - backend->server_cert = 0x0; + octx->server_cert = 0x0; #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME - if(connssl->peer.sni) { - if(!SSL_set_tlsext_host_name(backend->handle, connssl->peer.sni)) { + if(peer->sni) { + if(!SSL_set_tlsext_host_name(octx->ssl, peer->sni)) { failf(data, "Failed set SNI"); return CURLE_SSL_CONNECT_ERROR; } } -#endif - SSL_set_app_data(backend->handle, cf); +#ifdef USE_ECH + if(ECH_ENABLED(data)) { + unsigned char *ech_config = NULL; + size_t ech_config_len = 0; + char *outername = data->set.str[STRING_ECH_PUBLIC]; + int trying_ech_now = 0; + + if(data->set.tls_ech & CURLECH_GREASE) { + infof(data, "ECH: will GREASE ClientHello"); +# ifdef OPENSSL_IS_BORINGSSL + SSL_set_enable_ech_grease(octx->ssl, 1); +# else + SSL_set_options(octx->ssl, SSL_OP_ECH_GREASE); +# endif + } + else if(data->set.tls_ech & CURLECH_CLA_CFG) { +# ifdef OPENSSL_IS_BORINGSSL + /* have to do base64 decode here for boring */ + const char *b64 = data->set.str[STRING_ECH_CONFIG]; + + if(!b64) { + infof(data, "ECH: ECHConfig from command line empty"); + return CURLE_SSL_CONNECT_ERROR; + } + ech_config_len = 2 * strlen(b64); + result = Curl_base64_decode(b64, &ech_config, &ech_config_len); + if(result || !ech_config) { + infof(data, "ECH: can't base64 decode ECHConfig from command line"); + if(data->set.tls_ech & CURLECH_HARD) + return result; + } + if(SSL_set1_ech_config_list(octx->ssl, ech_config, + ech_config_len) != 1) { + infof(data, "ECH: SSL_ECH_set1_echconfig failed"); + if(data->set.tls_ech & CURLECH_HARD) { + free(ech_config); + return CURLE_SSL_CONNECT_ERROR; + } + } + free(ech_config); + trying_ech_now = 1; +# else + ech_config = (unsigned char *) data->set.str[STRING_ECH_CONFIG]; + if(!ech_config) { + infof(data, "ECH: ECHConfig from command line empty"); + return CURLE_SSL_CONNECT_ERROR; + } + ech_config_len = strlen(data->set.str[STRING_ECH_CONFIG]); + if(SSL_ech_set1_echconfig(octx->ssl, ech_config, ech_config_len) != 1) { + infof(data, "ECH: SSL_ECH_set1_echconfig failed"); + if(data->set.tls_ech & CURLECH_HARD) + return CURLE_SSL_CONNECT_ERROR; + } + else + trying_ech_now = 1; +# endif + infof(data, "ECH: ECHConfig from command line"); + } + else { + struct Curl_dns_entry *dns = NULL; + + dns = Curl_fetch_addr(data, connssl->peer.hostname, connssl->peer.port); + if(!dns) { + infof(data, "ECH: requested but no DNS info available"); + if(data->set.tls_ech & CURLECH_HARD) + return CURLE_SSL_CONNECT_ERROR; + } + else { + struct Curl_https_rrinfo *rinfo = NULL; + + rinfo = dns->hinfo; + if(rinfo && rinfo->echconfiglist) { + unsigned char *ecl = rinfo->echconfiglist; + size_t elen = rinfo->echconfiglist_len; + + infof(data, "ECH: ECHConfig from DoH HTTPS RR"); +# ifndef OPENSSL_IS_BORINGSSL + if(SSL_ech_set1_echconfig(octx->ssl, ecl, elen) != 1) { + infof(data, "ECH: SSL_ECH_set1_echconfig failed"); + if(data->set.tls_ech & CURLECH_HARD) + return CURLE_SSL_CONNECT_ERROR; + } +# else + if(SSL_set1_ech_config_list(octx->ssl, ecl, elen) != 1) { + infof(data, "ECH: SSL_set1_ech_config_list failed (boring)"); + if(data->set.tls_ech & CURLECH_HARD) + return CURLE_SSL_CONNECT_ERROR; + } +# endif + else { + trying_ech_now = 1; + infof(data, "ECH: imported ECHConfigList of length %ld", elen); + } + } + else { + infof(data, "ECH: requested but no ECHConfig available"); + if(data->set.tls_ech & CURLECH_HARD) + return CURLE_SSL_CONNECT_ERROR; + } + Curl_resolv_unlock(data, dns); + } + } +# ifdef OPENSSL_IS_BORINGSSL + if(trying_ech_now && outername) { + infof(data, "ECH: setting public_name not supported with boringssl"); + return CURLE_SSL_CONNECT_ERROR; + } +# else + if(trying_ech_now && outername) { + infof(data, "ECH: inner: '%s', outer: '%s'", + connssl->peer.hostname, outername); + result = SSL_ech_set_server_names(octx->ssl, + connssl->peer.hostname, outername, + 0 /* do send outer */); + if(result != 1) { + infof(data, "ECH: rv failed to set server name(s) %d [ERROR]", result); + return CURLE_SSL_CONNECT_ERROR; + } + } +# endif /* not BORING */ + if(trying_ech_now + && SSL_set_min_proto_version(octx->ssl, TLS1_3_VERSION) != 1) { + infof(data, "ECH: Can't force TLSv1.3 [ERROR]"); + return CURLE_SSL_CONNECT_ERROR; + } + } +#endif /* USE_ECH */ - connssl->reused_session = FALSE; - if(ssl_config->primary.sessionid) { +#endif + + octx->reused_session = FALSE; + if(ssl_config->primary.sessionid && transport == TRNSPRT_TCP) { Curl_ssl_sessionid_lock(data); - if(!Curl_ssl_getsessionid(cf, data, &ssl_sessionid, NULL)) { + if(!Curl_ssl_getsessionid(cf, data, peer, &ssl_sessionid, NULL)) { /* we got a session id, use it! */ - if(!SSL_set_session(backend->handle, ssl_sessionid)) { + if(!SSL_set_session(octx->ssl, ssl_sessionid)) { Curl_ssl_sessionid_unlock(data); failf(data, "SSL: SSL_set_session failed: %s", ossl_strerror(ERR_get_error(), error_buffer, @@ -3855,15 +3993,46 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, } /* Informational message */ infof(data, "SSL reusing session ID"); - connssl->reused_session = TRUE; + octx->reused_session = TRUE; } Curl_ssl_sessionid_unlock(data); } - backend->bio_method = ossl_bio_cf_method_create(); - if(!backend->bio_method) + return CURLE_OK; +} + +static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; + struct alpn_proto_buf proto; + BIO *bio; + CURLcode result; + + DEBUGASSERT(ssl_connect_1 == connssl->connecting_state); + DEBUGASSERT(octx); + memset(&proto, 0, sizeof(proto)); +#ifdef HAS_ALPN + if(connssl->alpn) { + result = Curl_alpn_to_proto_buf(&proto, connssl->alpn); + if(result) { + failf(data, "Error determining ALPN"); + return CURLE_SSL_CONNECT_ERROR; + } + } +#endif + + result = Curl_ossl_ctx_init(octx, cf, data, &connssl->peer, TRNSPRT_TCP, + proto.data, proto.len, NULL, NULL, + ossl_new_session_cb, cf); + if(result) + return result; + + octx->bio_method = ossl_bio_cf_method_create(); + if(!octx->bio_method) return CURLE_OUT_OF_MEMORY; - bio = BIO_new(backend->bio_method); + bio = BIO_new(octx->bio_method); if(!bio) return CURLE_OUT_OF_MEMORY; @@ -3875,40 +4044,109 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, * We check on the function in configure, since libressl and friends * each have their own versions to add support for this. */ BIO_up_ref(bio); - SSL_set0_rbio(backend->handle, bio); - SSL_set0_wbio(backend->handle, bio); + SSL_set0_rbio(octx->ssl, bio); + SSL_set0_wbio(octx->ssl, bio); #else - SSL_set_bio(backend->handle, bio, bio); + SSL_set_bio(octx->ssl, bio, bio); #endif - connssl->connecting_state = ssl_connect_2; +#ifdef HAS_ALPN + if(connssl->alpn) { + Curl_alpn_to_proto_str(&proto, connssl->alpn); + infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); + } +#endif + connssl->connecting_state = ssl_connect_2; return CURLE_OK; } +#ifdef USE_ECH +/* If we have retry configs, then trace those out */ +static void ossl_trace_ech_retry_configs(struct Curl_easy *data, SSL* ssl, + int reason) +{ + CURLcode result = CURLE_OK; + size_t rcl = 0; + int rv = 1; +# ifndef OPENSSL_IS_BORINGSSL + char *inner = NULL; + unsigned char *rcs = NULL; + char *outer = NULL; +# else + const char *inner = NULL; + const uint8_t *rcs = NULL; + const char *outer = NULL; + size_t out_name_len = 0; + int servername_type = 0; +# endif + + /* nothing to trace if not doing ECH */ + if(!ECH_ENABLED(data)) + return; +# ifndef OPENSSL_IS_BORINGSSL + rv = SSL_ech_get_retry_config(ssl, &rcs, &rcl); +# else + SSL_get0_ech_retry_configs(ssl, &rcs, &rcl); + rv = (int)rcl; +# endif + + if(rv && rcs) { +# define HEXSTR_MAX 800 + char *b64str = NULL; + size_t blen = 0; + + result = Curl_base64_encode((const char *)rcs, rcl, + &b64str, &blen); + if(!result && b64str) + infof(data, "ECH: retry_configs %s", b64str); + free(b64str); +# ifndef OPENSSL_IS_BORINGSSL + rv = SSL_ech_get_status(ssl, &inner, &outer); + infof(data, "ECH: retry_configs for %s from %s, %d %d", + inner ? inner : "NULL", outer ? outer : "NULL", reason, rv); +#else + rv = SSL_ech_accepted(ssl); + servername_type = SSL_get_servername_type(ssl); + inner = SSL_get_servername(ssl, servername_type); + SSL_get0_ech_name_override(ssl, &outer, &out_name_len); + /* TODO: get the inner from boring */ + infof(data, "ECH: retry_configs for %s from %s, %d %d", + inner ? inner : "NULL", outer ? outer : "NULL", reason, rv); +#endif + } + else + infof(data, "ECH: no retry_configs (rv = %d)", rv); +# ifndef OPENSSL_IS_BORINGSSL + OPENSSL_free((void *)rcs); +# endif + return; +} + +#endif + static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) { int err; struct ssl_connect_data *connssl = cf->ctx; - struct ossl_ssl_backend_data *backend = - (struct ossl_ssl_backend_data *)connssl->backend; + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); DEBUGASSERT(ssl_connect_2 == connssl->connecting_state || ssl_connect_2_reading == connssl->connecting_state || ssl_connect_2_writing == connssl->connecting_state); - DEBUGASSERT(backend); + DEBUGASSERT(octx); ERR_clear_error(); - err = SSL_connect(backend->handle); + err = SSL_connect(octx->ssl); - if(!backend->x509_store_setup) { + if(!octx->x509_store_setup) { /* After having send off the ClientHello, we prepare the x509 * store to verify the coming certificate from the server */ - CURLcode result = Curl_ssl_setup_x509_store(cf, data, backend->ctx); + CURLcode result = Curl_ssl_setup_x509_store(cf, data, octx->ssl_ctx); if(result) return result; - backend->x509_store_setup = TRUE; + octx->x509_store_setup = TRUE; } #ifndef HAVE_KEYLOG_CALLBACK @@ -3916,7 +4154,9 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, /* If key logging is enabled, wait for the handshake to complete and then * proceed with logging secrets (for TLS 1.2 or older). */ - ossl_log_tls12_secret(backend->handle, &backend->keylog_done); + bool done = FALSE; + ossl_log_tls12_secret(octx->ssl, &done); + octx->keylog_done = done; } #endif @@ -3924,7 +4164,7 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, 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(backend->handle, err); + int detail = SSL_get_error(octx->ssl, err); if(SSL_ERROR_WANT_READ == detail) { connssl->connecting_state = ssl_connect_2_reading; @@ -3946,7 +4186,7 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, return CURLE_OK; } #endif - if(backend->io_result == CURLE_AGAIN) { + if(octx->io_result == CURLE_AGAIN) { return CURLE_OK; } else { @@ -3974,7 +4214,7 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, (reason == SSL_R_SSLV3_ALERT_CERTIFICATE_EXPIRED))) { result = CURLE_PEER_FAILED_VERIFICATION; - lerr = SSL_get_verify_result(backend->handle); + lerr = SSL_get_verify_result(octx->ssl); if(lerr != X509_V_OK) { ssl_config->certverifyresult = lerr; msnprintf(error_buffer, sizeof(error_buffer), @@ -3997,6 +4237,21 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, ossl_strerror(errdetail, error_buffer, sizeof(error_buffer)); } #endif +#ifdef USE_ECH + else if((lib == ERR_LIB_SSL) && +# ifndef OPENSSL_IS_BORINGSSL + (reason == SSL_R_ECH_REQUIRED)) { +# else + (reason == SSL_R_ECH_REJECTED)) { +# endif + + /* trace retry_configs if we got some */ + ossl_trace_ech_retry_configs(data, octx->ssl, reason); + + result = CURLE_ECH_REQUIRED; + ossl_strerror(errdetail, error_buffer, sizeof(error_buffer)); + } +#endif else { result = CURLE_SSL_CONNECT_ERROR; ossl_strerror(errdetail, error_buffer, sizeof(error_buffer)); @@ -4016,7 +4271,7 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, Curl_strerror(sockerr, extramsg, sizeof(extramsg)); failf(data, OSSL_PACKAGE " SSL_connect: %s in connection to %s:%d ", extramsg[0] ? extramsg : SSL_ERROR_to_str(detail), - connssl->peer.hostname, connssl->port); + connssl->peer.hostname, connssl->peer.port); return result; } @@ -4034,22 +4289,84 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, connssl->connecting_state = ssl_connect_3; #if (OPENSSL_VERSION_NUMBER >= 0x30000000L) - SSL_get_peer_signature_type_nid(backend->handle, &psigtype_nid); + SSL_get_peer_signature_type_nid(octx->ssl, &psigtype_nid); #if (OPENSSL_VERSION_NUMBER >= 0x30200000L) - negotiated_group_name = SSL_get0_group_name(backend->handle); + negotiated_group_name = SSL_get0_group_name(octx->ssl); #else negotiated_group_name = - OBJ_nid2sn(SSL_get_negotiated_group(backend->handle) & 0x0000FFFF); + OBJ_nid2sn(SSL_get_negotiated_group(octx->ssl) & 0x0000FFFF); #endif #endif /* Informational message */ infof(data, "SSL connection using %s / %s / %s / %s", - SSL_get_version(backend->handle), - SSL_get_cipher(backend->handle), + SSL_get_version(octx->ssl), + SSL_get_cipher(octx->ssl), negotiated_group_name? negotiated_group_name : "[blank]", OBJ_nid2sn(psigtype_nid)); +#ifdef USE_ECH +# ifndef OPENSSL_IS_BORINGSSL + if(ECH_ENABLED(data)) { + char *inner = NULL, *outer = NULL; + const char *status = NULL; + int rv; + + rv = SSL_ech_get_status(octx->ssl, &inner, &outer); + switch(rv) { + case SSL_ECH_STATUS_SUCCESS: + status = "succeeded"; + break; + case SSL_ECH_STATUS_GREASE_ECH: + status = "sent GREASE, got retry-configs"; + break; + case SSL_ECH_STATUS_GREASE: + status = "sent GREASE"; + break; + case SSL_ECH_STATUS_NOT_TRIED: + status = "not attempted"; + break; + case SSL_ECH_STATUS_NOT_CONFIGURED: + status = "not configured"; + break; + case SSL_ECH_STATUS_BACKEND: + status = "backend (unexpected)"; + break; + case SSL_ECH_STATUS_FAILED: + status = "failed"; + break; + case SSL_ECH_STATUS_BAD_CALL: + status = "bad call (unexpected)"; + break; + case SSL_ECH_STATUS_BAD_NAME: + status = "bad name (unexpected)"; + break; + default: + status = "unexpected status"; + infof(data, "ECH: unexpected status %d",rv); + } + infof(data, "ECH: result: status is %s, inner is %s, outer is %s", + (status?status:"NULL"), + (inner?inner:"NULL"), + (outer?outer:"NULL")); + OPENSSL_free(inner); + OPENSSL_free(outer); + if(rv == SSL_ECH_STATUS_GREASE_ECH) { + /* trace retry_configs if we got some */ + ossl_trace_ech_retry_configs(data, octx->ssl, 0); + } + if(rv != SSL_ECH_STATUS_SUCCESS + && data->set.tls_ech & CURLECH_HARD) { + infof(data, "ECH: ech-hard failed"); + return CURLE_SSL_CONNECT_ERROR; + } + } + else { + infof(data, "ECH: result: status is not attempted"); + } +# endif /* BORING */ +#endif /* USE_ECH */ + #ifdef HAS_ALPN /* Sets data and len to negotiated protocol, len is 0 if no protocol was * negotiated @@ -4057,7 +4374,7 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, if(connssl->alpn) { const unsigned char *neg_protocol; unsigned int len; - SSL_get0_alpn_selected(backend->handle, &neg_protocol, &len); + SSL_get0_alpn_selected(octx->ssl, &neg_protocol, &len); return Curl_alpn_set_negotiated(cf, data, neg_protocol, len); } @@ -4194,20 +4511,12 @@ static void infof_certstack(struct Curl_easy *data, const SSL *ssl) #define infof_certstack(data, ssl) #endif -/* - * Get the server cert, verify it and show it, etc., only call failf() if the - * 'strict' argument is TRUE as otherwise all this is for informational - * purposes only! - * - * We check certificates to authenticate the server; otherwise we risk - * man-in-the-middle attack. - */ -static CURLcode servercert(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool strict) +CURLcode Curl_oss_check_peer_cert(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ossl_ctx *octx, + struct ssl_peer *peer) { struct connectdata *conn = cf->conn; - struct ssl_connect_data *connssl = cf->ctx; struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); CURLcode result = CURLE_OK; @@ -4219,10 +4528,9 @@ static CURLcode servercert(struct Curl_cfilter *cf, char buffer[2048]; const char *ptr; BIO *mem = BIO_new(BIO_s_mem()); - struct ossl_ssl_backend_data *backend = - (struct ossl_ssl_backend_data *)connssl->backend; + bool strict = (conn_config->verifypeer || conn_config->verifyhost); - DEBUGASSERT(backend); + DEBUGASSERT(octx); if(!mem) { failf(data, @@ -4235,10 +4543,10 @@ static CURLcode servercert(struct Curl_cfilter *cf, if(data->set.ssl.certinfo) /* asked to gather certificate info */ - (void)Curl_ossl_certchain(data, backend->handle); + (void)Curl_ossl_certchain(data, octx->ssl); - backend->server_cert = SSL_get1_peer_certificate(backend->handle); - if(!backend->server_cert) { + octx->server_cert = SSL_get1_peer_certificate(octx->ssl); + if(!octx->server_cert) { BIO_free(mem); if(!strict) return CURLE_OK; @@ -4250,19 +4558,19 @@ static CURLcode servercert(struct Curl_cfilter *cf, infof(data, "%s certificate:", Curl_ssl_cf_is_proxy(cf)? "Proxy" : "Server"); - rc = x509_name_oneline(X509_get_subject_name(backend->server_cert), + rc = x509_name_oneline(X509_get_subject_name(octx->server_cert), buffer, sizeof(buffer)); infof(data, " subject: %s", rc?"[NONE]":buffer); #ifndef CURL_DISABLE_VERBOSE_STRINGS { long len; - ASN1_TIME_print(mem, X509_get0_notBefore(backend->server_cert)); + ASN1_TIME_print(mem, X509_get0_notBefore(octx->server_cert)); len = BIO_get_mem_data(mem, (char **) &ptr); infof(data, " start date: %.*s", (int)len, ptr); (void)BIO_reset(mem); - ASN1_TIME_print(mem, X509_get0_notAfter(backend->server_cert)); + ASN1_TIME_print(mem, X509_get0_notAfter(octx->server_cert)); len = BIO_get_mem_data(mem, (char **) &ptr); infof(data, " expire date: %.*s", (int)len, ptr); (void)BIO_reset(mem); @@ -4272,16 +4580,15 @@ static CURLcode servercert(struct Curl_cfilter *cf, BIO_free(mem); if(conn_config->verifyhost) { - result = Curl_ossl_verifyhost(data, conn, &connssl->peer, - backend->server_cert); + result = Curl_ossl_verifyhost(data, conn, peer, octx->server_cert); if(result) { - X509_free(backend->server_cert); - backend->server_cert = NULL; + X509_free(octx->server_cert); + octx->server_cert = NULL; return result; } } - rc = x509_name_oneline(X509_get_issuer_name(backend->server_cert), + rc = x509_name_oneline(X509_get_issuer_name(octx->server_cert), buffer, sizeof(buffer)); if(rc) { if(strict) @@ -4305,8 +4612,8 @@ static CURLcode servercert(struct Curl_cfilter *cf, " error %s", ossl_strerror(ERR_get_error(), error_buffer, sizeof(error_buffer)) ); - X509_free(backend->server_cert); - backend->server_cert = NULL; + X509_free(octx->server_cert); + octx->server_cert = NULL; return CURLE_OUT_OF_MEMORY; } } @@ -4318,8 +4625,8 @@ static CURLcode servercert(struct Curl_cfilter *cf, " error %s", ossl_strerror(ERR_get_error(), error_buffer, sizeof(error_buffer)) ); - X509_free(backend->server_cert); - backend->server_cert = NULL; + X509_free(octx->server_cert); + octx->server_cert = NULL; return CURLE_OUT_OF_MEMORY; } @@ -4328,8 +4635,8 @@ static CURLcode servercert(struct Curl_cfilter *cf, failf(data, "SSL: Unable to open issuer cert (%s)", conn_config->issuercert); BIO_free(fp); - X509_free(backend->server_cert); - backend->server_cert = NULL; + X509_free(octx->server_cert); + octx->server_cert = NULL; return CURLE_SSL_ISSUER_ERROR; } } @@ -4341,19 +4648,19 @@ static CURLcode servercert(struct Curl_cfilter *cf, conn_config->issuercert); BIO_free(fp); X509_free(issuer); - X509_free(backend->server_cert); - backend->server_cert = NULL; + X509_free(octx->server_cert); + octx->server_cert = NULL; return CURLE_SSL_ISSUER_ERROR; } - if(X509_check_issued(issuer, backend->server_cert) != X509_V_OK) { + if(X509_check_issued(issuer, octx->server_cert) != X509_V_OK) { if(strict) failf(data, "SSL: Certificate issuer check failed (%s)", conn_config->issuercert); BIO_free(fp); X509_free(issuer); - X509_free(backend->server_cert); - backend->server_cert = NULL; + X509_free(octx->server_cert); + octx->server_cert = NULL; return CURLE_SSL_ISSUER_ERROR; } @@ -4363,7 +4670,7 @@ static CURLcode servercert(struct Curl_cfilter *cf, X509_free(issuer); } - lerr = SSL_get_verify_result(backend->handle); + lerr = SSL_get_verify_result(octx->ssl); ssl_config->certverifyresult = lerr; if(lerr != X509_V_OK) { if(conn_config->verifypeer) { @@ -4383,11 +4690,11 @@ static CURLcode servercert(struct Curl_cfilter *cf, infof(data, " SSL certificate verify ok."); } - infof_certstack(data, backend->handle); + infof_certstack(data, octx->ssl); #if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_TLSEXT) && \ !defined(OPENSSL_NO_OCSP) - if(conn_config->verifystatus && !connssl->reused_session) { + if(conn_config->verifystatus && !octx->reused_session) { /* don't do this after Session ID reuse */ result = verifystatus(cf, data); if(result) { @@ -4397,7 +4704,8 @@ static CURLcode servercert(struct Curl_cfilter *cf, void *old_ssl_sessionid = NULL; bool incache; Curl_ssl_sessionid_lock(data); - incache = !(Curl_ssl_getsessionid(cf, data, &old_ssl_sessionid, NULL)); + incache = !(Curl_ssl_getsessionid(cf, data, peer, + &old_ssl_sessionid, NULL)); if(incache) { infof(data, "Remove session ID again from cache"); Curl_ssl_delsessionid(data, old_ssl_sessionid); @@ -4405,8 +4713,8 @@ static CURLcode servercert(struct Curl_cfilter *cf, Curl_ssl_sessionid_unlock(data); } - X509_free(backend->server_cert); - backend->server_cert = NULL; + X509_free(octx->server_cert); + octx->server_cert = NULL; return result; } } @@ -4416,18 +4724,21 @@ static CURLcode servercert(struct Curl_cfilter *cf, /* when not strict, we don't bother about the verify cert problems */ result = CURLE_OK; +#ifndef CURL_DISABLE_PROXY ptr = Curl_ssl_cf_is_proxy(cf)? data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY]: data->set.str[STRING_SSL_PINNEDPUBLICKEY]; +#else + ptr = data->set.str[STRING_SSL_PINNEDPUBLICKEY]; +#endif if(!result && ptr) { - result = ossl_pkp_pin_peer_pubkey(data, backend->server_cert, ptr); + result = ossl_pkp_pin_peer_pubkey(data, octx->server_cert, ptr); if(result) failf(data, "SSL: public key does not match pinned public key"); } - X509_free(backend->server_cert); - backend->server_cert = NULL; - connssl->connecting_state = ssl_connect_done; + X509_free(octx->server_cert); + octx->server_cert = NULL; return result; } @@ -4437,7 +4748,7 @@ static CURLcode ossl_connect_step3(struct Curl_cfilter *cf, { CURLcode result = CURLE_OK; struct ssl_connect_data *connssl = cf->ctx; - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; DEBUGASSERT(ssl_connect_3 == connssl->connecting_state); @@ -4448,9 +4759,7 @@ static CURLcode ossl_connect_step3(struct Curl_cfilter *cf, * operations. */ - result = servercert(cf, data, conn_config->verifypeer || - conn_config->verifyhost); - + result = Curl_oss_check_peer_cert(cf, data, octx, &connssl->peer); if(!result) connssl->connecting_state = ssl_connect_done; @@ -4590,12 +4899,11 @@ static bool ossl_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data) { struct ssl_connect_data *connssl = cf->ctx; - struct ossl_ssl_backend_data *backend = - (struct ossl_ssl_backend_data *)connssl->backend; + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; (void)data; - DEBUGASSERT(connssl && backend); - if(backend->handle && SSL_pending(backend->handle)) + DEBUGASSERT(connssl && octx); + if(octx->ssl && SSL_pending(octx->ssl)) return TRUE; return FALSE; } @@ -4614,19 +4922,18 @@ static ssize_t ossl_send(struct Curl_cfilter *cf, int memlen; int rc; struct ssl_connect_data *connssl = cf->ctx; - struct ossl_ssl_backend_data *backend = - (struct ossl_ssl_backend_data *)connssl->backend; + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; (void)data; - DEBUGASSERT(backend); + DEBUGASSERT(octx); ERR_clear_error(); memlen = (len > (size_t)INT_MAX) ? INT_MAX : (int)len; - rc = SSL_write(backend->handle, mem, memlen); + rc = SSL_write(octx->ssl, mem, memlen); if(rc <= 0) { - err = SSL_get_error(backend->handle, rc); + err = SSL_get_error(octx->ssl, rc); switch(err) { case SSL_ERROR_WANT_READ: @@ -4641,7 +4948,7 @@ static ssize_t ossl_send(struct Curl_cfilter *cf, { int sockerr = SOCKERRNO; - if(backend->io_result == CURLE_AGAIN) { + if(octx->io_result == CURLE_AGAIN) { *curlcode = CURLE_AGAIN; rc = -1; goto out; @@ -4698,20 +5005,19 @@ static ssize_t ossl_recv(struct Curl_cfilter *cf, int buffsize; struct connectdata *conn = cf->conn; struct ssl_connect_data *connssl = cf->ctx; - struct ossl_ssl_backend_data *backend = - (struct ossl_ssl_backend_data *)connssl->backend; + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; (void)data; - DEBUGASSERT(backend); + DEBUGASSERT(octx); ERR_clear_error(); buffsize = (buffersize > (size_t)INT_MAX) ? INT_MAX : (int)buffersize; - nread = (ssize_t)SSL_read(backend->handle, buf, buffsize); + nread = (ssize_t)SSL_read(octx->ssl, buf, buffsize); if(nread <= 0) { /* failed SSL_read */ - int err = SSL_get_error(backend->handle, (int)nread); + int err = SSL_get_error(octx->ssl, (int)nread); switch(err) { case SSL_ERROR_NONE: /* this is not an error */ @@ -4733,7 +5039,7 @@ static ssize_t ossl_recv(struct Curl_cfilter *cf, /* openssl/ssl.h for SSL_ERROR_SYSCALL says "look at error stack/return value/errno" */ /* https://www.openssl.org/docs/crypto/ERR_get_error.html */ - if(backend->io_result == CURLE_AGAIN) { + if(octx->io_result == CURLE_AGAIN) { *curlcode = CURLE_AGAIN; nread = -1; goto out; @@ -4924,11 +5230,10 @@ static void *ossl_get_internals(struct ssl_connect_data *connssl, CURLINFO info) { /* Legacy: CURLINFO_TLS_SESSION must return an SSL_CTX pointer. */ - struct ossl_ssl_backend_data *backend = - (struct ossl_ssl_backend_data *)connssl->backend; - DEBUGASSERT(backend); + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; + DEBUGASSERT(octx); return info == CURLINFO_TLS_SESSION ? - (void *)backend->ctx : (void *)backend->handle; + (void *)octx->ssl_ctx : (void *)octx->ssl; } static void ossl_free_multi_ssl_backend_data( @@ -4956,9 +5261,12 @@ const struct Curl_ssl Curl_ssl_openssl = { #ifdef HAVE_SSL_CTX_SET_CIPHERSUITES SSLSUPP_TLS13_CIPHERSUITES | #endif +#ifdef USE_ECH + SSLSUPP_ECH | +#endif SSLSUPP_HTTPS_PROXY, - sizeof(struct ossl_ssl_backend_data), + sizeof(struct ossl_ctx), ossl_init, /* init */ ossl_cleanup, /* cleanup */ @@ -4974,7 +5282,6 @@ const struct Curl_ssl Curl_ssl_openssl = { ossl_get_internals, /* get_internals */ ossl_close, /* close_one */ ossl_close_all, /* close_all */ - ossl_session_free, /* session_free */ ossl_set_engine, /* set_engine */ ossl_set_engine_default, /* set_engine_default */ ossl_engines_list, /* engines_list */ diff --git a/lib/vtls/openssl.h b/lib/vtls/openssl.h index e802363..55e06bd 100644 --- a/lib/vtls/openssl.h +++ b/lib/vtls/openssl.h @@ -36,6 +36,39 @@ #include "urldata.h" +/* Struct to hold a Curl OpenSSL instance */ +struct ossl_ctx { + /* these ones requires specific SSL-types */ + SSL_CTX* ssl_ctx; + SSL* ssl; + X509* server_cert; + BIO_METHOD *bio_method; + CURLcode io_result; /* result of last BIO cfilter operation */ +#ifndef HAVE_KEYLOG_CALLBACK + /* Set to true once a valid keylog entry has been created to avoid dupes. */ + BIT(keylog_done); +#endif + BIT(x509_store_setup); /* x509 store has been set up */ + BIT(reused_session); /* session-ID was reused for this */ +}; + +typedef CURLcode Curl_ossl_ctx_setup_cb(struct Curl_cfilter *cf, + struct Curl_easy *data, + void *user_data); + +typedef int Curl_ossl_new_session_cb(SSL *ssl, SSL_SESSION *ssl_sessionid); + +CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + int transport, /* TCP or QUIC */ + const unsigned char *alpn, size_t alpn_len, + Curl_ossl_ctx_setup_cb *cb_setup, + void *cb_user_data, + Curl_ossl_new_session_cb *cb_new_session, + void *ssl_user_data); + #if (OPENSSL_VERSION_NUMBER < 0x30000000L) #define SSL_get1_peer_certificate SSL_get_peer_certificate #endif @@ -66,5 +99,23 @@ CURLcode Curl_ossl_ctx_configure(struct Curl_cfilter *cf, struct Curl_easy *data, SSL_CTX *ssl_ctx); +/* + * Add a new session to the cache. Takes ownership of the session. + */ +CURLcode Curl_ossl_add_session(struct Curl_cfilter *cf, + struct Curl_easy *data, + const struct ssl_peer *peer, + SSL_SESSION *ssl_sessionid); + +/* + * Get the server cert, verify it and show it, etc., only call failf() if + * ssl config verifypeer or -host is set. Otherwise all this is for + * informational purposes only! + */ +CURLcode Curl_oss_check_peer_cert(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ossl_ctx *octx, + struct ssl_peer *peer); + #endif /* USE_OPENSSL */ #endif /* HEADER_CURL_SSLUSE_H */ diff --git a/lib/vtls/rustls.c b/lib/vtls/rustls.c index ceb26c5..8b6588a 100644 --- a/lib/vtls/rustls.c +++ b/lib/vtls/rustls.c @@ -46,7 +46,8 @@ struct rustls_ssl_backend_data { const struct rustls_client_config *config; struct rustls_connection *conn; - bool data_pending; + size_t plain_out_buffered; + BIT(data_in_pending); }; /* For a given rustls_result error code, return the best-matching CURLcode. */ @@ -61,7 +62,7 @@ static CURLcode map_error(rustls_result r) case RUSTLS_RESULT_NULL_PARAMETER: return CURLE_BAD_FUNCTION_ARGUMENT; default: - return CURLE_READ_ERROR; + return CURLE_RECV_ERROR; } } @@ -74,7 +75,7 @@ cr_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data) (void)data; DEBUGASSERT(ctx && ctx->backend); backend = (struct rustls_ssl_backend_data *)ctx->backend; - return backend->data_pending; + return backend->data_in_pending; } struct io_ctx { @@ -101,6 +102,8 @@ read_cb(void *userdata, uint8_t *buf, uintptr_t len, uintptr_t *out_n) else if(nread == 0) connssl->peer_closed = TRUE; *out_n = (int)nread; + CURL_TRC_CF(io_ctx->data, io_ctx->cf, "cf->next recv(len=%zu) -> %zd, %d", + len, nread, result); return ret; } @@ -120,10 +123,8 @@ write_cb(void *userdata, const uint8_t *buf, uintptr_t len, uintptr_t *out_n) ret = EINVAL; } *out_n = (int)nwritten; - /* - CURL_TRC_CFX(io_ctx->data, io_ctx->cf, "cf->next send(len=%zu) -> %zd, %d", - len, nwritten, result)); - */ + CURL_TRC_CF(io_ctx->data, io_ctx->cf, "cf->next send(len=%zu) -> %zd, %d", + len, nwritten, result); return ret; } @@ -150,7 +151,7 @@ static ssize_t tls_recv_more(struct Curl_cfilter *cf, char buffer[STRERROR_LEN]; failf(data, "reading from socket: %s", Curl_strerror(io_error, buffer, sizeof(buffer))); - *err = CURLE_READ_ERROR; + *err = CURLE_RECV_ERROR; return -1; } @@ -165,7 +166,7 @@ static ssize_t tls_recv_more(struct Curl_cfilter *cf, return -1; } - backend->data_pending = TRUE; + backend->data_in_pending = TRUE; *err = CURLE_OK; return (ssize_t)tls_bytes_read; } @@ -200,7 +201,7 @@ cr_recv(struct Curl_cfilter *cf, struct Curl_easy *data, rconn = backend->conn; while(plain_bytes_copied < plainlen) { - if(!backend->data_pending) { + if(!backend->data_in_pending) { if(tls_recv_more(cf, data, err) < 0) { if(*err != CURLE_AGAIN) { nread = -1; @@ -215,12 +216,12 @@ cr_recv(struct Curl_cfilter *cf, struct Curl_easy *data, plainlen - plain_bytes_copied, &n); if(rresult == RUSTLS_RESULT_PLAINTEXT_EMPTY) { - backend->data_pending = FALSE; + backend->data_in_pending = FALSE; } else if(rresult == RUSTLS_RESULT_UNEXPECTED_EOF) { failf(data, "rustls: peer closed TCP connection " "without first closing TLS connection"); - *err = CURLE_READ_ERROR; + *err = CURLE_RECV_ERROR; nread = -1; goto out; } @@ -230,7 +231,7 @@ cr_recv(struct Curl_cfilter *cf, struct Curl_easy *data, size_t errorlen; rustls_error(rresult, errorbuf, sizeof(errorbuf), &errorlen); failf(data, "rustls_connection_read: %.*s", (int)errorlen, errorbuf); - *err = CURLE_READ_ERROR; + *err = CURLE_RECV_ERROR; nread = -1; goto out; } @@ -265,6 +266,42 @@ out: return nread; } +static CURLcode cr_flush_out(struct Curl_cfilter *cf, struct Curl_easy *data, + struct rustls_connection *rconn) +{ + struct io_ctx io_ctx; + rustls_io_result io_error; + size_t tlswritten = 0; + size_t tlswritten_total = 0; + CURLcode result = CURLE_OK; + + io_ctx.cf = cf; + io_ctx.data = data; + + while(rustls_connection_wants_write(rconn)) { + io_error = rustls_connection_write_tls(rconn, write_cb, &io_ctx, + &tlswritten); + if(io_error == EAGAIN || io_error == EWOULDBLOCK) { + CURL_TRC_CF(data, cf, "cf_send: EAGAIN after %zu bytes", + tlswritten_total); + return CURLE_AGAIN; + } + else if(io_error) { + char buffer[STRERROR_LEN]; + failf(data, "writing to socket: %s", + Curl_strerror(io_error, buffer, sizeof(buffer))); + return CURLE_SEND_ERROR; + } + if(tlswritten == 0) { + failf(data, "EOF in swrite"); + return CURLE_SEND_ERROR; + } + CURL_TRC_CF(data, cf, "cf_send: wrote %zu TLS bytes", tlswritten); + tlswritten_total += tlswritten; + } + return result; +} + /* * On each call: * - Copy `plainlen` bytes into rustls' plaintext input buffer (if > 0). @@ -283,26 +320,43 @@ cr_send(struct Curl_cfilter *cf, struct Curl_easy *data, struct rustls_ssl_backend_data *const backend = (struct rustls_ssl_backend_data *)connssl->backend; struct rustls_connection *rconn = NULL; - struct io_ctx io_ctx; size_t plainwritten = 0; - size_t tlswritten = 0; - size_t tlswritten_total = 0; rustls_result rresult; - rustls_io_result io_error; char errorbuf[256]; size_t errorlen; + const unsigned char *buf = plainbuf; + size_t blen = plainlen; + ssize_t nwritten = 0; DEBUGASSERT(backend); rconn = backend->conn; + DEBUGASSERT(rconn); + + CURL_TRC_CF(data, cf, "cf_send(len=%zu)", plainlen); + + /* If a previous send blocked, we already added its plain bytes + * to rustsls and must not do that again. Flush the TLS bytes and, + * if successful, deduct the previous plain bytes from the current + * send. */ + if(backend->plain_out_buffered) { + *err = cr_flush_out(cf, data, rconn); + CURL_TRC_CF(data, cf, "cf_send: flushing %zu previously added bytes -> %d", + backend->plain_out_buffered, *err); + if(*err) + return -1; + if(blen > backend->plain_out_buffered) { + blen -= backend->plain_out_buffered; + buf += backend->plain_out_buffered; + } + else + blen = 0; + nwritten += (ssize_t)backend->plain_out_buffered; + backend->plain_out_buffered = 0; + } - CURL_TRC_CF(data, cf, "cf_send: %zu plain bytes", plainlen); - - io_ctx.cf = cf; - io_ctx.data = data; - - if(plainlen > 0) { - rresult = rustls_connection_write(rconn, plainbuf, plainlen, - &plainwritten); + if(blen > 0) { + CURL_TRC_CF(data, cf, "cf_send: adding %zu plain bytes to rustls", blen); + rresult = rustls_connection_write(rconn, buf, blen, &plainwritten); if(rresult != RUSTLS_RESULT_OK) { rustls_error(rresult, errorbuf, sizeof(errorbuf), &errorlen); failf(data, "rustls_connection_write: %.*s", (int)errorlen, errorbuf); @@ -316,32 +370,27 @@ cr_send(struct Curl_cfilter *cf, struct Curl_easy *data, } } - while(rustls_connection_wants_write(rconn)) { - io_error = rustls_connection_write_tls(rconn, write_cb, &io_ctx, - &tlswritten); - if(io_error == EAGAIN || io_error == EWOULDBLOCK) { - CURL_TRC_CF(data, cf, "cf_send: EAGAIN after %zu bytes", - tlswritten_total); - *err = CURLE_AGAIN; - return -1; - } - else if(io_error) { - char buffer[STRERROR_LEN]; - failf(data, "writing to socket: %s", - Curl_strerror(io_error, buffer, sizeof(buffer))); - *err = CURLE_WRITE_ERROR; - return -1; - } - if(tlswritten == 0) { - failf(data, "EOF in swrite"); - *err = CURLE_WRITE_ERROR; - return -1; + *err = cr_flush_out(cf, data, rconn); + if(*err) { + if(CURLE_AGAIN == *err) { + /* The TLS bytes may have been partially written, but we fail the + * complete send() and remember how much we already added to rustls. */ + CURL_TRC_CF(data, cf, "cf_send: EAGAIN, remember we added %zu plain" + " bytes already to rustls", blen); + backend->plain_out_buffered = plainwritten; + if(nwritten) { + *err = CURLE_OK; + return (ssize_t)nwritten; + } } - CURL_TRC_CF(data, cf, "cf_send: wrote %zu TLS bytes", tlswritten); - tlswritten_total += tlswritten; + return -1; } + else + nwritten += (ssize_t)plainwritten; - return plainwritten; + CURL_TRC_CF(data, cf, "cf_send(len=%zu) -> %d, %zd", + plainlen, *err, nwritten); + return nwritten; } /* A server certificate verify callback for rustls that always returns @@ -357,12 +406,12 @@ static bool cr_hostname_is_ip(const char *hostname) { struct in_addr in; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 struct in6_addr in6; if(Curl_inet_pton(AF_INET6, hostname, &in6) > 0) { return true; } -#endif /* ENABLE_IPV6 */ +#endif /* USE_IPV6 */ if(Curl_inet_pton(AF_INET, hostname, &in) > 0) { return true; } @@ -479,18 +528,14 @@ cr_init_backend(struct Curl_cfilter *cf, struct Curl_easy *data, backend->config = rustls_client_config_builder_build(config_builder); DEBUGASSERT(rconn == NULL); - { - /* rustls claims to manage ip address hostnames as well here. So, - * if we have an SNI, we use it, otherwise we pass the hostname */ - char *server = connssl->peer.sni? - connssl->peer.sni : connssl->peer.hostname; - result = rustls_client_connection_new(backend->config, server, &rconn); - } + result = rustls_client_connection_new(backend->config, + connssl->peer.hostname, &rconn); if(result != RUSTLS_RESULT_OK) { rustls_error(result, errorbuf, sizeof(errorbuf), &errorlen); failf(data, "rustls_client_connection_new: %.*s", (int)errorlen, errorbuf); return CURLE_COULDNT_CONNECT; } + DEBUGASSERT(rconn); rustls_connection_set_userdata(rconn, backend); backend->conn = rconn; return CURLE_OK; @@ -539,9 +584,12 @@ cr_connect_common(struct Curl_cfilter *cf, DEBUGASSERT(backend); - if(ssl_connection_none == connssl->state) { + CURL_TRC_CF(data, cf, "cr_connect_common, state=%d", connssl->state); + *done = FALSE; + if(!backend->conn) { result = cr_init_backend(cf, data, (struct rustls_ssl_backend_data *)connssl->backend); + CURL_TRC_CF(data, cf, "cr_connect_common, init backend -> %d", result); if(result != CURLE_OK) { return result; } @@ -558,21 +606,34 @@ cr_connect_common(struct Curl_cfilter *cf, */ if(!rustls_connection_is_handshaking(rconn)) { infof(data, "Done handshaking"); - /* Done with the handshake. Set up callbacks to send/receive data. */ - connssl->state = ssl_connection_complete; - + /* rustls claims it is no longer handshaking *before* it has + * send its FINISHED message off. We attempt to let it write + * one more time. Oh my. + */ cr_set_negotiated_alpn(cf, data, rconn); - + cr_send(cf, data, NULL, 0, &tmperr); + if(tmperr == CURLE_AGAIN) { + connssl->connecting_state = ssl_connect_2_writing; + return CURLE_OK; + } + else if(tmperr != CURLE_OK) { + return tmperr; + } + /* REALLY Done with the handshake. */ + connssl->state = ssl_connection_complete; *done = TRUE; return CURLE_OK; } wants_read = rustls_connection_wants_read(rconn); - wants_write = rustls_connection_wants_write(rconn); + wants_write = rustls_connection_wants_write(rconn) || + backend->plain_out_buffered; DEBUGASSERT(wants_read || wants_write); writefd = wants_write?sockfd:CURL_SOCKET_BAD; readfd = wants_read?sockfd:CURL_SOCKET_BAD; + connssl->connecting_state = wants_write? + ssl_connect_2_writing : ssl_connect_2_reading; /* check allowed time left */ timeout_ms = Curl_timeleft(data, NULL, TRUE); @@ -584,8 +645,8 @@ cr_connect_common(struct Curl_cfilter *cf, socket_check_timeout = blocking?timeout_ms:0; - what = Curl_socket_check( - readfd, CURL_SOCKET_BAD, writefd, socket_check_timeout); + what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd, + socket_check_timeout); if(what < 0) { /* fatal error */ failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); @@ -597,19 +658,18 @@ cr_connect_common(struct Curl_cfilter *cf, return CURLE_OPERATION_TIMEDOUT; } if(0 == what) { - infof(data, "Curl_socket_check: %s would block", + CURL_TRC_CF(data, cf, "Curl_socket_check: %s would block", wants_read&&wants_write ? "writing and reading" : wants_write ? "writing" : "reading"); - *done = FALSE; return CURLE_OK; } /* socket is readable or writable */ if(wants_write) { - infof(data, "rustls_connection wants us to write_tls."); + CURL_TRC_CF(data, cf, "rustls_connection wants us to write_tls."); cr_send(cf, data, NULL, 0, &tmperr); if(tmperr == CURLE_AGAIN) { - infof(data, "writing would block"); + CURL_TRC_CF(data, cf, "writing would block"); /* fall through */ } else if(tmperr != CURLE_OK) { @@ -618,14 +678,13 @@ cr_connect_common(struct Curl_cfilter *cf, } if(wants_read) { - infof(data, "rustls_connection wants us to read_tls."); - + CURL_TRC_CF(data, cf, "rustls_connection wants us to read_tls."); if(tls_recv_more(cf, data, &tmperr) < 0) { if(tmperr == CURLE_AGAIN) { - infof(data, "reading would block"); + CURL_TRC_CF(data, cf, "reading would block"); /* fall through */ } - else if(tmperr == CURLE_READ_ERROR) { + else if(tmperr == CURLE_RECV_ERROR) { return CURLE_SSL_CONNECT_ERROR; } else { @@ -648,37 +707,12 @@ cr_connect_nonblocking(struct Curl_cfilter *cf, } static CURLcode -cr_connect_blocking(struct Curl_cfilter *cf UNUSED_PARAM, - struct Curl_easy *data UNUSED_PARAM) +cr_connect_blocking(struct Curl_cfilter *cf, struct Curl_easy *data) { bool done; /* unused */ return cr_connect_common(cf, data, true, &done); } -static void cr_adjust_pollset(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct easy_pollset *ps) -{ - if(!cf->connected) { - curl_socket_t sock = Curl_conn_cf_get_socket(cf->next, data); - struct ssl_connect_data *const connssl = cf->ctx; - struct rustls_ssl_backend_data *const backend = - (struct rustls_ssl_backend_data *)connssl->backend; - struct rustls_connection *rconn = NULL; - - (void)data; - DEBUGASSERT(backend); - rconn = backend->conn; - - if(rustls_connection_wants_write(rconn)) { - Curl_pollset_add_out(data, ps, sock); - } - if(rustls_connection_wants_read(rconn)) { - Curl_pollset_add_in(data, ps, sock); - } - } -} - static void * cr_get_internals(struct ssl_connect_data *connssl, CURLINFO info UNUSED_PARAM) @@ -699,8 +733,8 @@ cr_close(struct Curl_cfilter *cf, struct Curl_easy *data) 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) { @@ -725,7 +759,6 @@ static size_t cr_version(char *buffer, size_t size) const struct Curl_ssl Curl_ssl_rustls = { { CURLSSLBACKEND_RUSTLS, "rustls" }, SSLSUPP_CAINFO_BLOB | /* supports */ - SSLSUPP_TLS13_CIPHERSUITES | SSLSUPP_HTTPS_PROXY, sizeof(struct rustls_ssl_backend_data), @@ -739,11 +772,10 @@ const struct Curl_ssl Curl_ssl_rustls = { Curl_none_cert_status_request, /* cert_status_request */ cr_connect_blocking, /* connect */ cr_connect_nonblocking, /* connect_nonblocking */ - cr_adjust_pollset, /* adjust_pollset */ + Curl_ssl_adjust_pollset, /* adjust_pollset */ cr_get_internals, /* get_internals */ cr_close, /* close_one */ Curl_none_close_all, /* close_all */ - Curl_none_session_free, /* session_free */ Curl_none_set_engine, /* set_engine */ Curl_none_set_engine_default, /* set_engine_default */ Curl_none_engines_list, /* engines_list */ diff --git a/lib/vtls/schannel.c b/lib/vtls/schannel.c index 8736b9e..19cdc4b 100644 --- a/lib/vtls/schannel.c +++ b/lib/vtls/schannel.c @@ -1071,7 +1071,7 @@ schannel_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) DEBUGASSERT(backend); DEBUGF(infof(data, "schannel: SSL/TLS connection with %s port %d (step 1/3)", - connssl->peer.hostname, connssl->port)); + connssl->peer.hostname, connssl->peer.port)); if(curlx_verify_windows_version(5, 1, 0, PLATFORM_WINNT, VERSION_LESS_THAN_EQUAL)) { @@ -1129,7 +1129,8 @@ schannel_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) /* check for an existing reusable credential handle */ if(ssl_config->primary.sessionid) { Curl_ssl_sessionid_lock(data); - if(!Curl_ssl_getsessionid(cf, data, (void **)&old_cred, NULL)) { + if(!Curl_ssl_getsessionid(cf, data, &connssl->peer, + (void **)&old_cred, NULL)) { backend->cred = old_cred; DEBUGF(infof(data, "schannel: reusing existing credential handle")); @@ -1335,7 +1336,7 @@ schannel_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) DEBUGF(infof(data, "schannel: SSL/TLS connection with %s port %d (step 2/3)", - connssl->peer.hostname, connssl->port)); + connssl->peer.hostname, connssl->peer.port)); if(!backend->cred || !backend->ctxt) return CURLE_SSL_CONNECT_ERROR; @@ -1569,9 +1570,13 @@ schannel_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) DEBUGF(infof(data, "schannel: SSL/TLS handshake complete")); } +#ifndef CURL_DISABLE_PROXY pubkey_ptr = Curl_ssl_cf_is_proxy(cf)? data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY]: data->set.str[STRING_SSL_PINNEDPUBLICKEY]; +#else + pubkey_ptr = data->set.str[STRING_SSL_PINNEDPUBLICKEY]; +#endif if(pubkey_ptr) { result = schannel_pkp_pin_peer_pubkey(cf, data, pubkey_ptr); if(result) { @@ -1670,6 +1675,28 @@ add_cert_to_certinfo(const CERT_CONTEXT *ccert_context, bool reverse_order, return args->result == CURLE_OK; } +static void schannel_session_free(void *sessionid, size_t idsize) +{ + /* this is expected to be called under sessionid lock */ + struct Curl_schannel_cred *cred = sessionid; + + (void)idsize; + if(cred) { + cred->refcount--; + if(cred->refcount == 0) { + s_pSecFn->FreeCredentialsHandle(&cred->cred_handle); + curlx_unicodefree(cred->sni_hostname); +#ifdef HAS_CLIENT_CERT_PATH + if(cred->client_cert_store) { + CertCloseStore(cred->client_cert_store, 0); + cred->client_cert_store = NULL; + } +#endif + Curl_safefree(cred); + } + } +} + static CURLcode schannel_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data) { @@ -1689,7 +1716,7 @@ schannel_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data) DEBUGF(infof(data, "schannel: SSL/TLS connection with %s port %d (step 3/3)", - connssl->peer.hostname, connssl->port)); + connssl->peer.hostname, connssl->peer.port)); if(!backend->cred) return CURLE_SSL_CONNECT_ERROR; @@ -1747,11 +1774,11 @@ schannel_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data) /* save the current session data for possible reuse */ if(ssl_config->primary.sessionid) { bool incache; - bool added = FALSE; struct Curl_schannel_cred *old_cred = NULL; Curl_ssl_sessionid_lock(data); - incache = !(Curl_ssl_getsessionid(cf, data, (void **)&old_cred, NULL)); + incache = !(Curl_ssl_getsessionid(cf, data, &connssl->peer, + (void **)&old_cred, NULL)); if(incache) { if(old_cred != backend->cred) { DEBUGF(infof(data, @@ -1762,20 +1789,15 @@ schannel_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data) } } if(!incache) { - result = Curl_ssl_addsessionid(cf, data, backend->cred, + /* Up ref count since call takes ownership */ + backend->cred->refcount++; + result = Curl_ssl_addsessionid(cf, data, &connssl->peer, backend->cred, sizeof(struct Curl_schannel_cred), - &added); + schannel_session_free); if(result) { Curl_ssl_sessionid_unlock(data); - failf(data, "schannel: failed to store credential handle"); return result; } - else if(added) { - /* this cred session is now also referenced by sessionid cache */ - backend->cred->refcount++; - DEBUGF(infof(data, - "schannel: stored credential handle in session cache")); - } } Curl_ssl_sessionid_unlock(data); } @@ -2450,27 +2472,6 @@ static bool schannel_data_pending(struct Curl_cfilter *cf, return FALSE; } -static void schannel_session_free(void *ptr) -{ - /* this is expected to be called under sessionid lock */ - struct Curl_schannel_cred *cred = ptr; - - if(cred) { - cred->refcount--; - if(cred->refcount == 0) { - s_pSecFn->FreeCredentialsHandle(&cred->cred_handle); - curlx_unicodefree(cred->sni_hostname); -#ifdef HAS_CLIENT_CERT_PATH - if(cred->client_cert_store) { - CertCloseStore(cred->client_cert_store, 0); - cred->client_cert_store = NULL; - } -#endif - Curl_safefree(cred); - } - } -} - /* 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). */ @@ -2489,7 +2490,7 @@ static int schannel_shutdown(struct Curl_cfilter *cf, if(backend->ctxt) { infof(data, "schannel: shutting down SSL/TLS connection with %s port %d", - connssl->peer.hostname, connssl->port); + connssl->peer.hostname, connssl->peer.port); } if(backend->cred && backend->ctxt) { @@ -2554,7 +2555,7 @@ static int schannel_shutdown(struct Curl_cfilter *cf, /* free SSPI Schannel API credential handle */ if(backend->cred) { Curl_ssl_sessionid_lock(data); - schannel_session_free(backend->cred); + schannel_session_free(backend->cred, 0); Curl_ssl_sessionid_unlock(data); backend->cred = NULL; } @@ -2749,7 +2750,7 @@ HCERTSTORE Curl_schannel_get_cached_cert_store(struct Curl_cfilter *cf, const struct Curl_easy *data) { struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); - struct Curl_multi *multi = data->multi_easy ? data->multi_easy : data->multi; + struct Curl_multi *multi = data->multi; const struct curl_blob *ca_info_blob = conn_config->ca_info_blob; struct schannel_multi_ssl_backend_data *mbackend; const struct ssl_general_config *cfg = &data->set.general_ssl; @@ -2818,7 +2819,7 @@ bool Curl_schannel_set_cached_cert_store(struct Curl_cfilter *cf, HCERTSTORE cert_store) { struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); - struct Curl_multi *multi = data->multi_easy ? data->multi_easy : data->multi; + struct Curl_multi *multi = data->multi; const struct curl_blob *ca_info_blob = conn_config->ca_info_blob; struct schannel_multi_ssl_backend_data *mbackend; unsigned char *CAinfo_blob_digest = NULL; @@ -2917,7 +2918,6 @@ const struct Curl_ssl Curl_ssl_schannel = { schannel_get_internals, /* get_internals */ schannel_close, /* close_one */ Curl_none_close_all, /* close_all */ - schannel_session_free, /* session_free */ Curl_none_set_engine, /* set_engine */ Curl_none_set_engine_default, /* set_engine_default */ Curl_none_engines_list, /* engines_list */ diff --git a/lib/vtls/schannel_int.h b/lib/vtls/schannel_int.h index fe7450d..5e233a9 100644 --- a/lib/vtls/schannel_int.h +++ b/lib/vtls/schannel_int.h @@ -53,6 +53,16 @@ #define CERT_ALT_NAME_IP_ADDRESS 8 #endif +#if defined(_MSC_VER) && (_MSC_VER <= 1600) +/* Workaround for warning: + 'type cast' : conversion from 'int' to 'LPCSTR' of greater size */ +#undef CERT_STORE_PROV_MEMORY +#undef CERT_STORE_PROV_SYSTEM_A +#undef CERT_STORE_PROV_SYSTEM_W +#define CERT_STORE_PROV_MEMORY ((LPCSTR)(size_t)2) +#define CERT_STORE_PROV_SYSTEM_A ((LPCSTR)(size_t)9) +#define CERT_STORE_PROV_SYSTEM_W ((LPCSTR)(size_t)10) +#endif #ifndef SCH_CREDENTIALS_VERSION diff --git a/lib/vtls/sectransp.c b/lib/vtls/sectransp.c index 67f534c..f49db64 100644 --- a/lib/vtls/sectransp.c +++ b/lib/vtls/sectransp.c @@ -1453,7 +1453,7 @@ static bool is_cipher_suite_strong(SSLCipherSuite suite_num) return true; } -static bool is_separator(char c) +static bool sectransp_is_separator(char c) { /* Return whether character is a cipher list separator. */ switch(c) { @@ -1547,7 +1547,7 @@ static CURLcode sectransp_set_selected_ciphers(struct Curl_easy *data, if(!ciphers) return CURLE_OK; - while(is_separator(*ciphers)) /* Skip initial separators. */ + while(sectransp_is_separator(*ciphers)) /* Skip initial separators. */ ciphers++; if(!*ciphers) return CURLE_OK; @@ -1561,14 +1561,14 @@ static CURLcode sectransp_set_selected_ciphers(struct Curl_easy *data, size_t i; /* Skip separators */ - while(is_separator(*cipher_start)) + while(sectransp_is_separator(*cipher_start)) cipher_start++; if(*cipher_start == '\0') { break; } /* Find last position of a cipher in the ciphers string */ cipher_end = cipher_start; - while(*cipher_end != '\0' && !is_separator(*cipher_end)) { + while(*cipher_end != '\0' && !sectransp_is_separator(*cipher_end)) { ++cipher_end; } @@ -1636,6 +1636,18 @@ static CURLcode sectransp_set_selected_ciphers(struct Curl_easy *data, return CURLE_OK; } +static void sectransp_session_free(void *sessionid, size_t idsize) +{ + /* ST, as of iOS 5 and Mountain Lion, has no public method of deleting a + cached session ID inside the Security framework. There is a private + function that does this, but I don't want to have to explain to you why I + got your application rejected from the App Store due to the use of a + private API, so the best we can do is free up our own char array that we + created way back in sectransp_connect_step1... */ + (void)idsize; + Curl_safefree(sessionid); +} + static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) { @@ -2047,8 +2059,8 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf, size_t ssl_sessionid_len; Curl_ssl_sessionid_lock(data); - if(!Curl_ssl_getsessionid(cf, data, (void **)&ssl_sessionid, - &ssl_sessionid_len)) { + if(!Curl_ssl_getsessionid(cf, data, &connssl->peer, + (void **)&ssl_sessionid, &ssl_sessionid_len)) { /* we got a session id, use it! */ err = SSLSetPeerID(backend->ssl_ctx, ssl_sessionid, ssl_sessionid_len); Curl_ssl_sessionid_unlock(data); @@ -2067,7 +2079,7 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf, aprintf("%s:%d:%d:%s:%d", ssl_cafile ? ssl_cafile : "(blob memory)", verifypeer, conn_config->verifyhost, connssl->peer.hostname, - connssl->port); + connssl->peer.port); ssl_sessionid_len = strlen(ssl_sessionid); err = SSLSetPeerID(backend->ssl_ctx, ssl_sessionid, ssl_sessionid_len); @@ -2077,13 +2089,12 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf, return CURLE_SSL_CONNECT_ERROR; } - result = Curl_ssl_addsessionid(cf, data, ssl_sessionid, - ssl_sessionid_len, NULL); + result = Curl_ssl_addsessionid(cf, data, &connssl->peer, ssl_sessionid, + ssl_sessionid_len, + sectransp_session_free); Curl_ssl_sessionid_unlock(data); - if(result) { - failf(data, "failed to store ssl session"); + if(result) return result; - } } } @@ -3225,17 +3236,6 @@ static int sectransp_shutdown(struct Curl_cfilter *cf, return rc; } -static void sectransp_session_free(void *ptr) -{ - /* ST, as of iOS 5 and Mountain Lion, has no public method of deleting a - cached session ID inside the Security framework. There is a private - function that does this, but I don't want to have to explain to you why I - got your application rejected from the App Store due to the use of a - private API, so the best we can do is free up our own char array that we - created way back in sectransp_connect_step1... */ - Curl_safefree(ptr); -} - static size_t sectransp_version(char *buffer, size_t size) { return msnprintf(buffer, size, "SecureTransport"); @@ -3469,7 +3469,6 @@ const struct Curl_ssl Curl_ssl_sectransp = { sectransp_get_internals, /* get_internals */ sectransp_close, /* close_one */ Curl_none_close_all, /* close_all */ - sectransp_session_free, /* session_free */ Curl_none_set_engine, /* set_engine */ Curl_none_set_engine_default, /* set_engine_default */ Curl_none_engines_list, /* engines_list */ diff --git a/lib/vtls/vtls.c b/lib/vtls/vtls.c index d13a3cb..570a10d 100644 --- a/lib/vtls/vtls.c +++ b/lib/vtls/vtls.c @@ -534,10 +534,10 @@ void Curl_ssl_sessionid_unlock(struct Curl_easy *data) */ bool Curl_ssl_getsessionid(struct Curl_cfilter *cf, struct Curl_easy *data, + const struct ssl_peer *peer, void **ssl_sessionid, size_t *idsize) /* set 0 if unknown */ { - struct ssl_connect_data *connssl = cf->ctx; struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); struct Curl_ssl_session *check; @@ -567,14 +567,15 @@ bool Curl_ssl_getsessionid(struct Curl_cfilter *cf, if(!check->sessionid) /* not session ID means blank entry */ continue; - if(strcasecompare(connssl->peer.hostname, check->name) && + if(strcasecompare(peer->hostname, check->name) && ((!cf->conn->bits.conn_to_host && !check->conn_to_host) || (cf->conn->bits.conn_to_host && check->conn_to_host && strcasecompare(cf->conn->conn_to_host.name, check->conn_to_host))) && ((!cf->conn->bits.conn_to_port && check->conn_to_port == -1) || (cf->conn->bits.conn_to_port && check->conn_to_port != -1 && cf->conn->conn_to_port == check->conn_to_port)) && - (connssl->port == check->remote_port) && + (peer->port == check->remote_port) && + (peer->transport == check->transport) && strcasecompare(cf->conn->handler->scheme, check->scheme) && match_ssl_primary_config(data, conn_config, &check->ssl_config)) { /* yes, we have a session ID! */ @@ -591,8 +592,7 @@ bool Curl_ssl_getsessionid(struct Curl_cfilter *cf, DEBUGF(infof(data, "%s Session ID in cache for %s %s://%s:%d", no_match? "Didn't find": "Found", Curl_ssl_cf_is_proxy(cf) ? "proxy" : "host", - cf->conn->handler->scheme, connssl->peer.hostname, - connssl->port)); + cf->conn->handler->scheme, peer->hostname, peer->port)); return no_match; } @@ -605,9 +605,10 @@ void Curl_ssl_kill_session(struct Curl_ssl_session *session) /* defensive check */ /* free the ID the SSL-layer specific way */ - Curl_ssl->session_free(session->sessionid); + session->sessionid_free(session->sessionid, session->idsize); session->sessionid = NULL; + session->sessionid_free = NULL; session->age = 0; /* fresh */ Curl_free_primary_ssl_config(&session->ssl_config); @@ -642,45 +643,44 @@ void Curl_ssl_delsessionid(struct Curl_easy *data, void *ssl_sessionid) */ CURLcode Curl_ssl_addsessionid(struct Curl_cfilter *cf, struct Curl_easy *data, + const struct ssl_peer *peer, void *ssl_sessionid, size_t idsize, - bool *added) + Curl_ssl_sessionid_dtor *sessionid_free_cb) { - struct ssl_connect_data *connssl = cf->ctx; struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); size_t i; struct Curl_ssl_session *store; long oldest_age; - char *clone_host; - char *clone_conn_to_host; + char *clone_host = NULL; + char *clone_conn_to_host = NULL; int conn_to_port; long *general_age; + CURLcode result = CURLE_OUT_OF_MEMORY; - if(added) - *added = FALSE; + DEBUGASSERT(ssl_sessionid); + DEBUGASSERT(sessionid_free_cb); - if(!data->state.session) + if(!data->state.session) { + sessionid_free_cb(ssl_sessionid, idsize); return CURLE_OK; + } store = &data->state.session[0]; oldest_age = data->state.session[0].age; /* zero if unused */ - (void)ssl_config; DEBUGASSERT(ssl_config->primary.sessionid); + (void)ssl_config; - clone_host = strdup(connssl->peer.hostname); + clone_host = strdup(peer->hostname); if(!clone_host) - return CURLE_OUT_OF_MEMORY; /* bail out */ + goto out; if(cf->conn->bits.conn_to_host) { clone_conn_to_host = strdup(cf->conn->conn_to_host.name); - if(!clone_conn_to_host) { - free(clone_host); - return CURLE_OUT_OF_MEMORY; /* bail out */ - } + if(!clone_conn_to_host) + goto out; } - else - clone_conn_to_host = NULL; if(cf->conn->bits.conn_to_port) conn_to_port = cf->conn->conn_to_port; @@ -713,33 +713,43 @@ CURLcode Curl_ssl_addsessionid(struct Curl_cfilter *cf, store = &data->state.session[i]; /* use this slot */ /* now init the session struct wisely */ + if(!clone_ssl_primary_config(conn_config, &store->ssl_config)) { + Curl_free_primary_ssl_config(&store->ssl_config); + store->sessionid = NULL; /* let caller free sessionid */ + goto out; + } store->sessionid = ssl_sessionid; store->idsize = idsize; + store->sessionid_free = sessionid_free_cb; store->age = *general_age; /* set current age */ /* free it if there's one already present */ free(store->name); free(store->conn_to_host); store->name = clone_host; /* clone host name */ + clone_host = NULL; store->conn_to_host = clone_conn_to_host; /* clone connect to host name */ + clone_conn_to_host = NULL; store->conn_to_port = conn_to_port; /* connect to port number */ /* port number */ - store->remote_port = connssl->port; + store->remote_port = peer->port; store->scheme = cf->conn->handler->scheme; + store->transport = peer->transport; - if(!clone_ssl_primary_config(conn_config, &store->ssl_config)) { - Curl_free_primary_ssl_config(&store->ssl_config); - store->sessionid = NULL; /* let caller free sessionid */ - free(clone_host); - free(clone_conn_to_host); - return CURLE_OUT_OF_MEMORY; - } - - if(added) - *added = TRUE; + result = CURLE_OK; - DEBUGF(infof(data, "Added Session ID to cache for %s://%s:%d [%s]", - store->scheme, store->name, store->remote_port, - Curl_ssl_cf_is_proxy(cf) ? "PROXY" : "server")); +out: + free(clone_host); + free(clone_conn_to_host); + if(result) { + failf(data, "Failed to add Session ID to cache for %s://%s:%d [%s]", + store->scheme, store->name, store->remote_port, + Curl_ssl_cf_is_proxy(cf) ? "PROXY" : "server"); + sessionid_free_cb(ssl_sessionid, idsize); + return result; + } + CURL_TRC_CF(data, cf, "Added Session ID to cache for %s://%s:%d [%s]", + store->scheme, store->name, store->remote_port, + Curl_ssl_cf_is_proxy(cf) ? "PROXY" : "server"); return CURLE_OK; } @@ -1322,7 +1332,6 @@ static const struct Curl_ssl Curl_ssl_multi = { multissl_get_internals, /* get_internals */ multissl_close, /* close_one */ Curl_none_close_all, /* close_all */ - Curl_none_session_free, /* session_free */ Curl_none_set_engine, /* set_engine */ Curl_none_set_engine_default, /* set_engine_default */ Curl_none_engines_list, /* engines_list */ @@ -1533,14 +1542,14 @@ static void cf_close(struct Curl_cfilter *cf, struct Curl_easy *data) static ssl_peer_type get_peer_type(const char *hostname) { if(hostname && hostname[0]) { -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 struct in6_addr addr; #else struct in_addr addr; #endif if(Curl_inet_pton(AF_INET, hostname, &addr)) return CURL_SSL_PEER_IPV4; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 else if(Curl_inet_pton(AF_INET6, hostname, &addr)) { return CURL_SSL_PEER_IPV6; } @@ -1549,9 +1558,9 @@ static ssl_peer_type get_peer_type(const char *hostname) return CURL_SSL_PEER_DNS; } -CURLcode Curl_ssl_peer_init(struct ssl_peer *peer, struct Curl_cfilter *cf) +CURLcode Curl_ssl_peer_init(struct ssl_peer *peer, struct Curl_cfilter *cf, + int transport) { - struct ssl_connect_data *connssl = cf->ctx; const char *ehostname, *edispname; int eport; @@ -1594,7 +1603,6 @@ CURLcode Curl_ssl_peer_init(struct ssl_peer *peer, struct Curl_cfilter *cf) } } - peer->sni = NULL; peer->type = get_peer_type(peer->hostname); if(peer->type == CURL_SSL_PEER_DNS && peer->hostname[0]) { /* not an IP address, normalize according to RCC 6066 ch. 3, @@ -1614,7 +1622,8 @@ CURLcode Curl_ssl_peer_init(struct ssl_peer *peer, struct Curl_cfilter *cf) } } - connssl->port = eport; + peer->port = eport; + peer->transport = transport; return CURLE_OK; } @@ -1667,7 +1676,7 @@ static CURLcode ssl_cf_connect(struct Curl_cfilter *cf, goto out; *done = FALSE; - result = Curl_ssl_peer_init(&connssl->peer, cf); + result = Curl_ssl_peer_init(&connssl->peer, cf, TRNSPRT_TCP); if(result) goto out; @@ -1857,7 +1866,7 @@ struct Curl_cftype Curl_cft_ssl = { struct Curl_cftype Curl_cft_ssl_proxy = { "SSL-PROXY", - CF_TYPE_SSL, + CF_TYPE_SSL|CF_TYPE_PROXY, CURL_LOG_LVL_NONE, ssl_cf_destroy, ssl_cf_connect, @@ -2033,12 +2042,7 @@ CURLcode Curl_ssl_cfilter_remove(struct Curl_easy *data, bool Curl_ssl_cf_is_proxy(struct Curl_cfilter *cf) { -#ifndef CURL_DISABLE_PROXY - return (cf->cft == &Curl_cft_ssl_proxy); -#else - (void)cf; - return FALSE; -#endif + return (cf->cft->flags & CF_TYPE_SSL) && (cf->cft->flags & CF_TYPE_PROXY); } struct ssl_config_data * diff --git a/lib/vtls/vtls.h b/lib/vtls/vtls.h index 744bbf8..c40ff26 100644 --- a/lib/vtls/vtls.h +++ b/lib/vtls/vtls.h @@ -37,6 +37,7 @@ struct Curl_ssl_session; #define SSLSUPP_HTTPS_PROXY (1<<4) /* supports access via HTTPS proxies */ #define SSLSUPP_TLS13_CIPHERSUITES (1<<5) /* supports TLS 1.3 ciphersuites */ #define SSLSUPP_CAINFO_BLOB (1<<6) +#define SSLSUPP_ECH (1<<7) #define ALPN_ACCEPTED "ALPN: server accepted " @@ -107,7 +108,8 @@ void Curl_ssl_conn_config_update(struct Curl_easy *data, bool for_proxy); /** * Init SSL peer information for filter. Can be called repeatedly. */ -CURLcode Curl_ssl_peer_init(struct ssl_peer *peer, struct Curl_cfilter *cf); +CURLcode Curl_ssl_peer_init(struct ssl_peer *peer, + struct Curl_cfilter *cf, int transport); /** * Free all allocated data and reset peer information. */ diff --git a/lib/vtls/vtls_int.h b/lib/vtls/vtls_int.h index 0361fa9..5259bab 100644 --- a/lib/vtls/vtls_int.h +++ b/lib/vtls/vtls_int.h @@ -73,9 +73,7 @@ struct ssl_connect_data { void *backend; /* vtls backend specific props */ struct cf_call_data call_data; /* data handle used in current call */ struct curltime handshake_done; /* time when handshake finished */ - int port; /* remote port at origin */ BIT(use_alpn); /* if ALPN shall be used in handshake */ - BIT(reused_session); /* session-ID was reused for this */ BIT(peer_closed); /* peer has closed connection */ }; @@ -125,7 +123,6 @@ struct Curl_ssl { void *(*get_internals)(struct ssl_connect_data *connssl, CURLINFO info); void (*close)(struct Curl_cfilter *cf, struct Curl_easy *data); void (*close_all)(struct Curl_easy *data); - void (*session_free)(void *ptr); CURLcode (*set_engine)(struct Curl_easy *data, const char *engine); CURLcode (*set_engine_default)(struct Curl_easy *data); @@ -181,18 +178,22 @@ bool Curl_ssl_cf_is_proxy(struct Curl_cfilter *cf); */ bool Curl_ssl_getsessionid(struct Curl_cfilter *cf, struct Curl_easy *data, + const struct ssl_peer *peer, void **ssl_sessionid, size_t *idsize); /* set 0 if unknown */ /* add a new session ID * Sessionid mutex must be locked (see Curl_ssl_sessionid_lock). * Caller must ensure that it has properly shared ownership of this sessionid * object with cache (e.g. incrementing refcount on success) + * Call takes ownership of `ssl_sessionid`, using `sessionid_free_cb` + * to destroy it in case of failure or later removal. */ CURLcode Curl_ssl_addsessionid(struct Curl_cfilter *cf, struct Curl_easy *data, + const struct ssl_peer *peer, void *ssl_sessionid, size_t idsize, - bool *added); + Curl_ssl_sessionid_dtor *sessionid_free_cb); #include "openssl.h" /* OpenSSL versions */ #include "gtls.h" /* GnuTLS versions */ diff --git a/lib/vtls/wolfssl.c b/lib/vtls/wolfssl.c index 3187bb8..2c92f56 100644 --- a/lib/vtls/wolfssl.c +++ b/lib/vtls/wolfssl.c @@ -74,6 +74,14 @@ #include "curl_memory.h" #include "memdebug.h" +#ifdef USE_ECH +# include "curl_base64.h" +# define ECH_ENABLED(__data__) \ + (__data__->set.tls_ech && \ + !(__data__->set.tls_ech & CURLECH_DISABLE)\ + ) +#endif /* USE_ECH */ + /* KEEP_PEER_CERT is a product of the presence of build time symbol OPENSSL_EXTRA without NO_CERTS, depending on the version. KEEP_PEER_CERT is in wolfSSL's settings.h, and the latter two are build time symbols in @@ -409,11 +417,11 @@ wolfssl_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) #if defined(WOLFSSL_ALLOW_TLSV10) && !defined(NO_OLD_TLS) req_method = TLSv1_client_method(); use_sni(TRUE); + break; #else failf(data, "wolfSSL does not support TLS 1.0"); return CURLE_NOT_BUILT_IN; #endif - break; case CURL_SSLVERSION_TLSv1_1: #ifndef NO_OLD_TLS req_method = TLSv1_1_client_method(); @@ -481,6 +489,7 @@ wolfssl_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) return CURLE_SSL_CONNECT_ERROR; } #endif + FALLTHROUGH(); default: break; } @@ -711,7 +720,8 @@ wolfssl_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) void *ssl_sessionid = NULL; Curl_ssl_sessionid_lock(data); - if(!Curl_ssl_getsessionid(cf, data, &ssl_sessionid, NULL)) { + if(!Curl_ssl_getsessionid(cf, data, &connssl->peer, + &ssl_sessionid, NULL)) { /* we got a session id, use it! */ if(!SSL_set_session(backend->handle, ssl_sessionid)) { Curl_ssl_delsessionid(data, ssl_sessionid); @@ -723,6 +733,82 @@ wolfssl_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) Curl_ssl_sessionid_unlock(data); } +#ifdef USE_ECH + if(ECH_ENABLED(data)) { + int trying_ech_now = 0; + + if(data->set.str[STRING_ECH_PUBLIC]) { + infof(data, "ECH: outername not (yet) supported with WolfSSL"); + return CURLE_SSL_CONNECT_ERROR; + } + if(data->set.tls_ech == CURLECH_GREASE) { + infof(data, "ECH: GREASE'd ECH not yet supported for wolfSSL"); + return CURLE_SSL_CONNECT_ERROR; + } + if(data->set.tls_ech & CURLECH_CLA_CFG + && data->set.str[STRING_ECH_CONFIG]) { + char *b64val = data->set.str[STRING_ECH_CONFIG]; + word32 b64len = 0; + + b64len = (word32) strlen(b64val); + if(b64len + && wolfSSL_SetEchConfigsBase64(backend->handle, b64val, b64len) + != WOLFSSL_SUCCESS) { + if(data->set.tls_ech & CURLECH_HARD) + return CURLE_SSL_CONNECT_ERROR; + } + else { + trying_ech_now = 1; + infof(data, "ECH: ECHConfig from command line"); + } + } + else { + struct Curl_dns_entry *dns = NULL; + + dns = Curl_fetch_addr(data, connssl->peer.hostname, connssl->peer.port); + if(!dns) { + infof(data, "ECH: requested but no DNS info available"); + if(data->set.tls_ech & CURLECH_HARD) + return CURLE_SSL_CONNECT_ERROR; + } + else { + struct Curl_https_rrinfo *rinfo = NULL; + + rinfo = dns->hinfo; + if(rinfo && rinfo->echconfiglist) { + unsigned char *ecl = rinfo->echconfiglist; + size_t elen = rinfo->echconfiglist_len; + + infof(data, "ECH: ECHConfig from DoH HTTPS RR"); + if(wolfSSL_SetEchConfigs(backend->handle, ecl, (word32) elen) != + WOLFSSL_SUCCESS) { + infof(data, "ECH: wolfSSL_SetEchConfigs failed"); + if(data->set.tls_ech & CURLECH_HARD) + return CURLE_SSL_CONNECT_ERROR; + } + else { + trying_ech_now = 1; + infof(data, "ECH: imported ECHConfigList of length %ld", elen); + } + } + else { + infof(data, "ECH: requested but no ECHConfig available"); + if(data->set.tls_ech & CURLECH_HARD) + return CURLE_SSL_CONNECT_ERROR; + } + Curl_resolv_unlock(data, dns); + } + } + + if(trying_ech_now + && SSL_set_min_proto_version(backend->handle, TLS1_3_VERSION) != 1) { + infof(data, "ECH: Can't force TLSv1.3 [ERROR]"); + return CURLE_SSL_CONNECT_ERROR; + } + + } +#endif /* USE_ECH */ + #ifdef USE_BIO_CHAIN { WOLFSSL_BIO *bio; @@ -756,9 +842,13 @@ wolfssl_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) struct wolfssl_ssl_backend_data *backend = (struct wolfssl_ssl_backend_data *)connssl->backend; struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); +#ifndef CURL_DISABLE_PROXY const char * const pinnedpubkey = Curl_ssl_cf_is_proxy(cf)? data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY]: data->set.str[STRING_SSL_PINNEDPUBLICKEY]; +#else + const char * const pinnedpubkey = data->set.str[STRING_SSL_PINNEDPUBLICKEY]; +#endif DEBUGASSERT(backend); @@ -853,6 +943,31 @@ wolfssl_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) } } #endif +#ifdef USE_ECH + else if(-1 == detail) { + /* try access a retry_config ECHConfigList for tracing */ + byte echConfigs[1000]; + word32 echConfigsLen = 1000; + int rv = 0; + + /* this currently doesn't produce the retry_configs */ + rv = wolfSSL_GetEchConfigs(backend->handle, echConfigs, + &echConfigsLen); + if(rv != WOLFSSL_SUCCESS) { + infof(data, "Failed to get ECHConfigs"); + } + else { + char *b64str = NULL; + size_t blen = 0; + + rv = Curl_base64_encode((const char *)echConfigs, echConfigsLen, + &b64str, &blen); + if(!rv && b64str) + infof(data, "ECH: (not yet) retry_configs %s", b64str); + free(b64str); + } + } +#endif else if(backend->io_result == CURLE_AGAIN) { return CURLE_OK; } @@ -898,6 +1013,7 @@ wolfssl_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) pinnedpubkey, (const unsigned char *)pubkey->header, (size_t)(pubkey->end - pubkey->header)); + wolfSSL_FreeX509(x509); if(result) { failf(data, "SSL: public key does not match pinned public key"); return result; @@ -942,6 +1058,13 @@ wolfssl_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) } +static void wolfssl_session_free(void *sessionid, size_t idsize) +{ + (void)idsize; + wolfSSL_SESSION_free(sessionid); +} + + static CURLcode wolfssl_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data) { @@ -955,40 +1078,27 @@ wolfssl_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data) DEBUGASSERT(backend); if(ssl_config->primary.sessionid) { - bool incache; - bool added = FALSE; - void *old_ssl_sessionid = NULL; /* wolfSSL_get1_session allocates memory that has to be freed. */ WOLFSSL_SESSION *our_ssl_sessionid = wolfSSL_get1_session(backend->handle); if(our_ssl_sessionid) { + void *old_ssl_sessionid = NULL; + bool incache; Curl_ssl_sessionid_lock(data); - incache = !(Curl_ssl_getsessionid(cf, data, &old_ssl_sessionid, NULL)); + incache = !(Curl_ssl_getsessionid(cf, data, &connssl->peer, + &old_ssl_sessionid, NULL)); if(incache) { - if(old_ssl_sessionid != our_ssl_sessionid) { - infof(data, "old SSL session ID is stale, removing"); - Curl_ssl_delsessionid(data, old_ssl_sessionid); - incache = FALSE; - } + Curl_ssl_delsessionid(data, old_ssl_sessionid); } - if(!incache) { - result = Curl_ssl_addsessionid(cf, data, our_ssl_sessionid, 0, NULL); - if(result) { - Curl_ssl_sessionid_unlock(data); - wolfSSL_SESSION_free(our_ssl_sessionid); - failf(data, "failed to store ssl session"); - return result; - } - else { - added = TRUE; - } - } + /* call takes ownership of `our_ssl_sessionid` */ + result = Curl_ssl_addsessionid(cf, data, &connssl->peer, + our_ssl_sessionid, 0, + wolfssl_session_free); Curl_ssl_sessionid_unlock(data); - - if(!added) { - /* If the session info wasn't added to the cache, free our copy. */ - wolfSSL_SESSION_free(our_ssl_sessionid); + if(result) { + failf(data, "failed to store ssl session"); + return result; } } } @@ -1122,12 +1232,6 @@ static ssize_t wolfssl_recv(struct Curl_cfilter *cf, } -static void wolfssl_session_free(void *ptr) -{ - wolfSSL_SESSION_free(ptr); -} - - static size_t wolfssl_version(char *buffer, size_t size) { #if LIBWOLFSSL_VERSION_HEX >= 0x03006000 @@ -1386,6 +1490,9 @@ const struct Curl_ssl Curl_ssl_wolfssl = { #endif SSLSUPP_CA_PATH | SSLSUPP_CAINFO_BLOB | +#ifdef USE_ECH + SSLSUPP_ECH | +#endif SSLSUPP_SSL_CTX, sizeof(struct wolfssl_ssl_backend_data), @@ -1404,7 +1511,6 @@ const struct Curl_ssl Curl_ssl_wolfssl = { wolfssl_get_internals, /* get_internals */ wolfssl_close, /* close_one */ Curl_none_close_all, /* close_all */ - wolfssl_session_free, /* session_free */ Curl_none_set_engine, /* set_engine */ Curl_none_set_engine_default, /* set_engine_default */ Curl_none_engines_list, /* engines_list */ diff --git a/lib/vtls/x509asn1.c b/lib/vtls/x509asn1.c index da07936..4564ea9 100644 --- a/lib/vtls/x509asn1.c +++ b/lib/vtls/x509asn1.c @@ -160,6 +160,7 @@ static const struct Curl_OID OIDtable[] = { { "2.16.840.1.101.3.4.2.1", "sha256" }, { "2.16.840.1.101.3.4.2.2", "sha384" }, { "2.16.840.1.101.3.4.2.3", "sha512" }, + { "1.2.840.113549.1.9.2", "unstructuredName" }, { (const char *) NULL, (const char *) NULL } }; @@ -467,6 +468,8 @@ static CURLcode OID2str(struct dynbuf *store, const struct Curl_OID *op = searchOID(Curl_dyn_ptr(&buf)); if(op) result = Curl_dyn_add(store, op->textoid); + else + result = CURLE_BAD_FUNCTION_ARGUMENT; Curl_dyn_free(&buf); } } diff --git a/lib/warnless.h b/lib/warnless.h index e5a02c8..6adf63a 100644 --- a/lib/warnless.h +++ b/lib/warnless.h @@ -77,20 +77,6 @@ ssize_t curlx_write(int fd, const void *buf, size_t count); #endif /* _WIN32 */ -#if defined(__INTEL_COMPILER) && defined(__unix__) - -int curlx_FD_ISSET(int fd, fd_set *fdset); - -void curlx_FD_SET(int fd, fd_set *fdset); - -void curlx_FD_ZERO(fd_set *fdset); - -unsigned short curlx_htons(unsigned short usnum); - -unsigned short curlx_ntohs(unsigned short usnum); - -#endif /* __INTEL_COMPILER && __unix__ */ - #endif /* HEADER_CURL_WARNLESS_H */ #ifndef HEADER_CURL_WARNLESS_H_REDEFS @@ -114,23 +114,23 @@ static void ws_dec_info(struct ws_decoder *dec, struct Curl_easy *data, case 0: break; case 1: - infof(data, "WS-DEC: %s [%s%s]", msg, - ws_frame_name_of_op(dec->head[0]), - (dec->head[0] & WSBIT_FIN)? "" : " NON-FINAL"); + CURL_TRC_WRITE(data, "websocket, decoded %s [%s%s]", msg, + ws_frame_name_of_op(dec->head[0]), + (dec->head[0] & WSBIT_FIN)? "" : " NON-FINAL"); break; default: if(dec->head_len < dec->head_total) { - infof(data, "WS-DEC: %s [%s%s](%d/%d)", msg, - ws_frame_name_of_op(dec->head[0]), - (dec->head[0] & WSBIT_FIN)? "" : " NON-FINAL", - dec->head_len, dec->head_total); + CURL_TRC_WRITE(data, "websocket, decoded %s [%s%s](%d/%d)", msg, + ws_frame_name_of_op(dec->head[0]), + (dec->head[0] & WSBIT_FIN)? "" : " NON-FINAL", + dec->head_len, dec->head_total); } else { - infof(data, "WS-DEC: %s [%s%s payload=%" CURL_FORMAT_CURL_OFF_T - "/%" CURL_FORMAT_CURL_OFF_T "]", - msg, ws_frame_name_of_op(dec->head[0]), - (dec->head[0] & WSBIT_FIN)? "" : " NON-FINAL", - dec->payload_offset, dec->payload_len); + CURL_TRC_WRITE(data, "websocket, decoded %s [%s%s payload=%" + CURL_FORMAT_CURL_OFF_T "/%" CURL_FORMAT_CURL_OFF_T "]", + msg, ws_frame_name_of_op(dec->head[0]), + (dec->head[0] & WSBIT_FIN)? "" : " NON-FINAL", + dec->payload_offset, dec->payload_len); } break; } @@ -277,9 +277,8 @@ static CURLcode ws_dec_pass_payload(struct ws_decoder *dec, Curl_bufq_skip(inraw, (size_t)nwritten); dec->payload_offset += (curl_off_t)nwritten; remain = dec->payload_len - dec->payload_offset; - /* infof(data, "WS-DEC: passed %zd bytes payload, %" - CURL_FORMAT_CURL_OFF_T " remain", - nwritten, remain); */ + CURL_TRC_WRITE(data, "websocket, passed %zd bytes payload, %" + CURL_FORMAT_CURL_OFF_T " remain", nwritten, remain); } return remain? CURLE_AGAIN : CURLE_OK; @@ -454,10 +453,12 @@ static CURLcode ws_cw_write(struct Curl_easy *data, pass_ctx.cw_type = type; result = ws_dec_pass(&ws->dec, data, &ctx->buf, ws_cw_dec_next, &pass_ctx); - if(result == CURLE_AGAIN) + if(result == CURLE_AGAIN) { /* insufficient amount of data, keep it for later. * we pretend to have written all since we have a copy */ + CURL_TRC_WRITE(data, "websocket, buffered incomplete frame head"); return CURLE_OK; + } else if(result) { infof(data, "WS: decode error %d", (int)result); return result; @@ -717,8 +718,10 @@ CURLcode Curl_ws_request(struct Curl_easy *data, REQTYPE *req) if(result) return result; DEBUGASSERT(randlen < sizeof(keyval)); - if(randlen >= sizeof(keyval)) + if(randlen >= sizeof(keyval)) { + free(randstr); return CURLE_FAILED_INIT; + } strcpy(keyval, randstr); free(randstr); for(i = 0; !result && (i < sizeof(heads)/sizeof(heads[0])); i++) { @@ -949,10 +952,6 @@ CURL_EXTERN CURLcode curl_ws_recv(struct Curl_easy *data, void *buffer, *nread = 0; *metap = NULL; - /* get a download buffer */ - result = Curl_preconnect(data); - if(result) - return result; memset(&ctx, 0, sizeof(ctx)); ctx.data = data; @@ -1198,6 +1197,7 @@ const struct Curl_handler Curl_handler_ws = { ZERO_NULL, /* perform_getsock */ ws_disconnect, /* disconnect */ Curl_http_write_resp, /* write_resp */ + Curl_http_write_resp_hd, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_HTTP, /* defport */ @@ -1223,6 +1223,7 @@ const struct Curl_handler Curl_handler_wss = { ZERO_NULL, /* perform_getsock */ ws_disconnect, /* disconnect */ Curl_http_write_resp, /* write_resp */ + Curl_http_write_resp_hd, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_HTTPS, /* defport */ |