diff --git a/Utilities/cmcurl/CMake/CurlTests.c b/Utilities/cmcurl/CMake/CurlTests.c index be3b6f73ee..e8117410fd 100644 --- a/Utilities/cmcurl/CMake/CurlTests.c +++ b/Utilities/cmcurl/CMake/CurlTests.c @@ -48,7 +48,7 @@ int main(void) { /* O_NONBLOCK source test */ int flags = 0; - if(0 != fcntl(0, F_SETFL, flags | O_NONBLOCK)) + if(fcntl(0, F_SETFL, flags | O_NONBLOCK)) return 1; return 0; } @@ -65,7 +65,7 @@ int main(void) #include int main(void) { - const char *address = "example.com"; + static const char address[] = "localhost"; struct hostent h; int rc = 0; #if defined(HAVE_GETHOSTBYNAME_R_3) || \ @@ -114,17 +114,6 @@ int main(void) } #endif -#ifdef STDC_HEADERS -#include -#include -#include -#include -int main(void) -{ - return 0; -} -#endif - #ifdef HAVE_FILE_OFFSET_BITS #include /* Check that off_t can represent 2**63 - 1 correctly. @@ -144,7 +133,7 @@ int main(void) #ifdef HAVE_IOCTLSOCKET #ifdef _WIN32 -# include +#include #endif int main(void) { @@ -162,7 +151,7 @@ int main(void) int main(void) { /* IoctlSocket source code */ - if(0 != IoctlSocket(0, 0, 0)) + if(IoctlSocket(0, 0, 0)) return 1; return 0; } @@ -171,13 +160,13 @@ int main(void) #ifdef HAVE_IOCTLSOCKET_CAMEL_FIONBIO #include #ifdef HAVE_SYS_IOCTL_H -# include +#include #endif int main(void) { /* IoctlSocket source code */ long flags = 0; - if(0 != IoctlSocket(0, FIONBIO, &flags)) + if(IoctlSocket(0, FIONBIO, &flags)) return 1; (void)flags; return 0; @@ -186,12 +175,12 @@ int main(void) #ifdef HAVE_IOCTLSOCKET_FIONBIO #ifdef _WIN32 -# include +#include #endif int main(void) { unsigned long flags = 0; - if(0 != ioctlsocket(0, FIONBIO, &flags)) + if(ioctlsocket(0, FIONBIO, &flags)) return 1; (void)flags; return 0; @@ -201,24 +190,24 @@ int main(void) #ifdef HAVE_IOCTL_FIONBIO /* headers for FIONBIO test */ #ifdef HAVE_SYS_TYPES_H -# include +#include #endif #ifdef HAVE_UNISTD_H -# include +#include #endif #ifndef _WIN32 -# include +#include #endif #ifdef HAVE_SYS_IOCTL_H -# include +#include #endif #ifdef HAVE_STROPTS_H -# include +#include #endif int main(void) { int flags = 0; - if(0 != ioctl(0, FIONBIO, &flags)) + if(ioctl(0, FIONBIO, &flags)) return 1; (void)flags; return 0; @@ -226,27 +215,27 @@ int main(void) #endif #ifdef HAVE_IOCTL_SIOCGIFADDR -/* headers for FIONBIO test */ +/* headers for SIOCGIFADDR test */ #ifdef HAVE_SYS_TYPES_H -# include +#include #endif #ifdef HAVE_UNISTD_H -# include +#include #endif #ifndef _WIN32 -# include +#include #endif #ifdef HAVE_SYS_IOCTL_H -# include +#include #endif #ifdef HAVE_STROPTS_H -# include +#include #endif #include int main(void) { struct ifreq ifr; - if(0 != ioctl(0, SIOCGIFADDR, &ifr)) + if(ioctl(0, SIOCGIFADDR, &ifr)) return 1; (void)ifr; return 0; @@ -255,17 +244,17 @@ int main(void) #ifdef HAVE_SETSOCKOPT_SO_NONBLOCK #ifdef _WIN32 -# include +#include #endif #ifdef HAVE_SYS_TYPES_H -# include +#include #endif #ifndef _WIN32 -# include +#include #endif int main(void) { - if(0 != setsockopt(0, SOL_SOCKET, SO_NONBLOCK, 0, 0)) + if(setsockopt(0, SOL_SOCKET, SO_NONBLOCK, 0, 0)) return 1; return 0; } @@ -349,13 +338,13 @@ int main(void) #ifdef HAVE_ATOMIC #ifdef HAVE_SYS_TYPES_H -# include +#include #endif #ifdef HAVE_UNISTD_H -# include +#include #endif #ifdef HAVE_STDATOMIC_H -# include +#include #endif int main(void) { @@ -367,10 +356,10 @@ int main(void) #ifdef HAVE_WIN32_WINNT #ifdef _WIN32 -# ifndef NOGDI -# define NOGDI -# endif -# include +#ifndef NOGDI +#define NOGDI +#endif +#include #endif #define enquote(x) #x @@ -385,7 +374,7 @@ int main(void) #ifdef MINGW64_VERSION #ifdef __MINGW32__ -# include <_mingw.h> +#include <_mingw.h> #endif #define enquote(x) #x diff --git a/Utilities/cmcurl/CMake/FindGSS.cmake b/Utilities/cmcurl/CMake/FindGSS.cmake index 6f352b2e4f..46d768699d 100644 --- a/Utilities/cmcurl/CMake/FindGSS.cmake +++ b/Utilities/cmcurl/CMake/FindGSS.cmake @@ -23,6 +23,10 @@ ########################################################################### # Find the GSS Kerberos library # +# This module accepts optional COMPONENTS to control the GSS library flavor: +# +# - Apple +# # Input variables: # # - `GSS_ROOT_DIR`: Absolute path to the root installation of GSS. (also supported as environment) @@ -32,7 +36,7 @@ # - `GSS_FOUND`: System has GSS. # - `GSS_VERSION`: Version of GSS. # - `CURL::gss`: GSS library target. -# - `INTERFACE_CURL_GSS_FLAVOR`: Custom property. "GNU" or "MIT" if detected. +# - `INTERFACE_CURL_GSS_FLAVOR`: Custom property. "Apple", "GNU" or "MIT" if detected. set(_gnu_modname "gss") set(_mit_modname "mit-krb5-gssapi") @@ -46,8 +50,12 @@ set(_gss_root_hints "${GSS_ROOT_DIR}" "$ENV{GSS_ROOT_DIR}") set(_gss_CFLAGS "") set(_gss_LIBRARY_DIRS "") +if(NOT APPLE AND GSS_FIND_COMPONENTS STREQUAL "Apple") + set(GSS_FIND_COMPONENTS "") +endif() + # Try to find library using system pkg-config if user did not specify root dir -if(NOT GSS_ROOT_DIR AND NOT "$ENV{GSS_ROOT_DIR}") +if(NOT GSS_ROOT_DIR AND NOT "$ENV{GSS_ROOT_DIR}" AND NOT GSS_FIND_COMPONENTS STREQUAL "Apple") if(CURL_USE_PKGCONFIG) find_package(PkgConfig QUIET) pkg_search_module(_gss ${_mit_modname} ${_gnu_modname}) @@ -60,136 +68,146 @@ if(NOT GSS_ROOT_DIR AND NOT "$ENV{GSS_ROOT_DIR}") endif() if(NOT _gss_FOUND) # Not found by pkg-config. Let us take more traditional approach. - find_file(_gss_configure_script NAMES "krb5-config" PATH_SUFFIXES "bin" HINTS ${_gss_root_hints} - NO_CMAKE_PATH NO_CMAKE_ENVIRONMENT_PATH) - # If not found in user-supplied directories, maybe system knows better - find_file(_gss_configure_script NAMES "krb5-config" PATH_SUFFIXES "bin") + if(GSS_FIND_COMPONENTS STREQUAL "Apple") + find_path(_gss_INCLUDE_DIRS NAMES "GSS/gssapi.h" PATH_SUFFIXES "include") + find_library(_gss_LIBRARIES NAMES "GSS") - if(_gss_configure_script) - - set(_gss_INCLUDE_DIRS "") - set(_gss_LIBRARIES "") - - execute_process(COMMAND ${_gss_configure_script} "--cflags" "gssapi" - OUTPUT_VARIABLE _gss_cflags_raw - RESULT_VARIABLE _gss_configure_failed - OUTPUT_STRIP_TRAILING_WHITESPACE) - message(STATUS "FindGSS krb5-config --cflags: ${_gss_cflags_raw}") - - if(NOT _gss_configure_failed) # 0 means success - # Should also work in an odd case when multiple directories are given. - string(STRIP "${_gss_cflags_raw}" _gss_cflags_raw) - string(REGEX REPLACE " +-(I)" ";-\\1" _gss_cflags_raw "${_gss_cflags_raw}") - string(REGEX REPLACE " +-([^I][^ \\t;]*)" ";-\\1" _gss_cflags_raw "${_gss_cflags_raw}") - - foreach(_flag IN LISTS _gss_cflags_raw) - if(_flag MATCHES "^-I") - string(REGEX REPLACE "^-I" "" _flag "${_flag}") - list(APPEND _gss_INCLUDE_DIRS "${_flag}") - else() - list(APPEND _gss_CFLAGS "${_flag}") - endif() - endforeach() + if(_gss_INCLUDE_DIRS AND _gss_LIBRARIES) + message(STATUS "Found AppleGSS: ${_gss_INCLUDE_DIRS}") + set(_gss_flavor "Apple") endif() + else() + find_file(_gss_configure_script NAMES "krb5-config" PATH_SUFFIXES "bin" HINTS ${_gss_root_hints} + NO_CMAKE_PATH NO_CMAKE_ENVIRONMENT_PATH) + # If not found in user-supplied directories, maybe system knows better + find_file(_gss_configure_script NAMES "krb5-config" PATH_SUFFIXES "bin") - execute_process(COMMAND ${_gss_configure_script} "--libs" "gssapi" - OUTPUT_VARIABLE _gss_lib_flags - RESULT_VARIABLE _gss_configure_failed - OUTPUT_STRIP_TRAILING_WHITESPACE) - message(STATUS "FindGSS krb5-config --libs: ${_gss_lib_flags}") + if(_gss_configure_script) - if(NOT _gss_configure_failed) # 0 means success - # This script gives us libraries and link directories. - string(STRIP "${_gss_lib_flags}" _gss_lib_flags) - string(REGEX REPLACE " +-(L|l)" ";-\\1" _gss_lib_flags "${_gss_lib_flags}") - string(REGEX REPLACE " +-([^Ll][^ \\t;]*)" ";-\\1" _gss_lib_flags "${_gss_lib_flags}") + set(_gss_INCLUDE_DIRS "") + set(_gss_LIBRARIES "") - foreach(_flag IN LISTS _gss_lib_flags) - if(_flag MATCHES "^-l") - string(REGEX REPLACE "^-l" "" _flag "${_flag}") - list(APPEND _gss_LIBRARIES "${_flag}") - elseif(_flag MATCHES "^-L") - string(REGEX REPLACE "^-L" "" _flag "${_flag}") - list(APPEND _gss_LIBRARY_DIRS "${_flag}") - endif() - endforeach() - endif() + execute_process(COMMAND ${_gss_configure_script} "--cflags" "gssapi" + OUTPUT_VARIABLE _gss_cflags_raw + RESULT_VARIABLE _gss_configure_failed + OUTPUT_STRIP_TRAILING_WHITESPACE) + message(STATUS "FindGSS krb5-config --cflags: ${_gss_cflags_raw}") - execute_process(COMMAND ${_gss_configure_script} "--version" - OUTPUT_VARIABLE _gss_version - RESULT_VARIABLE _gss_configure_failed - OUTPUT_STRIP_TRAILING_WHITESPACE) + if(NOT _gss_configure_failed) # 0 means success + # Should also work in an odd case when multiple directories are given. + string(STRIP "${_gss_cflags_raw}" _gss_cflags_raw) + string(REGEX REPLACE " +-(I)" ";-\\1" _gss_cflags_raw "${_gss_cflags_raw}") + string(REGEX REPLACE " +-([^I][^ \\t;]*)" ";-\\1" _gss_cflags_raw "${_gss_cflags_raw}") - # Older versions may not have the "--version" parameter. In this case we do not care. - if(_gss_configure_failed) - set(_gss_version 0) - else() - # Strip prefix string to leave the version number only - string(REPLACE "Kerberos 5 release " "" _gss_version "${_gss_version}") - endif() - - execute_process(COMMAND ${_gss_configure_script} "--vendor" - OUTPUT_VARIABLE _gss_vendor - RESULT_VARIABLE _gss_configure_failed - OUTPUT_STRIP_TRAILING_WHITESPACE) - - # Older versions may not have the "--vendor" parameter. In this case we do not care. - if(NOT _gss_configure_failed AND NOT _gss_vendor MATCHES "Heimdal|heimdal") - set(_gss_flavor "MIT") # assume a default, should not really matter - endif() - - else() # Either there is no config script or we are on a platform that does not provide one (Windows?) - - find_path(_gss_INCLUDE_DIRS NAMES "gssapi/gssapi.h" HINTS ${_gss_root_hints} PATH_SUFFIXES "include" "inc") - - if(_gss_INCLUDE_DIRS) # We have found something - set(_gss_libdir_suffixes "") - - cmake_push_check_state() - list(APPEND CMAKE_REQUIRED_INCLUDES "${_gss_INCLUDE_DIRS}") - check_include_files("gssapi/gssapi_generic.h;gssapi/gssapi_krb5.h" _gss_have_mit_headers) - cmake_pop_check_state() - - if(_gss_have_mit_headers) - set(_gss_flavor "MIT") - if(WIN32) - if(CMAKE_SIZEOF_VOID_P EQUAL 8) - list(APPEND _gss_libdir_suffixes "lib/AMD64") - set(_gss_libname "gssapi64") + foreach(_flag IN LISTS _gss_cflags_raw) + if(_flag MATCHES "^-I") + string(REGEX REPLACE "^-I" "" _flag "${_flag}") + list(APPEND _gss_INCLUDE_DIRS "${_flag}") else() - list(APPEND _gss_libdir_suffixes "lib/i386") - set(_gss_libname "gssapi32") + list(APPEND _gss_CFLAGS "${_flag}") endif() - else() - list(APPEND _gss_libdir_suffixes "lib" "lib64") # those suffixes are not checked for HINTS - set(_gss_libname "gssapi_krb5") + endforeach() + endif() + + execute_process(COMMAND ${_gss_configure_script} "--libs" "gssapi" + OUTPUT_VARIABLE _gss_lib_flags + RESULT_VARIABLE _gss_configure_failed + OUTPUT_STRIP_TRAILING_WHITESPACE) + message(STATUS "FindGSS krb5-config --libs: ${_gss_lib_flags}") + + if(NOT _gss_configure_failed) # 0 means success + # This script gives us libraries and link directories. + string(STRIP "${_gss_lib_flags}" _gss_lib_flags) + string(REGEX REPLACE " +-(L|l)" ";-\\1" _gss_lib_flags "${_gss_lib_flags}") + string(REGEX REPLACE " +-([^Ll][^ \\t;]*)" ";-\\1" _gss_lib_flags "${_gss_lib_flags}") + + foreach(_flag IN LISTS _gss_lib_flags) + if(_flag MATCHES "^-l") + string(REGEX REPLACE "^-l" "" _flag "${_flag}") + list(APPEND _gss_LIBRARIES "${_flag}") + elseif(_flag MATCHES "^-L") + string(REGEX REPLACE "^-L" "" _flag "${_flag}") + list(APPEND _gss_LIBRARY_DIRS "${_flag}") + endif() + endforeach() + endif() + + execute_process(COMMAND ${_gss_configure_script} "--version" + OUTPUT_VARIABLE _gss_version + RESULT_VARIABLE _gss_configure_failed + OUTPUT_STRIP_TRAILING_WHITESPACE) + + # Older versions may not have the "--version" parameter. In this case we do not care. + if(_gss_configure_failed) + set(_gss_version 0) + else() + # Strip prefix string to leave the version number only + string(REPLACE "Kerberos 5 release " "" _gss_version "${_gss_version}") + endif() + + execute_process(COMMAND ${_gss_configure_script} "--vendor" + OUTPUT_VARIABLE _gss_vendor + RESULT_VARIABLE _gss_configure_failed + OUTPUT_STRIP_TRAILING_WHITESPACE) + + # Older versions may not have the "--vendor" parameter. In this case we do not care. + if(NOT _gss_configure_failed AND NOT _gss_vendor MATCHES "Heimdal|heimdal") + set(_gss_flavor "MIT") # assume a default, should not really matter + endif() + + else() # Either there is no config script or we are on a platform that does not provide one (Windows?) + + find_path(_gss_INCLUDE_DIRS NAMES "gssapi/gssapi.h" HINTS ${_gss_root_hints} PATH_SUFFIXES "include" "inc") + + if(_gss_INCLUDE_DIRS) # We have found something + set(_gss_libdir_suffixes "") + + cmake_push_check_state() + list(APPEND CMAKE_REQUIRED_INCLUDES "${_gss_INCLUDE_DIRS}") + check_include_files("gssapi/gssapi_generic.h;gssapi/gssapi_krb5.h" _gss_have_mit_headers) + cmake_pop_check_state() + + if(_gss_have_mit_headers) + set(_gss_flavor "MIT") + if(WIN32) + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + list(APPEND _gss_libdir_suffixes "lib/AMD64") + set(_gss_libname "gssapi64") + else() + list(APPEND _gss_libdir_suffixes "lib/i386") + set(_gss_libname "gssapi32") + endif() + else() + list(APPEND _gss_libdir_suffixes "lib" "lib64") # those suffixes are not checked for HINTS + set(_gss_libname "gssapi_krb5") + endif() + endif() + else() + find_path(_gss_INCLUDE_DIRS NAMES "gss.h" HINTS ${_gss_root_hints} PATH_SUFFIXES "include") + + if(_gss_INCLUDE_DIRS) + set(_gss_flavor "GNU") + set(_gss_pc_requires ${_gnu_modname}) + set(_gss_libname "gss") endif() endif() - else() - find_path(_gss_INCLUDE_DIRS NAMES "gss.h" HINTS ${_gss_root_hints} PATH_SUFFIXES "include") - if(_gss_INCLUDE_DIRS) - set(_gss_flavor "GNU") - set(_gss_pc_requires ${_gnu_modname}) - set(_gss_libname "gss") + # If we have headers, look up libraries + if(_gss_flavor) + set(_gss_libdir_hints ${_gss_root_hints}) + if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.20) + cmake_path(GET _gss_INCLUDE_DIRS PARENT_PATH _gss_calculated_potential_root) + else() + get_filename_component(_gss_calculated_potential_root "${_gss_INCLUDE_DIRS}" DIRECTORY) + endif() + list(APPEND _gss_libdir_hints ${_gss_calculated_potential_root}) + + find_library(_gss_LIBRARIES NAMES ${_gss_libname} HINTS ${_gss_libdir_hints} PATH_SUFFIXES ${_gss_libdir_suffixes}) endif() endif() - - # If we have headers, look up libraries - if(_gss_flavor) - set(_gss_libdir_hints ${_gss_root_hints}) - if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.20) - cmake_path(GET _gss_INCLUDE_DIRS PARENT_PATH _gss_calculated_potential_root) - else() - get_filename_component(_gss_calculated_potential_root "${_gss_INCLUDE_DIRS}" DIRECTORY) - endif() - list(APPEND _gss_libdir_hints ${_gss_calculated_potential_root}) - - find_library(_gss_LIBRARIES NAMES ${_gss_libname} HINTS ${_gss_libdir_hints} PATH_SUFFIXES ${_gss_libdir_suffixes}) - endif() endif() if(NOT _gss_flavor) - message(FATAL_ERROR "GNU or MIT GSS is required") + message(FATAL_ERROR "MIT, GNU or Apple GSS is required") endif() else() if(_gss_MODULE_NAME STREQUAL _gnu_modname) @@ -199,7 +217,7 @@ else() set(_gss_flavor "MIT") set(_gss_pc_requires ${_mit_modname}) else() - message(FATAL_ERROR "GNU or MIT GSS is required") + message(FATAL_ERROR "MIT, GNU or Apple GSS is required") endif() message(STATUS "Found GSS/${_gss_flavor} (via pkg-config): ${_gss_INCLUDE_DIRS} (found version \"${_gss_version}\")") endif() diff --git a/Utilities/cmcurl/CMake/FindLibgsasl.cmake b/Utilities/cmcurl/CMake/FindLibgsasl.cmake index 7fbaa7128f..25bae07503 100644 --- a/Utilities/cmcurl/CMake/FindLibgsasl.cmake +++ b/Utilities/cmcurl/CMake/FindLibgsasl.cmake @@ -46,6 +46,7 @@ endif() if(_libgsasl_FOUND) set(Libgsasl_FOUND TRUE) set(LIBGSASL_FOUND TRUE) + set(LIBGSASL_VERSION ${_libgsasl_VERSION}) message(STATUS "Found Libgsasl (via pkg-config): ${_libgsasl_INCLUDE_DIRS} (found version \"${LIBGSASL_VERSION}\")") else() find_path(LIBGSASL_INCLUDE_DIR NAMES "gsasl.h") diff --git a/Utilities/cmcurl/CMake/FindNettle.cmake b/Utilities/cmcurl/CMake/FindNettle.cmake index c963180cba..f14cf23886 100644 --- a/Utilities/cmcurl/CMake/FindNettle.cmake +++ b/Utilities/cmcurl/CMake/FindNettle.cmake @@ -25,19 +25,21 @@ # # Input variables: # -# - `NETTLE_INCLUDE_DIR`: Absolute path to nettle include directory. -# - `NETTLE_LIBRARY`: Absolute path to `nettle` library. +# - `NETTLE_INCLUDE_DIR`: Absolute path to nettle include directory. +# - `NETTLE_HOGWEED_LIBRARY`: Absolute path to `hogweed` library. +# - `NETTLE_LIBRARY`: Absolute path to `nettle` library. # # Defines: # -# - `NETTLE_FOUND`: System has nettle. -# - `NETTLE_VERSION`: Version of nettle. -# - `CURL::nettle`: nettle library target. +# - `NETTLE_FOUND`: System has nettle. +# - `NETTLE_VERSION`: Version of nettle. +# - `CURL::nettle`: nettle library target. -set(_nettle_pc_requires "nettle") +set(_nettle_pc_requires "hogweed" "nettle") if(CURL_USE_PKGCONFIG AND NOT DEFINED NETTLE_INCLUDE_DIR AND + NOT DEFINED NETTLE_HOGWEED_LIBRARY AND NOT DEFINED NETTLE_LIBRARY) find_package(PkgConfig QUIET) pkg_check_modules(_nettle ${_nettle_pc_requires}) @@ -46,10 +48,11 @@ endif() if(_nettle_FOUND) set(Nettle_FOUND TRUE) set(NETTLE_FOUND TRUE) - set(NETTLE_VERSION ${_nettle_VERSION}) + set(NETTLE_VERSION ${_nettle_nettle_VERSION}) message(STATUS "Found Nettle (via pkg-config): ${_nettle_INCLUDE_DIRS} (found version \"${NETTLE_VERSION}\")") else() find_path(NETTLE_INCLUDE_DIR NAMES "nettle/sha2.h") + find_library(NETTLE_HOGWEED_LIBRARY NAMES "hogweed") find_library(NETTLE_LIBRARY NAMES "nettle") unset(NETTLE_VERSION CACHE) @@ -71,6 +74,7 @@ else() find_package_handle_standard_args(Nettle REQUIRED_VARS NETTLE_INCLUDE_DIR + NETTLE_HOGWEED_LIBRARY NETTLE_LIBRARY VERSION_VAR NETTLE_VERSION @@ -78,10 +82,10 @@ else() if(NETTLE_FOUND) set(_nettle_INCLUDE_DIRS ${NETTLE_INCLUDE_DIR}) - set(_nettle_LIBRARIES ${NETTLE_LIBRARY}) + set(_nettle_LIBRARIES ${NETTLE_HOGWEED_LIBRARY} ${NETTLE_LIBRARY}) endif() - mark_as_advanced(NETTLE_INCLUDE_DIR NETTLE_LIBRARY) + mark_as_advanced(NETTLE_INCLUDE_DIR NETTLE_HOGWEED_LIBRARY NETTLE_LIBRARY) endif() if(NETTLE_FOUND) diff --git a/Utilities/cmcurl/CMake/FindZstd.cmake b/Utilities/cmcurl/CMake/FindZstd.cmake index 176645d97b..5a2db76bb1 100644 --- a/Utilities/cmcurl/CMake/FindZstd.cmake +++ b/Utilities/cmcurl/CMake/FindZstd.cmake @@ -54,7 +54,7 @@ if(NOT DEFINED ZSTD_INCLUDE_DIR AND endif() if(NOT _zstd_FOUND AND CURL_USE_CMAKECONFIG) find_package(zstd CONFIG QUIET) - # Skip using if older than v1.4.5 + # Only use if 1.4.5 or greater if(zstd_CONFIG AND NOT TARGET zstd::libzstd_static AND NOT TARGET zstd::libzstd_shared) @@ -81,7 +81,7 @@ elseif(zstd_CONFIG) if(ZSTD_USE_STATIC_LIBS) set(_zstd_LIBRARIES zstd::libzstd_static) elseif(TARGET zstd::libzstd) - set(_zstd_LIBRARIES zstd::libzstd) # v1.5.6+ + set(_zstd_LIBRARIES zstd::libzstd) # 1.5.6+ else() set(_zstd_LIBRARIES zstd::libzstd_shared) endif() diff --git a/Utilities/cmcurl/CMake/Macros.cmake b/Utilities/cmcurl/CMake/Macros.cmake index 953c1c6ee6..503074a0a0 100644 --- a/Utilities/cmcurl/CMake/Macros.cmake +++ b/Utilities/cmcurl/CMake/Macros.cmake @@ -43,7 +43,7 @@ macro(curl_internal_test _curl_test) message(STATUS "Performing Test ${_curl_test}") try_compile(${_curl_test} ${PROJECT_BINARY_DIR} - "${CMAKE_CURRENT_SOURCE_DIR}/CMake/CurlTests.c" + "${PROJECT_SOURCE_DIR}/CMake/CurlTests.c" COMPILE_DEFINITIONS "-D${_curl_test}" ${CURL_TEST_DEFINES} ${CMAKE_REQUIRED_FLAGS} ${CMAKE_REQUIRED_DEFINITIONS} LINK_LIBRARIES "${CMAKE_REQUIRED_LIBRARIES}" OUTPUT_VARIABLE CURL_TEST_OUTPUT) @@ -255,7 +255,7 @@ macro(curl_collect_target_link_options _target) get_target_property(_val ${_target} IMPORTED) if(_val) # LOCATION is empty for interface library targets and safe to ignore. - # Explicitly skip this query to avoid CMake v3.18 and older erroring out. + # Explicitly skip this query to avoid CMake 3.18 and older erroring out. get_target_property(_val ${_target} TYPE) if(NOT "${_val}" STREQUAL "INTERFACE_LIBRARY") get_target_property(_val ${_target} LOCATION) diff --git a/Utilities/cmcurl/CMake/OtherTests.cmake b/Utilities/cmcurl/CMake/OtherTests.cmake index 6619f3ab3e..b0727d32e4 100644 --- a/Utilities/cmcurl/CMake/OtherTests.cmake +++ b/Utilities/cmcurl/CMake/OtherTests.cmake @@ -77,12 +77,14 @@ if(WIN32) set(HAVE_GETADDRINFO_THREADSAFE ${HAVE_GETADDRINFO}) elseif(NOT HAVE_GETADDRINFO) set(HAVE_GETADDRINFO_THREADSAFE FALSE) -elseif(APPLE OR - AIX OR CMAKE_SYSTEM_NAME STREQUAL "AIX" OR - CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR - CMAKE_SYSTEM_NAME STREQUAL "HP-UX" OR - CMAKE_SYSTEM_NAME STREQUAL "MidnightBSD" OR - CMAKE_SYSTEM_NAME STREQUAL "NetBSD" OR +elseif(APPLE OR # Darwin 9+ / macOS 10.5+ + AIX OR CMAKE_SYSTEM_NAME STREQUAL "AIX" OR # 5.2+ + CMAKE_SYSTEM_NAME STREQUAL "DragonFlyBSD" OR # 2.2.0+ + CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR # 5.5+ + CMAKE_SYSTEM_NAME STREQUAL "HP-UX" OR # 11.11+ + CMAKE_SYSTEM_NAME STREQUAL "MidnightBSD" OR # all versions + CMAKE_SYSTEM_NAME STREQUAL "NetBSD" OR # 4+ + CMAKE_SYSTEM_NAME STREQUAL "OpenBSD" OR # 5.4+ CMAKE_SYSTEM_NAME STREQUAL "SunOS") set(HAVE_GETADDRINFO_THREADSAFE TRUE) elseif(BSD OR CMAKE_SYSTEM_NAME MATCHES "BSD") diff --git a/Utilities/cmcurl/CMake/PickyWarnings.cmake b/Utilities/cmcurl/CMake/PickyWarnings.cmake index 1ce5f7bda6..e412cd351d 100644 --- a/Utilities/cmcurl/CMake/PickyWarnings.cmake +++ b/Utilities/cmcurl/CMake/PickyWarnings.cmake @@ -97,7 +97,7 @@ if(PICKY_COMPILER) # to suppress undesired warnings in case -Weverything is passed as a custom option. # Assume these options always exist with both clang and gcc. - # Require clang 3.0 / gcc 2.95 or later. + # Require clang 3.0 / gcc 2.95 or greater. list(APPEND _picky_enable -Wbad-function-cast # clang 2.7 gcc 2.95 -Wconversion # clang 2.7 gcc 2.95 @@ -215,7 +215,7 @@ if(PICKY_COMPILER) if((CMAKE_C_COMPILER_ID STREQUAL "Clang" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 10.0) OR (CMAKE_C_COMPILER_ID STREQUAL "AppleClang" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 12)) list(APPEND _picky_enable - -Wimplicit-fallthrough # clang 4.0 gcc 7.0 appleclang 9.0 # We do silencing for clang 10.0 and above only + -Wimplicit-fallthrough # clang 4.0 gcc 7.0 appleclang 9.0 # We do silencing for clang 10.0+ only -Wxor-used-as-pow # clang 10.0 gcc 13.0 appleclang 12.0 ) endif() @@ -451,6 +451,9 @@ if(_picky_nocheck OR _picky) string(REPLACE ";" " " _picky_tmp "${_picky_tmp}") string(STRIP "${_picky_tmp}" _picky_tmp) message(STATUS "Picky compiler options: ${_picky_tmp}") + if(_CURL_SAVE_PICKY_OPTIONS) + file(WRITE "${PROJECT_BINARY_DIR}/picky-options.txt" "${_picky_tmp}") + endif() set_property(DIRECTORY APPEND PROPERTY COMPILE_OPTIONS "${_picky_nocheck}" "${_picky}") # Apply to all feature checks diff --git a/Utilities/cmcurl/CMake/Utilities.cmake b/Utilities/cmcurl/CMake/Utilities.cmake index f86a6aa1e8..319160b0a5 100644 --- a/Utilities/cmcurl/CMake/Utilities.cmake +++ b/Utilities/cmcurl/CMake/Utilities.cmake @@ -37,24 +37,30 @@ endfunction() # Dump all defined variables with their values function(curl_dumpvars) message("::group::CMake Variable Dump") - get_cmake_property(_vars VARIABLES) - foreach(_var IN ITEMS ${_vars}) - get_property(_var_type CACHE ${_var} PROPERTY TYPE) - get_property(_var_advanced CACHE ${_var} PROPERTY ADVANCED) - if(_var_type) - set(_var_type ":${_var_type}") - endif() - if(_var_advanced) - set(_var_advanced " [adv]") - endif() - message("${_var}${_var_type}${_var_advanced} = '${${_var}}'") - endforeach() + if(CMAKE_VERSION VERSION_GREATER_EQUAL 4.5) + cmake_language(PRINT_VARIABLES ALL) + else() + get_cmake_property(_vars VARIABLES) + foreach(_var IN ITEMS ${_vars}) + get_property(_var_type CACHE ${_var} PROPERTY TYPE) + get_property(_var_advanced CACHE ${_var} PROPERTY ADVANCED) + if(_var_type) + set(_var_type ":${_var_type}") + endif() + if(_var_advanced) + set(_var_advanced " [adv]") + endif() + message(" ${_var}${_var_type}${_var_advanced} = \"${${_var}}\"") + endforeach() + endif() message("::endgroup::") endfunction() # Dump all target properties function(curl_dumptargetprops _target) - if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.19 AND TARGET "${_target}") + if(CMAKE_VERSION VERSION_GREATER_EQUAL 4.5) + cmake_language(PRINT_PROPERTIES TARGETS "${_target}" ALL) + elseif(CMAKE_VERSION VERSION_GREATER_EQUAL 3.19 AND TARGET "${_target}") execute_process(COMMAND "${CMAKE_COMMAND}" "--help-property-list" OUTPUT_VARIABLE _cmake_property_list) string(REPLACE "\n" ";" _cmake_property_list "${_cmake_property_list}") list(REMOVE_DUPLICATES _cmake_property_list) @@ -64,21 +70,22 @@ function(curl_dumptargetprops _target) if(NOT _target_imported) list(REMOVE_ITEM _cmake_property_list "LOCATION" "LOCATION_" "MACOSX_PACKAGE_LOCATION" "VS_DEPLOYMENT_LOCATION") endif() + list(SORT _cmake_property_list) foreach(_prop IN LISTS _cmake_property_list) if(_prop MATCHES "") - foreach(_config IN ITEMS "DEBUG" "RELEASE" "MINSIZEREL" "RELWITHDEBINFO") + foreach(_config IN ITEMS "DEBUG" "MINSIZEREL" "RELEASE" "RELWITHDEBINFO") string(REPLACE "" "${_config}" _propconfig "${_prop}") get_property(_is_set TARGET "${_target}" PROPERTY "${_propconfig}" SET) if(_is_set) get_target_property(_val "${_target}" "${_propconfig}") - message("${_target}.${_propconfig} = '${_val}'") + message(" ${_target}.${_propconfig} = \"${_val}\"") endif() endforeach() else() get_property(_is_set TARGET "${_target}" PROPERTY "${_prop}" SET) if(_is_set) get_target_property(_val "${_target}" "${_prop}") - message("${_target}.${_prop} = '${_val}'") + message(" ${_target}.${_prop} = \"${_val}\"") endif() endif() endforeach() diff --git a/Utilities/cmcurl/CMake/cmake_uninstall.in.cmake b/Utilities/cmcurl/CMake/cmake_uninstall.in.cmake index e4f3eae34c..30e812b124 100644 --- a/Utilities/cmcurl/CMake/cmake_uninstall.in.cmake +++ b/Utilities/cmcurl/CMake/cmake_uninstall.in.cmake @@ -21,30 +21,28 @@ # SPDX-License-Identifier: curl # ########################################################################### -if(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") - message(FATAL_ERROR "Cannot find install manifest: @CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") +set(_manifest "@PROJECT_BINARY_DIR@/install_manifest.txt") +if(NOT EXISTS "${_manifest}") + message(FATAL_ERROR "Cannot find install manifest: ${_manifest}") endif() -if(NOT DEFINED CMAKE_INSTALL_PREFIX) - set(CMAKE_INSTALL_PREFIX "@CMAKE_INSTALL_PREFIX@") +set(_destdir "$ENV{DESTDIR}") +if(NOT _destdir STREQUAL "") + message(STATUS "DESTDIR environment: ${_destdir}") endif() -message(${CMAKE_INSTALL_PREFIX}) -file(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" _files) +file(READ "${_manifest}" _files) string(REGEX REPLACE "\n" ";" _files "${_files}") -foreach(_file ${_files}) - message(STATUS "Uninstalling $ENV{DESTDIR}${_file}") - if(IS_SYMLINK "$ENV{DESTDIR}${_file}" OR EXISTS "$ENV{DESTDIR}${_file}") - execute_process( - COMMAND "@CMAKE_COMMAND@" -E rm -f "$ENV{DESTDIR}${_file}" - RESULT_VARIABLE rm_retval - OUTPUT_QUIET - ERROR_QUIET - ) - if(NOT "${rm_retval}" STREQUAL 0) - message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${_file}") +foreach(_file IN LISTS _files) + set(_target "${_destdir}${_file}") + if(IS_SYMLINK "${_target}" OR EXISTS "${_target}") + file(REMOVE "${_target}") + if(IS_SYMLINK "${_target}" OR EXISTS "${_target}") + message(STATUS "Failed to delete: ${_target}") + else() + message(STATUS "Uninstalled: ${_target}") endif() else() - message(STATUS "File $ENV{DESTDIR}${_file} does not exist.") + message(STATUS "File does not exist: ${_target}") endif() endforeach() diff --git a/Utilities/cmcurl/CMake/curl-config.in.cmake b/Utilities/cmcurl/CMake/curl-config.in.cmake index 2dcf88c1d3..f554a6fc0b 100644 --- a/Utilities/cmcurl/CMake/curl-config.in.cmake +++ b/Utilities/cmcurl/CMake/curl-config.in.cmake @@ -30,7 +30,7 @@ option(CURL_USE_PKGCONFIG "Enable pkg-config to detect @PROJECT_NAME@ dependenci if(CMAKE_VERSION VERSION_LESS @CMAKE_MINIMUM_REQUIRED_VERSION@) message(STATUS "@PROJECT_NAME@: @PROJECT_NAME@-specific Find modules require " - "CMake @CMAKE_MINIMUM_REQUIRED_VERSION@ or upper, found: ${CMAKE_VERSION}.") + "CMake @CMAKE_MINIMUM_REQUIRED_VERSION@ or greater, found: ${CMAKE_VERSION}.") endif() include(CMakeFindDependencyMacro) @@ -74,7 +74,7 @@ if("@USE_ARES@") list(APPEND _curl_libs CURL::cares) endif() if("@HAVE_GSSAPI@") - find_dependency(GSS MODULE) + find_dependency(GSS MODULE COMPONENTS "@GSS_FLAVOR@") list(APPEND _curl_libs CURL::gss) endif() if("@USE_BACKTRACE@") diff --git a/Utilities/cmcurl/CMake/unix-cache.cmake b/Utilities/cmcurl/CMake/unix-cache.cmake index cf7a6e9f1c..813758a32c 100644 --- a/Utilities/cmcurl/CMake/unix-cache.cmake +++ b/Utilities/cmcurl/CMake/unix-cache.cmake @@ -75,9 +75,9 @@ if(ANDROID AND ANDROID_PLATFORM_LEVEL GREATER_EQUAL 34) set(HAVE_MEMSET_EXPLICIT 1) endif() if((APPLE AND CMAKE_OSX_DEPLOYMENT_TARGET VERSION_GREATER_EQUAL 10.9) OR - CMAKE_SYSTEM_NAME STREQUAL "DragonFlyBSD" OR # v6+ - CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR # v11.2+ - CMAKE_SYSTEM_NAME STREQUAL "MidnightBSD") # v1.3+ + CMAKE_SYSTEM_NAME STREQUAL "DragonFlyBSD" OR # 6+ + CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR # 11.2+ + CMAKE_SYSTEM_NAME STREQUAL "MidnightBSD") # 1.3+ set(HAVE_MEMSET_S 1) elseif(NOT APPLE) set(HAVE_MEMSET_S 0) @@ -108,14 +108,7 @@ elseif(CYGWIN OR set(HAVE_FSETXATTR_6 0) endif() set(HAVE_GETADDRINFO 1) -if(CMAKE_SYSTEM_NAME STREQUAL "OpenBSD") - set(HAVE_GETADDRINFO_THREADSAFE 0) -elseif(CYGWIN OR - CMAKE_SYSTEM_NAME STREQUAL "Linux" OR - CMAKE_SYSTEM_NAME STREQUAL "DragonFlyBSD" OR - CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR - CMAKE_SYSTEM_NAME STREQUAL "MidnightBSD" OR - CMAKE_SYSTEM_NAME STREQUAL "NetBSD") +if(CYGWIN) set(HAVE_GETADDRINFO_THREADSAFE 1) endif() set(HAVE_GETEUID 1) @@ -136,6 +129,7 @@ set(HAVE_GETHOSTBYNAME_R_5 0) set(HAVE_GETHOSTBYNAME_R_5_REENTRANT 0) if(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR CMAKE_SYSTEM_NAME STREQUAL "DragonFlyBSD" OR + CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR CMAKE_SYSTEM_NAME STREQUAL "MidnightBSD") set(HAVE_GETHOSTBYNAME_R_6 1) set(HAVE_GETHOSTBYNAME_R_6_REENTRANT 1) @@ -175,8 +169,6 @@ endif() set(HAVE_GMTIME_R 1) set(HAVE_IFADDRS_H 1) set(HAVE_IF_NAMETOINDEX 1) -set(HAVE_INET_NTOP 1) -set(HAVE_INET_PTON 1) set(HAVE_IOCTLSOCKET 0) set(HAVE_IOCTLSOCKET_CAMEL 0) set(HAVE_IOCTLSOCKET_CAMEL_FIONBIO 0) @@ -214,6 +206,7 @@ endif() if(0) # XXX(cmake): cache this result for nghttp2 set(HAVE_NETINET_IN_H 1) endif() # XXX(cmake): end +set(HAVE_NETINET_IP_H 1) set(HAVE_NETINET_TCP_H 1) set(HAVE_NETINET_UDP_H 1) set(HAVE_NET_IF_H 1) @@ -223,12 +216,7 @@ if(APPLE OR CYGWIN) set(HAVE_PIPE2 0) elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR - BSD OR - CMAKE_SYSTEM_NAME STREQUAL "DragonFlyBSD" OR - CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR - CMAKE_SYSTEM_NAME STREQUAL "MidnightBSD" OR - CMAKE_SYSTEM_NAME STREQUAL "NetBSD" OR - CMAKE_SYSTEM_NAME STREQUAL "OpenBSD" OR + BSD OR CMAKE_SYSTEM_NAME MATCHES "BSD" OR CMAKE_SYSTEM_NAME STREQUAL "SunOS") set(HAVE_PIPE2 1) endif() @@ -244,7 +232,6 @@ set(HAVE_REALPATH 1) set(HAVE_RECV 1) set(HAVE_SA_FAMILY_T 1) set(HAVE_SCHED_YIELD 1) -set(HAVE_SELECT 1) set(HAVE_SEND 1) if(APPLE OR CYGWIN OR @@ -277,7 +264,7 @@ set(HAVE_STRINGS_H 1) if(_CURL_OLD_LINUX) set(HAVE_STROPTS_H 1) else() - set(HAVE_STROPTS_H 0) # glibc 2.30 or newer. https://sourceware.org/legacy-ml/libc-alpha/2019-08/msg00029.html + set(HAVE_STROPTS_H 0) # glibc 2.30 or greater. https://sourceware.org/legacy-ml/libc-alpha/2019-08/msg00029.html endif() set(HAVE_STRUCT_SOCKADDR_STORAGE 1) set(HAVE_STRUCT_TIMEVAL 1) @@ -332,5 +319,4 @@ set(HAVE_UTIME 1) set(HAVE_UTIMES 1) set(HAVE_UTIME_H 1) set(HAVE_WRITABLE_ARGV 1) -set(STDC_HEADERS 1) set(USE_UNIX_SOCKETS 1) diff --git a/Utilities/cmcurl/CMake/win32-cache.cmake b/Utilities/cmcurl/CMake/win32-cache.cmake index bd9bb6e3fc..2d8506eb69 100644 --- a/Utilities/cmcurl/CMake/win32-cache.cmake +++ b/Utilities/cmcurl/CMake/win32-cache.cmake @@ -28,10 +28,7 @@ endif() if(MINGW) set(HAVE_BASENAME 1) set(HAVE_BOOL_T 1) # = HAVE_STDBOOL_H - set(HAVE_DIRENT_H 1) - set(HAVE_GETTIMEOFDAY 1) set(HAVE_LIBGEN_H 1) - set(HAVE_OPENDIR 1) set(HAVE_STDBOOL_H 1) set(HAVE_STDDEF_H 1) # detected by CMake internally in check_type_size() set(HAVE_STDINT_H 1) # detected by CMake internally in check_type_size() @@ -40,24 +37,22 @@ if(MINGW) set(HAVE_UNISTD_H 1) set(HAVE_UTIME_H 1) # wrapper to sys/utime.h else() - set(HAVE_DIRENT_H 0) - set(HAVE_GETTIMEOFDAY 0) set(HAVE_LIBGEN_H 0) - set(HAVE_OPENDIR 0) set(HAVE_STRINGS_H 0) set(HAVE_SYS_PARAM_H 0) set(HAVE_UTIME_H 0) if(MSVC) - set(HAVE_UNISTD_H 0) + set(HAVE_BASENAME 0) set(HAVE_STDDEF_H 1) # detected by CMake internally in check_type_size() set(HAVE_STDINT_H 1) # detected by CMake internally in check_type_size() if(MSVC_VERSION GREATER_EQUAL 1800) + set(HAVE_BOOL_T 1) # = HAVE_STDBOOL_H set(HAVE_STDBOOL_H 1) else() + set(HAVE_BOOL_T 0) # = HAVE_STDBOOL_H set(HAVE_STDBOOL_H 0) endif() - set(HAVE_BOOL_T "${HAVE_STDBOOL_H}") - set(HAVE_BASENAME 0) + set(HAVE_UNISTD_H 0) endif() endif() @@ -104,8 +99,6 @@ set(HAVE_GETSOCKNAME 1) set(HAVE_GLIBC_STRERROR_R 0) set(HAVE_GMTIME_R 0) set(HAVE_IFADDRS_H 0) -set(HAVE_INET_NTOP 0) -set(HAVE_INET_PTON 0) set(HAVE_IOCTLSOCKET 1) set(HAVE_IOCTLSOCKET_CAMEL 0) set(HAVE_IOCTLSOCKET_CAMEL_FIONBIO 0) @@ -120,6 +113,7 @@ set(HAVE_MEMRCHR 0) set(HAVE_NETDB_H 0) set(HAVE_NETINET_IN6_H 0) set(HAVE_NETINET_IN_H 0) +set(HAVE_NETINET_IP_H 0) set(HAVE_NETINET_TCP_H 0) set(HAVE_NETINET_UDP_H 0) set(HAVE_NET_IF_H 0) @@ -130,7 +124,6 @@ set(HAVE_POLL_H 0) set(HAVE_POSIX_STRERROR_R 0) set(HAVE_PWD_H 0) set(HAVE_RECV 1) -set(HAVE_SELECT 1) set(HAVE_SEND 1) set(HAVE_SENDMMSG 0) set(HAVE_SENDMSG 0) @@ -163,7 +156,6 @@ set(HAVE_TERMIO_H 0) set(HAVE_TIME_T_UNSIGNED 0) set(HAVE_UTIME 1) set(HAVE_UTIMES 0) -set(STDC_HEADERS 1) # Types and sizes diff --git a/Utilities/cmcurl/CMakeLists.txt b/Utilities/cmcurl/CMakeLists.txt index 428d9b5ade..da4c396376 100644 --- a/Utilities/cmcurl/CMakeLists.txt +++ b/Utilities/cmcurl/CMakeLists.txt @@ -230,13 +230,14 @@ set(CMAKE_PROJECT_VERSION_PATCH 0) set(CMAKE_PROJECT_VERSION_TWEAK 0) string(REGEX REPLACE "([0-9]+\.[0-9]+\.[0-9]+).+" "\\1" _curl_version_sem "${_curl_version}") +# Initializes PROJECT_BINARY_DIR, PROJECT_SOURCE_DIR project(CURL VERSION "${_curl_version_sem}" LANGUAGES C) # CMake does not recognize some targets accurately. Touch up configuration manually as a workaround. if(WINDOWS_STORE AND MINGW) # MinGW UWP build - # CMake (as of v3.31.2) gets confused and applies the MSVC rc.exe command-line + # CMake (as of 3.31.2) gets confused and applies the MSVC rc.exe command-line # template to windres. Reset it to the windres template as in 'Modules/Platform/Windows-windres.cmake': set(CMAKE_RC_COMPILE_OBJECT " -O coff ") elseif(DOS AND CMAKE_C_COMPILER_ID STREQUAL "GNU") # DJGPP @@ -258,7 +259,7 @@ endif() if(UNIX) string(APPEND _target_flags " UNIX") endif() -if(BSD) +if(BSD OR CMAKE_SYSTEM_NAME MATCHES "BSD") string(APPEND _target_flags " BSD") endif() if(ANDROID) @@ -402,7 +403,7 @@ if(WIN32) if(MINGW64_VERSION) message(STATUS "Found MINGW64_VERSION=${MINGW64_VERSION}") if(MINGW64_VERSION VERSION_LESS 3.0) - message(FATAL_ERROR "mingw-w64 3.0 or upper is required") + message(FATAL_ERROR "mingw-w64 3.0 or greater required") endif() endif() endif() @@ -431,8 +432,11 @@ if(CYGWIN OR CMAKE_SYSTEM_NAME STREQUAL "Linux" OR CMAKE_SYSTEM_NAME STREQUAL "G list(APPEND CMAKE_REQUIRED_DEFINITIONS "-D_GNU_SOURCE") # Apply to all feature checks endif() -option(ENABLE_DEBUG "Enable curl debug features (for developing curl itself)" OFF) +option(ENABLE_DEBUG "Enable curl debug features (for developing curl)" OFF) if(ENABLE_DEBUG) + if(CMAKE_BUILD_TYPE AND NOT CMAKE_BUILD_TYPE STREQUAL "Debug") + message(FATAL_ERROR "Debug-enabled (aka development mode) curl requires the Debug configuration") + endif() message(WARNING "This curl build is Debug-enabled and insecure, do not use in production.") endif() @@ -599,8 +603,6 @@ mark_as_advanced(CURL_ENABLE_EXPORT_TARGET) option(CURL_DISABLE_ALTSVC "Disable alt-svc support" OFF) mark_as_advanced(CURL_DISABLE_ALTSVC) -option(CURL_DISABLE_SRP "Disable TLS-SRP support" OFF) -mark_as_advanced(CURL_DISABLE_SRP) option(CURL_DISABLE_COOKIES "Disable cookies support" OFF) mark_as_advanced(CURL_DISABLE_COOKIES) option(CURL_DISABLE_BASIC_AUTH "Disable Basic authentication" OFF) @@ -635,6 +637,8 @@ option(CURL_DISABLE_HTTP "Disable HTTP" OFF) mark_as_advanced(CURL_DISABLE_HTTP) option(CURL_DISABLE_HTTP_AUTH "Disable all HTTP authentication methods" OFF) mark_as_advanced(CURL_DISABLE_HTTP_AUTH) +option(CURL_DISABLE_HTTPSIG "Disable HTTP Message Signatures (RFC 9421) (experimental)" ON) +mark_as_advanced(CURL_DISABLE_HTTPSIG) option(CURL_DISABLE_IMAP "Disable IMAP" OFF) mark_as_advanced(CURL_DISABLE_IMAP) option(CURL_DISABLE_LDAP "Disable LDAP" OFF) @@ -801,13 +805,13 @@ include(CheckSymbolExists) include(CheckTypeSize) include(CheckCSourceCompiles) -option(_CURL_PREFILL "Fast-track known feature detection results (Windows, some Apple)" "${WIN32}") +option(_CURL_PREFILL "Pre-fill known feature detection results (Windows, some Apple)" "${WIN32}") mark_as_advanced(_CURL_PREFILL) if(_CURL_PREFILL) if(WIN32) - include("${CMAKE_CURRENT_SOURCE_DIR}/CMake/win32-cache.cmake") + include("${PROJECT_SOURCE_DIR}/CMake/win32-cache.cmake") elseif(UNIX) - include("${CMAKE_CURRENT_SOURCE_DIR}/CMake/unix-cache.cmake") + include("${PROJECT_SOURCE_DIR}/CMake/unix-cache.cmake") if(0) # XXX(cmake): not needed for build within cmake message(STATUS "Pre-filling feature detection results for UNIX") endif() # XXX(cmake): end @@ -993,6 +997,14 @@ else() set(USE_APPLE_SECTRUST OFF) endif() +set(USE_APPLE_FAST_UDP 0) +if(APPLE) + option(CURL_ENABLE_APPLE_FAST_UDP "Use Apple fast UDP (experimental)" OFF) + if(CURL_ENABLE_APPLE_FAST_UDP) + set(USE_APPLE_FAST_UDP 1) + endif() +endif() + if(_use_core_foundation_and_core_services) find_library(COREFOUNDATION_FRAMEWORK NAMES "CoreFoundation") mark_as_advanced(COREFOUNDATION_FRAMEWORK) @@ -1033,10 +1045,10 @@ if(CURL_USE_OPENSSL) if(NOT DEFINED HAVE_AWSLC) check_symbol_exists("OPENSSL_IS_AWSLC" "openssl/base.h" HAVE_AWSLC) endif() - if(NOT DEFINED HAVE_BORINGSSL) + if(NOT DEFINED HAVE_BORINGSSL AND NOT HAVE_AWSLC) check_symbol_exists("OPENSSL_IS_BORINGSSL" "openssl/base.h" HAVE_BORINGSSL) endif() - if(NOT DEFINED HAVE_LIBRESSL) + if(NOT DEFINED HAVE_LIBRESSL AND NOT HAVE_AWSLC AND NOT HAVE_BORINGSSL) check_symbol_exists("LIBRESSL_VERSION_NUMBER" "openssl/opensslv.h" HAVE_LIBRESSL) endif() cmake_pop_check_state() @@ -1097,7 +1109,7 @@ endif() if(CURL_USE_MBEDTLS) find_package(MbedTLS MODULE REQUIRED) if(MBEDTLS_VERSION VERSION_LESS 3.2.0) - message(FATAL_ERROR "mbedTLS v3.2.0 or newer is required.") + message(FATAL_ERROR "mbedTLS 3.2.0 or greater required") endif() set(_ssl_enabled ON) set(USE_MBEDTLS ON) @@ -1131,7 +1143,7 @@ if(CURL_USE_WOLFSSL) set(_curl_ca_bundle_supported TRUE) if(USE_OPENSSL AND WOLFSSL_VERSION VERSION_LESS 5.7.6) - message(FATAL_ERROR "wolfSSL 5.7.6 or newer is required to coexist with OpenSSL.") + message(FATAL_ERROR "wolfSSL 5.7.6 or greater required to coexist with OpenSSL") endif() set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS "WOLFSSL_OPTIONS_IGNORE_SYS") @@ -1149,15 +1161,6 @@ if(CURL_USE_GNUTLS) set(_valid_default_ssl_backend TRUE) endif() set(_curl_ca_bundle_supported TRUE) - - if(NOT DEFINED HAVE_GNUTLS_SRP AND NOT CURL_DISABLE_SRP) - cmake_push_check_state() - list(APPEND CMAKE_REQUIRED_LIBRARIES CURL::gnutls) - # In GnuTLS 3.8.0 (2023-02-10) and upper, this check always succeeds. - # Detecting actual TLS-SRP support needs poking the API at runtime. - check_symbol_exists("gnutls_srp_verifier" "gnutls/gnutls.h" HAVE_GNUTLS_SRP) - cmake_pop_check_state() - endif() endif() if(CURL_USE_RUSTLS) @@ -1177,7 +1180,7 @@ if(CURL_USE_RUSTLS) endif() endif() if(NOT HAVE_RUSTLS_SUPPORTED_HPKE) - message(FATAL_ERROR "rustls-ffi library does not provide rustls_supported_hpke function. Required version is 0.15 or newer.") + message(FATAL_ERROR "rustls-ffi library does not provide rustls_supported_hpke function. Required version is 0.15 or greater.") endif() if(CURL_DEFAULT_SSL_BACKEND AND CURL_DEFAULT_SSL_BACKEND STREQUAL "rustls") @@ -1220,7 +1223,7 @@ if(ZSTD_FOUND) set(HAVE_ZSTD ON) list(APPEND CURL_LIBS CURL::zstd) else() - message(WARNING "zstd v1.0.0 or newer is required, disabling zstd support.") + message(WARNING "zstd 1.0.0 or greater required, disabling zstd support") endif() endif() @@ -1229,7 +1232,6 @@ macro(curl_openssl_check_exists) cmake_push_check_state() if(USE_OPENSSL) list(APPEND CMAKE_REQUIRED_LIBRARIES OpenSSL::SSL OpenSSL::Crypto) - list(APPEND CMAKE_REQUIRED_DEFINITIONS "-DOPENSSL_SUPPRESS_DEPRECATED") # for SSL_CTX_set_srp_username deprecated since 3.0.0 if(HAVE_LIBZ) if(CMAKE_USE_SYSTEM_ZLIB) list(APPEND CMAKE_REQUIRED_LIBRARIES ZLIB::ZLIB) @@ -1247,7 +1249,7 @@ macro(curl_openssl_check_exists) if(HAVE_LIBZ) list(APPEND CMAKE_REQUIRED_LIBRARIES ZLIB::ZLIB) # Public wolfSSL headers also require zlib headers endif() - list(APPEND CMAKE_REQUIRED_DEFINITIONS "-DHAVE_UINTPTR_T") # to pull in stdint.h (as of wolfSSL v5.5.4) + list(APPEND CMAKE_REQUIRED_DEFINITIONS "-DHAVE_UINTPTR_T") # to pull in stdint.h (as of wolfSSL 5.5.4) endif() if(WIN32) list(APPEND CMAKE_REQUIRED_LIBRARIES "ws2_32" "crypt32") # for OpenSSL/wolfSSL @@ -1298,9 +1300,6 @@ if(USE_OPENSSL) if(NOT DEFINED HAVE_SSL_SET0_WBIO) curl_openssl_check_exists("SSL_set0_wbio" HAVE_SSL_SET0_WBIO) endif() - if(NOT DEFINED HAVE_OPENSSL_SRP AND NOT CURL_DISABLE_SRP) - curl_openssl_check_exists("SSL_CTX_set_srp_username" "openssl/ssl.h" HAVE_OPENSSL_SRP) - endif() endif() option(USE_HTTPSRR "Enable HTTPS RR support (experimental)" OFF) @@ -1332,6 +1331,11 @@ if(USE_ECH) endif() endif() +if(NOT CURL_DISABLE_HTTPSIG AND NOT USE_GNUTLS AND NOT USE_OPENSSL AND NOT USE_WOLFSSL) + message(WARNING "HTTPSIG requires GnuTLS, OpenSSL or wolfSSL. HTTPSIG support disabled.") + set(CURL_DISABLE_HTTPSIG ON) +endif() + option(USE_SSLS_EXPORT "Enable SSL session import/export (experimental)" OFF) if(USE_SSLS_EXPORT) if(_ssl_enabled) @@ -1365,7 +1369,7 @@ if(USE_NGTCP2) elseif(OPENSSL_VERSION VERSION_GREATER_EQUAL 3.5.0) find_package(NGTCP2 MODULE REQUIRED COMPONENTS "ossl") if(NGTCP2_VERSION VERSION_LESS 1.12.0) - message(FATAL_ERROR "ngtcp2 1.12.0 or upper required for OpenSSL") + message(FATAL_ERROR "ngtcp2 1.12.0 or greater required for OpenSSL") endif() set(OPENSSL_QUIC_API2 1) elseif(HAVE_LIBRESSL) @@ -1425,10 +1429,6 @@ if(USE_PROXY_HTTP3) endif() endif() -if(NOT CURL_DISABLE_SRP AND (HAVE_GNUTLS_SRP OR HAVE_OPENSSL_SRP)) - set(USE_TLS_SRP 1) -endif() - if(NOT CURL_DISABLE_LDAP) if(WIN32 AND NOT WINDOWS_STORE) option(USE_WIN32_LDAP "Use Windows LDAP implementation" ON) @@ -1558,6 +1558,9 @@ option(CURL_USE_GSASL "Use libgsasl" OFF) mark_as_advanced(CURL_USE_GSASL) if(CURL_USE_GSASL) find_package(Libgsasl MODULE REQUIRED) + if(LIBGSASL_VERSION VERSION_LESS 1.6.0) + message(FATAL_ERROR "libgsasl 1.6.0 or greater required") + endif() list(APPEND CURL_LIBS CURL::libgsasl) set(USE_GSASL ON) endif() @@ -1566,18 +1569,27 @@ option(CURL_USE_GSSAPI "Use GSSAPI implementation" OFF) mark_as_advanced(CURL_USE_GSSAPI) if(CURL_USE_GSSAPI) - find_package(GSS MODULE) + set(CURL_GSS_FLAVOR "" CACHE STRING "Set preferred GSSAPI implementation ('Apple'). Default: MIT, then GNU (experimental)") + + find_package(GSS MODULE COMPONENTS "${CURL_GSS_FLAVOR}") set(HAVE_GSSAPI ${GSS_FOUND}) if(GSS_FOUND) list(APPEND CURL_LIBS CURL::gss) - get_target_property(_gss_flavor CURL::gss INTERFACE_CURL_GSS_FLAVOR) - if(_gss_flavor STREQUAL "GNU") + get_target_property(GSS_FLAVOR CURL::gss INTERFACE_CURL_GSS_FLAVOR) + if(GSS_FLAVOR STREQUAL "Apple") + set(HAVE_GSSAPPLE 1) + elseif(GSS_FLAVOR STREQUAL "GNU") set(HAVE_GSSGNU 1) elseif(GSS_VERSION) # MIT set(CURL_KRB5_VERSION "\"${GSS_VERSION}\"") endif() + + cmake_push_check_state() + list(APPEND CMAKE_REQUIRED_LIBRARIES CURL::gss) + check_function_exists("gss_set_neg_mechs" HAVE_GSS_SET_NEG_MECHS) + cmake_pop_check_state() else() message(WARNING "GSSAPI has been requested, but no supporting libraries found. Skipping.") endif() @@ -1769,7 +1781,6 @@ check_include_file("sys/un.h" HAVE_SYS_UN_H) check_include_file_concat_curl("sys/utime.h" HAVE_SYS_UTIME_H) # sys/types.h (AmigaOS) check_include_file_concat_curl("arpa/inet.h" HAVE_ARPA_INET_H) -check_include_file("dirent.h" HAVE_DIRENT_H) check_include_file("fcntl.h" HAVE_FCNTL_H) check_include_file_concat_curl("ifaddrs.h" HAVE_IFADDRS_H) check_include_file("io.h" HAVE_IO_H) @@ -1780,6 +1791,7 @@ check_include_file_concat_curl("net/if.h" HAVE_NET_IF_H) # sys/select.h check_include_file_concat_curl("netdb.h" HAVE_NETDB_H) check_include_file_concat_curl("netinet/in.h" HAVE_NETINET_IN_H) check_include_file("netinet/in6.h" HAVE_NETINET_IN6_H) +check_include_file("netinet/ip.h" HAVE_NETINET_IP_H) check_include_file_concat_curl("netinet/tcp.h" HAVE_NETINET_TCP_H) # sys/types.h (e.g. Cygwin) netinet/in.h check_include_file_concat_curl("netinet/udp.h" HAVE_NETINET_UDP_H) # sys/types.h (e.g. Cygwin) check_include_file("poll.h" HAVE_POLL_H) @@ -1840,7 +1852,6 @@ endif() check_function_exists("accept4" HAVE_ACCEPT4) check_function_exists("fnmatch" HAVE_FNMATCH) check_symbol_exists("basename" "${CURL_INCLUDES};string.h" HAVE_BASENAME) # libgen.h unistd.h -check_symbol_exists("opendir" "dirent.h" HAVE_OPENDIR) check_function_exists("poll" HAVE_POLL) # poll.h check_symbol_exists("socket" "${CURL_INCLUDES}" HAVE_SOCKET) # winsock2.h sys/socket.h check_symbol_exists("socketpair" "${CURL_INCLUDES}" HAVE_SOCKETPAIR) # sys/socket.h @@ -1848,14 +1859,12 @@ check_symbol_exists("recv" "${CURL_INCLUDES}" HAVE_RECV) # proto/bsd check_symbol_exists("send" "${CURL_INCLUDES}" HAVE_SEND) # proto/bsdsocket.h sys/types.h sys/socket.h check_function_exists("sendmsg" HAVE_SENDMSG) check_function_exists("sendmmsg" HAVE_SENDMMSG) -check_symbol_exists("select" "${CURL_INCLUDES}" HAVE_SELECT) # proto/bsdsocket.h sys/select.h sys/socket.h check_symbol_exists("memrchr" "string.h" HAVE_MEMRCHR) check_symbol_exists("alarm" "unistd.h" HAVE_ALARM) check_symbol_exists("fcntl" "fcntl.h" HAVE_FCNTL) check_function_exists("getppid" HAVE_GETPPID) check_function_exists("utimes" HAVE_UTIMES) -check_function_exists("gettimeofday" HAVE_GETTIMEOFDAY) # sys/time.h check_symbol_exists("closesocket" "${CURL_INCLUDES}" HAVE_CLOSESOCKET) # winsock2.h check_symbol_exists("sigsetjmp" "setjmp.h" HAVE_SIGSETJMP) check_function_exists("getpass_r" HAVE_GETPASS_R) @@ -1894,12 +1903,14 @@ else() endif() if(NOT WIN32) + check_include_file("dirent.h" HAVE_DIRENT_H) + + check_function_exists("gettimeofday" HAVE_GETTIMEOFDAY) # sys/time.h check_function_exists("if_nametoindex" HAVE_IF_NAMETOINDEX) # net/if.h check_function_exists("realpath" HAVE_REALPATH) check_function_exists("sched_yield" HAVE_SCHED_YIELD) - check_symbol_exists("inet_ntop" "${CURL_INCLUDES};stdlib.h;string.h" HAVE_INET_NTOP) # arpa/inet.h netinet/in.h sys/socket.h - check_symbol_exists("inet_pton" "${CURL_INCLUDES};stdlib.h;string.h" HAVE_INET_PTON) # arpa/inet.h netinet/in.h sys/socket.h + check_symbol_exists("opendir" "dirent.h" HAVE_OPENDIR) check_symbol_exists("strcasecmp" "string.h" HAVE_STRCASECMP) check_symbol_exists("stricmp" "string.h" HAVE_STRICMP) check_symbol_exists("strcmpi" "string.h" HAVE_STRCMPI) @@ -1946,7 +1957,6 @@ foreach(_curl_test IN ITEMS HAVE_GETHOSTBYNAME_R_5 HAVE_GETHOSTBYNAME_R_6 HAVE_BOOL_T - STDC_HEADERS HAVE_ATOMIC ) curl_internal_test(${_curl_test}) @@ -2016,7 +2026,7 @@ if(NOT WIN32 AND NOT CMAKE_CROSSCOMPILING) # On non-Windows and not cross-compiling, check for writable argv[] include(CheckCSourceRuns) check_c_source_runs(" - int main(int argc, char **argv) + int main(int argc, char *argv[]) { (void)argc; argv[0][0] = ' '; @@ -2029,7 +2039,8 @@ if(NOT CMAKE_CROSSCOMPILING) include(CheckCSourceRuns) check_c_source_runs(" #include - int main(void) { + int main(void) + { time_t t = -1; return t < 0; }" HAVE_TIME_T_UNSIGNED) @@ -2172,9 +2183,8 @@ include(GNUInstallDirs) set(_install_cmake_dir "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}") set(TARGETS_EXPORT_NAME "${PROJECT_NAME}Targets") -set(_generated_dir "${CMAKE_CURRENT_BINARY_DIR}/generated") -set(_project_config "${_generated_dir}/${PROJECT_NAME}Config.cmake") -set(_version_config "${_generated_dir}/${PROJECT_NAME}ConfigVersion.cmake") +set(_project_config "${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake") +set(_version_config "${PROJECT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake") option(BUILD_TESTING "Build tests" ON) if(BUILD_TESTING AND Perl_FOUND) @@ -2289,13 +2299,13 @@ curl_add_if("SSPI" USE_WINDOWS_SSPI) curl_add_if("GSS-API" HAVE_GSSAPI) curl_add_if("alt-svc" NOT CURL_DISABLE_ALTSVC) curl_add_if("HSTS" NOT CURL_DISABLE_HSTS) +curl_add_if("HTTPSIG" NOT CURL_DISABLE_HTTPSIG) curl_add_if("SPNEGO" NOT CURL_DISABLE_NEGOTIATE_AUTH AND (HAVE_GSSAPI OR USE_WINDOWS_SSPI)) curl_add_if("Kerberos" NOT CURL_DISABLE_KERBEROS_AUTH AND (HAVE_GSSAPI OR USE_WINDOWS_SSPI)) curl_add_if("NTLM" CURL_ENABLE_NTLM AND (_use_curl_ntlm_core OR USE_WINDOWS_SSPI)) -curl_add_if("TLS-SRP" USE_TLS_SRP) curl_add_if("HTTP2" USE_NGHTTP2) curl_add_if("HTTP3" USE_NGTCP2 OR USE_QUICHE) curl_add_if("proxy-HTTP3" USE_PROXY_HTTP3) @@ -2322,12 +2332,12 @@ message(STATUS "Features: ${SUPPORT_FEATURES}") # Clear list and collect SSL backends set(_items "") -curl_add_if("Schannel" _ssl_enabled AND USE_SCHANNEL) -curl_add_if("${_openssl}" _ssl_enabled AND USE_OPENSSL) -curl_add_if("mbedTLS" _ssl_enabled AND USE_MBEDTLS) -curl_add_if("wolfSSL" _ssl_enabled AND USE_WOLFSSL) -curl_add_if("GnuTLS" _ssl_enabled AND USE_GNUTLS) -curl_add_if("Rustls" _ssl_enabled AND USE_RUSTLS) +curl_add_if("Schannel" _ssl_enabled AND USE_SCHANNEL) +curl_add_if("${_openssl}" _ssl_enabled AND USE_OPENSSL) +curl_add_if("mbedTLS" _ssl_enabled AND USE_MBEDTLS) +curl_add_if("wolfSSL" _ssl_enabled AND USE_WOLFSSL) +curl_add_if("GnuTLS" _ssl_enabled AND USE_GNUTLS) +curl_add_if("Rustls" _ssl_enabled AND USE_RUSTLS) if(_items) list(SORT _items CASE INSENSITIVE) @@ -2531,6 +2541,7 @@ if(NOT CURL_DISABLE_INSTALL) set(LIBCURL_PC_REQUIRES "${LIBCURL_PC_REQUIRES_PRIVATE}") set(LIBCURL_PC_LIBS "${LIBCURL_PC_LIBS_PRIVATE}") set(LIBCURL_PC_CFLAGS "${LIBCURL_PC_CFLAGS_PRIVATE}") + set(LIBCURL_PC_REQUIRES_PRIVATE "") endif() if(BUILD_STATIC_LIBS) set(ENABLE_STATIC "yes") @@ -2570,6 +2581,7 @@ if(NOT CURL_DISABLE_INSTALL) # Generate a pkg-config file matching this config. # Consumed variables: + # CURL_PACKAGE_MAINTAINER # CURLVERSION # exec_prefix # includedir @@ -2589,9 +2601,18 @@ if(NOT CURL_DISABLE_INSTALL) # https://manpages.debian.org/unstable/pkgconf/pkg-config.1.en.html # https://manpages.debian.org/unstable/pkg-config/pkg-config.1.en.html # https://www.msys2.org/docs/pkgconfig/ + if(NOT "$ENV{CURL_CI}" STREQUAL "") + set(CURL_PACKAGE_MAINTAINER "https://curl.se/") + endif() configure_file( "${PROJECT_SOURCE_DIR}/libcurl.pc.in" "${PROJECT_BINARY_DIR}/libcurl.pc" @ONLY) + # Strip trailing spaces, duplicate spaces after colon, empty properties + file(READ "${PROJECT_BINARY_DIR}/libcurl.pc" _libcurl_pc) + string(REGEX REPLACE " +\n" "\n" _libcurl_pc "${_libcurl_pc}") + string(REGEX REPLACE "\nLibs\.private: +" "\nLibs.private: " _libcurl_pc "${_libcurl_pc}") + string(REGEX REPLACE "\n([A-Za-z.]+:\n)+" "\n" _libcurl_pc "${_libcurl_pc}") + file(WRITE "${PROJECT_BINARY_DIR}/libcurl.pc" "${_libcurl_pc}") install(FILES "${PROJECT_BINARY_DIR}/libcurl.pc" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") @@ -2613,7 +2634,7 @@ if(NOT CURL_DISABLE_INSTALL) endif() ${_generated_version_config}") - # Consumed custom variables: + # Consumed variables: # CMAKE_MINIMUM_REQUIRED_VERSION # CURLVERSION # LIBCURL_PC_LIBS_PRIVATE_LIST @@ -2625,6 +2646,7 @@ if(NOT CURL_DISABLE_INSTALL) # CURL_SUPPORTED_PROTOCOLS_LIST # CURL_USE_CMAKECONFIG # CURL_USE_PKGCONFIG + # GSS_FLAVOR # HAVE_BROTLI # HAVE_GSSAPI # HAVE_LIBIDN2 @@ -2664,37 +2686,38 @@ if(NOT CURL_DISABLE_INSTALL) FILES ${_version_config} ${_project_config} - "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindBrotli.cmake" - "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindCares.cmake" - "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindGSS.cmake" - "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindGnuTLS.cmake" - "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindLDAP.cmake" - "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindLibbacktrace.cmake" - "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindLibgsasl.cmake" - "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindLibidn2.cmake" - "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindLibpsl.cmake" - "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindLibssh.cmake" - "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindLibssh2.cmake" - "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindLibuv.cmake" - "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindMbedTLS.cmake" - "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindNGHTTP2.cmake" - "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindNGHTTP3.cmake" - "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindNGTCP2.cmake" - "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindNettle.cmake" - "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindQuiche.cmake" - "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindRustls.cmake" - "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindWolfSSL.cmake" - "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindZstd.cmake" + "${PROJECT_SOURCE_DIR}/CMake/FindBrotli.cmake" + "${PROJECT_SOURCE_DIR}/CMake/FindCares.cmake" + "${PROJECT_SOURCE_DIR}/CMake/FindGSS.cmake" + "${PROJECT_SOURCE_DIR}/CMake/FindGnuTLS.cmake" + "${PROJECT_SOURCE_DIR}/CMake/FindLDAP.cmake" + "${PROJECT_SOURCE_DIR}/CMake/FindLibbacktrace.cmake" + "${PROJECT_SOURCE_DIR}/CMake/FindLibgsasl.cmake" + "${PROJECT_SOURCE_DIR}/CMake/FindLibidn2.cmake" + "${PROJECT_SOURCE_DIR}/CMake/FindLibpsl.cmake" + "${PROJECT_SOURCE_DIR}/CMake/FindLibssh.cmake" + "${PROJECT_SOURCE_DIR}/CMake/FindLibssh2.cmake" + "${PROJECT_SOURCE_DIR}/CMake/FindLibuv.cmake" + "${PROJECT_SOURCE_DIR}/CMake/FindMbedTLS.cmake" + "${PROJECT_SOURCE_DIR}/CMake/FindNGHTTP2.cmake" + "${PROJECT_SOURCE_DIR}/CMake/FindNGHTTP3.cmake" + "${PROJECT_SOURCE_DIR}/CMake/FindNGTCP2.cmake" + "${PROJECT_SOURCE_DIR}/CMake/FindNettle.cmake" + "${PROJECT_SOURCE_DIR}/CMake/FindQuiche.cmake" + "${PROJECT_SOURCE_DIR}/CMake/FindRustls.cmake" + "${PROJECT_SOURCE_DIR}/CMake/FindWolfSSL.cmake" + "${PROJECT_SOURCE_DIR}/CMake/FindZstd.cmake" DESTINATION ${_install_cmake_dir}) if(NOT TARGET curl_uninstall) + # Consumed variable: + # PROJECT_BINARY_DIR configure_file( - "${CMAKE_CURRENT_SOURCE_DIR}/CMake/cmake_uninstall.in.cmake" - "${CMAKE_CURRENT_BINARY_DIR}/CMake/cmake_uninstall.cmake" - @ONLY) + "${PROJECT_SOURCE_DIR}/CMake/cmake_uninstall.in.cmake" + "${PROJECT_BINARY_DIR}/cmake_uninstall.cmake" @ONLY) add_custom_target(curl_uninstall - COMMAND ${CMAKE_COMMAND} -P "${CMAKE_CURRENT_BINARY_DIR}/CMake/cmake_uninstall.cmake") + COMMAND ${CMAKE_COMMAND} -P "${PROJECT_BINARY_DIR}/cmake_uninstall.cmake") endif() if(BUILD_CURL_EXE) diff --git a/Utilities/cmcurl/include/curl/curl.h b/Utilities/cmcurl/include/curl/curl.h index c013f6cd42..091387aae3 100644 --- a/Utilities/cmcurl/include/curl/curl.h +++ b/Utilities/cmcurl/include/curl/curl.h @@ -93,7 +93,9 @@ defined(__CYGWIN__) || defined(AMIGA) || defined(__NuttX__) || \ (defined(__FreeBSD_version) && (__FreeBSD_version < 800000)) || \ (defined(__MidnightBSD_version) && (__MidnightBSD_version < 100000)) || \ - defined(__sun__) || defined(__serenity__) || defined(__vxworks__) + defined(__sun__) || defined(__serenity__) || defined(__vxworks__) || \ + (defined(__linux__) && defined(_POSIX_C_SOURCE) && \ + _POSIX_C_SOURCE >= 200112L) #include #endif @@ -632,7 +634,7 @@ typedef enum { match */ CURLE_SSL_INVALIDCERTSTATUS, /* 91 - invalid certificate status */ CURLE_HTTP2_STREAM, /* 92 - stream error in HTTP/2 framing layer - */ + */ CURLE_RECURSIVE_API_CALL, /* 93 - an api function was called from inside a callback */ CURLE_AUTH_ERROR, /* 94 - an authentication function returned an @@ -843,12 +845,20 @@ typedef enum { #endif #define CURLAUTH_BEARER (((unsigned long)1) << 6) #define CURLAUTH_AWS_SIGV4 (((unsigned long)1) << 7) +#define CURLAUTH_HTTPSIG (((unsigned long)1) << 8) #define CURLAUTH_ONLY (((unsigned long)1) << 31) -#define CURLAUTH_ANY ((~CURLAUTH_DIGEST_IE) & \ +#define CURLAUTH_ANY ((~(CURLAUTH_DIGEST_IE | \ + CURLAUTH_HTTPSIG)) & \ ((unsigned long)0xffffffff)) -#define CURLAUTH_ANYSAFE ((~(CURLAUTH_BASIC | CURLAUTH_DIGEST_IE)) & \ +#define CURLAUTH_ANYSAFE ((~(CURLAUTH_BASIC | CURLAUTH_DIGEST_IE | \ + CURLAUTH_HTTPSIG)) & \ ((unsigned long)0xffffffff)) +/* constants for CURLOPT_HTTPSIG_ALGORITHM */ +#define CURLHTTPSIG_NONE 0L +#define CURLHTTPSIG_ED25519 1L +#define CURLHTTPSIG_HMAC_SHA256 2L + /* all types supported by server */ #define CURLSSH_AUTH_ANY ((unsigned long)0xffffffff) #define CURLSSH_AUTH_NONE 0L /* none allowed, silly but complete */ @@ -1537,8 +1547,7 @@ typedef enum { /* FTP Option that causes missing dirs to be created on the remote server. In 7.19.4 we introduced the convenience enums for this option using the - CURLFTP_CREATE_DIR prefix. - */ + CURLFTP_CREATE_DIR prefix. */ CURLOPT(CURLOPT_FTP_CREATE_MISSING_DIRS, CURLOPTTYPE_LONG, 110), /* Set this to a bitmask value to enable the particular authentications @@ -1589,7 +1598,7 @@ typedef enum { CURLUSESSL_TRY - try using SSL, proceed anyway otherwise CURLUSESSL_CONTROL - SSL for the control connection or fail CURLUSESSL_ALL - SSL for all communication or fail - */ + */ CURLOPT(CURLOPT_USE_SSL, CURLOPTTYPE_VALUES, 119), /* The _LARGE version of the standard POSTFIELDSIZE option */ @@ -1615,7 +1624,7 @@ typedef enum { CURLFTPAUTH_DEFAULT - let libcurl decide CURLFTPAUTH_SSL - try "AUTH SSL" first, then TLS CURLFTPAUTH_TLS - try "AUTH TLS" first, then SSL - */ + */ CURLOPT(CURLOPT_FTPSSLAUTH, CURLOPTTYPE_VALUES, 129), CURLOPTDEPRECATED(CURLOPT_IOCTLFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 130, @@ -1650,8 +1659,7 @@ typedef enum { CURLOPT(CURLOPT_LOCALPORT, CURLOPTTYPE_LONG, 139), /* Number of ports to try, including the first one set with LOCALPORT. - Thus, setting it to 1 makes no additional attempts but the first. - */ + Thus, setting it to 1 makes no additional attempts but the first. */ CURLOPT(CURLOPT_LOCALPORTRANGE, CURLOPTTYPE_LONG, 140), /* no transfer, set up connection and let application use the socket by @@ -1863,13 +1871,16 @@ typedef enum { CURLOPT(CURLOPT_RESOLVE, CURLOPTTYPE_SLISTPOINT, 203), /* Set a username for authenticated TLS */ - CURLOPT(CURLOPT_TLSAUTH_USERNAME, CURLOPTTYPE_STRINGPOINT, 204), + CURLOPTDEPRECATED(CURLOPT_TLSAUTH_USERNAME, CURLOPTTYPE_STRINGPOINT, 204, + 8.22.0, "Support was removed"), /* Set a password for authenticated TLS */ - CURLOPT(CURLOPT_TLSAUTH_PASSWORD, CURLOPTTYPE_STRINGPOINT, 205), + CURLOPTDEPRECATED(CURLOPT_TLSAUTH_PASSWORD, CURLOPTTYPE_STRINGPOINT, 205, + 8.22.0, "Support was removed"), /* Set authentication type for authenticated TLS */ - CURLOPT(CURLOPT_TLSAUTH_TYPE, CURLOPTTYPE_STRINGPOINT, 206), + CURLOPTDEPRECATED(CURLOPT_TLSAUTH_TYPE, CURLOPTTYPE_STRINGPOINT, 206, + 8.22.0, "Support was removed"), /* Set to 1 to enable the "TE:" header in HTTP requests to ask for compressed transfer-encoded responses. Set to 0 to disable the use of TE: @@ -2030,13 +2041,16 @@ typedef enum { CURLOPT(CURLOPT_PROXY_SSLVERSION, CURLOPTTYPE_VALUES, 250), /* Set a username for authenticated TLS for proxy */ - CURLOPT(CURLOPT_PROXY_TLSAUTH_USERNAME, CURLOPTTYPE_STRINGPOINT, 251), + CURLOPTDEPRECATED(CURLOPT_PROXY_TLSAUTH_USERNAME, CURLOPTTYPE_STRINGPOINT, + 251, 8.22.0, "Support was removed"), /* Set a password for authenticated TLS for proxy */ - CURLOPT(CURLOPT_PROXY_TLSAUTH_PASSWORD, CURLOPTTYPE_STRINGPOINT, 252), + CURLOPTDEPRECATED(CURLOPT_PROXY_TLSAUTH_PASSWORD, CURLOPTTYPE_STRINGPOINT, + 252, 8.22.0, "Support was removed"), /* Set authentication type for authenticated TLS for proxy */ - CURLOPT(CURLOPT_PROXY_TLSAUTH_TYPE, CURLOPTTYPE_STRINGPOINT, 253), + CURLOPTDEPRECATED(CURLOPT_PROXY_TLSAUTH_TYPE, CURLOPTTYPE_STRINGPOINT, 253, + 8.22.0, "Support was removed"), /* name of the file keeping your private SSL-certificate for proxy */ CURLOPT(CURLOPT_PROXY_SSLCERT, CURLOPTTYPE_STRINGPOINT, 254), @@ -2262,6 +2276,18 @@ typedef enum { /* set TLS supported signature algorithms */ CURLOPT(CURLOPT_SSL_SIGNATURE_ALGORITHMS, CURLOPTTYPE_STRINGPOINT, 328), + /* RFC 9421 HTTP Message Signatures algorithm */ + CURLOPT(CURLOPT_HTTPSIG_ALGORITHM, CURLOPTTYPE_VALUES, 329), + + /* Hex-encoded key for HTTP Message Signatures */ + CURLOPT(CURLOPT_HTTPSIG_KEY, CURLOPTTYPE_STRINGPOINT, 330), + + /* Key identifier for HTTP Message Signatures */ + CURLOPT(CURLOPT_HTTPSIG_KEYID, CURLOPTTYPE_STRINGPOINT, 331), + + /* Space-separated list of components to sign for HTTP Message Signatures */ + CURLOPT(CURLOPT_HTTPSIG_HEADERS, CURLOPTTYPE_STRINGPOINT, 332), + CURLOPT_LASTENTRY /* the last unused */ } CURLoption; diff --git a/Utilities/cmcurl/include/curl/curlver.h b/Utilities/cmcurl/include/curl/curlver.h index 93bb8292c4..3a431c9871 100644 --- a/Utilities/cmcurl/include/curl/curlver.h +++ b/Utilities/cmcurl/include/curl/curlver.h @@ -32,12 +32,12 @@ /* This is the version number of the libcurl package from which this header file origins: */ -#define LIBCURL_VERSION "8.21.0" +#define LIBCURL_VERSION "8.22.0" /* The numeric version number is also available "in parts" by using these defines: */ #define LIBCURL_VERSION_MAJOR 8 -#define LIBCURL_VERSION_MINOR 21 +#define LIBCURL_VERSION_MINOR 22 #define LIBCURL_VERSION_PATCH 0 /* This is the numeric version of the libcurl version number, meant for easier parsing and comparisons by programs. The LIBCURL_VERSION_NUM define always @@ -56,9 +56,8 @@ Note: This define is the full hex number and _does not_ use the CURL_VERSION_BITS() macro since curl's own configure script greps for it - and needs it to contain the full number. -*/ -#define LIBCURL_VERSION_NUM 0x081500 + and needs it to contain the full number. */ +#define LIBCURL_VERSION_NUM 0x081600 /* * This is the date and time when the full source package was created. The diff --git a/Utilities/cmcurl/include/curl/multi.h b/Utilities/cmcurl/include/curl/multi.h index 060b73eeec..8cef6e774e 100644 --- a/Utilities/cmcurl/include/curl/multi.h +++ b/Utilities/cmcurl/include/curl/multi.h @@ -24,20 +24,20 @@ * ***************************************************************************/ /* - This is an "external" header file. Do not give away any internals here! - - GOALS - - o Enable a "pull" interface. The application that uses libcurl decides where - and when to ask libcurl to get/send data. - - o Enable multiple simultaneous transfers in the same thread without making it - complicated for the application. - - o Enable the application to select() on its own file descriptors and curl's - file descriptors simultaneous easily. - -*/ + * This is an "external" header file. Do not give away any internals here! + * + * GOALS + * + * o Enable a "pull" interface. The application that uses libcurl decides where + * and when to ask libcurl to get/send data. + * + * o Enable multiple simultaneous transfers in the same thread without making + * it complicated for the application. + * + * o Enable the application to select() on its own file descriptors and curl's + * file descriptors simultaneous easily. + * + */ /* * This header file should not really need to include "curl.h" since curl.h diff --git a/Utilities/cmcurl/include/curl/typecheck-gcc.h b/Utilities/cmcurl/include/curl/typecheck-gcc.h index d600c73cdc..f2e4542772 100644 --- a/Utilities/cmcurl/include/curl/typecheck-gcc.h +++ b/Utilities/cmcurl/include/curl/typecheck-gcc.h @@ -496,6 +496,9 @@ CURLWARNING(Wcurl_easy_getinfo_err_curl_off_t, (option) == CURLOPT_USERAGENT || \ (option) == CURLOPT_USERNAME || \ (option) == CURLOPT_AWS_SIGV4 || \ + (option) == CURLOPT_HTTPSIG_HEADERS || \ + (option) == CURLOPT_HTTPSIG_KEY || \ + (option) == CURLOPT_HTTPSIG_KEYID || \ (option) == CURLOPT_USERPWD || \ (option) == CURLOPT_XOAUTH2_BEARER || \ 0) @@ -605,9 +608,10 @@ CURLWARNING(Wcurl_easy_getinfo_err_curl_off_t, * == or whatsoever. */ -/* XXX: should evaluate to true if expr is a pointer */ +/* XXX: should evaluate to true if expr is a pointer or a char[] array */ #define curlcheck_any_ptr(expr) \ - (sizeof(expr) == sizeof(void *)) + (sizeof(expr) == sizeof(void *) || \ + __builtin_types_compatible_p(__typeof__(expr), char[])) /* evaluates to true if expr is NULL */ /* XXX: must not evaluate expr, so this check is not accurate */ @@ -674,7 +678,7 @@ CURLWARNING(Wcurl_easy_getinfo_err_curl_off_t, (__builtin_types_compatible_p(__typeof__(expr), curl_off_t)) /* evaluates to true if expr is abuffer suitable for CURLOPT_ERRORBUFFER */ -/* XXX: also check size of an char[] array? */ +/* XXX: also check size of a char[] array? */ #define curlcheck_error_buffer(expr) \ (curlcheck_NULL(expr) || \ __builtin_types_compatible_p(__typeof__(expr), char *) || \ diff --git a/Utilities/cmcurl/include/curl/urlapi.h b/Utilities/cmcurl/include/curl/urlapi.h index b1f3a2316b..02553dfebf 100644 --- a/Utilities/cmcurl/include/curl/urlapi.h +++ b/Utilities/cmcurl/include/curl/urlapi.h @@ -64,6 +64,7 @@ typedef enum { CURLUE_BAD_USER, /* 29 */ CURLUE_LACKS_IDN, /* 30 */ CURLUE_TOO_LARGE, /* 31 */ + CURLUE_BACKSLASH, /* 32 */ CURLUE_LAST } CURLUcode; diff --git a/Utilities/cmcurl/lib/CMakeLists.txt b/Utilities/cmcurl/lib/CMakeLists.txt index a9ed6928f4..97f0bd822e 100644 --- a/Utilities/cmcurl/lib/CMakeLists.txt +++ b/Utilities/cmcurl/lib/CMakeLists.txt @@ -290,6 +290,9 @@ if(BUILD_SHARED_LIBS) CMAKE_SYSTEM_NAME STREQUAL "SunOS" OR CMAKE_SYSTEM_NAME STREQUAL "Haiku" OR CMAKE_SYSTEM_NAME STREQUAL "OHOS" OR # OpenHarmony + # FreeBSD comes with the a.out and ELF flavors but a.out was supported + # up to v3.x and ELF from v3.x. I cannot imagine someone running CMake + # on those ancient systems. CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") set(_soversion_default TRUE) else() diff --git a/Utilities/cmcurl/lib/Makefile.inc b/Utilities/cmcurl/lib/Makefile.inc index 266ba52af3..c4e26849aa 100644 --- a/Utilities/cmcurl/lib/Makefile.inc +++ b/Utilities/cmcurl/lib/Makefile.inc @@ -84,6 +84,26 @@ LIB_VAUTH_HFILES = \ vauth/digest.h \ vauth/vauth.h +LIB_VDNS_CFILES = \ + vdns/asyn-ares.c \ + vdns/asyn-base.c \ + vdns/asyn-thrdd.c \ + vdns/cf-dns.c \ + vdns/dnscache.c \ + vdns/doh.c \ + vdns/hostip.c \ + vdns/hostip4.c \ + vdns/hostip6.c \ + vdns/httpsrr.c + +LIB_VDNS_HFILES = \ + vdns/asyn.h \ + vdns/cf-dns.h \ + vdns/dnscache.h \ + vdns/doh.h \ + vdns/hostip.h \ + vdns/httpsrr.h + LIB_VTLS_CFILES = \ vtls/apple.c \ vtls/cipher_suite.c \ @@ -154,12 +174,9 @@ LIB_VSSH_HFILES = \ LIB_CFILES = \ altsvc.c \ amigaos.c \ - asyn-ares.c \ - asyn-base.c \ - asyn-thrdd.c \ + api.c \ bufq.c \ bufref.c \ - cf-dns.c \ cf-h1-proxy.c \ cf-h2-proxy.c \ cf-haproxy.c \ @@ -194,8 +211,6 @@ LIB_CFILES = \ cw-out.c \ cw-pause.c \ dict.c \ - dnscache.c \ - doh.c \ dynhds.c \ easy.c \ easygetopt.c \ @@ -213,20 +228,18 @@ LIB_CFILES = \ hash.c \ headers.c \ hmac.c \ - hostip.c \ - hostip4.c \ - hostip6.c \ hsts.c \ http.c \ http1.c \ http2.c \ http_aws_sigv4.c \ + http_httpsig.c \ + curl_ed25519.c \ http_chunks.c \ http_digest.c \ http_negotiate.c \ http_ntlm.c \ http_proxy.c \ - httpsrr.c \ idn.c \ if2ip.c \ imap.c \ @@ -279,6 +292,7 @@ LIB_CFILES = \ transfer.c \ uint-bset.c \ uint-hash.c \ + uint-hashset.c \ uint-spbset.c \ uint-table.c \ url.c \ @@ -289,11 +303,10 @@ LIB_CFILES = \ LIB_HFILES = \ altsvc.h \ amigaos.h \ + api.h \ arpa_telnet.h \ - asyn.h \ bufq.h \ bufref.h \ - cf-dns.h \ cf-h1-proxy.h \ cf-h2-proxy.h \ cf-haproxy.h \ @@ -336,8 +349,6 @@ LIB_HFILES = \ cw-out.h \ cw-pause.h \ dict.h \ - dnscache.h \ - doh.h \ dynhds.h \ easy_lock.h \ easyif.h \ @@ -355,18 +366,18 @@ LIB_HFILES = \ gopher.h \ hash.h \ headers.h \ - hostip.h \ hsts.h \ http.h \ http1.h \ http2.h \ http_aws_sigv4.h \ + http_httpsig.h \ + curl_ed25519.h \ http_chunks.h \ http_digest.h \ http_negotiate.h \ http_ntlm.h \ http_proxy.h \ - httpsrr.h \ idn.h \ if2ip.h \ imap.h \ @@ -415,6 +426,7 @@ LIB_HFILES = \ transfer.h \ uint-bset.h \ uint-hash.h \ + uint-hashset.h \ uint-spbset.h \ uint-table.h \ url.h \ @@ -424,7 +436,9 @@ LIB_HFILES = \ LIB_RCFILES = libcurl.rc -CSOURCES = $(LIB_CFILES) $(LIB_VAUTH_CFILES) $(LIB_VTLS_CFILES) \ - $(LIB_VQUIC_CFILES) $(LIB_VSSH_CFILES) $(LIB_CURLX_CFILES) -HHEADERS = $(LIB_HFILES) $(LIB_VAUTH_HFILES) $(LIB_VTLS_HFILES) \ - $(LIB_VQUIC_HFILES) $(LIB_VSSH_HFILES) $(LIB_CURLX_HFILES) +CSOURCES = $(LIB_CFILES) $(LIB_VAUTH_CFILES) $(LIB_VDNS_CFILES) \ + $(LIB_VTLS_CFILES) $(LIB_VQUIC_CFILES) $(LIB_VSSH_CFILES) \ + $(LIB_CURLX_CFILES) +HHEADERS = $(LIB_HFILES) $(LIB_VAUTH_HFILES) $(LIB_VDNS_HFILES) \ + $(LIB_VTLS_HFILES) $(LIB_VQUIC_HFILES) $(LIB_VSSH_HFILES) \ + $(LIB_CURLX_HFILES) diff --git a/Utilities/cmcurl/lib/altsvc.c b/Utilities/cmcurl/lib/altsvc.c index 80913d9a1d..c57d3f6b40 100644 --- a/Utilities/cmcurl/lib/altsvc.c +++ b/Utilities/cmcurl/lib/altsvc.c @@ -78,6 +78,12 @@ const char *Curl_alpnid2str(enum alpnid id) } } +static enum alpnid Curl_str2alpnid(const struct Curl_str *cstr) +{ + return Curl_alpn2alpnid((const unsigned char *)curlx_str(cstr), + curlx_strlen(cstr)); +} + #define altsvc_free(x) curlx_free(x) static struct altsvc *altsvc_createid(const char *srchost, @@ -447,23 +453,125 @@ static bool hostcompare(const char *host, const char *check) /* altsvc_flush() removes all alternatives for this source origin from the list */ -static void altsvc_flush(struct altsvcinfo *asi, enum alpnid srcalpnid, - const char *srchost, unsigned short srcport) +static void altsvc_flush(struct altsvcinfo *asi, + struct Curl_peer *origin, + enum alpnid origin_alpnid) { struct Curl_llist_node *e; struct Curl_llist_node *n; for(e = Curl_llist_head(&asi->list); e; e = n) { struct altsvc *as = Curl_node_elem(e); n = Curl_node_next(e); - if((srcalpnid == as->src.alpnid) && - (srcport == as->src.port) && - hostcompare(srchost, as->src.host)) { + if((origin_alpnid == as->src.alpnid) && + (origin->port == as->src.port) && + hostcompare(origin->hostname, as->src.host)) { Curl_node_remove(e); altsvc_free(as); } } } +#define ALTSVC_MA 1 +#define ALTSVC_PERSIST 2 +static void altsvc_parse_params(const char **pp, + time_t *pmaxage, + bool *ppersist) +{ + curlx_str_passblanks(pp); + if(curlx_str_single(pp, ';')) + return; + + for(;;) { + struct Curl_str name; + struct Curl_str val; + curl_off_t num; + int keyword = 0; + + /* allow some extra whitespaces around name and value */ + if(curlx_str_until(pp, &name, MAX_ALTSVC_LINE, '=') || + curlx_str_single(pp, '=')) + break; /* skip further parameter parsing */ + + curlx_str_trimblanks(&name); + curlx_str_passblanks(pp); + if(**pp == '\"') { + if(curlx_str_quotedword(pp, &val, MAX_ALTSVC_LINE)) + break; + } + else { + if(curlx_str_cspn(pp, &val, ",;\r\n")) + break; + } + curlx_str_trimblanks(&val); + + if(curlx_str_casecompare(&name, "ma")) + keyword = ALTSVC_MA; + else if(curlx_str_casecompare(&name, "persist")) + keyword = ALTSVC_PERSIST; + + if(keyword) { + const char *vp = curlx_str(&val); + const char *vend = vp + curlx_strlen(&val); + if(curlx_str_number(&vp, &num, TIME_T_MAX)) + break; /* not a number, skip further parameter parsing */ + if(vp != vend) + break; /* not entirely a number, skip further parameter parsing */ + if(keyword == ALTSVC_MA) + *pmaxage = (time_t)num; + else if(num == 1) + *ppersist = TRUE; + } + + curlx_str_passblanks(pp); + if(curlx_str_single(pp, ';')) + break; /* no further parameters */ + } +} + +static bool altsvc_parse_dest(const char **pp, + struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_str *dsthost, + uint16_t *pdstport) +{ + curl_off_t port = 0; + + if(curlx_str_single(pp, '\"')) + return FALSE; + + /* quoted string, with hostname or just :port ? */ + if(curlx_str_single(pp, ':')) { /* is hostname:port ? */ + if(curlx_str_single(pp, '[')) { /* DNS hostname/IPv4 */ + if(curlx_str_until(pp, dsthost, MAX_ALTSVC_HOSTLEN, ':')) { + infof(data, "Bad alt-svc hostname, ignoring."); + return FALSE; + } + } + else { /* IPv6 hostname */ + if(curlx_str_until(pp, dsthost, MAX_IPADR_LEN, ']') || + curlx_str_single(pp, ']')) { + infof(data, "Bad alt-svc IPv6 hostname, ignoring."); + return FALSE; + } + } + if(curlx_str_single(pp, ':')) + return FALSE; /* not followed by ':' */ + } + else /* is only :port, hostname is effectively origin */ + curlx_str_assign(dsthost, origin->hostname, + strlen(origin->hostname)); + + if(curlx_str_number(pp, &port, 0xffff)) { + infof(data, "Unknown alt-svc port number, ignoring."); + return FALSE; + } + + *pdstport = (uint16_t)port; + if(curlx_str_single(pp, '\"')) + return FALSE; /* quoted string not ending here as expected */ + return TRUE; +} + /* * Curl_altsvc_parse() takes an incoming alt-svc response header and stores * the data correctly in the cache. @@ -477,161 +585,91 @@ static void altsvc_flush(struct altsvcinfo *asi, enum alpnid srcalpnid, */ CURLcode Curl_altsvc_parse(struct Curl_easy *data, struct altsvcinfo *asi, const char *value, - enum alpnid srcalpnid, const char *srchost, - unsigned short srcport) + struct Curl_peer *origin, + enum alpnid origin_alpnid) { - const char *p = value; struct altsvc *as; - unsigned short dstport = srcport; /* the same by default */ size_t entries = 0; struct Curl_str alpn; + const char *p; DEBUGASSERT(asi); + DEBUGASSERT(origin); + /* RFC 7838, The "Alt-Svc" header field value is basically + * Alt-Svc: (clear|alpn="(host)?:port"\s*(;\s*parameter=value)*) + * This can be repeated, comma-separated. + * + * We parse "best effort", ignoring values we do not recognize. + */ - /* initial check for "clear" */ + /* Try to catch a standalone "clear" */ + p = value; if(!curlx_str_cspn(&p, &alpn, ";\n\r")) { curlx_str_trimblanks(&alpn); /* "clear" is a magic keyword */ if(curlx_str_casecompare(&alpn, "clear")) { /* Flush cached alternatives for this source origin */ - altsvc_flush(asi, srcalpnid, srchost, srcport); + altsvc_flush(asi, origin, origin_alpnid); return CURLE_OK; } } - p = value; + /* Not a standalone "clear", parse from start for alpn entries */ + for(p = value; *p;) { + time_t maxage = 24 * 3600; /* default is 24 hours */ + bool persist = FALSE; + enum alpnid dstalpnid; + struct Curl_str dsthost; + uint16_t dstport; - if(curlx_str_until(&p, &alpn, MAX_ALTSVC_LINE, '=')) - return CURLE_OK; /* strange line */ + if(curlx_str_until(&p, &alpn, MAX_ALTSVC_LINE, '=')) + break; /* not another entry, leave */ + curlx_str_trimblanks(&alpn); + dstalpnid = Curl_str2alpnid(&alpn); - curlx_str_trimblanks(&alpn); + if(curlx_str_single(&p, '=')) + break; - do { - if(!curlx_str_single(&p, '=')) { - time_t maxage = 24 * 3600; /* default is 24 hours */ - bool persist = FALSE; - /* [protocol]="[host][:port], [protocol]="[host][:port]" */ - enum alpnid dstalpnid = Curl_str2alpnid(&alpn); - if(!curlx_str_single(&p, '\"')) { - struct Curl_str dsthost; - curl_off_t port = 0; - if(curlx_str_single(&p, ':')) { - /* hostname starts here */ - if(curlx_str_single(&p, '[')) { - if(curlx_str_until(&p, &dsthost, MAX_ALTSVC_HOSTLEN, ':')) { - infof(data, "Bad alt-svc hostname, ignoring."); - break; - } - } - else { - /* IPv6 hostname */ - if(curlx_str_until(&p, &dsthost, MAX_IPADR_LEN, ']') || - curlx_str_single(&p, ']')) { - infof(data, "Bad alt-svc IPv6 hostname, ignoring."); - break; - } - } - if(curlx_str_single(&p, ':')) - break; - } + /* Parse altsvc hostname:port */ + if(!altsvc_parse_dest(&p, data, origin, &dsthost, &dstport)) + break; + + /* Parse optional parameters */ + altsvc_parse_params(&p, &maxage, &persist); + + if(dstalpnid) { /* this is a known ALPN id, e.g. not ALPN_none */ + if(!entries++) + /* Flush cached alternatives for this source origin, if any - when + this is the first entry of the line. */ + altsvc_flush(asi, origin, origin_alpnid); + + as = altsvc_createid(origin->hostname, strlen(origin->hostname), + curlx_str(&dsthost), + curlx_strlen(&dsthost), + origin_alpnid, dstalpnid, + origin->port, dstport); + if(as) { + time_t secs = time(NULL); + /* The expires time also needs to take the Age: value (if any) + into account. [See RFC 7838 section 3.1] */ + if(maxage > (TIME_T_MAX - secs)) + as->expires = TIME_T_MAX; else - /* no destination name, use source host */ - curlx_str_assign(&dsthost, srchost, strlen(srchost)); - - if(curlx_str_number(&p, &port, 0xffff)) { - infof(data, "Unknown alt-svc port number, ignoring."); - break; - } - - dstport = (unsigned short)port; - - if(curlx_str_single(&p, '\"')) - break; - - /* Handle the optional 'ma' and 'persist' flags. Unknown flags are - skipped. */ - curlx_str_passblanks(&p); - if(!curlx_str_single(&p, ';')) { - for(;;) { - struct Curl_str name; - struct Curl_str val; - const char *vp; - curl_off_t num; - bool quoted; - /* allow some extra whitespaces around name and value */ - if(curlx_str_until(&p, &name, 20, '=') || - curlx_str_single(&p, '=') || - curlx_str_cspn(&p, &val, ",;")) - break; - curlx_str_trimblanks(&name); - curlx_str_trimblanks(&val); - /* the value might be quoted */ - vp = curlx_str(&val); - quoted = (*vp == '\"'); - if(quoted) - vp++; - if(!curlx_str_number(&vp, &num, TIME_T_MAX)) { - if(curlx_str_casecompare(&name, "ma")) - maxage = (time_t)num; - else if(curlx_str_casecompare(&name, "persist") && (num == 1)) - persist = TRUE; - } - else - break; - p = vp; /* point to the byte ending the value */ - curlx_str_passblanks(&p); - if(quoted && curlx_str_single(&p, '\"')) - break; - curlx_str_passblanks(&p); - if(curlx_str_single(&p, ';')) - break; - } - } - if(dstalpnid) { - if(!entries++) - /* Flush cached alternatives for this source origin, if any - when - this is the first entry of the line. */ - altsvc_flush(asi, srcalpnid, srchost, srcport); - - as = altsvc_createid(srchost, strlen(srchost), - curlx_str(&dsthost), - curlx_strlen(&dsthost), - srcalpnid, dstalpnid, - srcport, dstport); - if(as) { - time_t secs = time(NULL); - /* The expires time also needs to take the Age: value (if any) - into account. [See RFC 7838 section 3.1] */ - if(maxage > (TIME_T_MAX - secs)) - as->expires = TIME_T_MAX; - else - as->expires = maxage + secs; - as->persist = persist; - altsvc_append(asi, as); - infof(data, "Added alt-svc: %.*s:%d over %s", - (int)curlx_strlen(&dsthost), curlx_str(&dsthost), - dstport, Curl_alpnid2str(dstalpnid)); - } - else - return CURLE_OUT_OF_MEMORY; - } + as->expires = maxage + secs; + as->persist = persist; + altsvc_append(asi, as); + infof(data, "Added alt-svc: %.*s:%u over %s", + (int)curlx_strlen(&dsthost), curlx_str(&dsthost), + dstport, Curl_alpnid2str(dstalpnid)); } else - break; - - /* after the double quote there can be a comma if there is another - string or a semicolon if no more */ - if(curlx_str_single(&p, ',')) - break; - - /* comma means another alternative is present */ - if(curlx_str_until(&p, &alpn, MAX_ALTSVC_LINE, '=')) - break; - curlx_str_trimblanks(&alpn); + return CURLE_OUT_OF_MEMORY; } - else + + /* When this is followed by a comma, we expect another entry */ + if(curlx_str_single(&p, ',')) break; - } while(1); + } return CURLE_OK; } @@ -640,38 +678,42 @@ CURLcode Curl_altsvc_parse(struct Curl_easy *data, * Return TRUE on a match */ bool Curl_altsvc_lookup(struct altsvcinfo *asi, - enum alpnid srcalpnid, const char *srchost, - int srcport, + struct Curl_peer *origin, + enum alpnid origin_alpnid, struct altsvc **dstentry, const int versions, /* one or more bits */ bool *psame_destination) { - struct Curl_llist_node *e; - struct Curl_llist_node *n; - time_t now = time(NULL); DEBUGASSERT(asi); - DEBUGASSERT(srchost); + DEBUGASSERT(origin); DEBUGASSERT(dstentry); - *psame_destination = FALSE; - for(e = Curl_llist_head(&asi->list); e; e = n) { - struct altsvc *as = Curl_node_elem(e); - n = Curl_node_next(e); - if(as->expires < now) { - /* an expired entry, remove */ - Curl_node_remove(e); - altsvc_free(as); - continue; - } - if((as->src.alpnid == srcalpnid) && - hostcompare(srchost, as->src.host) && - (as->src.port == srcport) && - (versions & (int)as->dst.alpnid)) { - /* match */ - *dstentry = as; - *psame_destination = (srcport == as->dst.port) && - hostcompare(srchost, as->dst.host); - return TRUE; + + if(Curl_llist_count(&asi->list)) { + struct Curl_llist_node *e; + struct Curl_llist_node *n; + time_t now = time(NULL); + + for(e = Curl_llist_head(&asi->list); e; e = n) { + struct altsvc *as = Curl_node_elem(e); + n = Curl_node_next(e); + if(as->expires < now) { + /* an expired entry, remove */ + Curl_node_remove(e); + altsvc_free(as); + continue; + } + if((origin_alpnid == as->src.alpnid) && + (versions & (int)as->dst.alpnid) && + (origin->port == as->src.port) && + hostcompare(origin->hostname, as->src.host)) { + /* match */ + *dstentry = as; + /* alt-svc on the same host+port or another one? */ + *psame_destination = (origin->port == as->dst.port) && + hostcompare(origin->hostname, as->dst.host); + return TRUE; + } } } return FALSE; diff --git a/Utilities/cmcurl/lib/altsvc.h b/Utilities/cmcurl/lib/altsvc.h index dc1740bce1..cf9e21808e 100644 --- a/Utilities/cmcurl/lib/altsvc.h +++ b/Utilities/cmcurl/lib/altsvc.h @@ -28,6 +28,8 @@ #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_ALTSVC) #include "llist.h" +struct Curl_peer; + /* the maximum number of alt-svc entries kept in a single cache */ #define MAX_ALTSVC_ENTRIES 5000 @@ -60,11 +62,11 @@ CURLcode Curl_altsvc_ctrl(struct Curl_easy *data, const long ctrl); void Curl_altsvc_cleanup(struct altsvcinfo **asi); CURLcode Curl_altsvc_parse(struct Curl_easy *data, struct altsvcinfo *asi, const char *value, - enum alpnid srcalpnid, const char *srchost, - unsigned short srcport); + struct Curl_peer *origin, + enum alpnid origin_alpnid); bool Curl_altsvc_lookup(struct altsvcinfo *asi, - enum alpnid srcalpnid, const char *srchost, - int srcport, + struct Curl_peer *origin, + enum alpnid origin_alpnid, struct altsvc **dstentry, const int versions, /* CURLALTSVC_H* bits */ bool *psame_destination); diff --git a/Utilities/cmcurl/lib/amigaos.c b/Utilities/cmcurl/lib/amigaos.c index e4f3bfb77c..3f61565183 100644 --- a/Utilities/cmcurl/lib/amigaos.c +++ b/Utilities/cmcurl/lib/amigaos.c @@ -25,7 +25,7 @@ #ifdef __AMIGA__ -#include "hostip.h" +#include "vdns/hostip.h" #include "curl_addrinfo.h" #include "amigaos.h" diff --git a/Utilities/cmcurl/lib/api.c b/Utilities/cmcurl/lib/api.c new file mode 100644 index 0000000000..15e3789861 --- /dev/null +++ b/Utilities/cmcurl/lib/api.c @@ -0,0 +1,451 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * 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" + +#include "urldata.h" +#include "api.h" +#include "curl_threads.h" +#include "multiif.h" +#include "vtls/vtls_scache.h" + +struct Curl_eapi_fn_props { + Curl_eapi_fn fn; + uint8_t data_is_killed; /* easy handle is killed in call */ + uint8_t recurse; /* may be called when another call is in progress */ + uint8_t no_event_cb; /* may not be called during a multi event callback */ + uint8_t no_scache_lock; /* may not be called with easy's vtls_scache + locked by current thread */ +}; + +static const struct Curl_eapi_fn_props eapi_fn_props[CURL_EAPI_FN_LAST] = { + /* function kill rec !ev !scach */ + { CURL_EAPI_FN_easy_cleanup, 1, 0, 0, 0 }, + { CURL_EAPI_FN_easy_duphandle, 0, 1, 0, 0 }, + { CURL_EAPI_FN_easy_getinfo, 0, 1, 0, 0 }, + { CURL_EAPI_FN_easy_header, 0, 1, 0, 0 }, + { CURL_EAPI_FN_easy_nextheader, 0, 1, 0, 0 }, + { CURL_EAPI_FN_easy_pause, 0, 1, 1, 0 }, + { CURL_EAPI_FN_easy_perform_ev, 0, 0, 0, 1 }, + { CURL_EAPI_FN_easy_perform, 0, 0, 0, 1 }, + { CURL_EAPI_FN_easy_recv, 0, 0, 0, 1 }, + { CURL_EAPI_FN_easy_reset, 0, 0, 0, 0 }, + { CURL_EAPI_FN_easy_send, 0, 0, 0, 1 }, + { CURL_EAPI_FN_easy_setopt, 0, 1, 0, 0 }, + { CURL_EAPI_FN_easy_ssls_export, 0, 0, 0, 1 }, + { CURL_EAPI_FN_easy_ssls_import, 0, 0, 0, 1 }, + { CURL_EAPI_FN_easy_upkeep, 0, 0, 0, 1 }, + { CURL_EAPI_FN_ws_recv, 0, 1, 0, 1 }, + { CURL_EAPI_FN_ws_send, 0, 1, 0, 1 }, + { CURL_EAPI_FN_ws_start_frame, 0, 1, 0, 0 }, +}; + +struct Curl_mapi_fn_props { + Curl_mapi_fn fn; + uint8_t multi_is_killed; /* multi handle is killed during call */ + uint8_t recurse; /* may be called when another call is in progress */ + uint8_t allow_ntfy_cb; /* may be called during a notify callback */ + uint8_t no_scache_lock; /* may not be called with multi's vtls_scache + locked by current thread */ +}; + +static const struct Curl_mapi_fn_props mapi_fn_props[CURL_MAPI_FN_LAST] = { + /* function kill rec ntfy !scach */ + { CURL_MAPI_FN_multi_add_handle, 0, 0, 1, 0 }, + { CURL_MAPI_FN_multi_assign, 0, 1, 1, 0 }, + { CURL_MAPI_FN_multi_cleanup, 1, 0, 0, 1 }, + { CURL_MAPI_FN_multi_fdset, 0, 0, 1, 0 }, + { CURL_MAPI_FN_multi_get_handles, 0, 1, 1, 0 }, + { CURL_MAPI_FN_multi_get_offt, 0, 1, 1, 0 }, + { CURL_MAPI_FN_multi_info_read, 0, 1, 1, 0 }, + { CURL_MAPI_FN_multi_notify_disable, 0, 1, 1, 0 }, + { CURL_MAPI_FN_multi_notify_enable, 0, 1, 1, 0 }, + { CURL_MAPI_FN_multi_perform, 0, 0, 0, 1 }, + { CURL_MAPI_FN_multi_poll, 0, 0, 1, 0 }, + { CURL_MAPI_FN_multi_remove_handle, 0, 0, 1, 0 }, + { CURL_MAPI_FN_multi_setopt, 0, 0, 1, 0 }, + { CURL_MAPI_FN_multi_socket_action, 0, 0, 0, 1 }, + { CURL_MAPI_FN_multi_socket_all, 0, 0, 0, 1 }, + { CURL_MAPI_FN_multi_socket, 0, 0, 0, 1 }, + { CURL_MAPI_FN_multi_timeout, 0, 0, 1, 1 }, + { CURL_MAPI_FN_multi_wait, 0, 0, 1, 0 }, + { CURL_MAPI_FN_multi_waitfds, 0, 0, 1, 0 }, +}; + +struct Curl_cbapi_fn_props { + Curl_cbapi_fn fn; + uint8_t is_event_cb; /* is a multi event processing callback */ +}; + +static const struct Curl_cbapi_fn_props +cbapi_fn_props[CURL_CBAPI_FN_LAST - CURL_CBAPI_FN_START] = { + { CURL_CBAPI_FN_easy_chunk_bgn, 0 }, + { CURL_CBAPI_FN_easy_chunk_end, 0 }, + { CURL_CBAPI_FN_easy_closesocket, 0 }, + { CURL_CBAPI_FN_easy_cr_in_read, 0 }, + { CURL_CBAPI_FN_easy_cr_in_resume_from, 0 }, + { CURL_CBAPI_FN_easy_cw_out_cb, 0 }, + { CURL_CBAPI_FN_easy_fdebug, 0 }, + { CURL_CBAPI_FN_easy_fnmatch_data, 0 }, + { CURL_CBAPI_FN_easy_fopensocket, 0 }, + { CURL_CBAPI_FN_easy_fprereq, 0 }, + { CURL_CBAPI_FN_easy_fprogress, 0 }, + { CURL_CBAPI_FN_easy_fread_func, 0 }, + { CURL_CBAPI_FN_easy_fsockopt, 0 }, + { CURL_CBAPI_FN_easy_fsslctx, 0 }, + { CURL_CBAPI_FN_easy_fwrite_rtp, 0 }, + { CURL_CBAPI_FN_easy_fxferinfo, 0 }, + { CURL_CBAPI_FN_easy_ioctl_func, 0 }, + { CURL_CBAPI_FN_easy_resolver_start, 0 }, + { CURL_CBAPI_FN_easy_seek_func, 0 }, + { CURL_CBAPI_FN_easy_ssh_hostkeyfunc, 0 }, + { CURL_CBAPI_FN_easy_ssh_keyfunc, 0 }, + { CURL_CBAPI_FN_easy_trailer_callback, 0 }, + + { CURL_CBAPI_FN_multi_ntfy_cb, 0 }, + { CURL_CBAPI_FN_multi_push_cb, 0 }, + { CURL_CBAPI_FN_multi_socket_cb, 1 }, + { CURL_CBAPI_FN_multi_timer_cb, 1 }, +}; + +static bool eapi_in_event_cb(struct Curl_easy *data) +{ + struct Curl_multi *multi = data->multi; + if(multi && multi->callstack.count) { + size_t i; + for(i = 0; i < multi->callstack.count; ++i) { + if(multi->callstack.calls[i] >= CURL_CBAPI_FN_START) { + uint16_t fn = multi->callstack.calls[i]; + if((fn < CURL_CBAPI_FN_LAST) && + cbapi_fn_props[fn - CURL_CBAPI_FN_START].is_event_cb) + return TRUE; + } + } + } + return FALSE; +} + +static bool mapi_in_ntfy_cb(struct Curl_multi *multi) +{ + if(multi && multi->callstack.count) { + size_t i; + for(i = 0; i < multi->callstack.count; ++i) { + if(multi->callstack.calls[i] == CURL_CBAPI_FN_multi_ntfy_cb) + return TRUE; + } + } + return FALSE; +} + +bool Curl_api_multi_is_in_callback(struct Curl_multi *multi) +{ + if(multi && multi->callstack.count) { + size_t i; + for(i = 0; i < multi->callstack.count; ++i) { + if(multi->callstack.calls[i] >= CURL_CBAPI_FN_START) + return TRUE; + } + } + return FALSE; +} + +bool Curl_api_is_in_callback(struct Curl_easy *data) +{ + if(data && data->multi) { + return Curl_api_multi_is_in_callback(data->multi); + } + return FALSE; +} + +bool Curl_eapi_enter(struct Curl_eapi_guard *guard, + CURL *curl, + Curl_eapi_fn fn, + CURLcode *presult) +{ + struct Curl_easy *data = curl; + const struct Curl_eapi_fn_props *fn_props; + CURLcode result = CURLE_OK; + + guard->depth = 0; + + /* Verify that we got an easy handle we can work with. */ + if(!GOOD_EASY_HANDLE(data)) { + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; + } + /* verify that is either not added to a multi handle OR has a + * GOOD multi handle that knows `data` for `data->mid`. */ + if(data->mid != UINT32_MAX) { + if(GOOD_MULTI_HANDLE(data->multi)) { + if(!Curl_multi_knows_easy(data->multi, data)) { + /* But multi does not know it, something is fishy, better deny call */ + DEBUGASSERT(0); + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; + } + } + else { + DEBUGASSERT(0); /* data needs to have a GOOD multi handle */ + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; + } + } + else if(data->multi) { + DEBUGASSERT(0); /* data should not have a multi handle */ + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; + } + /* verify that the call `fn` we're about to enter is known + * and check call properties to be admitting. */ + if(fn >= CURL_EAPI_FN_LAST) { + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; + } + fn_props = &eapi_fn_props[fn]; + DEBUGASSERT(fn_props->fn == fn); + if(!fn_props->recurse) { + if(data->callstack.count) { +#ifdef CURLVERBOSE + DEBUGF(curl_mfprintf(stderr, + "EAPI guard: calling %hu with call to %u ongoing\n", (uint16_t)fn, + data->callstack.calls[data->callstack.count-1])); +#endif + result = CURLE_RECURSIVE_API_CALL; + goto out; + } + if(data->multi && data->multi->callstack.count) { +#ifdef CURLVERBOSE + + DEBUGF(curl_mfprintf(stderr, + "EAPI guard: calling %hu with multi call to %u ongoing\n", + (uint16_t)fn, + data->multi->callstack.calls[data->multi->callstack.count-1])); +#endif + result = CURLE_RECURSIVE_API_CALL; + goto out; + } + } + + if(fn_props->no_event_cb && eapi_in_event_cb(data)) { + /* Not allowed to be invoked while an event cb is ongoing */ +#ifdef CURLVERBOSE + DEBUGF(curl_mfprintf(stderr, + "EAPI guard: calling %hu while event callback ongoing\n", + (uint16_t)fn)); +#endif + result = CURLE_RECURSIVE_API_CALL; + goto out; + } + +#if defined(USE_SSL) && defined(USE_MUTEX) + if(fn_props->no_scache_lock && + Curl_ssl_scache_is_locked_by_current_thread(data)) { +#ifdef CURLVERBOSE + DEBUGF(curl_mfprintf(stderr, + "EAPI guard: calling %hu while vtls_scache is locked by " + "current thread\n", (uint16_t)fn)); +#endif + result = CURLE_RECURSIVE_API_CALL; + goto out; + } +#endif + + /* all fine, add to data's callstack */ + if(data->callstack.count >= CURL_EAPI_MAX_RECURSION) { + result = CURLE_RECURSIVE_API_CALL; + goto out; + } + data->callstack.calls[data->callstack.count] = (uint16_t)fn; + ++data->callstack.count; + guard->depth = data->callstack.count; + guard->data = fn_props->data_is_killed ? NULL : data; + +out: + if(presult) + *presult = result; + return guard->depth > 0; +} + +void Curl_eapi_leave(struct Curl_eapi_guard *guard) +{ + if(guard->depth) { + /* guard->data is set when handle is supposed to stay alive during call */ + if(guard->data && GOOD_EASY_HANDLE(guard->data)) { + if(guard->depth > guard->data->callstack.count) { + DEBUGASSERT(0); /* something very wrong */ + } + else { + if(guard->depth < guard->data->callstack.count) { + DEBUGASSERT(0); /* someone forgot to clean up */ + } + /* reset to depth the guard was entered in */ + guard->data->callstack.count = (uint16_t)(guard->depth - 1); + } + } + } +} + +CURLHcode Curl_eapi_hcode(CURLcode result) +{ + switch(result) { + case CURLE_OK: + return CURLHE_OK; + case CURLE_BAD_FUNCTION_ARGUMENT: + return CURLHE_BAD_ARGUMENT; + case CURLE_OUT_OF_MEMORY: + return CURLHE_OUT_OF_MEMORY; + case CURLE_NOT_BUILT_IN: + return CURLHE_NOT_BUILT_IN; + default: + /* Unfortunately, we cannot convert RECURSIVE_API_CALL, + * but since the header API is reentrant, this should not happen. */ + return CURLHE_BAD_ARGUMENT; + } +} + +bool Curl_mapi_enter(struct Curl_mapi_guard *guard, + CURLM *m, + Curl_mapi_fn fn, + CURLMcode *pmresult) +{ + struct Curl_multi *multi = m; + const struct Curl_mapi_fn_props *fn_props; + CURLMcode mresult = CURLM_OK; + + guard->depth = 0; + + /* Verify that we got an easy handle we can work with. */ + if(!GOOD_MULTI_HANDLE(multi)) { + mresult = CURLM_BAD_HANDLE; + goto out; + } + if(fn >= CURL_MAPI_FN_LAST) { + mresult = CURLM_BAD_FUNCTION_ARGUMENT; + goto out; + } + fn_props = &mapi_fn_props[fn]; + DEBUGASSERT(fn_props->fn == fn); + if(fn_props->allow_ntfy_cb && mapi_in_ntfy_cb(multi)) { + /* explicitly allowed, even though normal recursion may not */ + } + else if(!fn_props->recurse && multi->callstack.count) { +#ifdef CURLVERBOSE + DEBUGF(curl_mfprintf(stderr, + "MAPI guard: calling %hu with call to %u ongoing\n", (uint16_t)fn, + multi->callstack.calls[multi->callstack.count-1])); +#endif + mresult = CURLM_RECURSIVE_API_CALL; + goto out; + } + +#if defined(USE_SSL) && defined(USE_MUTEX) + if(fn_props->no_scache_lock && multi->ssl_scache && + Curl_ssl_scache_is_locked_by_current_thread(multi->admin)) { +#ifdef CURLVERBOSE + DEBUGF(curl_mfprintf(stderr, + "MAPI guard: calling %hu while its vtls_scache is locked by " + "current thread\n", (uint16_t)fn)); +#endif + mresult = CURLM_RECURSIVE_API_CALL; + goto out; + } +#endif + + /* all fine, add to data's callstack */ + if(multi->callstack.count >= CURL_MAPI_MAX_RECURSION) { + mresult = CURLM_RECURSIVE_API_CALL; + goto out; + } + multi->callstack.calls[multi->callstack.count] = (uint16_t)fn; + ++multi->callstack.count; + guard->depth = multi->callstack.count; + guard->multi = fn_props->multi_is_killed ? NULL : multi; + +out: + if(pmresult) + *pmresult = mresult; + return guard->depth > 0; +} + +void Curl_mapi_leave(struct Curl_mapi_guard *guard) +{ + if(guard->depth) { + /* guard->data is set when handle is supposed to stay alive during call */ + if(guard->multi && GOOD_MULTI_HANDLE(guard->multi)) { + if(guard->depth > guard->multi->callstack.count) { + DEBUGASSERT(0); /* something very wrong */ + } + else { + if(guard->depth < guard->multi->callstack.count) { + DEBUGASSERT(0); /* someone forgot to clean up */ + } + /* reset to depth the guard was entered in */ + guard->multi->callstack.count = (uint16_t)(guard->depth - 1); + } + } + } +} + +void Curl_cbapi_enter(struct Curl_mapi_guard *guard, + struct Curl_easy *data, + struct Curl_multi *multi, + Curl_cbapi_fn fn) +{ + guard->depth = 0; + + if(!multi) + multi = data ? data->multi : NULL; + /* if not multi is involved here, just leave */ + if(!multi) + return; + /* invalid callback specifier? */ + if((fn >= CURL_CBAPI_FN_LAST) || (fn < CURL_CBAPI_FN_START)) { + DEBUGASSERT(0); + return; + } + DEBUGASSERT(cbapi_fn_props[fn - CURL_CBAPI_FN_START].fn == fn); + if(multi->callstack.count) { + size_t i; + for(i = multi->callstack.count; i; --i) { + if(multi->callstack.calls[i - 1] == fn) { + /* recursive invocation of the same callback */ + DEBUGASSERT(0); + return; + } + } + } + + /* all fine, add to data's callstack */ + /* if multi callstack already at max depth, leave */ + if(multi->callstack.count >= CURL_MAPI_MAX_RECURSION) + return; + multi->callstack.calls[multi->callstack.count] = (uint16_t)fn; + ++multi->callstack.count; + guard->depth = multi->callstack.count; + guard->multi = multi; +} + +void Curl_cbapi_leave(struct Curl_mapi_guard *guard) +{ + Curl_mapi_leave(guard); +} diff --git a/Utilities/cmcurl/lib/api.h b/Utilities/cmcurl/lib/api.h new file mode 100644 index 0000000000..337d686bb5 --- /dev/null +++ b/Utilities/cmcurl/lib/api.h @@ -0,0 +1,212 @@ +#ifndef HEADER_CURL_API_H +#define HEADER_CURL_API_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * 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" + +#define CURLEASY_MAGIC_NUMBER 0xc0dedbadU +#ifdef DEBUGBUILD +/* On a debug build, we want to fail hard on easy handles that + * are not NULL, but no longer have the MAGIC touch. This gives + * us early warning on things only discovered by valgrind otherwise. */ +#define GOOD_EASY_HANDLE(x) \ + (((x) && ((x)->magic == CURLEASY_MAGIC_NUMBER)) ? TRUE : \ + (DEBUGASSERT(!(x)), FALSE)) +#else +#define GOOD_EASY_HANDLE(x) \ + ((x) && ((x)->magic == CURLEASY_MAGIC_NUMBER)) +#endif + +#define CURLMULTI_MAGIC_NUMBER 0x000bab1e + +#ifdef DEBUGBUILD +/* On a debug build, we want to fail hard on multi handles that + * are not NULL, but no longer have the MAGIC touch. This gives + * us early warning on things only discovered by valgrind otherwise. */ +#define GOOD_MULTI_HANDLE(x) \ + (((x) && (x)->magic == CURLMULTI_MAGIC_NUMBER) ? TRUE : \ + (DEBUGASSERT(!(x)), FALSE)) +#else +#define GOOD_MULTI_HANDLE(x) \ + ((x) && (x)->magic == CURLMULTI_MAGIC_NUMBER) +#endif + +/* the API functions called on a CURL* */ +typedef enum { + CURL_EAPI_FN_easy_cleanup, + CURL_EAPI_FN_easy_duphandle, + CURL_EAPI_FN_easy_getinfo, + CURL_EAPI_FN_easy_header, + CURL_EAPI_FN_easy_nextheader, + CURL_EAPI_FN_easy_pause, + CURL_EAPI_FN_easy_perform_ev, + CURL_EAPI_FN_easy_perform, + CURL_EAPI_FN_easy_recv, + CURL_EAPI_FN_easy_reset, + CURL_EAPI_FN_easy_send, + CURL_EAPI_FN_easy_setopt, + CURL_EAPI_FN_easy_ssls_export, + CURL_EAPI_FN_easy_ssls_import, + CURL_EAPI_FN_easy_upkeep, + CURL_EAPI_FN_ws_recv, + CURL_EAPI_FN_ws_send, + CURL_EAPI_FN_ws_start_frame, + CURL_EAPI_FN_LAST +} Curl_eapi_fn; + +/* the API functions called on a CURLM* */ +typedef enum { + CURL_MAPI_FN_multi_add_handle, + CURL_MAPI_FN_multi_assign, + CURL_MAPI_FN_multi_cleanup, + CURL_MAPI_FN_multi_fdset, + CURL_MAPI_FN_multi_get_handles, + CURL_MAPI_FN_multi_get_offt, + CURL_MAPI_FN_multi_info_read, + CURL_MAPI_FN_multi_notify_disable, + CURL_MAPI_FN_multi_notify_enable, + CURL_MAPI_FN_multi_perform, + CURL_MAPI_FN_multi_poll, + CURL_MAPI_FN_multi_remove_handle, + CURL_MAPI_FN_multi_setopt, + CURL_MAPI_FN_multi_socket_action, + CURL_MAPI_FN_multi_socket_all, + CURL_MAPI_FN_multi_socket, + CURL_MAPI_FN_multi_timeout, + CURL_MAPI_FN_multi_wait, + CURL_MAPI_FN_multi_waitfds, + CURL_MAPI_FN_LAST +} Curl_mapi_fn; + +#define CURL_CBAPI_FN_START (16 * 1024) + +/* the callback functions */ +typedef enum { + CURL_CBAPI_FN_easy_chunk_bgn = CURL_CBAPI_FN_START, + CURL_CBAPI_FN_easy_chunk_end, + CURL_CBAPI_FN_easy_closesocket, + CURL_CBAPI_FN_easy_cr_in_read, + CURL_CBAPI_FN_easy_cr_in_resume_from, + CURL_CBAPI_FN_easy_cw_out_cb, + CURL_CBAPI_FN_easy_fdebug, + CURL_CBAPI_FN_easy_fnmatch_data, + CURL_CBAPI_FN_easy_fopensocket, + CURL_CBAPI_FN_easy_fprereq, + CURL_CBAPI_FN_easy_fprogress, + CURL_CBAPI_FN_easy_fread_func, + CURL_CBAPI_FN_easy_fsockopt, + CURL_CBAPI_FN_easy_fsslctx, + CURL_CBAPI_FN_easy_fwrite_rtp, + CURL_CBAPI_FN_easy_fxferinfo, + CURL_CBAPI_FN_easy_ioctl_func, + CURL_CBAPI_FN_easy_resolver_start, + CURL_CBAPI_FN_easy_seek_func, + CURL_CBAPI_FN_easy_ssh_hostkeyfunc, + CURL_CBAPI_FN_easy_ssh_keyfunc, + CURL_CBAPI_FN_easy_trailer_callback, + + CURL_CBAPI_FN_multi_ntfy_cb, + CURL_CBAPI_FN_multi_push_cb, + CURL_CBAPI_FN_multi_socket_cb, + CURL_CBAPI_FN_multi_timer_cb, + + CURL_CBAPI_FN_LAST +} Curl_cbapi_fn; + +/* EAPI */ + +#define CURL_EAPI_MAX_RECURSION 7 + +struct Curl_eapi_stack { + uint16_t count; + uint16_t calls[CURL_EAPI_MAX_RECURSION]; +}; + +struct Curl_eapi_guard { + struct Curl_easy *data; /* != NULL if handle stays */ + uint16_t depth; /* > 0 if this guard was entered */ +}; + +bool Curl_eapi_enter(struct Curl_eapi_guard *guard, + CURL *curl, + Curl_eapi_fn fn, + CURLcode *presult); +void Curl_eapi_leave(struct Curl_eapi_guard *guard); + +/* Convert an EAPI failure to a header API result */ +CURLHcode Curl_eapi_hcode(CURLcode result); + +/* Curl_eapi_enter() checks for curl being NULL, but windows compiler + * analyzers do not realize this. *sigh* */ +#define CURL_EAPI_ENTER(g, curl, fn, r) \ + Curl_eapi_enter((g), (curl), CURL_EAPI_FN_##fn, (r)) && (curl) +#define CURL_EAPI_LEAVE(g) \ + Curl_eapi_leave(g) + +/* MAPI */ + +#define CURL_MAPI_MAX_RECURSION 15 + +struct Curl_mapi_stack { + uint16_t count; + uint16_t calls[CURL_MAPI_MAX_RECURSION]; +}; + +struct Curl_mapi_guard { + struct Curl_multi *multi; /* != NULL if handle stays */ + uint16_t depth; /* > 0 if this guard was entered */ +}; + +bool Curl_mapi_enter(struct Curl_mapi_guard *guard, + CURLM *m, + Curl_mapi_fn fn, + CURLMcode *pmresult); +void Curl_mapi_leave(struct Curl_mapi_guard *guard); + +/* Curl_mapi_enter() checks for m being NULL, but windows compiler + * analyzers do not realize this. *sigh* */ +#define CURL_MAPI_ENTER(g, m, fn, r) \ + Curl_mapi_enter((g), (m), CURL_MAPI_FN_##fn, (r)) && (m) +#define CURL_MAPI_LEAVE(g) \ + Curl_mapi_leave(g) + +void Curl_cbapi_enter(struct Curl_mapi_guard *guard, + struct Curl_easy *data, + struct Curl_multi *multi, + Curl_cbapi_fn fn); +void Curl_cbapi_leave(struct Curl_mapi_guard *guard); + +#define CURL_CBAPI_START(g, d, fn) \ + Curl_cbapi_enter((g), (d), NULL, CURL_CBAPI_FN_##fn) +#define CURL_CBAPI_MULTI_START(g, m, fn) \ + Curl_cbapi_enter((g), NULL, (m), CURL_CBAPI_FN_##fn) +#define CURL_CBAPI_END(g) \ + Curl_cbapi_leave(g) +#define CURL_CBAPI_MULTI_END(g) \ + Curl_cbapi_leave(g) + +bool Curl_api_is_in_callback(struct Curl_easy *data); +bool Curl_api_multi_is_in_callback(struct Curl_multi *multi); + +#endif /* HEADER_CURL_API_H */ diff --git a/Utilities/cmcurl/lib/cf-h1-proxy.c b/Utilities/cmcurl/lib/cf-h1-proxy.c index f1e5c7a42a..49b157bb3a 100644 --- a/Utilities/cmcurl/lib/cf-h1-proxy.c +++ b/Utilities/cmcurl/lib/cf-h1-proxy.c @@ -135,7 +135,7 @@ static CURLcode tunnel_init(struct Curl_cfilter *cf, curlx_dyn_init(&ts->rcvbuf, DYN_PROXY_CONNECT_HEADERS); curlx_dyn_init(&ts->request_data, DYN_HTTP_REQUEST); - Curl_httpchunk_init(data, &ts->ch, TRUE); + Curl_httpchunk_init(data, &ts->ch, TRUE, TRUE); *pts = ts; return tunnel_reinit(cf, data, ts); @@ -329,7 +329,7 @@ static CURLcode on_resp_header_udp(struct Curl_cfilter *cf, k->httpcode); } else { - const char *p = header + strlen("Content-Length:"); + const char *p = header + CURL_CSTRLEN("Content-Length:"); if(curlx_str_numblanks(&p, &ts->cl)) { failf(data, "Unsupported Content-Length value"); return CURLE_WEIRD_SERVER_REPLY; @@ -419,7 +419,7 @@ static CURLcode on_resp_header(struct Curl_cfilter *cf, k->httpcode); } else { - const char *p = header + strlen("Content-Length:"); + const char *p = header + CURL_CSTRLEN("Content-Length:"); if(curlx_str_numblanks(&p, &ts->cl)) { failf(data, "Unsupported Content-Length value"); return CURLE_WEIRD_SERVER_REPLY; @@ -980,7 +980,7 @@ CURLcode Curl_cf_h1_proxy_insert_after(struct Curl_cfilter *cf_at, ts->httpversion = httpversion; curlx_dyn_init(&ts->rcvbuf, DYN_PROXY_CONNECT_HEADERS); curlx_dyn_init(&ts->request_data, DYN_HTTP_REQUEST); - Curl_httpchunk_init(data, &ts->ch, TRUE); + Curl_httpchunk_init(data, &ts->ch, TRUE, TRUE); pctx = curlx_calloc(1, sizeof(*pctx)); if(!pctx) { diff --git a/Utilities/cmcurl/lib/cf-h2-proxy.c b/Utilities/cmcurl/lib/cf-h2-proxy.c index be303ffd3e..fde9e1bc79 100644 --- a/Utilities/cmcurl/lib/cf-h2-proxy.c +++ b/Utilities/cmcurl/lib/cf-h2-proxy.c @@ -187,7 +187,7 @@ struct cf_h2_proxy_ctx { struct Curl_peer *dest; /* where to tunnel to */ struct tunnel_stream tunnel; /* our tunnel CONNECT stream */ int32_t goaway_error; - int32_t last_stream_id; + int32_t remote_max_sid; BIT(conn_closed); BIT(rcvd_goaway); BIT(sent_goaway); @@ -500,6 +500,11 @@ static int proxy_h2_on_frame_recv(nghttp2_session *session, break; case NGHTTP2_GOAWAY: ctx->rcvd_goaway = TRUE; + ctx->remote_max_sid = frame->goaway.last_stream_id; + if(data) { + infof(data, "received GOAWAY, error=%u, last_stream=%d", + frame->goaway.error_code, ctx->remote_max_sid); + } break; default: break; @@ -574,7 +579,7 @@ static int proxy_h2_on_header(nghttp2_session *session, return 0; } - if(namelen == sizeof(HTTP_PSEUDO_STATUS) - 1 && + if(namelen == CURL_CSTRLEN(HTTP_PSEUDO_STATUS) && !memcmp(HTTP_PSEUDO_STATUS, name, namelen)) { int http_status; struct http_resp *resp; @@ -911,6 +916,7 @@ static CURLcode cf_h2_proxy_ctx_init(struct Curl_cfilter *cf, Curl_bufq_init(&ctx->inbufq, PROXY_H2_CHUNK_SIZE, PROXY_H2_NW_RECV_CHUNKS); Curl_bufq_init(&ctx->outbufq, PROXY_H2_CHUNK_SIZE, PROXY_H2_NW_SEND_CHUNKS); + ctx->remote_max_sid = INT32_MAX; if(tunnel_stream_init(&ctx->tunnel, ctx->dest)) goto out; @@ -1187,7 +1193,7 @@ static CURLcode tunnel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, else if(ctx->tunnel.reset || (ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) || (ctx->rcvd_goaway && - ctx->last_stream_id < ctx->tunnel.stream_id)) { + ctx->remote_max_sid < ctx->tunnel.stream_id)) { result = CURLE_RECV_ERROR; } else diff --git a/Utilities/cmcurl/lib/cf-haproxy.c b/Utilities/cmcurl/lib/cf-haproxy.c index 16ee25066c..e3286107a3 100644 --- a/Utilities/cmcurl/lib/cf-haproxy.c +++ b/Utilities/cmcurl/lib/cf-haproxy.c @@ -81,8 +81,8 @@ static CURLcode cf_haproxy_date_out_set(struct Curl_cfilter *cf, if(result) return result; - if(data->set.str[STRING_HAPROXY_CLIENT_IP]) { - client_source_ip = data->set.str[STRING_HAPROXY_CLIENT_IP]; + client_source_ip = CURL_EASY_STR(data, STRING_HAPROXY_CLIENT_IP); + if(client_source_ip) { client_dest_ip = client_source_ip; is_ipv6 = !Curl_is_ipv4addr(client_source_ip); } diff --git a/Utilities/cmcurl/lib/cf-https-connect.c b/Utilities/cmcurl/lib/cf-https-connect.c index 1a2e966ef6..2e13a5cc09 100644 --- a/Utilities/cmcurl/lib/cf-https-connect.c +++ b/Utilities/cmcurl/lib/cf-https-connect.c @@ -28,16 +28,16 @@ #include "urldata.h" #include "curl_trc.h" #include "cfilters.h" -#include "cf-dns.h" #include "cf-setup.h" #include "connect.h" -#include "hostip.h" -#include "httpsrr.h" #include "multiif.h" #include "cf-https-connect.h" #include "http2.h" #include "progress.h" #include "select.h" +#include "vdns/cf-dns.h" +#include "vdns/hostip.h" +#include "vdns/httpsrr.h" #include "vquic/vquic.h" typedef enum { @@ -52,7 +52,6 @@ struct cf_hc_baller { const char *name; struct Curl_cfilter *cf; CURLcode result; - struct curltime started; int reply_ms; uint8_t transport; enum alpnid alpn_id; @@ -110,6 +109,7 @@ static CURLcode cf_hc_baller_cntrl(struct cf_hc_baller *b, struct cf_hc_ctx { cf_hc_state state; + struct Curl_peer *destination; /* who we ultimately want to talk to */ struct curltime started; /* when connect started */ CURLcode result; /* overall result */ CURLcode check_h3_result; @@ -123,21 +123,14 @@ struct cf_hc_ctx { BIT(ballers_complete); }; -static void cf_hc_ctx_close(struct Curl_easy *data, - struct cf_hc_ctx *ctx) +static void cf_hc_ctx_destroy(struct Curl_easy *data, + struct cf_hc_ctx *ctx) { if(ctx) { size_t i; for(i = 0; i < ctx->baller_count; ++i) cf_hc_baller_discard(&ctx->ballers[i], data); - } -} - -static void cf_hc_ctx_destroy(struct Curl_easy *data, - struct cf_hc_ctx *ctx) -{ - if(ctx) { - cf_hc_ctx_close(data, ctx); + Curl_peer_unlink(&ctx->destination); curlx_free(ctx); } } @@ -179,7 +172,6 @@ static void cf_hc_baller_init(struct cf_hc_baller *b, struct Curl_cfilter *save = cf->next; cf->next = NULL; - b->started = *Curl_pgrs_now(data); b->result = Curl_cf_setup_insert_after(cf, data, b->transport, CURL_CF_SSL_ENABLE); b->cf = cf->next; @@ -222,7 +214,6 @@ static CURLcode baller_connected(struct Curl_cfilter *cf, ctx->state = CF_HC_SUCCESS; cf->connected = TRUE; - cf_hc_ctx_close(data, ctx); /* ballers may have failf()'d, the winner resets it, so our * errorbuf is clean again. */ Curl_reset_fail(data); @@ -245,7 +236,8 @@ static CURLcode baller_connected(struct Curl_cfilter *cf, } static bool time_to_start_baller2(struct Curl_cfilter *cf, - struct Curl_easy *data) + struct Curl_easy *data, + const struct curltime *pnow) { struct cf_hc_ctx *ctx = cf->ctx; timediff_t elapsed_ms; @@ -260,7 +252,7 @@ static bool time_to_start_baller2(struct Curl_cfilter *cf, return TRUE; } - elapsed_ms = curlx_ptimediff_ms(Curl_pgrs_now(data), &ctx->started); + elapsed_ms = curlx_ptimediff_ms(pnow, &ctx->started); if(elapsed_ms >= ctx->hard_eyeballs_timeout_ms) { CURL_TRC_CF(data, cf, "%s inconclusive after %" FMT_TIMEDIFF_T ", " "starting %s", ctx->ballers[0].name, @@ -296,6 +288,7 @@ static enum alpnid cf_hc_get_httpsrr_alpn(struct Curl_cfilter *cf, enum alpnid not_this_one) { #ifdef USE_HTTPSRR + struct cf_hc_ctx *ctx = cf->ctx; /* Is there an HTTPSRR use its ALPNs here. * We are here after having selected a connection to a host+port and * can no longer change that. Any HTTPSRR advice for other hosts and ports @@ -304,9 +297,9 @@ static enum alpnid cf_hc_get_httpsrr_alpn(struct Curl_cfilter *cf, size_t i; /* Do we have HTTPS-RR information? */ - rr = Curl_conn_dns_get_https( - data, cf->sockindex, Curl_conn_get_destination(cf->conn, cf->sockindex)); + rr = Curl_conn_dns_get_https(data, cf->sockindex, ctx->destination); + CURL_TRC_CF(data, cf, "HTTPS-RR %savailable", rr ? "" : "not "); /* We do not support `rr->no_def_alpn`. */ if(Curl_httpsrr_applicable(data, rr) && !rr->no_def_alpn) { for(i = 0; i < CURL_ARRAYSIZE(rr->alpns); ++i) { @@ -485,6 +478,7 @@ static CURLcode cf_hc_connect(struct Curl_cfilter *cf, { struct cf_hc_ctx *ctx = cf->ctx; CURLcode result = CURLE_OK; + const struct curltime *pnow = NULL; if(cf->connected) { *done = TRUE; @@ -495,7 +489,7 @@ static CURLcode cf_hc_connect(struct Curl_cfilter *cf, if(!ctx->httpsrr_resolved) { ctx->httpsrr_resolved = Curl_conn_dns_resolved_https( - data, cf->sockindex, Curl_conn_get_destination(cf->conn, cf->sockindex)); + data, cf->sockindex, ctx->destination); #ifdef DEBUGBUILD if(!ctx->httpsrr_resolved && getenv("CURL_DBG_AWAIT_HTTPSRR")) { CURL_TRC_CF(data, cf, "awaiting HTTPS-RR"); @@ -519,10 +513,12 @@ static CURLcode cf_hc_connect(struct Curl_cfilter *cf, goto out; } cf_hc_set_baller2(cf, data); - ctx->started = *Curl_pgrs_now(data); + pnow = Curl_pgrs_now(data); + ctx->started = *pnow; cf_hc_baller_init(&ctx->ballers[0], cf, data); if((ctx->baller_count > 1) || !ctx->ballers_complete) { - Curl_expire(data, ctx->soft_eyeballs_timeout_ms, EXPIRE_ALPN_EYEBALLS); + Curl_expire_set(data, EXPIRE_ALPN_EYEBALLS, + ctx->soft_eyeballs_timeout_ms, pnow); } ctx->state = CF_HC_CONNECT; FALLTHROUGH(); @@ -539,7 +535,8 @@ static CURLcode cf_hc_connect(struct Curl_cfilter *cf, } } - if(time_to_start_baller2(cf, data)) { + pnow = Curl_pgrs_now(data); + if(time_to_start_baller2(cf, data, pnow)) { cf_hc_baller_init(&ctx->ballers[1], cf, data); } @@ -658,26 +655,6 @@ static bool cf_hc_data_pending(struct Curl_cfilter *cf, return FALSE; } -static struct curltime cf_get_max_baller_time(struct Curl_cfilter *cf, - struct Curl_easy *data, - int query) -{ - struct cf_hc_ctx *ctx = cf->ctx; - struct curltime t, tmax; - size_t i; - - memset(&tmax, 0, sizeof(tmax)); - for(i = 0; i < ctx->baller_count; i++) { - struct Curl_cfilter *cfb = ctx->ballers[i].cf; - memset(&t, 0, sizeof(t)); - if(cfb && !cfb->cft->query(cfb, data, query, NULL, &t)) { - if((t.tv_sec || t.tv_usec) && curlx_ptimediff_us(&t, &tmax) > 0) - tmax = t; - } - } - return tmax; -} - static CURLcode cf_hc_query(struct Curl_cfilter *cf, struct Curl_easy *data, int query, int *pres1, void *pres2) @@ -687,16 +664,6 @@ static CURLcode cf_hc_query(struct Curl_cfilter *cf, if(!cf->connected) { switch(query) { - case CF_QUERY_TIMER_CONNECT: { - struct curltime *when = pres2; - *when = cf_get_max_baller_time(cf, data, CF_QUERY_TIMER_CONNECT); - return CURLE_OK; - } - case CF_QUERY_TIMER_APPCONNECT: { - struct curltime *when = pres2; - *when = cf_get_max_baller_time(cf, data, CF_QUERY_TIMER_APPCONNECT); - return CURLE_OK; - } case CF_QUERY_NEED_FLUSH: { for(i = 0; i < ctx->baller_count; i++) if(cf_hc_baller_needs_flush(&ctx->ballers[i], data)) { @@ -723,12 +690,26 @@ static CURLcode cf_hc_cntrl(struct Curl_cfilter *cf, size_t i; if(!cf->connected) { - for(i = 0; i < ctx->baller_count; i++) { - result = cf_hc_baller_cntrl(&ctx->ballers[i], data, event, arg1, arg2); - if(result && (result != CURLE_AGAIN)) - goto out; + switch(event) { + case CF_CTRL_REPORT_STATS: + for(i = 0; i < ctx->baller_count; i++) { + /* Make the first baller that connected at network level report */ + if(Curl_conn_cf_is_ip_connected(ctx->ballers[i].cf, data)) { + Curl_conn_cf_cntrl(ctx->ballers[i].cf, data, TRUE, + event, arg1, arg2); + break; + } + } + break; + default: + for(i = 0; i < ctx->baller_count; i++) { + result = cf_hc_baller_cntrl(&ctx->ballers[i], data, event, arg1, arg2); + if(result && (result != CURLE_AGAIN)) + goto out; + } + result = CURLE_OK; + break; } - result = CURLE_OK; } out: return result; @@ -744,7 +725,7 @@ static void cf_hc_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) struct Curl_cftype Curl_cft_http_connect = { "HTTPS-CONNECT", - CF_TYPE_SETUP | CF_TYPE_HTTPSRR, + CF_TYPE_SETUP, CURL_LOG_LVL_NONE, cf_hc_destroy, cf_hc_connect, @@ -761,6 +742,7 @@ struct Curl_cftype Curl_cft_http_connect = { static CURLcode cf_hc_create(struct Curl_cfilter **pcf, struct Curl_easy *data, + struct Curl_peer *destination, uint8_t def_transport) { struct Curl_cfilter *cf = NULL; @@ -772,6 +754,7 @@ static CURLcode cf_hc_create(struct Curl_cfilter **pcf, result = CURLE_OUT_OF_MEMORY; goto out; } + Curl_peer_link(&ctx->destination, destination); ctx->def_transport = def_transport; ctx->hard_eyeballs_timeout_ms = data->set.happy_eyeballs_timeout; ctx->soft_eyeballs_timeout_ms = data->set.happy_eyeballs_timeout / 2; @@ -788,25 +771,32 @@ out: } static CURLcode cf_hc_add(struct Curl_easy *data, + struct Curl_peer *destination, struct connectdata *conn, - int sockindex, + int8_t sockindex, uint8_t def_transport) { struct Curl_cfilter *cf; CURLcode result = CURLE_OK; DEBUGASSERT(data); - result = cf_hc_create(&cf, data, def_transport); + result = cf_hc_create(&cf, data, destination, def_transport); if(result) goto out; Curl_conn_cf_add(data, conn, sockindex, cf); + +#ifdef USE_HTTPSRR + result = Curl_conn_dns_add_https_resolve(data, cf->conn, cf->sockindex, + destination); +#endif out: return result; } CURLcode Curl_cf_https_setup(struct Curl_easy *data, + struct Curl_peer *destination, struct connectdata *conn, - int sockindex) + int8_t sockindex) { CURLcode result = CURLE_OK; @@ -821,7 +811,8 @@ CURLcode Curl_cf_https_setup(struct Curl_easy *data, !conn->bits.tls_enable_alpn) goto out; - result = cf_hc_add(data, conn, sockindex, conn->transport_wanted); + result = cf_hc_add(data, destination, conn, sockindex, + conn->transport_wanted); out: return result; diff --git a/Utilities/cmcurl/lib/cf-https-connect.h b/Utilities/cmcurl/lib/cf-https-connect.h index 3160c0382a..efac194856 100644 --- a/Utilities/cmcurl/lib/cf-https-connect.h +++ b/Utilities/cmcurl/lib/cf-https-connect.h @@ -31,12 +31,14 @@ struct Curl_cfilter; struct Curl_easy; struct connectdata; struct Curl_cftype; +struct Curl_peer; extern struct Curl_cftype Curl_cft_http_connect; CURLcode Curl_cf_https_setup(struct Curl_easy *data, + struct Curl_peer *destination, struct connectdata *conn, - int sockindex); + int8_t sockindex); #endif /* !CURL_DISABLE_HTTP */ #endif /* HEADER_CURL_CF_HTTP_H */ diff --git a/Utilities/cmcurl/lib/cf-ip-happy.c b/Utilities/cmcurl/lib/cf-ip-happy.c index 963ccea94d..baca4682f0 100644 --- a/Utilities/cmcurl/lib/cf-ip-happy.c +++ b/Utilities/cmcurl/lib/cf-ip-happy.c @@ -49,7 +49,6 @@ #include "urldata.h" #include "connect.h" #include "cfilters.h" -#include "cf-dns.h" #include "cf-ip-happy.h" #include "curl_addrinfo.h" #include "curl_trc.h" @@ -57,6 +56,7 @@ #include "progress.h" #include "select.h" #include "sockaddr.h" +#include "vdns/cf-dns.h" #include "vquic/vquic.h" /* for quic cfilters */ @@ -165,7 +165,6 @@ struct cf_ip_attempt { struct Curl_sockaddr_ex addr; struct Curl_cfilter *cf; /* current sub-cfilter connecting */ cf_ip_connect_create *cf_create; - struct curltime started; /* start of current attempt */ CURLcode result; int ai_family; uint8_t transport_peer; @@ -277,7 +276,6 @@ struct cf_ip_ballers { struct Curl_peer *peer; struct Curl_peer *tunnel_peer; cf_ip_connect_create *cf_create; /* for creating cf */ - struct curltime started; struct curltime last_attempt_started; timediff_t attempt_delay_ms; int last_attempt_ai_family; @@ -399,6 +397,7 @@ static CURLcode cf_ip_ballers_run(struct cf_ip_ballers *bs, bool do_more; timediff_t next_expire_ms; uint32_t inconclusive, ongoing; + const struct curltime *pnow = NULL; VERBOSE(int i); if(bs->winner) @@ -409,7 +408,7 @@ evaluate: /* check if a running baller connects now */ VERBOSE(i = -1); - for(panchor = &bs->running; *panchor; panchor = &((*panchor)->next)) { + for(panchor = &bs->running; *panchor; panchor = &(*panchor)->next) { VERBOSE(++i); a = *panchor; a->result = cf_ip_attempt_connect(a, data, connected); @@ -439,9 +438,8 @@ evaluate: "%u ongoing, %u inconclusive", ongoing, inconclusive); /* no attempt connected yet, start another one? */ + pnow = Curl_pgrs_now(data); if(!ongoing) { - if(!bs->started.tv_sec && !bs->started.tv_usec) - bs->started = *Curl_pgrs_now(data); do_more = TRUE; } else { @@ -451,7 +449,7 @@ evaluate: more_possible = cf_ai_iter_has_more(&bs->ipv6_iter, data); #endif do_more = more_possible && - (curlx_ptimediff_ms(Curl_pgrs_now(data), &bs->last_attempt_started) >= + (curlx_ptimediff_ms(pnow, &bs->last_attempt_started) >= bs->attempt_delay_ms); if(do_more) CURL_TRC_CF(data, cf, "happy eyeballs timeout expired, " @@ -509,9 +507,9 @@ evaluate: /* append to running list */ panchor = &bs->running; while(*panchor) - panchor = &((*panchor)->next); + panchor = &(*panchor)->next; *panchor = a; - bs->last_attempt_started = *Curl_pgrs_now(data); + bs->last_attempt_started = *pnow; bs->last_attempt_ai_family = ai_family; /* and run everything again */ goto evaluate; @@ -520,7 +518,7 @@ evaluate: /* tried all addresses, no success but some where inconclusive. * Let's restart the inconclusive ones. */ timediff_t since_ms = - curlx_ptimediff_ms(Curl_pgrs_now(data), &bs->last_attempt_started); + curlx_ptimediff_ms(pnow, &bs->last_attempt_started); timediff_t delay_ms = bs->attempt_delay_ms - since_ms; if(delay_ms <= 0) { CURL_TRC_CF(data, cf, "all attempts inconclusive, restarting one"); @@ -533,7 +531,7 @@ evaluate: CURL_TRC_CF(data, cf, "restarted baller %d -> %d", i, (int)result); if(result) /* serious failure */ goto out; - bs->last_attempt_started = *Curl_pgrs_now(data); + bs->last_attempt_started = *pnow; goto evaluate; } DEBUGASSERT(0); /* should not come here */ @@ -542,7 +540,7 @@ evaluate: /* let's wait some more before restarting */ infof(data, "connect attempts inconclusive, retrying " "in %" FMT_TIMEDIFF_T "ms", delay_ms); - Curl_expire(data, delay_ms, EXPIRE_HAPPY_EYEBALLS); + Curl_expire_set(data, EXPIRE_HAPPY_EYEBALLS, delay_ms, pnow); } /* attempt timeout for restart has not expired yet */ goto out; @@ -565,11 +563,11 @@ out: bool more_possible; /* when do we need to be called again? */ - next_expire_ms = Curl_timeleft_ms(data); + pnow = Curl_pgrs_now(data); + next_expire_ms = Curl_timeleft_now_ms(data, pnow); if(next_expire_ms < 0) { failf(data, "Connection timeout after %" FMT_OFF_T " ms", - curlx_ptimediff_ms(Curl_pgrs_now(data), - &data->progress.t_startsingle)); + Curl_pgrs_since_ms(data, NULL, TIMER_STARTSINGLE)); return CURLE_OPERATION_TIMEDOUT; } @@ -580,8 +578,8 @@ out: #endif if(more_possible) { timediff_t expire_ms, elapsed_ms; - elapsed_ms = - curlx_ptimediff_ms(Curl_pgrs_now(data), &bs->last_attempt_started); + + elapsed_ms = curlx_ptimediff_ms(pnow, &bs->last_attempt_started); expire_ms = CURLMAX(bs->attempt_delay_ms - elapsed_ms, 0); next_expire_ms = CURLMIN(next_expire_ms, expire_ms); if(next_expire_ms <= 0) { @@ -590,7 +588,7 @@ out: } CURL_TRC_CF(data, cf, "next HAPPY_EYEBALLS timeout in %" FMT_TIMEDIFF_T "ms", next_expire_ms); - Curl_expire(data, next_expire_ms, EXPIRE_HAPPY_EYEBALLS); + Curl_expire_set(data, EXPIRE_HAPPY_EYEBALLS, next_expire_ms, pnow); } } return result; @@ -646,24 +644,6 @@ static bool cf_ip_ballers_pending(struct cf_ip_ballers *bs, return FALSE; } -static struct curltime cf_ip_ballers_max_time(struct cf_ip_ballers *bs, - struct Curl_easy *data, - int query) -{ - struct curltime t, tmax; - struct cf_ip_attempt *a; - - memset(&tmax, 0, sizeof(tmax)); - for(a = bs->running; a; a = a->next) { - memset(&t, 0, sizeof(t)); - if(a->cf && !a->cf->cft->query(a->cf, data, query, NULL, &t)) { - if((t.tv_sec || t.tv_usec) && curlx_ptimediff_us(&t, &tmax) > 0) - tmax = t; - } - } - return tmax; -} - static int cf_ip_ballers_min_reply_ms(struct cf_ip_ballers *bs, struct Curl_easy *data) { @@ -690,7 +670,6 @@ struct cf_ip_happy_ctx { cf_ip_connect_create *cf_create; cf_connect_state state; struct cf_ip_ballers ballers; - struct curltime started; BIT(dns_resolved); }; @@ -741,8 +720,7 @@ static CURLcode is_connected(struct Curl_cfilter *cf, proxy_peer ? "over proxy " : "", proxy_peer ? proxy_peer->hostname : "", proxy_peer ? " " : "", - curlx_ptimediff_ms(Curl_pgrs_now(data), - &data->progress.t_startsingle), + Curl_pgrs_since_ms(data, NULL, TIMER_STARTSINGLE), curl_easy_strerror(result)); #ifdef SOCKETIMEDOUT @@ -783,7 +761,6 @@ static CURLcode cf_ip_happy_init(struct Curl_cfilter *cf, CURL_TRC_CF(data, cf, "init ip ballers for transport %u", ctx->ballers.transport_peer); - ctx->started = *Curl_pgrs_now(data); return CURLE_OK; } @@ -857,7 +834,8 @@ static CURLcode cf_ip_happy_connect(struct Curl_cfilter *cf, *done = FALSE; if(!ctx->dns_resolved) { - result = Curl_conn_dns_result(cf->conn, cf->sockindex, ctx->ballers.peer); + result = Curl_conn_dns_addr_result(cf->conn, cf->sockindex, + ctx->ballers.peer); if(!result) ctx->dns_resolved = TRUE; else if(result == CURLE_AGAIN) { @@ -888,10 +866,6 @@ static CURLcode cf_ip_happy_connect(struct Curl_cfilter *cf, cf->connected = TRUE; cf->next = ctx->ballers.winner->cf; ctx->ballers.winner->cf = NULL; - cf_ip_happy_ctx_clear(ctx, data); - Curl_expire_done(data, EXPIRE_HAPPY_EYEBALLS); - /* whatever errors were reported by ballers, clear our errorbuf */ - Curl_reset_fail(data); if(cf->conn->scheme->protocol & PROTO_FAMILY_SSH) Curl_pgrsTime(data, TIMER_APPCONNECT); /* we are connected already */ @@ -900,13 +874,16 @@ static CURLcode cf_ip_happy_connect(struct Curl_cfilter *cf, struct ip_quadruple ipquad; bool is_ipv6; if(!Curl_conn_cf_get_ip_info(cf->next, data, &is_ipv6, &ipquad)) { - const char *host; - Curl_conn_get_current_host(data, cf->sockindex, &host, NULL); CURL_TRC_CF(data, cf, "Connected to %s (%s) port %u", - host, ipquad.remote_ip, ipquad.remote_port); + ctx->ballers.peer->hostname, + ipquad.remote_ip, ipquad.remote_port); } } #endif + cf_ip_happy_ctx_clear(ctx, data); + Curl_expire_clear(data, EXPIRE_HAPPY_EYEBALLS); + /* whatever errors were reported by ballers, clear our errorbuf */ + Curl_reset_fail(data); data->info.numconnects++; /* to track the # of connections made */ } break; @@ -942,18 +919,6 @@ static CURLcode cf_ip_happy_query(struct Curl_cfilter *cf, CURL_TRC_CF(data, cf, "query connect reply: %dms", *pres1); return CURLE_OK; } - case CF_QUERY_TIMER_CONNECT: { - struct curltime *when = pres2; - *when = cf_ip_ballers_max_time(&ctx->ballers, data, - CF_QUERY_TIMER_CONNECT); - return CURLE_OK; - } - case CF_QUERY_TIMER_APPCONNECT: { - struct curltime *when = pres2; - *when = cf_ip_ballers_max_time(&ctx->ballers, data, - CF_QUERY_TIMER_APPCONNECT); - return CURLE_OK; - } default: break; } @@ -1031,13 +996,13 @@ out: return result; } -CURLcode cf_ip_happy_insert_after(struct Curl_cfilter *cf_at, - struct Curl_easy *data, - struct Curl_peer *origin, - struct Curl_peer *peer, - uint8_t transport_peer, - struct Curl_peer *tunnel_peer, - uint8_t tunnel_transport) +CURLcode Curl_cf_ip_happy_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer, + uint8_t transport_peer, + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport) { struct Curl_cfilter *cf; CURLcode result; diff --git a/Utilities/cmcurl/lib/cf-ip-happy.h b/Utilities/cmcurl/lib/cf-ip-happy.h index dd9b29ac8f..88de0c3245 100644 --- a/Utilities/cmcurl/lib/cf-ip-happy.h +++ b/Utilities/cmcurl/lib/cf-ip-happy.h @@ -60,13 +60,13 @@ typedef CURLcode cf_ip_connect_create(struct Curl_cfilter **pcf, * With a `tunnel_peer` present, the filter will be used to proxy tunnel * to it and the tunnel will use `tunnel_transport`. */ -CURLcode cf_ip_happy_insert_after(struct Curl_cfilter *cf_at, - struct Curl_easy *data, - struct Curl_peer *origin, - struct Curl_peer *peer, - uint8_t transport_peer, - struct Curl_peer *tunnel_peer, - uint8_t tunnel_transport); +CURLcode Curl_cf_ip_happy_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer, + uint8_t transport_peer, + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport); extern struct Curl_cftype Curl_cft_ip_happy; diff --git a/Utilities/cmcurl/lib/cf-recvbuf.c b/Utilities/cmcurl/lib/cf-recvbuf.c index 8ccc148bd3..55b01a26f5 100644 --- a/Utilities/cmcurl/lib/cf-recvbuf.c +++ b/Utilities/cmcurl/lib/cf-recvbuf.c @@ -137,7 +137,7 @@ out: CURLcode Curl_cf_recvbuf_add(struct Curl_easy *data, struct connectdata *conn, - int sockindex, + int8_t sockindex, const uint8_t *buf, size_t blen) { struct Curl_cfilter *cf; diff --git a/Utilities/cmcurl/lib/cf-recvbuf.h b/Utilities/cmcurl/lib/cf-recvbuf.h index 1b08fcc3be..e126c3c6b6 100644 --- a/Utilities/cmcurl/lib/cf-recvbuf.h +++ b/Utilities/cmcurl/lib/cf-recvbuf.h @@ -30,7 +30,7 @@ CURLcode Curl_cf_recvbuf_add(struct Curl_easy *data, struct connectdata *conn, - int sockindex, + int8_t sockindex, const uint8_t *buf, size_t blen); extern struct Curl_cftype Curl_cft_recvbuf; diff --git a/Utilities/cmcurl/lib/cf-setup.c b/Utilities/cmcurl/lib/cf-setup.c index 119294a89a..2d77b1cb6d 100644 --- a/Utilities/cmcurl/lib/cf-setup.c +++ b/Utilities/cmcurl/lib/cf-setup.c @@ -159,7 +159,7 @@ static CURLcode cf_setup_add_http_proxy(struct Curl_cfilter *cf, /* Get the origin curl connects its socket to. * Can be origin or the first proxy. */ static struct Curl_peer *conn_get_first_origin(struct connectdata *conn, - int sockindex) + int8_t sockindex) { #ifndef CURL_DISABLE_PROXY if(conn->socks_proxy.peer) @@ -202,9 +202,9 @@ static CURLcode cf_setup_add_ip_happy(struct Curl_cfilter *cf, } #endif /* !CURL_DISABLE_PROXY && !CURL_DISABLE_HTTP */ - result = cf_ip_happy_insert_after(cf, data, first_origin, first_peer, - first_transport, - tunnel_peer, ctx->transport); + result = Curl_cf_ip_happy_insert_after(cf, data, first_origin, first_peer, + first_transport, + tunnel_peer, ctx->transport); if(result) { CURL_TRC_CF(data, cf, "adding happy eyeballs failed -> %d", (int)result); return result; @@ -251,7 +251,7 @@ static CURLcode cf_setup_add_origin_filters(struct Curl_cfilter *cf, (int)result); return result; } - result = Curl_cf_quic_insert_after(cf, origin, peer); + result = Curl_cf_quic_insert_after(cf, data, origin, peer); if(result) { CURL_TRC_CF(data, cf, "adding QUIC filter failed -> %d", (int)result); return result; @@ -273,12 +273,15 @@ static CURLcode cf_setup_add_origin_filters(struct Curl_cfilter *cf, else #endif { - /* Another FTP quirk: when adding SSL verification, to a DATA - * connection, always verify against the control's origin */ - struct Curl_peer *origin = Curl_conn_get_origin(cf->conn, FIRSTSOCKET); - struct Curl_peer *peer = - Curl_conn_get_destination(cf->conn, cf->sockindex); - result = Curl_cf_ssl_insert_after(cf, data, origin, peer); + /* FTP is a bitch. Wherever we really connect to on the DATA + * (secondary) connection, many servers require TLS sessions reuse + * to prove they are talking to the same client. + * For the TLS session lookup to work, we need to instantiate + * the SSL filter with the same peers as FIRSTSOCKET. See #22225 + * Meaning: cf->sockindex does not matter here. */ + result = Curl_cf_ssl_insert_after(cf, data, + Curl_conn_get_origin(cf->conn, FIRSTSOCKET), + Curl_conn_get_destination(cf->conn, FIRSTSOCKET)); } if(result) { CURL_TRC_CF(data, cf, "adding SSL filter for origin failed -> %d", @@ -442,7 +445,7 @@ out: CURLcode Curl_cf_setup_add(struct Curl_easy *data, struct connectdata *conn, - int sockindex, + int8_t sockindex, uint8_t transport, int ssl_mode) { diff --git a/Utilities/cmcurl/lib/cf-setup.h b/Utilities/cmcurl/lib/cf-setup.h index 80e20064b8..71c6093fe5 100644 --- a/Utilities/cmcurl/lib/cf-setup.h +++ b/Utilities/cmcurl/lib/cf-setup.h @@ -32,7 +32,7 @@ struct Curl_str; CURLcode Curl_cf_setup_add(struct Curl_easy *data, struct connectdata *conn, - int sockindex, + int8_t sockindex, uint8_t transport, int ssl_mode); diff --git a/Utilities/cmcurl/lib/cf-socket.c b/Utilities/cmcurl/lib/cf-socket.c index 0741f2ba41..870a98ecbd 100644 --- a/Utilities/cmcurl/lib/cf-socket.c +++ b/Utilities/cmcurl/lib/cf-socket.c @@ -37,6 +37,9 @@ #ifdef HAVE_NETINET_UDP_H #include #endif +#ifdef HAVE_NETINET_IP_H +#include +#endif #ifdef HAVE_SYS_IOCTL_H #include #endif @@ -75,23 +78,25 @@ #include "rand.h" #include "sockaddr.h" #include "curlx/strdup.h" -#include "system_win32.h" #include "curlx/nonblock.h" #include "curlx/strcopy.h" #include "curlx/version_win32.h" #include "curlx/strerr.h" #include "curlx/strparse.h" - +#ifdef _WIN32 +#include /* for TCP_INITIAL_RTO_PARAMETERS */ +#endif /* retrieves ip address and port from a sockaddr structure. note it calls - * curlx_inet_ntop which sets errno on fail, not SOCKERRNO. + * curlx_inet_ntop() and returns CURLcode. * @unittest 1607 */ -UNITTEST bool sockaddr2string(struct sockaddr *sa, curl_socklen_t salen, - char *addr, uint16_t *port); -UNITTEST bool sockaddr2string(struct sockaddr *sa, curl_socklen_t salen, - char *addr, uint16_t *port) +UNITTEST CURLcode sockaddr2string(struct sockaddr *sa, curl_socklen_t salen, + char *addr, uint16_t *port); +UNITTEST CURLcode sockaddr2string(struct sockaddr *sa, curl_socklen_t salen, + char *addr, uint16_t *port) { + CURLcode result; struct sockaddr_in *si = NULL; #ifdef USE_IPV6 struct sockaddr_in6 *si6 = NULL; @@ -105,17 +110,21 @@ UNITTEST bool sockaddr2string(struct sockaddr *sa, curl_socklen_t salen, switch(sa->sa_family) { case AF_INET: si = (struct sockaddr_in *)(void *)sa; - if(curlx_inet_ntop(sa->sa_family, &si->sin_addr, addr, MAX_IPADR_LEN)) { + result = curlx_inet_ntop(sa->sa_family, &si->sin_addr, addr, + MAX_IPADR_LEN); + if(!result) { *port = ntohs(si->sin_port); - return TRUE; + return result; } break; #ifdef USE_IPV6 case AF_INET6: si6 = (struct sockaddr_in6 *)(void *)sa; - if(curlx_inet_ntop(sa->sa_family, &si6->sin6_addr, addr, MAX_IPADR_LEN)) { + result = curlx_inet_ntop(sa->sa_family, &si6->sin6_addr, addr, + MAX_IPADR_LEN); + if(!result) { *port = ntohs(si6->sin6_port); - return TRUE; + return result; } break; #endif @@ -128,16 +137,16 @@ UNITTEST bool sockaddr2string(struct sockaddr *sa, curl_socklen_t salen, else addr[0] = 0; /* socket with no name */ *port = 0; - return TRUE; + return CURLE_OK; #endif default: + result = CURLE_UNSUPPORTED_PROTOCOL; break; } addr[0] = '\0'; *port = 0; - errno = SOCKEAFNOSUPPORT; - return FALSE; + return result; } static void tcpnodelay(struct Curl_cfilter *cf, @@ -430,11 +439,12 @@ static CURLcode socket_open(struct Curl_easy *data, * might have been changed and this 'new' address will actually be used * here to connect. */ - Curl_set_in_callback(data, TRUE); + struct Curl_mapi_guard guard; + CURL_CBAPI_START(&guard, data, easy_fopensocket); *sockfd = data->set.fopensocket(data->set.opensocket_client, CURLSOCKTYPE_IPCXN, (struct curl_sockaddr *)addr); - Curl_set_in_callback(data, FALSE); + CURL_CBAPI_END(&guard); } else { /* opensocket callback not set, so create the socket now */ @@ -521,11 +531,12 @@ static int socket_close(struct Curl_easy *data, struct connectdata *conn, return 0; if(use_callback && conn && conn->fclosesocket) { + struct Curl_mapi_guard guard; int rc; Curl_multi_will_close(data, sock); - Curl_set_in_callback(data, TRUE); + CURL_CBAPI_START(&guard, data, easy_closesocket); rc = conn->fclosesocket(conn->closesocket_client, sock); - Curl_set_in_callback(data, FALSE); + CURL_CBAPI_END(&guard); return rc; } @@ -647,9 +658,9 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, "random" */ /* how many port numbers to try to bind to, increasing one at a time */ int portnum = data->set.localportrange; - const char *dev = data->set.str[STRING_DEVICE]; - const char *iface_input = data->set.str[STRING_INTERFACE]; - const char *host_input = data->set.str[STRING_BINDHOST]; + const char *dev = CURL_EASY_STR(data, STRING_DEVICE); + const char *iface_input = CURL_EASY_STR(data, STRING_INTERFACE); + const char *host_input = CURL_EASY_STR(data, STRING_BINDHOST); const char *iface = iface_input ? iface_input : dev; const char *host = host_input ? host_input : dev; int sockerr; @@ -979,6 +990,7 @@ struct cf_socket_ctx { BIT(accepted); /* socket was accepted, not connected */ BIT(sock_connected); /* socket is "connected", e.g. in UDP */ BIT(active); + BIT(stats_reported); }; static CURLcode cf_socket_ctx_init(struct cf_socket_ctx *ctx, @@ -1083,18 +1095,19 @@ static void set_local_ip(struct Curl_cfilter *cf, /* TFTP does not connect, so it cannot get the IP like this */ struct Curl_sockaddr_storage ssloc; curl_socklen_t slen = sizeof(struct Curl_sockaddr_storage); - VERBOSE(char buffer[STRERROR_LEN]); memset(&ssloc, 0, sizeof(ssloc)); if(getsockname(ctx->sock, (struct sockaddr *)&ssloc, &slen)) { + VERBOSE(char buffer[STRERROR_LEN]); VERBOSE(int sockerr = SOCKERRNO); infof(data, "getsockname() failed with errno %d: %s", sockerr, curlx_strerror(sockerr, buffer, sizeof(buffer))); } - else if(!sockaddr2string((struct sockaddr *)&ssloc, slen, - ctx->ip.local_ip, &ctx->ip.local_port)) { - infof(data, "ssloc inet_ntop() failed with errno %d: %s", - errno, curlx_strerror(errno, buffer, sizeof(buffer))); + else { + CURLcode result = sockaddr2string((struct sockaddr *)&ssloc, slen, + ctx->ip.local_ip, &ctx->ip.local_port); + if(result) + infof(data, "ssloc inet_ntop() failed with %d", (int)result); } } #else @@ -1106,21 +1119,17 @@ static CURLcode set_remote_ip(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_socket_ctx *ctx = cf->ctx; + CURLcode result; /* store remote address and port used in this connection attempt */ ctx->ip.transport = ctx->transport; - if(!sockaddr2string(&ctx->addr.curl_sa_addr, - (curl_socklen_t)ctx->addr.addrlen, - ctx->ip.remote_ip, &ctx->ip.remote_port)) { - char buffer[STRERROR_LEN]; - - /* using bare errno instead of SOCKERRNO is safe here, because - sockaddr2string() calls curlx_inet_ntop(), and they both report failures - via errno (even on Windows builds). */ - ctx->sockerr = errno; + result = sockaddr2string(&ctx->addr.curl_sa_addr, + (curl_socklen_t)ctx->addr.addrlen, + ctx->ip.remote_ip, &ctx->ip.remote_port); + if(result) { + ctx->sockerr = SOCKEAFNOSUPPORT; /* malformed address or bug in inet_ntop, try next address */ - failf(data, "curl_sa_addr inet_ntop() failed with errno %d: %s", - errno, curlx_strerror(errno, buffer, sizeof(buffer))); + failf(data, "curl_sa_addr inet_ntop() failed with %d", (int)result); return CURLE_FAILED_INIT; } return CURLE_OK; @@ -1133,12 +1142,56 @@ static int cf_socktype(int x) #ifdef SOCK_CLOEXEC x &= ~SOCK_CLOEXEC; #endif -#ifdef SOCK_NONBLOCK +#ifdef CURL_USE_SOCK_NONBLOCK x &= ~SOCK_NONBLOCK; #endif return x; } +#ifdef _WIN32 +/* Offered by mingw-w64 v10+, MS SDK 8.0/~VS2012+ */ +#ifndef SIO_TCP_INITIAL_RTO +#define SIO_TCP_INITIAL_RTO _WSAIOW(IOC_VENDOR, 17) +#define TCP_INITIAL_RTO_DEFAULT_RTT 0 + +/* !checksrc! disable TYPEDEFSTRUCT 1 */ +typedef struct _TCP_INITIAL_RTO_PARAMETERS { + USHORT Rtt; + UCHAR MaxSynRetransmissions; +} TCP_INITIAL_RTO_PARAMETERS; +#endif /* SIO_TCP_INITIAL_RTO */ + +#ifndef TCP_INITIAL_RTO_NO_SYN_RETRANSMISSIONS +#define TCP_INITIAL_RTO_NO_SYN_RETRANSMISSIONS 0xFE /* -2 */ +#endif + +static bool targets_localhost(struct cf_socket_ctx *ctx) +{ + return (((ctx->addr.family == AF_INET) && + !strcmp(ctx->ip.remote_ip, "127.0.0.1")) || + ((ctx->addr.family == AF_INET6) && + !strcmp(ctx->ip.remote_ip, "::1"))); +} + +/* disable TCP SYN retransmissions for localhost connection on Windows to + detect problems faster */ +static void tcplocalhost(struct Curl_cfilter *cf, + curl_socket_t sockfd) +{ + if(targets_localhost(cf->ctx)) { + TCP_INITIAL_RTO_PARAMETERS rto; + DWORD bytes = 0; + memset(&rto, 0, sizeof(rto)); + rto.Rtt = TCP_INITIAL_RTO_DEFAULT_RTT; + rto.MaxSynRetransmissions = TCP_INITIAL_RTO_NO_SYN_RETRANSMISSIONS; + (void)WSAIoctl(sockfd, SIO_TCP_INITIAL_RTO, &rto, sizeof(rto), + NULL, 0, &bytes, NULL, NULL); + } +} +#else +#define tcplocalhost(x, y) +#endif /* _WIN32 */ + static CURLcode cf_socket_open(struct Curl_cfilter *cf, struct Curl_easy *data) { @@ -1150,16 +1203,16 @@ static CURLcode cf_socket_open(struct Curl_cfilter *cf, DEBUGASSERT(ctx->sock == CURL_SOCKET_BAD); ctx->started_at = *Curl_pgrs_now(data); -#ifdef SOCK_NONBLOCK +#ifdef CURL_USE_SOCK_NONBLOCK /* Do not tuck SOCK_NONBLOCK into socktype when opensocket callback is set - * because we would not know how socketype is about to be used in the + * because we would not know how socktype is about to be used in the * callback, SOCK_NONBLOCK might get factored out before calling socket(). */ if(!data->set.fopensocket) ctx->addr.socktype |= SOCK_NONBLOCK; #endif result = socket_open(data, &ctx->addr, &ctx->sock); -#ifdef SOCK_NONBLOCK +#ifdef CURL_USE_SOCK_NONBLOCK /* Restore the socktype after the socket is created. */ if(!data->set.fopensocket) ctx->addr.socktype &= ~SOCK_NONBLOCK; @@ -1212,19 +1265,24 @@ static CURLcode cf_socket_open(struct Curl_cfilter *cf, is_tcp = (ctx->addr.family == AF_INET) && cf_socktype(ctx->addr.socktype) == SOCK_STREAM; #endif - if(is_tcp && data->set.tcp_nodelay) - tcpnodelay(cf, data, ctx->sock); + if(is_tcp) { + if(data->set.tcp_nodelay) + tcpnodelay(cf, data, ctx->sock); - if(is_tcp && data->set.tcp_keepalive) - tcpkeepalive(cf, data, ctx->sock); + if(data->set.tcp_keepalive) + tcpkeepalive(cf, data, ctx->sock); + + tcplocalhost(cf, ctx->sock); + } if(data->set.fsockopt) { /* activate callback for setting socket options */ - Curl_set_in_callback(data, TRUE); + struct Curl_mapi_guard guard; + CURL_CBAPI_START(&guard, data, easy_fsockopt); error = data->set.fsockopt(data->set.sockopt_client, ctx->sock, CURLSOCKTYPE_IPCXN); - Curl_set_in_callback(data, FALSE); + CURL_CBAPI_END(&guard); if(error == CURL_SOCKOPT_ALREADY_CONNECTED) isconnected = TRUE; @@ -1255,7 +1313,7 @@ static CURLcode cf_socket_open(struct Curl_cfilter *cf, } #endif -#ifndef SOCK_NONBLOCK +#ifndef CURL_USE_SOCK_NONBLOCK /* Set socket non-blocking, must be a non-blocking socket for * a non-blocking connect. */ error = curlx_nonblock(ctx->sock, TRUE); @@ -1673,6 +1731,26 @@ static CURLcode cf_socket_cntrl(struct Curl_cfilter *cf, case CF_CTRL_FORGET_SOCKET: ctx->sock = CURL_SOCKET_BAD; break; + case CF_CTRL_REPORT_STATS: + if(cf->connected && !ctx->stats_reported) { + struct curltime *ts = NULL; + switch(ctx->transport) { + case TRNSPRT_UDP: + case TRNSPRT_QUIC: + /* Since UDP connected sockets work different from TCP, we use the + * time of the first byte from the peer as the "connect" time. */ + if(ctx->got_first_byte) + ts = &ctx->first_byte_at; + break; + default: + ts = &ctx->connected_at; + break; + } + if(ts) { + Curl_pgrsTimeWas(data, TIMER_CONNECT, *ts); + ctx->stats_reported = TRUE; + } + } } return CURLE_OK; } @@ -1743,24 +1821,6 @@ static CURLcode cf_socket_query(struct Curl_cfilter *cf, else *pres1 = -1; return CURLE_OK; - case CF_QUERY_TIMER_CONNECT: { - struct curltime *when = pres2; - switch(ctx->transport) { - case TRNSPRT_UDP: - case TRNSPRT_QUIC: - /* Since UDP connected sockets work different from TCP, we use the - * time of the first byte from the peer as the "connect" time. */ - if(ctx->got_first_byte) { - *when = ctx->first_byte_at; - break; - } - FALLTHROUGH(); - default: - *when = ctx->connected_at; - break; - } - return CURLE_OK; - } case CF_QUERY_IP_INFO: #ifdef USE_IPV6 *pres1 = (ctx->addr.family == AF_INET6); @@ -1769,6 +1829,12 @@ static CURLcode cf_socket_query(struct Curl_cfilter *cf, #endif *(struct ip_quadruple *)pres2 = ctx->ip; return CURLE_OK; + case CF_QUERY_REALLY_CONNECTED: + if(cf->cft != &Curl_cft_udp) + *pres1 = cf->connected; + else + *pres1 = ctx->got_first_byte; + return CURLE_OK; default: break; } @@ -1879,6 +1945,47 @@ static void linux_quic_gro(struct cf_socket_ctx *ctx) #define linux_quic_gro(x) #endif +#if (defined(__linux__) || defined(__APPLE__)) && defined(IP_RECVTOS) +static void linux_quic_ecn(struct cf_socket_ctx *ctx) +{ + unsigned int tos = 1; + switch(ctx->addr.family) { + case AF_INET: + (void)setsockopt(ctx->sock, IPPROTO_IP, IP_RECVTOS, &tos, sizeof(tos)); + break; +#ifdef IPV6_RECVTCLASS + case AF_INET6: + (void)setsockopt(ctx->sock, IPPROTO_IPV6, IPV6_RECVTCLASS, + &tos, sizeof(tos)); + break; +#endif + } +} +#else +#define linux_quic_ecn(x) +#endif + +#if (defined(__linux__) || defined(__APPLE__)) && defined(IP_DONTFRAG) +static void linux_ip_dontfrag(struct cf_socket_ctx *ctx) +{ + int val = 1; + + switch(ctx->addr.family) { + case AF_INET: + (void)setsockopt(ctx->sock, IPPROTO_IP, IP_DONTFRAG, &val, sizeof(val)); + break; +#ifdef IPV6_DONTFRAG + case AF_INET6: + (void)setsockopt(ctx->sock, IPPROTO_IPV6, IPV6_DONTFRAG, + &val, sizeof(val)); + break; +#endif + } +} +#else +#define linux_ip_dontfrag(x) +#endif + static CURLcode cf_udp_setup_quic(struct Curl_cfilter *cf, struct Curl_easy *data) { @@ -1908,7 +2015,9 @@ static CURLcode cf_udp_setup_quic(struct Curl_cfilter *cf, * non-blocking socket created by cf_socket_open() to it. Thus, we * do not need to call curlx_nonblock() in cf_udp_setup_quic() anymore. */ + linux_quic_ecn(ctx); linux_quic_mtu(ctx); + linux_ip_dontfrag(ctx); linux_quic_gro(ctx); return CURLE_OK; @@ -2102,7 +2211,7 @@ static void cf_tcp_set_accepted_remote_ip(struct Curl_cfilter *cf, { struct cf_socket_ctx *ctx = cf->ctx; #ifdef HAVE_GETPEERNAME - char buffer[STRERROR_LEN]; + CURLcode result; struct Curl_sockaddr_storage ssrem; curl_socklen_t plen; @@ -2111,15 +2220,16 @@ static void cf_tcp_set_accepted_remote_ip(struct Curl_cfilter *cf, plen = sizeof(ssrem); memset(&ssrem, 0, plen); if(getpeername(ctx->sock, (struct sockaddr *)&ssrem, &plen)) { + char buffer[STRERROR_LEN]; int sockerr = SOCKERRNO; failf(data, "getpeername() failed with errno %d: %s", sockerr, curlx_strerror(sockerr, buffer, sizeof(buffer))); return; } - if(!sockaddr2string((struct sockaddr *)&ssrem, plen, - ctx->ip.remote_ip, &ctx->ip.remote_port)) { - failf(data, "ssrem inet_ntop() failed with errno %d: %s", - errno, curlx_strerror(errno, buffer, sizeof(buffer))); + result = sockaddr2string((struct sockaddr *)&ssrem, plen, + ctx->ip.remote_ip, &ctx->ip.remote_port); + if(result) { + failf(data, "ssrem inet_ntop() failed with %d", (int)result); return; } #else @@ -2231,13 +2341,14 @@ static CURLcode cf_tcp_accept_connect(struct Curl_cfilter *cf, ctx->sock, ctx->ip.remote_ip, ctx->ip.remote_port); if(data->set.fsockopt) { + struct Curl_mapi_guard guard; int error = 0; /* activate callback for setting socket options */ - Curl_set_in_callback(data, TRUE); + CURL_CBAPI_START(&guard, data, easy_fsockopt); error = data->set.fsockopt(data->set.sockopt_client, ctx->sock, CURLSOCKTYPE_ACCEPT); - Curl_set_in_callback(data, FALSE); + CURL_CBAPI_END(&guard); if(error) return CURLE_ABORTED_BY_CALLBACK; @@ -2265,7 +2376,7 @@ struct Curl_cftype Curl_cft_tcp_accept = { CURLcode Curl_conn_tcp_listen_set(struct Curl_easy *data, struct connectdata *conn, - int sockindex, curl_socket_t *s) + int8_t sockindex, curl_socket_t *s) { CURLcode result; struct Curl_cfilter *cf = NULL; @@ -2305,7 +2416,7 @@ out: } bool Curl_conn_is_tcp_listen(struct Curl_easy *data, - int sockindex) + int8_t sockindex) { struct Curl_cfilter *cf = data->conn->cfilter[sockindex]; while(cf) { diff --git a/Utilities/cmcurl/lib/cf-socket.h b/Utilities/cmcurl/lib/cf-socket.h index 37ddc02576..f4a6755fe3 100644 --- a/Utilities/cmcurl/lib/cf-socket.h +++ b/Utilities/cmcurl/lib/cf-socket.h @@ -122,7 +122,7 @@ CURLcode Curl_cf_unix_create(struct Curl_cfilter **pcf, */ CURLcode Curl_conn_tcp_listen_set(struct Curl_easy *data, struct connectdata *conn, - int sockindex, + int8_t sockindex, curl_socket_t *s); /** @@ -130,7 +130,7 @@ CURLcode Curl_conn_tcp_listen_set(struct Curl_easy *data, * Curl_conn_tcp_listen_set(). */ bool Curl_conn_is_tcp_listen(struct Curl_easy *data, - int sockindex); + int8_t sockindex); /** * Peek at the socket and remote ip/port the socket filter is using. diff --git a/Utilities/cmcurl/lib/cfilters.c b/Utilities/cmcurl/lib/cfilters.c index fb1914d133..6ffa262eb1 100644 --- a/Utilities/cmcurl/lib/cfilters.c +++ b/Utilities/cmcurl/lib/cfilters.c @@ -128,7 +128,7 @@ CURLcode Curl_cf_def_query(struct Curl_cfilter *cf, #ifdef CURLVERBOSE void Curl_conn_trc_filters(struct Curl_easy *data, - int sockindex, const char *info) + int8_t sockindex, const char *info) { if(CURL_TRC_M_is_verbose(data) && data->conn) { struct Curl_cfilter *cf = data->conn->cfilter[sockindex]; @@ -177,14 +177,15 @@ void Curl_conn_cf_discard_chain(struct Curl_cfilter **pcf, } void Curl_conn_cf_discard_all(struct Curl_easy *data, - struct connectdata *conn, int sockindex) + struct connectdata *conn, int8_t sockindex) { struct curltime *pt = &conn->shutdown.start[sockindex]; memset(pt, 0, sizeof(*pt)); Curl_conn_cf_discard_chain(&conn->cfilter[sockindex], data); } -CURLcode Curl_conn_shutdown(struct Curl_easy *data, int sockindex, bool *done) +CURLcode Curl_conn_shutdown(struct Curl_easy *data, + int8_t sockindex, bool *done) { struct Curl_cfilter *cf; CURLcode result = CURLE_OK; @@ -206,7 +207,7 @@ CURLcode Curl_conn_shutdown(struct Curl_easy *data, int sockindex, bool *done) } *done = FALSE; - if(!Curl_shutdown_started(data, sockindex)) { + if(!Curl_shutdown_started(data->conn, sockindex)) { Curl_shutdown_start(data, sockindex, 0); } else { @@ -239,7 +240,7 @@ CURLcode Curl_conn_shutdown(struct Curl_easy *data, int sockindex, bool *done) return result; } -CURLcode Curl_cf_recv(struct Curl_easy *data, int sockindex, char *buf, +CURLcode Curl_cf_recv(struct Curl_easy *data, int8_t sockindex, char *buf, size_t len, size_t *pnread) { struct Curl_cfilter *cf; @@ -257,7 +258,7 @@ CURLcode Curl_cf_recv(struct Curl_easy *data, int sockindex, char *buf, return CURLE_FAILED_INIT; } -CURLcode Curl_cf_send(struct Curl_easy *data, int sockindex, +CURLcode Curl_cf_send(struct Curl_easy *data, int8_t sockindex, const uint8_t *buf, size_t len, bool eos, size_t *pnwritten) { @@ -358,7 +359,7 @@ out: void Curl_conn_cf_add(struct Curl_easy *data, struct connectdata *conn, - int sockindex, + int8_t sockindex, struct Curl_cfilter *cf) { DEBUGASSERT(conn); @@ -408,7 +409,7 @@ bool Curl_conn_cf_discard(struct Curl_cfilter **pcf, found = TRUE; break; } - pprev = &((*pprev)->next); + pprev = &(*pprev)->next; } } Curl_conn_cf_discard_chain(pcf, data); @@ -450,9 +451,9 @@ static CURLcode cf_cntrl_all(struct connectdata *conn, int event, int arg1, void *arg2) { CURLcode result = CURLE_OK; - size_t i; + int i; - for(i = 0; i < CURL_ARRAYSIZE(conn->cfilter); ++i) { + for(i = 0; i < (int)CURL_ARRAYSIZE(conn->cfilter); ++i) { result = Curl_conn_cf_cntrl(conn->cfilter[i], data, ignore_result, event, arg1, arg2); if(!ignore_result && result) @@ -461,14 +462,34 @@ static CURLcode cf_cntrl_all(struct connectdata *conn, return result; } +bool Curl_conn_cf_is_ip_connected(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + if(cf) { + int value = 0; + if(!cf->cft->query(cf, data, CF_QUERY_REALLY_CONNECTED, &value, NULL)) + return !!value; + } + return FALSE; +} + void Curl_conn_cntrl_update_info(struct Curl_easy *data, struct connectdata *conn) { cf_cntrl_all(conn, data, TRUE, CF_CTRL_CONN_INFO_UPDATE, 0, NULL); } +void Curl_conn_cntrl_report_stats(struct Curl_easy *data, + struct connectdata *conn, + int sockindex) +{ + if((unsigned)sockindex < CURL_ARRAYSIZE(conn->cfilter)) + (void)Curl_conn_cf_cntrl(conn->cfilter[sockindex], data, TRUE, + CF_CTRL_REPORT_STATS, 0, NULL); +} + void Curl_conn_remove_setup_filters(struct Curl_easy *data, - int sockindex) + int8_t sockindex) { struct Curl_cfilter **anchor = &data->conn->cfilter[sockindex]; while(*anchor) { @@ -485,14 +506,14 @@ void Curl_conn_remove_setup_filters(struct Curl_easy *data, } } -bool Curl_conn_is_setup(struct connectdata *conn, int sockindex) +bool Curl_conn_is_setup(struct connectdata *conn, int8_t sockindex) { if(!CONN_SOCK_IDX_VALID(sockindex)) return FALSE; return !!conn->cfilter[sockindex]; } -bool Curl_conn_is_connected(struct connectdata *conn, int sockindex) +bool Curl_conn_is_connected(struct connectdata *conn, int8_t sockindex) { struct Curl_cfilter *cf; @@ -506,7 +527,7 @@ bool Curl_conn_is_connected(struct connectdata *conn, int sockindex) return FALSE; } -bool Curl_conn_is_ip_connected(struct Curl_easy *data, int sockindex) +bool Curl_conn_is_ip_connected(struct Curl_easy *data, int8_t sockindex) { struct Curl_cfilter *cf; @@ -533,14 +554,14 @@ static bool cf_is_tunneling(struct Curl_cfilter *cf) return FALSE; } -bool Curl_conn_is_tunneling(struct connectdata *conn, int sockindex) +bool Curl_conn_is_tunneling(struct connectdata *conn, int8_t sockindex) { if(!CONN_SOCK_IDX_VALID(sockindex)) return FALSE; return conn ? cf_is_tunneling(conn->cfilter[sockindex]) : FALSE; } #else -bool Curl_conn_is_tunneling(struct connectdata *conn, int sockindex) +bool Curl_conn_is_tunneling(struct connectdata *conn, int8_t sockindex) { (void)conn; (void)sockindex; @@ -561,7 +582,7 @@ static bool cf_is_ssl(struct Curl_cfilter *cf) return FALSE; } -bool Curl_conn_is_ssl(struct connectdata *conn, int sockindex) +bool Curl_conn_is_ssl(struct connectdata *conn, int8_t sockindex) { if(!CONN_SOCK_IDX_VALID(sockindex)) return FALSE; @@ -569,7 +590,7 @@ bool Curl_conn_is_ssl(struct connectdata *conn, int sockindex) } bool Curl_conn_get_ssl_info(struct Curl_easy *data, - struct connectdata *conn, int sockindex, + struct connectdata *conn, int8_t sockindex, int query, struct curl_tlssessioninfo *info) { @@ -586,7 +607,7 @@ bool Curl_conn_get_ssl_info(struct Curl_easy *data, } CURLcode Curl_conn_get_ip_info(struct Curl_easy *data, - struct connectdata *conn, int sockindex, + struct connectdata *conn, int8_t sockindex, bool *is_ipv6, struct ip_quadruple *ipquad) { struct Curl_cfilter *cf; @@ -596,15 +617,13 @@ CURLcode Curl_conn_get_ip_info(struct Curl_easy *data, return Curl_conn_cf_get_ip_info(cf, data, is_ipv6, ipquad); } -bool Curl_conn_is_multiplex(struct connectdata *conn, int sockindex) +bool Curl_conn_is_multiplex(struct connectdata *conn, int8_t sockindex) { struct Curl_cfilter *cf; - if(!CONN_SOCK_IDX_VALID(sockindex)) + if(!conn || !CONN_SOCK_IDX_VALID(sockindex)) return FALSE; - cf = conn ? conn->cfilter[sockindex] : NULL; - - for(; cf; cf = cf->next) { + for(cf = conn->cfilter[sockindex]; cf; cf = cf->next) { if(cf->cft->flags & CF_TYPE_MULTIPLEX) return TRUE; if(cf->cft->flags & (CF_TYPE_IP_CONNECT | CF_TYPE_SSL)) @@ -644,17 +663,6 @@ int Curl_protocol_for_transport(uint8_t transport) } } -bool Curl_conn_cf_wants_httpsrr(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - (void)data; - for(; cf; cf = cf->next) { - if(cf->cft->flags & CF_TYPE_HTTPSRR) - return TRUE; - } - return FALSE; -} - const char *Curl_conn_get_alpn_negotiated(struct Curl_easy *data, struct connectdata *conn) { @@ -686,7 +694,7 @@ unsigned char Curl_conn_http_version(struct Curl_easy *data, return (unsigned char)(result ? 0 : v); } -bool Curl_conn_data_pending(struct Curl_easy *data, int sockindex) +bool Curl_conn_data_pending(struct Curl_easy *data, int8_t sockindex) { struct Curl_cfilter *cf; @@ -716,7 +724,7 @@ bool Curl_conn_cf_needs_flush(struct Curl_cfilter *cf, return (result || !pending) ? FALSE : TRUE; } -bool Curl_conn_needs_flush(struct Curl_easy *data, int sockindex) +bool Curl_conn_needs_flush(struct Curl_easy *data, int8_t sockindex) { if(!CONN_SOCK_IDX_VALID(sockindex)) return FALSE; @@ -728,17 +736,17 @@ CURLcode Curl_conn_cf_adjust_pollset(struct Curl_cfilter *cf, struct easy_pollset *ps) { CURLcode result = CURLE_OK; - /* Get the lowest not-connected filter, if there are any */ - while(cf && !cf->connected && cf->next && !cf->next->connected) - cf = cf->next; - /* Skip all filters that have already shut down */ - while(cf && cf->shutdown) - cf = cf->next; - /* From there on, give all filters a chance to adjust the pollset. - * Lower filters are called later, so they may override */ - while(cf && !result) { - result = cf->cft->adjust_pollset(cf, data, ps); - cf = cf->next; + /* Go through all filters, top to bottom, and let them manage the pollset + * - connected filters can do so + * - CF_TYPE_DNS filters can + * - unconnected filters without next or connect next can + */ + for(; cf && !result; cf = cf->next) { + if(cf->shutdown) + continue; + if(cf->connected || (cf->cft->flags & CF_TYPE_DNS) || + !cf->next || cf->next->connected) + result = cf->cft->adjust_pollset(cf, data, ps); } return result; } @@ -748,23 +756,29 @@ CURLcode Curl_conn_adjust_pollset(struct Curl_easy *data, struct easy_pollset *ps) { CURLcode result = CURLE_OK; - int i; + bool want_io = !!ps->n; + int8_t i; DEBUGASSERT(data); DEBUGASSERT(conn); /* During connect time, connection filters may add sockets to the pollset * even when the transfer neither wants to send nor receive. And those - * sockets, when having events, are served. - * Once connected however, a transfer that neither wants to send nor receive + * sockets, when having events, are served. The same applies to a + * filter chain whose shutdown has started. + * Once a filter chain is connected however and before its shutdown + * starts, a transfer that neither wants to send nor receive * will never call the connection filters. Any sockets added by the filters * will not change state and POLLIN/POLLOUT events will trigger forever, - * making us busy loop. See #21671 */ - if(ps->n || !Curl_conn_is_connected(conn, FIRSTSOCKET) || - (conn->cfilter[SECONDARYSOCKET] && - !Curl_conn_is_connected(conn, SECONDARYSOCKET))) { - for(i = 0; (i < 2) && !result && conn; ++i) { + * making us busy loop. See #21671. + * Gate each filter chain on its own state, so that one chain being in + * connect or shutdown does not add poll events for the other. Check + * against the transfer's own interest, before any chain added sockets + * of its own. */ + for(i = 0; (i < (int)CURL_ARRAYSIZE(conn->cfilter)) && !result; ++i) { + if(conn->cfilter[i] && + (want_io || !Curl_conn_is_connected(conn, i) || + Curl_shutdown_started(conn, i))) result = Curl_conn_cf_adjust_pollset(conn->cfilter[i], data, ps); - } } return result; } @@ -797,7 +811,7 @@ int Curl_conn_cf_poll(struct Curl_cfilter *cf, return rc; } -void Curl_conn_get_current_host(struct Curl_easy *data, int sockindex, +void Curl_conn_get_current_host(struct Curl_easy *data, int8_t sockindex, const char **phost, int *pport) { struct Curl_cfilter *cf, *cf_proxy = NULL; @@ -931,7 +945,7 @@ curl_socket_t Curl_conn_get_first_socket(struct Curl_easy *data) } const struct Curl_sockaddr_ex *Curl_conn_get_remote_addr( - struct Curl_easy *data, int sockindex) + struct Curl_easy *data, int8_t sockindex) { struct Curl_cfilter *cf = (data->conn && CONN_SOCK_IDX_VALID(sockindex)) ? @@ -944,7 +958,7 @@ CURLcode Curl_conn_ev_data_setup(struct Curl_easy *data) return cf_cntrl_all(data->conn, data, FALSE, CF_CTRL_DATA_SETUP, 0, NULL); } -CURLcode Curl_conn_flush(struct Curl_easy *data, int sockindex) +CURLcode Curl_conn_flush(struct Curl_easy *data, int8_t sockindex) { if(!CONN_SOCK_IDX_VALID(sockindex)) return CURLE_BAD_FUNCTION_ARGUMENT; @@ -985,20 +999,22 @@ bool Curl_conn_is_alive(struct Curl_easy *data, struct connectdata *conn, } CURLcode Curl_conn_keep_alive(struct Curl_easy *data, - struct connectdata *conn, - int sockindex) + struct connectdata *conn) { - struct Curl_cfilter *cf; + CURLcode result = CURLE_OK; + int i; - if(!CONN_SOCK_IDX_VALID(sockindex)) - return CURLE_BAD_FUNCTION_ARGUMENT; - cf = conn->cfilter[sockindex]; - return cf ? cf->cft->keep_alive(cf, data) : CURLE_OK; + for(i = 0; (i < (int)CURL_ARRAYSIZE(conn->cfilter)) && !result; ++i) { + struct Curl_cfilter *cf = conn->cfilter[i]; + if(cf) + result = cf->cft->keep_alive(cf, data); + } + return result; } size_t Curl_conn_get_max_concurrent(struct Curl_easy *data, struct connectdata *conn, - int sockindex) + int8_t sockindex) { struct Curl_cfilter *cf; CURLcode result; @@ -1018,7 +1034,7 @@ size_t Curl_conn_get_max_concurrent(struct Curl_easy *data, int Curl_conn_get_stream_error(struct Curl_easy *data, struct connectdata *conn, - int sockindex) + int8_t sockindex) { struct Curl_cfilter *cf; CURLcode result; @@ -1033,7 +1049,7 @@ int Curl_conn_get_stream_error(struct Curl_easy *data, return (result || n < 0) ? 0 : n; } -int Curl_conn_sockindex(struct Curl_easy *data, curl_socket_t sockfd) +int8_t Curl_conn_sockindex(struct Curl_easy *data, curl_socket_t sockfd) { if(data && data->conn && sockfd != CURL_SOCKET_BAD && sockfd == data->conn->sock[SECONDARYSOCKET]) @@ -1041,7 +1057,7 @@ int Curl_conn_sockindex(struct Curl_easy *data, curl_socket_t sockfd) return FIRSTSOCKET; } -CURLcode Curl_conn_recv(struct Curl_easy *data, int sockindex, +CURLcode Curl_conn_recv(struct Curl_easy *data, int8_t sockindex, char *buf, size_t len, size_t *pnread) { DEBUGASSERT(data); @@ -1054,7 +1070,7 @@ CURLcode Curl_conn_recv(struct Curl_easy *data, int sockindex, return CURLE_FAILED_INIT; } -CURLcode Curl_conn_send(struct Curl_easy *data, int sockindex, +CURLcode Curl_conn_send(struct Curl_easy *data, int8_t sockindex, const void *buf, size_t len, bool eos, size_t *pnwritten) { diff --git a/Utilities/cmcurl/lib/cfilters.h b/Utilities/cmcurl/lib/cfilters.h index b1aad029e0..979275b729 100644 --- a/Utilities/cmcurl/lib/cfilters.h +++ b/Utilities/cmcurl/lib/cfilters.h @@ -121,6 +121,7 @@ typedef CURLcode Curl_cft_conn_keep_alive(struct Curl_cfilter *cf, #define CF_CTRL_CONN_INFO_UPDATE (256 + 0) /* 0 NULL ignored */ #define CF_CTRL_FORGET_SOCKET (256 + 1) /* 0 NULL ignored */ #define CF_CTRL_FLUSH (256 + 2) /* 0 NULL first fail */ +#define CF_CTRL_REPORT_STATS (256 + 3) /* 0 NULL ignored */ /** * Handle event/control for the filter. @@ -156,13 +157,17 @@ typedef CURLcode Curl_cft_cntrl(struct Curl_cfilter *cf, null-terminated string or NULL if none selected/handshake not done. Implemented by filter types CF_TYPE_SSL or CF_TYPE_IP_CONNECT. + * - CF_QUERY_REALLY_CONNECTED: implemented in socket filters to return + * if a reply from a server has really arrived. For + * non-UDP sockets this is TRUE when the socket became + * writable. For UDP sockets, this is TRUE when the + * first byte from the peer was received. */ /* query res1 res2 */ #define CF_QUERY_MAX_CONCURRENT 1 /* number - */ #define CF_QUERY_CONNECT_REPLY_MS 2 /* number - */ #define CF_QUERY_SOCKET 3 /* - curl_socket_t */ -#define CF_QUERY_TIMER_CONNECT 4 /* - struct curltime */ -#define CF_QUERY_TIMER_APPCONNECT 5 /* - struct curltime */ +/* unused 4 + 5 */ #define CF_QUERY_STREAM_ERROR 6 /* error code - */ #define CF_QUERY_NEED_FLUSH 7 /* TRUE/FALSE - */ #define CF_QUERY_IP_INFO 8 /* TRUE/FALSE struct ip_quadruple */ @@ -175,6 +180,7 @@ typedef CURLcode Curl_cft_cntrl(struct Curl_cfilter *cf, #define CF_QUERY_SSL_CTX_INFO 13 /* - struct curl_tlssessioninfo * */ #define CF_QUERY_TRANSPORT 14 /* TRNSPRT_* - * */ #define CF_QUERY_ALPN_NEGOTIATED 15 /* - const char * */ +#define CF_QUERY_REALLY_CONNECTED 16 /* TRUE/FALSE - */ /** * Query the cfilter for properties. Filters ignorant of a query will @@ -197,7 +203,7 @@ typedef CURLcode Curl_cft_query(struct Curl_cfilter *cf, * CF_TYPE_HTTP implement a version of the HTTP protocol * CF_TYPE_SETUP filter is only needed for connection setup and * can be removed once connected - * CF_TYPE_HTTPSRR filter that wants HTTPS-RR information + * CF_TYPE_DNS filter managing DNS resolve operations */ #define CF_TYPE_IP_CONNECT (1 << 0) #define CF_TYPE_SSL (1 << 1) @@ -205,7 +211,7 @@ typedef CURLcode Curl_cft_query(struct Curl_cfilter *cf, #define CF_TYPE_PROXY (1 << 3) #define CF_TYPE_HTTP (1 << 4) #define CF_TYPE_SETUP (1 << 5) -#define CF_TYPE_HTTPSRR (1 << 6) +#define CF_TYPE_DNS (1 << 6) /* A connection filter type, e.g. specific implementation. */ struct Curl_cftype { @@ -231,7 +237,7 @@ struct Curl_cfilter { struct Curl_cfilter *next; /* next filter in chain */ void *ctx; /* filter type specific settings */ struct connectdata *conn; /* the connection this filter belongs to */ - int sockindex; /* the index the filter is installed at */ + int8_t sockindex; /* the index the filter is installed at */ BIT(connected); /* != 0 iff this filter is connected */ BIT(shutdown); /* != 0 iff this filter has shut down */ }; @@ -286,7 +292,7 @@ CURLcode Curl_cf_create(struct Curl_cfilter **pcf, */ void Curl_conn_cf_add(struct Curl_easy *data, struct connectdata *conn, - int sockindex, + int8_t sockindex, struct Curl_cfilter *cf); /** @@ -314,7 +320,7 @@ void Curl_conn_cf_discard_chain(struct Curl_cfilter **pcf, * Remove and destroy all filters at chain `sockindex` on connection `conn`. */ void Curl_conn_cf_discard_all(struct Curl_easy *data, - struct connectdata *conn, int sockindex); + struct connectdata *conn, int8_t sockindex); CURLcode Curl_conn_cf_connect(struct Curl_cfilter *cf, struct Curl_easy *data, @@ -343,6 +349,9 @@ CURLcode Curl_conn_cf_get_ip_info(struct Curl_cfilter *cf, bool Curl_conn_cf_needs_flush(struct Curl_cfilter *cf, struct Curl_easy *data); +bool Curl_conn_cf_is_ip_connected(struct Curl_cfilter *cf, + struct Curl_easy *data); + unsigned char Curl_conn_cf_get_transport(struct Curl_cfilter *cf, struct Curl_easy *data); @@ -352,10 +361,6 @@ int Curl_protocol_for_transport(uint8_t transport); const char *Curl_conn_cf_get_alpn_negotiated(struct Curl_cfilter *cf, struct Curl_easy *data); -/* The filter (or one of its sub-filters) wants HTTPS-RR information. */ -bool Curl_conn_cf_wants_httpsrr(struct Curl_cfilter *cf, - struct Curl_easy *data); - #define CURL_CF_SSL_DEFAULT (-1) #define CURL_CF_SSL_DISABLE 0 #define CURL_CF_SSL_ENABLE 1 @@ -363,31 +368,31 @@ bool Curl_conn_cf_wants_httpsrr(struct Curl_cfilter *cf, /** * Check if a filter chain at `sockindex` for connection `conn` exists. */ -bool Curl_conn_is_setup(struct connectdata *conn, int sockindex); +bool Curl_conn_is_setup(struct connectdata *conn, int8_t sockindex); /** * Check if the filter chain at `sockindex` for connection `conn` is * completely connected. */ -bool Curl_conn_is_connected(struct connectdata *conn, int sockindex); +bool Curl_conn_is_connected(struct connectdata *conn, int8_t sockindex); /** * Determine if we have reached the remote host on IP level, e.g. * have a TCP connection. This turns TRUE before a possible SSL * handshake has been started/done. */ -bool Curl_conn_is_ip_connected(struct Curl_easy *data, int sockindex); +bool Curl_conn_is_ip_connected(struct Curl_easy *data, int8_t sockindex); /** * Determine if the connection is using SSL to the remote host * (or will be once connected). This will return FALSE, if SSL * is only used in proxying and not for the tunnel itself. */ -bool Curl_conn_is_ssl(struct connectdata *conn, int sockindex); +bool Curl_conn_is_ssl(struct connectdata *conn, int8_t sockindex); /* Determine if the connection has one or more proxy filters. * e.g. is tunneling. */ -bool Curl_conn_is_tunneling(struct connectdata *conn, int sockindex); +bool Curl_conn_is_tunneling(struct connectdata *conn, int8_t sockindex); /* * Fill `info` with information about the TLS instance securing the connection @@ -395,18 +400,18 @@ bool Curl_conn_is_tunneling(struct connectdata *conn, int sockindex); * FALSE. 'query' should be CF_QUERY_SSL_INFO or CF_QUERY_SSL_CTX_INFO. */ bool Curl_conn_get_ssl_info(struct Curl_easy *data, - struct connectdata *conn, int sockindex, + struct connectdata *conn, int8_t sockindex, int query, struct curl_tlssessioninfo *info); CURLcode Curl_conn_get_ip_info(struct Curl_easy *data, - struct connectdata *conn, int sockindex, + struct connectdata *conn, int8_t sockindex, bool *is_ipv6, struct ip_quadruple *ipquad); /** * Connection provides multiplexing of easy handles at `socketindex`. */ -bool Curl_conn_is_multiplex(struct connectdata *conn, int sockindex); +bool Curl_conn_is_multiplex(struct connectdata *conn, int8_t sockindex); /** * Return the HTTP version used on the FIRSTSOCKET connection filters @@ -426,33 +431,38 @@ const char *Curl_conn_get_alpn_negotiated(struct Curl_easy *data, void Curl_conn_cntrl_update_info(struct Curl_easy *data, struct connectdata *conn); +void Curl_conn_cntrl_report_stats(struct Curl_easy *data, + struct connectdata *conn, + int sockindex); + void Curl_conn_remove_setup_filters(struct Curl_easy *data, - int sockindex); + int8_t sockindex); /** * Shutdown the connection at `sockindex` non-blocking, using timeout * from `data->set.shutdowntimeout`, default DEFAULT_SHUTDOWN_TIMEOUT_MS. * Return CURLE_OK and *done == FALSE if not finished. */ -CURLcode Curl_conn_shutdown(struct Curl_easy *data, int sockindex, bool *done); +CURLcode Curl_conn_shutdown(struct Curl_easy *data, + int8_t sockindex, bool *done); /** * Return if data is pending in some connection filter at chain * `sockindex` for connection `data->conn`. */ bool Curl_conn_data_pending(struct Curl_easy *data, - int sockindex); + int8_t sockindex); /** * Return TRUE if any of the connection filters at chain `sockindex` * have data still to send. */ -bool Curl_conn_needs_flush(struct Curl_easy *data, int sockindex); +bool Curl_conn_needs_flush(struct Curl_easy *data, int8_t sockindex); /** * Flush any pending data on the connection filters at chain `sockindex`. */ -CURLcode Curl_conn_flush(struct Curl_easy *data, int sockindex); +CURLcode Curl_conn_flush(struct Curl_easy *data, int8_t sockindex); /** * Return the socket used on data's connection for FIRSTSOCKET, @@ -463,12 +473,12 @@ curl_socket_t Curl_conn_get_first_socket(struct Curl_easy *data); /* Return a pointer to the connected socket address or NULL. */ const struct Curl_sockaddr_ex * -Curl_conn_get_remote_addr(struct Curl_easy *data, int sockindex); +Curl_conn_get_remote_addr(struct Curl_easy *data, int8_t sockindex); /** * Tell filters to forget about the socket at sockindex. */ -void Curl_conn_forget_socket(struct Curl_easy *data, int sockindex); +void Curl_conn_forget_socket(struct Curl_easy *data, int8_t sockindex); /** * Adjust the pollset for the filter chain starting at `cf`. @@ -498,7 +508,7 @@ int Curl_conn_cf_poll(struct Curl_cfilter *cf, * `data->conn`. Copy at most `len` bytes into `buf`. Return the * actual number of bytes copied in `*pnread`or an error. */ -CURLcode Curl_cf_recv(struct Curl_easy *data, int sockindex, char *buf, +CURLcode Curl_cf_recv(struct Curl_easy *data, int8_t sockindex, char *buf, size_t len, size_t *pnread); /** @@ -506,7 +516,7 @@ CURLcode Curl_cf_recv(struct Curl_easy *data, int sockindex, char *buf, * at connection `data->conn`. Return the actual number of bytes written * in `*pnwritten` or on error. */ -CURLcode Curl_cf_send(struct Curl_easy *data, int sockindex, +CURLcode Curl_cf_send(struct Curl_easy *data, int8_t sockindex, const uint8_t *buf, size_t len, bool eos, size_t *pnwritten); @@ -562,11 +572,10 @@ bool Curl_conn_is_alive(struct Curl_easy *data, struct connectdata *conn, bool *input_pending); /** - * Try to upkeep the connection filters at sockindex. + * Try to upkeep the connection filters. */ CURLcode Curl_conn_keep_alive(struct Curl_easy *data, - struct connectdata *conn, - int sockindex); + struct connectdata *conn); /** * Get the remote hostname and port that the connection is currently @@ -576,7 +585,7 @@ CURLcode Curl_conn_keep_alive(struct Curl_easy *data, * During connect, when tunneling proxies are involved (http or socks), * it will be the name and port the proxy currently negotiates with. */ -void Curl_conn_get_current_host(struct Curl_easy *data, int sockindex, +void Curl_conn_get_current_host(struct Curl_easy *data, int8_t sockindex, const char **phost, int *pport); /** @@ -585,18 +594,18 @@ void Curl_conn_get_current_host(struct Curl_easy *data, int sockindex, */ size_t Curl_conn_get_max_concurrent(struct Curl_easy *data, struct connectdata *conn, - int sockindex); + int8_t 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); + int8_t sockindex); #ifdef CURLVERBOSE void Curl_conn_trc_filters(struct Curl_easy *data, - int sockindex, const char *info); + int8_t sockindex, const char *info); #endif /** @@ -604,20 +613,20 @@ void Curl_conn_trc_filters(struct Curl_easy *data, * Useful in calling `Curl_conn_send()/Curl_conn_recv()` with the * correct socket index. */ -int Curl_conn_sockindex(struct Curl_easy *data, curl_socket_t sockfd); +int8_t Curl_conn_sockindex(struct Curl_easy *data, curl_socket_t sockfd); /* * Receive data on the connection, using FIRSTSOCKET/SECONDARYSOCKET. * Return CURLE_AGAIN iff blocked on receiving. */ -CURLcode Curl_conn_recv(struct Curl_easy *data, int sockindex, +CURLcode Curl_conn_recv(struct Curl_easy *data, int8_t sockindex, char *buf, size_t len, size_t *pnread); /* * Send data on the connection, using FIRSTSOCKET/SECONDARYSOCKET. * Return CURLE_AGAIN iff blocked on sending. */ -CURLcode Curl_conn_send(struct Curl_easy *data, int sockindex, +CURLcode Curl_conn_send(struct Curl_easy *data, int8_t sockindex, const void *buf, size_t len, bool eos, size_t *pnwritten); diff --git a/Utilities/cmcurl/lib/conncache.c b/Utilities/cmcurl/lib/conncache.c index 5ba2368622..01681d4ebb 100644 --- a/Utilities/cmcurl/lib/conncache.c +++ b/Utilities/cmcurl/lib/conncache.c @@ -42,7 +42,7 @@ do { \ if(c) { \ if(CURL_SHARE_KEEP_CONNECT((c)->share)) \ - Curl_share_lock((d), CURL_LOCK_DATA_CONNECT, \ + Curl_share_lock_share((c)->share, (d), CURL_LOCK_DATA_CONNECT, \ CURL_LOCK_ACCESS_SINGLE); \ DEBUGASSERT(!(c)->locked); \ (c)->locked = TRUE; \ @@ -55,7 +55,7 @@ DEBUGASSERT((c)->locked); \ (c)->locked = FALSE; \ if(CURL_SHARE_KEEP_CONNECT((c)->share)) \ - Curl_share_unlock((d), CURL_LOCK_DATA_CONNECT); \ + Curl_share_unlock_share((c)->share, (d), CURL_LOCK_DATA_CONNECT); \ } \ } while(0) @@ -111,16 +111,12 @@ static void cpool_bundle_free_entry(void *freethis) } void Curl_cpool_init(struct cpool *cpool, - struct Curl_easy *idata, struct Curl_share *share, size_t size) { Curl_hash_init(&cpool->dest2bundle, size, Curl_hash_str, curlx_str_key_compare, cpool_bundle_free_entry); - DEBUGASSERT(idata); - - cpool->idata = idata; cpool->share = share; cpool->initialized = TRUE; } @@ -186,6 +182,8 @@ static void cpool_discard_conn(struct cpool *cpool, struct connectdata *conn, bool aborted) { + struct cshutdn *cshutdn; + struct Curl_easy *admin; bool done = FALSE; DEBUGASSERT(data); @@ -193,12 +191,13 @@ static void cpool_discard_conn(struct cpool *cpool, DEBUGASSERT(cpool); DEBUGASSERT(!conn->bits.in_cpool); + admin = Curl_get_admin(data); /* * If this connection is not marked to force-close, leave it open if there * are other users of it */ if(CONN_INUSE(conn) && !aborted) { - CURL_TRC_M(data, "[CPOOL] not discarding #%" FMT_OFF_T + CURL_TRC_M(admin, "[CPOOL] not discarding #%" FMT_OFF_T " still in use by %u transfers", conn->connection_id, conn->attached_xfers); return; @@ -219,35 +218,36 @@ static void cpool_discard_conn(struct cpool *cpool, done = TRUE; if(!done) { /* Attempt to shutdown the connection right away. */ - Curl_cshutdn_run_once(cpool->idata, conn, &done); + Curl_conn_shutdown_once(admin, conn, &done); } - if(done || !data->multi) - Curl_cshutdn_terminate(cpool->idata, conn, FALSE); + cshutdn = Curl_cshutdn_get(data); + if(done || !cshutdn) + Curl_conn_terminate(admin, conn, FALSE); else - Curl_cshutdn_add(&data->multi->cshutdn, conn, cpool->num_conn); + Curl_cshutdn_add(cshutdn, conn, cpool->num_conn); } -void Curl_cpool_destroy(struct cpool *cpool) +void Curl_cpool_destroy(struct cpool *cpool, struct Curl_easy *admin) { - if(cpool && cpool->initialized && cpool->idata) { + if(cpool && cpool->initialized && admin) { struct connectdata *conn; struct Curl_sigpipe_ctx pipe_ctx; - CURL_TRC_M(cpool->idata, "%s[CPOOL] destroy, %zu connections", + CURL_TRC_M(admin, "%s[CPOOL] destroy, %zu connections", cpool->share ? "[SHARE] " : "", cpool->num_conn); /* Move all connections to the shutdown list */ sigpipe_init(&pipe_ctx); - CPOOL_LOCK(cpool, cpool->idata); + CPOOL_LOCK(cpool, admin); conn = cpool_get_first(cpool); if(conn) - sigpipe_apply(cpool->idata, &pipe_ctx); + sigpipe_apply(admin, &pipe_ctx); while(conn) { cpool_remove_conn(cpool, conn); - cpool_discard_conn(cpool, cpool->idata, conn, FALSE); + cpool_discard_conn(cpool, admin, conn, FALSE); conn = cpool_get_first(cpool); } - CPOOL_UNLOCK(cpool, cpool->idata); + CPOOL_UNLOCK(cpool, admin); sigpipe_restore(&pipe_ctx); Curl_hash_destroy(&cpool->dest2bundle); } @@ -255,27 +255,31 @@ void Curl_cpool_destroy(struct cpool *cpool) static struct cpool *cpool_get_instance(struct Curl_easy *data) { - if(data) { - if(CURL_SHARE_KEEP_CONNECT(data->share)) - return &data->share->cpool; - else if(data->multi_easy) - return &data->multi_easy->cpool; - else if(data->multi) - return &data->multi->cpool; - } + /* admin handles do not necessarily find the correct pool */ + DEBUGASSERT(data->mid); + if(CURL_SHARE_KEEP_CONNECT(data->share)) + return &data->share->cpool; + else if(data->multi_easy) + return &data->multi_easy->cpool; + else if(data->multi) + return &data->multi->cpool; return NULL; } +struct cpool *Curl_cpool_get_instance(struct Curl_easy *data) +{ + return cpool_get_instance(data); +} + void Curl_cpool_xfer_init(struct Curl_easy *data) { struct cpool *cpool = cpool_get_instance(data); - DEBUGASSERT(cpool); if(cpool) { CPOOL_LOCK(cpool, data); /* the identifier inside the connection cache */ data->id = cpool->next_easy_id++; - if(cpool->next_easy_id <= 0) + if(cpool->next_easy_id == CURL_OFF_T_MAX) cpool->next_easy_id = 0; data->state.lastconnect_id = -1; @@ -283,6 +287,7 @@ void Curl_cpool_xfer_init(struct Curl_easy *data) } else { /* We should not get here, but in a non-debug build, do something */ + DEBUGASSERT(0); data->id = 0; data->state.lastconnect_id = -1; } @@ -334,15 +339,16 @@ static struct connectdata *cpool_bundle_get_oldest_idle( } static struct connectdata *cpool_get_oldest_idle(struct cpool *cpool, - const struct curltime *pnow) + const struct curltime *pnow, + timediff_t min_age_ms) { struct Curl_hash_iterator iter; struct Curl_llist_node *curr; struct Curl_hash_element *he; - struct connectdata *oldest_idle = NULL; struct cpool_bundle *bundle; - timediff_t highscore = -1; - timediff_t score; + struct connectdata *oldest_idle = NULL; + timediff_t oldest_idle_ms = -1; + timediff_t idle_ms; Curl_hash_start_iterate(&cpool->dest2bundle, &iter); @@ -356,10 +362,9 @@ static struct connectdata *cpool_get_oldest_idle(struct cpool *cpool, conn = Curl_node_elem(curr); if(CONN_INUSE(conn) || conn->bits.close || conn->bits.connect_only) continue; - /* Set higher score for the age passed since the connection was used */ - score = curlx_ptimediff_ms(pnow, &conn->lastused); - if(score > highscore) { - highscore = score; + idle_ms = curlx_ptimediff_ms(pnow, &conn->lastused); + if((idle_ms >= min_age_ms) && (idle_ms > oldest_idle_ms)) { + oldest_idle_ms = idle_ms; oldest_idle = conn; } } @@ -367,10 +372,91 @@ static struct connectdata *cpool_get_oldest_idle(struct cpool *cpool, return oldest_idle; } -int Curl_cpool_check_limits(struct Curl_easy *data, - struct connectdata *conn) +static void cpool_conn_close(struct cpool *cpool, + struct Curl_easy *data, + struct connectdata *conn, + bool aborted) +{ + struct Curl_easy *admin; + bool do_lock; + + DEBUGASSERT(cpool); + DEBUGASSERT(data && !data->conn); + if(!cpool) + return; + + /* If this connection is not marked to force-close, leave it open if there + * are other users of it */ + if(CONN_INUSE(conn) && !aborted) { + DEBUGASSERT(0); /* does this ever happen? */ + DEBUGF(infof(data, "conn terminate when inuse: %u", conn->attached_xfers)); + return; + } + + /* This method may be called while we are under lock, e.g. from a + * user callback in find. */ + admin = Curl_get_admin(data); + do_lock = !CPOOL_IS_LOCKED(cpool); + if(do_lock) + CPOOL_LOCK(cpool, admin); + + if(conn->bits.in_cpool) { + cpool_remove_conn(cpool, conn); + DEBUGASSERT(!conn->bits.in_cpool); + } + + /* treat the connection as aborted in CONNECT_ONLY situations, + * so no graceful shutdown is attempted. */ + if(conn->bits.connect_only) + aborted = TRUE; + + if(data->multi) { + /* Add it to the multi's cpool for shutdown handling */ + infof(data, "%s connection #%" FMT_OFF_T, + aborted ? "closing" : "shutting down", conn->connection_id); + cpool_discard_conn(&data->multi->cpool, data, conn, aborted); + } + else { + /* No multi available, terminate */ + infof(data, "closing connection #%" FMT_OFF_T, conn->connection_id); + Curl_conn_terminate(admin, conn, !aborted); + } + + if(do_lock) + CPOOL_UNLOCK(cpool, admin); +} + +void Curl_conn_close(struct Curl_easy *data, + struct connectdata *conn, + bool aborted) { struct cpool *cpool = cpool_get_instance(data); + cpool_conn_close(cpool, data, conn, aborted); +} + +/* Evict an idle connection to make room in the pool. A pool owned by + * a share has no multi that could perform a controlled shutdown of the + * connection; terminate it right away. Otherwise, hand it to the + * transfer's multi for shutdown. Expects the pool to be locked. */ +static void cpool_evict_conn(struct cpool *cpool, + struct Curl_easy *admin, + struct connectdata *conn) +{ + if(cpool->share) { + cpool_remove_conn(cpool, conn); + Curl_conn_terminate(admin, conn, TRUE); + } + else + cpool_conn_close(cpool, admin, conn, FALSE); +} + +int Curl_cpool_check_limits(struct Curl_easy *data, + struct connectdata *conn, + const struct curltime *pnow) +{ + struct cpool *cpool = cpool_get_instance(data); + struct cshutdn *cshutdn = Curl_cshutdn_get(data); + struct Curl_easy *admin; struct cpool_bundle *bundle; size_t dest_limit = 0; size_t total_limit = 0; @@ -380,25 +466,27 @@ int Curl_cpool_check_limits(struct Curl_easy *data, if(!cpool) return CPOOL_LIMIT_OK; - if(cpool->idata->multi) { - dest_limit = cpool->idata->multi->max_host_connections; - total_limit = cpool->idata->multi->max_total_connections; + /* multi determines the limits, no matter who owns the pool */ + if(data->multi) { + dest_limit = data->multi->max_host_connections; + total_limit = data->multi->max_total_connections; } if(!dest_limit && !total_limit) return CPOOL_LIMIT_OK; - CPOOL_LOCK(cpool, cpool->idata); + admin = Curl_get_admin(data); + CPOOL_LOCK(cpool, admin); if(dest_limit) { size_t live; bundle = cpool_find_bundle(cpool, conn); live = bundle ? Curl_llist_count(&bundle->conns) : 0; - shutdowns = Curl_cshutdn_dest_count(data, conn->destination); + shutdowns = Curl_cshutdn_dest_count(cshutdn, conn->destination); while((live + shutdowns) >= dest_limit) { if(shutdowns) { /* close one connection in shutdown right away, if we can */ - if(!Curl_cshutdn_close_oldest(data, conn->destination)) + if(!Curl_cshutdn_close_oldest(cshutdn, conn->destination)) break; } else if(!bundle) @@ -407,22 +495,21 @@ int Curl_cpool_check_limits(struct Curl_easy *data, struct connectdata *oldest_idle = NULL; /* The bundle is full. Extract the oldest connection that may * be removed now, if there is one. */ - oldest_idle = cpool_bundle_get_oldest_idle(bundle, - Curl_pgrs_now(data)); + oldest_idle = cpool_bundle_get_oldest_idle(bundle, pnow); if(!oldest_idle) break; /* disconnect the old conn and continue */ - CURL_TRC_M(data, "Discarding connection #%" FMT_OFF_T + CURL_TRC_M(admin, "Discarding connection #%" FMT_OFF_T " from %zu to reach destination limit of %zu", oldest_idle->connection_id, Curl_llist_count(&bundle->conns), dest_limit); - Curl_conn_terminate(cpool->idata, oldest_idle, FALSE); + cpool_evict_conn(cpool, admin, oldest_idle); /* in case the bundle was destroyed in disconnect, look it up again */ bundle = cpool_find_bundle(cpool, conn); live = bundle ? Curl_llist_count(&bundle->conns) : 0; } - shutdowns = Curl_cshutdn_dest_count(cpool->idata, conn->destination); + shutdowns = Curl_cshutdn_dest_count(cshutdn, conn->destination); } if((live + shutdowns) >= dest_limit) { res = CPOOL_LIMIT_DEST; @@ -431,26 +518,26 @@ int Curl_cpool_check_limits(struct Curl_easy *data, } if(total_limit) { - shutdowns = Curl_cshutdn_count(cpool->idata); + shutdowns = Curl_cshutdn_count(cshutdn); while((cpool->num_conn + shutdowns) >= total_limit) { if(shutdowns) { /* close one connection in shutdown right away, if we can */ - if(!Curl_cshutdn_close_oldest(data, NULL)) + if(!Curl_cshutdn_close_oldest(cshutdn, NULL)) break; } else { struct connectdata *oldest_idle = - cpool_get_oldest_idle(cpool, Curl_pgrs_now(data)); + cpool_get_oldest_idle(cpool, pnow, 0); if(!oldest_idle) break; /* disconnect the old conn and continue */ - CURL_TRC_M(data, "Discarding connection #%" + CURL_TRC_M(admin, "Discarding connection #%" FMT_OFF_T " from %zu to reach total " "limit of %zu", oldest_idle->connection_id, cpool->num_conn, total_limit); - Curl_conn_terminate(cpool->idata, oldest_idle, FALSE); + cpool_evict_conn(cpool, admin, oldest_idle); } - shutdowns = Curl_cshutdn_count(cpool->idata); + shutdowns = Curl_cshutdn_count(cshutdn); } if((cpool->num_conn + shutdowns) >= total_limit) { res = CPOOL_LIMIT_TOTAL; @@ -459,7 +546,7 @@ int Curl_cpool_check_limits(struct Curl_easy *data, } out: - CPOOL_UNLOCK(cpool, cpool->idata); + CPOOL_UNLOCK(cpool, admin); return res; } @@ -512,7 +599,8 @@ out: static bool cpool_foreach(struct Curl_easy *data, struct cpool *cpool, void *param, - int (*func)(struct Curl_easy *data, + int (*func)(struct cpool *cpool, + struct Curl_easy *data, struct connectdata *conn, void *param)) { struct Curl_hash_iterator iter; @@ -536,7 +624,7 @@ static bool cpool_foreach(struct Curl_easy *data, struct connectdata *conn = Curl_node_elem(curr); curr = Curl_node_next(curr); - if(func(data, conn, param) == 1) { + if(func(cpool, data, conn, param) == 1) { return TRUE; } } @@ -556,37 +644,46 @@ bool Curl_cpool_conn_now_idle(struct Curl_easy *data, unsigned int maxconnects; struct connectdata *oldest_idle = NULL; struct cpool *cpool = cpool_get_instance(data); + struct Curl_easy *admin; bool kept = TRUE; + timediff_t min_age_ms = 0; if(!data || !data->multi) return kept; if(!data->multi->maxconnects) { - unsigned int running = Curl_multi_xfers_running(data->multi); - maxconnects = (running <= UINT_MAX / 4) ? running * 4 : UINT_MAX; + /* Attached transfers is a weak indicator of business. */ + uint32_t attached = Curl_multi_xfers_attached(data->multi); + maxconnects = (attached <= UINT_MAX / 2) ? attached * 2 : UINT_MAX; + /* We are guessing. So, only evict a "seemingly superfluous" connection + * when has not been used for this long, */ + min_age_ms = 1000; } else { maxconnects = data->multi->maxconnects; } - conn->lastused = *Curl_pgrs_now(data); /* it was used up until now */ + /* remember times, connection had been used just before */ + conn->lastchecked = conn->lastupkeep = conn->lastused = *Curl_pgrs_now(data); if(cpool && maxconnects) { /* may be called form a callback already under lock */ bool do_lock = !CPOOL_IS_LOCKED(cpool); + + admin = Curl_get_admin(data); if(do_lock) - CPOOL_LOCK(cpool, data); + CPOOL_LOCK(cpool, admin); if(cpool->num_conn > maxconnects) { infof(data, "Connection pool is full, closing the oldest of %zu/%u", cpool->num_conn, maxconnects); - oldest_idle = cpool_get_oldest_idle(cpool, Curl_pgrs_now(data)); + oldest_idle = cpool_get_oldest_idle(cpool, &conn->lastused, min_age_ms); kept = (oldest_idle != conn); if(oldest_idle) { - Curl_conn_terminate(data, oldest_idle, FALSE); + cpool_evict_conn(cpool, admin, oldest_idle); } } if(do_lock) - CPOOL_UNLOCK(cpool, data); + CPOOL_UNLOCK(cpool, admin); } return kept; @@ -632,78 +729,25 @@ bool Curl_cpool_find(struct Curl_easy *data, return found; } -void Curl_conn_terminate(struct Curl_easy *data, - struct connectdata *conn, - bool aborted) -{ - struct cpool *cpool = cpool_get_instance(data); - bool do_lock; - - DEBUGASSERT(cpool); - DEBUGASSERT(data && !data->conn); - if(!cpool) - return; - - /* If this connection is not marked to force-close, leave it open if there - * are other users of it */ - if(CONN_INUSE(conn) && !aborted) { - DEBUGASSERT(0); /* does this ever happen? */ - DEBUGF(infof(data, "conn terminate when inuse: %u", conn->attached_xfers)); - return; - } - - /* This method may be called while we are under lock, e.g. from a - * user callback in find. */ - do_lock = !CPOOL_IS_LOCKED(cpool); - if(do_lock) - CPOOL_LOCK(cpool, data); - - if(conn->bits.in_cpool) { - cpool_remove_conn(cpool, conn); - DEBUGASSERT(!conn->bits.in_cpool); - } - - /* treat the connection as aborted in CONNECT_ONLY situations, - * so no graceful shutdown is attempted. */ - if(conn->bits.connect_only) - aborted = TRUE; - - if(data->multi) { - /* Add it to the multi's cpool for shutdown handling */ - infof(data, "%s connection #%" FMT_OFF_T, - aborted ? "closing" : "shutting down", conn->connection_id); - cpool_discard_conn(&data->multi->cpool, data, conn, aborted); - } - else { - /* No multi available, terminate */ - infof(data, "closing connection #%" FMT_OFF_T, conn->connection_id); - Curl_cshutdn_terminate(cpool->idata, conn, !aborted); - } - - if(do_lock) - CPOOL_UNLOCK(cpool, data); -} - struct cpool_reaper_ctx { - size_t checked; size_t reaped; + struct curltime now; }; -static int cpool_reap_dead_cb(struct Curl_easy *data, +static int cpool_reap_dead_cb(struct cpool *cpool, + struct Curl_easy *admin, struct connectdata *conn, void *param) { struct cpool_reaper_ctx *reaper = param; - bool terminate = !CONN_INUSE(conn) && conn->bits.no_reuse; - if(!terminate) { - reaper->checked++; - terminate = Curl_conn_seems_dead(conn, data); - } - if(terminate) { - /* stop the iteration here, pass back the connection that was pruned */ - reaper->reaped++; - Curl_conn_terminate(data, conn, FALSE); - return 1; + if(!CONN_INUSE(conn)) { + if(conn->bits.no_reuse || conn->bits.close || + !Curl_cpool_conn_seems_healthy(conn, admin, &reaper->now)) { + /* terminate conn and stop the iteration */ + reaper->reaped++; + cpool_conn_close(cpool, admin, conn, FALSE); + return 1; + } } return 0; /* continue iteration */ } @@ -715,46 +759,69 @@ static int cpool_reap_dead_cb(struct Curl_easy *data, * * When called, this transfer has no connection attached. */ -void Curl_cpool_prune_dead(struct Curl_easy *data) +void Curl_cpool_prune_dead(struct cpool *cpool, + struct Curl_easy *data) { - struct cpool *cpool = cpool_get_instance(data); - struct cpool_reaper_ctx reaper; + struct Curl_easy *admin; timediff_t elapsed; if(!cpool) return; - memset(&reaper, 0, sizeof(reaper)); - CPOOL_LOCK(cpool, data); - elapsed = curlx_ptimediff_ms(Curl_pgrs_now(data), &cpool->last_cleanup); + admin = Curl_get_admin(data); + CPOOL_LOCK(cpool, admin); + elapsed = curlx_ptimediff_ms(Curl_pgrs_now(admin), &cpool->last_cleanup); if(elapsed >= 1000L) { - while(cpool_foreach(data, cpool, &reaper, cpool_reap_dead_cb)) + struct cpool_reaper_ctx reaper; + + memset(&reaper, 0, sizeof(reaper)); + reaper.now = *Curl_pgrs_now(admin); + while(cpool_foreach(admin, cpool, &reaper, cpool_reap_dead_cb)) ; - cpool->last_cleanup = *Curl_pgrs_now(data); + cpool->last_cleanup = *Curl_pgrs_now(admin); } - CPOOL_UNLOCK(cpool, data); + CPOOL_UNLOCK(cpool, admin); } -static int conn_upkeep(struct Curl_easy *data, +static int conn_upkeep(struct cpool *cpool, + struct Curl_easy *admin, struct connectdata *conn, void *param) { + const struct curltime *pnow = Curl_pgrs_now(admin); + (void)param; - Curl_conn_upkeep(data, conn); + if(curlx_ptimediff_ms(pnow, &conn->lastupkeep) >= + admin->set.upkeep_interval_ms) { + CURLcode result; + + conn->lastupkeep = *pnow; + /* briefly attach for action */ + Curl_attach_connection(admin, conn, FALSE); + result = Curl_conn_keep_alive(admin, conn); + Curl_detach_connection(admin); + + if(result && !CONN_INUSE(conn)) { + cpool_conn_close(cpool, admin, conn, FALSE); + return 1; + } + } return 0; /* continue iteration */ } CURLcode Curl_cpool_upkeep(struct Curl_easy *data) { struct cpool *cpool = cpool_get_instance(data); + struct Curl_easy *admin = Curl_get_admin(data); if(!cpool) return CURLE_OK; - CPOOL_LOCK(cpool, data); - cpool_foreach(data, cpool, NULL, conn_upkeep); - CPOOL_UNLOCK(cpool, data); + CPOOL_LOCK(cpool, admin); + while(cpool_foreach(admin, cpool, NULL, conn_upkeep)) + ; + CPOOL_UNLOCK(cpool, admin); return CURLE_OK; } @@ -763,10 +830,12 @@ struct cpool_find_ctx { struct connectdata *conn; }; -static int cpool_find_conn(struct Curl_easy *data, +static int cpool_find_conn(struct cpool *cpool, + struct Curl_easy *data, struct connectdata *conn, void *param) { struct cpool_find_ctx *fctx = param; + (void)cpool; (void)data; if(conn->connection_id == fctx->id) { fctx->conn = conn; @@ -791,40 +860,6 @@ struct connectdata *Curl_cpool_get_conn(struct Curl_easy *data, return fctx.conn; } -struct cpool_do_conn_ctx { - curl_off_t id; - Curl_cpool_conn_do_cb *cb; - void *cbdata; -}; - -static int cpool_do_conn(struct Curl_easy *data, - struct connectdata *conn, void *param) -{ - struct cpool_do_conn_ctx *dctx = param; - - if(conn->connection_id == dctx->id) { - dctx->cb(conn, data, dctx->cbdata); - return 1; - } - return 0; -} - -void Curl_cpool_do_by_id(struct Curl_easy *data, curl_off_t conn_id, - Curl_cpool_conn_do_cb *cb, void *cbdata) -{ - struct cpool *cpool = cpool_get_instance(data); - struct cpool_do_conn_ctx dctx; - - if(!cpool) - return; - dctx.id = conn_id; - dctx.cb = cb; - dctx.cbdata = cbdata; - CPOOL_LOCK(cpool, data); - cpool_foreach(data, cpool, &dctx, cpool_do_conn); - CPOOL_UNLOCK(cpool, data); -} - void Curl_cpool_do_locked(struct Curl_easy *data, struct connectdata *conn, Curl_cpool_conn_do_cb *cb, void *cbdata) @@ -839,39 +874,113 @@ void Curl_cpool_do_locked(struct Curl_easy *data, cb(conn, data, cbdata); } -static int cpool_mark_stale(struct Curl_easy *data, +static int cpool_mark_stale(struct cpool *cpool, + struct Curl_easy *admin, struct connectdata *conn, void *param) { - (void)data; + (void)cpool; + (void)admin; (void)param; conn->bits.no_reuse = TRUE; return 0; } -static int cpool_reap_no_reuse(struct Curl_easy *data, +static int cpool_reap_no_reuse(struct cpool *cpool, + struct Curl_easy *admin, struct connectdata *conn, void *param) { (void)param; if(!CONN_INUSE(conn) && conn->bits.no_reuse) { - Curl_conn_terminate(data, conn, FALSE); + cpool_conn_close(cpool, admin, conn, FALSE); return 1; } return 0; /* continue iteration */ } -void Curl_cpool_nw_changed(struct Curl_easy *data) +void Curl_cpool_nw_changed(struct cpool *cpool, struct Curl_easy *admin) { - struct cpool *cpool = cpool_get_instance(data); - - if(cpool) { - CPOOL_LOCK(cpool, data); - cpool_foreach(data, cpool, NULL, cpool_mark_stale); - while(cpool_foreach(data, cpool, NULL, cpool_reap_no_reuse)) + if(cpool && admin) { + CPOOL_LOCK(cpool, admin); + cpool_foreach(admin, cpool, NULL, cpool_mark_stale); + while(cpool_foreach(admin, cpool, NULL, cpool_reap_no_reuse)) ; - CPOOL_UNLOCK(cpool, data); + CPOOL_UNLOCK(cpool, admin); } } +/* A connection has to have been idle for less than 'conn_max_idle_ms' + (the success rate is too low after this), or created less than + 'conn_max_age_ms' ago, to be subject for reuse. */ +static bool cpool_conn_maxage(struct Curl_easy *data, + struct connectdata *conn, + const struct curltime *pnow) +{ + timediff_t age_ms; + + if(data->set.conn_max_idle_ms) { + age_ms = curlx_ptimediff_ms(pnow, &conn->lastused); + if(age_ms > data->set.conn_max_idle_ms) { + infof(data, "Too old connection (%" FMT_TIMEDIFF_T + " ms idle, max idle is %" FMT_TIMEDIFF_T " ms), disconnect it", + age_ms, data->set.conn_max_idle_ms); + return TRUE; + } + } + + if(data->set.conn_max_age_ms) { + age_ms = curlx_ptimediff_ms(pnow, &conn->created); + if(age_ms > data->set.conn_max_age_ms) { + infof(data, + "Too old connection (created %" FMT_TIMEDIFF_T + " ms ago, max lifetime is %" FMT_TIMEDIFF_T " ms), disconnect it", + age_ms, data->set.conn_max_age_ms); + return TRUE; + } + } + + return FALSE; +} + +bool Curl_cpool_conn_seems_healthy(struct connectdata *conn, + struct Curl_easy *data, + const struct curltime *pnow) +{ + struct Curl_easy *admin; + bool healthy = TRUE; + + DEBUGASSERT(!data->conn); + if(!CONN_INUSE(conn) && cpool_conn_maxage(data, conn, pnow)) /* too old? */ + return FALSE; + if(curlx_ptimediff_ms(pnow, &conn->lastchecked) < 1000) + return TRUE; + + admin = Curl_get_admin(data); + if(conn->scheme->run->connection_is_dead) { + Curl_attach_connection(admin, conn, FALSE); + healthy = !conn->scheme->run->connection_is_dead(admin, conn); + Curl_detach_connection(admin); + } + else { + bool input_pending = FALSE; + + Curl_attach_connection(admin, conn, FALSE); + healthy = Curl_conn_is_alive(admin, conn, &input_pending); + Curl_detach_connection(admin); + if(healthy && input_pending && + !CONN_INUSE(conn) && !Curl_conn_is_multiplex(conn, FIRSTSOCKET)) { + /* Non-multiplexed connections without attached transfers should + * not have input pending. The input might be a TLS Notify Close, + * for all we know. */ + DEBUGF(infof(data, "connection has no transfer but input, not healthy")); + healthy = FALSE; + } + } + + if(healthy) + conn->lastchecked = *pnow; + return healthy; +} + #if 0 /* Useful for debugging the connection pool */ void Curl_cpool_print(struct cpool *cpool) diff --git a/Utilities/cmcurl/lib/conncache.h b/Utilities/cmcurl/lib/conncache.h index 71940d724c..5e766c99d7 100644 --- a/Utilities/cmcurl/lib/conncache.h +++ b/Utilities/cmcurl/lib/conncache.h @@ -34,7 +34,7 @@ struct Curl_multi; struct Curl_share; /** - * Terminate the connection, e.g. close and destroy. + * Close and destroy the connection. * If the connection is in a cpool, remove it. * If a `cshutdn` is available (e.g. data has a multi handle), * pass the connection to that for controlled shutdown. @@ -42,9 +42,9 @@ struct Curl_share; * Takes ownership of `conn`. * `data` should not be attached to a connection. */ -void Curl_conn_terminate(struct Curl_easy *data, - struct connectdata *conn, - bool aborted); +void Curl_conn_close(struct Curl_easy *data, + struct connectdata *conn, + bool aborted); struct cpool { /* the pooled connections, bundled per destination */ @@ -53,22 +53,24 @@ struct cpool { curl_off_t next_connection_id; curl_off_t next_easy_id; struct curltime last_cleanup; - struct Curl_easy *idata; /* internal handle for maintenance */ struct Curl_share *share; /* != NULL if pool belongs to share */ BIT(locked); BIT(initialized); }; +/* Get connection pool instance for data or NULL if none exists */ +struct cpool *Curl_cpool_get_instance(struct Curl_easy *data); + /* Init the pool, pass multi only if pool is owned by it. * Cannot fail. */ void Curl_cpool_init(struct cpool *cpool, - struct Curl_easy *idata, struct Curl_share *share, size_t size); /* Destroy all connections and free all members */ -void Curl_cpool_destroy(struct cpool *cpool); +void Curl_cpool_destroy(struct cpool *cpool, + struct Curl_easy *admin); /* Init the transfer to be used within its connection pool. * Assigns `data->id`. */ @@ -91,7 +93,8 @@ CURLcode Curl_cpool_add(struct Curl_easy *data, #define CPOOL_LIMIT_DEST 1 #define CPOOL_LIMIT_TOTAL 2 int Curl_cpool_check_limits(struct Curl_easy *data, - struct connectdata *conn); + struct connectdata *conn, + const struct curltime *pnow); /* Return of conn is suitable. If so, stops iteration. */ typedef bool Curl_cpool_conn_match_cb(struct connectdata *conn, @@ -126,13 +129,12 @@ bool Curl_cpool_conn_now_idle(struct Curl_easy *data, struct connectdata *conn); /** - * This function scans the data's connection pool for half-open/dead + * Scans the connection pool for half-open/dead * connections, closes and removes them. * The cleanup is done at most once per second. - * - * When called, this transfer has no connection attached. */ -void Curl_cpool_prune_dead(struct Curl_easy *data); +void Curl_cpool_prune_dead(struct cpool *cpool, + struct Curl_easy *data); /** * Perform upkeep actions on connections in the transfer's pool. @@ -143,14 +145,6 @@ typedef void Curl_cpool_conn_do_cb(struct connectdata *conn, struct Curl_easy *data, void *cbdata); -/** - * Invoke the callback on the pool's connection with the - * given connection id (if it exists). - */ -void Curl_cpool_do_by_id(struct Curl_easy *data, - curl_off_t conn_id, - Curl_cpool_conn_do_cb *cb, void *cbdata); - /** * Invoked the callback for the given data + connection under the * connection pool's lock. @@ -162,6 +156,12 @@ void Curl_cpool_do_locked(struct Curl_easy *data, Curl_cpool_conn_do_cb *cb, void *cbdata); /* Close all unused connections, prevent reuse of existing ones. */ -void Curl_cpool_nw_changed(struct Curl_easy *data); +void Curl_cpool_nw_changed(struct cpool *cpool, struct Curl_easy *admin); + +/* Return TRUE iff the given connection is considered healthy, e.g. + * usable for more transfers. */ +bool Curl_cpool_conn_seems_healthy(struct connectdata *conn, + struct Curl_easy *data, + const struct curltime *pnow); #endif /* HEADER_CURL_CONNCACHE_H */ diff --git a/Utilities/cmcurl/lib/connect.c b/Utilities/cmcurl/lib/connect.c index a2b86c7458..cb3647544e 100644 --- a/Utilities/cmcurl/lib/connect.c +++ b/Utilities/cmcurl/lib/connect.c @@ -28,7 +28,6 @@ #include "strerror.h" #include "cfilters.h" #include "connect.h" -#include "cf-dns.h" #include "cf-https-connect.h" #include "cf-setup.h" #include "multiif.h" @@ -36,6 +35,7 @@ #include "conncache.h" #include "multihandle.h" #include "select.h" +#include "vdns/cf-dns.h" #include "curlx/strparse.h" #if !defined(CURL_DISABLE_ALTSVC) || defined(USE_HTTPSRR) @@ -57,12 +57,6 @@ enum alpnid Curl_alpn2alpnid(const unsigned char *name, size_t len) return ALPN_none; /* unknown, probably rubbish input */ } -enum alpnid Curl_str2alpnid(const struct Curl_str *cstr) -{ - return Curl_alpn2alpnid((const unsigned char *)curlx_str(cstr), - curlx_strlen(cstr)); -} - #endif /* @@ -81,13 +75,13 @@ UNITTEST timediff_t timeleft_now_ms(struct Curl_easy *data, timediff_t timeleft_ms = 0; timediff_t ctimeleft_ms = 0; - if(Curl_shutdown_started(data, FIRSTSOCKET)) + if(data->conn && Curl_shutdown_started(data->conn, FIRSTSOCKET)) return Curl_shutdown_timeleft(data, data->conn, FIRSTSOCKET); else if(Curl_is_connecting(data)) { timediff_t ctimeout_ms = (data->set.connecttimeout > 0) ? data->set.connecttimeout : DEFAULT_CONNECT_TIMEOUT; ctimeleft_ms = ctimeout_ms - - curlx_ptimediff_ms(pnow, &data->progress.t_startsingle); + Curl_pgrs_since_ms(data, pnow, TIMER_STARTSINGLE); if(!ctimeleft_ms) ctimeleft_ms = -1; /* 0 is "no limit", fake 1 ms expiry */ } @@ -97,7 +91,7 @@ UNITTEST timediff_t timeleft_now_ms(struct Curl_easy *data, if(data->set.timeout) { timeleft_ms = data->set.timeout - - curlx_ptimediff_ms(pnow, &data->progress.t_startop); + Curl_pgrs_since_ms(data, pnow, TIMER_STARTOP); if(!timeleft_ms) timeleft_ms = -1; /* 0 is "no limit", fake 1 ms expiry */ } @@ -114,27 +108,34 @@ timediff_t Curl_timeleft_ms(struct Curl_easy *data) return timeleft_now_ms(data, Curl_pgrs_now(data)); } -void Curl_shutdown_start(struct Curl_easy *data, int sockindex, +timediff_t Curl_timeleft_now_ms(struct Curl_easy *data, + const struct curltime *pnow) +{ + return timeleft_now_ms(data, pnow); +} + +void Curl_shutdown_start(struct Curl_easy *data, int8_t sockindex, int timeout_ms) { struct connectdata *conn = data->conn; + const struct curltime *pnow = Curl_pgrs_now(data); DEBUGASSERT(conn); - conn->shutdown.start[sockindex] = *Curl_pgrs_now(data); + conn->shutdown.start[sockindex] = *pnow; conn->shutdown.timeout_ms = (timeout_ms > 0) ? (timediff_t)timeout_ms : ((data->set.shutdowntimeout > 0) ? data->set.shutdowntimeout : DEFAULT_SHUTDOWN_TIMEOUT_MS); /* Set a timer, unless we operate on the admin handle */ if(data->mid) - Curl_expire_ex(data, conn->shutdown.timeout_ms, EXPIRE_SHUTDOWN); + Curl_expire_set(data, EXPIRE_SHUTDOWN, conn->shutdown.timeout_ms, pnow); CURL_TRC_M(data, "shutdown start on%s connection", sockindex ? " secondary" : ""); } timediff_t Curl_shutdown_timeleft(struct Curl_easy *data, struct connectdata *conn, - int sockindex) + int8_t sockindex) { timediff_t left_ms; @@ -152,7 +153,7 @@ timediff_t Curl_conn_shutdown_timeleft(struct Curl_easy *data, struct connectdata *conn) { timediff_t left_ms = 0, ms; - int i; + int8_t i; for(i = 0; conn->shutdown.timeout_ms && (i < 2); ++i) { if(!conn->shutdown.start[i].tv_sec) @@ -164,19 +165,16 @@ timediff_t Curl_conn_shutdown_timeleft(struct Curl_easy *data, return left_ms; } -void Curl_shutdown_clear(struct Curl_easy *data, int sockindex) +void Curl_shutdown_clear(struct Curl_easy *data, int8_t sockindex) { struct curltime *pt = &data->conn->shutdown.start[sockindex]; memset(pt, 0, sizeof(*pt)); } -bool Curl_shutdown_started(struct Curl_easy *data, int sockindex) +bool Curl_shutdown_started(struct connectdata *conn, int8_t sockindex) { - if(data->conn) { - struct curltime *pt = &data->conn->shutdown.start[sockindex]; - return (pt->tv_sec > 0) || (pt->tv_usec > 0); - } - return FALSE; + const struct curltime *pt = &conn->shutdown.start[sockindex]; + return (pt->tv_sec > 0) || (pt->tv_usec > 0); } /* @@ -201,67 +199,66 @@ curl_socket_t Curl_getconnectinfo(struct Curl_easy *data, conn = Curl_cpool_get_conn(data, data->state.lastconnect_id); if(!conn) { data->state.lastconnect_id = -1; + if(connp) + *connp = NULL; return CURL_SOCKET_BAD; } if(connp) - /* only store this if the caller cares for it */ *connp = conn; return conn->sock[FIRSTSOCKET]; } + if(connp) + *connp = NULL; return CURL_SOCKET_BAD; } -/* - * Curl_conncontrol() marks streams or connection for closure. - */ -void Curl_conncontrol(struct connectdata *conn, - int ctrl /* see defines in header */ -#if defined(DEBUGBUILD) && defined(CURLVERBOSE) - , const char *reason -#endif - ) +void Curl_conncontrol(struct connectdata *conn, int ctrl) { - /* close if a connection, or a stream that is not multiplexed. */ - /* This function will be called both before and after this connection is - associated with a transfer. */ - bool closeit, is_multiplex; - DEBUGASSERT(conn); -#if defined(DEBUGBUILD) && defined(CURLVERBOSE) - (void)reason; /* useful for debugging */ -#endif - is_multiplex = Curl_conn_is_multiplex(conn, FIRSTSOCKET); - closeit = (ctrl == CONNCTRL_CONNECTION) || - ((ctrl == CONNCTRL_STREAM) && !is_multiplex); - if((ctrl == CONNCTRL_STREAM) && is_multiplex) - ; /* stream signal on multiplex conn never affects close state */ - else if((curl_bit)closeit != conn->bits.close) { - conn->bits.close = closeit; /* the only place in the source code that - should assign this bit */ + if(!conn) { + DEBUGASSERT(0); + return; + } + switch(ctrl) { + case CONNCTRL_CONN_KEEP: + conn->bits.close = FALSE; + break; + case CONNCTRL_CONN_CLOSE: + conn->bits.close = TRUE; + break; + case CONNCTRL_STREAM_CLOSE: + /* stream close when multiplexing does not affect connection */ + if(!Curl_conn_is_multiplex(conn, FIRSTSOCKET)) + conn->bits.close = TRUE; + break; + default: + DEBUGASSERT(0); + break; } } CURLcode Curl_conn_setup(struct Curl_easy *data, struct connectdata *conn, - int sockindex, + int8_t sockindex, int ssl_mode) { + struct Curl_peer *first_peer = Curl_conn_get_first_peer(conn, sockindex); CURLcode result = CURLE_OK; - struct Curl_peer *peer = Curl_conn_get_first_peer(conn, sockindex); uint8_t dns_queries; DEBUGASSERT(data); DEBUGASSERT(conn->scheme); DEBUGASSERT(!conn->cfilter[sockindex]); - if(!peer) + if(!first_peer) return CURLE_FAILED_INIT; #ifndef CURL_DISABLE_HTTP if(!conn->cfilter[sockindex] && conn->scheme->protocol == CURLPROTO_HTTPS) { DEBUGASSERT(ssl_mode != CURL_CF_SSL_DISABLE); - result = Curl_cf_https_setup(data, conn, sockindex); + result = Curl_cf_https_setup( + data, Curl_conn_get_destination(conn, sockindex), conn, sockindex); if(result) goto out; } @@ -275,13 +272,16 @@ CURLcode Curl_conn_setup(struct Curl_easy *data, goto out; } + /* Whatever the filter chain will be in the end, it will need the + * resolving of `first_peer`. Add that now so the resolve is started + * right away. */ dns_queries = Curl_resolv_dns_queries(data, conn->ip_version); -#ifdef USE_HTTPSRR - if(sockindex == FIRSTSOCKET) - dns_queries |= CURL_DNSQ_HTTPS; -#endif - result = Curl_cf_dns_add(data, conn, sockindex, peer, dns_queries, - conn->transport_wanted); + result = Curl_conn_dns_add_addr_resolve(data, conn, sockindex, + first_peer, dns_queries, + conn->transport_wanted); + if(result) + goto out; + DEBUGASSERT(conn->cfilter[sockindex]); out: return result; @@ -313,27 +313,16 @@ static CURLcode conn_connect_trace(struct Curl_easy *data, /** * Update connection statistics */ -static void conn_report_connect_stats(struct Curl_cfilter *cf, - struct Curl_easy *data) +static void conn_report_stats(struct Curl_easy *data, int sockindex) { - if(cf) { - struct curltime connected; - struct curltime appconnected; - - memset(&connected, 0, sizeof(connected)); - cf->cft->query(cf, data, CF_QUERY_TIMER_CONNECT, NULL, &connected); - if(connected.tv_sec || connected.tv_usec) - Curl_pgrsTimeWas(data, TIMER_CONNECT, connected); - - memset(&appconnected, 0, sizeof(appconnected)); - cf->cft->query(cf, data, CF_QUERY_TIMER_APPCONNECT, NULL, &appconnected); - if(appconnected.tv_sec || appconnected.tv_usec) - Curl_pgrsTimeWas(data, TIMER_APPCONNECT, appconnected); + /* We do gather stats for the second socket...yet */ + if(sockindex == FIRSTSOCKET) { + Curl_conn_cntrl_report_stats(data, data->conn, sockindex); } } CURLcode Curl_conn_connect(struct Curl_easy *data, - int sockindex, + int8_t sockindex, bool blocking, bool *done) { @@ -391,8 +380,8 @@ CURLcode Curl_conn_connect(struct Curl_easy *data, * persist information at the connection. E.g. cf-socket sets the * socket and ip related information. */ Curl_conn_cntrl_update_info(data, data->conn); - conn_report_connect_stats(cf, data); - data->conn->keepalive = *Curl_pgrs_now(data); + conn_report_stats(data, sockindex); + data->conn->lastupkeep = *Curl_pgrs_now(data); VERBOSE(result = conn_connect_trace(data, cf)); VERBOSE(Curl_conn_trc_filters(data, sockindex, "connected")); Curl_conn_remove_setup_filters(data, sockindex); @@ -403,7 +392,7 @@ CURLcode Curl_conn_connect(struct Curl_easy *data, CURL_TRC_CF(data, cf, "Curl_conn_connect(), filter returned %d", (int)result); VERBOSE(Curl_conn_trc_filters(data, sockindex, "failed to connect")); - conn_report_connect_stats(cf, data); + conn_report_stats(data, sockindex); goto out; } @@ -465,14 +454,14 @@ void Curl_conn_set_multiplex(struct connectdata *conn) } struct Curl_peer *Curl_conn_get_origin(struct connectdata *conn, - int sockindex) + int8_t sockindex) { return (sockindex == SECONDARYSOCKET) ? conn->origin2 : conn->origin; } struct Curl_peer *Curl_conn_get_destination(struct connectdata *conn, - int sockindex) + int8_t sockindex) { return (sockindex == SECONDARYSOCKET) ? (conn->via_peer2 ? conn->via_peer2 : conn->origin2) : @@ -480,7 +469,7 @@ struct Curl_peer *Curl_conn_get_destination(struct connectdata *conn, } struct Curl_peer *Curl_conn_get_first_peer(struct connectdata *conn, - int sockindex) + int8_t sockindex) { #ifndef CURL_DISABLE_PROXY if(conn->socks_proxy.peer) diff --git a/Utilities/cmcurl/lib/connect.h b/Utilities/cmcurl/lib/connect.h index 95bde1eb4c..bc8c53aa83 100644 --- a/Utilities/cmcurl/lib/connect.h +++ b/Utilities/cmcurl/lib/connect.h @@ -25,41 +25,42 @@ ***************************************************************************/ #include "curl_setup.h" -#include "hostip.h" +#include "vdns/hostip.h" #include "curlx/timeval.h" struct Curl_peer; struct Curl_str; enum alpnid Curl_alpn2alpnid(const unsigned char *name, size_t len); -enum alpnid Curl_str2alpnid(const struct Curl_str *cstr); /* generic function that returns how much time there is left to run, according to the timeouts set */ timediff_t Curl_timeleft_ms(struct Curl_easy *data); +timediff_t Curl_timeleft_now_ms(struct Curl_easy *data, + const struct curltime *pnow); #define DEFAULT_CONNECT_TIMEOUT 300000 /* milliseconds == five minutes */ #define DEFAULT_SHUTDOWN_TIMEOUT_MS (2 * 1000) -void Curl_shutdown_start(struct Curl_easy *data, int sockindex, +void Curl_shutdown_start(struct Curl_easy *data, int8_t sockindex, int timeout_ms); /* return how much time there is left to shutdown the connection at * sockindex. Returns 0 if there is no limit or shutdown has not started. */ timediff_t Curl_shutdown_timeleft(struct Curl_easy *data, struct connectdata *conn, - int sockindex); + int8_t sockindex); /* return how much time there is left to shutdown the connection. * Returns 0 if there is no limit or shutdown has not started. */ timediff_t Curl_conn_shutdown_timeleft(struct Curl_easy *data, struct connectdata *conn); -void Curl_shutdown_clear(struct Curl_easy *data, int sockindex); +void Curl_shutdown_clear(struct Curl_easy *data, int8_t sockindex); /* TRUE iff shutdown has been started */ -bool Curl_shutdown_started(struct Curl_easy *data, int sockindex); +bool Curl_shutdown_started(struct connectdata *conn, int8_t sockindex); /* * Used to extract socket and connectdata struct for the most recent @@ -71,37 +72,23 @@ curl_socket_t Curl_getconnectinfo(struct Curl_easy *data, struct connectdata **connp); /* - * Curl_conncontrol() marks the end of a connection/stream. The 'ctrl' - * argument specifies if it is the end of a connection or a stream. - * - * For stream-based protocols (such as HTTP/2), a stream close will not cause - * a connection close. Other protocols will close the connection for both - * cases. - * - * It sets the bit.close bit to TRUE (with an explanation for debug builds), - * when the connection will close. + * Curl_conncontrol() manipulates the `conn->bits.close` bit on + * a connection: + * - CONNCTRL_CONN_KEEP: clear the bit + * - CONNCTRL_CONN_CLOSE: set the bit + * - CONNCTRL_STREAM_CLOSE: set the bit when the connection is not + * multiplexed + * The call does *NOT* cause any immediate connection close. */ +#define CONNCTRL_CONN_KEEP 0 +#define CONNCTRL_CONN_CLOSE 1 +#define CONNCTRL_STREAM_CLOSE 2 -#define CONNCTRL_KEEP 0 /* undo a marked closure */ -#define CONNCTRL_CONNECTION 1 -#define CONNCTRL_STREAM 2 +void Curl_conncontrol(struct connectdata *conn, int ctrl); -void Curl_conncontrol(struct connectdata *conn, - int ctrl -#if defined(DEBUGBUILD) && defined(CURLVERBOSE) - , const char *reason -#endif - ); - -#if defined(DEBUGBUILD) && defined(CURLVERBOSE) -#define streamclose(x, y) Curl_conncontrol(x, CONNCTRL_STREAM, y) -#define connclose(x, y) Curl_conncontrol(x, CONNCTRL_CONNECTION, y) -#define connkeep(x, y) Curl_conncontrol(x, CONNCTRL_KEEP, y) -#else /* !DEBUGBUILD || !CURLVERBOSE */ -#define streamclose(x, y) Curl_conncontrol(x, CONNCTRL_STREAM) -#define connclose(x, y) Curl_conncontrol(x, CONNCTRL_CONNECTION) -#define connkeep(x, y) Curl_conncontrol(x, CONNCTRL_KEEP) -#endif +#define streamclose(x) Curl_conncontrol((x), CONNCTRL_STREAM_CLOSE) +#define connclose(x) Curl_conncontrol((x), CONNCTRL_CONN_CLOSE) +#define connkeep(x) Curl_conncontrol((x), CONNCTRL_CONN_KEEP) /** * Setup the cfilters at `sockindex` in connection `conn`. @@ -110,7 +97,7 @@ void Curl_conncontrol(struct connectdata *conn, */ CURLcode Curl_conn_setup(struct Curl_easy *data, struct connectdata *conn, - int sockindex, + int8_t sockindex, int ssl_mode); /** @@ -120,7 +107,7 @@ CURLcode Curl_conn_setup(struct Curl_easy *data, * When not `blocking`, calls may return without error and `*done != TRUE`, * while the individual filters negotiated the connection. */ -CURLcode Curl_conn_connect(struct Curl_easy *data, int sockindex, +CURLcode Curl_conn_connect(struct Curl_easy *data, int8_t sockindex, bool blocking, bool *done); /* Set conn to allow multiplexing. */ @@ -128,17 +115,17 @@ void Curl_conn_set_multiplex(struct connectdata *conn); /* Get the origin peer at sockindex. */ struct Curl_peer *Curl_conn_get_origin(struct connectdata *conn, - int sockindex); + int8_t sockindex); /* Get the peer the connection actually connects to at sockindex. * Often the same as "origin", but can be redirected via "connect-to" * or "alt-svc". May tunnel through proxies. */ struct Curl_peer *Curl_conn_get_destination(struct connectdata *conn, - int sockindex); + int8_t sockindex); /* Get the peer curl connects its socket to. * Can be origin, "connect-to" or the first proxy. */ struct Curl_peer *Curl_conn_get_first_peer(struct connectdata *conn, - int sockindex); + int8_t sockindex); #endif /* HEADER_CURL_CONNECT_H */ diff --git a/Utilities/cmcurl/lib/content_encoding.c b/Utilities/cmcurl/lib/content_encoding.c index f35fe56f63..49d291e138 100644 --- a/Utilities/cmcurl/lib/content_encoding.c +++ b/Utilities/cmcurl/lib/content_encoding.c @@ -65,7 +65,7 @@ #ifdef HAVE_LIBZ #if !defined(ZLIB_VERNUM) || (ZLIB_VERNUM < 0x1252) -#error "requires zlib 1.2.5.2 or newer" +#error "zlib 1.2.5.2 or greater required" #endif typedef enum { @@ -206,6 +206,14 @@ static CURLcode inflate_stream(struct Curl_easy *data, /* No more data to flush: exit loop. */ break; case Z_STREAM_END: + if((started == ZLIB_INIT_GZIP) && (z->avail_in >= 2) && + (z->next_in[0] == 0x1f) && (z->next_in[1] == 0x8b)) { + /* a second gzip member follows; curl does not support + multi-member gzip responses */ + failf(data, "Multi-member gzip response not supported"); + result = exit_zlib(data, z, &zp->zlib_init, CURLE_WRITE_ERROR); + break; + } result = process_trailer(data, zp); break; case Z_DATA_ERROR: @@ -289,8 +297,10 @@ static void deflate_do_close(struct Curl_easy *data, static const struct Curl_cwtype deflate_encoding = { "deflate", NULL, + CURL_CW_FLAG_BLOWUP, deflate_do_init, deflate_do_write, + Curl_cwriter_def_flush, deflate_do_close, sizeof(struct zlib_writer) }; @@ -350,8 +360,10 @@ static void gzip_do_close(struct Curl_easy *data, static const struct Curl_cwtype gzip_encoding = { "gzip", "x-gzip", + CURL_CW_FLAG_BLOWUP, gzip_do_init, gzip_do_write, + Curl_cwriter_def_flush, gzip_do_close, sizeof(struct zlib_writer) }; @@ -483,8 +495,10 @@ static void brotli_do_close(struct Curl_easy *data, static const struct Curl_cwtype brotli_encoding = { "br", NULL, + CURL_CW_FLAG_BLOWUP, brotli_do_init, brotli_do_write, + Curl_cwriter_def_flush, brotli_do_close, sizeof(struct brotli_writer) }; @@ -596,8 +610,10 @@ static void zstd_do_close(struct Curl_easy *data, static const struct Curl_cwtype zstd_encoding = { "zstd", NULL, + CURL_CW_FLAG_BLOWUP, zstd_do_init, zstd_do_write, + Curl_cwriter_def_flush, zstd_do_close, sizeof(struct zstd_writer) }; @@ -607,8 +623,10 @@ static const struct Curl_cwtype zstd_encoding = { static const struct Curl_cwtype identity_encoding = { "identity", "none", + 0, Curl_cwriter_def_init, Curl_cwriter_def_write, + Curl_cwriter_def_flush, Curl_cwriter_def_close, sizeof(struct Curl_cwriter) }; @@ -694,8 +712,10 @@ static void error_do_close(struct Curl_easy *data, static const struct Curl_cwtype error_writer = { "ce-error", NULL, + 0, error_do_init, error_do_write, + Curl_cwriter_def_flush, error_do_close, sizeof(struct Curl_cwriter) }; @@ -763,7 +783,8 @@ CURLcode Curl_build_unencoding_stack(struct Curl_easy *data, * Exception is "chunked" transfer-encoding which always must happen */ if((is_transfer && !data->set.http_transfer_encoding && !is_chunked) || (!is_transfer && data->set.http_ce_skip)) { - bool is_identity = curl_strnequal(name, "identity", 8); + bool is_identity = (namelen == 8) && + curl_strnequal(name, "identity", 8); /* not requested, ignore */ CURL_TRC_WRITE(data, "decoder not requested, ignored: %.*s", (int)namelen, name); diff --git a/Utilities/cmcurl/lib/cookie.c b/Utilities/cmcurl/lib/cookie.c index 91dc8d5fd0..3255668ec7 100644 --- a/Utilities/cmcurl/lib/cookie.c +++ b/Utilities/cmcurl/lib/cookie.c @@ -48,8 +48,7 @@ future. (from RFC6265bis draft-19) For the sake of easier testing, align the capped time to an even 60 second - boundary. -*/ + boundary. */ static void cap_expires(time_t now, struct Cookie *co) { if(co->expires && (TIME_T_MAX - COOKIES_MAXAGE - 30) > now) { @@ -335,15 +334,14 @@ static bool bad_domain(const char *domain, size_t len) } #endif -/* - RFC 6265 section 4.1.1 says a server should accept this range: +/* RFC 6265 section 4.1.1 says a server should accept this range: - cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E + cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E - Yet, Firefox and Chrome as of June 2022 accept space, comma and double-quotes - fine. The prime reason for filtering out control bytes is that some HTTP - servers return 400 for requests that contain such. -*/ + Yet, Firefox and Chrome as of June 2022 accept space, comma and + double-quotes fine. The prime reason for filtering out control bytes is that + some HTTP servers return 400 for requests that contain such. + */ static bool invalid_octets(const char *ptr, size_t len) { const unsigned char *p = (const unsigned char *)ptr; @@ -359,8 +357,7 @@ static bool invalid_octets(const char *ptr, size_t len) /* The maximum length we accept a date string for the 'expire' keyword. The standard date formats are within the 30 bytes range. This adds an extra - margin to make sure it realistically works with what is used out there. -*/ + margin to make sure it realistically works with what is used out there. */ #define MAX_DATE_LENGTH 80 #define COOKIE_NAME 0 @@ -614,7 +611,7 @@ parse_cookie_header(struct Curl_easy *data, struct Curl_str name; /* we have a = pair or a stand-alone word here */ - if(!curlx_str_cspn(&ptr, &name, ";\t\r\n=")) { + if(!curlx_str_cspn(&ptr, &name, ";\r\n=")) { struct Curl_str val; bool sep = FALSE; curlx_str_trimblanks(&name); @@ -801,22 +798,28 @@ static CURLcode parse_netscape(struct Cookie *co, } static bool is_public_suffix(struct Curl_easy *data, - const struct Cookie *co, + struct Cookie *co, const char *domain) { #ifdef USE_LIBPSL /* - * Check if the domain is a Public Suffix and if yes, ignore the cookie. We - * must also check that the data handle is not NULL since the psl code will - * dereference it. + * Check if the domain is a Public Suffix and if yes, ignore the cookie. + * 'domain' is NULL when the cookie is loaded from file or + * CURLOPT_COOKIELIST. */ + DEBUGASSERT(data); + DEBUGASSERT(co); DEBUGF(infof(data, "PSL check set-cookie '%s' for domain=%s in %s", - co->name, co->domain, domain)); - if(data && (domain && co->domain && !Curl_host_is_ipnum(co->domain))) { + co->name, co->domain ? co->domain : "[blank]", + domain ? domain : "[file]")); + if(!co->domain || Curl_host_is_ipnum(co->domain)) + return FALSE; + + else { bool acceptable = FALSE; char lcase[256]; char lcookie[256]; - size_t dlen = strlen(domain); + size_t dlen = domain ? strlen(domain) : 0; size_t clen = strlen(co->domain); /* trim trailing dots */ @@ -829,11 +832,28 @@ static bool is_public_suffix(struct Curl_easy *data, const psl_ctx_t *psl = Curl_psl_use(data); if(psl) { /* the PSL check requires lowercase domain name and pattern */ - Curl_strntolower(lcase, domain, dlen); - lcase[dlen] = 0; Curl_strntolower(lcookie, co->domain, clen); lcookie[clen] = 0; - acceptable = psl_is_cookie_domain_acceptable(psl, lcase, lcookie); + if(domain) { + Curl_strntolower(lcase, domain, dlen); + lcase[dlen] = 0; + acceptable = psl_is_cookie_domain_acceptable(psl, lcase, lcookie); + + /* if the cookie is acceptable, but is set for a PSL domain, then it + cannot be tailmatching */ + if(acceptable && co->tailmatch && + curl_strequal(co->domain, domain) && + psl_is_public_suffix(psl, lcookie)) + co->tailmatch = FALSE; + } + else { + /* libpsl says localhost is a PSL, we think not */ + acceptable = + curl_strequal(lcookie, "localhost") || + /* note that this PSL function returns the opposite value than + psl_is_cookie_domain_acceptable() does */ + !psl_is_public_suffix(psl, lcookie); + } Curl_psl_release(data); } else @@ -842,7 +862,8 @@ static bool is_public_suffix(struct Curl_easy *data, if(!acceptable) { infof(data, "cookie '%s' dropped, domain '%s' must not " - "set cookies for '%s'", co->name, domain, co->domain); + "set cookies for '%s'", co->name, + domain ? domain : "[file]", co->domain); return TRUE; } } @@ -851,7 +872,7 @@ static bool is_public_suffix(struct Curl_easy *data, (void)co; (void)domain; DEBUGF(infof(data, "NO PSL to check set-cookie '%s' for domain=%s in %s", - co->name, co->domain, domain)); + co->name, co->domain, domain ? domain : "[file]")); #endif return FALSE; } @@ -971,16 +992,15 @@ static bool replace_existing(struct Curl_easy *data, * IPv6 address. * */ -CURLcode Curl_cookie_add( - struct Curl_easy *data, - struct CookieInfo *ci, - bool httpheader, /* TRUE if HTTP header-style line */ - bool noexpire, /* if TRUE, skip remove_expired() */ - const char *lineptr, /* first character of the line */ - const char *domain, /* default domain */ - const char *path, /* full path used when this cookie is set, used - to get default path for the cookie unless set */ - bool secure) /* TRUE if connection is over secure origin */ +CURLcode Curl_cookie_add(struct Curl_easy *data, + struct CookieInfo *ci, + const char *lineptr, /* first character of the line */ + const char *domain, /* default domain */ + const char *path, /* full path used when this + cookie is set, used to get + default path for the cookie + unless set */ + const int flags) { struct Cookie comem; struct Cookie *co; @@ -997,11 +1017,11 @@ CURLcode Curl_cookie_add( co = &comem; memset(co, 0, sizeof(comem)); - if(httpheader) + if(flags & COOKIE_HTTPHEADER) result = parse_cookie_header(data, co, ci, &okay, - lineptr, domain, path, secure); + lineptr, domain, path, flags & COOKIE_SECURE); else - result = parse_netscape(co, ci, &okay, lineptr, secure); + result = parse_netscape(co, ci, &okay, lineptr, flags & COOKIE_SECURE); if(result || !okay) goto fail; @@ -1010,6 +1030,9 @@ CURLcode Curl_cookie_add( /* The __Secure- prefix only requires that the cookie be set secure */ goto fail; + if(!(flags & COOKIE_NOPSL) && is_public_suffix(data, co, domain)) + goto fail; + if(co->prefix_host) { /* * The __Host- prefix requires the cookie to be secure, have a "/" path @@ -1029,20 +1052,15 @@ CURLcode Curl_cookie_add( co->livecookie = ci->running; co->creationtime = ++ci->lastct; + if(!(flags & COOKIE_NOEXPIRE)) + remove_expired(ci); + /* * Now we have parsed the incoming line, we must now check if this supersedes * an already existing cookie, which it may if the previous have the same * domain and path as this. */ - - /* remove expired cookies */ - if(!noexpire) - remove_expired(ci); - - if(is_public_suffix(data, co, domain)) - goto fail; - - if(!replace_existing(data, co, ci, secure, &replaces)) + if(!replace_existing(data, co, ci, flags & COOKIE_SECURE, &replaces)) goto fail; /* clone the stack struct into heap */ @@ -1074,7 +1092,7 @@ CURLcode Curl_cookie_add( if(co->expires && (co->expires < ci->next_expiration)) ci->next_expiration = co->expires; - if(httpheader) + if(flags & COOKIE_HTTPHEADER) data->req.setcookies++; return result; @@ -1123,11 +1141,11 @@ struct CookieInfo *Curl_cookie_init(void) * Reads cookies from a local file. This is always called before any cookies * are set. If file is "-" then STDIN is read. * - * If 'newsession' is TRUE, discard all "session cookies" on read from file. - * + * If 'flags' has the COOKIE_NOSESSION bit set, discard all "session cookies" + * read from file. */ static CURLcode cookie_load(struct Curl_easy *data, const char *file, - struct CookieInfo *ci, bool newsession) + struct CookieInfo *ci, int flags) { FILE *handle = NULL; CURLcode result = CURLE_OK; @@ -1136,7 +1154,7 @@ static CURLcode cookie_load(struct Curl_easy *data, const char *file, DEBUGASSERT(data); DEBUGASSERT(file); - ci->newsession = newsession; /* new session? */ + ci->newsession = !!(flags & COOKIE_NOSESSION); /* new session? */ ci->running = FALSE; /* this is not running, this is init */ if(file && *file) { @@ -1176,8 +1194,10 @@ static CURLcode cookie_load(struct Curl_easy *data, const char *file, curlx_str_passblanks(&lineptr); } - result = Curl_cookie_add(data, ci, headerline, TRUE, lineptr, NULL, - NULL, TRUE); + result = Curl_cookie_add(data, ci, lineptr, NULL, NULL, + (headerline ? COOKIE_HTTPHEADER : 0) | + COOKIE_NOEXPIRE | COOKIE_SECURE | + (flags & COOKIE_NOPSL)); /* File reading cookie failures are not propagated back to the caller because there is no way to do that */ } @@ -1202,7 +1222,8 @@ static CURLcode cookie_load(struct Curl_easy *data, const char *file, /* * Load cookies from all given cookie files (CURLOPT_COOKIEFILE). */ -CURLcode Curl_cookie_loadfiles(struct Curl_easy *data) +CURLcode Curl_cookie_loadfiles(struct Curl_easy *data, + int flags) { CURLcode result = CURLE_OK; struct curl_slist *list = data->state.cookielist; @@ -1215,8 +1236,7 @@ CURLcode Curl_cookie_loadfiles(struct Curl_easy *data) else { data->state.cookie_engine = TRUE; while(list) { - result = cookie_load(data, list->data, data->cookies, - (bool)data->set.cookiesession); + result = cookie_load(data, list->data, data->cookies, flags); if(result) break; list = list->next; @@ -1656,13 +1676,13 @@ void Curl_flush_cookies(struct Curl_easy *data, bool cleanup) might be cookie files that were not loaded so saving the file is the wrong thing. */ if(data->cookies) { - if(data->set.str[STRING_COOKIEJAR] && data->cookies->running) { + const char *cookiejar = CURL_EASY_STR(data, STRING_COOKIEJAR); + if(cookiejar && data->cookies->running) { /* if we have a destination file for all the cookies to get dumped to */ - CURLcode result = cookie_output(data, data->cookies, - data->set.str[STRING_COOKIEJAR]); + CURLcode result = cookie_output(data, data->cookies, cookiejar); if(result) infof(data, "WARNING: failed to save cookies in %s: %s", - data->set.str[STRING_COOKIEJAR], curl_easy_strerror(result)); + cookiejar, curl_easy_strerror(result)); } if(cleanup && (!data->share || (data->cookies != data->share->cookies))) { diff --git a/Utilities/cmcurl/lib/cookie.h b/Utilities/cmcurl/lib/cookie.h index b8ef8b8ea1..973ec09547 100644 --- a/Utilities/cmcurl/lib/cookie.h +++ b/Utilities/cmcurl/lib/cookie.h @@ -72,7 +72,7 @@ struct CookieInfo { In the 6265bis draft document section 5.4 it is phrased even stronger: "If the sum of the lengths of the name string and the value string is more than 4096 octets, abort these steps and ignore the set-cookie-string entirely." -*/ + */ /** Limits for INCOMING cookies **/ @@ -104,20 +104,25 @@ struct CookieInfo { struct Curl_easy; struct connectdata; +bool Curl_secure_context(struct Curl_easy *data, const char *host); + /* * Add a cookie to the internal list of cookies. The domain and path arguments - * are only used if the header boolean is TRUE. + * are only used if the COOKIE_HTTPHEADER bit is set in the flags. */ -bool Curl_secure_context(struct Curl_easy *data, const char *host); +#define COOKIE_HTTPHEADER (1<<0) /* if HTTP header-style line */ +#define COOKIE_NOEXPIRE (1<<1) /* skip remove_expired() */ +#define COOKIE_SECURE (1<<2) /* connection is over secure origin */ +#define COOKIE_NOPSL (1<<3) /* skip PSL check */ +#define COOKIE_NOSESSION (1<<6) /* drop session cookies */ + CURLcode Curl_cookie_add(struct Curl_easy *data, struct CookieInfo *ci, - bool httpheader, - bool noexpire, const char *lineptr, const char *domain, const char *path, - bool secure) WARN_UNUSED_RESULT; + const int flags) WARN_UNUSED_RESULT; CURLcode Curl_cookie_getlist(struct Curl_easy *data, bool *okay, const char *host, struct Curl_llist *list) WARN_UNUSED_RESULT; @@ -126,7 +131,7 @@ void Curl_cookie_clearsess(struct CookieInfo *ci); #if defined(CURL_DISABLE_HTTP) || defined(CURL_DISABLE_COOKIES) #define Curl_cookie_list(x) NULL -#define Curl_cookie_loadfiles(x) CURLE_OK +#define Curl_cookie_loadfiles(x, y) CURLE_OK #define Curl_cookie_init() NULL #define Curl_cookie_run(x) Curl_nop_stmt #define Curl_cookie_cleanup(x) Curl_nop_stmt @@ -136,7 +141,8 @@ void Curl_flush_cookies(struct Curl_easy *data, bool cleanup); void Curl_cookie_cleanup(struct CookieInfo *ci); struct CookieInfo *Curl_cookie_init(void); struct curl_slist *Curl_cookie_list(struct Curl_easy *data); -CURLcode Curl_cookie_loadfiles(struct Curl_easy *data) WARN_UNUSED_RESULT; +CURLcode Curl_cookie_loadfiles(struct Curl_easy *data, + int flags) WARN_UNUSED_RESULT; void Curl_cookie_run(struct Curl_easy *data); #endif diff --git a/Utilities/cmcurl/lib/creds.c b/Utilities/cmcurl/lib/creds.c index d22c166a42..5507ae86f6 100644 --- a/Utilities/cmcurl/lib/creds.c +++ b/Utilities/cmcurl/lib/creds.c @@ -46,6 +46,7 @@ CURLcode Curl_creds_create(const char *user, size_t salen = sasl_authzid ? strlen(sasl_authzid) : 0; size_t sslen = sasl_service ? strlen(sasl_service) : 0; char *s, *buf; + size_t bufsize; CURLcode result = CURLE_OK; Curl_creds_unlink(pcreds); @@ -64,13 +65,14 @@ CURLcode Curl_creds_create(const char *user, } /* null-terminator for user already part of struct */ - creds = curlx_calloc(1, sizeof(*creds) + - ulen + plen + 1 + olen + 1 + salen + 1 + sslen + 1); + bufsize = ulen + plen + 1 + olen + 1 + salen + 1 + sslen + 1; + creds = curlx_calloc(1, sizeof(*creds) + bufsize); if(!creds) { result = CURLE_OUT_OF_MEMORY; goto out; } + creds->bufsize = bufsize; creds->refcount = 1; creds->source = source; /* Some compilers try to be too smart about our dynamic struct size */ @@ -147,6 +149,7 @@ void Curl_creds_unlink(struct Curl_creds **pcreds) if(creds->refcount) creds->refcount--; if(!creds->refcount) { + curlx_memzero(creds, sizeof(*creds) + creds->bufsize); curlx_free(creds); } } diff --git a/Utilities/cmcurl/lib/creds.h b/Utilities/cmcurl/lib/creds.h index 0664d08a4f..5db3f8f22e 100644 --- a/Utilities/cmcurl/lib/creds.h +++ b/Utilities/cmcurl/lib/creds.h @@ -39,6 +39,7 @@ struct Curl_creds { const char *sasl_service; /* non-NULL, maybe empty string */ uint32_t refcount; uint8_t source; /* CREDS_* value */ + size_t bufsize; /* extra bytes added to sizeof(struct Curl_creds) */ char buf[1]; }; @@ -73,8 +74,8 @@ bool Curl_creds_equal(struct Curl_creds *c1, struct Curl_creds *c2); /* Provides properties for creds or, if creds is NULL, the empty string */ #define Curl_creds_has_user(c) ((c) && (c)->user[0]) #define Curl_creds_has_passwd(c) ((c) && (c)->passwd[0]) -#define Curl_creds_has_user_or_pass(c) \ - ((c) && ((c)->user[0] || (c)->passwd[0])) +#define Curl_creds_has_user_or_pass(c) \ + ((c) && ((c)->user[0] || (c)->passwd[0])) #define Curl_creds_has_oauth_bearer(c) ((c) && (c)->oauth_bearer[0]) #define Curl_creds_has_sasl_service(c) ((c) && (c)->sasl_service[0]) #define Curl_creds_user(c) ((c) ? (c)->user : "") diff --git a/Utilities/cmcurl/lib/cshutdn.c b/Utilities/cmcurl/lib/cshutdn.c index cc77e401fd..10cafdd065 100644 --- a/Utilities/cmcurl/lib/cshutdn.c +++ b/Utilities/cmcurl/lib/cshutdn.c @@ -38,6 +38,13 @@ #include "curlx/strparse.h" +struct cshutdn *Curl_cshutdn_get(struct Curl_easy *data) +{ + if(data && data->multi) + return &data->multi->cshutdn; + return NULL; +} + static void cshutdn_run_conn_handler(struct Curl_easy *data, struct connectdata *conn) { @@ -76,7 +83,7 @@ static void cshutdn_run_once(struct Curl_easy *data, /* We expect to be attached when called */ DEBUGASSERT(data->conn == conn); - if(!Curl_shutdown_started(data, FIRSTSOCKET)) { + if(!Curl_shutdown_started(conn, FIRSTSOCKET)) { Curl_shutdown_start(data, FIRSTSOCKET, 0); } @@ -107,22 +114,22 @@ static void cshutdn_run_once(struct Curl_easy *data, conn->bits.shutdown_filters = TRUE; } -void Curl_cshutdn_run_once(struct Curl_easy *data, +void Curl_conn_shutdown_once(struct Curl_easy *admin, struct connectdata *conn, bool *done) { - DEBUGASSERT(!data->conn); - Curl_attach_connection(data, conn); - cshutdn_run_once(data, conn, done); - CURL_TRC_M(data, "[SHUTDOWN] shutdown, done=%d", *done); - Curl_detach_connection(data); + DEBUGASSERT(!admin->conn); + DEBUGASSERT(!admin->mid); + Curl_attach_connection(admin, conn, FALSE); + cshutdn_run_once(admin, conn, done); + CURL_TRC_M(admin, "[SHUTDOWN] shutdown, done=%d", *done); + Curl_detach_connection(admin); } -void Curl_cshutdn_terminate(struct Curl_easy *data, - struct connectdata *conn, - bool do_shutdown) +void Curl_conn_terminate(struct Curl_easy *admin, + struct connectdata *conn, + bool do_shutdown) { - struct Curl_easy *admin = data; bool done; /* there must be a connection to close */ @@ -130,16 +137,10 @@ void Curl_cshutdn_terminate(struct Curl_easy *data, /* it must be removed from the connection pool */ DEBUGASSERT(!conn->bits.in_cpool); /* the transfer must be detached from the connection */ - DEBUGASSERT(data && !data->conn); + DEBUGASSERT(admin && !admin->conn); + DEBUGASSERT(!admin->mid); - /* If we can obtain an internal admin handle, use that to attach - * and terminate the connection. Some protocol will try to mess with - * `data` during shutdown and we do not want that with a `data` from - * the application. */ - if(data->multi && data->multi->admin) - admin = data->multi->admin; - - Curl_attach_connection(admin, conn); + Curl_attach_connection(admin, conn, FALSE); cshutdn_run_conn_handler(admin, conn); if(do_shutdown) { @@ -154,18 +155,17 @@ void Curl_cshutdn_terminate(struct Curl_easy *data, Curl_conn_cf_discard_all(admin, conn, FIRSTSOCKET); Curl_detach_connection(admin); - if(data->multi) - Curl_multi_ev_conn_done(data->multi, data, conn); + if(admin->multi) + Curl_multi_ev_conn_done(admin->multi, admin, conn); Curl_conn_free(admin, conn); - if(data->multi) { - CURL_TRC_M(data, "[SHUTDOWN] trigger multi connchanged"); - Curl_multi_connchanged(data->multi); + if(admin->multi) { + CURL_TRC_M(admin, "[SHUTDOWN] trigger multi connchanged"); + Curl_multi_connchanged(admin->multi); } } static bool cshutdn_destroy_oldest(struct cshutdn *cshutdn, - struct Curl_easy *data, const char *destination) { struct Curl_llist_node *e; @@ -184,20 +184,19 @@ static bool cshutdn_destroy_oldest(struct cshutdn *cshutdn, conn = Curl_node_elem(e); Curl_node_remove(e); sigpipe_init(&sigpipe_ctx); - sigpipe_apply(data, &sigpipe_ctx); - Curl_cshutdn_terminate(data, conn, FALSE); + sigpipe_apply(cshutdn->multi->admin, &sigpipe_ctx); + Curl_conn_terminate(cshutdn->multi->admin, conn, FALSE); sigpipe_restore(&sigpipe_ctx); return TRUE; } return FALSE; } -bool Curl_cshutdn_close_oldest(struct Curl_easy *data, +bool Curl_cshutdn_close_oldest(struct cshutdn *cshutdn, const char *destination) { - if(data && data->multi) { - struct cshutdn *csd = &data->multi->cshutdn; - return cshutdn_destroy_oldest(csd, data, destination); + if(cshutdn) { + return cshutdn_destroy_oldest(cshutdn, destination); } return FALSE; } @@ -205,7 +204,6 @@ bool Curl_cshutdn_close_oldest(struct Curl_easy *data, #define NUM_POLLS_ON_STACK 10 static CURLcode cshutdn_wait(struct cshutdn *cshutdn, - struct Curl_easy *data, int timeout_ms) { struct pollfd a_few_on_stack[NUM_POLLS_ON_STACK]; @@ -214,7 +212,7 @@ static CURLcode cshutdn_wait(struct cshutdn *cshutdn, Curl_pollfds_init(&cpfds, a_few_on_stack, NUM_POLLS_ON_STACK); - result = Curl_cshutdn_add_pollfds(cshutdn, data, &cpfds); + result = Curl_cshutdn_add_pollfds(cshutdn, &cpfds); if(result) goto out; @@ -226,11 +224,11 @@ out: } static void cshutdn_perform(struct cshutdn *cshutdn, - struct Curl_easy *data, struct Curl_sigpipe_ctx *sigpipe_ctx) { struct Curl_llist_node *e = Curl_llist_head(&cshutdn->list); struct Curl_llist_node *enext; + struct Curl_easy *admin = cshutdn->multi->admin; struct connectdata *conn; timediff_t next_expire_ms = 0, ms; bool done; @@ -238,67 +236,64 @@ static void cshutdn_perform(struct cshutdn *cshutdn, if(!e) return; - CURL_TRC_M(data, "[SHUTDOWN] perform on %zu connections", + CURL_TRC_M(admin, "[SHUTDOWN] perform on %zu connections", Curl_llist_count(&cshutdn->list)); - sigpipe_apply(data, sigpipe_ctx); + sigpipe_apply(admin, sigpipe_ctx); while(e) { enext = Curl_node_next(e); conn = Curl_node_elem(e); - Curl_cshutdn_run_once(data, conn, &done); + Curl_conn_shutdown_once(admin, conn, &done); if(done) { Curl_node_remove(e); - Curl_cshutdn_terminate(data, conn, FALSE); + Curl_conn_terminate(admin, conn, FALSE); } else { /* idata has one timer list, but maybe more than one connection. * Set EXPIRE_SHUTDOWN to the smallest time left for all. */ - ms = Curl_conn_shutdown_timeleft(data, conn); - if(ms && ms < next_expire_ms) + ms = Curl_conn_shutdown_timeleft(admin, conn); + if(ms && (!next_expire_ms || (ms < next_expire_ms))) next_expire_ms = ms; } e = enext; } if(next_expire_ms) - Curl_expire_ex(data, next_expire_ms, EXPIRE_SHUTDOWN); + Curl_expire(admin, next_expire_ms, EXPIRE_SHUTDOWN); } static void cshutdn_terminate_all(struct cshutdn *cshutdn, - struct Curl_easy *data, int timeout_ms) { - struct curltime started = *Curl_pgrs_now(data); + struct Curl_easy *admin = cshutdn->multi->admin; + struct curltime started = *Curl_pgrs_now(admin); struct Curl_llist_node *e; struct Curl_sigpipe_ctx sigpipe_ctx; - DEBUGASSERT(cshutdn); - DEBUGASSERT(data); - - CURL_TRC_M(data, "[SHUTDOWN] shutdown all"); + CURL_TRC_M(admin, "[SHUTDOWN] shutdown all"); sigpipe_init(&sigpipe_ctx); while(Curl_llist_head(&cshutdn->list)) { timediff_t spent_ms; int remain_ms; - cshutdn_perform(cshutdn, data, &sigpipe_ctx); + cshutdn_perform(cshutdn, &sigpipe_ctx); if(!Curl_llist_head(&cshutdn->list)) { - CURL_TRC_M(data, "[SHUTDOWN] shutdown finished cleanly"); + CURL_TRC_M(admin, "[SHUTDOWN] shutdown finished cleanly"); break; } /* wait for activity, timeout or "nothing" */ - spent_ms = curlx_ptimediff_ms(Curl_pgrs_now(data), &started); + spent_ms = curlx_ptimediff_ms(Curl_pgrs_now(admin), &started); if(spent_ms >= (timediff_t)timeout_ms) { - CURL_TRC_M(data, "[SHUTDOWN] shutdown finished, %s", + CURL_TRC_M(admin, "[SHUTDOWN] shutdown finished, %s", (timeout_ms > 0) ? "timeout" : "best effort done"); break; } remain_ms = timeout_ms - (int)spent_ms; - if(cshutdn_wait(cshutdn, data, remain_ms)) { - CURL_TRC_M(data, "[SHUTDOWN] shutdown finished, aborted"); + if(cshutdn_wait(cshutdn, remain_ms)) { + CURL_TRC_M(admin, "[SHUTDOWN] shutdown finished, aborted"); break; } } @@ -308,7 +303,7 @@ static void cshutdn_terminate_all(struct cshutdn *cshutdn, while(e) { struct connectdata *conn = Curl_node_elem(e); Curl_node_remove(e); - Curl_cshutdn_terminate(data, conn, FALSE); + Curl_conn_terminate(admin, conn, FALSE); e = Curl_llist_head(&cshutdn->list); } DEBUGASSERT(!Curl_llist_count(&cshutdn->list)); @@ -327,12 +322,13 @@ int Curl_cshutdn_init(struct cshutdn *cshutdn, } void Curl_cshutdn_destroy(struct cshutdn *cshutdn, - struct Curl_easy *data) + struct Curl_easy *admin) { - if(cshutdn->initialized && data) { + if(cshutdn->initialized && admin) { int timeout_ms = 0; /* for testing, run graceful shutdown */ #ifdef DEBUGBUILD + DEBUGASSERT(!admin->mid); { const char *p = getenv("CURL_GRACEFUL_SHUTDOWN"); if(p) { @@ -343,29 +339,28 @@ void Curl_cshutdn_destroy(struct cshutdn *cshutdn, } #endif - CURL_TRC_M(data, "[SHUTDOWN] destroy, %zu connections, timeout=%dms", + CURL_TRC_M(admin, "[SHUTDOWN] destroy, %zu connections, timeout=%dms", Curl_llist_count(&cshutdn->list), timeout_ms); - cshutdn_terminate_all(cshutdn, data, timeout_ms); + cshutdn_terminate_all(cshutdn, timeout_ms); } cshutdn->multi = NULL; + cshutdn->initialized = FALSE; } -size_t Curl_cshutdn_count(struct Curl_easy *data) +size_t Curl_cshutdn_count(struct cshutdn *cshutdn) { - if(data && data->multi) { - struct cshutdn *csd = &data->multi->cshutdn; - return Curl_llist_count(&csd->list); + if(cshutdn) { + return Curl_llist_count(&cshutdn->list); } return 0; } -size_t Curl_cshutdn_dest_count(struct Curl_easy *data, +size_t Curl_cshutdn_dest_count(struct cshutdn *cshutdn, const char *destination) { - if(data && data->multi) { - struct cshutdn *csd = &data->multi->cshutdn; + if(cshutdn) { size_t n = 0; - struct Curl_llist_node *e = Curl_llist_head(&csd->list); + struct Curl_llist_node *e = Curl_llist_head(&cshutdn->list); while(e) { struct connectdata *conn = Curl_node_elem(e); if(!strcmp(destination, conn->destination)) @@ -378,17 +373,17 @@ size_t Curl_cshutdn_dest_count(struct Curl_easy *data, } static CURLMcode cshutdn_update_ev(struct cshutdn *cshutdn, - struct Curl_easy *data, struct connectdata *conn) { + struct Curl_easy *admin = cshutdn->multi->admin; CURLMcode mresult; DEBUGASSERT(cshutdn); DEBUGASSERT(cshutdn->multi->socket_cb); - Curl_attach_connection(data, conn); - mresult = Curl_multi_ev_assess_conn(cshutdn->multi, data, conn); - Curl_detach_connection(data); + Curl_attach_connection(admin, conn, FALSE); + mresult = Curl_multi_ev_assess_conn(cshutdn->multi, admin, conn); + Curl_detach_connection(admin); return mresult; } @@ -396,47 +391,46 @@ void Curl_cshutdn_add(struct cshutdn *cshutdn, struct connectdata *conn, size_t conns_in_pool) { - struct Curl_easy *data = cshutdn->multi->admin; + struct Curl_easy *admin = cshutdn->multi->admin; size_t max_total = cshutdn->multi->max_total_connections; /* Add the connection to our shutdown list for non-blocking shutdown * during multi processing. */ if(max_total > 0 && (max_total <= (conns_in_pool + Curl_llist_count(&cshutdn->list)))) { - CURL_TRC_M(data, "[SHUTDOWN] discarding oldest shutdown connection " + CURL_TRC_M(admin, "[SHUTDOWN] discarding oldest shutdown connection " "due to connection limit of %zu", max_total); - cshutdn_destroy_oldest(cshutdn, data, NULL); + cshutdn_destroy_oldest(cshutdn, NULL); } if(cshutdn->multi->socket_cb) { - if(cshutdn_update_ev(cshutdn, data, conn)) { - CURL_TRC_M(data, "[SHUTDOWN] update events failed, discarding #%" + if(cshutdn_update_ev(cshutdn, conn)) { + CURL_TRC_M(admin, "[SHUTDOWN] update events failed, discarding #%" FMT_OFF_T, conn->connection_id); - Curl_cshutdn_terminate(data, conn, FALSE); + Curl_conn_terminate(admin, conn, FALSE); return; } } Curl_llist_append(&cshutdn->list, conn, &conn->cshutdn_node); - CURL_TRC_M(data, "[SHUTDOWN] added #%" FMT_OFF_T + CURL_TRC_M(admin, "[SHUTDOWN] added #%" FMT_OFF_T " to shutdowns, now %zu conns in shutdown", conn->connection_id, Curl_llist_count(&cshutdn->list)); } void Curl_cshutdn_perform(struct cshutdn *cshutdn, - struct Curl_easy *data, struct Curl_sigpipe_ctx *sigpipe_ctx) { - cshutdn_perform(cshutdn, data, sigpipe_ctx); + cshutdn_perform(cshutdn, sigpipe_ctx); } /* return fd_set info about the shutdown connections */ void Curl_cshutdn_setfds(struct cshutdn *cshutdn, - struct Curl_easy *data, fd_set *read_fd_set, fd_set *write_fd_set, int *maxfd) { if(Curl_llist_head(&cshutdn->list)) { + struct Curl_easy *admin = cshutdn->multi->admin; struct Curl_llist_node *e; struct easy_pollset ps; @@ -447,9 +441,9 @@ void Curl_cshutdn_setfds(struct cshutdn *cshutdn, CURLcode result; Curl_pollset_reset(&ps); - Curl_attach_connection(data, conn); - result = Curl_conn_adjust_pollset(data, conn, &ps); - Curl_detach_connection(data); + Curl_attach_connection(admin, conn, FALSE); + result = Curl_conn_adjust_pollset(admin, conn, &ps); + Curl_detach_connection(admin); if(result) continue; @@ -473,13 +467,13 @@ void Curl_cshutdn_setfds(struct cshutdn *cshutdn, /* return information about the shutdown connections */ unsigned int Curl_cshutdn_add_waitfds(struct cshutdn *cshutdn, - struct Curl_easy *data, struct Curl_waitfds *cwfds) { unsigned int need = 0; if(Curl_llist_head(&cshutdn->list)) { struct Curl_llist_node *e; + struct Curl_easy *admin = cshutdn->multi->admin; struct easy_pollset ps; struct connectdata *conn; CURLcode result; @@ -488,9 +482,9 @@ unsigned int Curl_cshutdn_add_waitfds(struct cshutdn *cshutdn, for(e = Curl_llist_head(&cshutdn->list); e; e = Curl_node_next(e)) { conn = Curl_node_elem(e); Curl_pollset_reset(&ps); - Curl_attach_connection(data, conn); - result = Curl_conn_adjust_pollset(data, conn, &ps); - Curl_detach_connection(data); + Curl_attach_connection(admin, conn, FALSE); + result = Curl_conn_adjust_pollset(admin, conn, &ps); + Curl_detach_connection(admin); if(!result) need += Curl_waitfds_add_ps(cwfds, &ps); @@ -501,7 +495,6 @@ unsigned int Curl_cshutdn_add_waitfds(struct cshutdn *cshutdn, } CURLcode Curl_cshutdn_add_pollfds(struct cshutdn *cshutdn, - struct Curl_easy *data, struct curl_pollfds *cpfds) { CURLcode result = CURLE_OK; @@ -509,15 +502,16 @@ CURLcode Curl_cshutdn_add_pollfds(struct cshutdn *cshutdn, if(Curl_llist_head(&cshutdn->list)) { struct Curl_llist_node *e; struct easy_pollset ps; + struct Curl_easy *admin = cshutdn->multi->admin; struct connectdata *conn; Curl_pollset_init(&ps); for(e = Curl_llist_head(&cshutdn->list); e; e = Curl_node_next(e)) { conn = Curl_node_elem(e); Curl_pollset_reset(&ps); - Curl_attach_connection(data, conn); - result = Curl_conn_adjust_pollset(data, conn, &ps); - Curl_detach_connection(data); + Curl_attach_connection(admin, conn, FALSE); + result = Curl_conn_adjust_pollset(admin, conn, &ps); + Curl_detach_connection(admin); if(!result) result = Curl_pollfds_add_ps(cpfds, &ps); diff --git a/Utilities/cmcurl/lib/cshutdn.h b/Utilities/cmcurl/lib/cshutdn.h index 8479524993..a0338f6488 100644 --- a/Utilities/cmcurl/lib/cshutdn.h +++ b/Utilities/cmcurl/lib/cshutdn.h @@ -33,20 +33,20 @@ struct Curl_share; struct Curl_sigpipe_ctx; /* Run the shutdown of the connection once. - * Shortly attach/detach `data` to `conn` while doing so. + * Shortly attach/detach the admin handle to `conn` while doing so. * `done` will be set TRUE if any error was encountered or if * the connection was shut down completely. */ -void Curl_cshutdn_run_once(struct Curl_easy *data, - struct connectdata *conn, - bool *done); +void Curl_conn_shutdown_once(struct Curl_easy *admin, + struct connectdata *conn, + bool *done); /* Terminates the connection, e.g. closes and destroys it. * If `do_shutdown` is TRUE, the shutdown will be run once before * terminating it. * Takes ownership of `conn`. */ -void Curl_cshutdn_terminate(struct Curl_easy *data, - struct connectdata *conn, - bool do_shutdown); +void Curl_conn_terminate(struct Curl_easy *admin, + struct connectdata *conn, + bool do_shutdown); /* A `cshutdown` is always owned by a multi handle to maintain * the connections to be shut down. It registers timers and @@ -57,25 +57,28 @@ struct cshutdn { BIT(initialized); }; +/* Get the cshutdn instance relevant for `data` or NULL if there is none */ +struct cshutdn *Curl_cshutdn_get(struct Curl_easy *data); + /* Init as part of the given multi handle. */ int Curl_cshutdn_init(struct cshutdn *cshutdn, struct Curl_multi *multi); /* Terminate all remaining connections and free resources. */ void Curl_cshutdn_destroy(struct cshutdn *cshutdn, - struct Curl_easy *data); + struct Curl_easy *admin); /* Number of connections being shut down. */ -size_t Curl_cshutdn_count(struct Curl_easy *data); +size_t Curl_cshutdn_count(struct cshutdn *cshutdn); /* Number of connections to the destination being shut down. */ -size_t Curl_cshutdn_dest_count(struct Curl_easy *data, +size_t Curl_cshutdn_dest_count(struct cshutdn *cshutdn, const char *destination); /* Close the oldest connection in shutdown to destination or, * when destination is NULL for any destination. * Return TRUE if a connection has been closed. */ -bool Curl_cshutdn_close_oldest(struct Curl_easy *data, +bool Curl_cshutdn_close_oldest(struct cshutdn *cshutdn, const char *destination); /* Add a connection to have it shut down. Terminate the oldest @@ -86,21 +89,17 @@ void Curl_cshutdn_add(struct cshutdn *cshutdn, /* Add sockets and POLLIN/OUT flags for connections being shut down. */ CURLcode Curl_cshutdn_add_pollfds(struct cshutdn *cshutdn, - struct Curl_easy *data, struct curl_pollfds *cpfds); unsigned int Curl_cshutdn_add_waitfds(struct cshutdn *cshutdn, - struct Curl_easy *data, struct Curl_waitfds *cwfds); void Curl_cshutdn_setfds(struct cshutdn *cshutdn, - struct Curl_easy *data, fd_set *read_fd_set, fd_set *write_fd_set, int *maxfd); /* Run maintenance on all connections. */ void Curl_cshutdn_perform(struct cshutdn *cshutdn, - struct Curl_easy *data, struct Curl_sigpipe_ctx *sigpipe_ctx); #endif /* HEADER_CURL_CSHUTDN_H */ diff --git a/Utilities/cmcurl/lib/curl_addrinfo.c b/Utilities/cmcurl/lib/curl_addrinfo.c index a927d44c99..98ba7aec1c 100644 --- a/Utilities/cmcurl/lib/curl_addrinfo.c +++ b/Utilities/cmcurl/lib/curl_addrinfo.c @@ -24,24 +24,24 @@ #include "curl_setup.h" #ifdef HAVE_NETINET_IN_H -# include +#include #endif #ifdef HAVE_NETINET_IN6_H -# include +#include #endif #ifdef HAVE_NETDB_H -# include +#include #endif #ifdef HAVE_ARPA_INET_H -# include +#include #endif #ifdef HAVE_SYS_UN_H -# include +#include #endif #ifdef __VMS -# include -# include +#include +#include #endif #include /* for offsetof() */ @@ -79,6 +79,21 @@ void Curl_freeaddrinfo(struct Curl_addrinfo *cahead) } } +struct Curl_addrinfo *Curl_addrinfo_get(struct Curl_addrinfo *ai, + int ai_family, + unsigned int n) +{ + unsigned int i; + for(i = 0; ai; ai = ai->ai_next) { + if(ai->ai_family == ai_family) { + if(i == n) + return ai; + ++i; + } + } + return NULL; +} + #ifdef HAVE_GETADDRINFO /* * Curl_getaddrinfo_ex() diff --git a/Utilities/cmcurl/lib/curl_addrinfo.h b/Utilities/cmcurl/lib/curl_addrinfo.h index 046be23816..7b2b4d2834 100644 --- a/Utilities/cmcurl/lib/curl_addrinfo.h +++ b/Utilities/cmcurl/lib/curl_addrinfo.h @@ -62,6 +62,11 @@ struct Curl_addrinfo { void Curl_freeaddrinfo(struct Curl_addrinfo *cahead); +/* Get the n-th addrinfo of family ai_family. */ +struct Curl_addrinfo *Curl_addrinfo_get(struct Curl_addrinfo *ai, + int ai_family, + unsigned int n); + #ifdef HAVE_GETADDRINFO int Curl_getaddrinfo_ex(const char *nodename, const char *servname, diff --git a/Utilities/cmcurl/lib/curl_config-cmake.h.in b/Utilities/cmcurl/lib/curl_config-cmake.h.in index 7d2485e4fd..523cd7a160 100644 --- a/Utilities/cmcurl/lib/curl_config-cmake.h.in +++ b/Utilities/cmcurl/lib/curl_config-cmake.h.in @@ -54,6 +54,9 @@ /* disables aws-sigv4 */ #cmakedefine CURL_DISABLE_AWS 1 +/* disables HTTP Message Signatures (RFC 9421) */ +#cmakedefine CURL_DISABLE_HTTPSIG 1 + /* disables DICT */ #cmakedefine CURL_DISABLE_DICT 1 @@ -314,9 +317,15 @@ /* if you have the gssapi libraries */ #cmakedefine HAVE_GSSAPI 1 +/* if you have Apple GSS */ +#cmakedefine HAVE_GSSAPPLE 1 + /* if you have the GNU gssapi libraries */ #cmakedefine HAVE_GSSGNU 1 +/* if you have gss_set_neg_mechs */ +#cmakedefine HAVE_GSS_SET_NEG_MECHS 1 + /* MIT Kerberos version */ #cmakedefine CURL_KRB5_VERSION ${CURL_KRB5_VERSION} @@ -329,12 +338,6 @@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_IFADDRS_H 1 -/* Define to 1 if you have an IPv6 capable working inet_ntop function. */ -#cmakedefine HAVE_INET_NTOP 1 - -/* Define to 1 if you have an IPv6 capable working inet_pton function. */ -#cmakedefine HAVE_INET_PTON 1 - /* Define to 1 if symbol `sa_family_t' exists */ #cmakedefine HAVE_SA_FAMILY_T 1 @@ -345,7 +348,7 @@ #cmakedefine HAVE_IOCTLSOCKET_CAMEL 1 /* Define to 1 if you have a working IoctlSocket camel case FIONBIO function. - */ + */ #cmakedefine HAVE_IOCTLSOCKET_CAMEL_FIONBIO 1 /* Define to 1 if you have a working ioctlsocket FIONBIO function. */ @@ -414,6 +417,9 @@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_NETINET_UDP_H 1 +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_NETINET_IP_H 1 + /* Define to 1 if you have the header file. */ #cmakedefine HAVE_LINUX_TCP_H 1 @@ -450,9 +456,6 @@ /* Define to 1 if you have the recv function. */ #cmakedefine HAVE_RECV 1 -/* Define to 1 if you have the select function. */ -#cmakedefine HAVE_SELECT 1 - /* Define to 1 if you have the sched_yield function. */ #cmakedefine HAVE_SCHED_YIELD 1 @@ -603,14 +606,12 @@ /* cpu-machine-OS */ #cmakedefine CURL_OS ${CURL_OS} -/* - Note: SIZEOF_* variables are fetched with CMake through check_type_size(). - As per CMake documentation on CheckTypeSize, C preprocessor code is - generated by CMake into SIZEOF_*_CODE. This is what we use in the - following statements. - - Reference: https://cmake.org/cmake/help/latest/module/CheckTypeSize.html -*/ +/* Note: SIZEOF_* variables are fetched with CMake through check_type_size(). + As per CMake documentation on CheckTypeSize, C preprocessor code is + generated by CMake into SIZEOF_*_CODE. This is what we use in the + following statements. + Ref: https://cmake.org/cmake/help/latest/module/CheckTypeSize.html + */ /* The size of `int', as computed by sizeof. */ #define SIZEOF_INT KWIML_ABI_SIZEOF_INT @@ -647,9 +648,6 @@ ${SIZEOF_SSIZE_T_CODE} /* The size of `time_t', as computed by sizeof. */ ${SIZEOF_TIME_T_CODE} -/* Define to 1 if you have the ANSI C header files. */ -#cmakedefine STDC_HEADERS 1 - /* Define if you have POSIX pthreads */ #cmakedefine HAVE_THREADS_POSIX 1 @@ -793,14 +791,8 @@ ${SIZEOF_TIME_T_CODE} /* to enable Apple OS-native certificate verification */ #cmakedefine USE_APPLE_SECTRUST 1 -/* Define to 1 if OpenSSL has the SSL_CTX_set_srp_username function. */ -#cmakedefine HAVE_OPENSSL_SRP 1 - -/* Define to 1 if GnuTLS has the gnutls_srp_verifier function. */ -#cmakedefine HAVE_GNUTLS_SRP 1 - -/* Define to 1 to enable TLS-SRP support. */ -#cmakedefine USE_TLS_SRP 1 +/* to use Apple fast UDP (SYS_recvmsg_x, SYS_sendmsg_x) */ +#cmakedefine USE_APPLE_FAST_UDP /* Define to 1 to query for HTTPSRR when using DoH */ #cmakedefine USE_HTTPSRR 1 diff --git a/Utilities/cmcurl/lib/curl_ctype.h b/Utilities/cmcurl/lib/curl_ctype.h index f3291ad818..fd6b7d723c 100644 --- a/Utilities/cmcurl/lib/curl_ctype.h +++ b/Utilities/cmcurl/lib/curl_ctype.h @@ -30,10 +30,8 @@ #define ISLOWCNTRL(x) ((unsigned char)(x) <= 0x1f) #define IS7F(x) ((x) == 0x7f) -#define ISLOWPRINT(x) (((x) >= 9) && ((x) <= 0x0d)) - -#define ISPRINT(x) (ISLOWPRINT(x) || (((x) >= ' ') && ((x) <= 0x7e))) -#define ISGRAPH(x) (ISLOWPRINT(x) || (((x) > ' ') && ((x) <= 0x7e))) +#define ISPRINT(x) (((x) >= ' ') && ((x) <= 0x7e)) +#define ISGRAPH(x) (((x) > ' ') && ((x) <= 0x7e)) #define ISCNTRL(x) (ISLOWCNTRL(x) || IS7F(x)) #define ISALPHA(x) (ISLOWER(x) || ISUPPER(x)) #define ISXDIGIT(x) (ISDIGIT(x) || ISLOWHEXALPHA(x) || ISUPHEXALPHA(x)) diff --git a/Utilities/cmcurl/lib/curl_ed25519.c b/Utilities/cmcurl/lib/curl_ed25519.c new file mode 100644 index 0000000000..44311ec54e --- /dev/null +++ b/Utilities/cmcurl/lib/curl_ed25519.c @@ -0,0 +1,175 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * 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(CURL_DISABLE_HTTPSIG) + +/* Please keep the SSL backend-specific #if branches in this order: + * + * 1. USE_OPENSSL + * 2. USE_WOLFSSL + * 3. USE_GNUTLS + * 4. USE_MBEDTLS + */ + +#include "curl_ed25519.h" + +#ifdef USE_WOLFSSL +#include +#include +#endif + +#ifdef USE_OPENSSL +#include + +CURLcode Curl_ed25519_sign(const unsigned char *key, size_t keylen, + const unsigned char *msg, size_t msglen, + unsigned char *sig, size_t *siglen) +{ + EVP_PKEY *pkey; + EVP_MD_CTX *mdctx; + size_t slen; + int rc; + + if(keylen != CURL_ED25519_KEYLEN) + return CURLE_BAD_FUNCTION_ARGUMENT; + + pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, key, keylen); + if(!pkey) + return CURLE_AUTH_ERROR; + + mdctx = EVP_MD_CTX_new(); + if(!mdctx) { + EVP_PKEY_free(pkey); + return CURLE_OUT_OF_MEMORY; + } + + rc = EVP_DigestSignInit(mdctx, NULL, NULL, NULL, pkey); + if(rc != 1) { + EVP_MD_CTX_free(mdctx); + EVP_PKEY_free(pkey); + return CURLE_AUTH_ERROR; + } + + slen = CURL_ED25519_SIGLEN; + rc = EVP_DigestSign(mdctx, sig, &slen, msg, msglen); + + EVP_MD_CTX_free(mdctx); + EVP_PKEY_free(pkey); + + if(rc != 1) + return CURLE_AUTH_ERROR; + + *siglen = slen; + return CURLE_OK; +} + +#elif defined(USE_WOLFSSL) && \ + (defined(HAVE_ED25519) || defined(WOLFSSL_CURVE25519_USE_ED25519)) && \ + defined(HAVE_ED25519_KEY_IMPORT) && defined(HAVE_ED25519_SIGN) +#include +#include +#include + +CURLcode Curl_ed25519_sign(const unsigned char *key, size_t keylen, + const unsigned char *msg, size_t msglen, + unsigned char *sig, size_t *siglen) +{ + int ret; + ed25519_key edkey; + word32 outlen; + unsigned char pubkey[ED25519_PUB_KEY_SIZE]; + + if(keylen != ED25519_KEY_SIZE) + return CURLE_BAD_FUNCTION_ARGUMENT; + + ret = wc_ed25519_init(&edkey); + if(ret) + return CURLE_AUTH_ERROR; + + ret = wc_ed25519_import_private_only(key, ED25519_KEY_SIZE, &edkey); + if(ret) + goto fail; + + ret = wc_ed25519_make_public(&edkey, pubkey, ED25519_PUB_KEY_SIZE); + if(ret) + goto fail; + + ret = wc_ed25519_import_private_key(key, ED25519_KEY_SIZE, + pubkey, ED25519_PUB_KEY_SIZE, &edkey); + if(ret) + goto fail; + + outlen = ED25519_SIG_SIZE; + ret = wc_ed25519_sign_msg(msg, (word32)msglen, sig, &outlen, &edkey); + if(ret) + goto fail; + + *siglen = (size_t)outlen; + wc_ed25519_free(&edkey); + return CURLE_OK; + +fail: + wc_ed25519_free(&edkey); + return CURLE_AUTH_ERROR; +} + +#elif defined(USE_GNUTLS) +#include + +CURLcode Curl_ed25519_sign(const unsigned char *key, size_t keylen, + const unsigned char *msg, size_t msglen, + unsigned char *sig, size_t *siglen) +{ + uint8_t pubkey[ED25519_KEY_SIZE]; + + if(keylen != ED25519_KEY_SIZE) + return CURLE_BAD_FUNCTION_ARGUMENT; + + nettle_ed25519_sha512_public_key(pubkey, key); + + nettle_ed25519_sha512_sign(pubkey, key, msglen, msg, sig); + *siglen = CURL_ED25519_SIGLEN; + + return CURLE_OK; +} + +#else /* no Ed25519-capable backend */ + +CURLcode Curl_ed25519_sign(const unsigned char *key, size_t keylen, + const unsigned char *msg, size_t msglen, + unsigned char *sig, size_t *siglen) +{ + (void)key; + (void)keylen; + (void)msg; + (void)msglen; + (void)sig; + (void)siglen; + return CURLE_NOT_BUILT_IN; +} + +#endif /* Ed25519 backends */ + +#endif /* !CURL_DISABLE_HTTP && !CURL_DISABLE_HTTPSIG */ diff --git a/Utilities/cmcurl/lib/curl_ed25519.h b/Utilities/cmcurl/lib/curl_ed25519.h new file mode 100644 index 0000000000..381b6511eb --- /dev/null +++ b/Utilities/cmcurl/lib/curl_ed25519.h @@ -0,0 +1,44 @@ +#ifndef HEADER_CURL_ED25519_H +#define HEADER_CURL_ED25519_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * 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(CURL_DISABLE_HTTPSIG) + +#define CURL_ED25519_SIGLEN 64 +#define CURL_ED25519_KEYLEN 32 + +/* Sign with Ed25519 (RFC 8032). + * key/keylen: raw 32-byte private seed + * msg/msglen: data to sign + * sig: output buffer (at least CURL_ED25519_SIGLEN bytes) + * siglen: out - actual signature length on success + * Returns CURLE_OK or CURLE_NOT_BUILT_IN if no backend supports Ed25519. */ +CURLcode Curl_ed25519_sign(const unsigned char *key, size_t keylen, + const unsigned char *msg, size_t msglen, + unsigned char *sig, size_t *siglen); + +#endif /* !CURL_DISABLE_HTTP && !CURL_DISABLE_HTTPSIG */ +#endif /* HEADER_CURL_ED25519_H */ diff --git a/Utilities/cmcurl/lib/curl_fopen.c b/Utilities/cmcurl/lib/curl_fopen.c index abb17eab4b..75054ffb77 100644 --- a/Utilities/cmcurl/lib/curl_fopen.c +++ b/Utilities/cmcurl/lib/curl_fopen.c @@ -30,16 +30,15 @@ #include "rand.h" #include "curl_fopen.h" -/* - The dirslash() function breaks a null-terminated pathname string into - directory and filename components then returns the directory component up - to, *AND INCLUDING*, a final '/'. If there is no directory in the path, - this instead returns a "" string. +/* The dirslash() function breaks a null-terminated pathname string into + directory and filename components then returns the directory component up + to, *AND INCLUDING*, a final '/'. If there is no directory in the path, + this instead returns a "" string. - This function returns a pointer to malloc'ed memory. + This function returns a pointer to malloc'ed memory. - The input path to this function is expected to have a filename part. -*/ + The input path to this function is expected to have a filename part. + */ #ifdef _WIN32 #define PATHSEP "\\" diff --git a/Utilities/cmcurl/lib/curl_gssapi.c b/Utilities/cmcurl/lib/curl_gssapi.c index 07a6c1e7ed..437f76d04a 100644 --- a/Utilities/cmcurl/lib/curl_gssapi.c +++ b/Utilities/cmcurl/lib/curl_gssapi.c @@ -55,7 +55,7 @@ #define CURL_ALIGN8 #endif -#if defined(CURL_HAVE_DIAG) && defined(__APPLE__) +#if defined(CURL_HAVE_DIAG) && defined(__APPLE__) && !defined(HAVE_GSSAPPLE) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" #endif @@ -81,7 +81,6 @@ enum min_err_code { /* libcurl is also passing this struct to these functions, which are not yet * stubbed: - * gss_inquire_context() * gss_unwrap() * gss_wrap() */ @@ -93,6 +92,12 @@ struct stub_gss_ctx_id_t_desc { char creds[250]; }; +/* Stub credential: tracks which mechanisms are allowed */ +struct stub_gss_cred_id_t_desc { + int allow_krb5; + int allow_ntlm; +}; + static OM_uint32 stub_gss_init_sec_context( OM_uint32 *min, gss_cred_id_t initiator_cred_handle, @@ -116,7 +121,6 @@ static OM_uint32 stub_gss_init_sec_context( char *token = NULL; const char *creds = NULL; - (void)initiator_cred_handle; (void)mech_type; (void)time_req; (void)input_chan_bindings; @@ -214,6 +218,16 @@ static OM_uint32 stub_gss_init_sec_context( if(strstr(creds, "NTLM")) ctx->have_ntlm = 1; + /* If a credential restricts allowed mechs, honour it */ + if(initiator_cred_handle != GSS_C_NO_CREDENTIAL) { + struct stub_gss_cred_id_t_desc *cred = + (struct stub_gss_cred_id_t_desc *)initiator_cred_handle; + if(!cred->allow_krb5) + ctx->have_krb5 = 0; + if(!cred->allow_ntlm) + ctx->have_ntlm = 0; + } + if(ctx->have_krb5) ctx->sent = STUB_GSS_KRB5; else if(ctx->have_ntlm) @@ -307,6 +321,174 @@ static OM_uint32 stub_gss_delete_sec_context( return GSS_S_COMPLETE; } + +/* NTLMSSP OID: 1.3.6.1.4.1.311.2.2.10 */ +static gss_OID_desc stub_ntlmssp_oid = { + 10, CURL_UNCONST("\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a") +}; + +static OM_uint32 stub_gss_inquire_context( + OM_uint32 *min, + struct stub_gss_ctx_id_t_desc *context, + gss_name_t *src_name, + gss_name_t *targ_name, + OM_uint32 *lifetime_rec, + gss_OID *mech_type, + OM_uint32 *ctx_flags, + int *locally_initiated, + int *open_context) +{ + (void)src_name; + (void)targ_name; + (void)lifetime_rec; + (void)ctx_flags; + (void)locally_initiated; + (void)open_context; + + if(!min) + return GSS_S_FAILURE; + + if(!context) { + *min = STUB_GSS_INVALID_CTX; + return GSS_S_FAILURE; + } + + *min = 0; + if(mech_type) { + switch(context->sent) { + case STUB_GSS_NTLM1: + case STUB_GSS_NTLM3: + *mech_type = &stub_ntlmssp_oid; + break; + default: + *mech_type = (gss_OID)&Curl_krb5_mech_oid; + break; + } + } + + return GSS_S_COMPLETE; +} +static OM_uint32 stub_gss_acquire_cred( + OM_uint32 *min, + gss_name_t desired_name, + OM_uint32 time_req, + gss_OID_set desired_mechs, + gss_cred_usage_t cred_usage, + gss_cred_id_t *output_cred_handle, + gss_OID_set *actual_mechs, + OM_uint32 *time_rec) +{ + (void)desired_name; + (void)time_req; + (void)desired_mechs; + (void)cred_usage; + (void)actual_mechs; + (void)time_rec; + + if(!min) + return GSS_S_FAILURE; + + *min = 0; + /* Allocate a stub credential that initially allows all mechanisms */ + if(output_cred_handle) { + struct stub_gss_cred_id_t_desc *cred = + curlx_calloc(1, sizeof(*cred)); + if(!cred) { + *min = STUB_GSS_NO_MEMORY; + return GSS_S_FAILURE; + } + cred->allow_krb5 = 1; + cred->allow_ntlm = 1; + *output_cred_handle = (gss_cred_id_t)cred; + } + return GSS_S_COMPLETE; +} + +static OM_uint32 stub_gss_indicate_mechs( + OM_uint32 *min, + gss_OID_set *mech_set) +{ + const char *creds; + OM_uint32 major; + + if(!min) + return GSS_S_FAILURE; + + *min = 0; + creds = getenv("CURL_STUB_GSS_CREDS"); + if(!creds) { + *min = STUB_GSS_INVALID_CREDS; + return GSS_S_FAILURE; + } + + major = gss_create_empty_oid_set(min, mech_set); + if(GSS_ERROR(major)) + return major; + + /* Always include Kerberos */ + gss_add_oid_set_member(min, (gss_OID)&Curl_krb5_mech_oid, mech_set); + + /* Include NTLM if the stub creds contain NTLM */ + if(strstr(creds, "NTLM")) + gss_add_oid_set_member(min, &stub_ntlmssp_oid, mech_set); + + return GSS_S_COMPLETE; +} + +#ifdef HAVE_GSS_SET_NEG_MECHS /* MIT Kerberos 1.8+ (2010-03-02), + missing from Apple GSS, GNU GSS */ +static OM_uint32 stub_gss_set_neg_mechs(OM_uint32 *min, + gss_cred_id_t cred_handle, + const gss_OID_set mech_set) +{ + struct stub_gss_cred_id_t_desc *cred; + size_t i; + int found_krb5 = 0; + int found_ntlm = 0; + + if(!min) + return GSS_S_FAILURE; + + *min = 0; + if(cred_handle == GSS_C_NO_CREDENTIAL) + return GSS_S_FAILURE; + + cred = (struct stub_gss_cred_id_t_desc *)cred_handle; + + /* Determine which mechs are in the allowed set */ + if(mech_set) { + for(i = 0; i < mech_set->count; i++) { + gss_OID oid = &mech_set->elements[i]; + if(oid->length == Curl_krb5_mech_oid.length && + !memcmp(oid->elements, Curl_krb5_mech_oid.elements, oid->length)) + found_krb5 = 1; + if(oid->length == stub_ntlmssp_oid.length && + !memcmp(oid->elements, stub_ntlmssp_oid.elements, oid->length)) + found_ntlm = 1; + } + } + + cred->allow_krb5 = found_krb5; + cred->allow_ntlm = found_ntlm; + return GSS_S_COMPLETE; +} +#endif /* HAVE_GSS_SET_NEG_MECHS */ + +static OM_uint32 stub_gss_release_cred( + OM_uint32 *min, + gss_cred_id_t *cred_handle) +{ + if(!min) + return GSS_S_FAILURE; + + *min = 0; + if(cred_handle && *cred_handle != GSS_C_NO_CREDENTIAL) { + curlx_free(*cred_handle); + *cred_handle = GSS_C_NO_CREDENTIAL; + } + return GSS_S_COMPLETE; +} + #endif /* CURL_GSS_STUB */ OM_uint32 Curl_gss_init_sec_context(struct Curl_easy *data, @@ -318,7 +500,8 @@ OM_uint32 Curl_gss_init_sec_context(struct Curl_easy *data, gss_buffer_t input_token, gss_buffer_t output_token, const bool mutual_auth, - OM_uint32 *ret_flags) + OM_uint32 *ret_flags, + gss_cred_id_t cred_handle) { OM_uint32 req_flags = GSS_C_REPLAY_FLAG; @@ -326,7 +509,8 @@ OM_uint32 Curl_gss_init_sec_context(struct Curl_easy *data, req_flags |= GSS_C_MUTUAL_FLAG; if(data->set.gssapi_delegation & CURLGSSAPI_DELEGATION_POLICY_FLAG) { -#ifdef GSS_C_DELEG_POLICY_FLAG /* MIT Kerberos 1.8+, missing from GNU GSS */ +#ifdef GSS_C_DELEG_POLICY_FLAG /* MIT Kerberos 1.7+ (2009-06-02), Apple GSS, + missing from GNU GSS */ req_flags |= GSS_C_DELEG_POLICY_FLAG; #else infof(data, "WARNING: support for CURLGSSAPI_DELEGATION_POLICY_FLAG not " @@ -340,7 +524,7 @@ OM_uint32 Curl_gss_init_sec_context(struct Curl_easy *data, #ifdef CURL_GSS_STUB if(getenv("CURL_STUB_GSS_CREDS")) return stub_gss_init_sec_context(minor_status, - GSS_C_NO_CREDENTIAL, /* cred_handle */ + cred_handle, (struct stub_gss_ctx_id_t_desc **)context, target_name, mech_type, @@ -355,7 +539,7 @@ OM_uint32 Curl_gss_init_sec_context(struct Curl_easy *data, #endif /* CURL_GSS_STUB */ return gss_init_sec_context(minor_status, - GSS_C_NO_CREDENTIAL, /* cred_handle */ + cred_handle, context, target_name, mech_type, @@ -383,6 +567,80 @@ OM_uint32 Curl_gss_delete_sec_context(OM_uint32 *min, return gss_delete_sec_context(min, context, output_token); } +OM_uint32 Curl_gss_inquire_context(OM_uint32 *minor_status, + gss_ctx_id_t context, + gss_OID *mech_type) +{ +#ifdef CURL_GSS_STUB + if(getenv("CURL_STUB_GSS_CREDS")) + return stub_gss_inquire_context(minor_status, + (struct stub_gss_ctx_id_t_desc *)context, + NULL, NULL, NULL, mech_type, + NULL, NULL, NULL); +#endif /* CURL_GSS_STUB */ + + return gss_inquire_context(minor_status, context, + NULL, NULL, NULL, mech_type, + NULL, NULL, NULL); +} + +OM_uint32 Curl_gss_acquire_cred(OM_uint32 *minor_status, + gss_name_t desired_name, + OM_uint32 time_req, + gss_OID_set desired_mechs, + gss_cred_usage_t cred_usage, + gss_cred_id_t *output_cred_handle, + gss_OID_set *actual_mechs, + OM_uint32 *time_rec) +{ +#ifdef CURL_GSS_STUB + if(getenv("CURL_STUB_GSS_CREDS")) + return stub_gss_acquire_cred(minor_status, desired_name, time_req, + desired_mechs, cred_usage, + output_cred_handle, actual_mechs, time_rec); +#endif /* CURL_GSS_STUB */ + + return gss_acquire_cred(minor_status, desired_name, time_req, + desired_mechs, cred_usage, + output_cred_handle, actual_mechs, time_rec); +} + +OM_uint32 Curl_gss_indicate_mechs(OM_uint32 *minor_status, + gss_OID_set *mech_set) +{ +#ifdef CURL_GSS_STUB + if(getenv("CURL_STUB_GSS_CREDS")) + return stub_gss_indicate_mechs(minor_status, mech_set); +#endif /* CURL_GSS_STUB */ + + return gss_indicate_mechs(minor_status, mech_set); +} + +#ifdef HAVE_GSS_SET_NEG_MECHS +OM_uint32 Curl_gss_set_neg_mechs(OM_uint32 *minor_status, + gss_cred_id_t cred_handle, + const gss_OID_set mech_set) +{ +#ifdef CURL_GSS_STUB + if(getenv("CURL_STUB_GSS_CREDS")) + return stub_gss_set_neg_mechs(minor_status, cred_handle, mech_set); +#endif /* CURL_GSS_STUB */ + + return gss_set_neg_mechs(minor_status, cred_handle, mech_set); +} +#endif /* HAVE_GSS_SET_NEG_MECHS */ + +OM_uint32 Curl_gss_release_cred(OM_uint32 *minor_status, + gss_cred_id_t *cred_handle) +{ +#ifdef CURL_GSS_STUB + if(getenv("CURL_STUB_GSS_CREDS")) + return stub_gss_release_cred(minor_status, cred_handle); +#endif /* CURL_GSS_STUB */ + + return gss_release_cred(minor_status, cred_handle); +} + #ifdef CURLVERBOSE #define GSS_LOG_BUFFER_LEN 1024 static size_t display_gss_error(OM_uint32 status, int type, @@ -440,7 +698,7 @@ void Curl_gss_log_error(struct Curl_easy *data, const char *prefix, } #endif /* CURLVERBOSE */ -#if defined(CURL_HAVE_DIAG) && defined(__APPLE__) +#if defined(CURL_HAVE_DIAG) && defined(__APPLE__) && !defined(HAVE_GSSAPPLE) #pragma GCC diagnostic pop #endif diff --git a/Utilities/cmcurl/lib/curl_gssapi.h b/Utilities/cmcurl/lib/curl_gssapi.h index fc3759ebcb..b33df77c17 100644 --- a/Utilities/cmcurl/lib/curl_gssapi.h +++ b/Utilities/cmcurl/lib/curl_gssapi.h @@ -41,12 +41,38 @@ OM_uint32 Curl_gss_init_sec_context(struct Curl_easy *data, gss_buffer_t input_token, gss_buffer_t output_token, const bool mutual_auth, - OM_uint32 *ret_flags); + OM_uint32 *ret_flags, + gss_cred_id_t cred_handle); OM_uint32 Curl_gss_delete_sec_context(OM_uint32 *min, gss_ctx_id_t *context, gss_buffer_t output_token); +OM_uint32 Curl_gss_inquire_context(OM_uint32 *minor_status, + gss_ctx_id_t context, + gss_OID *mech_type); + +OM_uint32 Curl_gss_acquire_cred(OM_uint32 *minor_status, + gss_name_t desired_name, + OM_uint32 time_req, + gss_OID_set desired_mechs, + gss_cred_usage_t cred_usage, + gss_cred_id_t *output_cred_handle, + gss_OID_set *actual_mechs, + OM_uint32 *time_rec); + +OM_uint32 Curl_gss_indicate_mechs(OM_uint32 *minor_status, + gss_OID_set *mech_set); + +#ifdef HAVE_GSS_SET_NEG_MECHS +OM_uint32 Curl_gss_set_neg_mechs(OM_uint32 *minor_status, + gss_cred_id_t cred_handle, + const gss_OID_set mech_set); +#endif + +OM_uint32 Curl_gss_release_cred(OM_uint32 *minor_status, + gss_cred_id_t *cred_handle); + #ifdef CURLVERBOSE /* Helper to log a GSS-API error status */ void Curl_gss_log_error(struct Curl_easy *data, const char *prefix, diff --git a/Utilities/cmcurl/lib/curl_hmac.h b/Utilities/cmcurl/lib/curl_hmac.h index d9a697a0a5..4fd8ec47bb 100644 --- a/Utilities/cmcurl/lib/curl_hmac.h +++ b/Utilities/cmcurl/lib/curl_hmac.h @@ -26,6 +26,7 @@ #if (defined(USE_CURL_NTLM_CORE) && !defined(USE_WINDOWS_SSPI)) || \ !defined(CURL_DISABLE_AWS) || !defined(CURL_DISABLE_DIGEST_AUTH) || \ + !defined(CURL_DISABLE_HTTPSIG) || \ defined(USE_LIBSSH2) || defined(USE_SSL) #define HMAC_MD5_LENGTH 16 diff --git a/Utilities/cmcurl/lib/curl_ntlm_core.c b/Utilities/cmcurl/lib/curl_ntlm_core.c index 58a446c95d..7aceb65e96 100644 --- a/Utilities/cmcurl/lib/curl_ntlm_core.c +++ b/Utilities/cmcurl/lib/curl_ntlm_core.c @@ -76,7 +76,7 @@ #elif defined(USE_MBEDTLS) && defined(HAVE_MBEDTLS_DES_CRYPT_ECB) # include # if MBEDTLS_VERSION_NUMBER < 0x03020000 -# error "mbedTLS 3.2.0 or later required" +# error "mbedTLS 3.2.0 or greater required" # endif # include # define USE_MBEDTLS_DES @@ -551,7 +551,7 @@ CURLcode Curl_ntlm_core_mk_ntlmv2_resp(const unsigned char *ntlmv2hash, unsigned char **ntresp, unsigned int *ntresp_len) { - /* NTLMv2 response structure : + /* NTLMv2 response structure: ----------------------------------------------------------------------------- 0 HMAC MD5 16 bytes ------BLOB------------------------------------------------------------------- diff --git a/Utilities/cmcurl/lib/curl_sasl.c b/Utilities/cmcurl/lib/curl_sasl.c index 7e867d753f..c4ff1e5262 100644 --- a/Utilities/cmcurl/lib/curl_sasl.c +++ b/Utilities/cmcurl/lib/curl_sasl.c @@ -142,7 +142,7 @@ CURLcode Curl_sasl_parse_url_auth_option(struct SASL *sasl, void Curl_sasl_init(struct SASL *sasl, struct Curl_easy *data, const struct SASLproto *params) { - unsigned long auth = data->set.httpauth; + uint32_t auth = data->set.httpauth; sasl->params = params; /* Set protocol dependent parameters */ sasl->state = SASL_STOP; /* Not yet running */ @@ -229,7 +229,7 @@ static CURLcode get_server_message(struct SASL *sasl, struct Curl_easy *data, if(!result && (sasl->params->flags & SASL_FLAG_BASE64)) { const char *serverdata = Curl_bufref_ptr(out); - if(!*serverdata || *serverdata == '=') + if(!*serverdata) Curl_bufref_set(out, NULL, 0, NULL); else { unsigned char *msg; @@ -252,9 +252,7 @@ static CURLcode build_message(struct SASL *sasl, struct bufref *msg) if(sasl->params->flags & SASL_FLAG_BASE64) { if(!Curl_bufref_ptr(msg)) /* Empty message. */ Curl_bufref_set(msg, "", 0, NULL); - else if(!Curl_bufref_len(msg)) /* Explicit empty response. */ - Curl_bufref_set(msg, "=", 1, NULL); - else { + else if(Curl_bufref_len(msg)) { char *base64; size_t base64len; diff --git a/Utilities/cmcurl/lib/curl_setup.h b/Utilities/cmcurl/lib/curl_setup.h index 981fb7c617..f621a7a8fa 100644 --- a/Utilities/cmcurl/lib/curl_setup.h +++ b/Utilities/cmcurl/lib/curl_setup.h @@ -77,7 +77,7 @@ #if defined(__MINGW32__) && \ (!defined(__MINGW64_VERSION_MAJOR) || (__MINGW64_VERSION_MAJOR < 3)) -#error "Building curl requires mingw-w64 3.0 or later" +#error "mingw-w64 3.0 or greater required" #endif /* Visual Studio 2010 is the minimum Visual Studio version we support. @@ -90,7 +90,7 @@ /* Disable Visual Studio warnings: 4127 "conditional expression is constant" */ #pragma warning(disable:4127) #ifndef _CRT_SECURE_NO_WARNINGS -#define _CRT_SECURE_NO_WARNINGS /* for getenv(), sscanf() */ +#define _CRT_SECURE_NO_WARNINGS /* for getenv(), sscanf(), vsnprintf() */ #endif #endif /* _MSC_VER */ @@ -149,10 +149,6 @@ # include "config-mac.h" #endif -#ifdef __riscos__ -# include "config-riscos.h" -#endif - #ifdef __OS400__ # include "config-os400.h" #endif @@ -186,7 +182,7 @@ #ifdef HAVE_LIBZ # ifndef ZLIB_CONST -# define ZLIB_CONST /* Use z_const. Supported by v1.2.5.2 and upper. */ +# define ZLIB_CONST /* Use z_const. Supported by v1.2.5.2 or greater. */ # endif #endif @@ -276,13 +272,6 @@ # endif #endif -/* - * When http is disabled rtsp is not supported. - */ -#if defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_RTSP) -# define CURL_DISABLE_RTSP -#endif - /* * When HTTP is disabled, disable HTTP-only features */ @@ -290,8 +279,8 @@ # ifndef CURL_DISABLE_ALTSVC # define CURL_DISABLE_ALTSVC # endif -# ifndef CURL_DISABLE_COOKIES -# define CURL_DISABLE_COOKIES +# ifndef CURL_DISABLE_AWS +# define CURL_DISABLE_AWS # endif # ifndef CURL_DISABLE_BASIC_AUTH # define CURL_DISABLE_BASIC_AUTH @@ -299,8 +288,8 @@ # ifndef CURL_DISABLE_BEARER_AUTH # define CURL_DISABLE_BEARER_AUTH # endif -# ifndef CURL_DISABLE_AWS -# define CURL_DISABLE_AWS +# ifndef CURL_DISABLE_COOKIES +# define CURL_DISABLE_COOKIES # endif # ifndef CURL_DISABLE_DOH # define CURL_DISABLE_DOH @@ -314,9 +303,15 @@ # ifndef CURL_DISABLE_HSTS # define CURL_DISABLE_HSTS # endif +# ifndef CURL_DISABLE_HTTPSIG +# define CURL_DISABLE_HTTPSIG +# endif # ifndef CURL_DISABLE_HTTP_AUTH # define CURL_DISABLE_HTTP_AUTH # endif +# ifndef CURL_DISABLE_RTSP +# define CURL_DISABLE_RTSP +# endif # ifndef CURL_DISABLE_WEBSOCKETS # define CURL_DISABLE_WEBSOCKETS /* no WebSockets without HTTP present */ # endif @@ -494,10 +489,6 @@ # endif #endif -#ifndef STDC_HEADERS /* no standard C headers! */ -#include -#endif - #include #define HAVE_UINTPTR_T /* assume uintptr_t is provided by stdint.h */ @@ -514,6 +505,10 @@ #endif #include +/* Include after setting system macros that may affect type sizes + (e.g. 'off_t' or 'time_t'), or suppress warnings + (e.g. '_CRT_SECURE_NO_WARNINGS`), but before including sys/stat.h */ +#include /* Default Windows file API selection. */ #ifdef _WIN32 @@ -530,7 +525,6 @@ # ifdef HAVE_IO_H # include # endif -# include # include /* Large file (>2Gb) support using Win32 functions. */ # define curl_lseek _lseeki64 @@ -849,10 +843,6 @@ #include #include -#ifdef HAVE_SYS_TYPES_H -#include -#endif - #include #if !defined(_WIN32) || defined(__MINGW32__) @@ -972,7 +962,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 */ @@ -995,7 +985,7 @@ struct timeval { #endif /* SEND_NONCONST_ARG2 */ #else /* HAVE_SEND */ #ifndef swrite -#error "Missing definition of macro swrite!" +#error "Missing definition of macro swrite" #endif #endif /* HAVE_SEND */ @@ -1075,6 +1065,10 @@ typedef unsigned int curl_bit; #include "curl_ctype.h" +#if defined(DEBUGBUILD) && defined(NDEBUG) +#error "Debug-enabled builds cannot be combined with NDEBUG" +#endif + /* * Macro used to include code only in debug builds. */ @@ -1209,7 +1203,7 @@ typedef unsigned int curl_bit; */ #if defined(__LWIP_OPT_H__) || defined(LWIP_HDR_OPT_H) # if defined(SOCKET) || defined(USE_WINSOCK) -# error "Winsock and lwIP TCP/IP stack definitions shall not coexist!" +# error "Winsock and lwIP TCP/IP stack definitions shall not coexist" # endif #endif @@ -1269,19 +1263,17 @@ typedef unsigned int curl_bit; #endif /* In Windows the default file mode is text but an application can override it. -Therefore we specify it explicitly. https://github.com/curl/curl/pull/258 -*/ + Therefore we specify it explicitly. https://github.com/curl/curl/pull/258 */ #if defined(_WIN32) || defined(MSDOS) #define FOPEN_READTEXT "rt" #define FOPEN_WRITETEXT "wt" #define FOPEN_APPENDTEXT "at" #elif defined(__CYGWIN__) /* Cygwin has specific behavior we need to address when _WIN32 is not defined. -https://cygwin.com/cygwin-ug-net/using-textbinary.html -For write we want our output to have line endings of LF and be compatible with -other Cygwin utilities. For read we want to handle input that may have line -endings either CRLF or LF so 't' is appropriate. -*/ + https://cygwin.com/cygwin-ug-net/using-textbinary.html + For write we want our output to have line endings of LF and be compatible + with other Cygwin utilities. For read we want to handle input that may have + line endings either CRLF or LF so 't' is appropriate. */ #define FOPEN_READTEXT "rt" #define FOPEN_WRITETEXT "w" #define FOPEN_APPENDTEXT "a" @@ -1310,10 +1302,14 @@ endings either CRLF or LF so 't' is appropriate. #define CURLMAX(x, y) ((x) > (y) ? (x) : (y)) #define CURLMIN(x, y) ((x) < (y) ? (x) : (y)) +/* Convenience macro to provide the length of a string literal size without + the null-terminator. Equivalent to strlen() for constant strings. */ +#define CURL_CSTRLEN(x) (sizeof(x) - 1) + /* A convenience macro to provide both the string literal and the length of the string literal in one go, useful for functions that take "string,len" as their argument */ -#define STRCONST(x) x, sizeof(x) - 1 +#define STRCONST(x) x, CURL_CSTRLEN(x) #define CURL_ARRAYSIZE(A) (sizeof(A) / sizeof((A)[0])) @@ -1353,20 +1349,6 @@ extern curl_calloc_callback Curl_ccalloc; (ptr) = NULL; \ } while(0) -/* Same as curlx_safefree() but zeroes memory before freeing */ -#define curlx_safefreezero(ptr, size) \ - do { \ - curlx_freezero(ptr, size); \ - (ptr) = NULL; \ - } while(0) - -/* Same as curlx_safefreezero() but determines length with strlen() */ -#define curlx_safefreezeroz(ptr) \ - do { \ - curlx_freezeroz(ptr); \ - (ptr) = NULL; \ - } while(0) - #include /* for CURL_EXTERN, curl_socket_t, mprintf.h */ #ifdef DEBUGBUILD @@ -1402,13 +1384,15 @@ extern FILE *curl_dbg_logfile; /* memory functions */ CURL_EXTERN void curl_dbg_free(void *ptr, int line, const char *source); CURL_EXTERN ALLOC_FUNC ALLOC_SIZE(1) - void *curl_dbg_malloc(size_t size, int line, const char *source); + void *curl_dbg_malloc(size_t wantedsize, int line, const char *source); CURL_EXTERN ALLOC_FUNC ALLOC_SIZE2(1, 2) - void *curl_dbg_calloc(size_t n, size_t size, int line, const char *source); + void *curl_dbg_calloc(size_t wanted_elements, size_t wanted_size, + int line, const char *source); CURL_EXTERN ALLOC_SIZE(2) - void *curl_dbg_realloc(void *ptr, size_t size, int line, const char *source); + void *curl_dbg_realloc(void *ptr, size_t wantedsize, int line, + const char *source); CURL_EXTERN ALLOC_FUNC - char *curl_dbg_strdup(const char *str, int line, const char *src); + char *curl_dbg_strdup(const char *str, int line, const char *source); #if defined(_WIN32) && defined(UNICODE) CURL_EXTERN ALLOC_FUNC wchar_t *curl_dbg_wcsdup(const wchar_t *str, int line, const char *source); @@ -1596,7 +1580,7 @@ typedef struct sockaddr_un { #ifdef USE_OPENSSL /* OpenSSL 3 marks these functions deprecated but we have no replacements (yet) so tell the compiler to not warn for them: - - DES_* (for NTLM), SSL_CTX_set_srp_* (for TLS-SRP) + - DES_* (for NTLM) - EVP_PKEY_get1_RSA, MD5_*, RSA_flags, RSA_free (auto-skipped for OpenSSL built with no-deprecated) */ # define OPENSSL_SUPPRESS_DEPRECATED @@ -1652,37 +1636,43 @@ typedef struct sockaddr_un { __NetBSD_Version__ */ #endif +/* NetBSD before 6.1 did not set SS_NBIO for SOCK_NONBLOCK. */ +#if defined(SOCK_NONBLOCK) && \ + (!defined(__NetBSD__) || (__NetBSD_Version__ >= 601000000)) +#define CURL_USE_SOCK_NONBLOCK +#endif + #ifndef _CURL_LOCAL_MEMZERO /* to be removed after a couple of releases */ #ifdef _WIN32 #if defined(_MSC_VER) && defined(NTDDI_VERSION) && \ (NTDDI_VERSION >= 0x0A000010) /* MS SDK 10.0.26100.0+ */ #pragma comment(lib, "volatileaccessu.lib") -#define curlx_memzero(buf, size) SecureZeroMemory2(buf, size) +#define curlx_memzero_low(buf, size) SecureZeroMemory2(buf, size) #else -#define curlx_memzero(buf, size) SecureZeroMemory(buf, size) +#define curlx_memzero_low(buf, size) SecureZeroMemory(buf, size) #endif #elif defined(HAVE_MEMSET_S) -#define curlx_memzero(buf, size) (void)memset_s(buf, size, 0, size) +#define curlx_memzero_low(buf, size) (void)memset_s(buf, size, 0, size) #elif defined(HAVE_MEMSET_EXPLICIT) -#define curlx_memzero(buf, size) (void)memset_explicit(buf, 0, size) +#define curlx_memzero_low(buf, size) (void)memset_explicit(buf, 0, size) #elif defined(__CYGWIN__) || \ (defined(__NEWLIB__) && !defined(__CLIB2__)) || \ (defined(__GLIBC__) && \ (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 25))) || \ - (defined(__DragonFly__) && __DragonFly_version >= 500600 /* v5.6+ */) || \ - (defined(__FreeBSD__) && __FreeBSD_version >= 1100037 /* v11.0+ */) || \ - (defined(__OpenBSD__) && OpenBSD >= 201405 /* v5.5+ */) -#define curlx_memzero(buf, size) explicit_bzero(buf, size) -#elif defined(__NetBSD__) && __NetBSD_Version__ >= 702000000 /* v7.2+ */ -#define curlx_memzero(buf, size) (void)explicit_memset(buf, 0, size) + (defined(__DragonFly__) && __DragonFly_version >= 500600 /* 5.6+ */) || \ + (defined(__FreeBSD__) && __FreeBSD_version >= 1100037 /* 11.0+ */) || \ + (defined(__OpenBSD__) && OpenBSD >= 201405 /* 5.5+ */) +#define curlx_memzero_low(buf, size) explicit_bzero(buf, size) +#elif defined(__NetBSD__) && __NetBSD_Version__ >= 702000000 /* 7.2+ */ +#define curlx_memzero_low(buf, size) (void)explicit_memset(buf, 0, size) #endif #endif /* !_CURL_LOCAL_MEMZERO */ -#ifndef curlx_memzero +#ifndef curlx_memzero_low #define USE_CURLX_MEMZERO -void curlx_memzero(void *buf, size_t size); +void curlx_memzero_low(void *buf, size_t size); #endif -void curlx_freezero(void *buf, size_t size); -void curlx_freezeroz(void *buf); +void curlx_memzero(void *buf, size_t size); +void curlx_strzero(void *buf); #endif /* HEADER_CURL_SETUP_H */ diff --git a/Utilities/cmcurl/lib/curl_sha256.h b/Utilities/cmcurl/lib/curl_sha256.h index 6c48a8e176..fb823169f7 100644 --- a/Utilities/cmcurl/lib/curl_sha256.h +++ b/Utilities/cmcurl/lib/curl_sha256.h @@ -26,7 +26,8 @@ ***************************************************************************/ #include "curl_setup.h" -#if !defined(CURL_DISABLE_AWS) || !defined(CURL_DISABLE_DIGEST_AUTH) || \ +#if !defined(CURL_DISABLE_AWS) || !defined(CURL_DISABLE_HTTPSIG) || \ + !defined(CURL_DISABLE_DIGEST_AUTH) || \ defined(USE_LIBSSH2) || defined(USE_SSL) #include "curl_hmac.h" diff --git a/Utilities/cmcurl/lib/curl_sha512_256.c b/Utilities/cmcurl/lib/curl_sha512_256.c index c429bba8f2..d0c81e7a55 100644 --- a/Utilities/cmcurl/lib/curl_sha512_256.c +++ b/Utilities/cmcurl/lib/curl_sha512_256.c @@ -112,7 +112,7 @@ static CURLcode Curl_sha512_256_init(void *context) { Curl_sha512_256_ctx * const ctx = (Curl_sha512_256_ctx *)context; - *ctx = EVP_MD_CTX_create(); + *ctx = EVP_MD_CTX_new(); if(!*ctx) return CURLE_OUT_OF_MEMORY; @@ -126,7 +126,7 @@ static CURLcode Curl_sha512_256_init(void *context) } /* Cleanup */ - EVP_MD_CTX_destroy(*ctx); + EVP_MD_CTX_free(*ctx); return CURLE_FAILED_INIT; } @@ -178,7 +178,7 @@ static CURLcode Curl_sha512_256_finish(unsigned char *digest, void *context) CURLE_OK : CURLE_BAD_FUNCTION_ARGUMENT; #endif /* NEED_NETBSD_SHA512_256_WORKAROUND */ - EVP_MD_CTX_destroy(*ctx); + EVP_MD_CTX_free(*ctx); *ctx = NULL; return result; @@ -650,7 +650,7 @@ static CURLcode Curl_sha512_256_update(void *context, if(length == 0) return CURLE_OK; /* Shortcut, do nothing */ - /* Note: (count & (CURL_SHA512_256_BLOCK_SIZE-1)) + /* Note: (count & (CURL_SHA512_256_BLOCK_SIZE - 1)) equals (count % CURL_SHA512_256_BLOCK_SIZE) for this block size. */ bytes_have = (unsigned int)(ctx->count & (CURL_SHA512_256_BLOCK_SIZE - 1)); ctx->count += length; diff --git a/Utilities/cmcurl/lib/curl_share.c b/Utilities/cmcurl/lib/curl_share.c index 94469bc3ef..cbadd57d37 100644 --- a/Utilities/cmcurl/lib/curl_share.c +++ b/Utilities/cmcurl/lib/curl_share.c @@ -34,8 +34,11 @@ static void share_destroy(struct Curl_share *share) { + if(!share) + return; + if(share->specifier & (1 << CURL_LOCK_DATA_CONNECT)) { - Curl_cpool_destroy(&share->cpool); + Curl_cpool_destroy(&share->cpool, share->admin); } Curl_dnscache_destroy(&share->dnscache); @@ -61,7 +64,7 @@ static void share_destroy(struct Curl_share *share) #ifdef USE_MUTEX Curl_mutex_destroy(&share->lock); #endif - share->magic = 0; + curlx_memzero(share, sizeof(*share)); curlx_free(share); } @@ -97,11 +100,11 @@ static uint32_t share_ref_inc(struct Curl_share *share) uint32_t n; #ifdef USE_MUTEX Curl_mutex_acquire(&share->lock); - n = ++(share->ref_count); + n = ++share->ref_count; share->has_been_shared = TRUE; Curl_mutex_release(&share->lock); #else - n = ++(share->ref_count); + n = ++share->ref_count; share->has_been_shared = TRUE; #endif return n; @@ -113,10 +116,10 @@ static uint32_t share_ref_dec(struct Curl_share *share) #ifdef USE_MUTEX Curl_mutex_acquire(&share->lock); DEBUGASSERT(share->ref_count); - n = --(share->ref_count); + n = --share->ref_count; Curl_mutex_release(&share->lock); #else - n = --(share->ref_count); + n = --share->ref_count; #endif return n; } @@ -261,7 +264,7 @@ CURLSHcode curl_share_setopt(CURLSH *sh, CURLSHoption option, ...) case CURL_LOCK_DATA_CONNECT: /* It is safe to set this option several times on a share. */ if(!share->cpool.initialized) { - Curl_cpool_init(&share->cpool, share->admin, share, 103); + Curl_cpool_init(&share->cpool, share, 103); } break; @@ -368,11 +371,11 @@ CURLSHcode curl_share_cleanup(CURLSH *sh) return CURLSHE_OK; } -CURLSHcode Curl_share_lock(struct Curl_easy *data, curl_lock_data type, - curl_lock_access accesstype) +CURLSHcode Curl_share_lock_share(struct Curl_share *share, + struct Curl_easy *data, + curl_lock_data type, + curl_lock_access accesstype) { - struct Curl_share *share = data->share; - if(!share) return CURLSHE_INVALID; @@ -385,10 +388,16 @@ CURLSHcode Curl_share_lock(struct Curl_easy *data, curl_lock_data type, return CURLSHE_OK; } -CURLSHcode Curl_share_unlock(struct Curl_easy *data, curl_lock_data type) +CURLSHcode Curl_share_lock(struct Curl_easy *data, curl_lock_data type, + curl_lock_access accesstype) { - struct Curl_share *share = data->share; + return Curl_share_lock_share(data->share, data, type, accesstype); +} +CURLSHcode Curl_share_unlock_share(struct Curl_share *share, + struct Curl_easy *data, + curl_lock_data type) +{ if(!share) return CURLSHE_INVALID; @@ -400,6 +409,11 @@ CURLSHcode Curl_share_unlock(struct Curl_easy *data, curl_lock_data type) return CURLSHE_OK; } +CURLSHcode Curl_share_unlock(struct Curl_easy *data, curl_lock_data type) +{ + return Curl_share_unlock_share(data->share, data, type); +} + CURLcode Curl_share_easy_unlink(struct Curl_easy *data) { struct Curl_share *share = data->share; @@ -407,9 +421,13 @@ CURLcode Curl_share_easy_unlink(struct Curl_easy *data) if(share) { bool locked = share_lock_acquire(share, data); - /* If data has a connection from this share, detach it. */ - if(data->conn && (share->specifier & (1 << CURL_LOCK_DATA_CONNECT))) - Curl_detach_connection(data); + /* If share caches connections, detach any existing connection and + * forget its identifier. */ + if((share->specifier & (1 << CURL_LOCK_DATA_CONNECT))) { + if(data->conn) + Curl_detach_connection(data); + data->state.lastconnect_id = -1; + } #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES) if(share->cookies == data->cookies) diff --git a/Utilities/cmcurl/lib/curl_share.h b/Utilities/cmcurl/lib/curl_share.h index 1d49a8d1dd..5c687989e0 100644 --- a/Utilities/cmcurl/lib/curl_share.h +++ b/Utilities/cmcurl/lib/curl_share.h @@ -80,6 +80,14 @@ CURLSHcode Curl_share_lock(struct Curl_easy *data, curl_lock_data type, curl_lock_access accesstype); CURLSHcode Curl_share_unlock(struct Curl_easy *data, curl_lock_data type); +CURLSHcode Curl_share_lock_share(struct Curl_share *share, + struct Curl_easy *data, + curl_lock_data type, + curl_lock_access accesstype); +CURLSHcode Curl_share_unlock_share(struct Curl_share *share, + struct Curl_easy *data, + curl_lock_data type); + /* convenience macro to check if this handle is using a shared SSL spool */ #define CURL_SHARE_ssl_scache(data) \ ((data)->share && \ diff --git a/Utilities/cmcurl/lib/curl_sspi.c b/Utilities/cmcurl/lib/curl_sspi.c index 3ea17621b1..799f3429a4 100644 --- a/Utilities/cmcurl/lib/curl_sspi.c +++ b/Utilities/cmcurl/lib/curl_sspi.c @@ -93,26 +93,29 @@ void Curl_sspi_global_cleanup(void) * Returns CURLE_OK on success. */ CURLcode Curl_create_sspi_identity(const char *userp, const char *passwdp, - SEC_WINNT_AUTH_IDENTITY *identity) + SEC_WINNT_AUTH_IDENTITY_EX *identity) { xcharp_u useranddomain; xcharp_u user, dup_user; xcharp_u domain, dup_domain; xcharp_u passwd, dup_passwd; size_t domlen = 0; + size_t pwlen; - domain.const_tchar_ptr = TEXT(""); + domain.const_tchar_ptr = _TEXT(""); /* Initialize the identity */ memset(identity, 0, sizeof(*identity)); + identity->Version = SEC_WINNT_AUTH_IDENTITY_VERSION; + identity->Length = sizeof(*identity); useranddomain.tchar_ptr = curlx_convert_UTF8_to_tchar(userp); if(!useranddomain.tchar_ptr) return CURLE_OUT_OF_MEMORY; - user.const_tchar_ptr = _tcschr(useranddomain.const_tchar_ptr, TEXT('\\')); + user.const_tchar_ptr = _tcschr(useranddomain.const_tchar_ptr, _TEXT('\\')); if(!user.const_tchar_ptr) - user.const_tchar_ptr = _tcschr(useranddomain.const_tchar_ptr, TEXT('/')); + user.const_tchar_ptr = _tcschr(useranddomain.const_tchar_ptr, _TEXT('/')); if(user.tchar_ptr) { domain.tchar_ptr = useranddomain.tchar_ptr; @@ -121,7 +124,7 @@ CURLcode Curl_create_sspi_identity(const char *userp, const char *passwdp, } else { user.tchar_ptr = useranddomain.tchar_ptr; - domain.const_tchar_ptr = TEXT(""); + domain.const_tchar_ptr = _TEXT(""); domlen = 0; } @@ -155,17 +158,20 @@ CURLcode Curl_create_sspi_identity(const char *userp, const char *passwdp, curlx_free(dup_domain.tchar_ptr); return CURLE_OUT_OF_MEMORY; } + pwlen = _tcslen(passwd.tchar_ptr); dup_passwd.tchar_ptr = curlx_tcsdup(passwd.tchar_ptr); if(!dup_passwd.tchar_ptr) { curlx_free(dup_user.tchar_ptr); curlx_free(dup_domain.tchar_ptr); + curlx_memzero(passwd.tchar_ptr, pwlen * sizeof(*passwd.tchar_ptr)); curlx_free(passwd.tchar_ptr); return CURLE_OUT_OF_MEMORY; } identity->Password = dup_passwd.tbyte_ptr; - identity->PasswordLength = curlx_uztoul(_tcslen(dup_passwd.tchar_ptr)); + identity->PasswordLength = curlx_uztoul(pwlen); dup_passwd.tchar_ptr = NULL; + curlx_memzero(passwd.tchar_ptr, pwlen * sizeof(*passwd.tchar_ptr)); curlx_free(passwd.tchar_ptr); identity->User = dup_user.tbyte_ptr; @@ -175,13 +181,7 @@ CURLcode Curl_create_sspi_identity(const char *userp, const char *passwdp, identity->DomainLength = curlx_uztoul(domlen); dup_domain.tchar_ptr = NULL; - /* Setup the identity's flags */ - identity->Flags = (unsigned long) -#ifdef UNICODE - SEC_WINNT_AUTH_IDENTITY_UNICODE; -#else - SEC_WINNT_AUTH_IDENTITY_ANSI; -#endif + identity->Flags = CURL_SEC_WINNT_AUTH_IDENTITY; return CURLE_OK; } @@ -195,10 +195,12 @@ CURLcode Curl_create_sspi_identity(const char *userp, const char *passwdp, * * identity [in/out] - The identity structure. */ -void Curl_sspi_free_identity(SEC_WINNT_AUTH_IDENTITY *identity) +void Curl_sspi_free_identity(SEC_WINNT_AUTH_IDENTITY_EX *identity) { if(identity) { curlx_safefree(identity->User); + curlx_memzero(identity->Password, + identity->PasswordLength * sizeof(*identity->Password)); curlx_safefree(identity->Password); curlx_safefree(identity->Domain); } diff --git a/Utilities/cmcurl/lib/curl_sspi.h b/Utilities/cmcurl/lib/curl_sspi.h index 2bd7eb4be8..da8e26d642 100644 --- a/Utilities/cmcurl/lib/curl_sspi.h +++ b/Utilities/cmcurl/lib/curl_sspi.h @@ -29,19 +29,36 @@ #include +/* Local helper macro */ +#ifdef UNICODE +#define CURL_SEC_WINNT_AUTH_IDENTITY SEC_WINNT_AUTH_IDENTITY_UNICODE +#else +#define CURL_SEC_WINNT_AUTH_IDENTITY SEC_WINNT_AUTH_IDENTITY_ANSI +#endif + +/* Offered by mingw-w64 v9+, MS SDK 7.0A/VS2010+ */ +#ifndef SECPKG_ATTR_ENDPOINT_BINDINGS +#define SECPKG_ATTR_ENDPOINT_BINDINGS 26 +/* !checksrc! disable TYPEDEFSTRUCT 1 */ +typedef struct { + unsigned long BindingsLength; + SEC_CHANNEL_BINDINGS *Bindings; +} SecPkgContext_Bindings; +#endif + CURLcode Curl_sspi_global_init(void); void Curl_sspi_global_cleanup(void); /* This is used to populate the domain in an SSPI identity structure */ CURLcode Curl_override_sspi_http_realm(const char *chlg, - SEC_WINNT_AUTH_IDENTITY *identity); + SEC_WINNT_AUTH_IDENTITY_EX *identity); /* This is used to generate an SSPI identity structure */ CURLcode Curl_create_sspi_identity(const char *userp, const char *passwdp, - SEC_WINNT_AUTH_IDENTITY *identity); + SEC_WINNT_AUTH_IDENTITY_EX *identity); /* This is used to free an SSPI identity structure */ -void Curl_sspi_free_identity(SEC_WINNT_AUTH_IDENTITY *identity); +void Curl_sspi_free_identity(SEC_WINNT_AUTH_IDENTITY_EX *identity); /* Forward-declaration of global variables defined in curl_sspi.c */ extern PSecurityFunctionTable Curl_pSecFn; diff --git a/Utilities/cmcurl/lib/curl_threads.c b/Utilities/cmcurl/lib/curl_threads.c index 01041f63c3..a5857b43b9 100644 --- a/Utilities/cmcurl/lib/curl_threads.c +++ b/Utilities/cmcurl/lib/curl_threads.c @@ -27,7 +27,41 @@ #ifdef USE_THREADS -#ifdef HAVE_THREADS_POSIX +#ifdef _WIN32 + +curl_thread_t Curl_thread_create( + CURL_THREAD_RETURN_T(CURL_STDCALL *func)(void *), void *arg) +{ + curl_thread_t t = CreateThread(NULL, 0, func, arg, 0, NULL); + if(!t) { + DWORD gle = GetLastError(); + /* !checksrc! disable ERRNOVAR 1 */ + errno = (gle == ERROR_ACCESS_DENIED || + gle == ERROR_NOT_ENOUGH_MEMORY) ? + EACCES : EINVAL; + return curl_thread_t_null; + } + return t; +} + +void Curl_thread_destroy(curl_thread_t *hnd) +{ + if(*hnd != curl_thread_t_null) { + CloseHandle(*hnd); + *hnd = curl_thread_t_null; + } +} + +int Curl_thread_join(curl_thread_t *hnd) +{ + int ret = (WaitForSingleObjectEx(*hnd, INFINITE, FALSE) == WAIT_OBJECT_0); + + Curl_thread_destroy(hnd); + + return ret; +} + +#elif defined(HAVE_THREADS_POSIX) struct Curl_actual_call { unsigned int (*func)(void *); @@ -95,40 +129,6 @@ int Curl_thread_join(curl_thread_t *hnd) return ret; } -#elif defined(_WIN32) - -curl_thread_t Curl_thread_create( - CURL_THREAD_RETURN_T(CURL_STDCALL *func)(void *), void *arg) -{ - curl_thread_t t = CreateThread(NULL, 0, func, arg, 0, NULL); - if(!t) { - DWORD gle = GetLastError(); - /* !checksrc! disable ERRNOVAR 1 */ - errno = (gle == ERROR_ACCESS_DENIED || - gle == ERROR_NOT_ENOUGH_MEMORY) ? - EACCES : EINVAL; - return curl_thread_t_null; - } - return t; -} - -void Curl_thread_destroy(curl_thread_t *hnd) -{ - if(*hnd != curl_thread_t_null) { - CloseHandle(*hnd); - *hnd = curl_thread_t_null; - } -} - -int Curl_thread_join(curl_thread_t *hnd) -{ - int ret = (WaitForSingleObjectEx(*hnd, INFINITE, FALSE) == WAIT_OBJECT_0); - - Curl_thread_destroy(hnd); - - return ret; -} - #else #error neither HAVE_THREADS_POSIX nor _WIN32 defined #endif @@ -136,7 +136,40 @@ int Curl_thread_join(curl_thread_t *hnd) #ifdef USE_MUTEX -#ifdef HAVE_THREADS_POSIX +#ifdef _WIN32 + +void Curl_cond_signal(CONDITION_VARIABLE *c) +{ + WakeConditionVariable(c); +} + +void Curl_cond_wait(CONDITION_VARIABLE *c, CRITICAL_SECTION *m) +{ + SleepConditionVariableCS(c, m, INFINITE); +} + +CURLcode Curl_cond_timedwait(CONDITION_VARIABLE *c, CRITICAL_SECTION *m, + uint32_t timeout_ms) +{ + if(!SleepConditionVariableCS(c, m, (DWORD)timeout_ms)) { + DWORD err = GetLastError(); + return (err == ERROR_TIMEOUT) ? + CURLE_OPERATION_TIMEDOUT : CURLE_UNRECOVERABLE_POLL; + } + return CURLE_OK; +} + +curl_thread_id_t Curl_thread_get_current_id(void) +{ + return GetCurrentThreadId(); +} + +bool Curl_thread_is_current(curl_thread_id_t tid) +{ + return tid == GetCurrentThreadId(); +} + +#elif defined(HAVE_THREADS_POSIX) void Curl_cond_signal(pthread_cond_t *c) { @@ -186,28 +219,16 @@ CURLcode Curl_cond_timedwait(pthread_cond_t *c, pthread_mutex_t *m, return rc ? CURLE_UNRECOVERABLE_POLL : CURLE_OK; } -#elif defined(_WIN32) - -void Curl_cond_signal(CONDITION_VARIABLE *c) +curl_thread_id_t Curl_thread_get_current_id(void) { - WakeConditionVariable(c); + return pthread_self(); } -void Curl_cond_wait(CONDITION_VARIABLE *c, CRITICAL_SECTION *m) +bool Curl_thread_is_current(curl_thread_id_t tid) { - SleepConditionVariableCS(c, m, INFINITE); + return !!pthread_equal(tid, pthread_self()); } -CURLcode Curl_cond_timedwait(CONDITION_VARIABLE *c, CRITICAL_SECTION *m, - uint32_t timeout_ms) -{ - if(!SleepConditionVariableCS(c, m, (DWORD)timeout_ms)) { - DWORD err = GetLastError(); - return (err == ERROR_TIMEOUT) ? - CURLE_OPERATION_TIMEDOUT : CURLE_UNRECOVERABLE_POLL; - } - return CURLE_OK; -} #else #error neither HAVE_THREADS_POSIX nor _WIN32 defined #endif diff --git a/Utilities/cmcurl/lib/curl_threads.h b/Utilities/cmcurl/lib/curl_threads.h index 081d155695..fc73c1222e 100644 --- a/Utilities/cmcurl/lib/curl_threads.h +++ b/Utilities/cmcurl/lib/curl_threads.h @@ -26,24 +26,12 @@ #include "curl_setup.h" #ifdef USE_MUTEX -#ifdef HAVE_THREADS_POSIX -# define CURL_THREAD_RETURN_T unsigned int -# define CURL_STDCALL -# define curl_mutex_t pthread_mutex_t -# define curl_thread_t pthread_t * -# define curl_thread_t_null (pthread_t *)0 -# define Curl_mutex_init(m) pthread_mutex_init(m, NULL) -# define Curl_mutex_acquire(m) pthread_mutex_lock(m) -# define Curl_mutex_release(m) pthread_mutex_unlock(m) -# define Curl_mutex_destroy(m) pthread_mutex_destroy(m) -# define curl_cond_t pthread_cond_t -# define Curl_cond_init(c) pthread_cond_init(c, NULL) -# define Curl_cond_destroy(c) pthread_cond_destroy(c) -#elif defined(_WIN32) +#ifdef _WIN32 # define CURL_THREAD_RETURN_T DWORD # define CURL_STDCALL WINAPI # define curl_mutex_t CRITICAL_SECTION # define curl_thread_t HANDLE +# define curl_thread_id_t DWORD # define curl_thread_t_null (HANDLE)0 # define Curl_mutex_init(m) InitializeCriticalSectionEx(m, 0, 1) # define Curl_mutex_acquire(m) EnterCriticalSection(m) @@ -52,6 +40,20 @@ # define curl_cond_t CONDITION_VARIABLE # define Curl_cond_init(c) InitializeConditionVariable(c) # define Curl_cond_destroy(c) (void)(c) +#elif defined(HAVE_THREADS_POSIX) +# define CURL_THREAD_RETURN_T unsigned int +# define CURL_STDCALL +# define curl_mutex_t pthread_mutex_t +# define curl_thread_t pthread_t * +# define curl_thread_id_t pthread_t +# define curl_thread_t_null (pthread_t *)0 +# define Curl_mutex_init(m) pthread_mutex_init(m, NULL) +# define Curl_mutex_acquire(m) pthread_mutex_lock(m) +# define Curl_mutex_release(m) pthread_mutex_unlock(m) +# define Curl_mutex_destroy(m) pthread_mutex_destroy(m) +# define curl_cond_t pthread_cond_t +# define Curl_cond_init(c) pthread_cond_init(c, NULL) +# define Curl_cond_destroy(c) pthread_cond_destroy(c) #else #error neither HAVE_THREADS_POSIX nor _WIN32 defined #endif @@ -61,6 +63,10 @@ void Curl_cond_wait(curl_cond_t *c, curl_mutex_t *m); /* Returns CURLE_OPERATION_TIMEDOUT on timeout */ CURLcode Curl_cond_timedwait(curl_cond_t *c, curl_mutex_t *m, uint32_t timeout_ms); + +curl_thread_id_t Curl_thread_get_current_id(void); +bool Curl_thread_is_current(curl_thread_id_t tid); + #endif /* USE_MUTEX */ #ifdef USE_THREADS diff --git a/Utilities/cmcurl/lib/curl_trc.c b/Utilities/cmcurl/lib/curl_trc.c index f8287e420f..2bf81e8119 100644 --- a/Utilities/cmcurl/lib/curl_trc.c +++ b/Utilities/cmcurl/lib/curl_trc.c @@ -28,7 +28,6 @@ #include "cfilters.h" #include "multiif.h" -#include "cf-dns.h" #include "cf-recvbuf.h" #include "cf-socket.h" #include "cf-setup.h" @@ -42,6 +41,7 @@ #include "progress.h" #include "socks.h" #include "curlx/strparse.h" +#include "vdns/cf-dns.h" #include "vtls/vtls.h" #include "vquic/vquic.h" #include "curlx/strcopy.h" @@ -51,11 +51,11 @@ static void trc_write(struct Curl_easy *data, curl_infotype type, { if(data->set.verbose) { if(data->set.fdebug) { - bool inCallback = Curl_is_in_callback(data); - Curl_set_in_callback(data, TRUE); + struct Curl_mapi_guard guard; + CURL_CBAPI_START(&guard, data, easy_fdebug); (void)(*data->set.fdebug)(data, type, CURL_UNCONST(ptr), size, data->set.debugdata); - Curl_set_in_callback(data, inCallback); + CURL_CBAPI_END(&guard); } else { static const char s_infotype[CURLINFO_END][3] = { @@ -92,8 +92,7 @@ static struct curl_trc_feat Curl_trc_feat_ids = { static size_t trc_print_ids(struct Curl_easy *data, char *buf, size_t maxlen) { - curl_off_t cid = data->conn ? - data->conn->connection_id : data->state.recent_conn_id; + curl_off_t cid = data->state.lastconnect_id; if(data->id >= 0) { if(cid >= 0) return curl_msnprintf(buf, maxlen, CURL_TRC_FMT_IDSDC, data->id, cid); @@ -133,22 +132,21 @@ void Curl_debug(struct Curl_easy *data, curl_infotype type, char buf[TRC_LINE_MAX]; size_t len; if(data->set.fdebug) { - bool inCallback = Curl_is_in_callback(data); - + struct Curl_mapi_guard guard; if(CURL_TRC_IDS(data) && (size < TRC_LINE_MAX)) { len = trc_print_ids(data, buf, TRC_LINE_MAX); len += curl_msnprintf(buf + len, TRC_LINE_MAX - len, "%.*s", (int)size, ptr); len = trc_end_buf(buf, len, TRC_LINE_MAX, FALSE); - Curl_set_in_callback(data, TRUE); + CURL_CBAPI_START(&guard, data, easy_fdebug); (void)(*data->set.fdebug)(data, type, buf, len, data->set.debugdata); - Curl_set_in_callback(data, inCallback); + CURL_CBAPI_END(&guard); } else { - Curl_set_in_callback(data, TRUE); + CURL_CBAPI_START(&guard, data, easy_fdebug); (void)(*data->set.fdebug)(data, type, CURL_UNCONST(ptr), size, data->set.debugdata); - Curl_set_in_callback(data, inCallback); + CURL_CBAPI_END(&guard); } } else { @@ -220,6 +218,12 @@ struct curl_trc_feat Curl_trc_feat_dns = { "DNS", CURL_LOG_LVL_NONE, }; +#ifndef CURL_DISABLE_DOH +struct curl_trc_feat Curl_trc_feat_doh = { + "DoH", + CURL_LOG_LVL_NONE, +}; +#endif struct curl_trc_feat Curl_trc_feat_timer = { "TIMER", CURL_LOG_LVL_NONE, @@ -303,9 +307,6 @@ static const char * const Curl_trc_timer_names[] = { "100_TIMEOUT", "ASYNC_NAME", "CONNECTTIMEOUT", - "DNS_PER_NAME", - "DNS_PER_NAME2", - "HAPPY_EYEBALLS_DNS", "HAPPY_EYEBALLS", "MULTI_PENDING", "SPEEDCHECK", @@ -321,7 +322,7 @@ static const char *trc_timer_name(int tid) { if((tid >= 0) && ((size_t)tid < CURL_ARRAYSIZE(Curl_trc_timer_names))) return Curl_trc_timer_names[(size_t)tid]; - return "UNKNOWN?"; + return "TIMER-???"; } void Curl_trc_timer(struct Curl_easy *data, int tid, const char *fmt, ...) @@ -338,15 +339,15 @@ void Curl_trc_timer(struct Curl_easy *data, int tid, const char *fmt, ...) void Curl_trc_easy_timers(struct Curl_easy *data) { - if(CURL_TRC_TIMER_is_verbose(data)) { - struct Curl_llist_node *e = Curl_llist_head(&data->state.timeoutlist); - if(e) { - const struct curltime *pnow = Curl_pgrs_now(data); - while(e) { - struct time_node *n = Curl_node_elem(e); - e = Curl_node_next(e); - CURL_TRC_TIMER(data, n->eid, "expires in %" FMT_TIMEDIFF_T "ns", - curlx_ptimediff_us(&n->time, pnow)); + if(CURL_TRC_TIMER_is_verbose(data) && data->multi) { + if(data->state.timeouts.first < EXPIRE_LAST) { + struct expire_timers *timeouts = &data->state.timeouts; + timediff_t base_us = + Curl_timeouts_offset_us(&data->multi->timeouts, Curl_pgrs_now(data)); + uint8_t id = data->state.timeouts.first; + for(; id < EXPIRE_LAST; id = timeouts->next[id]) { + CURL_TRC_TIMER(data, id, "expires in %" FMT_TIMEDIFF_T "us", + timeouts->offset_us[id] - base_us); } } } @@ -531,6 +532,9 @@ static struct trc_feat_def trc_feats[] = { { &Curl_trc_feat_read, TRC_CT_NONE }, { &Curl_trc_feat_write, TRC_CT_NONE }, { &Curl_trc_feat_dns, TRC_CT_NETWORK }, +#ifndef CURL_DISABLE_DOH + { &Curl_trc_feat_doh, TRC_CT_NETWORK }, +#endif { &Curl_trc_feat_timer, TRC_CT_NETWORK }, #ifdef USE_THREADS { &Curl_trc_feat_threads, TRC_CT_NONE }, @@ -545,7 +549,7 @@ static struct trc_feat_def trc_feats[] = { { &Curl_trc_feat_ssls, TRC_CT_NETWORK }, #endif #ifdef USE_SSH - { &Curl_trc_feat_ssh, TRC_CT_PROTOCOL }, + { &Curl_trc_feat_ssh, TRC_CT_PROTOCOL }, #endif #if !defined(CURL_DISABLE_WEBSOCKETS) && !defined(CURL_DISABLE_HTTP) { &Curl_trc_feat_ws, TRC_CT_PROTOCOL }, @@ -653,10 +657,6 @@ static CURLcode trc_opt(const char *config) trc_apply_level_by_category(TRC_CT_NETWORK, lvl); else if(curlx_str_casecompare(&out, "proxy")) trc_apply_level_by_category(TRC_CT_PROXY, lvl); - else if(curlx_str_casecompare(&out, "doh")) { - struct Curl_str dns = { "dns", 3 }; - trc_apply_level_by_name(&dns, lvl); - } else trc_apply_level_by_name(&out, lvl); diff --git a/Utilities/cmcurl/lib/curl_trc.h b/Utilities/cmcurl/lib/curl_trc.h index b4ae8e5314..fc0e6dedcf 100644 --- a/Utilities/cmcurl/lib/curl_trc.h +++ b/Utilities/cmcurl/lib/curl_trc.h @@ -307,6 +307,9 @@ extern struct curl_trc_feat Curl_trc_feat_multi; extern struct curl_trc_feat Curl_trc_feat_read; extern struct curl_trc_feat Curl_trc_feat_write; extern struct curl_trc_feat Curl_trc_feat_dns; +#ifndef CURL_DISABLE_DOH +extern struct curl_trc_feat Curl_trc_feat_doh; +#endif extern struct curl_trc_feat Curl_trc_feat_timer; #ifdef USE_THREADS extern struct curl_trc_feat Curl_trc_feat_threads; diff --git a/Utilities/cmcurl/lib/curlx/base64.c b/Utilities/cmcurl/lib/curlx/base64.c index 7f51576f50..519b045cfe 100644 --- a/Utilities/cmcurl/lib/curlx/base64.c +++ b/Utilities/cmcurl/lib/curlx/base64.c @@ -149,7 +149,7 @@ CURLcode curlx_base64_decode(const char *src, pos += 3 - padding; } - /* Null-terminate */ + /* null-terminate */ *pos = '\0'; /* Return the decoded data */ @@ -213,7 +213,7 @@ static CURLcode base64_encode(const char *table64, } } - /* Null-terminate */ + /* null-terminate */ *output = '\0'; /* Return the pointer to the new data (allocated memory) */ diff --git a/Utilities/cmcurl/lib/curlx/basename.c b/Utilities/cmcurl/lib/curlx/basename.c index d2fd160ff2..eda3d1195a 100644 --- a/Utilities/cmcurl/lib/curlx/basename.c +++ b/Utilities/cmcurl/lib/curlx/basename.c @@ -27,30 +27,28 @@ #include "curlx/basename.h" -/* - (Quote from The Open Group Base Specifications Issue 6 IEEE Std 1003.1, 2004 - Edition) +/* (Quote from The Open Group Base Specifications Issue 6 IEEE Std 1003.1, 2004 + Edition) - The basename() function shall take the pathname pointed to by path and - return a pointer to the final component of the pathname, deleting any - trailing '/' characters. + The basename() function shall take the pathname pointed to by path and + return a pointer to the final component of the pathname, deleting any + trailing '/' characters. - If the string pointed to by path consists entirely of the '/' character, - basename() shall return a pointer to the string "/". If the string pointed - to by path is exactly "//", it is implementation-defined whether '/' or "//" - is returned. + If the string pointed to by path consists entirely of the '/' character, + basename() shall return a pointer to the string "/". If the string pointed + to by path is exactly "//", it is implementation-defined whether '/' or "//" + is returned. - If path is a null pointer or points to an empty string, basename() shall - return a pointer to the string ".". + If path is a null pointer or points to an empty string, basename() shall + return a pointer to the string ".". - The basename() function may modify the string pointed to by path, and may - return a pointer to static storage that may then be overwritten by a - subsequent call to basename(). + The basename() function may modify the string pointed to by path, and may + return a pointer to static storage that may then be overwritten by a + subsequent call to basename(). - The basename() function need not be reentrant. A function that is not - required to be reentrant is not required to be thread-safe. - -*/ + The basename() function need not be reentrant. A function that is not + required to be reentrant is not required to be thread-safe. + */ char *curlx_basename(char *path) { /* Ignore all the details above for now and make a quick and simple diff --git a/Utilities/cmcurl/lib/curlx/fopen.c b/Utilities/cmcurl/lib/curlx/fopen.c index 37ca02671a..09264aeb10 100644 --- a/Utilities/cmcurl/lib/curlx/fopen.c +++ b/Utilities/cmcurl/lib/curlx/fopen.c @@ -68,7 +68,7 @@ static wchar_t *fn_convert_UTF8_to_wchar(const char *str_utf8) if(str_w_len > 0) { str_w = CURLX_MALLOC(str_w_len * sizeof(wchar_t)); if(str_w) { - if(MultiByteToWideChar(CP_UTF8, 0, + if(MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, str_utf8, -1, str_w, str_w_len) == 0) { CURLX_FREE(str_w); return NULL; @@ -110,7 +110,7 @@ static bool fix_excessive_path(const TCHAR *in, TCHAR **out) const wchar_t *in_w; wchar_t *fbuf = NULL; - /* MS documented "approximate" limit for the maximum path length */ + /* MS-documented "approximate" limit for the maximum path length */ const size_t max_path_len = 32767; #ifndef _UNICODE @@ -121,7 +121,7 @@ static bool fix_excessive_path(const TCHAR *in, TCHAR **out) *out = NULL; /* skip paths already normalized */ - if(!_tcsncmp(in, _T("\\\\?\\"), 4)) + if(!_tcsncmp(in, _TEXT("\\\\?\\"), 4)) goto cleanup; #ifndef _UNICODE @@ -291,6 +291,37 @@ HANDLE curlx_CreateFile(const char *filename, return handle; } + +HANDLE curlx_FindFirstFile(const char *filename, + WIN32_FIND_DATA *find_data) +{ + HANDLE handle = INVALID_HANDLE_VALUE; + +#ifdef UNICODE + TCHAR *filename_t = curlx_convert_UTF8_to_wchar(filename); +#else + const TCHAR *filename_t = filename; +#endif + + if(filename_t) { + TCHAR *fixed = NULL; + const TCHAR *target; + + if(fix_excessive_path(filename_t, &fixed)) + target = fixed; + else + target = filename_t; + + handle = FindFirstFile(target, find_data); + CURLX_FREE(fixed); + +#ifdef UNICODE + curlx_free(filename_t); +#endif + } + + return handle; +} #endif /* !CURL_WINDOWS_UWP */ int curlx_win32_open(const char *filename, int oflag, ...) diff --git a/Utilities/cmcurl/lib/curlx/fopen.h b/Utilities/cmcurl/lib/curlx/fopen.h index b64fbf6514..469d3c8813 100644 --- a/Utilities/cmcurl/lib/curlx/fopen.h +++ b/Utilities/cmcurl/lib/curlx/fopen.h @@ -43,6 +43,8 @@ HANDLE curlx_CreateFile(const char *filename, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile); +HANDLE curlx_FindFirstFile(const char *filename, + WIN32_FIND_DATA *find_data); #endif /* !CURL_WINDOWS_UWP */ #define curlx_fstat _fstati64 #define curlx_struct_stat struct _stati64 diff --git a/Utilities/cmcurl/lib/curlx/inet_ntop.c b/Utilities/cmcurl/lib/curlx/inet_ntop.c index 803b9887ac..ed06314520 100644 --- a/Utilities/cmcurl/lib/curlx/inet_ntop.c +++ b/Utilities/cmcurl/lib/curlx/inet_ntop.c @@ -18,8 +18,6 @@ */ #include "curl_setup.h" -#ifndef HAVE_INET_NTOP - #ifdef HAVE_SYS_PARAM_H #include #endif @@ -50,12 +48,12 @@ /* * Format an IPv4 address, more or less like inet_ntop(). * - * Returns `dst' (as a const) + * Returns CURLcode. * Note: * - uses no static variables * - takes an unsigned char* not an in_addr as input */ -static char *inet_ntop4(const unsigned char *src, char *dst, size_t size) +static CURLcode inet_ntop4(const unsigned char *src, char *dst, size_t size) { char tmp[sizeof("255.255.255.255")]; size_t len; @@ -70,22 +68,16 @@ static char *inet_ntop4(const unsigned char *src, char *dst, size_t size) ((int)((unsigned char)src[3])) & 0xff); len = strlen(tmp); - if(len == 0 || len >= size) { -#ifdef USE_WINSOCK - errno = WSAEINVAL; -#else - errno = ENOSPC; -#endif - return NULL; - } + if(len == 0 || len >= size) + return CURLE_TOO_LARGE; curlx_strcopy(dst, size, tmp, len); - return dst; + return CURLE_OK; } /* * Convert IPv6 binary address into presentation (printable) format. */ -static char *inet_ntop6(const unsigned char *src, char *dst, size_t size) +static CURLcode inet_ntop6(const unsigned char *src, char *dst, size_t size) { /* * Note that int32_t and int16_t need only be "at least" large enough @@ -154,9 +146,9 @@ static char *inet_ntop6(const unsigned char *src, char *dst, size_t size) */ if(i == 6 && best.base == 0 && (best.len == 6 || (best.len == 5 && words[5] == 0xffff))) { - if(!inet_ntop4(src + 12, tp, sizeof(tmp) - (tp - tmp))) { - return NULL; - } + CURLcode result = inet_ntop4(src + 12, tp, sizeof(tmp) - (tp - tmp)); + if(result) + return result; tp += strlen(tp); break; } @@ -183,31 +175,18 @@ static char *inet_ntop6(const unsigned char *src, char *dst, size_t size) *tp++ = ':'; /* Check for overflow, copy, and we are done. */ - if((size_t)(tp - tmp) >= size) { -#ifdef USE_WINSOCK - errno = WSAEINVAL; -#else - errno = ENOSPC; -#endif - return NULL; - } - + if((size_t)(tp - tmp) >= size) + return CURLE_TOO_LARGE; curlx_strcopy(dst, size, tmp, tp - tmp); - return dst; + return CURLE_OK; } /* * Convert a network format address to presentation format. * - * Returns pointer to presentation format address (`buf'). - * Returns NULL on error and errno set with the specific - * error, EAFNOSUPPORT or ENOSPC. - * - * On Windows we store the error in the thread errno, not in the Winsock error - * code. This is to avoid losing the actual last Winsock error. When this - * function returns NULL, check errno not SOCKERRNO. + * Copies result to 'buf' and returns CURLcode. */ -char *curlx_inet_ntop(int af, const void *src, char *buf, size_t size) +CURLcode curlx_inet_ntop(int af, const void *src, char *buf, size_t size) { switch(af) { case AF_INET: @@ -215,8 +194,6 @@ char *curlx_inet_ntop(int af, const void *src, char *buf, size_t size) case AF_INET6: return inet_ntop6((const unsigned char *)src, buf, size); default: - errno = SOCKEAFNOSUPPORT; - return NULL; + return CURLE_UNSUPPORTED_PROTOCOL; } } -#endif /* HAVE_INET_NTOP */ diff --git a/Utilities/cmcurl/lib/curlx/inet_ntop.h b/Utilities/cmcurl/lib/curlx/inet_ntop.h index ba8299fe4e..f0abeb80b7 100644 --- a/Utilities/cmcurl/lib/curlx/inet_ntop.h +++ b/Utilities/cmcurl/lib/curlx/inet_ntop.h @@ -25,26 +25,6 @@ ***************************************************************************/ #include "curl_setup.h" -#ifdef HAVE_INET_NTOP -#ifdef HAVE_NETINET_IN_H -#include -#endif -#ifndef _WIN32 -#include -#endif -#ifdef HAVE_ARPA_INET_H -#include -#endif -#ifdef __AMIGA__ -#define curlx_inet_ntop(af, src, buf, size) \ - (char *)inet_ntop(af, CURL_UNCONST(src), (unsigned char *)(buf), \ - (curl_socklen_t)(size)) -#else -#define curlx_inet_ntop(af, src, buf, size) \ - inet_ntop(af, src, buf, (curl_socklen_t)(size)) -#endif -#else -char *curlx_inet_ntop(int af, const void *src, char *buf, size_t size); -#endif /* HAVE_INET_NTOP */ +CURLcode curlx_inet_ntop(int af, const void *src, char *buf, size_t size); #endif /* HEADER_CURL_INET_NTOP_H */ diff --git a/Utilities/cmcurl/lib/curlx/inet_pton.c b/Utilities/cmcurl/lib/curlx/inet_pton.c index 7994f258fe..23e76725fe 100644 --- a/Utilities/cmcurl/lib/curlx/inet_pton.c +++ b/Utilities/cmcurl/lib/curlx/inet_pton.c @@ -19,8 +19,6 @@ */ #include "curl_setup.h" -#ifndef HAVE_INET_PTON - #ifdef HAVE_SYS_PARAM_H #include #endif @@ -189,18 +187,15 @@ static int inet_pton6(const char *src, unsigned char *dst) return 1; } -/* int inet_pton(af, src, dst) - * convert from presentation format (which usually means ASCII printable) - * to network format (which is usually some kind of binary format). - * return: - * 1 if the address was valid for the specified address family - * 0 if the address was not valid (`dst' is untouched in this case) - * -1 if some other error occurred (`dst' is untouched in this case, too) - * notice: - * On Windows we store the error in the thread errno, not - * in the Winsock error code. This is to avoid losing the - * actual last Winsock error. When this function returns - * -1, check errno not SOCKERRNO. +/* + * Convert from presentation format (which usually means ASCII printable) + * to network format (which is usually some kind of binary format). + * + * Return: + * 1 if the address was valid for the specified address family + * 0 if the address was not valid (`dst' is untouched in this case) + * -1 if some other error occurred (`dst' is untouched in this case, too) + * * author: * Paul Vixie, 1996. */ @@ -212,10 +207,7 @@ int curlx_inet_pton(int af, const char *src, void *dst) case AF_INET6: return inet_pton6(src, (unsigned char *)dst); default: - errno = SOCKEAFNOSUPPORT; return -1; } /* NOTREACHED */ } - -#endif /* HAVE_INET_PTON */ diff --git a/Utilities/cmcurl/lib/curlx/inet_pton.h b/Utilities/cmcurl/lib/curlx/inet_pton.h index 02ae7f2269..4e45e83285 100644 --- a/Utilities/cmcurl/lib/curlx/inet_pton.h +++ b/Utilities/cmcurl/lib/curlx/inet_pton.h @@ -25,25 +25,6 @@ ***************************************************************************/ #include "curl_setup.h" -#ifdef HAVE_INET_PTON -#ifdef HAVE_NETINET_IN_H -#include -#endif -#ifndef _WIN32 -#include -#endif -#ifdef HAVE_ARPA_INET_H -#include -#endif -#ifdef __AMIGA__ -#define curlx_inet_pton(x, y, z) \ - inet_pton(x, (unsigned char *)CURL_UNCONST(y), z) -#else -#define curlx_inet_pton(x, y, z) \ - inet_pton(x, y, z) -#endif -#else int curlx_inet_pton(int af, const char *src, void *dst); -#endif /* HAVE_INET_PTON */ #endif /* HEADER_CURL_INET_PTON_H */ diff --git a/Utilities/cmcurl/lib/curlx/multibyte.c b/Utilities/cmcurl/lib/curlx/multibyte.c index 715d2b8dc2..4ee39d962a 100644 --- a/Utilities/cmcurl/lib/curlx/multibyte.c +++ b/Utilities/cmcurl/lib/curlx/multibyte.c @@ -41,8 +41,8 @@ wchar_t *curlx_convert_UTF8_to_wchar(const char *str_utf8) if(str_w_len > 0) { str_w = curlx_malloc(str_w_len * sizeof(wchar_t)); if(str_w) { - if(MultiByteToWideChar(CP_UTF8, 0, str_utf8, -1, str_w, - str_w_len) == 0) { + if(MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, + str_utf8, -1, str_w, str_w_len) == 0) { curlx_free(str_w); return NULL; } diff --git a/Utilities/cmcurl/lib/curlx/strdup.c b/Utilities/cmcurl/lib/curlx/strdup.c index 8e788ea34c..ebc58c2363 100644 --- a/Utilities/cmcurl/lib/curlx/strdup.c +++ b/Utilities/cmcurl/lib/curlx/strdup.c @@ -101,25 +101,23 @@ static void *(* const volatile p_curlx_memset)(void *buf, int val, /* Local fallback in case there is no system function to securely zero a memory buffer. */ -void curlx_memzero(void *buf, size_t size) +void curlx_memzero_low(void *buf, size_t size) { if(buf) p_curlx_memset(buf, 0, size); } #endif -/* Free 'buf' after zeroing its content. */ -void curlx_freezero(void *buf, size_t size) +/* Fill 'buf' with zeroes. */ +void curlx_memzero(void *buf, size_t size) { if(buf) - curlx_memzero(buf, size); - curlx_free(buf); + curlx_memzero_low(buf, size); } -/* Free 'buf' after zeroing its content, where 'buf' is null-terminated. */ -void curlx_freezeroz(void *buf) +/* Fill 'buf' with zeroes, where 'buf' is null-terminated. */ +void curlx_strzero(void *buf) { if(buf) - curlx_memzero(buf, strlen(buf)); - curlx_free(buf); + curlx_memzero_low(buf, strlen(buf)); } diff --git a/Utilities/cmcurl/lib/curlx/strparse.c b/Utilities/cmcurl/lib/curlx/strparse.c index 736f620504..9e8fe4963d 100644 --- a/Utilities/cmcurl/lib/curlx/strparse.c +++ b/Utilities/cmcurl/lib/curlx/strparse.c @@ -263,7 +263,7 @@ int curlx_str_cmp(struct Curl_str *str, const char *check) size_t clen = strlen(check); return ((str->len == clen) && !strncmp(str->str, check, clen)); } - return !!(str->len); + return !!str->len; } /* Trim off 'num' number of bytes from the beginning (left side) of the diff --git a/Utilities/cmcurl/lib/curlx/strparse.h b/Utilities/cmcurl/lib/curlx/strparse.h index c7801b2cb3..7963dd6212 100644 --- a/Utilities/cmcurl/lib/curlx/strparse.h +++ b/Utilities/cmcurl/lib/curlx/strparse.h @@ -106,8 +106,7 @@ void curlx_str_passblanks(const char **linep); /* given a hexadecimal letter, return the binary value. '0' returns 0, 'a' returns 10. THIS ONLY WORKS ON VALID HEXADECIMAL LETTER INPUT. Verify - before calling this! -*/ + before calling this. */ extern const unsigned char curlx_hexasciitable[]; #define curlx_hexval(x) (unsigned char)(curlx_hexasciitable[(x) - '0'] & 0x0f) diff --git a/Utilities/cmcurl/lib/curlx/timeval.c b/Utilities/cmcurl/lib/curlx/timeval.c index 2363e60839..b43aedf15c 100644 --- a/Utilities/cmcurl/lib/curlx/timeval.c +++ b/Utilities/cmcurl/lib/curlx/timeval.c @@ -25,26 +25,24 @@ #ifdef _WIN32 -#include "system_win32.h" - -LARGE_INTEGER Curl_freq; +static LARGE_INTEGER s_time_freq; /* For tool or tests, we must initialize before calling curlx_now(). Providing this function here is wrong. */ void curlx_now_init(void) { - QueryPerformanceFrequency(&Curl_freq); + QueryPerformanceFrequency(&s_time_freq); } /* In case of bug fix this function has a counterpart in tool_util.c */ void curlx_pnow(struct curltime *pnow) { LARGE_INTEGER count; - DEBUGASSERT(Curl_freq.QuadPart); + DEBUGASSERT(s_time_freq.QuadPart); QueryPerformanceCounter(&count); - pnow->tv_sec = (time_t)(count.QuadPart / Curl_freq.QuadPart); - pnow->tv_usec = (int)((count.QuadPart % Curl_freq.QuadPart) * 1000000 / - Curl_freq.QuadPart); + pnow->tv_sec = (time_t)(count.QuadPart / s_time_freq.QuadPart); + pnow->tv_usec = (int)((count.QuadPart % s_time_freq.QuadPart) * 1000000 / + s_time_freq.QuadPart); } #elif defined(HAVE_CLOCK_GETTIME_MONOTONIC) || \ @@ -216,6 +214,11 @@ timediff_t curlx_timediff_ceil_ms(struct curltime newer, return (diff * 1000) + ((newer.tv_usec - older.tv_usec + 999) / 1000); } +timediff_t curlx_us_to_ceil_ms(timediff_t us) +{ + return (us / 1000) + ((us > 0) && (us % 1000)); +} + /* * Returns: time difference in number of microseconds. For too large diffs it * returns max value. diff --git a/Utilities/cmcurl/lib/curlx/timeval.h b/Utilities/cmcurl/lib/curlx/timeval.h index c01f95d87d..284c452df0 100644 --- a/Utilities/cmcurl/lib/curlx/timeval.h +++ b/Utilities/cmcurl/lib/curlx/timeval.h @@ -59,6 +59,9 @@ timediff_t curlx_ptimediff_ms(const struct curltime *newer, timediff_t curlx_timediff_ceil_ms(struct curltime newer, struct curltime older); +/* Returns milliseconds from microseconds, rounded up. */ +timediff_t curlx_us_to_ceil_ms(timediff_t us); + /* * Make sure that the first argument (newer) is the more recent time and older * is the older time, as otherwise you get a weird negative time-diff back... diff --git a/Utilities/cmcurl/lib/curlx/wait.c b/Utilities/cmcurl/lib/curlx/wait.c index e50a0f0af6..7610a4ba36 100644 --- a/Utilities/cmcurl/lib/curlx/wait.c +++ b/Utilities/cmcurl/lib/curlx/wait.c @@ -23,10 +23,6 @@ ***************************************************************************/ #include "curl_setup.h" -#ifndef HAVE_SELECT -#error "We cannot compile without select() support." -#endif - #ifdef HAVE_SYS_SELECT_H #include #elif defined(HAVE_UNISTD_H) diff --git a/Utilities/cmcurl/lib/curlx/winapi.c b/Utilities/cmcurl/lib/curlx/winapi.c index f025ca48c9..1d6ef87abe 100644 --- a/Utilities/cmcurl/lib/curlx/winapi.c +++ b/Utilities/cmcurl/lib/curlx/winapi.c @@ -103,4 +103,24 @@ const char *curlx_winapi_strerror(DWORD err, char *buf, size_t buflen) return buf; } + +#ifndef WITHOUT_LIBCURL + +#include +#ifndef STATUS_SUCCESS +#define STATUS_SUCCESS ((NTSTATUS)0x00000000L) +#endif + +CURLcode curlx_win32_random(unsigned char *entropy, size_t length) +{ + memset(entropy, 0, length); + + if(BCryptGenRandom(NULL, entropy, (ULONG)length, + BCRYPT_USE_SYSTEM_PREFERRED_RNG) != STATUS_SUCCESS) + return CURLE_FAILED_INIT; + + return CURLE_OK; +} +#endif /* WITHOUT_LIBCURL */ + #endif /* _WIN32 */ diff --git a/Utilities/cmcurl/lib/curlx/winapi.h b/Utilities/cmcurl/lib/curlx/winapi.h index d30f5efa13..21e864c028 100644 --- a/Utilities/cmcurl/lib/curlx/winapi.h +++ b/Utilities/cmcurl/lib/curlx/winapi.h @@ -28,6 +28,7 @@ #define WINAPI_ERROR_LEN 100 const char *curlx_get_winapi_error(DWORD err, char *buf, size_t buflen); const char *curlx_winapi_strerror(DWORD err, char *buf, size_t buflen); +CURLcode curlx_win32_random(unsigned char *entropy, size_t length); #endif #endif /* HEADER_CURLX_WINAPI_H */ diff --git a/Utilities/cmcurl/lib/cw-out.c b/Utilities/cmcurl/lib/cw-out.c index fad89b392c..0137975b1e 100644 --- a/Utilities/cmcurl/lib/cw-out.c +++ b/Utilities/cmcurl/lib/cw-out.c @@ -97,7 +97,6 @@ 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); }; @@ -171,8 +170,7 @@ static void cw_get_writefunc(struct Curl_easy *data, cw_out_type otype, } } -static CURLcode cw_out_cb_write(struct cw_out_ctx *ctx, - struct Curl_easy *data, +static CURLcode cw_out_cb_write(struct Curl_easy *data, curl_write_callback wcb, void *wcb_data, cw_out_type otype, @@ -186,12 +184,15 @@ static CURLcode cw_out_cb_write(struct cw_out_ctx *ctx, DEBUGASSERT(data->conn); *pnwritten = 0; - Curl_set_in_callback(data, TRUE); - nwritten = wcb((char *)CURL_UNCONST(buf), 1, blen, wcb_data); - Curl_set_in_callback(data, FALSE); - CURL_TRC_WRITE(data, "[OUT] wrote %zu %s bytes -> %zu", + { + struct Curl_mapi_guard guard; + CURL_CBAPI_START(&guard, data, easy_cw_out_cb); + nwritten = wcb((char *)CURL_UNCONST(buf), 1, blen, wcb_data); + CURL_CBAPI_END(&guard); + } + CURL_TRC_WRITE(data, "[OUT] wrote %zu %s bytes, type=%x -> %zu", blen, (otype == CW_OUT_HDS) ? "header" : "body", - nwritten); + (unsigned int)otype, nwritten); if(nwritten == CURL_WRITEFUNC_PAUSE) { if(data->conn->scheme->flags & PROTOPT_NONETWORK) { /* Protocols that work without network cannot be paused. This is @@ -200,7 +201,7 @@ static CURLcode cw_out_cb_write(struct cw_out_ctx *ctx, failf(data, "Write callback asked for PAUSE when not supported"); return CURLE_WRITE_ERROR; } - ctx->paused = TRUE; + data->req.writer.paused = TRUE; CURL_TRC_WRITE(data, "[OUT] PAUSE requested by client"); result = Curl_xfer_pause_recv(data, TRUE); return result ? result : CURLE_AGAIN; @@ -245,18 +246,18 @@ static CURLcode cw_out_ptr_flush(struct cw_out_ctx *ctx, *pconsumed = 0; if(otype == CW_OUT_BODY_0LEN) { DEBUGASSERT(!blen); - return cw_out_cb_write(ctx, data, wcb, wcb_data, otype, + return cw_out_cb_write(data, wcb, wcb_data, otype, buf, blen, &nwritten); } else { - while(blen && !ctx->paused) { + while(blen && !data->req.writer.paused) { if(!flush_all && blen < min_write) break; wlen = max_write ? CURLMIN(blen, max_write) : blen; if(otype == CW_OUT_BODY) result = Curl_pgrs_deliver_check(data, wlen); if(!result) - result = cw_out_cb_write(ctx, data, wcb, wcb_data, otype, + result = cw_out_cb_write(data, wcb, wcb_data, otype, buf, wlen, &nwritten); if(result) return result; @@ -314,7 +315,7 @@ static CURLcode cw_out_flush_chain(struct cw_out_ctx *ctx, if(!cwbuf) return CURLE_OK; - if(ctx->paused) + if(data->req.writer.paused) return CURLE_OK; /* write the end of the chain until it blocks or gets empty */ @@ -327,7 +328,7 @@ static CURLcode cw_out_flush_chain(struct cw_out_ctx *ctx, return result; if(*plast) { /* could not write last, paused again? */ - DEBUGASSERT(ctx->paused); + DEBUGASSERT(data->req.writer.paused); return CURLE_OK; } } @@ -412,7 +413,7 @@ static CURLcode cw_out_do_write(struct cw_out_ctx *ctx, out: if(result) { - /* We do not want to invoked client callbacks a second time after + /* We do not want to invoke client callbacks a second time after * encountering an error. See issue #13337 */ ctx->errored = TRUE; cw_out_bufs_free(ctx); @@ -446,66 +447,46 @@ static CURLcode cw_out_write(struct Curl_easy *data, return CURLE_OK; } -const struct Curl_cwtype Curl_cwt_out = { - "cw-out", - NULL, - cw_out_init, - cw_out_write, - cw_out_close, - sizeof(struct cw_out_ctx) -}; - -bool Curl_cw_out_is_paused(struct Curl_easy *data) -{ - struct Curl_cwriter *cw_out; - struct cw_out_ctx *ctx; - - cw_out = Curl_cwriter_get_by_type(data, &Curl_cwt_out); - if(!cw_out) - return FALSE; - - ctx = (struct cw_out_ctx *)cw_out; - return (bool)ctx->paused; -} - -static CURLcode cw_out_flush(struct Curl_easy *data, - struct Curl_cwriter *cw_out, - bool flush_all) +static CURLcode cw_out_do_flush(struct Curl_easy *data, + struct Curl_cwriter *cw_out, + bool flush_all) { struct cw_out_ctx *ctx = (struct cw_out_ctx *)cw_out; - CURLcode result = CURLE_OK; if(ctx->errored) return CURLE_WRITE_ERROR; - 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; + if(!data->req.writer.paused && ctx->buf) { + CURLcode result; + + CURL_TRC_WRITE(data, "[OUT] flush"); + 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; + return CURLE_OK; } -CURLcode Curl_cw_out_unpause(struct Curl_easy *data) +static CURLcode cw_out_flush(struct Curl_easy *data, + struct Curl_cwriter *writer) { - struct Curl_cwriter *cw_out; - CURLcode result = CURLE_OK; - - 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; - CURL_TRC_WRITE(data, "[OUT] unpause"); - ctx->paused = FALSE; - result = Curl_cw_pause_flush(data); - if(!result) - result = cw_out_flush(data, cw_out, FALSE); - } - return result; + return cw_out_do_flush(data, writer, FALSE); } +const struct Curl_cwtype Curl_cwt_out = { + "cw-out", + NULL, + 0, + cw_out_init, + cw_out_write, + cw_out_flush, + cw_out_close, + sizeof(struct cw_out_ctx) +}; + CURLcode Curl_cw_out_done(struct Curl_easy *data) { struct Curl_cwriter *cw_out; @@ -514,9 +495,9 @@ CURLcode Curl_cw_out_done(struct Curl_easy *data) cw_out = Curl_cwriter_get_by_type(data, &Curl_cwt_out); if(cw_out) { CURL_TRC_WRITE(data, "[OUT] done"); - result = Curl_cw_pause_flush(data); + result = Curl_client_flush(data); if(!result) - result = cw_out_flush(data, cw_out, TRUE); + result = cw_out_do_flush(data, cw_out, TRUE); } return result; } diff --git a/Utilities/cmcurl/lib/cw-out.h b/Utilities/cmcurl/lib/cw-out.h index 7de6524bc5..ee2b4b6307 100644 --- a/Utilities/cmcurl/lib/cw-out.h +++ b/Utilities/cmcurl/lib/cw-out.h @@ -34,16 +34,6 @@ struct Curl_easy; */ extern const struct Curl_cwtype Curl_cwt_out; -/** - * Return TRUE iff 'cw-out' client write has paused data. - */ -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_unpause(struct Curl_easy *data); - /** * Mark EndOfStream reached and flush ALL data to the client. */ diff --git a/Utilities/cmcurl/lib/cw-pause.c b/Utilities/cmcurl/lib/cw-pause.c index 5561a8d7b7..a5b9ba148e 100644 --- a/Utilities/cmcurl/lib/cw-pause.c +++ b/Utilities/cmcurl/lib/cw-pause.c @@ -100,7 +100,6 @@ static CURLcode cw_pause_flush(struct Curl_easy *data, struct Curl_cwriter *cw_pause) { struct cw_pause_ctx *ctx = (struct cw_pause_ctx *)cw_pause; - bool decoding = Curl_cwriter_is_content_decoding(data); CURLcode result = CURLE_OK; /* write the end of the chain until it blocks or gets empty */ @@ -112,7 +111,7 @@ static CURLcode cw_pause_flush(struct Curl_easy *data, while((*plast)->next) /* got to last in list */ plast = &(*plast)->next; if(Curl_bufq_peek(&(*plast)->b, &buf, &blen)) { - wlen = (decoding && ((*plast)->type & CLIENTWRITE_BODY)) ? + wlen = ((*plast)->type & CLIENTWRITE_BODY) ? CURLMIN(blen, CW_PAUSE_DEC_WRITE_CHUNK) : blen; result = Curl_cwriter_write(data, cw_pause->next, (*plast)->type, (const char *)buf, wlen); @@ -138,6 +137,9 @@ static CURLcode cw_pause_flush(struct Curl_easy *data, *plast = NULL; } } + + if(!result) + result = Curl_cwriter_flush(data, cw_pause->next); return result; } @@ -148,7 +150,6 @@ static CURLcode cw_pause_write(struct Curl_easy *data, struct cw_pause_ctx *ctx = writer->ctx; CURLcode result = CURLE_OK; size_t wlen = 0; - bool decoding = Curl_cwriter_is_content_decoding(data); if(ctx->buf && !Curl_cwriter_is_paused(data)) { result = cw_pause_flush(data, writer); @@ -161,13 +162,11 @@ static CURLcode cw_pause_write(struct Curl_easy *data, DEBUGASSERT(!ctx->buf); /* content decoding might blow up size considerably, write smaller * chunks to make pausing need buffer less. */ - wlen = (decoding && (type & CLIENTWRITE_BODY)) ? + wlen = (type & CLIENTWRITE_BODY) ? CURLMIN(blen, CW_PAUSE_DEC_WRITE_CHUNK) : blen; if(wlen < blen) wtype &= ~CLIENTWRITE_EOS; result = Curl_cwriter_write(data, writer->next, wtype, buf, wlen); - CURL_TRC_WRITE(data, "[PAUSE] writing %zu/%zu bytes of type %x -> %d", - wlen, blen, (unsigned int)wtype, (int)result); if(result) return result; buf += wlen; @@ -208,20 +207,10 @@ static CURLcode cw_pause_write(struct Curl_easy *data, const struct Curl_cwtype Curl_cwt_pause = { "cw-pause", NULL, + 0, cw_pause_init, cw_pause_write, + cw_pause_flush, cw_pause_close, sizeof(struct cw_pause_ctx) }; - -CURLcode Curl_cw_pause_flush(struct Curl_easy *data) -{ - struct Curl_cwriter *cw_pause; - CURLcode result = CURLE_OK; - - cw_pause = Curl_cwriter_get_by_type(data, &Curl_cwt_pause); - if(cw_pause) - result = cw_pause_flush(data, cw_pause); - - return result; -} diff --git a/Utilities/cmcurl/lib/cw-pause.h b/Utilities/cmcurl/lib/cw-pause.h index 544cbfa577..9a41a1c9b1 100644 --- a/Utilities/cmcurl/lib/cw-pause.h +++ b/Utilities/cmcurl/lib/cw-pause.h @@ -33,6 +33,4 @@ struct Curl_easy; */ extern const struct Curl_cwtype Curl_cwt_pause; -CURLcode Curl_cw_pause_flush(struct Curl_easy *data); - #endif /* HEADER_CURL_CW_PAUSE_H */ diff --git a/Utilities/cmcurl/lib/dict.c b/Utilities/cmcurl/lib/dict.c index db25d5a721..75cdac3714 100644 --- a/Utilities/cmcurl/lib/dict.c +++ b/Utilities/cmcurl/lib/dict.c @@ -55,6 +55,8 @@ #include "transfer.h" #include "curl_trc.h" +#include "connect.h" +#include "select.h" #include "escape.h" #define DICT_MATCH "/MATCH:" @@ -93,9 +95,12 @@ static CURLcode sendf(struct Curl_easy *data, static CURLcode sendf(struct Curl_easy *data, const char *fmt, ...) { + curl_socket_t sockfd = data->conn->sock[FIRSTSOCKET]; size_t bytes_written; size_t write_len; CURLcode result = CURLE_OK; + timediff_t timeout_ms; + int what; char *s; char *sptr; va_list ap; @@ -126,6 +131,29 @@ static CURLcode sendf(struct Curl_easy *data, const char *fmt, ...) } else break; + + timeout_ms = Curl_timeleft_ms(data); + if(timeout_ms < 0) { + result = CURLE_OPERATION_TIMEDOUT; + break; + } + if(!timeout_ms) + timeout_ms = TIMEDIFF_T_MAX; + + /* Do not busyloop. The entire loop thing is a workaround as it causes a + BLOCKING behavior which is a NO-NO. This function should rather be + split up in a do and a doing piece where the pieces that are not + possible to send now will be sent in the doing function repeatedly + until the entire request is sent. */ + what = SOCKET_WRITABLE(sockfd, timeout_ms); + if(what < 0) { + result = CURLE_SEND_ERROR; + break; + } + else if(!what) { + result = CURLE_OPERATION_TIMEDOUT; + break; + } } curlx_free(s); /* free the output string */ @@ -153,9 +181,9 @@ static CURLcode dict_do(struct Curl_easy *data, bool *done) if(result) return result; - if(curl_strnequal(path, DICT_MATCH, sizeof(DICT_MATCH) - 1) || - curl_strnequal(path, DICT_MATCH2, sizeof(DICT_MATCH2) - 1) || - curl_strnequal(path, DICT_MATCH3, sizeof(DICT_MATCH3) - 1)) { + if(curl_strnequal(path, DICT_MATCH, CURL_CSTRLEN(DICT_MATCH)) || + curl_strnequal(path, DICT_MATCH2, CURL_CSTRLEN(DICT_MATCH2)) || + curl_strnequal(path, DICT_MATCH3, CURL_CSTRLEN(DICT_MATCH3))) { word = strchr(path, ':'); if(word) { @@ -200,9 +228,9 @@ static CURLcode dict_do(struct Curl_easy *data, bool *done) } Curl_xfer_setup_recv(data, FIRSTSOCKET, -1); } - else if(curl_strnequal(path, DICT_DEFINE, sizeof(DICT_DEFINE) - 1) || - curl_strnequal(path, DICT_DEFINE2, sizeof(DICT_DEFINE2) - 1) || - curl_strnequal(path, DICT_DEFINE3, sizeof(DICT_DEFINE3) - 1)) { + else if(curl_strnequal(path, DICT_DEFINE, CURL_CSTRLEN(DICT_DEFINE)) || + curl_strnequal(path, DICT_DEFINE2, CURL_CSTRLEN(DICT_DEFINE2)) || + curl_strnequal(path, DICT_DEFINE3, CURL_CSTRLEN(DICT_DEFINE3))) { word = strchr(path, ':'); if(word) { diff --git a/Utilities/cmcurl/lib/easy.c b/Utilities/cmcurl/lib/easy.c index d60bdaed7b..04c5a003a0 100644 --- a/Utilities/cmcurl/lib/easy.c +++ b/Utilities/cmcurl/lib/easy.c @@ -44,13 +44,14 @@ #endif #include "urldata.h" +#include "api.h" #include "transfer.h" +#include "vdns/hostip.h" #include "vtls/vtls.h" #include "vtls/vtls_scache.h" #include "vquic/vquic.h" #include "url.h" #include "getinfo.h" -#include "hostip.h" #include "curlx/strdup.h" #include "easyif.h" #include "multiif.h" @@ -79,7 +80,9 @@ /* true globals -- for curl_global_init() and curl_global_cleanup() */ static unsigned int initialized; +#ifdef _WIN32 static long easy_init_flags; +#endif #ifdef GLOBAL_INIT_IS_THREADSAFE @@ -135,6 +138,11 @@ static CURLcode global_init(long flags, bool memoryfuncs) Curl_ccalloc = (curl_calloc_callback)calloc; } + if(Curl_win32_init(flags)) { + DEBUGF(curl_mfprintf(stderr, "Error: win32_init failed\n")); + goto fail; + } + if(Curl_trc_init()) { DEBUGF(curl_mfprintf(stderr, "Error: Curl_trc_init failed\n")); goto fail; @@ -150,11 +158,6 @@ static CURLcode global_init(long flags, bool memoryfuncs) goto fail; } - if(Curl_win32_init(flags)) { - DEBUGF(curl_mfprintf(stderr, "Error: win32_init failed\n")); - goto fail; - } - if(Curl_amiga_init()) { DEBUGF(curl_mfprintf(stderr, "Error: Curl_amiga_init failed\n")); goto fail; @@ -166,7 +169,7 @@ static CURLcode global_init(long flags, bool memoryfuncs) } if(Curl_async_global_init()) { - DEBUGF(curl_mfprintf(stderr, "Error: resolver_global_init failed\n")); + DEBUGF(curl_mfprintf(stderr, "Error: Curl_async_global_init failed\n")); goto fail; } @@ -175,7 +178,11 @@ static CURLcode global_init(long flags, bool memoryfuncs) goto fail; } +#ifdef _WIN32 easy_init_flags = flags; +#else + (void)flags; +#endif #ifdef DEBUGBUILD if(getenv("CURL_GLOBAL_INIT")) @@ -267,10 +274,12 @@ void curl_global_cleanup(void) } Curl_ssl_cleanup(); + Curl_vquic_cleanup(); Curl_async_global_cleanup(); #ifdef _WIN32 Curl_win32_cleanup(easy_init_flags); + easy_init_flags = 0; #endif Curl_amiga_cleanup(); @@ -281,8 +290,6 @@ void curl_global_cleanup(void) curlx_free(leakpointer); #endif - easy_init_flags = 0; - global_init_unlock(); } @@ -756,13 +763,9 @@ static CURLcode easy_perform(struct Curl_easy *data, bool events) /* if the handle has a connection still attached (it is/was a connect-only handle) then disconnect before performing */ if(data->conn) { - struct connectdata *c; - curl_socket_t s; + struct connectdata *conn = data->conn; Curl_detach_connection(data); - s = Curl_getconnectinfo(data, &c); - if((s != CURL_SOCKET_BAD) && c) { - Curl_conn_terminate(data, c, TRUE); - } + Curl_conn_close(data, conn, TRUE); DEBUGASSERT(!data->conn); } @@ -776,7 +779,7 @@ static CURLcode easy_perform(struct Curl_easy *data, bool events) return CURLE_OUT_OF_MEMORY; } - if(multi->in_callback) + if(Curl_api_multi_is_in_callback(multi)) return CURLE_RECURSIVE_API_CALL; /* Copy relevant easy options to the multi handle */ @@ -784,7 +787,7 @@ static CURLcode easy_perform(struct Curl_easy *data, bool events) curl_multi_setopt(multi, CURLMOPT_QUICK_EXIT, (long)data->set.quick_exit); data->multi_easy = NULL; /* pretend it does not exist */ - mresult = curl_multi_add_handle(multi, data); + mresult = Curl_multi_add_handle(multi, data); if(mresult) { curl_multi_cleanup(multi); if(mresult == CURLM_OUT_OF_MEMORY) @@ -803,7 +806,7 @@ static CURLcode easy_perform(struct Curl_easy *data, bool events) /* ignoring the return code is not nice, but atm we cannot really handle a failure here, room for future improvement! */ - (void)curl_multi_remove_handle(multi, data); + (void)Curl_multi_remove_handle(multi, data); sigpipe_restore(&sigpipe_ctx); @@ -817,7 +820,14 @@ static CURLcode easy_perform(struct Curl_easy *data, bool events) */ CURLcode curl_easy_perform(CURL *curl) { - return easy_perform(curl, FALSE); + struct Curl_eapi_guard guard = { 0 }; + CURLcode result; + + if(CURL_EAPI_ENTER(&guard, curl, easy_perform, &result)) { + result = easy_perform(curl, FALSE); + } + CURL_EAPI_LEAVE(&guard); + return result; } #ifdef DEBUGBUILD @@ -827,7 +837,14 @@ CURLcode curl_easy_perform(CURL *curl) */ CURLcode curl_easy_perform_ev(struct Curl_easy *easy) { - return easy_perform(easy, TRUE); + struct Curl_eapi_guard guard; + CURLcode result; + + if(CURL_EAPI_ENTER(&guard, easy, easy_perform_ev, &result)) { + result = easy_perform(easy, TRUE); + } + CURL_EAPI_LEAVE(&guard); + return result; } #endif @@ -837,13 +854,16 @@ CURLcode curl_easy_perform_ev(struct Curl_easy *easy) */ void curl_easy_cleanup(CURL *curl) { - struct Curl_easy *data = curl; - if(GOOD_EASY_HANDLE(data)) { + struct Curl_eapi_guard guard; + + if(CURL_EAPI_ENTER(&guard, curl, easy_cleanup, NULL)) { + struct Curl_easy *data = curl; struct Curl_sigpipe_ctx sigpipe_ctx; sigpipe_ignore(data, &sigpipe_ctx); Curl_close(&data); sigpipe_restore(&sigpipe_ctx); } + CURL_EAPI_LEAVE(&guard); } /* @@ -853,27 +873,28 @@ void curl_easy_cleanup(CURL *curl) #undef curl_easy_getinfo CURLcode curl_easy_getinfo(CURL *curl, CURLINFO info, ...) { - struct Curl_easy *data = curl; - va_list arg; - void *paramp; + struct Curl_eapi_guard guard; CURLcode result; - if(!GOOD_EASY_HANDLE(data)) - return CURLE_BAD_FUNCTION_ARGUMENT; + if(CURL_EAPI_ENTER(&guard, curl, easy_getinfo, &result)) { + struct Curl_easy *data = curl; + va_list arg; + void *paramp; - va_start(arg, info); - paramp = va_arg(arg, void *); + va_start(arg, info); + paramp = va_arg(arg, void *); - result = Curl_getinfo(data, info, paramp); + result = Curl_getinfo(data, info, paramp); - va_end(arg); + va_end(arg); + } + CURL_EAPI_LEAVE(&guard); return result; } static CURLcode dupset(struct Curl_easy *dst, struct Curl_easy *src) { CURLcode result = CURLE_OK; - enum dupstring i; enum dupblob j; /* Copy src->set into dst->set first, then deal with the strings @@ -882,17 +903,17 @@ static CURLcode dupset(struct Curl_easy *dst, struct Curl_easy *src) #if !defined(CURL_DISABLE_MIME) || !defined(CURL_DISABLE_FORM_API) dst->set.mimepostp = NULL; #endif + dst->set.str_copypostfields = NULL; + + Curl_u8_strset_init(&dst->set.strings); /* clear all dest string and blob pointers first, in case we error out mid-function */ - memset(dst->set.str, 0, STRING_LAST * sizeof(char *)); memset(dst->set.blobs, 0, BLOB_LAST * sizeof(struct curl_blob *)); /* duplicate all strings */ - for(i = (enum dupstring)0; i < STRING_LASTZEROTERMINATED; i++) { - result = Curl_setstropt(&dst->set.str[i], src->set.str[i]); - if(result) - return result; - } + result = Curl_u8_strset_copy(&dst->set.strings, &src->set.strings); + if(result) + return result; /* duplicate all blobs */ for(j = (enum dupblob)0; j < BLOB_LAST; j++) { @@ -902,18 +923,18 @@ static CURLcode dupset(struct Curl_easy *dst, struct Curl_easy *src) } /* duplicate memory areas pointed to */ - i = STRING_COPYPOSTFIELDS; - if(src->set.str[i]) { + if(src->set.str_copypostfields) { if(src->set.postfieldsize == -1) - dst->set.str[i] = curlx_strdup(src->set.str[i]); + dst->set.str_copypostfields = curlx_strdup(src->set.str_copypostfields); else /* postfieldsize is curl_off_t, curlx_memdup() takes a size_t ... */ - dst->set.str[i] = curlx_memdup(src->set.str[i], - curlx_sotouz(src->set.postfieldsize)); - if(!dst->set.str[i]) + dst->set.str_copypostfields = + curlx_memdup(src->set.str_copypostfields, + curlx_sotouz(src->set.postfieldsize)); + if(!dst->set.str_copypostfields) return CURLE_OUT_OF_MEMORY; /* point to the new copy */ - dst->set.postfields = dst->set.str[i]; + dst->set.postfields = dst->set.str_copypostfields; } #if !defined(CURL_DISABLE_MIME) || !defined(CURL_DISABLE_FORM_API) @@ -952,122 +973,125 @@ static void dupeasy_meta_freeentry(void *p) */ CURL *curl_easy_duphandle(CURL *curl) { - struct Curl_easy *data = curl; + struct Curl_eapi_guard guard; struct Curl_easy *outcurl = NULL; - if(!GOOD_EASY_HANDLE(data)) - goto fail; - outcurl = curlx_calloc(1, sizeof(struct Curl_easy)); - if(!outcurl) - goto fail; + if(CURL_EAPI_ENTER(&guard, curl, easy_duphandle, NULL)) { + struct Curl_easy *data = curl; + const char *str; - /* - * We setup a few buffers we need. We should probably make them - * get setup on-demand in the code, as that would probably decrease - * the likeliness of us forgetting to init a buffer here in the future. - */ - outcurl->set.buffer_size = data->set.buffer_size; + outcurl = curlx_calloc(1, sizeof(struct Curl_easy)); + if(!outcurl) + goto fail; - Curl_hash_init(&outcurl->meta_hash, 23, - Curl_hash_str, curlx_str_key_compare, dupeasy_meta_freeentry); - curlx_dyn_init(&outcurl->state.headerb, CURL_MAX_HTTP_HEADER); - Curl_bufref_init(&outcurl->state.url); - Curl_bufref_init(&outcurl->state.referer); - Curl_netrc_init(&outcurl->state.netrc); + /* + * We setup a few buffers we need. We should probably make them + * get setup on-demand in the code, as that would probably decrease + * the likeliness of us forgetting to init a buffer here in the future. + */ + outcurl->set.buffer_size = data->set.buffer_size; - /* the connection pool is setup on demand */ - outcurl->state.lastconnect_id = -1; - outcurl->state.recent_conn_id = -1; - outcurl->id = -1; - outcurl->mid = UINT32_MAX; - outcurl->master_mid = UINT32_MAX; + Curl_hash_init(&outcurl->meta_hash, 23, + Curl_hash_str, curlx_str_key_compare, + dupeasy_meta_freeentry); + curlx_dyn_init(&outcurl->state.headerb, CURL_MAX_HTTP_HEADER); + Curl_bufref_init(&outcurl->state.url); + Curl_bufref_init(&outcurl->state.referer); + Curl_netrc_init(&outcurl->state.netrc); + + /* the connection pool is setup on demand */ + outcurl->state.lastconnect_id = -1; + outcurl->id = -1; + outcurl->mid = UINT32_MAX; + outcurl->master_mid = UINT32_MAX; #ifndef CURL_DISABLE_HTTP - Curl_llist_init(&outcurl->state.httphdrs, NULL); + Curl_llist_init(&outcurl->state.httphdrs, NULL); #endif - Curl_initinfo(outcurl); + Curl_initinfo(outcurl); - /* copy all userdefined values */ - if(dupset(outcurl, data)) - goto fail; + /* copy all userdefined values */ + if(dupset(outcurl, data)) + goto fail; - outcurl->progress.hide = data->progress.hide; - outcurl->progress.callback = data->progress.callback; + outcurl->progress.hide = data->progress.hide; + outcurl->progress.callback = data->progress.callback; #ifndef CURL_DISABLE_COOKIES - outcurl->state.cookielist = NULL; - if(data->cookies && data->state.cookie_engine) { - /* If cookies are enabled in the parent handle, we enable them - in the clone as well! */ - outcurl->cookies = Curl_cookie_init(); - if(!outcurl->cookies) - goto fail; - outcurl->state.cookie_engine = TRUE; - } + outcurl->state.cookielist = NULL; + if(data->cookies && data->state.cookie_engine) { + /* If cookies are enabled in the parent handle, we enable them + in the clone as well! */ + outcurl->cookies = Curl_cookie_init(); + if(!outcurl->cookies) + goto fail; + outcurl->state.cookie_engine = TRUE; + } - if(data->state.cookielist) { - outcurl->state.cookielist = Curl_slist_duplicate(data->state.cookielist); - if(!outcurl->state.cookielist) - goto fail; - } + if(data->state.cookielist) { + outcurl->state.cookielist = Curl_slist_duplicate(data->state.cookielist); + if(!outcurl->state.cookielist) + goto fail; + } #endif - if(Curl_bufref_ptr(&data->state.url)) { - Curl_bufref_set(&outcurl->state.url, - Curl_bufref_dup(&data->state.url), 0, - curl_free); - if(!Curl_bufref_ptr(&outcurl->state.url)) - goto fail; - } - if(Curl_bufref_ptr(&data->state.referer)) { - Curl_bufref_set(&outcurl->state.referer, - Curl_bufref_dup(&data->state.referer), 0, - curl_free); - if(!Curl_bufref_ptr(&outcurl->state.referer)) - goto fail; - } + if(Curl_bufref_ptr(&data->state.url)) { + Curl_bufref_set(&outcurl->state.url, + Curl_bufref_dup(&data->state.url), 0, + curl_free); + if(!Curl_bufref_ptr(&outcurl->state.url)) + goto fail; + } + if(Curl_bufref_ptr(&data->state.referer)) { + Curl_bufref_set(&outcurl->state.referer, + Curl_bufref_dup(&data->state.referer), 0, + curl_free); + if(!Curl_bufref_ptr(&outcurl->state.referer)) + goto fail; + } - /* Reinitialize an SSL engine for the new handle - * note: the engine name has already been copied by dupset */ - if(outcurl->set.str[STRING_SSL_ENGINE]) { - if(Curl_ssl_set_engine(outcurl, outcurl->set.str[STRING_SSL_ENGINE])) - goto fail; - } + /* Reinitialize an SSL engine for the new handle + * note: the engine name has already been copied by dupset */ + str = CURL_EASY_STR(outcurl, STRING_SSL_ENGINE); + if(str) { + if(Curl_ssl_set_engine(outcurl, str)) + goto fail; + } #ifndef CURL_DISABLE_ALTSVC - if(data->asi) { - outcurl->asi = Curl_altsvc_init(); - if(!outcurl->asi) - goto fail; - if(outcurl->set.str[STRING_ALTSVC]) - (void)Curl_altsvc_load(outcurl->asi, outcurl->set.str[STRING_ALTSVC]); - } + if(data->asi) { + outcurl->asi = Curl_altsvc_init(); + if(!outcurl->asi) + goto fail; + str = CURL_EASY_STR(outcurl, STRING_ALTSVC); + if(str) + (void)Curl_altsvc_load(outcurl->asi, str); + } #endif #ifndef CURL_DISABLE_HSTS - if(data->hsts) { - outcurl->hsts = Curl_hsts_init(); - if(!outcurl->hsts) - goto fail; - if(outcurl->set.str[STRING_HSTS]) - (void)Curl_hsts_loadfile(outcurl, - outcurl->hsts, outcurl->set.str[STRING_HSTS]); - (void)Curl_hsts_loadcb(outcurl, outcurl->hsts); + if(data->hsts) { + outcurl->hsts = Curl_hsts_init(); + if(!outcurl->hsts) + goto fail; + str = CURL_EASY_STR(outcurl, STRING_HSTS); + if(str) + (void)Curl_hsts_loadfile(outcurl, outcurl->hsts, str); + (void)Curl_hsts_loadcb(outcurl, outcurl->hsts); - /* Copy entries learned at runtime. (E.g. Strict-Transport-Security - headers.) */ - if(Curl_hsts_copy(outcurl->hsts, data->hsts)) - goto fail; - } + /* Copy entries learned at runtime. (E.g. Strict-Transport-Security + headers.) */ + if(Curl_hsts_copy(outcurl->hsts, data->hsts)) + goto fail; + } #endif - outcurl->magic = CURLEASY_MAGIC_NUMBER; - - /* we reach this point and thus we are OK */ - + /* we reach this point and thus we are OK */ + outcurl->magic = CURLEASY_MAGIC_NUMBER; + } + CURL_EAPI_LEAVE(&guard); return outcurl; fail: - if(outcurl) { #ifndef CURL_DISABLE_COOKIES curlx_free(outcurl->cookies); @@ -1079,6 +1103,7 @@ fail: curlx_free(outcurl); } + CURL_EAPI_LEAVE(&guard); return NULL; } @@ -1088,38 +1113,40 @@ fail: */ void curl_easy_reset(CURL *curl) { - struct Curl_easy *data = curl; - if(!GOOD_EASY_HANDLE(data)) - return; + struct Curl_eapi_guard guard; - Curl_req_hard_reset(&data->req, data); - Curl_hash_clean(&data->meta_hash); + if(CURL_EAPI_ENTER(&guard, curl, easy_reset, NULL)) { + struct Curl_easy *data = curl; - /* clear all meta data */ - Curl_meta_reset(data); - /* zero out UserDefined data: */ - Curl_freeset(data); - memset(&data->set, 0, sizeof(struct UserDefined)); - Curl_init_userdefined(data); + data->state.lastconnect_id = -1; /* clear remembered connection id */ + Curl_req_hard_reset(&data->req, data); + Curl_hash_clean(&data->meta_hash); - /* zero out Progress data: */ - memset(&data->progress, 0, sizeof(struct Progress)); + /* clear all meta data */ + Curl_meta_reset(data); + /* zero out UserDefined data: */ + Curl_freeset(data); + memset(&data->set, 0, sizeof(struct UserDefined)); + Curl_init_userdefined(data); - /* zero out PureInfo data: */ - Curl_initinfo(data); + /* zero out Progress data: */ + memset(&data->progress, 0, sizeof(struct Progress)); - data->progress.hide = TRUE; - data->state.current_speed = -1; /* init to negative == impossible */ - data->state.recent_conn_id = -1; /* clear remembered connection id */ + /* zero out PureInfo data: */ + Curl_initinfo(data); - /* zero out authentication data: */ - memset(&data->state.authhost, 0, sizeof(struct auth)); - memset(&data->state.authproxy, 0, sizeof(struct auth)); + data->progress.hide = TRUE; + + /* zero out authentication data: */ + memset(&data->state.authhost, 0, sizeof(struct auth)); + memset(&data->state.authproxy, 0, sizeof(struct auth)); #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_DIGEST_AUTH) - Curl_http_auth_cleanup_digest(data); + Curl_http_auth_cleanup_digest(data); #endif - data->master_mid = UINT32_MAX; + data->master_mid = UINT32_MAX; + } + CURL_EAPI_LEAVE(&guard); } /* @@ -1137,62 +1164,60 @@ void curl_easy_reset(CURL *curl) */ CURLcode curl_easy_pause(CURL *curl, int action) { + struct Curl_eapi_guard guard; CURLcode result = CURLE_OK; - bool recursive = FALSE; - bool changed = FALSE; - struct Curl_easy *data = curl; - bool recv_paused, recv_paused_new; - bool send_paused, send_paused_new; - if(!GOOD_EASY_HANDLE(data) || !data->conn) - /* crazy input, do not continue */ - return CURLE_BAD_FUNCTION_ARGUMENT; + if(CURL_EAPI_ENTER(&guard, curl, easy_pause, &result)) { + bool changed = FALSE; + struct Curl_easy *data = curl; + bool recv_paused, recv_paused_new; + bool send_paused, send_paused_new; - if(Curl_is_in_callback(data)) - recursive = TRUE; + if(!data->conn) { + /* crazy input, do not continue */ + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; + } - recv_paused = Curl_xfer_recv_is_paused(data); - recv_paused_new = (action & CURLPAUSE_RECV); - send_paused = Curl_xfer_send_is_paused(data); - send_paused_new = (action & CURLPAUSE_SEND); + recv_paused = Curl_xfer_recv_is_paused(data); + recv_paused_new = (action & CURLPAUSE_RECV); + send_paused = Curl_xfer_send_is_paused(data); + send_paused_new = (action & CURLPAUSE_SEND); - if((send_paused != send_paused_new) || - (send_paused_new != Curl_creader_is_paused(data))) { - changed = TRUE; - result = Curl_1st_fatal( - result, Curl_xfer_pause_send(data, send_paused_new)); - } + if((send_paused != send_paused_new) || + (send_paused_new != Curl_creader_is_paused(data))) { + changed = TRUE; + result = Curl_1st_fatal( + result, Curl_xfer_pause_send(data, send_paused_new)); + } - if(recv_paused != recv_paused_new) { - changed = TRUE; - result = Curl_1st_fatal( - result, Curl_xfer_pause_recv(data, recv_paused_new)); - } + if(recv_paused != recv_paused_new) { + changed = TRUE; + result = Curl_1st_fatal( + result, Curl_xfer_pause_recv(data, recv_paused_new)); + } - /* If not completely pausing both directions now, run again in any case. */ - if(!Curl_xfer_is_blocked(data)) { - /* reset the too-slow time keeper */ - data->state.keeps_speed.tv_sec = 0; - if(data->multi) { - Curl_multi_mark_dirty(data); /* make it run */ - /* On changes, tell application to update its timers. */ - if(changed) { - if(Curl_update_timer(data->multi) && !result) - result = CURLE_ABORTED_BY_CALLBACK; + /* If not completely pausing both directions, run again in any case. */ + if(!Curl_xfer_is_blocked(data)) { + /* reset the too-slow time keeper */ + data->state.keeps_speed.tv_sec = 0; + if(data->multi) { + Curl_multi_mark_dirty(data); /* make it run */ + /* On changes, tell application to update its timers. */ + if(changed) { + if(Curl_update_timer(data->multi) && !result) + result = CURLE_ABORTED_BY_CALLBACK; + } } } + + if(!result && changed && !data->state.done && data->multi) + /* pause/unpausing may result in multi event changes */ + if(Curl_multi_ev_assess_xfer(data->multi, data) && !result) + result = CURLE_ABORTED_BY_CALLBACK; } - - if(!result && changed && !data->state.done && data->multi) - /* pause/unpausing may result in multi event changes */ - if(Curl_multi_ev_assess_xfer(data->multi, data) && !result) - result = CURLE_ABORTED_BY_CALLBACK; - - if(recursive) - /* this might have called a callback recursively which might have set this - to false again on exit */ - Curl_set_in_callback(data, TRUE); - +out: + CURL_EAPI_LEAVE(&guard); return result; } @@ -1213,7 +1238,8 @@ static CURLcode easy_connection(struct Curl_easy *data, sfd = Curl_getconnectinfo(data, connp); if(sfd == CURL_SOCKET_BAD) { - failf(data, "Failed to get recent socket"); + failf(data, "Failed to get last socket used for connection #%" FMT_OFF_T, + data->state.lastconnect_id); return CURLE_UNSUPPORTED_PROTOCOL; } @@ -1225,16 +1251,11 @@ static CURLcode easy_connection(struct Curl_easy *data, * curl_easy_perform() with CURLOPT_CONNECT_ONLY option. * Returns CURLE_OK on success, error code on error. */ -CURLcode curl_easy_recv(CURL *curl, void *buffer, size_t buflen, size_t *n) +CURLcode Curl_easy_recv(struct Curl_easy *data, + void *buffer, size_t buflen, size_t *n) { CURLcode result; struct connectdata *c; - struct Curl_easy *data = curl; - - if(!GOOD_EASY_HANDLE(data)) - return CURLE_BAD_FUNCTION_ARGUMENT; - if(Curl_is_in_callback(data)) - return CURLE_RECURSIVE_API_CALL; result = easy_connection(data, &c); if(result) @@ -1243,12 +1264,24 @@ CURLcode curl_easy_recv(CURL *curl, void *buffer, size_t buflen, size_t *n) if(!data->conn) /* on first invoke, the transfer has been detached from the connection and needs to be reattached */ - Curl_attach_connection(data, c); + Curl_attach_connection(data, c, TRUE); *n = 0; return Curl_conn_recv(data, FIRSTSOCKET, buffer, buflen, n); } +CURLcode curl_easy_recv(CURL *curl, void *buffer, size_t buflen, size_t *n) +{ + struct Curl_eapi_guard guard; + CURLcode result; + + if(CURL_EAPI_ENTER(&guard, curl, easy_recv, &result)) { + result = Curl_easy_recv(curl, buffer, buflen, n); + } + CURL_EAPI_LEAVE(&guard); + return result; +} + #ifndef CURL_DISABLE_WEBSOCKETS CURLcode Curl_connect_only_attach(struct Curl_easy *data) { @@ -1262,7 +1295,7 @@ CURLcode Curl_connect_only_attach(struct Curl_easy *data) if(!data->conn) /* on first invoke, the transfer has been detached from the connection and needs to be reattached */ - Curl_attach_connection(data, c); + Curl_attach_connection(data, c, TRUE); return CURLE_OK; } @@ -1288,7 +1321,7 @@ CURLcode Curl_senddata(struct Curl_easy *data, const void *buffer, if(!data->conn) /* on first invoke, the transfer has been detached from the connection and needs to be reattached */ - Curl_attach_connection(data, c); + Curl_attach_connection(data, c, TRUE); sigpipe_ignore(data, &sigpipe_ctx); result = Curl_conn_send(data, FIRSTSOCKET, buffer, buflen, FALSE, n); @@ -1306,16 +1339,17 @@ CURLcode Curl_senddata(struct Curl_easy *data, const void *buffer, CURLcode curl_easy_send(CURL *curl, const void *buffer, size_t buflen, size_t *n) { - size_t written = 0; + struct Curl_eapi_guard guard; CURLcode result; - struct Curl_easy *data = curl; - if(!GOOD_EASY_HANDLE(data)) - return CURLE_BAD_FUNCTION_ARGUMENT; - if(Curl_is_in_callback(data)) - return CURLE_RECURSIVE_API_CALL; - result = Curl_senddata(data, buffer, buflen, &written); - *n = written; + if(CURL_EAPI_ENTER(&guard, curl, easy_send, &result)) { + struct Curl_easy *data = curl; + size_t written = 0; + + result = Curl_senddata(data, buffer, buflen, &written); + *n = written; + } + CURL_EAPI_LEAVE(&guard); return result; } @@ -1324,16 +1358,15 @@ CURLcode curl_easy_send(CURL *curl, const void *buffer, size_t buflen, */ CURLcode curl_easy_upkeep(CURL *curl) { - struct Curl_easy *data = curl; - /* Verify that we got an easy handle we can work with. */ - if(!GOOD_EASY_HANDLE(data)) - return CURLE_BAD_FUNCTION_ARGUMENT; + struct Curl_eapi_guard guard; + CURLcode result; - if(Curl_is_in_callback(data)) - return CURLE_RECURSIVE_API_CALL; - - /* Use the common function to keep connections alive. */ - return Curl_cpool_upkeep(data); + if(CURL_EAPI_ENTER(&guard, curl, easy_upkeep, &result)) { + /* Use the common function to keep connections alive. */ + result = Curl_cpool_upkeep((struct Curl_easy *)curl); + } + CURL_EAPI_LEAVE(&guard); + return result; } CURLcode curl_easy_ssls_import(CURL *curl, const char *session_key, @@ -1341,13 +1374,15 @@ CURLcode curl_easy_ssls_import(CURL *curl, const char *session_key, const unsigned char *sdata, size_t sdata_len) { #if defined(USE_SSL) && defined(USE_SSLS_EXPORT) - struct Curl_easy *data = curl; - if(!GOOD_EASY_HANDLE(data)) - return CURLE_BAD_FUNCTION_ARGUMENT; - if(Curl_is_in_callback(data) || Curl_ssl_scache_is_locked(data)) - return CURLE_RECURSIVE_API_CALL; - return Curl_ssl_session_import(data, session_key, - shmac, shmac_len, sdata, sdata_len); + struct Curl_eapi_guard guard; + CURLcode result; + + if(CURL_EAPI_ENTER(&guard, curl, easy_ssls_import, &result)) { + result = Curl_ssl_session_import((struct Curl_easy *)curl, session_key, + shmac, shmac_len, sdata, sdata_len); + } + CURL_EAPI_LEAVE(&guard); + return result; #else (void)curl; (void)session_key; @@ -1364,12 +1399,15 @@ CURLcode curl_easy_ssls_export(CURL *curl, void *userptr) { #if defined(USE_SSL) && defined(USE_SSLS_EXPORT) - struct Curl_easy *data = curl; - if(!GOOD_EASY_HANDLE(data)) - return CURLE_BAD_FUNCTION_ARGUMENT; - if(Curl_is_in_callback(data) || Curl_ssl_scache_is_locked(data)) - return CURLE_RECURSIVE_API_CALL; - return Curl_ssl_session_export(data, export_fn, userptr); + struct Curl_eapi_guard guard; + CURLcode result; + + if(CURL_EAPI_ENTER(&guard, curl, easy_ssls_export, &result)) { + result = Curl_ssl_session_export((struct Curl_easy *)curl, + export_fn, userptr); + } + CURL_EAPI_LEAVE(&guard); + return result; #else (void)curl; (void)export_fn; diff --git a/Utilities/cmcurl/lib/easy_lock.h b/Utilities/cmcurl/lib/easy_lock.h index b8f916ff65..9f21448cbe 100644 --- a/Utilities/cmcurl/lib/easy_lock.h +++ b/Utilities/cmcurl/lib/easy_lock.h @@ -71,6 +71,7 @@ static CURL_INLINE void curl_simple_lock_lock(curl_simple_lock *lock) #ifdef HAVE_BUILTIN_IA32_PAUSE __builtin_ia32_pause(); #elif defined(__aarch64__) + /* NOLINTNEXTLINE(portability-no-assembler) */ __asm__ volatile("yield" ::: "memory"); #elif defined(HAVE_SCHED_YIELD) sched_yield(); diff --git a/Utilities/cmcurl/lib/easyoptions.c b/Utilities/cmcurl/lib/easyoptions.c index 4c0b4f0ce4..1643ec3256 100644 --- a/Utilities/cmcurl/lib/easyoptions.c +++ b/Utilities/cmcurl/lib/easyoptions.c @@ -140,6 +140,10 @@ const struct curl_easyoption Curl_easyopts[] = { { "HTTPHEADER", CURLOPT_HTTPHEADER, CURLOT_SLIST, 0 }, { "HTTPPOST", CURLOPT_HTTPPOST, CURLOT_OBJECT, 0 }, { "HTTPPROXYTUNNEL", CURLOPT_HTTPPROXYTUNNEL, CURLOT_LONG, 0 }, + { "HTTPSIG_ALGORITHM", CURLOPT_HTTPSIG_ALGORITHM, CURLOT_VALUES, 0 }, + { "HTTPSIG_HEADERS", CURLOPT_HTTPSIG_HEADERS, CURLOT_STRING, 0 }, + { "HTTPSIG_KEY", CURLOPT_HTTPSIG_KEY, CURLOT_STRING, 0 }, + { "HTTPSIG_KEYID", CURLOPT_HTTPSIG_KEYID, CURLOT_STRING, 0 }, { "HTTP_CONTENT_DECODING", CURLOPT_HTTP_CONTENT_DECODING, CURLOT_LONG, 0 }, { "HTTP_TRANSFER_DECODING", CURLOPT_HTTP_TRANSFER_DECODING, CURLOT_LONG, 0 }, @@ -385,6 +389,6 @@ const struct curl_easyoption Curl_easyopts[] = { */ int Curl_easyopts_check(void) { - return (CURLOPT_LASTENTRY % 10000) != (328 + 1); + return (CURLOPT_LASTENTRY % 10000) != (332 + 1); } #endif diff --git a/Utilities/cmcurl/lib/escape.c b/Utilities/cmcurl/lib/escape.c index 4aff583de1..a1a714f8f5 100644 --- a/Utilities/cmcurl/lib/escape.c +++ b/Utilities/cmcurl/lib/escape.c @@ -196,7 +196,7 @@ void curl_free(void *p) * Curl_hexencode() * * Converts binary input to lowercase hex-encoded ASCII output. - * Null-terminated. + * null-terminated. */ void Curl_hexencode(const unsigned char *src, size_t len, /* input length */ unsigned char *out, size_t olen) /* output buffer size */ diff --git a/Utilities/cmcurl/lib/fake_addrinfo.c b/Utilities/cmcurl/lib/fake_addrinfo.c index 6b04b5d2f5..6857d63485 100644 --- a/Utilities/cmcurl/lib/fake_addrinfo.c +++ b/Utilities/cmcurl/lib/fake_addrinfo.c @@ -42,6 +42,7 @@ void r_freeaddrinfo(struct addrinfo *cahead) struct context { struct ares_addrinfo *addr; + int status; }; static void async_addrinfo_cb(void *userp, int status, int timeouts, @@ -49,6 +50,7 @@ static void async_addrinfo_cb(void *userp, int status, int timeouts, { struct context *ctx = (struct context *)userp; (void)timeouts; + ctx->status = status; if(ARES_SUCCESS == status) { ctx->addr = addr; } @@ -127,14 +129,10 @@ static struct addrinfo *mk_getaddrinfo(const struct ares_addrinfo *aihead) return cafirst; } -/* - RETURN VALUE - - getaddrinfo() returns 0 if it succeeds, or one of the following nonzero - error codes: - - ... -*/ +/* RETURN VALUE + getaddrinfo() returns 0 if it succeeds, or one of the following nonzero + error codes: + ... */ int r_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, @@ -190,8 +188,10 @@ int r_getaddrinfo(const char *node, /* free the old */ ares_freeaddrinfo(ctx.addr); } + else if((ctx.status == ARES_ENOTFOUND) || (ctx.status == ARES_ENODATA)) + rc = EAI_NONAME; /* no such name */ else - rc = EAI_NONAME; /* got nothing */ + rc = EAI_AGAIN; /* failed without an authoritative answer */ /* Cleanup */ ares_destroy(channel); diff --git a/Utilities/cmcurl/lib/fake_addrinfo.h b/Utilities/cmcurl/lib/fake_addrinfo.h index 07d5b6da8c..69579da2e5 100644 --- a/Utilities/cmcurl/lib/fake_addrinfo.h +++ b/Utilities/cmcurl/lib/fake_addrinfo.h @@ -43,7 +43,7 @@ # include #endif -void r_freeaddrinfo(struct addrinfo *res); +void r_freeaddrinfo(struct addrinfo *cahead); int r_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, diff --git a/Utilities/cmcurl/lib/file.c b/Utilities/cmcurl/lib/file.c index fff8feeb92..d3553b7df2 100644 --- a/Utilities/cmcurl/lib/file.c +++ b/Utilities/cmcurl/lib/file.c @@ -47,10 +47,6 @@ #include #endif -#ifdef HAVE_SYS_TYPES_H -#include -#endif - #ifdef HAVE_DIRENT_H #include #endif @@ -80,12 +76,14 @@ struct FILEPROTO { char *freepath; /* pointer to the allocated block we must free, this might differ from the 'path' pointer */ int fd; /* open file descriptor to read from! */ + bool is_dir; }; static void file_cleanup(struct FILEPROTO *file) { curlx_safefree(file->freepath); file->path = NULL; + file->is_dir = FALSE; if(file->fd != -1) { curlx_close(file->fd); file->fd = -1; @@ -130,6 +128,38 @@ static CURLcode file_done(struct Curl_easy *data, return CURLE_OK; } +static int file_stat(const char *path, curlx_struct_stat *statbuf) +{ +#ifdef _WIN32 + int result = curlx_stat(path, statbuf); + + if(result) { + size_t pathlen = strlen(path); + + /* MSVCRT's narrow stat() rejects trailing directory separators. */ + if((pathlen > 3) && + ((path[pathlen - 1] == '\\') || (path[pathlen - 1] == '/'))) { + char *trimmed = curlx_strdup(path); + + if(trimmed) { + do { + trimmed[--pathlen] = '\0'; + } while((pathlen > 3) && + ((trimmed[pathlen - 1] == '\\') || + (trimmed[pathlen - 1] == '/'))); + + result = curlx_stat(trimmed, statbuf); + curlx_free(trimmed); + } + } + } + + return result; +#else + return curlx_stat(path, statbuf); +#endif +} + /* * file_connect() gets called from Curl_protocol_connect() to allow us to * do protocol-specific actions at connect-time. We emulate a @@ -139,6 +169,9 @@ static CURLcode file_connect(struct Curl_easy *data, bool *done) { char *real_path; struct FILEPROTO *file = Curl_meta_get(data, CURL_META_FILE_EASY); +#ifdef _WIN32 + curlx_struct_stat statbuf; +#endif int fd; #ifdef DOS_FILESYSTEM size_t i; @@ -177,8 +210,7 @@ static CURLcode file_connect(struct Curl_easy *data, bool *done) On other platforms, we need the slash to indicate an absolute pathname. On Windows, absolute paths start - with a drive letter. - */ + with a drive letter. */ actual_path = real_path; if((actual_path[0] == '/') && actual_path[1] && @@ -238,7 +270,13 @@ static CURLcode file_connect(struct Curl_easy *data, bool *done) file->freepath = real_path; /* free this when done */ file->fd = fd; - if(!data->state.upload && (fd == -1)) { +#ifdef _WIN32 + if(!data->state.upload && (fd == -1) && + !file_stat(file->path, &statbuf) && S_ISDIR(statbuf.st_mode)) + file->is_dir = TRUE; +#endif + + if(!data->state.upload && (fd == -1) && !file->is_dir) { failf(data, "Could not open file %s", data->state.up.path); file_done(data, CURLE_FILE_COULDNT_READ_FILE, FALSE); return CURLE_FILE_COULDNT_READ_FILE; @@ -375,6 +413,58 @@ out: return result; } +#if defined(_WIN32) && !defined(CURL_WINDOWS_UWP) +static CURLcode win32_file_list(struct Curl_easy *data, const char *path) +{ + WIN32_FIND_DATA entry; + HANDLE handle; + char *pattern; + size_t pathlen = strlen(path); + CURLcode result = CURLE_OK; + DWORD error; + + pattern = curl_maprintf("%s%s*", path, + pathlen && + (path[pathlen - 1] == '\\' || + path[pathlen - 1] == '/') ? "" : "\\"); + if(!pattern) + return CURLE_OUT_OF_MEMORY; + + handle = curlx_FindFirstFile(pattern, &entry); + curlx_free(pattern); + if(handle == INVALID_HANDLE_VALUE) + return CURLE_READ_ERROR; + + do { + if(entry.cFileName[0] != TEXT('.')) { + char *name = curlx_convert_tchar_to_UTF8(entry.cFileName); + + if(!name) { + result = CURLE_OUT_OF_MEMORY; + break; + } + + result = Curl_client_write(data, CLIENTWRITE_BODY, + name, strlen(name)); + curlx_free(name); + if(result) + break; + + result = Curl_client_write(data, CLIENTWRITE_BODY, "\n", 1); + if(result) + break; + } + } while(FindNextFile(handle, &entry)); + + error = GetLastError(); + if(!result && error != ERROR_NO_MORE_FILES) + result = CURLE_READ_ERROR; + + FindClose(handle); + return result; +} +#endif + /* * file_do() is the protocol-specific function for the do-phase, separated * from the connect-phase above. Other protocols merely setup the transfer in @@ -388,8 +478,7 @@ static CURLcode file_do(struct Curl_easy *data, bool *done) /* This implementation ignores the hostname in conformance with RFC 1738. Only local files (reachable via the standard file system) are supported. This means that files on remotely mounted directories - (via NFS, Samba, NT sharing) can be accessed through a file:// URL - */ + (via NFS, Samba, NT sharing) can be accessed through a file:// URL */ struct FILEPROTO *file = Curl_meta_get(data, CURL_META_FILE_EASY); CURLcode result = CURLE_OK; curlx_struct_stat statbuf; @@ -411,8 +500,10 @@ static CURLcode file_do(struct Curl_easy *data, bool *done) fd = file->fd; /* VMS: This only works reliable for STREAMLF files */ - if(curlx_fstat(fd, &statbuf) != -1) { - if(!S_ISDIR(statbuf.st_mode)) + if((file->is_dir ? file_stat(file->path, &statbuf) : + curlx_fstat(fd, &statbuf)) != -1) { + file->is_dir = (fd == -1) || S_ISDIR(statbuf.st_mode); + if(!file->is_dir) expected_size = statbuf.st_size; /* and store the modification time */ data->info.filetime = statbuf.st_mtime; @@ -439,7 +530,7 @@ static CURLcode file_do(struct Curl_easy *data, bool *done) return result; result = Curl_client_write(data, CLIENTWRITE_HEADER, - accept_ranges, sizeof(accept_ranges) - 1); + accept_ranges, CURL_CSTRLEN(accept_ranges)); if(result != CURLE_OK) return result; } @@ -516,7 +607,7 @@ static CURLcode file_do(struct Curl_easy *data, bool *done) Curl_pgrsSetDownloadSize(data, expected_size); if(data->state.resume_from) { - if(!S_ISDIR(statbuf.st_mode)) { + if(!file->is_dir) { if(data->state.resume_from != curl_lseek(fd, data->state.resume_from, SEEK_SET)) return CURLE_BAD_DOWNLOAD_RESUME; @@ -530,7 +621,7 @@ static CURLcode file_do(struct Curl_easy *data, bool *done) if(result) goto out; - if(!S_ISDIR(statbuf.st_mode)) { + if(!file->is_dir) { while(!result) { ssize_t nread; /* Do not fill a whole buffer if we want less than all data */ @@ -564,7 +655,11 @@ static CURLcode file_do(struct Curl_easy *data, bool *done) } } else { -#ifdef HAVE_OPENDIR +#if defined(_WIN32) && !defined(CURL_WINDOWS_UWP) + result = win32_file_list(data, file->path); + if(result) + goto out; +#elif defined(HAVE_OPENDIR) DIR *dir = opendir(file->path); struct dirent *entry; diff --git a/Utilities/cmcurl/lib/formdata.c b/Utilities/cmcurl/lib/formdata.c index 681902db71..0c81d8c781 100644 --- a/Utilities/cmcurl/lib/formdata.c +++ b/Utilities/cmcurl/lib/formdata.c @@ -46,7 +46,7 @@ struct Curl_easy; /*************************************************************************** * - * AddHttpPost() + * httppost_add() * * Adds an HttpPost structure to the list, if parent_post is given becomes * a subpost of parent_post instead of a direct list element. @@ -54,10 +54,10 @@ struct Curl_easy; * Returns newly allocated HttpPost on success and NULL if malloc failed. * ***************************************************************************/ -static struct curl_httppost *AddHttpPost(struct FormInfo *src, - struct curl_httppost *parent_post, - struct curl_httppost **httppost, - struct curl_httppost **last_post) +static struct curl_httppost *httppost_add(struct FormInfo *src, + struct curl_httppost *parent_post, + struct curl_httppost **httppost, + struct curl_httppost **last_post) { struct curl_httppost *post; size_t namelength = src->namelength; @@ -103,7 +103,7 @@ static struct curl_httppost *AddHttpPost(struct FormInfo *src, } /* Allocate and initialize a new FormInfo structure. */ -static struct FormInfo *NewFormInfo(void) +static struct FormInfo *forminfo_new(void) { struct FormInfo *form_info = curlx_calloc(1, sizeof(struct FormInfo)); @@ -118,7 +118,7 @@ static struct FormInfo *NewFormInfo(void) } /* Replace the target field data by a dynamic copy of it. */ -static CURLcode FormInfoCopyField(struct bufref *field, size_t len) +static CURLcode forminfo_copyfield(struct bufref *field, size_t len) { const char *value = Curl_bufref_ptr(field); CURLcode result = CURLE_OK; @@ -134,12 +134,12 @@ static CURLcode FormInfoCopyField(struct bufref *field, size_t len) /*************************************************************************** * - * AddFormInfo() + * forminfo_add() * * Adds a FormInfo structure to the list presented by parent. * ***************************************************************************/ -static void AddFormInfo(struct FormInfo *form_info, struct FormInfo *parent) +static void forminfo_add(struct FormInfo *form_info, struct FormInfo *parent) { form_info->flags |= HTTPPOST_FILENAME; @@ -164,7 +164,7 @@ static void free_formlist(struct FormInfo *ptr) /*************************************************************************** * - * FormAdd() + * formadd_check() * * Stores a formpost parameter and builds the appropriate linked list. * @@ -186,7 +186,7 @@ static void free_formlist(struct FormInfo *ptr) * curl_formadd(&post, &last, CURLFORM_COPYNAME, "name", * CURLFORM_PTRCONTENTS, ptr, CURLFORM_CONTENTSLENGTH, 10L, * CURLFORM_END); - * (if CURLFORM_CONTENTSLENGTH is missing strlen () is used) + * (if CURLFORM_CONTENTSLENGTH is missing strlen() is used) * * storing a filename (CONTENTTYPE is optional!): * curl_formadd(&post, &last, CURLFORM_COPYNAME, "name", @@ -212,9 +212,9 @@ static void free_formlist(struct FormInfo *ptr) * ***************************************************************************/ -static CURLFORMcode FormAddCheck(struct FormInfo *first_form, - struct curl_httppost **httppost, - struct curl_httppost **last_post) +static CURLFORMcode formadd_check(struct FormInfo *first_form, + struct curl_httppost **httppost, + struct curl_httppost **last_post) { const char *prevtype = NULL; struct FormInfo *form = NULL; @@ -263,16 +263,16 @@ static CURLFORMcode FormAddCheck(struct FormInfo *first_form, if(!(form->flags & HTTPPOST_PTRNAME)) { /* Note that there is small risk that form->name is NULL here if the app passed in a bad combo, so we check for that. */ - if(FormInfoCopyField(&form->name, form->namelength)) + if(forminfo_copyfield(&form->name, form->namelength)) return CURL_FORMADD_MEMORY; } if(!(form->flags & (HTTPPOST_FILENAME | HTTPPOST_READFILE | HTTPPOST_PTRCONTENTS | HTTPPOST_PTRBUFFER | HTTPPOST_CALLBACK))) { - if(FormInfoCopyField(&form->value, (size_t)form->contentslength)) + if(forminfo_copyfield(&form->value, (size_t)form->contentslength)) return CURL_FORMADD_MEMORY; } - post = AddHttpPost(form, post, httppost, last_post); + post = httppost_add(form, post, httppost, last_post); if(!post) return CURL_FORMADD_MEMORY; @@ -297,7 +297,7 @@ static void free_chain(struct curl_httppost *c) } } -static CURLFORMcode FormAdd(struct curl_httppost **httppost, +static CURLFORMcode formadd(struct curl_httppost **httppost, struct curl_httppost **last_post, va_list params) { struct FormInfo *first_form, *curr, *form = NULL; @@ -318,7 +318,7 @@ static CURLFORMcode FormAdd(struct curl_httppost **httppost, /* * We need to allocate the first struct to fill in. */ - first_form = NewFormInfo(); + first_form = forminfo_new(); if(!first_form) return CURL_FORMADD_MEMORY; @@ -436,14 +436,14 @@ static CURLFORMcode FormAdd(struct curl_httppost **httppost, if(Curl_bufref_ptr(&curr->value)) { if(curr->flags & HTTPPOST_FILENAME) { if(avalue) { - form = NewFormInfo(); + form = forminfo_new(); if(!form || Curl_bufref_memdup0(&form->value, avalue, strlen(avalue))) { curlx_free(form); retval = CURL_FORMADD_MEMORY; } else { - AddFormInfo(form, curr); + forminfo_add(form, curr); curr = form; form = NULL; } @@ -512,14 +512,14 @@ static CURLFORMcode FormAdd(struct curl_httppost **httppost, if(Curl_bufref_ptr(&curr->contenttype)) { if(curr->flags & HTTPPOST_FILENAME) { if(avalue) { - form = NewFormInfo(); + form = forminfo_new(); if(!form || Curl_bufref_memdup0(&form->contenttype, avalue, strlen(avalue))) { curlx_free(form); retval = CURL_FORMADD_MEMORY; } else { - AddFormInfo(form, curr); + forminfo_add(form, curr); curr = form; form = NULL; } @@ -566,7 +566,7 @@ static CURLFORMcode FormAdd(struct curl_httppost **httppost, } if(!retval) - retval = FormAddCheck(first_form, &newchain, &lastnode); + retval = formadd_check(first_form, &newchain, &lastnode); if(retval) /* On error, free allocated fields for all nodes of the FormInfo linked @@ -611,7 +611,7 @@ CURLFORMcode curl_formadd(struct curl_httppost **httppost, va_list arg; CURLFORMcode form; va_start(arg, last_post); - form = FormAdd(httppost, last_post, arg); + form = formadd(httppost, last_post, arg); va_end(arg); return form; } diff --git a/Utilities/cmcurl/lib/ftp.c b/Utilities/cmcurl/lib/ftp.c index 8a8b6f7042..ab7c41857a 100644 --- a/Utilities/cmcurl/lib/ftp.c +++ b/Utilities/cmcurl/lib/ftp.c @@ -44,7 +44,6 @@ #include "curl_addrinfo.h" #include "curl_trc.h" #include "if2ip.h" -#include "hostip.h" #include "progress.h" #include "transfer.h" #include "escape.h" @@ -65,6 +64,7 @@ #include "multiif.h" #include "url.h" #include "http_proxy.h" +#include "vdns/hostip.h" #include "curlx/strdup.h" #include "curlx/strerr.h" #include "curlx/strparse.h" @@ -232,12 +232,10 @@ static CURLcode ftp_parse_url_path(struct Curl_easy *data, if((pathLen > 0) && (rawPath[pathLen - 1] != '/')) fileName = rawPath; /* this is a full file path */ - /* - else: ftpc->file is not used anywhere other than for operations on - a file. In other words, never for directory operations, - so we can safely leave filename as NULL here and use it as a - argument in dir/file decisions. - */ + /* else: ftpc->file is not used anywhere other than for operations on + a file. In other words, never for directory operations, + so we can safely leave filename as NULL here and use it as a + argument in dir/file decisions. */ break; case FTPFILE_SINGLECWD: @@ -443,8 +441,10 @@ static CURLcode ftp_cw_lc_write(struct Curl_easy *data, static const struct Curl_cwtype ftp_cw_lc = { "ftp-lineconv", NULL, + 0, Curl_cwriter_def_init, ftp_cw_lc_write, + Curl_cwriter_def_flush, Curl_cwriter_def_close, sizeof(struct ftp_cw_lc_ctx) }; @@ -590,7 +590,7 @@ static bool ftp_endofresp(struct Curl_easy *data, struct connectdata *conn, static CURLcode ftp_readresp(struct Curl_easy *data, struct ftp_conn *ftpc, - int sockindex, + int8_t sockindex, struct pingpong *pp, int *ftpcodep, /* return the ftp-code if done */ size_t *size) /* size of the response */ @@ -819,8 +819,7 @@ static const char *pathpiece(struct ftp_conn *ftpc, int num) ftp_state_cwd() sends the range of CWD commands to the server to change to the correct directory. It may also need to send MKD commands to create - missing ones, if that option is enabled. -*/ + missing ones, if that option is enabled. */ static CURLcode ftp_state_cwd(struct Curl_easy *data, struct ftp_conn *ftpc, struct FTP *ftp) @@ -929,11 +928,11 @@ static CURLcode ftp_port_parse_string(struct Curl_easy *data, #ifdef USE_IPV6 struct sockaddr_in6 * const sa6 = (void *)ss; #endif - /* either ipv6 or (ipv4|domain|interface):port(-range) */ + /* either IPv6 or (ipv4|domain|interface):port(-range) */ addrlen = ip_end - string_ftpport; #ifdef USE_IPV6 if(curlx_inet_pton(AF_INET6, string_ftpport, &sa6->sin6_addr) == 1) { - /* ipv6 */ + /* IPv6 */ addrlen = strlen(string_ftpport); ip_end = NULL; /* this got no port ! */ } @@ -1023,7 +1022,7 @@ static CURLcode ftp_port_default_host(struct Curl_easy *data, struct sockaddr_in6 * const sa6 = (void *)sa; #endif char buffer[STRERROR_LEN]; - const char *r; + CURLcode result; *sslenp = sizeof(*ss); if(getsockname(conn->sock[FIRSTSOCKET], sa, sslenp)) { @@ -1034,14 +1033,14 @@ static CURLcode ftp_port_default_host(struct Curl_easy *data, switch(sa->sa_family) { #ifdef USE_IPV6 case AF_INET6: - r = curlx_inet_ntop(sa->sa_family, &sa6->sin6_addr, hbuf, hbuflen); + result = curlx_inet_ntop(sa->sa_family, &sa6->sin6_addr, hbuf, hbuflen); break; #endif default: - r = curlx_inet_ntop(sa->sa_family, &sa4->sin_addr, hbuf, hbuflen); + result = curlx_inet_ntop(sa->sa_family, &sa4->sin_addr, hbuf, hbuflen); break; } - if(!r) + if(result) return CURLE_FTP_PORT_FAILED; *hostp = hbuf; @@ -1334,7 +1333,7 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, curl_socklen_t sslen; char hbuf[NI_MAXHOST]; const char *host = NULL; - const char *string_ftpport = data->set.str[STRING_FTPPORT]; + const char *string_ftpport = CURL_EASY_STR(data, STRING_FTPPORT); struct Curl_dns_entry *dns_entry = NULL; const struct Curl_addrinfo *res = NULL; const struct Curl_addrinfo *ai = NULL; @@ -1399,7 +1398,6 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, conn, SECONDARYSOCKET); } conn->bits.do_more = FALSE; - Curl_pgrsTime(data, TIMER_STARTACCEPT); Curl_expire(data, (data->set.accepttimeout > 0) ? data->set.accepttimeout: DEFAULT_ACCEPT_TIMEOUT, EXPIRE_FTP_ACCEPT); @@ -1414,19 +1412,17 @@ static CURLcode ftp_state_use_pasv(struct Curl_easy *data, struct connectdata *conn) { CURLcode result = CURLE_OK; - /* - Here's the executive summary on what to do: + /* Here's the executive summary on what to do: - PASV is RFC959, expect: - 227 Entering Passive Mode (a1,a2,a3,a4,p1,p2) + PASV is RFC959, expect: + 227 Entering Passive Mode (a1,a2,a3,a4,p1,p2) - LPSV is RFC1639, expect: - 228 Entering Long Passive Mode (4,4,a1,a2,a3,a4,2,p1,p2) + LPSV is RFC1639, expect: + 228 Entering Long Passive Mode (4,4,a1,a2,a3,a4,2,p1,p2) - EPSV is RFC2428, expect: - 229 Entering Extended Passive Mode (|||port|) - - */ + EPSV is RFC2428, expect: + 229 Entering Extended Passive Mode (|||port|) + */ static const char mode[][5] = { "EPSV", "PASV" }; int modeoff; @@ -1481,8 +1477,8 @@ static CURLcode ftp_state_prepare_transfer(struct Curl_easy *data, to prepare the server for the upcoming PASV */ if(!ftpc->file) result = Curl_pp_sendf(data, &ftpc->pp, "PRET %s", - data->set.str[STRING_CUSTOMREQUEST] ? - data->set.str[STRING_CUSTOMREQUEST] : + CURL_EASY_STR(data, STRING_CUSTOMREQUEST) ? + CURL_EASY_STR(data, STRING_CUSTOMREQUEST) : (data->state.list_only ? "NLST" : "LIST")); else if(data->state.upload) result = Curl_pp_sendf(data, &ftpc->pp, "PRET STOR %s", ftpc->file); @@ -1549,14 +1545,12 @@ static CURLcode ftp_state_list(struct Curl_easy *data, way. It has turned out that the NLST list output is not the same on all servers either... */ - /* - if FTPFILE_NOCWD was specified, we should add the path + /* if FTPFILE_NOCWD was specified, we should add the path as argument for the LIST / NLST / or custom command. Whether the server will support this, is uncertain. The other ftp_filemethods will CWD into dir/dir/ first and - then do LIST (in that case: nothing to do here) - */ + then do LIST (in that case: nothing to do here) */ const char *lstArg = NULL; int lstArglen = 0; char *cmd; @@ -1578,8 +1572,8 @@ static CURLcode ftp_state_list(struct Curl_easy *data, } cmd = curl_maprintf("%s%s%.*s", - data->set.str[STRING_CUSTOMREQUEST] ? - data->set.str[STRING_CUSTOMREQUEST] : + CURL_EASY_STR(data, STRING_CUSTOMREQUEST) ? + CURL_EASY_STR(data, STRING_CUSTOMREQUEST) : (data->state.list_only ? "NLST" : "LIST"), lstArg ? " " : "", lstArglen, lstArg ? lstArg : ""); @@ -1713,10 +1707,11 @@ static CURLcode ftp_state_ul_setup(struct Curl_easy *data, /* Let's read off the proper amount of bytes from the input. */ if(data->set.seek_func) { - Curl_set_in_callback(data, TRUE); + struct Curl_mapi_guard guard; + CURL_CBAPI_START(&guard, data, easy_seek_func); seekerr = data->set.seek_func(data->set.seek_client, data->state.resume_from, SEEK_SET); - Curl_set_in_callback(data, FALSE); + CURL_CBAPI_END(&guard); } if(seekerr != CURL_SEEKFUNC_OK) { @@ -1946,8 +1941,7 @@ static CURLcode ftp_state_quote(struct Curl_easy *data, In addition: asking for the size for 'TYPE A' transfers is not constructive since servers do not report the converted size. - Thus, skip it. - */ + Thus, skip it. */ result = Curl_pp_sendf(data, &ftpc->pp, "RETR %s", ftpc->file); if(!result) ftp_state(data, ftpc, FTP_RETR); @@ -2334,7 +2328,7 @@ static CURLcode ftp_do_more(struct Curl_easy *data, domore *more) if(result) ; else if((data->state.list_only || !ftpc->file) && - !(data->set.prequote)) { + !data->set.prequote) { /* The specified path ends with a slash, and therefore we think this is a directory that is requested, use LIST. Before that, we also need to set ASCII transfer mode. */ @@ -2852,7 +2846,7 @@ static CURLcode ftp_state_get_resp(struct Curl_easy *data, if(data->req.size > data->req.maxdownload && data->req.maxdownload > 0) data->req.size = data->req.maxdownload; - else if((instate != FTP_LIST) && (data->state.prefer_ascii)) + else if((instate != FTP_LIST) && data->state.prefer_ascii) data->req.size = -1; /* for servers that understate ASCII mode file size */ @@ -2926,6 +2920,20 @@ static CURLcode ftp_state_loggedin(struct Curl_easy *data, return result; } +/* A value that becomes part of an FTP control command must not carry a + control byte: a CR or LF would end the command line and let a second + command be smuggled onto the control connection. */ +static bool ftp_has_ctrl(const char *string) +{ + const unsigned char *s = (const unsigned char *)string; + while(*s) { + if(*s < 0x20) + return TRUE; + s++; + } + return FALSE; +} + /* for USER and PASS responses */ static CURLcode ftp_state_user_resp(struct Curl_easy *data, struct ftp_conn *ftpc, @@ -2948,16 +2956,20 @@ static CURLcode ftp_state_user_resp(struct Curl_easy *data, result = ftp_state_loggedin(data, ftpc); } else if(ftpcode == 332) { - if(data->set.str[STRING_FTP_ACCOUNT]) { - result = Curl_pp_sendf(data, &ftpc->pp, "ACCT %s", - data->set.str[STRING_FTP_ACCOUNT]); - if(!result) - ftp_state(data, ftpc, FTP_ACCT); - } - else { + const char *account = CURL_EASY_STR(data, STRING_FTP_ACCOUNT); + if(!account) { failf(data, "ACCT requested but none available"); result = CURLE_LOGIN_DENIED; } + else if(ftp_has_ctrl(account)) { + failf(data, "Control byte in FTP account"); + result = CURLE_BAD_FUNCTION_ARGUMENT; + } + else { + result = Curl_pp_sendf(data, &ftpc->pp, "ACCT %s", account); + if(!result) + ftp_state(data, ftpc, FTP_ACCT); + } } else { /* All other response codes, like: @@ -2965,14 +2977,19 @@ static CURLcode ftp_state_user_resp(struct Curl_easy *data, 530 User ... access denied (the server denies to log the specified user) */ - if(data->set.str[STRING_FTP_ALTERNATIVE_TO_USER] && - !ftpc->ftp_trying_alternative) { + const char *alt = CURL_EASY_STR(data, STRING_FTP_ALTERNATIVE_TO_USER); + if(alt && !ftpc->ftp_trying_alternative) { /* Ok, USER failed. Let's try the supplied command. */ - result = Curl_pp_sendf(data, &ftpc->pp, "%s", - data->set.str[STRING_FTP_ALTERNATIVE_TO_USER]); - if(!result) { - ftpc->ftp_trying_alternative = TRUE; - ftp_state(data, ftpc, FTP_USER); + if(ftp_has_ctrl(alt)) { + failf(data, "Control byte in FTP alternative-to-user command"); + result = CURLE_BAD_FUNCTION_ARGUMENT; + } + else { + result = Curl_pp_sendf(data, &ftpc->pp, "%s", alt); + if(!result) { + ftpc->ftp_trying_alternative = TRUE; + ftp_state(data, ftpc, FTP_USER); + } } } else { @@ -3020,7 +3037,7 @@ static CURLcode ftp_pwd_resp(struct Curl_easy *data, The directory name can contain any character; embedded double-quotes should be escaped by double-quotes (the "quote-doubling" convention). - */ + */ /* scan for the first double-quote for non-standard responses */ while(*ptr != '\n' && *ptr != '\0' && *ptr != '"') @@ -3296,7 +3313,7 @@ static CURLcode ftp_pp_statemachine(struct Curl_easy *data, /* Reply format is like 215 - */ + */ while(*ptr == ' ') ptr++; for(start = ptr; *ptr && *ptr != ' '; ptr++) @@ -3593,7 +3610,7 @@ static CURLcode ftp_sendquote(struct Curl_easy *data, return CURLE_OK; } -static CURLcode ftp_done_status(struct connectdata *conn, +static CURLcode ftp_done_status(struct Curl_easy *data, struct ftp_conn *ftpc, CURLcode status, bool premature) { @@ -3624,7 +3641,8 @@ static CURLcode ftp_done_status(struct connectdata *conn, ftpc->ctl_valid = FALSE; ftpc->cwdfail = TRUE; /* set this TRUE to prevent us to remember the current path, as this connection is going */ - connclose(conn, "FTP ended with bad error code"); + CURL_TRC_FTP(data, "FTP ended with bad error code"); + connclose(data->conn); return status; /* use the already set error code */ } return CURLE_OK; @@ -3634,9 +3652,10 @@ static void ftp_done_wildcard(struct Curl_easy *data, struct ftp_conn *ftpc) { if(data->state.wildcardmatch) { if(data->set.chunk_end && ftpc->file) { - Curl_set_in_callback(data, TRUE); + struct Curl_mapi_guard guard; + CURL_CBAPI_START(&guard, data, easy_chunk_end); data->set.chunk_end(data->set.wildcardptr); - Curl_set_in_callback(data, FALSE); + CURL_CBAPI_END(&guard); freedirs(ftpc); } ftpc->known_filesize = -1; @@ -3651,7 +3670,7 @@ static void ftp_done_path(struct Curl_easy *data, struct ftp_conn *ftpc, /* We can limp along anyway (and should try to since we may already be in * the error path) */ ftpc->ctl_valid = FALSE; /* mark control connection as bad */ - connclose(conn, "FTP: out of memory!"); /* mark for connection closure */ + connclose(conn); /* mark for connection closure */ curlx_safefree(ftpc->prevpath); /* no path remembering */ } else { /* remember working directory for connection reuse */ @@ -3694,7 +3713,7 @@ static CURLcode ftp_done_secondary_socket(struct Curl_easy *data, failf(data, "Failure sending ABOR command: %s", curl_easy_strerror(result)); ftpc->ctl_valid = FALSE; /* mark control connection as bad */ - connclose(conn, "ABOR command failed"); /* connection closure */ + connclose(conn); /* connection closure */ } } @@ -3726,7 +3745,7 @@ static CURLcode ftp_done_control_reply(struct Curl_easy *data, if(!nread && (result == CURLE_OPERATION_TIMEDOUT)) { failf(data, "control connection looks dead"); ftpc->ctl_valid = FALSE; /* mark control connection as bad */ - connclose(conn, "Timeout or similar in FTP DONE operation"); /* close */ + connclose(conn); /* close */ } if(result) @@ -3736,7 +3755,7 @@ static CURLcode ftp_done_control_reply(struct Curl_easy *data, /* we have sent ABOR and there is no reliable way to check if it was * successful or not; we have to close the connection now */ infof(data, "partial download completed, closing connection"); - connclose(conn, "Partial download with no ability to check"); + connclose(conn); return result; } @@ -3774,7 +3793,7 @@ static CURLcode ftp_done_check_partial(struct Curl_easy *data, (data->state.infilesize != -1) && /* upload with known size */ ((!data->set.crlf && !data->state.prefer_ascii && /* no conversion */ (data->state.infilesize != data->req.writebytecount)) || - ((data->set.crlf || data->state.prefer_ascii) && /* maybe crlf conv */ + ((data->set.crlf || data->state.prefer_ascii) && /* maybe CRLF conv */ (data->state.infilesize > data->req.writebytecount)) )) { failf(data, "Uploaded unaligned file size (%" FMT_OFF_T @@ -3820,7 +3839,7 @@ static CURLcode ftp_done(struct Curl_easy *data, CURLcode status, if(!ftp || !ftpc) return CURLE_OK; - result = ftp_done_status(data->conn, ftpc, status, premature); + result = ftp_done_status(data, ftpc, status, premature); ftp_done_wildcard(data, ftpc); ftp_done_path(data, ftpc, result); @@ -4078,11 +4097,12 @@ static CURLcode wc_statemach(struct Curl_easy *data, infof(data, "Wildcard - START of \"%s\"", finfo->filename); if(data->set.chunk_bgn) { long userresponse; - Curl_set_in_callback(data, TRUE); + struct Curl_mapi_guard guard; + CURL_CBAPI_START(&guard, data, easy_chunk_bgn); userresponse = data->set.chunk_bgn( finfo, data->set.wildcardptr, (int)Curl_llist_count(&wildcard->filelist)); - Curl_set_in_callback(data, FALSE); + CURL_CBAPI_END(&guard); switch(userresponse) { case CURL_CHUNK_BGN_FUNC_SKIP: infof(data, "Wildcard - \"%s\" skipped by user", finfo->filename); @@ -4120,9 +4140,10 @@ static CURLcode wc_statemach(struct Curl_easy *data, case CURLWC_SKIP: { if(data->set.chunk_end) { - Curl_set_in_callback(data, TRUE); + struct Curl_mapi_guard guard; + CURL_CBAPI_START(&guard, data, easy_chunk_end); data->set.chunk_end(data->set.wildcardptr); - Curl_set_in_callback(data, FALSE); + CURL_CBAPI_END(&guard); } Curl_node_remove(Curl_llist_head(&wildcard->filelist)); wildcard->state = (Curl_llist_count(&wildcard->filelist) == 0) ? @@ -4280,7 +4301,7 @@ static CURLcode ftp_quit(struct Curl_easy *data, failf(data, "Failure sending QUIT command: %s", curl_easy_strerror(result)); ftpc->ctl_valid = FALSE; /* mark control connection as bad */ - connclose(data->conn, "QUIT command failed"); /* mark for closure */ + connclose(data->conn); /* mark for closure */ ftp_state(data, ftpc, FTP_STOP); return result; } @@ -4313,8 +4334,7 @@ static CURLcode ftp_disconnect(struct Curl_easy *data, disconnect wait in vain and cause more problems than we need to. ftp_quit() will check the state of ftp->ctl_valid. If it is ok it - will try to send the QUIT command, otherwise it will return. - */ + will try to send the QUIT command, otherwise it will return. */ ftpc->shutdown = TRUE; if(dead_connection || Curl_pp_needs_flush(data, &ftpc->pp)) ftpc->ctl_valid = FALSE; @@ -4417,16 +4437,16 @@ static CURLcode ftp_setup_connection(struct Curl_easy *data, return CURLE_OUT_OF_MEMORY; /* clone connection related data that is FTP specific */ - if(data->set.str[STRING_FTP_ACCOUNT]) { - ftpc->account = curlx_strdup(data->set.str[STRING_FTP_ACCOUNT]); + if(CURL_EASY_STR(data, STRING_FTP_ACCOUNT)) { + ftpc->account = curlx_strdup(CURL_EASY_STR(data, STRING_FTP_ACCOUNT)); if(!ftpc->account) { Curl_conn_meta_remove(conn, CURL_META_FTP_CONN); return CURLE_OUT_OF_MEMORY; } } - if(data->set.str[STRING_FTP_ALTERNATIVE_TO_USER]) { + if(CURL_EASY_STR(data, STRING_FTP_ALTERNATIVE_TO_USER)) { ftpc->alternative_to_user = - curlx_strdup(data->set.str[STRING_FTP_ALTERNATIVE_TO_USER]); + curlx_strdup(CURL_EASY_STR(data, STRING_FTP_ALTERNATIVE_TO_USER)); if(!ftpc->alternative_to_user) { curlx_safefree(ftpc->account); Curl_conn_meta_remove(conn, CURL_META_FTP_CONN); @@ -4450,7 +4470,7 @@ static CURLcode ftp_setup_connection(struct Curl_easy *data, return result; } -bool ftp_conns_match(struct connectdata *needle, struct connectdata *conn) +bool Curl_ftp_conns_match(struct connectdata *needle, struct connectdata *conn) { struct ftp_conn *nftpc = Curl_conn_meta_get(needle, CURL_META_FTP_CONN); struct ftp_conn *cftpc = Curl_conn_meta_get(conn, CURL_META_FTP_CONN); diff --git a/Utilities/cmcurl/lib/ftp.h b/Utilities/cmcurl/lib/ftp.h index 257f595826..3ce02c8793 100644 --- a/Utilities/cmcurl/lib/ftp.h +++ b/Utilities/cmcurl/lib/ftp.h @@ -28,7 +28,8 @@ #ifndef CURL_DISABLE_FTP extern const struct Curl_protocol Curl_protocol_ftp; -bool ftp_conns_match(struct connectdata *needle, struct connectdata *conn); +bool Curl_ftp_conns_match(struct connectdata *needle, + struct connectdata *conn); typedef enum { FTPFILE_MULTICWD = 1, /* as defined by RFC1738 */ diff --git a/Utilities/cmcurl/lib/ftplistparser.c b/Utilities/cmcurl/lib/ftplistparser.c index 23fbd1f07c..47e3d75d94 100644 --- a/Utilities/cmcurl/lib/ftplistparser.c +++ b/Utilities/cmcurl/lib/ftplistparser.c @@ -323,18 +323,21 @@ static CURLcode ftp_pl_insert_finfo(struct Curl_easy *data, compare = Curl_fnmatch; /* filter pattern-corresponding filenames */ - Curl_set_in_callback(data, TRUE); - if(compare(data->set.fnmatch_data, wc->pattern, finfo->filename) == 0) { - /* discard symlink which is containing multiple " -> " */ - if((finfo->filetype == CURLFILETYPE_SYMLINK) && finfo->strings.target && - (strstr(finfo->strings.target, " -> "))) { + { + struct Curl_mapi_guard guard; + CURL_CBAPI_START(&guard, data, easy_fnmatch_data); + if(compare(data->set.fnmatch_data, wc->pattern, finfo->filename) == 0) { + /* discard symlink which is containing multiple " -> " */ + if((finfo->filetype == CURLFILETYPE_SYMLINK) && finfo->strings.target && + strstr(finfo->strings.target, " -> ")) { + add = FALSE; + } + } + else { add = FALSE; } + CURL_CBAPI_END(&guard); } - else { - add = FALSE; - } - Curl_set_in_callback(data, FALSE); if(add) { Curl_llist_append(llist, finfo, &infop->list); diff --git a/Utilities/cmcurl/lib/functypes.h b/Utilities/cmcurl/lib/functypes.h index 3938295dd1..7fcee834ff 100644 --- a/Utilities/cmcurl/lib/functypes.h +++ b/Utilities/cmcurl/lib/functypes.h @@ -33,7 +33,7 @@ 1. For systems that run configure or cmake, the alternatives are provided here. 2. For systems with config-*.h files, define them there. -*/ + */ #ifdef USE_WINSOCK /* int recv(SOCKET, char *, int, int) */ diff --git a/Utilities/cmcurl/lib/getinfo.c b/Utilities/cmcurl/lib/getinfo.c index fde4aa4ef2..23f1de270c 100644 --- a/Utilities/cmcurl/lib/getinfo.c +++ b/Utilities/cmcurl/lib/getinfo.c @@ -43,15 +43,9 @@ void Curl_initinfo(struct Curl_easy *data) struct Progress *pro = &data->progress; struct PureInfo *info = &data->info; - pro->t_nslookup = 0; - pro->t_connect = 0; - pro->t_appconnect = 0; - pro->t_pretransfer = 0; - pro->t_posttransfer = 0; - pro->t_starttransfer = 0; - pro->timespent = 0; - pro->t_redirect = 0; - pro->is_t_startransfer_set = FALSE; + memset(&pro->delta, 0, sizeof(pro->delta)); + memset(&pro->total, 0, sizeof(pro->total)); + pro->startransfer_added = FALSE; info->httpcode = 0; info->httpproxycode = 0; @@ -92,7 +86,7 @@ static CURLcode getinfo_char(struct Curl_easy *data, CURLINFO info, } break; case CURLINFO_EFFECTIVE_METHOD: { - const char *m = data->set.str[STRING_CUSTOMREQUEST]; + const char *m = CURL_EASY_STR(data, STRING_CUSTOMREQUEST); if(!m) { if(data->set.opt_no_body) m = "HEAD"; @@ -155,7 +149,7 @@ static CURLcode getinfo_char(struct Curl_easy *data, CURLINFO info, break; case CURLINFO_RTSP_SESSION_ID: #ifndef CURL_DISABLE_RTSP - *param_charp = data->set.str[STRING_RTSP_SESSION_ID]; + *param_charp = CURL_EASY_STR(data, STRING_RTSP_SESSION_ID); #else *param_charp = NULL; #endif @@ -436,31 +430,31 @@ static CURLcode getinfo_offt(struct Curl_easy *data, CURLINFO info, data->progress.ul.total_size : -1; break; case CURLINFO_TOTAL_TIME_T: - *param_offt = data->progress.timespent; + *param_offt = data->progress.total.spent_us; break; case CURLINFO_NAMELOOKUP_TIME_T: - *param_offt = data->progress.t_nslookup; + *param_offt = data->progress.total.nslookup_us; break; case CURLINFO_CONNECT_TIME_T: - *param_offt = data->progress.t_connect; + *param_offt = data->progress.total.connect_us; break; case CURLINFO_APPCONNECT_TIME_T: - *param_offt = data->progress.t_appconnect; + *param_offt = data->progress.total.appconnect_us; break; case CURLINFO_PRETRANSFER_TIME_T: - *param_offt = data->progress.t_pretransfer; + *param_offt = data->progress.total.pretransfer_us; break; case CURLINFO_POSTTRANSFER_TIME_T: - *param_offt = data->progress.t_posttransfer; + *param_offt = data->progress.total.posttransfer_us; break; case CURLINFO_STARTTRANSFER_TIME_T: - *param_offt = data->progress.t_starttransfer; + *param_offt = data->progress.total.starttransfer_us; break; case CURLINFO_QUEUE_TIME_T: - *param_offt = data->progress.t_postqueue; + *param_offt = data->progress.total.queued_us; break; case CURLINFO_REDIRECT_TIME_T: - *param_offt = data->progress.t_redirect; + *param_offt = data->progress.delta.startredirect_us; break; case CURLINFO_RETRY_AFTER: *param_offt = data->info.retry_after; @@ -469,8 +463,7 @@ static CURLcode getinfo_offt(struct Curl_easy *data, CURLINFO info, *param_offt = data->id; break; case CURLINFO_CONN_ID: - *param_offt = data->conn ? - data->conn->connection_id : data->state.recent_conn_id; + *param_offt = data->state.lastconnect_id; break; case CURLINFO_EARLYDATA_SENT_T: *param_offt = data->progress.earlydata_sent; @@ -512,22 +505,22 @@ static CURLcode getinfo_double(struct Curl_easy *data, CURLINFO info, #endif switch(info) { case CURLINFO_TOTAL_TIME: - *param_doublep = DOUBLE_SECS(data->progress.timespent); + *param_doublep = DOUBLE_SECS(data->progress.total.spent_us); break; case CURLINFO_NAMELOOKUP_TIME: - *param_doublep = DOUBLE_SECS(data->progress.t_nslookup); + *param_doublep = DOUBLE_SECS(data->progress.total.nslookup_us); break; case CURLINFO_CONNECT_TIME: - *param_doublep = DOUBLE_SECS(data->progress.t_connect); + *param_doublep = DOUBLE_SECS(data->progress.total.connect_us); break; case CURLINFO_APPCONNECT_TIME: - *param_doublep = DOUBLE_SECS(data->progress.t_appconnect); + *param_doublep = DOUBLE_SECS(data->progress.total.appconnect_us); break; case CURLINFO_PRETRANSFER_TIME: - *param_doublep = DOUBLE_SECS(data->progress.t_pretransfer); + *param_doublep = DOUBLE_SECS(data->progress.total.pretransfer_us); break; case CURLINFO_STARTTRANSFER_TIME: - *param_doublep = DOUBLE_SECS(data->progress.t_starttransfer); + *param_doublep = DOUBLE_SECS(data->progress.total.starttransfer_us); break; case CURLINFO_SIZE_UPLOAD: *param_doublep = (double)data->progress.ul.cur_size; @@ -550,7 +543,7 @@ static CURLcode getinfo_double(struct Curl_easy *data, CURLINFO info, (double)data->progress.ul.total_size : -1; break; case CURLINFO_REDIRECT_TIME: - *param_doublep = DOUBLE_SECS(data->progress.t_redirect); + *param_doublep = DOUBLE_SECS(data->progress.delta.startredirect_us); break; default: diff --git a/Utilities/cmcurl/lib/gopher.c b/Utilities/cmcurl/lib/gopher.c index 039697466b..a5d0a4b0b4 100644 --- a/Utilities/cmcurl/lib/gopher.c +++ b/Utilities/cmcurl/lib/gopher.c @@ -51,25 +51,82 @@ static CURLcode gopher_connecting(struct Curl_easy *data, bool *done) result = Curl_conn_connect(data, FIRSTSOCKET, TRUE, done); if(result) - connclose(conn, "Failed TLS connection"); + connclose(conn); *done = TRUE; return result; } #endif -static CURLcode gopher_do(struct Curl_easy *data, bool *done) +/* Sends buf to the server and, optionally, writes it to the client too. */ +static CURLcode send_buf(struct Curl_easy *data, + const char *buf, + size_t buf_len, + bool client_write) { CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; + size_t nwritten; + timediff_t timeout_ms; + int what; + + while(buf_len) { + result = Curl_xfer_send(data, buf, buf_len, FALSE, &nwritten); + if(!result) { /* Which may not have written it all! */ + if(client_write) { + result = Curl_client_write(data, CLIENTWRITE_HEADER, buf, nwritten); + if(result) + break; + } + + if(nwritten > buf_len) { + DEBUGASSERT(0); + break; + } + buf_len -= nwritten; + buf += nwritten; + if(!buf_len) + break; /* but it did write it all */ + } + else + break; + + timeout_ms = Curl_timeleft_ms(data); + if(timeout_ms < 0) { + result = CURLE_OPERATION_TIMEDOUT; + break; + } + if(!timeout_ms) + timeout_ms = TIMEDIFF_T_MAX; + + /* Do not busyloop. The entire loop thing is a workaround as it causes a + BLOCKING behavior which is a NO-NO. This function should rather be + split up in a do and a doing piece where the pieces that are not + possible to send now will be sent in the doing function repeatedly + until the entire request is sent. */ + what = SOCKET_WRITABLE(sockfd, timeout_ms); + if(what < 0) { + result = CURLE_SEND_ERROR; + break; + } + else if(!what) { + result = CURLE_OPERATION_TIMEDOUT; + break; + } + } + + return result; +} + +static CURLcode gopher_do(struct Curl_easy *data, bool *done) +{ + CURLcode result = CURLE_OK; char *gopherpath; const char *path = data->state.up.path; const char *query = data->state.up.query; const char *buf = NULL; char *buf_alloc = NULL; - size_t nwritten, buf_len; - timediff_t timeout_ms; - int what; + size_t buf_len; *done = TRUE; /* unconditionally */ @@ -103,57 +160,25 @@ static CURLcode gopher_do(struct Curl_easy *data, bool *done) if(result) return result; buf = buf_alloc; - } - for(; buf_len;) { - - result = Curl_xfer_send(data, buf, buf_len, FALSE, &nwritten); - if(!result) { /* Which may not have written it all! */ - result = Curl_client_write(data, CLIENTWRITE_HEADER, buf, nwritten); - if(result) - break; - - if(nwritten > buf_len) { - DEBUGASSERT(0); - break; - } - buf_len -= nwritten; - buf += nwritten; - if(!buf_len) - break; /* but it did write it all */ - } - else - break; - - timeout_ms = Curl_timeleft_ms(data); - if(timeout_ms < 0) { - result = CURLE_OPERATION_TIMEDOUT; - break; - } - if(!timeout_ms) - timeout_ms = TIMEDIFF_T_MAX; - - /* Do not busyloop. The entire loop thing is a workaround as it causes a - BLOCKING behavior which is a NO-NO. This function should rather be - split up in a do and a doing piece where the pieces that are not - possible to send now will be sent in the doing function repeatedly - until the entire request is sent. - */ - what = SOCKET_WRITABLE(sockfd, timeout_ms); - if(what < 0) { - result = CURLE_SEND_ERROR; - break; - } - else if(!what) { - result = CURLE_OPERATION_TIMEDOUT; - break; + /* A decoded CR or LF would terminate the single-line gopher request and + let a crafted URL smuggle additional bytes onto the wire. REJECT_ZERO + only blocks NUL; reject CR and LF here too. A TAB is left alone as it + is the legitimate gopher type-7 selector/search separator. */ + if(memchr(buf, '\r', buf_len) || memchr(buf, '\n', buf_len)) { + curlx_free(buf_alloc); + failf(data, "Bad gopher selector, CR or LF not allowed"); + return CURLE_URL_MALFORMAT; } } + result = send_buf(data, buf, buf_len, TRUE); curlx_free(buf_alloc); if(!result) - result = Curl_xfer_send(data, "\r\n", 2, FALSE, &nwritten); + /* Send CRLF to the server now, but defer writing it to the client to + preserve the historical behavior of this file. */ + result = send_buf(data, "\r\n", 2, FALSE); if(result) { failf(data, "Failed sending Gopher request"); return result; diff --git a/Utilities/cmcurl/lib/hash.h b/Utilities/cmcurl/lib/hash.h index 1d7d3de8ce..d4c73b7786 100644 --- a/Utilities/cmcurl/lib/hash.h +++ b/Utilities/cmcurl/lib/hash.h @@ -31,8 +31,8 @@ typedef size_t (*hash_function)(void *key, size_t slots_num); /* - Comparator function prototype. Compares two keys. -*/ + * Comparator function prototype. Compares two keys. + */ typedef size_t (*comp_function)(void *key1, size_t key1_len, void *key2, diff --git a/Utilities/cmcurl/lib/headers.c b/Utilities/cmcurl/lib/headers.c index b290a9e5b3..a6416b0f07 100644 --- a/Utilities/cmcurl/lib/headers.c +++ b/Utilities/cmcurl/lib/headers.c @@ -59,62 +59,84 @@ CURLHcode curl_easy_header(CURL *curl, int request, struct curl_header **hout) { - struct Curl_llist_node *e; - struct Curl_llist_node *e_pick = NULL; - struct Curl_easy *data = curl; - size_t match = 0; - size_t amount = 0; - struct Curl_header_store *hs = NULL; - struct Curl_header_store *pick = NULL; - if(!name || !hout || !data || - (origin > (CURLH_HEADER | CURLH_TRAILER | CURLH_CONNECT | CURLH_1XX | - CURLH_PSEUDO)) || !origin || (request < -1)) - return CURLHE_BAD_ARGUMENT; - if(!Curl_llist_count(&data->state.httphdrs)) - return CURLHE_NOHEADERS; /* no headers available */ - if(request > data->state.requests) - return CURLHE_NOREQUEST; - if(request == -1) - request = data->state.requests; + struct Curl_eapi_guard guard; + CURLHcode hresult = CURLHE_OK; + CURLcode result; - /* we need a first round to count amount of this header */ - for(e = Curl_llist_head(&data->state.httphdrs); e; e = Curl_node_next(e)) { - hs = Curl_node_elem(e); - if(curl_strequal(hs->name, name) && - (hs->type & origin) && - (hs->request == request)) { - amount++; - pick = hs; - e_pick = e; + if(CURL_EAPI_ENTER(&guard, curl, easy_header, &result)) { + struct Curl_easy *data = curl; + struct Curl_llist_node *e; + struct Curl_llist_node *e_pick = NULL; + size_t match = 0; + size_t amount = 0; + struct Curl_header_store *hs = NULL; + struct Curl_header_store *pick = NULL; + if(!name || !hout || !data || + (origin > (CURLH_HEADER | CURLH_TRAILER | CURLH_CONNECT | CURLH_1XX | + CURLH_PSEUDO)) || !origin || (request < -1)) { + hresult = CURLHE_BAD_ARGUMENT; + goto out; } - } - if(!amount) - return CURLHE_MISSING; - else if(nameindex >= amount) - return CURLHE_BADINDEX; + if(!Curl_llist_count(&data->state.httphdrs)) { + hresult = CURLHE_NOHEADERS; /* no headers available */ + goto out; + } + if(request > data->state.requests) { + hresult = CURLHE_NOREQUEST; + goto out; + } + if(request == -1) + request = data->state.requests; - if(nameindex == amount - 1) - /* if the last or only occurrence is what's asked for, then we know it */ - hs = pick; - else { + /* we need a first round to count amount of this header */ for(e = Curl_llist_head(&data->state.httphdrs); e; e = Curl_node_next(e)) { hs = Curl_node_elem(e); if(curl_strequal(hs->name, name) && (hs->type & origin) && - (hs->request == request) && - (match++ == nameindex)) { + (hs->request == request)) { + amount++; + pick = hs; e_pick = e; - break; } } - if(!e) /* this should not happen */ - return CURLHE_MISSING; + if(!amount) + hresult = CURLHE_MISSING; + else if(nameindex >= amount) + hresult = CURLHE_BADINDEX; + if(hresult) + goto out; + + if(nameindex == amount - 1) + /* if the last or only occurrence is what's asked for, then we know it */ + hs = pick; + else { + for(e = Curl_llist_head(&data->state.httphdrs); e; + e = Curl_node_next(e)) { + hs = Curl_node_elem(e); + if(curl_strequal(hs->name, name) && + (hs->type & origin) && + (hs->request == request) && + (match++ == nameindex)) { + e_pick = e; + break; + } + } + if(!e) { /* this should not happen */ + hresult = CURLHE_MISSING; + goto out; + } + } + /* this is the name we want */ + copy_header_external(hs, nameindex, amount, e_pick, + &data->state.headerout[0]); + *hout = &data->state.headerout[0]; + hresult = CURLHE_OK; } - /* this is the name we want */ - copy_header_external(hs, nameindex, amount, e_pick, - &data->state.headerout[0]); - *hout = &data->state.headerout[0]; - return CURLHE_OK; +out: + CURL_EAPI_LEAVE(&guard); + if(result) + hresult = Curl_eapi_hcode(result); + return hresult; } /* public API */ @@ -123,59 +145,68 @@ struct curl_header *curl_easy_nextheader(CURL *curl, int request, struct curl_header *prev) { - struct Curl_easy *data = curl; - struct Curl_llist_node *pick; - struct Curl_llist_node *e; - struct Curl_header_store *hs; - size_t amount = 0; - size_t index = 0; + struct Curl_eapi_guard guard; + struct curl_header *hd = NULL; + CURLcode result; - if(request > data->state.requests) - return NULL; - if(request == -1) - request = data->state.requests; + if(CURL_EAPI_ENTER(&guard, curl, easy_nextheader, &result)) { + struct Curl_easy *data = curl; + struct Curl_llist_node *pick; + struct Curl_llist_node *e; + struct Curl_header_store *hs; + size_t amount = 0; + size_t index = 0; - if(prev) { - pick = prev->anchor; - if(!pick) - /* something is wrong */ - return NULL; - pick = Curl_node_next(pick); - } - else - pick = Curl_llist_head(&data->state.httphdrs); + if(request > data->state.requests) + goto out; + if(request == -1) + request = data->state.requests; - if(pick) { - /* make sure it is the next header of the desired type */ - do { - hs = Curl_node_elem(pick); - if((hs->type & origin) && (hs->request == request)) - break; + if(prev) { + pick = prev->anchor; + if(!pick) + /* something is wrong */ + goto out; pick = Curl_node_next(pick); - } while(pick); + } + else + pick = Curl_llist_head(&data->state.httphdrs); + + if(pick) { + /* make sure it is the next header of the desired type */ + do { + hs = Curl_node_elem(pick); + if((hs->type & origin) && (hs->request == request)) + break; + pick = Curl_node_next(pick); + } while(pick); + } + + if(!pick) + /* no more headers available */ + goto out; + + hs = Curl_node_elem(pick); + + /* count number of occurrences of this name within the mask and figure out + the index for the currently selected entry */ + for(e = Curl_llist_head(&data->state.httphdrs); e; e = Curl_node_next(e)) { + struct Curl_header_store *check = Curl_node_elem(e); + if(curl_strequal(hs->name, check->name) && + (check->request == request) && + (check->type & origin)) + amount++; + if(e == pick) + index = amount - 1; + } + + copy_header_external(hs, index, amount, pick, + &data->state.headerout[1]); + hd = &data->state.headerout[1]; } - - if(!pick) - /* no more headers available */ - return NULL; - - hs = Curl_node_elem(pick); - - /* count number of occurrences of this name within the mask and figure out - the index for the currently selected entry */ - for(e = Curl_llist_head(&data->state.httphdrs); e; e = Curl_node_next(e)) { - struct Curl_header_store *check = Curl_node_elem(e); - if(curl_strequal(hs->name, check->name) && - (check->request == request) && - (check->type & origin)) - amount++; - if(e == pick) - index = amount - 1; - } - - copy_header_external(hs, index, amount, pick, - &data->state.headerout[1]); - return &data->state.headerout[1]; +out: + CURL_EAPI_LEAVE(&guard); + return hd; } static CURLcode namevalue(char *header, size_t hlen, unsigned int type, @@ -271,7 +302,6 @@ CURLcode Curl_headers_push(struct Curl_easy *data, const char *header, /* insert this node into the list of headers */ Curl_llist_append(&data->state.httphdrs, hs, &hs->node); - data->state.prevhead = hs; } else { failf(data, "Invalid response header"); @@ -286,7 +316,6 @@ CURLcode Curl_headers_push(struct Curl_easy *data, const char *header, static void headers_reset(struct Curl_easy *data) { Curl_llist_init(&data->state.httphdrs, NULL); - data->state.prevhead = NULL; } struct hds_cw_collect_ctx { @@ -315,8 +344,10 @@ static CURLcode hds_cw_collect_write(struct Curl_easy *data, static const struct Curl_cwtype hds_cw_collect = { "hds-collect", NULL, + 0, Curl_cwriter_def_init, hds_cw_collect_write, + Curl_cwriter_def_flush, Curl_cwriter_def_close, sizeof(struct hds_cw_collect_ctx) }; diff --git a/Utilities/cmcurl/lib/hsts.c b/Utilities/cmcurl/lib/hsts.c index b8745a5195..73c01db018 100644 --- a/Utilities/cmcurl/lib/hsts.c +++ b/Utilities/cmcurl/lib/hsts.c @@ -223,53 +223,55 @@ CURLcode Curl_hsts_parse(struct hsts *h, const char *hostname, return CURLE_OK; do { - curlx_str_passblanks(&p); - if(curl_strnequal("max-age", p, 7)) { - bool quoted = FALSE; - int rc; + struct Curl_str word; + struct Curl_str val = { 0 }; + int rc; + bool assign = FALSE; + do { + curlx_str_passblanks(&p); + if(*p == ';') + p++; + else + break; + } while(1); + if(curlx_str_cspn(&p, &word, ";=\r\n \t")) + break; + + curlx_str_passblanks(&p); + if(!curlx_str_single(&p, '=')) { + assign = TRUE; + curlx_str_passblanks(&p); + + if(*p == '\"') { + if(curlx_str_quotedword(&p, &val, MAX_HSTS_LINE)) + break; + } + else { + if(curlx_str_cspn(&p, &val, ", ;\r\n")) + break; + } + } + + if(assign && curlx_str_casecompare(&word, "max-age")) { + const char *vp = curlx_str(&val); if(gotma) return CURLE_BAD_FUNCTION_ARGUMENT; - - p += 7; - curlx_str_passblanks(&p); - if(curlx_str_single(&p, '=')) - return CURLE_BAD_FUNCTION_ARGUMENT; - curlx_str_passblanks(&p); - - if(!curlx_str_single(&p, '\"')) - quoted = TRUE; - - rc = curlx_str_number(&p, &expires, TIME_T_MAX); + rc = curlx_str_number(&vp, &expires, TIME_T_MAX); if(rc == STRE_OVERFLOW) expires = CURL_OFF_T_MAX; else if(rc) /* invalid max-age */ return CURLE_BAD_FUNCTION_ARGUMENT; - if(quoted) { - if(*p != '\"') - return CURLE_BAD_FUNCTION_ARGUMENT; - p++; - } gotma = TRUE; } - else if(curl_strnequal("includesubdomains", p, 17)) { + else if(curlx_str_casecompare(&word, "includesubdomains")) { if(gotinc) return CURLE_BAD_FUNCTION_ARGUMENT; subdomains = TRUE; - p += 17; gotinc = TRUE; } - else { - /* unknown directive, do a lame attempt to skip */ - while(*p && (*p != ';')) - p++; - } - - curlx_str_passblanks(&p); - if(*p == ';') - p++; } while(*p); if(!gotma) diff --git a/Utilities/cmcurl/lib/http.c b/Utilities/cmcurl/lib/http.c index 83d0e01152..4b79b2ab93 100644 --- a/Utilities/cmcurl/lib/http.c +++ b/Utilities/cmcurl/lib/http.c @@ -61,10 +61,10 @@ #include "http_ntlm.h" #include "http_negotiate.h" #include "http_aws_sigv4.h" +#include "http_httpsig.h" #include "url.h" #include "urlapi-int.h" #include "curl_share.h" -#include "hostip.h" #include "dynhds.h" #include "http.h" #include "headers.h" @@ -167,15 +167,14 @@ char *Curl_checkProxyheaders(struct Curl_easy *data, #endif /* If the header has a value, this function returns TRUE and the value is in - 'outp' with blanks trimmed off. -*/ + 'outp' with blanks trimmed off. */ static bool header_has_value(const char **headerp, struct Curl_str *outp) { bool value = !curlx_str_cspn(headerp, outp, ";:") && (!curlx_str_single(headerp, ':') || !curlx_str_single(headerp, ';')); if(value) { - curlx_str_untilnl(headerp, outp, MAX_HTTP_RESP_HEADER_SIZE); + curlx_str_cspn(headerp, outp, "\r\n"); curlx_str_trimblanks(outp); } return value; @@ -201,7 +200,7 @@ static bool http_header_is_empty(const char *header) */ static CURLcode copy_custom_value(const char *header, char **valp) { - struct Curl_str out; + struct Curl_str out = { 0 }; /* find the end of the header name */ if(header_has_value(&header, &out)) { @@ -377,6 +376,10 @@ static bool pickoneauth(struct auth *pick, unsigned long mask, #ifndef CURL_DISABLE_AWS else if(avail & CURLAUTH_AWS_SIGV4) pick->picked = CURLAUTH_AWS_SIGV4; +#endif +#ifndef CURL_DISABLE_HTTPSIG + else if(avail & CURLAUTH_HTTPSIG) + pick->picked = CURLAUTH_HTTPSIG; #endif else { pick->picked = CURLAUTH_PICKNONE; /* we select to use nothing */ @@ -465,7 +468,7 @@ static CURLcode http_perhapsrewind(struct Curl_easy *data, ongoing_auth ? ongoing_auth : "", ongoing_auth ? " send, " : ""); /* We decided to abort the ongoing transfer */ - streamclose(conn, "Mid-auth HTTP and much data left to send"); + streamclose(conn); data->req.size = 0; /* do not download any more than 0 bytes */ data->req.http_bodyless = TRUE; } @@ -521,7 +524,7 @@ static bool http_should_fail(struct Curl_easy *data, int httpcode) /* * Examine the current authentication state to see if this is an error. The * idea is for this function to get called after processing all the headers - * in a response message. If we have been to asked to authenticate + * in a response message. If we have been asked to authenticate at * a particular stage, and we have done it, we are OK. If we are already * completely authenticated, it is not OK to get another 401 or 407. * @@ -578,7 +581,7 @@ CURLcode Curl_http_auth_act(struct Curl_easy *data) if(data->state.authhost.picked == CURLAUTH_NTLM && (data->req.httpversion_sent > 11)) { infof(data, "Forcing HTTP/1.1 for NTLM"); - connclose(conn, "Force HTTP/1.1 connection"); + connclose(conn); data->state.http_neg.wanted = CURL_HTTP_V1x; data->state.http_neg.allowed = CURL_HTTP_V1x; } @@ -666,12 +669,37 @@ static CURLcode output_auth_headers(struct Curl_easy *data, } else #endif +#ifndef CURL_DISABLE_HTTPSIG + if((authstatus->picked == CURLAUTH_HTTPSIG) && !proxy) { + /* HTTPSIG uses its own configured key material rather than + data->state.creds. Do not let unrelated credentials from a + redirected URL bypass the cross-host auth boundary. */ + if(Curl_auth_allowed_to_host(data)) { + auth = "HTTPSIG"; + result = Curl_output_httpsig(data); + if(result) + return result; + } + else + authstatus->done = TRUE; + } + else +#endif #ifdef USE_SPNEGO if(authstatus->picked == CURLAUTH_NEGOTIATE) { - auth = "Negotiate"; - result = Curl_output_negotiate(data, conn, proxy); - if(result) - return result; + if( +#ifndef CURL_DISABLE_PROXY + (proxy && !Curl_checkProxyheaders(data, conn, + STRCONST("Proxy-authorization"))) || +#endif + (!proxy && !Curl_checkheaders(data, STRCONST("Authorization")))) { + auth = "Negotiate"; + result = Curl_output_negotiate(data, conn, proxy); + if(result) + return result; + } + else + authstatus->done = TRUE; } else #endif @@ -793,6 +821,9 @@ CURLcode Curl_http_output_auth(struct Curl_easy *data, #ifdef USE_SPNEGO !(authhost->want & CURLAUTH_NEGOTIATE) && !(authproxy->want & CURLAUTH_NEGOTIATE) && +#endif +#ifndef CURL_DISABLE_HTTPSIG + !(authhost->want & CURLAUTH_HTTPSIG) && #endif !data->state.creds) { /* no authentication with no user or password */ @@ -1122,7 +1153,7 @@ CURLcode Curl_http_input_auth(struct Curl_easy *data, bool proxy, static void http_switch_to_get(struct Curl_easy *data, int code) { - const char *req = data->set.str[STRING_CUSTOMREQUEST]; + const char *req = CURL_EASY_STR(data, STRING_CUSTOMREQUEST); if((req || data->state.httpreq != HTTPREQ_GET) && (data->set.http_follow_mode == CURLFOLLOW_OBEYCODE)) { @@ -1275,8 +1306,8 @@ CURLcode Curl_http_follow(struct Curl_easy *data, const char *newurl, rewind_result = Curl_req_soft_reset(&data->req, data); infof(data, "Issue another request to this URL: '%s'", follow_url); if((data->set.http_follow_mode == CURLFOLLOW_FIRSTONLY) && - data->set.str[STRING_CUSTOMREQUEST] && - !data->state.http_ignorecustom) { + !data->state.http_ignorecustom && + CURL_EASY_STR(data, STRING_CUSTOMREQUEST)) { data->state.http_ignorecustom = TRUE; infof(data, "Drop custom request method for next request"); } @@ -1415,7 +1446,7 @@ bool Curl_compareheader(const char *headerline, /* line to check */ /* pass the header */ p = &headerline[hlen]; - if(curlx_str_untilnl(&p, &val, MAX_HTTP_RESP_HEADER_SIZE)) + if(curlx_str_cspn(&p, &val, "\r\n")) return FALSE; curlx_str_trimblanks(&val); @@ -1432,7 +1463,7 @@ bool Curl_compareheader(const char *headerline, /* line to check */ !p[clen])) return TRUE; /* match! */ /* advance to the next comma */ - if(curlx_str_until(&p, &next, MAX_HTTP_RESP_HEADER_SIZE, ',') || + if(curlx_str_until(&p, &next, len, ',') || curlx_str_single(&p, ',')) break; /* no comma, get out */ @@ -1440,6 +1471,10 @@ bool Curl_compareheader(const char *headerline, /* line to check */ do curlx_str_passblanks(&p); while(!curlx_str_single(&p, ',')); + /* trailing blanks may move the parsing point past the value end, + then there is nothing left to match */ + if((size_t)(p - o) > len) + break; len -= (p - o); } } @@ -1460,7 +1495,7 @@ static void http_exp100_continue(struct Curl_easy *data, struct cr_exp100_ctx *ctx = reader->ctx; if(ctx->state > EXP100_SEND_DATA) { ctx->state = EXP100_SEND_DATA; - Curl_expire_done(data, EXPIRE_100_TIMEOUT); + Curl_expire_clear(data, EXPIRE_100_TIMEOUT); } } @@ -1488,7 +1523,8 @@ static CURLcode cr_exp100_read(struct Curl_easy *data, "timeout %dms", data->set.expect_100_timeout)); ctx->state = EXP100_AWAITING_CONTINUE; ctx->start = *Curl_pgrs_now(data); - Curl_expire(data, data->set.expect_100_timeout, EXPIRE_100_TIMEOUT); + Curl_expire_set(data, EXPIRE_100_TIMEOUT, + data->set.expect_100_timeout, &ctx->start); *nread = 0; *eos = FALSE; return CURLE_OK; @@ -1520,7 +1556,7 @@ static void cr_exp100_done(struct Curl_easy *data, { struct cr_exp100_ctx *ctx = reader->ctx; ctx->state = premature ? EXP100_FAILED : EXP100_SEND_DATA; - Curl_expire_done(data, EXPIRE_100_TIMEOUT); + Curl_expire_clear(data, EXPIRE_100_TIMEOUT); } static const struct Curl_crtype cr_exp100 = { @@ -1675,7 +1711,7 @@ CURLcode Curl_http_done(struct Curl_easy *data, return an error here */ failf(data, "Empty reply from server"); /* Mark it as closed to avoid the "left intact" message */ - streamclose(conn, "Empty reply from server"); + streamclose(conn); return CURLE_GOT_NOTHING; } @@ -1695,7 +1731,7 @@ static bool http_may_use_1_1(const struct Curl_easy *data) return FALSE; /* We want 1.0 and have seen no previous response on *this* connection with a higher version (maybe no response at all yet). */ - if((data->state.http_neg.only_10) && + if(data->state.http_neg.only_10 && (!conn || conn->httpversion_seen <= 10)) return FALSE; /* We are not restricted to use 1.0 only. */ @@ -1780,21 +1816,22 @@ CURLcode Curl_add_custom_headers(struct Curl_easy *data, struct Curl_str name; const char *p = headers->data; const char *origp = p; + size_t hlen = strlen(origp); /* explicitly asked to send header without content is done by a header that ends with a semicolon, but there must be no colon present in the name */ - if(!curlx_str_until(&p, &name, MAX_HTTP_RESP_HEADER_SIZE, ';') && + if(!curlx_str_until(&p, &name, hlen, ';') && !curlx_str_single(&p, ';') && !curlx_str_single(&p, '\0') && !memchr(curlx_str(&name), ':', curlx_strlen(&name))) blankheader = TRUE; else { p = origp; - if(!curlx_str_until(&p, &name, MAX_HTTP_RESP_HEADER_SIZE, ':') && + if(!curlx_str_until(&p, &name, hlen, ':') && !curlx_str_single(&p, ':')) { struct Curl_str val; - curlx_str_untilnl(&p, &val, MAX_HTTP_RESP_HEADER_SIZE); + curlx_str_untilnl(&p, &val, hlen); curlx_str_trimblanks(&val); if(!curlx_strlen(&val)) /* no content, do not send this */ @@ -1805,9 +1842,15 @@ CURLcode Curl_add_custom_headers(struct Curl_easy *data, continue; } + /* a field name is a token and carries no surrounding whitespace, so + trim the parsed name before matching. Otherwise `Authorization :` + (space before the colon) slips past the Authorization/Cookie check + below and gets forwarded to another host on a redirect. */ + curlx_str_trimblanks(&name); + /* only send this if the contents was non-blank or done special */ - if(data->state.aptr.host && + if(data->state.http_host && /* a Host: header was sent already, do not pass on any custom Host: header as that will produce *two* in the same request! */ @@ -1839,9 +1882,11 @@ CURLcode Curl_add_custom_headers(struct Curl_easy *data, other hosts */ !Curl_auth_allowed_to_host(data)) ; - else if(blankheader) - result = curlx_dyn_addf(req, "%.*s:\r\n", (int)curlx_strlen(&name), - curlx_str(&name)); + else if(blankheader) { + result = curlx_dyn_addn(req, curlx_str(&name), curlx_strlen(&name)); + if(!result) + result = curlx_dyn_addn(req, STRCONST(":\r\n")); + } else result = curlx_dyn_addf(req, "%s\r\n", origp); @@ -1947,9 +1992,9 @@ void Curl_http_method(struct Curl_easy *data, httpreq = HTTPREQ_PUT; /* Now set the 'request' pointer to the proper request string */ - if(data->set.str[STRING_CUSTOMREQUEST] && - !data->state.http_ignorecustom) { - request = data->set.str[STRING_CUSTOMREQUEST]; + if(!data->state.http_ignorecustom && + CURL_EASY_STR(data, STRING_CUSTOMREQUEST)) { + request = CURL_EASY_STR(data, STRING_CUSTOMREQUEST); } else { if(data->req.no_body) @@ -1979,32 +2024,20 @@ void Curl_http_method(struct Curl_easy *data, *reqp = httpreq; } -static CURLcode http_useragent(struct Curl_easy *data) -{ - /* The User-Agent string might have been allocated already, because - it might have been used in the proxy connect, but if we have got a header - with the user-agent string specified, we erase the previously made string - here. */ - if(Curl_checkheaders(data, STRCONST("User-Agent"))) - curlx_safefree(data->state.aptr.uagent); - return CURLE_OK; -} - static CURLcode http_set_aptr_host(struct Curl_easy *data) { struct connectdata *conn = data->conn; - struct dynamically_allocated_data *aptr = &data->state.aptr; - const char *ptr; + const char *ptr = NULL; - curlx_safefree(aptr->host); + curlx_safefree(data->state.http_host); #ifndef CURL_DISABLE_COOKIES curlx_safefree(data->req.cookiehost); #endif - ptr = Curl_checkheaders(data, STRCONST("Host")); - if(ptr && - (!data->state.this_is_a_follow || - Curl_peer_equal(data->state.initial_origin, data->state.origin))) { + if(Curl_peer_equal(data->state.initial_origin, data->state.origin)) + ptr = Curl_checkheaders(data, STRCONST("Host")); + + if(ptr) { #ifndef CURL_DISABLE_COOKIES /* If we have a given custom Host: header, we extract the hostname in order to possibly use it for cookie reasons later on. We only allow the @@ -2041,30 +2074,40 @@ static CURLcode http_set_aptr_host(struct Curl_easy *data) #endif if(!curl_strequal("Host:", ptr)) { - aptr->host = curl_maprintf("Host:%s\r\n", &ptr[5]); - if(!aptr->host) + data->state.http_host = curl_maprintf("Host:%s", &ptr[5]); + if(!data->state.http_host) return CURLE_OUT_OF_MEMORY; } } else { - /* Use the hostname as present in the URL if it was IPv6. */ - char *host = (data->state.origin->user_hostname[0] == '[') ? - data->state.origin->user_hostname : data->state.origin->hostname; + /* This is the HTTP Host: header, so we want + * - for IPv6 origins: "[ipv6-address]" where the IPv6 address is + * found in origin->hostname, stripped of zoneid/scopeid. + * - the (IDN converted) origin->hostname (DNS name or IPv4) otherwise. + * Note: zoneid/scopeid only applies to local routing and has no + * meaning on the remote HTTP server (eg. would confuse it). */ + bool ipv6 = (bool)data->state.origin->ipv6; + struct dynbuf tmp; + size_t hlen; + CURLcode result; - if(((conn->given->protocol & (CURLPROTO_HTTPS | CURLPROTO_WSS)) && - (data->state.origin->port == PORT_HTTPS)) || - ((conn->given->protocol & (CURLPROTO_HTTP | CURLPROTO_WS)) && - (data->state.origin->port == PORT_HTTP))) - /* if(HTTPS on port 443) OR (HTTP on port 80) then do not include - the port number in the host string */ - aptr->host = curl_maprintf("Host: %s\r\n", host); - else - aptr->host = curl_maprintf("Host: %s:%d\r\n", - host, data->state.origin->port); + curlx_dyn_init(&tmp, DYN_HTTP_REQUEST); + result = curlx_dyn_addn(&tmp, STRCONST("Host: ")); + if(!result && ipv6) + result = curlx_dyn_addn(&tmp, STRCONST("[")); + if(!result) + result = curlx_dyn_add(&tmp, data->state.origin->hostname); + if(!result && ipv6) + result = curlx_dyn_addn(&tmp, STRCONST("]")); + if(!result && + ((data->state.origin->port != data->state.origin->scheme->defport) || + (data->state.origin->scheme->family != conn->scheme->family))) { + result = curlx_dyn_addf(&tmp, ":%u", data->state.origin->port); + } - if(!aptr->host) - /* without Host: we cannot make a nice request */ - return CURLE_OUT_OF_MEMORY; + data->state.http_host = result ? NULL : curlx_dyn_take(&tmp, &hlen); + curlx_dyn_free(&tmp); + return result; } return CURLE_OK; } @@ -2082,8 +2125,8 @@ static CURLcode http_target(struct Curl_easy *data, struct connectdata *conn = data->conn; #endif - if(data->set.str[STRING_TARGET]) { - path = data->set.str[STRING_TARGET]; + if(CURL_EASY_STR(data, STRING_TARGET)) { + path = CURL_EASY_STR(data, STRING_TARGET); query = NULL; } @@ -2093,7 +2136,7 @@ static CURLcode http_target(struct Curl_easy *data, /* The path sent to the proxy is in fact the entire URL, but if the remote host is a IDN-name, we must make sure that the request we produce only - uses the encoded hostname! */ + uses the decoded hostname! */ /* and no fragment part */ CURLUcode uc; @@ -2102,7 +2145,8 @@ static CURLcode http_target(struct Curl_easy *data, if(!h) return CURLE_OUT_OF_MEMORY; - if(data->state.origin->user_hostname != data->state.origin->hostname) { + if(!data->state.origin->ipv6 && + (data->state.origin->user_hostname != data->state.origin->hostname)) { uc = curl_url_set(h, CURLUPART_HOST, data->state.origin->hostname, 0); if(uc) { curl_url_cleanup(h); @@ -2115,7 +2159,7 @@ static CURLcode http_target(struct Curl_easy *data, return CURLE_OUT_OF_MEMORY; } - if(curl_strequal("http", data->state.up.scheme)) { + if(data->state.origin->scheme == &Curl_scheme_http) { /* when getting HTTP, we do not want the userinfo the URL */ uc = curl_url_set(h, CURLUPART_USER, NULL, 0); if(uc) { @@ -2151,13 +2195,13 @@ static CURLcode http_target(struct Curl_easy *data, curl_url_cleanup(h); /* target or URL */ - result = curlx_dyn_add(r, data->set.str[STRING_TARGET] ? - data->set.str[STRING_TARGET] : url); + result = curlx_dyn_add(r, CURL_EASY_STR(data, STRING_TARGET) ? + CURL_EASY_STR(data, STRING_TARGET) : url); curlx_free(url); if(result) return result; - if(curl_strequal("ftp", data->state.up.scheme) && + if((data->state.origin->scheme == &Curl_scheme_ftp) && data->set.proxy_transfer_mode) { /* when doing ftp, append ;type= if not present */ size_t len = strlen(path); @@ -2533,12 +2577,12 @@ static CURLcode http_cookies(struct Curl_easy *data, struct dynbuf *r) { CURLcode result = CURLE_OK; - char *addcookies = NULL; + const char *addcookies = NULL; bool linecap = FALSE; - if(data->set.str[STRING_COOKIE] && + if(CURL_EASY_STR(data, STRING_COOKIE) && !Curl_checkheaders(data, STRCONST("Cookie")) && Curl_auth_allowed_to_host(data)) - addcookies = data->set.str[STRING_COOKIE]; + addcookies = CURL_EASY_STR(data, STRING_COOKIE); if(data->cookies || addcookies) { struct Curl_llist list; @@ -2615,23 +2659,23 @@ static CURLcode http_range(struct Curl_easy *data, if(((httpreq == HTTPREQ_GET) || (httpreq == HTTPREQ_HEAD)) && !Curl_checkheaders(data, STRCONST("Range"))) { /* if a line like this was already allocated, free the previous one */ - curlx_free(data->state.aptr.rangeline); - data->state.aptr.rangeline = curl_maprintf("Range: bytes=%s\r\n", + curlx_free(data->state.rangeline); + data->state.rangeline = curl_maprintf("Range: bytes=%s\r\n", data->state.range); - if(!data->state.aptr.rangeline) + if(!data->state.rangeline) return CURLE_OUT_OF_MEMORY; } else if((httpreq == HTTPREQ_POST || httpreq == HTTPREQ_PUT) && !Curl_checkheaders(data, STRCONST("Content-Range"))) { curl_off_t req_clen = Curl_creader_total_length(data); /* if a line like this was already allocated, free the previous one */ - curlx_free(data->state.aptr.rangeline); + curlx_free(data->state.rangeline); if(data->set.set_resume_from < 0) { /* Upload resume was asked for, but we do not know the size of the remote part so we tell the server (and act accordingly) that we upload the whole file (again) */ - data->state.aptr.rangeline = + data->state.rangeline = curl_maprintf("Content-Range: bytes 0-%" FMT_OFF_T "/" "%" FMT_OFF_T "\r\n", req_clen - 1, req_clen); } @@ -2643,7 +2687,7 @@ static CURLcode http_range(struct Curl_easy *data, curl_off_t total_len = data->req.authneg ? data->state.infilesize : (data->state.resume_from + req_clen); - data->state.aptr.rangeline = + data->state.rangeline = curl_maprintf("Content-Range: bytes %s%" FMT_OFF_T "/" "%" FMT_OFF_T "\r\n", data->state.range, total_len - 1, total_len); @@ -2651,11 +2695,11 @@ static CURLcode http_range(struct Curl_easy *data, else { /* Range was selected and then we pass the incoming range and append total size */ - data->state.aptr.rangeline = + data->state.rangeline = curl_maprintf("Content-Range: bytes %s/%" FMT_OFF_T "\r\n", data->state.range, req_clen); } - if(!data->state.aptr.rangeline) + if(!data->state.rangeline) return CURLE_OUT_OF_MEMORY; } } @@ -2688,7 +2732,7 @@ static CURLcode http_firstwrite(struct Curl_easy *data) /* The resume point is at the end of file, consider this fine even if it does not allow resume from here. */ infof(data, "The entire document is already downloaded"); - streamclose(conn, "already downloaded"); + streamclose(conn); /* Abort download */ CURL_REQ_CLEAR_RECV(data); k->done = TRUE; @@ -2716,7 +2760,7 @@ static CURLcode http_firstwrite(struct Curl_easy *data) infof(data, "Simulate an HTTP 304 response"); /* we abort the transfer before it is completed == we ruin the reuse ability. Close the connection */ - streamclose(conn, "Simulated 304 handling"); + streamclose(conn); return CURLE_OK; } } /* we have a time condition */ @@ -2890,8 +2934,11 @@ static CURLcode http_add_hd(struct Curl_easy *data, break; case H1_HD_HOST: - if(data->state.aptr.host) - result = curlx_dyn_add(req, data->state.aptr.host); + if(data->state.http_host) { + result = curlx_dyn_add(req, data->state.http_host); + if(!result) + result = curlx_dyn_addn(req, STRCONST("\r\n")); + } break; #ifndef CURL_DISABLE_PROXY @@ -2907,16 +2954,16 @@ static CURLcode http_add_hd(struct Curl_easy *data, break; case H1_HD_RANGE: - if(data->state.use_range && data->state.aptr.rangeline) - result = curlx_dyn_add(req, data->state.aptr.rangeline); + if(data->state.use_range && data->state.rangeline) + result = curlx_dyn_add(req, data->state.rangeline); break; - case H1_HD_USER_AGENT: - if(data->set.str[STRING_USERAGENT] && /* User-Agent: */ - *data->set.str[STRING_USERAGENT] && - data->state.aptr.uagent) - result = curlx_dyn_add(req, data->state.aptr.uagent); + case H1_HD_USER_AGENT: { + const char *ua = CURL_EASY_STR(data, STRING_USERAGENT); + if(ua && *ua && !Curl_checkheaders(data, STRCONST("User-Agent"))) + result = curlx_dyn_addf(req, "User-Agent: %s\r\n", ua); break; + } case H1_HD_ACCEPT: if(!Curl_checkheaders(data, STRCONST("Accept"))) @@ -2933,16 +2980,14 @@ static CURLcode http_add_hd(struct Curl_easy *data, #endif break; - case H1_HD_ACCEPT_ENCODING: - curlx_safefree(data->state.aptr.accept_encoding); - if(!Curl_checkheaders(data, STRCONST("Accept-Encoding")) && - data->set.str[STRING_ENCODING]) - result = curlx_dyn_addf(req, "Accept-Encoding: %s\r\n", - data->set.str[STRING_ENCODING]); + case H1_HD_ACCEPT_ENCODING: { + const char *enc = CURL_EASY_STR(data, STRING_ENCODING); + if(enc && !Curl_checkheaders(data, STRCONST("Accept-Encoding"))) + result = curlx_dyn_addf(req, "Accept-Encoding: %s\r\n", enc); break; + } case H1_HD_REFERER: - curlx_safefree(data->state.aptr.ref); if(Curl_bufref_ptr(&data->state.referer) && !Curl_checkheaders(data, STRCONST("Referer"))) result = curlx_dyn_addf(req, "Referer: %s\r\n", @@ -3065,8 +3110,6 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) result = Curl_http_output_auth(data, data->conn, method, httpreq, data->state.up.path, data->state.up.query, FALSE); - if(!result) - result = http_useragent(data); /* Setup input reader, resume information and ranges */ if(!result) result = set_reader(data, httpreq); @@ -3137,7 +3180,7 @@ static statusline checkhttpprefix(struct Curl_easy *data, head = head->next; } - if((rc != STATUS_DONE) && (checkprefixmax("HTTP/", s, len))) + if((rc != STATUS_DONE) && checkprefixmax("HTTP/", s, len)) rc = onmatch; return rc; @@ -3208,9 +3251,7 @@ static CURLcode http_header_a(struct Curl_easy *data, struct SingleRequest *k = &data->req; enum alpnid id = (k->httpversion == 30) ? ALPN_h3 : (k->httpversion == 20) ? ALPN_h2 : ALPN_h1; - return Curl_altsvc_parse( - data, data->asi, v, id, data->state.origin->hostname, - curlx_uitous((unsigned int)data->state.origin->port)); + return Curl_altsvc_parse(data, data->asi, v, data->state.origin, id); } #else (void)data; @@ -3248,7 +3289,7 @@ static CURLcode http_header_c(struct Curl_easy *data, failf(data, "Maximum file size exceeded"); return CURLE_FILESIZE_EXCEEDED; } - streamclose(conn, "overflow content-length"); + streamclose(conn); infof(data, "Overflow Content-Length: value"); return CURLE_OK; } @@ -3275,7 +3316,7 @@ static CURLcode http_header_c(struct Curl_easy *data, } } while(1); } - v = (!k->http_bodyless && data->set.str[STRING_ENCODING]) ? + v = (!k->http_bodyless && CURL_EASY_STR(data, STRING_ENCODING)) ? HD_VAL(hd, hdlen, "Content-Encoding:") : NULL; if(v) { /* @@ -3309,7 +3350,7 @@ static CURLcode http_header_c(struct Curl_easy *data, * the connection will close when this request has been * served. */ - connclose(conn, "Connection: close used"); + connclose(conn); return CURLE_OK; } if((k->httpversion == 10) && @@ -3320,7 +3361,7 @@ static CURLcode http_header_c(struct Curl_easy *data, * pleasure. Default action for 1.0 is to close. * * [RFC2068, section 19.7.1] */ - connkeep(conn, "Connection keep-alive"); + connkeep(conn); infof(data, "HTTP/1.0 connection set to keep alive"); return CURLE_OK; } @@ -3335,7 +3376,7 @@ static CURLcode http_header_c(struct Curl_easy *data, JavaWebServer/1.1.1 obviously sends the header this way! The third added since some servers use that! The fourth means the requested range was unsatisfied. - */ + */ const char *ptr = v; @@ -3436,7 +3477,7 @@ static CURLcode http_header_p(struct Curl_easy *data, * connection will be kept alive for our pleasure. * Default action for 1.0 is to close. */ - connkeep(conn, "Proxy-Connection keep-alive"); /* do not close */ + connkeep(conn); /* do not close */ infof(data, "HTTP/1.0 proxy connection set to keep alive"); } else if((k->httpversion == 11) && conn->http_proxy.peer && @@ -3445,7 +3486,7 @@ static CURLcode http_header_p(struct Curl_easy *data, * We get an HTTP/1.1 response from a proxy and it says it will * close down after this transfer. */ - connclose(conn, "Proxy-Connection: asked to close after done"); + connclose(conn); infof(data, "HTTP/1.1 proxy connection set close"); } return CURLE_OK; @@ -3537,11 +3578,13 @@ static CURLcode http_header_s(struct Curl_easy *data, * real peer hostname. */ const char *host = data->req.cookiehost ? data->req.cookiehost : data->state.origin->hostname; - const bool secure_context = Curl_secure_context(data, host); + const unsigned char secure_context = Curl_secure_context(data, host) ? + COOKIE_SECURE : 0; CURLcode result; Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); - result = Curl_cookie_add(data, data->cookies, TRUE, FALSE, v, host, - data->state.up.path, secure_context); + result = Curl_cookie_add(data, data->cookies, v, host, + data->state.up.path, + COOKIE_HTTPHEADER | secure_context); Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); return result; } @@ -3610,7 +3653,8 @@ static CURLcode http_header_t(struct Curl_easy *data, /* if this is not chunked, only close can signal the end of this * transfer as Content-Length is said not to be trusted for * transfer-encoding! */ - connclose(conn, "HTTP/1.1 transfer-encoding without chunks"); + CURL_TRC_M(data, "HTTP/1.1 transfer-encoding without chunks"); + connclose(conn); k->ignore_cl = TRUE; } return CURLE_OK; @@ -3756,7 +3800,7 @@ static CURLcode http_statusline(struct Curl_easy *data, we get one of those fancy headers that tell us the server keeps it open for us! */ infof(data, "HTTP 1.0, assume close after body"); - connclose(conn, "HTTP/1.0 close after body"); + connclose(conn); } k->http_bodyless = k->httpcode >= 100 && k->httpcode < 200; @@ -4054,7 +4098,7 @@ static CURLcode http_handle_send_error(struct Curl_easy *data) } else { infof(data, "Got HTTP failure 417 while sending data"); - streamclose(conn, "Stop sending data before everything sent"); + streamclose(conn); result = http_perhapsrewind(data, conn); if(result) return result; @@ -4072,7 +4116,7 @@ static CURLcode http_handle_send_error(struct Curl_easy *data) } else { infof(data, "HTTP error before end of send, stop sending"); - streamclose(conn, "Stop sending data before everything sent"); + streamclose(conn); result = Curl_req_abort_sending(data); if(result) return result; @@ -4148,7 +4192,7 @@ static CURLcode http_on_response(struct Curl_easy *data, 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"); + streamclose(conn); } http_check_auth_closure(data, conn); @@ -4320,8 +4364,7 @@ static CURLcode http_rw_hd(struct Curl_easy *data, if(!fine_statusline) { /* If user has set option HTTP200ALIASES, - compare header line against list of aliases - */ + compare header line against list of aliases */ statusline check = checkhttpprefix(data, hd, hdlen); if(check == STATUS_DONE) { fine_statusline = TRUE; @@ -4485,7 +4528,7 @@ static CURLcode http_parse_headers(struct Curl_easy *data, /* this is not the beginning of a protocol first header line. * Cannot be 0.9 if version was detected or connection was reused. */ k->header = FALSE; - streamclose(conn, "bad HTTP: No end-of-message indicator"); + streamclose(conn); if((k->httpversion >= 10) || conn->bits.reuse) { failf(data, "Invalid status line"); return CURLE_WEIRD_SERVER_REPLY; @@ -4522,7 +4565,7 @@ static CURLcode http_parse_headers(struct Curl_easy *data, /* the first read "header", the status line */ statusline st = checkprotoprefix(data, conn, hd, hlen); if(st == STATUS_BAD) { - streamclose(conn, "bad HTTP: No end-of-message indicator"); + streamclose(conn); /* this is not the beginning of a protocol first header line. * Cannot be 0.9 if version was detected or connection was reused. */ if((k->httpversion >= 10) || conn->bits.reuse) { @@ -4978,7 +5021,7 @@ CURLcode Curl_http_req_to_h2(struct dynhds *h2_headers, if(e->namelen == 2 && curl_strequal("TE", e->name)) { if(http_TE_has_token(e->value, "trailers")) result = Curl_dynhds_add(h2_headers, e->name, e->namelen, - "trailers", sizeof("trailers") - 1); + "trailers", CURL_CSTRLEN("trailers")); } else if(h2_permissible_field(e)) { result = Curl_dynhds_add(h2_headers, e->name, e->namelen, diff --git a/Utilities/cmcurl/lib/http.h b/Utilities/cmcurl/lib/http.h index 5050215743..83b4cc6c64 100644 --- a/Utilities/cmcurl/lib/http.h +++ b/Utilities/cmcurl/lib/http.h @@ -137,8 +137,7 @@ CURLcode Curl_http_follow(struct Curl_easy *data, const char *newurl, will always be unconditionally sent and thus it may not be larger than can always be afforded to send twice. - It must not be greater than 64K to work on VMS. -*/ + It must not be greater than 64K to work on VMS. */ #ifndef MAX_INITIAL_POST_SIZE #define MAX_INITIAL_POST_SIZE (64 * 1024) #endif diff --git a/Utilities/cmcurl/lib/http2.c b/Utilities/cmcurl/lib/http2.c index a820439ef4..0cfdf20571 100644 --- a/Utilities/cmcurl/lib/http2.c +++ b/Utilities/cmcurl/lib/http2.c @@ -46,6 +46,7 @@ #include "bufref.h" #include "curlx/dynbuf.h" #include "headers.h" +#include "curl_share.h" #if NGHTTP2_VERSION_NUM < 0x010f00 #error "nghttp2 1.15.0 or greater required" @@ -178,7 +179,7 @@ static void cf_h2_ctx_init(struct cf_h2_ctx *ctx, bool via_h1_upgrade) Curl_bufq_initp(&ctx->outbufq, &ctx->stream_bufcp, H2_NW_SEND_CHUNKS, 0); curlx_dyn_init(&ctx->scratch, CURL_MAX_HTTP_HEADER); Curl_uint32_hash_init(&ctx->streams, 63, h2_stream_hash_free); - ctx->remote_max_sid = 2147483647; + ctx->remote_max_sid = INT32_MAX; ctx->via_h1_upgrade = via_h1_upgrade; ctx->initialized = TRUE; } @@ -284,8 +285,7 @@ static struct h2_stream_ctx *h2_stream_ctx_create(struct cf_h2_ctx *ctx) static int32_t cf_h2_get_desired_local_win(struct Curl_cfilter *cf, struct Curl_easy *data) { - curl_off_t avail = Curl_rlimit_avail(&data->progress.dl.rlimit, - Curl_pgrs_now(data)); + curl_off_t avail = Curl_rlimit_avail(&data->progress.dl.rlimit, NULL); (void)cf; if(avail < CURL_OFF_T_MAX) { /* limit in place */ @@ -513,7 +513,8 @@ static CURLcode h2_process_pending_input(struct Curl_cfilter *cf, the connection may not be reused. This is set when a GOAWAY frame has been received or when the limit of stream identifiers has been reached. */ - connclose(cf->conn, "http/2: No new requests allowed"); + CURL_TRC_M(data, "http/2: No new requests allowed"); + connclose(cf->conn); } return CURLE_OK; @@ -545,9 +546,9 @@ static bool http2_connisalive(struct Curl_cfilter *cf, struct Curl_easy *data, *input_pending = FALSE; result = Curl_cf_recv_bufq(cf->next, data, &ctx->inbufq, 0, &nread); + CURL_TRC_CF(data, cf, "connisalive, recv pending input -> %d, %zu", + (int)result, nread); if(!result) { - CURL_TRC_CF(data, cf, "%zu bytes stray data read before trying " - "h2 connection", nread); result = h2_process_pending_input(cf, data); if(result) /* immediate error, considered dead */ @@ -699,11 +700,13 @@ char *curl_pushheader_byname(struct curl_pushheaders *h, const char *name) static struct Curl_easy *h2_duphandle(struct Curl_cfilter *cf, struct Curl_easy *data) { - struct Curl_easy *second = curl_easy_duphandle(data); + struct Curl_easy *second = curl_easy_init(); if(second) { struct h2_stream_ctx *second_stream; http2_data_setup(cf, second, &second_stream); - second->state.priority.weight = data->state.priority.weight; + second->state.weight = data->state.weight; + if(data->share) + (void)Curl_share_easy_link(second, data->share); } return second; } @@ -818,11 +821,14 @@ static int push_promise(struct Curl_cfilter *cf, goto fail; } - Curl_set_in_callback(data, TRUE); - rv = data->multi->push_cb(data, newhandle, - stream->push_headers_used, &heads, - data->multi->push_userp); - Curl_set_in_callback(data, FALSE); + { + struct Curl_mapi_guard guard; + CURL_CBAPI_START(&guard, data, multi_push_cb); + rv = data->multi->push_cb(data, newhandle, + stream->push_headers_used, &heads, + data->multi->push_userp); + CURL_CBAPI_END(&guard); + } /* free the headers again */ free_push_headers(stream); @@ -1424,7 +1430,7 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, if(frame->hd.type == NGHTTP2_PUSH_PROMISE) { char *h; - if((namelen == (sizeof(HTTP_PSEUDO_AUTHORITY) - 1)) && + if((namelen == CURL_CSTRLEN(HTTP_PSEUDO_AUTHORITY)) && !strncmp(HTTP_PSEUDO_AUTHORITY, (const char *)name, namelen)) { /* pseudo headers are lower case */ int rc = 0; @@ -1500,7 +1506,7 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, return 0; } - if(namelen == sizeof(HTTP_PSEUDO_STATUS) - 1 && + if(namelen == CURL_CSTRLEN(HTTP_PSEUDO_STATUS) && !memcmp(HTTP_PSEUDO_STATUS, name, namelen)) { /* nghttp2 guarantees :status is received first and only once. */ char buffer[32]; @@ -1686,7 +1692,7 @@ static CURLcode http2_handle_stream_close(struct Curl_cfilter *cf, if(stream->error == NGHTTP2_REFUSED_STREAM) { infof(data, "HTTP/2 stream %d refused by server, try again on a new " "connection", stream->id); - connclose(cf->conn, "REFUSED_STREAM"); /* do not use this anymore */ + connclose(cf->conn); /* do not use this anymore */ data->state.refused_stream = TRUE; return CURLE_RECV_ERROR; /* trigger Curl_retry_request() later */ } @@ -1751,15 +1757,15 @@ out: static int sweight_wanted(const struct Curl_easy *data) { /* 0 weight is not set by user and we take the nghttp2 default one */ - return data->set.priority.weight ? - data->set.priority.weight : NGHTTP2_DEFAULT_WEIGHT; + return data->set.weight ? + data->set.weight : NGHTTP2_DEFAULT_WEIGHT; } static int sweight_in_effect(const struct Curl_easy *data) { /* 0 weight is not set by user and we take the nghttp2 default one */ - return data->state.priority.weight ? - data->state.priority.weight : NGHTTP2_DEFAULT_WEIGHT; + return data->state.weight ? + data->state.weight : NGHTTP2_DEFAULT_WEIGHT; } /* @@ -1771,10 +1777,9 @@ static int sweight_in_effect(const struct Curl_easy *data) static void h2_pri_spec(struct Curl_easy *data, nghttp2_priority_spec *pri_spec) { - struct Curl_data_priority *prio = &data->set.priority; - nghttp2_priority_spec_init(pri_spec, 0, - sweight_wanted(data), FALSE); - data->state.priority = *prio; + int prio = data->set.weight; + nghttp2_priority_spec_init(pri_spec, 0, sweight_wanted(data), FALSE); + data->state.weight = prio; } /* @@ -1896,10 +1901,6 @@ static CURLcode h2_progress_ingress(struct Curl_cfilter *cf, Curl_multi_mark_dirty(data); break; } - else if(!stream) { - DEBUGASSERT(0); - break; - } result = Curl_cf_recv_bufq(cf->next, data, &ctx->inbufq, 0, &nread); if(result) { @@ -1928,8 +1929,9 @@ static CURLcode h2_progress_ingress(struct Curl_cfilter *cf, } if(ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) { - connclose(cf->conn, ctx->rcvd_goaway ? "server closed with GOAWAY" : - "server closed abruptly"); + CURL_TRC_CF(data, cf, "server closed %s", + ctx->rcvd_goaway ? "with GOAWAY" : "abruptly"); + connclose(cf->conn); } CURL_TRC_CF(data, cf, "[0] ingress: done"); @@ -2082,10 +2084,11 @@ static CURLcode h2_submit(struct h2_stream_ctx **pstream, if(result) goto out; - result = Curl_h1_req_parse_read(&stream->h1, buf, len, NULL, - !data->state.http_ignorecustom ? - data->set.str[STRING_CUSTOMREQUEST] : NULL, - 0, &nwritten); + result = Curl_h1_req_parse_read( + &stream->h1, buf, len, NULL, + !data->state.http_ignorecustom ? + CURL_EASY_STR(data, STRING_CUSTOMREQUEST) : NULL, + 0, &nwritten); if(result) goto out; *pnwritten = nwritten; @@ -2408,7 +2411,9 @@ static CURLcode cf_h2_ctx_open(struct Curl_cfilter *cf, failf(data, "Could not initialize nghttp2"); goto out; } - ctx->max_concurrent_streams = DEFAULT_MAX_CONCURRENT_STREAMS; + ctx->max_concurrent_streams = data->multi ? + Curl_multi_max_concurrent_streams(data->multi) : + DEFAULT_MAX_CONCURRENT_STREAMS; if(ctx->via_h1_upgrade) { /* HTTP/1.1 Upgrade issued. H2 Settings have already been submitted @@ -2776,7 +2781,7 @@ struct Curl_cftype Curl_cft_nghttp2 = { static CURLcode http2_cfilter_add(struct Curl_cfilter **pcf, struct Curl_easy *data, struct connectdata *conn, - int sockindex, + int8_t sockindex, bool via_h1_upgrade) { struct Curl_cfilter *cf = NULL; @@ -2886,7 +2891,7 @@ CURLcode Curl_http2_switch_at(struct Curl_cfilter *cf, struct Curl_easy *data) } CURLcode Curl_http2_upgrade(struct Curl_easy *data, - struct connectdata *conn, int sockindex, + struct connectdata *conn, int8_t sockindex, const char *mem, size_t nread) { struct Curl_cfilter *cf; diff --git a/Utilities/cmcurl/lib/http2.h b/Utilities/cmcurl/lib/http2.h index e38dc5745c..c3270da1cc 100644 --- a/Utilities/cmcurl/lib/http2.h +++ b/Utilities/cmcurl/lib/http2.h @@ -54,7 +54,7 @@ CURLcode Curl_http2_switch(struct Curl_easy *data); CURLcode Curl_http2_switch_at(struct Curl_cfilter *cf, struct Curl_easy *data); CURLcode Curl_http2_upgrade(struct Curl_easy *data, - struct connectdata *conn, int sockindex, + struct connectdata *conn, int8_t sockindex, const char *mem, size_t nread); void *Curl_nghttp2_malloc(size_t size, void *user_data); diff --git a/Utilities/cmcurl/lib/http_aws_sigv4.c b/Utilities/cmcurl/lib/http_aws_sigv4.c index 780eed67b9..b16c2cd2aa 100644 --- a/Utilities/cmcurl/lib/http_aws_sigv4.c +++ b/Utilities/cmcurl/lib/http_aws_sigv4.c @@ -52,7 +52,7 @@ #define TIMESTAMP_SIZE 17 -/* hex-encoded with trailing null */ +/* hex-encoded with null-terminator */ #define SHA256_HEX_LENGTH ((2 * CURL_SHA256_DIGEST_LENGTH) + 1) #define MAX_QUERY_COMPONENTS 128 @@ -223,8 +223,7 @@ static CURLcode uri_encode_path(struct Curl_str *original_path, } /* Normalize the query part. Make sure %2B is left percent encoded, and not - decoded to plus, then encoded to space. -*/ + decoded to plus, then encoded to space. */ static CURLcode normalize_query(const char *string, size_t len, struct dynbuf *db) { @@ -370,7 +369,6 @@ static CURLcode merge_duplicate_headers(struct curl_slist *head) /* timestamp should point to a buffer of at last TIMESTAMP_SIZE bytes */ static CURLcode make_headers(struct Curl_easy *data, - const char *hostname, char *timestamp, const char *provider1, size_t plen, /* length of provider1 */ @@ -398,16 +396,10 @@ static CURLcode make_headers(struct Curl_easy *data, /* provider1 lowercase */ Curl_strntolower(&date_full_hdr[2], provider1, plen); - if(!Curl_checkheaders(data, STRCONST("Host"))) { - char *fullhost; - - if(data->state.aptr.host) { - /* remove /r/n as the separator for canonical request must be '\n' */ - size_t pos = strcspn(data->state.aptr.host, "\n\r"); - fullhost = curlx_memdup0(data->state.aptr.host, pos); - } - else - fullhost = curl_maprintf("host:%s", hostname); + if(!Curl_checkheaders(data, STRCONST("Host")) && + data->state.http_host) { + /* Host: [host]:[port] */ + char *fullhost = curlx_strdup(data->state.http_host); if(fullhost) head = Curl_slist_append_nodup(NULL, fullhost); @@ -627,7 +619,7 @@ static CURLcode calc_s3_payload_hash(struct Curl_easy *data, } else { /* Fall back to s3's UNSIGNED-PAYLOAD */ - size_t len = sizeof(S3_UNSIGNED_PAYLOAD) - 1; + size_t len = CURL_CSTRLEN(S3_UNSIGNED_PAYLOAD); DEBUGASSERT(len < SHA256_HEX_LENGTH); /* 16 < 65 */ memcpy(sha_hex, S3_UNSIGNED_PAYLOAD, len); sha_hex[len] = 0; @@ -824,14 +816,13 @@ static CURLcode parse_sigv4_params(struct Curl_easy *data, struct Curl_str *region, struct Curl_str *service) { - const char *line = data->set.str[STRING_AWS_SIGV4]; + const char *line = CURL_EASY_STR(data, STRING_AWS_SIGV4); if(!line || !*line) line = "aws:amz"; /* provider0[:provider1[:region[:service]]] - No string can be longer than N bytes of non-whitespace - */ + No string can be longer than N bytes of non-whitespace */ if(curlx_str_until(&line, provider0, MAX_SIGV4_LEN, ':')) { failf(data, "first aws-sigv4 provider cannot be empty"); return CURLE_BAD_FUNCTION_ARGUMENT; @@ -938,7 +929,6 @@ static CURLcode get_timestamp(char *timestamp, size_t stampsize) } static CURLcode make_canonical_request(struct Curl_easy *data, - const char *hostname, char *timestamp, struct Curl_str *provider1, struct Curl_str *service, @@ -958,7 +948,7 @@ static CURLcode make_canonical_request(struct Curl_easy *data, curlx_dyn_init(&canonical_query, CURL_MAX_HTTP_HEADER); curlx_dyn_init(&canonical_path, CURL_MAX_HTTP_HEADER); - result = make_headers(data, hostname, timestamp, + result = make_headers(data, timestamp, curlx_str(provider1), curlx_strlen(provider1), date_header_out, content_sha256_hdr, canonical_headers, signed_headers); @@ -1148,7 +1138,7 @@ static CURLcode sign_and_set_auth_headers(struct Curl_easy *data, goto fail; /* provider 0 uppercase */ - Curl_strntoupper(&auth_headers[sizeof("Authorization: ") - 1], + Curl_strntoupper(&auth_headers[CURL_CSTRLEN("Authorization: ")], curlx_str(provider0), curlx_strlen(provider0)); curlx_free(data->req.hd_auth); @@ -1213,7 +1203,7 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data) result = get_timestamp(timestamp, sizeof(timestamp)); if(!result) - result = make_canonical_request(data, hostname, timestamp, + result = make_canonical_request(data, timestamp, &provider1, &service, method, payload_hash, payload_hash_len, &date_header, content_sha256_hdr, diff --git a/Utilities/cmcurl/lib/http_chunks.c b/Utilities/cmcurl/lib/http_chunks.c index 9596fc6693..95929d27ba 100644 --- a/Utilities/cmcurl/lib/http_chunks.c +++ b/Utilities/cmcurl/lib/http_chunks.c @@ -71,7 +71,7 @@ */ void Curl_httpchunk_init(struct Curl_easy *data, struct Curl_chunker *ch, - bool ignore_body) + bool ignore_body, bool in_connect) { (void)data; ch->hexindex = 0; /* start at 0 */ @@ -79,6 +79,7 @@ void Curl_httpchunk_init(struct Curl_easy *data, struct Curl_chunker *ch, ch->last_code = CHUNKE_OK; curlx_dyn_init(&ch->trailer, DYN_H1_TRAILER); ch->ignore_body = ignore_body; + ch->in_connect = in_connect; } void Curl_httpchunk_reset(struct Curl_easy *data, struct Curl_chunker *ch, @@ -194,8 +195,7 @@ static CURLcode httpchunk_readwrite(struct Curl_easy *data, case CHUNK_DATA: /* We expect 'datasize' of data. We have 'blen' right now, it can be - more or less than 'datasize'. Get the smallest piece. - */ + more or less than 'datasize'. Get the smallest piece. */ piece = blen; if(ch->datasize < (curl_off_t)blen) piece = curlx_sotouz(ch->datasize); @@ -269,16 +269,14 @@ static CURLcode httpchunk_readwrite(struct Curl_easy *data, } if(!data->set.http_te_skip) { + int hd_type = CLIENTWRITE_HEADER | CLIENTWRITE_TRAILER; + if(ch->in_connect) + hd_type |= CLIENTWRITE_CONNECT; if(cw_next) - result = Curl_cwriter_write(data, cw_next, - CLIENTWRITE_HEADER | - CLIENTWRITE_TRAILER, - tr, trlen); + result = Curl_cwriter_write(data, cw_next, hd_type, tr, trlen); else - result = Curl_client_write(data, - CLIENTWRITE_HEADER | - CLIENTWRITE_TRAILER, - tr, trlen); + result = Curl_client_write(data, hd_type, tr, trlen); + CURL_TRC_WRITE(data, "wrote trailer '%s'", tr); if(result) { ch->state = CHUNK_FAILED; ch->last_code = CHUNKE_PASSTHRU_ERROR; @@ -409,7 +407,7 @@ static CURLcode cw_chunked_init(struct Curl_easy *data, struct chunked_writer *ctx = writer->ctx; data->req.chunk = TRUE; /* chunks coming our way. */ - Curl_httpchunk_init(data, &ctx->ch, FALSE); + Curl_httpchunk_init(data, &ctx->ch, FALSE, FALSE); return CURLE_OK; } @@ -466,8 +464,10 @@ static CURLcode cw_chunked_write(struct Curl_easy *data, const struct Curl_cwtype Curl_httpchunk_unencoder = { "chunked", NULL, + 0, cw_chunked_init, cw_chunked_write, + Curl_cwriter_def_flush, cw_chunked_close, sizeof(struct chunked_writer) }; @@ -518,9 +518,12 @@ static CURLcode add_last_chunk(struct Curl_easy *data, if(result) goto out; - Curl_set_in_callback(data, TRUE); - rc = data->set.trailer_callback(&trailers, data->set.trailer_data); - Curl_set_in_callback(data, FALSE); + { + struct Curl_mapi_guard guard; + CURL_CBAPI_START(&guard, data, easy_trailer_callback); + rc = data->set.trailer_callback(&trailers, data->set.trailer_data); + CURL_CBAPI_END(&guard); + } if(rc != CURL_TRAILERFUNC_OK) { failf(data, "operation aborted by trailing headers callback"); diff --git a/Utilities/cmcurl/lib/http_chunks.h b/Utilities/cmcurl/lib/http_chunks.h index d8e5982e6d..0cb1f88340 100644 --- a/Utilities/cmcurl/lib/http_chunks.h +++ b/Utilities/cmcurl/lib/http_chunks.h @@ -99,11 +99,12 @@ struct Curl_chunker { unsigned char hexindex; char hexbuffer[CHUNK_MAXNUM_LEN + 1]; /* +1 for null-terminator */ BIT(ignore_body); /* never write response body data */ + BIT(in_connect); /* this is a CONNECT response body */ }; /* The following functions are defined in http_chunks.c */ void Curl_httpchunk_init(struct Curl_easy *data, struct Curl_chunker *ch, - bool ignore_body); + bool ignore_body, bool in_connect); void Curl_httpchunk_free(struct Curl_easy *data, struct Curl_chunker *ch); void Curl_httpchunk_reset(struct Curl_easy *data, struct Curl_chunker *ch, bool ignore_body); diff --git a/Utilities/cmcurl/lib/http_digest.c b/Utilities/cmcurl/lib/http_digest.c index 25783d0969..1ec29bd391 100644 --- a/Utilities/cmcurl/lib/http_digest.c +++ b/Utilities/cmcurl/lib/http_digest.c @@ -26,18 +26,46 @@ #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_DIGEST_AUTH) #include "urldata.h" +#include "curl_trc.h" #include "strcase.h" #include "vauth/vauth.h" #include "http_digest.h" #include "curlx/strparse.h" +/* Flush the Digest state if it was created for a different origin or with + different credentials than the ones now in use, then link the current + ones. */ +static void digest_flush_stale(struct Curl_easy *data, + struct digestdata *digest, + struct Curl_peer *peer, + struct Curl_creds *creds) +{ + bool flush = FALSE; + if(digest->origin && !Curl_peer_same_destination(peer, digest->origin)) { + CURL_TRC_M(data, "http_digest, reset on peer change to %s:%u", + peer->hostname, peer->port); + flush = TRUE; + } + else if(digest->creds && !Curl_creds_same(creds, digest->creds)) { + CURL_TRC_M(data, "http_digest, reset on creds change to %s", + creds ? creds->user : "-"); + flush = TRUE; + } + + if(flush) { + /* flush Digest state */ + Curl_auth_digest_cleanup(digest); + } + + Curl_peer_link(&digest->origin, peer); + Curl_creds_link(&digest->creds, creds); +} + /* Test example headers: -WWW-Authenticate: Digest realm="testrealm", nonce="1053604598" -Proxy-Authenticate: Digest realm="testrealm", nonce="1053604598" - -*/ - + WWW-Authenticate: Digest realm="testrealm", nonce="1053604598" + Proxy-Authenticate: Digest realm="testrealm", nonce="1053604598" + */ CURLcode Curl_input_digest(struct Curl_easy *data, bool proxy, const char *header) /* rest of the *-authenticate: @@ -45,42 +73,37 @@ CURLcode Curl_input_digest(struct Curl_easy *data, { /* Point to the correct struct with this */ struct digestdata *digest; + struct Curl_peer *origin = NULL; + CURLcode result; if(proxy) { digest = &data->state.proxydigest; +#ifdef CURL_DISABLE_PROXY + Curl_auth_digest_cleanup(digest); + return CURLE_OK; /* just ignore such a header without proxy support */ +#else + origin = data->conn->http_proxy.peer; +#endif } else { digest = &data->state.digest; + origin = data->state.origin; } - if(!checkprefix("Digest", header) || !ISBLANK(header[6])) + if(!checkprefix("Digest", header) || !ISBLANK(header[6])) { + Curl_auth_digest_cleanup(digest); return CURLE_AUTH_ERROR; + } - header += strlen("Digest"); + header += CURL_CSTRLEN("Digest"); curlx_str_passblanks(&header); - return Curl_auth_decode_digest_http_message(header, digest); -} - -/* Flush the Digest state if it was created for a different origin or with - different credentials than the ones now in use, then link the current - ones. */ -static void digest_flush_stale(struct digestdata *digest, - struct Curl_peer *peer, - struct Curl_creds *creds) -{ - bool flush = FALSE; - if(digest->origin && !Curl_peer_same_destination(peer, digest->origin)) - flush = TRUE; - else if(digest->creds && !Curl_creds_same(creds, digest->creds)) - flush = TRUE; - - if(flush) - /* flush Digest state */ - Curl_auth_digest_cleanup(digest); - - Curl_peer_link(&digest->origin, peer); - Curl_creds_link(&digest->creds, creds); + /* This resets the digest struct before decoding */ + result = Curl_auth_decode_digest_http_message(header, digest); + /* Remember only the peer, the data we take in has no relation to creds + * at this time. We can use it even if creds change. */ + Curl_peer_link(&digest->origin, origin); + return result; } CURLcode Curl_output_digest(struct Curl_easy *data, @@ -88,6 +111,7 @@ CURLcode Curl_output_digest(struct Curl_easy *data, const unsigned char *request, const unsigned char *uripath) { + struct Curl_peer *origin = NULL; CURLcode result; char *response; size_t len; @@ -109,22 +133,22 @@ CURLcode Curl_output_digest(struct Curl_easy *data, return CURLE_NOT_BUILT_IN; #else digest = &data->state.proxydigest; - digest_flush_stale(digest, data->conn->http_proxy.peer, - data->conn->http_proxy.creds); - allocuserpwd = &data->req.hd_proxy_auth; + origin = data->conn->http_proxy.peer; creds = data->conn->http_proxy.creds; + allocuserpwd = &data->req.hd_proxy_auth; authp = &data->state.authproxy; #endif } else { DEBUGASSERT(data->state.origin); digest = &data->state.digest; - digest_flush_stale(digest, data->state.origin, data->state.creds); - allocuserpwd = &data->req.hd_auth; + origin = data->state.origin; creds = data->state.creds; + allocuserpwd = &data->req.hd_auth; authp = &data->state.authhost; } + digest_flush_stale(data, digest, origin, creds); curlx_safefree(*allocuserpwd); #ifdef USE_WINDOWS_SSPI diff --git a/Utilities/cmcurl/lib/http_httpsig.c b/Utilities/cmcurl/lib/http_httpsig.c new file mode 100644 index 0000000000..37f23a09cb --- /dev/null +++ b/Utilities/cmcurl/lib/http_httpsig.c @@ -0,0 +1,689 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * 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(CURL_DISABLE_HTTPSIG) + +#include "urldata.h" +#include "http_httpsig.h" +#include "curl_ed25519.h" +#include "curl_hmac.h" +#include "curl_sha256.h" +#include "http.h" +#include "transfer.h" +#include "curl_trc.h" +#include "slist.h" +#include "curlx/dynbuf.h" +#include "curlx/base64.h" +#include "curlx/strdup.h" +#include "curlx/strparse.h" +#include "strcase.h" + +#include + +#define HTTPSIG_MAX_SIG_BASE CURL_MAX_HTTP_HEADER +#define HTTPSIG_MAX_COMPONENTS 16 +#define HTTPSIG_MAX_RAW_SIG CURL_ED25519_SIGLEN +#define HTTPSIG_DEFAULT_LABEL "sig1" + +enum httpsig_alg { + HTTPSIG_ALG_ED25519, + HTTPSIG_ALG_HMAC_SHA256, + HTTPSIG_ALG_UNKNOWN +}; + +static const char *alg_to_str(enum httpsig_alg alg) +{ + switch(alg) { + case HTTPSIG_ALG_ED25519: + return "ed25519"; + case HTTPSIG_ALG_HMAC_SHA256: + return "hmac-sha256"; + default: + break; + } + return NULL; +} + +static enum httpsig_alg id_to_alg(uint8_t val) +{ + switch(val) { + case CURLHTTPSIG_ED25519: + return HTTPSIG_ALG_ED25519; + case CURLHTTPSIG_HMAC_SHA256: + return HTTPSIG_ALG_HMAC_SHA256; + default: + break; + } + return HTTPSIG_ALG_UNKNOWN; +} + +static CURLcode decode_hex_key(struct Curl_easy *data, + const char *hexstr, + unsigned char **keyout, + size_t *keylen) +{ + size_t len, i; + unsigned char *keybuf; + + *keyout = NULL; + *keylen = 0; + + len = strlen(hexstr); + while(len > 0 && ISNEWLINE(hexstr[len - 1])) + len--; + + if(len == 0 || (len & 1) != 0) { + failf(data, "httpsig: invalid hex key (length %zu)", len); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + + if(len > CURL_MAX_INPUT_LENGTH) { + failf(data, "httpsig: hex key too long"); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + + keybuf = curlx_malloc(len / 2); + if(!keybuf) + return CURLE_OUT_OF_MEMORY; + + for(i = 0; i < len; i += 2) { + if(!ISXDIGIT(hexstr[i]) || !ISXDIGIT(hexstr[i + 1])) { + failf(data, "httpsig: invalid hex at position %zu ('%c%c')", + i, hexstr[i], hexstr[i + 1]); + curlx_free(keybuf); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + keybuf[i / 2] = (unsigned char)((curlx_hexval(hexstr[i]) << 4) | + curlx_hexval(hexstr[i + 1])); + } + + *keyout = keybuf; + *keylen = len / 2; + return CURLE_OK; +} + +/* @authority matches the Host header field-value when available (RFC 9421). + data->state.http_host is produced by http_set_aptr_host() before auth. */ +static CURLcode httpsig_authority(struct Curl_easy *data, + struct connectdata *conn, + struct dynbuf *authority_buf) +{ + const char *h = data->state.http_host; + + if(h && curl_strnequal(h, "host:", 5)) { + const char *value = h + 5; + const char *end; + + while(ISBLANK(*value)) + value++; + if(*value) { + CURLcode result; + + end = value; + while(*end && !ISNEWLINE(*end)) + end++; + while(end > value && ISBLANK(end[-1])) + end--; + result = curlx_dyn_addn(authority_buf, value, (size_t)(end - value)); + if(result) + return result; + return CURLE_OK; + } + } + + { + const char *hostname = conn->origin->hostname; + uint16_t port = conn->origin->port; + + if((conn->given->defport != port) && port) + return curlx_dyn_addf(authority_buf, "%s:%u", hostname, port); + return curlx_dyn_add(authority_buf, hostname); + } +} + +static CURLcode sf_append_quoted(struct dynbuf *buf, const char *str) +{ + CURLcode result = curlx_dyn_addn(buf, "\"", 1); + if(result) + return result; + while(*str) { + if(ISCNTRL(*str)) + return CURLE_BAD_FUNCTION_ARGUMENT; + if(*str == '\\' || *str == '"') { + result = curlx_dyn_addn(buf, "\\", 1); + if(result) + return result; + } + result = curlx_dyn_addn(buf, str, 1); + if(result) + return result; + str++; + } + return curlx_dyn_addn(buf, "\"", 1); +} + +/* base64-encode raw bytes into an RFC 8941 byte sequence (:base64:) */ +static CURLcode sf_encode_byte_seq(const unsigned char *raw, size_t rawlen, + struct dynbuf *out) +{ + CURLcode result; + size_t b64len; + char *b64; + + result = curlx_base64_encode(raw, rawlen, &b64, &b64len); + if(result) + return result; + + result = curlx_dyn_addn(out, ":", 1); + if(!result) + result = curlx_dyn_addn(out, b64, b64len); + if(!result) + result = curlx_dyn_addn(out, ":", 1); + + curlx_free(b64); + return result; +} + +static CURLcode build_sig_params(struct dynbuf *params, + const char **components, size_t count, + time_t created, const char *keyid, + enum httpsig_alg alg) +{ + CURLcode result; + size_t i; + + result = curlx_dyn_addn(params, "(", 1); + if(result) + return result; + + for(i = 0; i < count; i++) { + if(i > 0) { + result = curlx_dyn_addn(params, " ", 1); + if(result) + return result; + } + result = sf_append_quoted(params, components[i]); + if(result) + return result; + } + + result = curlx_dyn_addn(params, ")", 1); + if(result) + return result; + + result = curlx_dyn_addf(params, ";created=%lld", (long long)created); + if(result) + return result; + + if(keyid && *keyid) { + result = curlx_dyn_add(params, ";keyid="); + if(result) + return result; + result = sf_append_quoted(params, keyid); + if(result) + return result; + } + + result = curlx_dyn_addf(params, ";alg=\"%s\"", alg_to_str(alg)); + return result; +} + +/* strings defined by RFC 9421 */ +#define SIG_METHOD "@method" +#define SIG_AUTHORITY "@authority" +#define SIG_PATH "@path" +#define SIG_QUERY "@query" + +/* Resolve a component identifier to its value. + * For headers, we walk the full user-supplied header list to combine + * duplicate field values with ", " per RFC 9421 Section 2.1. Each + * individual value is trimmed of leading/trailing OWS and the trailing + * \r\n. The combined result is written into the caller-provided buffer. */ +static CURLcode resolve_component(const char *name, + const char *method, + const char *authority, + const char *path, + const char *query, + struct Curl_easy *data, + struct dynbuf *valbuf, + const char **out) +{ + *out = NULL; + + if(name[0] == '@') { + if(curl_strequal(name, SIG_METHOD)) + *out = method; + else if(curl_strequal(name, SIG_AUTHORITY)) + *out = authority; + else if(curl_strequal(name, SIG_PATH)) + *out = path; + else if(curl_strequal(name, SIG_QUERY)) + *out = query; + else { + failf(data, "httpsig: unsupported derived component '%s'", name); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + if(!*out) { + failf(data, "httpsig: derived component '%s' has no value", name); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + return CURLE_OK; + } + else { + /* RFC 9421 Section 2.1: walk all user-supplied headers and combine + duplicate field values with ", " per HTTP field combination rules. */ + struct curl_slist *head; + size_t namelen = strlen(name); + bool found = FALSE; + + curlx_dyn_reset(valbuf); + + for(head = data->set.headers; head; head = head->next) { + if(curl_strnequal(head->data, name, namelen) && + Curl_headersep(head->data[namelen])) { + const char *p = strchr(head->data, ':'); + if(p) { + CURLcode result; + struct Curl_str content; + curlx_str_assign(&content, p + 1, strlen(p + 1)); + curlx_str_trimblanks(&content); + if(found) { + result = curlx_dyn_addn(valbuf, ", ", 2); + if(result) + return result; + } + result = curlx_dyn_addn(valbuf, curlx_str(&content), + curlx_strlen(&content)); + if(result) + return result; + found = TRUE; + } + } + } + + if(found) { + *out = curlx_dyn_ptr(valbuf); + return CURLE_OK; + } + failf(data, "httpsig: header '%s' not found in request", name); + return CURLE_BAD_FUNCTION_ARGUMENT; + } +} + +static CURLcode build_sig_base(struct dynbuf *base, + const char **components, size_t count, + const char *method, + const char *authority, + const char *path, + const char *query, + struct Curl_easy *data, + const char *sig_params) +{ + CURLcode result; + size_t i; + struct dynbuf hdrvalbuf; + + curlx_dyn_init(&hdrvalbuf, CURL_MAX_HTTP_HEADER); + + for(i = 0; i < count; i++) { + const char *val = NULL; + result = resolve_component(components[i], method, + authority, path, query, data, + &hdrvalbuf, &val); + if(result || !val) { + failf(data, "httpsig: cannot resolve component '%s'", components[i]); + curlx_dyn_free(&hdrvalbuf); + return result ? result : CURLE_BAD_FUNCTION_ARGUMENT; + } + + result = curlx_dyn_addf(base, "\"%s\": %s\n", components[i], val); + if(result) { + curlx_dyn_free(&hdrvalbuf); + return result; + } + } + + curlx_dyn_free(&hdrvalbuf); + result = curlx_dyn_addf(base, "\"@signature-params\": %s", sig_params); + return result; +} + +static CURLcode parse_components(struct Curl_easy *data, + const char *query, + const char **components, + size_t *ncomp_out, + char **hdrs_copy_out) +{ + const char *hdrs = CURL_EASY_STR(data, STRING_HTTPSIG_HEADERS); + size_t ncomp = 0; + + *hdrs_copy_out = NULL; + if(hdrs && *hdrs) { + char *p; + char *hdrs_copy = curlx_strdup(hdrs); + if(!hdrs_copy) + return CURLE_OUT_OF_MEMORY; + *hdrs_copy_out = hdrs_copy; + p = hdrs_copy; + while(*p && ncomp < HTTPSIG_MAX_COMPONENTS) { + char *start; + size_t tlen = 0; + const char *p2 = p; + + curlx_str_passblanks(&p2); + if(!*p2) + break; + p = start = CURL_UNCONST(p2); + while(*p && !ISBLANK(*p)) { + if((*p == '\"') || (*p == '\\')) + return CURLE_BAD_FUNCTION_ARGUMENT; + p++; + tlen++; + } + if(*p) + *p++ = '\0'; + + if(tlen && start[tlen - 1] == ':') { + /* Header field: drop the trailing ':' marker. RFC 9421 field + names are canonically lowercase (Section 2.1). */ + start[--tlen] = '\0'; + if(!tlen) { + failf(data, "httpsig: empty header component name"); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + Curl_strntolower(start, start, tlen); + components[ncomp++] = start; + } + else { + /* Derived component: map the bare name to its canonical RFC 9421 + '@'-prefixed identifier (Section 2.2). */ + Curl_strntolower(start, start, tlen); + if(!strcmp(start, "method")) + components[ncomp++] = SIG_METHOD; + else if(!strcmp(start, "authority")) + components[ncomp++] = SIG_AUTHORITY; + else if(!strcmp(start, "path")) + components[ncomp++] = SIG_PATH; + else if(!strcmp(start, "query")) + components[ncomp++] = SIG_QUERY; + else { + failf(data, "httpsig: unknown component '%s'", start); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + } + } + if(!ncomp) { + failf(data, "httpsig: no signature components specified"); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + if(*p) { + failf(data, "httpsig: too many signature components (max %u)", + (unsigned int)HTTPSIG_MAX_COMPONENTS); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + + /* RFC 9421 Section 2: each covered component MUST occur only once */ + { + size_t i, j; + + for(i = 0; i < ncomp; i++) { + for(j = i + 1; j < ncomp; j++) { + if(!strcmp(components[i], components[j])) { + failf(data, "httpsig: duplicate signature component '%s'", + components[i]); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + } + } + } + } + else { + components[ncomp++] = SIG_METHOD; + components[ncomp++] = SIG_AUTHORITY; + components[ncomp++] = SIG_PATH; + if(query) + components[ncomp++] = SIG_QUERY; + } + + *ncomp_out = ncomp; + return CURLE_OK; +} + +static time_t httpsig_get_created(void) +{ +#ifdef DEBUGBUILD + char *force = getenv("CURL_FORCETIME"); + if(force && *force) { + char *sigts = getenv("CURL_HTTPSIG_CREATED"); + if(sigts && *sigts) { + const char *p = sigts; + curl_off_t num; + if(!curlx_str_number(&p, &num, CURL_OFF_T_MAX)) + return (time_t)num; + } + return 0; + } +#endif + return time(NULL); +} + +static CURLcode httpsig_sign_base(struct Curl_easy *data, + enum httpsig_alg alg, + const unsigned char *keybuf, + size_t keylen, + const struct dynbuf *sig_base, + unsigned char *raw_sig, + size_t *raw_sig_len) +{ + CURLcode result; + + switch(alg) { + case HTTPSIG_ALG_ED25519: + result = Curl_ed25519_sign( + keybuf, keylen, + (const unsigned char *)curlx_dyn_ptr(sig_base), + curlx_dyn_len(sig_base), + raw_sig, raw_sig_len); + break; + case HTTPSIG_ALG_HMAC_SHA256: + result = Curl_hmacit(&Curl_HMAC_SHA256, keybuf, keylen, + (const unsigned char *)curlx_dyn_ptr(sig_base), + curlx_dyn_len(sig_base), raw_sig); + if(!result) + *raw_sig_len = CURL_SHA256_DIGEST_LENGTH; + break; + default: + result = CURLE_BAD_FUNCTION_ARGUMENT; + break; + } + + if(result && result == CURLE_NOT_BUILT_IN) { + failf(data, "httpsig: algorithm '%s' not supported by TLS backend", + alg_to_str(alg)); + } + return result; +} + +CURLcode Curl_output_httpsig(struct Curl_easy *data) +{ + CURLcode result = CURLE_OUT_OF_MEMORY; + struct connectdata *conn = data->conn; + const char *path; + const char *query; + Curl_HttpReq httpreq; + const char *method = NULL; + const char *hexkey = CURL_EASY_STR(data, STRING_HTTPSIG_KEY); + const char *keyid = CURL_EASY_STR(data, STRING_HTTPSIG_KEYID); + enum httpsig_alg alg; + time_t created; + struct dynbuf sig_params; + struct dynbuf sig_base; + struct dynbuf sig_hdr; + struct dynbuf input_hdr; + struct dynbuf authority_buf; + const char *authority; + const char *components[HTTPSIG_MAX_COMPONENTS]; + size_t ncomp = 0; + unsigned char *keybuf = NULL; + size_t keylen = 0; + unsigned char raw_sig[HTTPSIG_MAX_RAW_SIG]; + size_t raw_sig_len = 0; + char *auth_headers = NULL; + char *hdrs_copy = NULL; + struct dynbuf query_dyn; + + alg = id_to_alg(data->set.httpsig_algorithm); + if(alg == HTTPSIG_ALG_UNKNOWN) { + failf(data, "httpsig: CURLOPT_HTTPSIG_ALGORITHM is required"); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + + if(!hexkey || !*hexkey) { + failf(data, "httpsig: CURLOPT_HTTPSIG_KEY is required"); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + if(!keyid || !*keyid) { + failf(data, "httpsig: CURLOPT_HTTPSIG_KEYID is required"); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + + curlx_dyn_init(&sig_params, CURL_MAX_HTTP_HEADER); + curlx_dyn_init(&sig_base, HTTPSIG_MAX_SIG_BASE); + curlx_dyn_init(&sig_hdr, CURL_MAX_HTTP_HEADER); + curlx_dyn_init(&input_hdr, CURL_MAX_HTTP_HEADER); + curlx_dyn_init(&authority_buf, CURL_MAX_HTTP_HEADER); + curlx_dyn_init(&query_dyn, CURL_MAX_HTTP_HEADER); + + if(Curl_checkheaders(data, STRCONST("Signature")) || + Curl_checkheaders(data, STRCONST("Signature-Input"))) { + /* user provides their own Signature / Signature-Input headers, consider + this done */ + goto done; + } + + result = decode_hex_key(data, hexkey, &keybuf, &keylen); + if(result) + goto fail; + + if(alg == HTTPSIG_ALG_ED25519 && keylen != 32) { + failf(data, "httpsig: ed25519 requires a 32-byte key (got %zu)", keylen); + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto fail; + } + + Curl_http_method(data, &method, &httpreq); + + path = data->state.up.path; + if(!path || !*path) + path = "/"; + + query = data->state.up.query; + + result = httpsig_authority(data, conn, &authority_buf); + if(result) + goto fail; + authority = curlx_dyn_ptr(&authority_buf); + + /* Build @query value: RFC 9421 Section 2.2.7 - always starts with "?" */ + if(query && *query) + result = curlx_dyn_addf(&query_dyn, "?%s", query); + else + result = curlx_dyn_add(&query_dyn, "?"); + if(result) + goto fail; + + result = parse_components(data, query, components, &ncomp, &hdrs_copy); + if(result) + goto fail; + + created = httpsig_get_created(); + + result = build_sig_params(&sig_params, components, ncomp, + created, keyid, alg); + if(result) + goto fail; + + infof(data, "httpsig: Signature-Input params: %s", + curlx_dyn_ptr(&sig_params)); + + result = build_sig_base(&sig_base, components, ncomp, + method, authority, path, + curlx_dyn_ptr(&query_dyn), + data, curlx_dyn_ptr(&sig_params)); + if(result) + goto fail; + + infof(data, "httpsig: Signature base: [%s]", + curlx_dyn_ptr(&sig_base)); + + result = httpsig_sign_base(data, alg, keybuf, keylen, &sig_base, + raw_sig, &raw_sig_len); + if(result) + goto fail; + + result = curlx_dyn_add(&sig_hdr, HTTPSIG_DEFAULT_LABEL "="); + if(result) + goto fail; + result = sf_encode_byte_seq(raw_sig, raw_sig_len, &sig_hdr); + if(result) + goto fail; + + result = curlx_dyn_addf(&input_hdr, "%s=%s", HTTPSIG_DEFAULT_LABEL, + curlx_dyn_ptr(&sig_params)); + if(result) + goto fail; + + auth_headers = curl_maprintf("Signature-Input: %s\r\n" + "Signature: %s\r\n", + curlx_dyn_ptr(&input_hdr), + curlx_dyn_ptr(&sig_hdr)); + if(!auth_headers) + goto fail; + + infof(data, "httpsig: Signature-Input: %s", curlx_dyn_ptr(&input_hdr)); + infof(data, "httpsig: Signature: %s", curlx_dyn_ptr(&sig_hdr)); +done: + curlx_free(data->req.hd_auth); + data->req.hd_auth = auth_headers; + data->state.authhost.done = TRUE; + result = CURLE_OK; + +fail: + if(keybuf) { + memset(keybuf, 0, keylen); + curlx_free(keybuf); + } + memset(raw_sig, 0, sizeof(raw_sig)); + curlx_free(hdrs_copy); + curlx_dyn_free(&sig_params); + curlx_dyn_free(&sig_base); + curlx_dyn_free(&sig_hdr); + curlx_dyn_free(&input_hdr); + curlx_dyn_free(&authority_buf); + curlx_dyn_free(&query_dyn); + return result; +} + +#endif /* !CURL_DISABLE_HTTP && !CURL_DISABLE_HTTPSIG */ diff --git a/Utilities/cmcurl/lib/http_httpsig.h b/Utilities/cmcurl/lib/http_httpsig.h new file mode 100644 index 0000000000..410755d7e0 --- /dev/null +++ b/Utilities/cmcurl/lib/http_httpsig.h @@ -0,0 +1,35 @@ +#ifndef HEADER_CURL_HTTP_HTTPSIG_H +#define HEADER_CURL_HTTP_HTTPSIG_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * 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(CURL_DISABLE_HTTPSIG) + +#include "urldata.h" + +CURLcode Curl_output_httpsig(struct Curl_easy *data); + +#endif /* !CURL_DISABLE_HTTP && !CURL_DISABLE_HTTPSIG */ +#endif /* HEADER_CURL_HTTP_HTTPSIG_H */ diff --git a/Utilities/cmcurl/lib/http_negotiate.c b/Utilities/cmcurl/lib/http_negotiate.c index 891369b5bc..5e8798134f 100644 --- a/Utilities/cmcurl/lib/http_negotiate.c +++ b/Utilities/cmcurl/lib/http_negotiate.c @@ -83,7 +83,7 @@ CURLcode Curl_input_negotiate(struct Curl_easy *data, struct connectdata *conn, return CURLE_OUT_OF_MEMORY; /* Obtain the input token, if any */ - header += strlen("Negotiate"); + header += CURL_CSTRLEN("Negotiate"); curlx_str_passblanks(&header); len = strlen(header); @@ -102,7 +102,7 @@ CURLcode Curl_input_negotiate(struct Curl_easy *data, struct connectdata *conn, } /* Supports SSL channel binding for Windows ISS extended protection */ -#if defined(USE_WINDOWS_SSPI) && defined(SECPKG_ATTR_ENDPOINT_BINDINGS) +#ifdef USE_WINDOWS_SSPI neg_ctx->sslContext = conn->sslContext; #endif /* Check if the connection is using SSL and get the channel binding data */ diff --git a/Utilities/cmcurl/lib/http_ntlm.c b/Utilities/cmcurl/lib/http_ntlm.c index dc9911fdac..a1de75fc07 100644 --- a/Utilities/cmcurl/lib/http_ntlm.c +++ b/Utilities/cmcurl/lib/http_ntlm.c @@ -65,7 +65,7 @@ CURLcode Curl_input_ntlm(struct Curl_easy *data, if(!ntlm) return CURLE_OUT_OF_MEMORY; - header += strlen("NTLM"); + header += CURL_CSTRLEN("NTLM"); curlx_str_passblanks(&header); if(*header) { unsigned char *hdr; @@ -168,9 +168,7 @@ CURLcode Curl_output_ntlm(struct Curl_easy *data, bool proxy) if(!Curl_pSecFn) return result; } -#ifdef SECPKG_ATTR_ENDPOINT_BINDINGS ntlm->sslContext = conn->sslContext; -#endif #endif Curl_bufref_init(&ntlmmsg); diff --git a/Utilities/cmcurl/lib/http_proxy.c b/Utilities/cmcurl/lib/http_proxy.c index d019796b53..7774589ecd 100644 --- a/Utilities/cmcurl/lib/http_proxy.c +++ b/Utilities/cmcurl/lib/http_proxy.c @@ -129,7 +129,10 @@ static CURLcode dynhds_add_custom(struct Curl_easy *data, continue; DEBUGASSERT(curlx_strlen(&name) && value); - if(data->state.aptr.host && + /* trim surrounding whitespace so a padded field name (e.g. + `Authorization :`) cannot slip past the Authorization/Cookie check */ + curlx_str_trimblanks(&name); + if(data->state.http_host && /* a Host: header was sent already, do not pass on any custom Host: header as that will produce *two* in the same request! */ curlx_str_casecompare(&name, "Host")) @@ -198,6 +201,7 @@ static CURLcode http_proxy_create_CONNECT(struct httpreq **preq, proxy_http_ver ver) { char *authority = NULL; + const char *ua; int httpversion = proxy_http_ver_major(ver); CURLcode result; struct httpreq *req = NULL; @@ -212,7 +216,7 @@ static CURLcode http_proxy_create_CONNECT(struct httpreq **preq, goto out; } - result = Curl_http_req_make(&req, "CONNECT", sizeof("CONNECT") - 1, + result = Curl_http_req_make(&req, "CONNECT", CURL_CSTRLEN("CONNECT"), NULL, 0, authority, strlen(authority), NULL, 0); if(result) @@ -239,10 +243,10 @@ static CURLcode http_proxy_create_CONNECT(struct httpreq **preq, goto out; } + ua = CURL_EASY_STR(data, STRING_USERAGENT); if(!Curl_checkProxyheaders(data, cf->conn, STRCONST("User-Agent")) && - data->set.str[STRING_USERAGENT] && *data->set.str[STRING_USERAGENT]) { - result = Curl_dynhds_cadd(&req->headers, "User-Agent", - data->set.str[STRING_USERAGENT]); + ua && *ua) { + result = Curl_dynhds_cadd(&req->headers, "User-Agent", ua); if(result) goto out; } @@ -273,7 +277,7 @@ static CURLcode http_proxy_create_CONNECTUDP(struct httpreq **preq, struct Curl_peer *dest, proxy_http_ver ver) { - const char *proxy_scheme = "http"; + const char *proxy_scheme = "http", *ua; const char *proxy_host = cf->conn->http_proxy.peer->hostname; int httpversion = proxy_http_ver_major(ver); char *authority = NULL; @@ -337,7 +341,7 @@ static CURLcode http_proxy_create_CONNECTUDP(struct httpreq **preq, } if(ver == PROXY_HTTP_V1) { - result = Curl_http_req_make(&req, "GET", sizeof("GET")-1, + result = Curl_http_req_make(&req, "GET", CURL_CSTRLEN("GET"), proxy_scheme, strlen(proxy_scheme), authority, strlen(authority), path, strlen(path)); @@ -345,7 +349,7 @@ static CURLcode http_proxy_create_CONNECTUDP(struct httpreq **preq, goto out; } else if(ver == PROXY_HTTP_V2 || ver == PROXY_HTTP_V3) { - result = Curl_http_req_make(&req, "CONNECT", sizeof("CONNECT") - 1, + result = Curl_http_req_make(&req, "CONNECT", CURL_CSTRLEN("CONNECT"), proxy_scheme, strlen(proxy_scheme), authority, strlen(authority), path, strlen(path)); @@ -378,11 +382,11 @@ static CURLcode http_proxy_create_CONNECTUDP(struct httpreq **preq, goto out; } + ua = CURL_EASY_STR(data, STRING_USERAGENT); if(ver == PROXY_HTTP_V1 && !Curl_checkProxyheaders(data, cf->conn, STRCONST("User-Agent")) && - data->set.str[STRING_USERAGENT] && *data->set.str[STRING_USERAGENT]) { - result = Curl_dynhds_cadd(&req->headers, "User-Agent", - data->set.str[STRING_USERAGENT]); + ua && *ua) { + result = Curl_dynhds_cadd(&req->headers, "User-Agent", ua); if(result) goto out; } @@ -449,10 +453,10 @@ CURLcode Curl_http_proxy_create_tunnel_request( return result; if(udp_tunnel) - infof(data, "Establishing %s proxy UDP tunnel to %s:%s", + infof(data, "Establishing %s proxy UDP tunnel to %s:%u", (ver == PROXY_HTTP_V2) ? "HTTP/2" : (ver == PROXY_HTTP_V3) ? "HTTP/3" : "HTTP", - data->state.up.hostname, data->state.up.port); + dest->user_hostname, dest->port); else infof(data, "Establishing %s proxy tunnel to %s", (ver == PROXY_HTTP_V2) ? "HTTP/2" : diff --git a/Utilities/cmcurl/lib/idn.c b/Utilities/cmcurl/lib/idn.c index b26f251d97..943a520eff 100644 --- a/Utilities/cmcurl/lib/idn.c +++ b/Utilities/cmcurl/lib/idn.c @@ -172,7 +172,8 @@ static CURLcode win32_idn_to_ascii(const char *in, char **out) /* Returned in_w_len includes the null-terminator, which then gets preserved across the calls that follow, ending up terminating the buffer returned to the caller. */ - in_w_len = MultiByteToWideChar(CP_UTF8, 0, in, -1, in_w, IDN_MAX_LENGTH); + in_w_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, + in, -1, in_w, IDN_MAX_LENGTH); if(in_w_len) { wchar_t punycode[IDN_MAX_LENGTH]; int chars = IdnToAscii(0, in_w, in_w_len, punycode, IDN_MAX_LENGTH); @@ -198,7 +199,8 @@ static CURLcode win32_ascii_to_idn(const char *in, char **out) /* Returned in_w_len includes the null-terminator, which then gets preserved across the calls that follow, ending up terminating the buffer returned to the caller. */ - in_w_len = MultiByteToWideChar(CP_UTF8, 0, in, -1, in_w, IDN_MAX_LENGTH); + in_w_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, + in, -1, in_w, IDN_MAX_LENGTH); if(in_w_len) { WCHAR idn[IDN_MAX_LENGTH]; /* stores a UTF-16 string */ int chars = IdnToUnicode(0, in_w, in_w_len, idn, IDN_MAX_LENGTH); diff --git a/Utilities/cmcurl/lib/if2ip.c b/Utilities/cmcurl/lib/if2ip.c index fd34f204b5..9a9fbe7691 100644 --- a/Utilities/cmcurl/lib/if2ip.c +++ b/Utilities/cmcurl/lib/if2ip.c @@ -24,31 +24,31 @@ #include "curl_setup.h" #ifdef HAVE_NETINET_IN_H -# include +#include #endif #ifdef HAVE_ARPA_INET_H -# include +#include #endif #ifdef HAVE_NET_IF_H -# include +#include #endif #ifdef HAVE_SYS_IOCTL_H -# include +#include #endif #ifdef HAVE_NETDB_H -# include +#include #endif #ifdef HAVE_SYS_SOCKIO_H -# include +#include #endif #ifdef HAVE_IFADDRS_H -# include +#include #endif #ifdef HAVE_STROPTS_H -# include +#include #endif #ifdef __VMS -# include +#include #endif #include "curlx/inet_ntop.h" @@ -112,9 +112,9 @@ if2ip_result_t Curl_if2ip(int af, if(iface->ifa_addr->sa_family == af) { if(curl_strequal(iface->ifa_name, interf)) { void *addr; - const char *ip; char scope[12] = ""; char ipstr[64]; + CURLcode result; #ifdef USE_IPV6 if(af == AF_INET6) { #ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID @@ -155,8 +155,8 @@ if2ip_result_t Curl_if2ip(int af, addr = &((struct sockaddr_in *)(void *)iface->ifa_addr)->sin_addr; res = IF2IP_FOUND; - ip = curlx_inet_ntop(af, addr, ipstr, sizeof(ipstr)); - curl_msnprintf(buf, buf_size, "%s%s", ip, scope); + result = curlx_inet_ntop(af, addr, ipstr, sizeof(ipstr)); + curl_msnprintf(buf, buf_size, "%s%s", result ? "" : ipstr, scope); break; } } @@ -188,7 +188,7 @@ if2ip_result_t Curl_if2ip(int af, struct sockaddr_in *s; curl_socket_t dummy; size_t len; - const char *r; + CURLcode result; #ifdef USE_IPV6 (void)remote_scope; @@ -228,10 +228,10 @@ if2ip_result_t Curl_if2ip(int af, s = (struct sockaddr_in *)(void *)&req.ifr_addr; memcpy(&in, &s->sin_addr, sizeof(in)); - r = curlx_inet_ntop(s->sin_family, &in, buf, buf_size); + result = curlx_inet_ntop(s->sin_family, &in, buf, buf_size); sclose(dummy); - if(!r) + if(result) return IF2IP_NOT_FOUND; return IF2IP_FOUND; } diff --git a/Utilities/cmcurl/lib/imap.c b/Utilities/cmcurl/lib/imap.c index 976c9f573e..62ab0a283b 100644 --- a/Utilities/cmcurl/lib/imap.c +++ b/Utilities/cmcurl/lib/imap.c @@ -57,7 +57,6 @@ #include "curlx/dynbuf.h" #include "sendf.h" #include "curl_trc.h" -#include "hostip.h" #include "progress.h" #include "transfer.h" #include "escape.h" @@ -613,6 +612,7 @@ static CURLcode imap_perform_login(struct Curl_easy *data, passwd ? passwd : ""); curlx_free(user); + curlx_strzero(passwd); curlx_free(passwd); if(!result) @@ -641,7 +641,8 @@ static CURLcode imap_perform_authenticate(struct Curl_easy *data, return CURLE_FAILED_INIT; if(ir) { /* Send the AUTHENTICATE command with the initial response */ - result = imap_sendf(data, imapc, "AUTHENTICATE %s %s", mech, ir); + result = imap_sendf(data, imapc, "AUTHENTICATE %s %s", + mech, *ir ? ir : "="); } else { /* Send the AUTHENTICATE command */ @@ -885,7 +886,6 @@ static CURLcode imap_perform_append(struct Curl_easy *data, result = Curl_creader_set_mime(data, postp); if(result) return result; - data->state.infilesize = Curl_creader_client_length(data); } else #endif @@ -895,9 +895,15 @@ static CURLcode imap_perform_append(struct Curl_easy *data, return result; } - /* Check we know the size of the upload */ + /* Check we know the size of the upload. This takes all readers + * into account. Especially crlf conversions which make the size + * unpredictable, e.g. -1. */ + data->state.infilesize = Curl_creader_total_length(data); if(data->state.infilesize < 0) { - failf(data, "Cannot APPEND with unknown input file size"); + if(data->set.crlf) + failf(data, "Cannot APPEND with CRLF conversion making size unknown"); + else + failf(data, "Cannot APPEND with unknown input file size"); return CURLE_UPLOAD_FAILED; } @@ -1356,7 +1362,7 @@ static CURLcode imap_state_select_resp(struct Curl_easy *data, size_t len = curlx_dyn_len(&imapc->pp.recvbuf); if((len >= 18) && checkprefix("OK [UIDVALIDITY ", &line[2])) { curl_off_t value; - const char *p = &line[2] + strlen("OK [UIDVALIDITY "); + const char *p = &line[2] + CURL_CSTRLEN("OK [UIDVALIDITY "); if(!curlx_str_number(&p, &value, UINT_MAX)) { imapc->mb_uidvalidity = (unsigned int)value; imapc->mb_uidvalidity_set = TRUE; @@ -1922,7 +1928,7 @@ static CURLcode imap_parse_custom_request(struct Curl_easy *data, struct IMAP *imap) { CURLcode result = CURLE_OK; - const char *custom = data->set.str[STRING_CUSTOMREQUEST]; + const char *custom = CURL_EASY_STR(data, STRING_CUSTOMREQUEST); if(custom) { /* URL decode the custom request */ @@ -2009,7 +2015,8 @@ static CURLcode imap_done(struct Curl_easy *data, CURLcode status, return CURLE_OK; if(status) { - connclose(conn, "IMAP done with bad status"); /* marked for closure */ + CURL_TRC_M(data, "IMAP done with bad status"); + connclose(conn); /* marked for closure */ result = status; /* use the already set error code */ } else if(!data->set.connect_only && diff --git a/Utilities/cmcurl/lib/ldap.c b/Utilities/cmcurl/lib/ldap.c index a6a69eb7f1..2676f732bb 100644 --- a/Utilities/cmcurl/lib/ldap.c +++ b/Utilities/cmcurl/lib/ldap.c @@ -157,7 +157,7 @@ static ULONG ldap_win_bind_auth(LDAP *server, const char *user, const char *passwd, unsigned long authflags) { ULONG method = 0; - SEC_WINNT_AUTH_IDENTITY cred; + SEC_WINNT_AUTH_IDENTITY_EX cred; ULONG rc = LDAP_AUTH_METHOD_NOT_SUPPORTED; memset(&cred, 0, sizeof(cred)); @@ -238,8 +238,10 @@ static bool ldap_value_needs_base64(const char *attr, size_t attr_len, if((attr_len > 7) && curl_strequal(";binary", attr + attr_len - 7)) return TRUE; - /* check for leading or trailing whitespace */ - if(val->bv_len && (ISBLANK(val->bv_val[0]) || + /* check for a leading ':' or '<' (not a SAFE-INIT-CHAR per RFC 2849) or + leading or trailing whitespace */ + if(val->bv_len && ((val->bv_val[0] == ':') || (val->bv_val[0] == '<') || + ISBLANK(val->bv_val[0]) || ISBLANK(val->bv_val[val->bv_len - 1]))) return TRUE; @@ -251,6 +253,63 @@ static bool ldap_value_needs_base64(const char *attr, size_t attr_len, return FALSE; } +#ifdef USE_WIN32_LDAP +static BOOLEAN bypass_cert_verify(PLDAP Connection, + PCCERT_CONTEXT *ppServerCert) +{ + (void)Connection; + CertFreeCertificateContext(*ppServerCert); + /* approve any certificate since the verification is set to bypass */ + return TRUE; +} +#endif + +static CURLcode show_vals(struct Curl_easy *data, BerValue **vals, + const char *attr) +{ + CURLcode result = CURLE_OK; + int i; + size_t attr_len = strlen(attr); + + for(i = 0; vals[i] && !result; i++) { + result = Curl_client_write(data, CLIENTWRITE_BODY, "\t", 1); + if(!result) + result = Curl_client_write(data, CLIENTWRITE_BODY, attr, attr_len); + if(!result) + result = Curl_client_write(data, CLIENTWRITE_BODY, ":", 1); + + if(result) + break; + + if(ldap_value_needs_base64(attr, attr_len, vals[i])) { + char *val_b64 = NULL; + size_t val_b64_sz = 0; + + /* Binary attribute, encode to base64. */ + if(vals[i]->bv_len) + result = curlx_base64_encode((uint8_t *)vals[i]->bv_val, + vals[i]->bv_len, + &val_b64, &val_b64_sz); + if(!result) + result = Curl_client_write(data, CLIENTWRITE_BODY, ": ", 2); + if(!result && val_b64_sz) + result = Curl_client_write(data, CLIENTWRITE_BODY, val_b64, + val_b64_sz); + curlx_free(val_b64); + } + else { + result = Curl_client_write(data, CLIENTWRITE_BODY, " ", 1); + if(!result) + result = Curl_client_write(data, CLIENTWRITE_BODY, + vals[i]->bv_val, vals[i]->bv_len); + } + + if(!result) + result = Curl_client_write(data, CLIENTWRITE_BODY, "\n", 1); + } + return result; +} + static CURLcode ldap_do(struct Curl_easy *data, bool *done) { CURLcode result = CURLE_OK; @@ -262,7 +321,7 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) int num = 0; struct connectdata *conn = data->conn; int ldap_proto = LDAP_VERSION3; - int ldap_ssl = 0; + bool ldap_ssl = FALSE; #ifdef LDAP_OPT_NETWORK_TIMEOUT struct timeval ldap_timeout = { 10, 0 }; /* 10s connection/search timeout */ #endif @@ -271,10 +330,8 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) #else char *host = NULL; #endif - const char *user = Curl_creds_has_user(data->state.creds) ? - data->state.creds->user : NULL; - const char *passwd = Curl_creds_has_passwd(data->state.creds) ? - data->state.creds->passwd : NULL; + const char *user = data->state.creds ? data->state.creds->user : NULL; + const char *passwd = data->state.creds ? data->state.creds->passwd : NULL; struct ip_quadruple ipquad; bool is_ipv6; BerElement *ber = NULL; @@ -300,8 +357,7 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) goto quit; /* Get the URL scheme (either ldap or ldaps) */ - if(Curl_conn_is_ssl(conn, FIRSTSOCKET)) - ldap_ssl = 1; + ldap_ssl = Curl_conn_is_ssl(conn, FIRSTSOCKET); infof(data, "LDAP local: trying to establish %s connection", ldap_ssl ? "encrypted" : "cleartext"); @@ -340,8 +396,18 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) if(ldap_ssl) { #ifdef HAVE_LDAP_SSL #ifdef USE_WIN32_LDAP - /* Win32 LDAP SDK does not support insecure mode without CA! */ + /* Win32 LDAP uses the Windows CA store to verify certificates */ ldap_set_option(server, LDAP_OPT_SSL, LDAP_OPT_ON); + if(!conn->ssl_config.verifypeer) { + if(conn->ssl_config.verifyhost) { + failf(data, "LDAP local: host verification cannot be enabled when " + "peer verification is disabled for Windows native LDAP"); + result = CURLE_NOT_BUILT_IN; + goto quit; + } + ldap_set_option(server, LDAP_OPT_SERVER_CERTIFICATE, + (void *)(uintptr_t)bypass_cert_verify); + } #else /* !USE_WIN32_LDAP */ int ldap_option; const char *ldap_ca = conn->ssl_config.CAfile; @@ -452,7 +518,6 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) #else char *attribute; #endif - int i; /* Get the DN and write it to the client */ { @@ -486,7 +551,6 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) attribute; attribute = ldap_next_attribute(server, entryIterator, ber)) { BerValue **vals; - size_t attr_len; #ifdef USE_WIN32_LDAP char *attr = curlx_convert_tchar_to_UTF8(attribute); if(!attr) { @@ -497,103 +561,9 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) #else char *attr = attribute; #endif - attr_len = strlen(attr); - vals = ldap_get_values_len(server, entryIterator, attribute); if(vals) { - for(i = 0; vals[i]; i++) { - result = Curl_client_write(data, CLIENTWRITE_BODY, "\t", 1); - if(result) { - ldap_value_free_len(vals); - FREE_ON_WINLDAP(attr); - ldap_memfree(attribute); - goto quit; - } - - result = Curl_client_write(data, CLIENTWRITE_BODY, attr, attr_len); - if(result) { - ldap_value_free_len(vals); - FREE_ON_WINLDAP(attr); - ldap_memfree(attribute); - goto quit; - } - - result = Curl_client_write(data, CLIENTWRITE_BODY, ":", 1); - if(result) { - ldap_value_free_len(vals); - FREE_ON_WINLDAP(attr); - ldap_memfree(attribute); - goto quit; - } - - if(ldap_value_needs_base64(attr, attr_len, vals[i])) { - char *val_b64 = NULL; - size_t val_b64_sz = 0; - - /* Binary attribute, encode to base64. */ - if(vals[i]->bv_len) { - result = curlx_base64_encode((uint8_t *)vals[i]->bv_val, - vals[i]->bv_len, - &val_b64, &val_b64_sz); - if(result) { - ldap_value_free_len(vals); - FREE_ON_WINLDAP(attr); - ldap_memfree(attribute); - goto quit; - } - } - - result = Curl_client_write(data, CLIENTWRITE_BODY, ": ", 2); - if(result) { - curlx_free(val_b64); - ldap_value_free_len(vals); - FREE_ON_WINLDAP(attr); - ldap_memfree(attribute); - goto quit; - } - - if(val_b64_sz) { - result = Curl_client_write(data, CLIENTWRITE_BODY, val_b64, - val_b64_sz); - if(result) { - curlx_free(val_b64); - ldap_value_free_len(vals); - FREE_ON_WINLDAP(attr); - ldap_memfree(attribute); - goto quit; - } - } - - curlx_free(val_b64); - } - else { - result = Curl_client_write(data, CLIENTWRITE_BODY, " ", 1); - if(result) { - ldap_value_free_len(vals); - FREE_ON_WINLDAP(attr); - ldap_memfree(attribute); - goto quit; - } - - result = Curl_client_write(data, CLIENTWRITE_BODY, - vals[i]->bv_val, vals[i]->bv_len); - if(result) { - ldap_value_free_len(vals); - FREE_ON_WINLDAP(attr); - ldap_memfree(attribute); - goto quit; - } - } - - result = Curl_client_write(data, CLIENTWRITE_BODY, "\n", 1); - if(result) { - ldap_value_free_len(vals); - FREE_ON_WINLDAP(attr); - ldap_memfree(attribute); - goto quit; - } - } - + result = show_vals(data, vals, attr); /* Free memory used to store values */ ldap_value_free_len(vals); } @@ -602,7 +572,8 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) FREE_ON_WINLDAP(attr); ldap_memfree(attribute); - result = Curl_client_write(data, CLIENTWRITE_BODY, "\n", 1); + if(!result) + result = Curl_client_write(data, CLIENTWRITE_BODY, "\n", 1); if(result) goto quit; } @@ -631,7 +602,7 @@ quit: /* no data to transfer */ Curl_xfer_setup_nop(data); - connclose(conn, "LDAP connection always disable reuse"); + connclose(conn); return result; } @@ -718,7 +689,8 @@ static curl_ldap_num_t ldap_url_parse2_low(struct Curl_easy *data, if(!data || !data->state.up.path || data->state.up.path[0] != '/' || - !curl_strnequal("LDAP", data->state.up.scheme, 4)) + ((data->state.origin->scheme != &Curl_scheme_ldap) && + (data->state.origin->scheme != &Curl_scheme_ldaps))) return LDAP_INVALID_SYNTAX; ludp->lud_scope = LDAP_SCOPE_BASE; @@ -870,7 +842,7 @@ static curl_ldap_num_t ldap_url_parse2_low(struct Curl_easy *data, LDAP_TRACE(("filter '%s'\n", filter)); /* Unescape the filter */ - result = Curl_urldecode(filter, 0, &unescaped, NULL, REJECT_ZERO); + result = Curl_urldecode(filter, 0, &unescaped, NULL, REJECT_CTRL); if(result) { rc = LDAP_NO_MEMORY; @@ -954,9 +926,9 @@ void Curl_ldap_version(char *buf, size_t bufsz) curl_msnprintf(buf, bufsz, "WinLDAP"); #else #ifdef LDAP_OPT_X_TLS_PASSPHRASE - static const char *flavor = "/Apple"; + static const char flavor[] = "/Apple"; #else - static const char *flavor = ""; + static const char flavor[] = ""; #endif LDAPAPIInfo api; api.ldapai_info_version = LDAP_API_INFO_VERSION; diff --git a/Utilities/cmcurl/lib/llist.c b/Utilities/cmcurl/lib/llist.c index ce0c59cc50..6528ebaef2 100644 --- a/Utilities/cmcurl/lib/llist.c +++ b/Utilities/cmcurl/lib/llist.c @@ -211,8 +211,7 @@ struct Curl_llist_node *Curl_llist_head(const struct Curl_llist *list) /* llist_tail() returns the last 'struct Curl_llist_node *', which might be NULL - @unittest 1300 -*/ + @unittest 1300 */ UNITTEST struct Curl_llist_node *llist_tail(const struct Curl_llist *list); UNITTEST struct Curl_llist_node *llist_tail(const struct Curl_llist *list) { @@ -251,8 +250,7 @@ struct Curl_llist_node *Curl_node_next(const struct Curl_llist_node *n) /* llist_node_prev() returns the previous element in a list from a given Curl_llist_node - @unittest 1300 -*/ + @unittest 1300 */ UNITTEST struct Curl_llist_node *llist_node_prev( const struct Curl_llist_node *n); UNITTEST struct Curl_llist_node *llist_node_prev( diff --git a/Utilities/cmcurl/lib/md5.c b/Utilities/cmcurl/lib/md5.c index 1f1b4f8ad6..f842c7ae63 100644 --- a/Utilities/cmcurl/lib/md5.c +++ b/Utilities/cmcurl/lib/md5.c @@ -40,7 +40,7 @@ #ifdef USE_MBEDTLS #include #if MBEDTLS_VERSION_NUMBER < 0x03020000 -#error "mbedTLS 3.2.0 or later required" +#error "mbedTLS 3.2.0 or greater required" #endif #include #endif @@ -68,7 +68,7 @@ static void my_md5_final(unsigned char *digest, void *ctx) #if NETTLE_VERSION_MAJOR >= 4 md5_digest(ctx, digest); #else - md5_digest(ctx, 16, digest); + md5_digest(ctx, MD5_DIGEST_LEN, digest); #endif } @@ -128,8 +128,9 @@ typedef psa_hash_operation_t my_md5_ctx; static CURLcode my_md5_init(void *ctx) { - memset(ctx, 0, sizeof(my_md5_ctx)); - if(psa_hash_setup(ctx, PSA_ALG_MD5) != PSA_SUCCESS) + psa_hash_operation_t *pctx = (psa_hash_operation_t *)ctx; + *pctx = psa_hash_operation_init(); + if(psa_hash_setup(pctx, PSA_ALG_MD5) != PSA_SUCCESS) return CURLE_OUT_OF_MEMORY; return CURLE_OK; } @@ -143,7 +144,7 @@ static void my_md5_update(void *ctx, static void my_md5_final(unsigned char *digest, void *ctx) { size_t actual_length; - (void)psa_hash_finish(ctx, digest, 16, &actual_length); + (void)psa_hash_finish(ctx, digest, MD5_DIGEST_LEN, &actual_length); } #elif (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && \ @@ -220,7 +221,7 @@ static void my_md5_final(unsigned char *digest, void *in) my_md5_ctx *ctx = (my_md5_ctx *)in; unsigned long length = 0; CryptGetHashParam(ctx->hHash, HP_HASHVAL, NULL, &length, 0); - if(length == 16) + if(length == MD5_DIGEST_LEN) CryptGetHashParam(ctx->hHash, HP_HASHVAL, digest, &length, 0); if(ctx->hHash) CryptDestroyHash(ctx->hHash); @@ -529,7 +530,7 @@ const struct HMAC_params Curl_HMAC_MD5 = { my_md5_final, /* Hash computation end function. */ sizeof(my_md5_ctx), /* Size of hash context structure. */ 64, /* Maximum key length. */ - 16 /* Result size. */ + MD5_DIGEST_LEN /* Result size. */ }; const struct MD5_params Curl_DIGEST_MD5 = { @@ -537,7 +538,7 @@ const struct MD5_params Curl_DIGEST_MD5 = { my_md5_update, /* Digest update function */ my_md5_final, /* Digest computation end function */ sizeof(my_md5_ctx), /* Size of digest context struct */ - 16 /* Result size */ + MD5_DIGEST_LEN /* Result size */ }; /* diff --git a/Utilities/cmcurl/lib/mime.c b/Utilities/cmcurl/lib/mime.c index 077e599e43..6004375765 100644 --- a/Utilities/cmcurl/lib/mime.c +++ b/Utilities/cmcurl/lib/mime.c @@ -223,7 +223,7 @@ static char *escape_string(struct Curl_easy *data, table = formtable; /* data can be NULL when this function is called indirectly from curl_formget(). */ - if(strategy == MIMESTRATEGY_MAIL || (data && (data->set.mime_formescape))) + if(strategy == MIMESTRATEGY_MAIL || (data && data->set.mime_formescape)) table = mimetable; curlx_dyn_init(&db, CURL_MAX_INPUT_LENGTH); @@ -419,6 +419,11 @@ static size_t encoder_base64_read(char *buffer, size_t size, bool ateof, return cursize; } +/* The maximum input size that does not cause an overflow. */ +#define BASE64_MAX_INPUT_SIZE \ + (((CURL_OFF_T_MAX / (MAX_ENCODED_LINE_LENGTH + 2)) * \ + MAX_ENCODED_LINE_LENGTH / 4) * 3 - 3) + static curl_off_t encoder_base64_size(curl_mimepart *part) { curl_off_t size = part->datasize; @@ -426,6 +431,10 @@ static curl_off_t encoder_base64_size(curl_mimepart *part) if(size <= 0) return size; /* Unknown size or no data. */ + /* Prevent integer overflows */ + if(size > BASE64_MAX_INPUT_SIZE) + return -1; + /* Compute base64 character count. */ size = 4 * (1 + ((size - 1) / 3)); @@ -1708,6 +1717,16 @@ static CURLcode add_content_disposition(struct Curl_easy *data, CURLcode result = CURLE_OK; char *name = NULL; char *filename = NULL; + /* The mail (and legacy mime_formescape) strategy quotes the name and + filename with a backslash and has no in-band way to represent a CR or + LF, so one embedded in the value would split the generated header. The + form strategy percent-encodes CR/LF (see escape_string) and is safe. */ + bool backslash = (strategy == MIMESTRATEGY_MAIL) || + (data && data->set.mime_formescape); + if(backslash && + ((part->name && part->name[strcspn(part->name, "\r\n")]) || + (part->filename && part->filename[strcspn(part->filename, "\r\n")]))) + return CURLE_BAD_FUNCTION_ARGUMENT; if(part->name) { name = escape_string(data, part->name, strategy); diff --git a/Utilities/cmcurl/lib/mprintf.c b/Utilities/cmcurl/lib/mprintf.c index 230b554191..d4725c905f 100644 --- a/Utilities/cmcurl/lib/mprintf.c +++ b/Utilities/cmcurl/lib/mprintf.c @@ -395,22 +395,26 @@ static bool parse_conversion(const char f, unsigned int *flagp, flags |= FLAGS_CHAR; break; case 'f': - type = MTYPE_DOUBLE; + type = flags & FLAGS_LONGDOUBLE ? MTYPE_LONGDOUBLE : MTYPE_DOUBLE; + break; + case 'F': + type = flags & FLAGS_LONGDOUBLE ? MTYPE_LONGDOUBLE : MTYPE_DOUBLE; + flags |= FLAGS_UPPER; break; case 'e': - type = MTYPE_DOUBLE; + type = flags & FLAGS_LONGDOUBLE ? MTYPE_LONGDOUBLE : MTYPE_DOUBLE; flags |= FLAGS_FLOATE; break; case 'E': - type = MTYPE_DOUBLE; + type = flags & FLAGS_LONGDOUBLE ? MTYPE_LONGDOUBLE : MTYPE_DOUBLE; flags |= FLAGS_FLOATE | FLAGS_UPPER; break; case 'g': - type = MTYPE_DOUBLE; + type = flags & FLAGS_LONGDOUBLE ? MTYPE_LONGDOUBLE : MTYPE_DOUBLE; flags |= FLAGS_FLOATG; break; case 'G': - type = MTYPE_DOUBLE; + type = flags & FLAGS_LONGDOUBLE ? MTYPE_LONGDOUBLE : MTYPE_DOUBLE; flags |= FLAGS_FLOATG | FLAGS_UPPER; break; default: @@ -618,6 +622,10 @@ static int parsefmt(const char *format, iptr->val.dnum = va_arg(arglist, double); break; + case MTYPE_LONGDOUBLE: + iptr->val.dnum = (double)va_arg(arglist, long double); + break; + default: DEBUGASSERT(NULL); /* unexpected */ break; @@ -678,7 +686,7 @@ static bool out_double(void *userp, prec = maxprec - 1; if(width > 0 && prec <= width) maxprec -= width; - while(val >= 10.0) { + while(maxprec && (val >= 10.0)) { val /= 10; maxprec--; } @@ -699,7 +707,7 @@ static bool out_double(void *userp, else if(flags & FLAGS_FLOATG) *fptr++ = (char)((flags & FLAGS_UPPER) ? 'G' : 'g'); else - *fptr++ = 'f'; + *fptr++ = (flags & FLAGS_UPPER) ? 'F' : 'f'; *fptr = 0; /* and a final null-termination */ @@ -712,9 +720,7 @@ static bool out_double(void *userp, #ifdef _WIN32 curlx_win32_snprintf(work, BUFFSIZE, fmt, dnum); #else - /* !checksrc! disable BANNEDFUNC 1 */ - /* !checksrc! disable LONGLINE */ - /* NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) */ + /* !checksrc! disable BANNEDFUNC 2 */ snprintf(work, BUFFSIZE, fmt, dnum); #endif #ifdef CURL_HAVE_DIAG @@ -878,9 +884,9 @@ static bool out_string(void *userp, if(!str) { /* Write null string if there is space. */ - if(prec == -1 || prec >= (int)sizeof(nilstr) - 1) { + if(prec == -1 || prec >= (int)CURL_CSTRLEN(nilstr)) { str = nilstr; - len = sizeof(nilstr) - 1; + len = CURL_CSTRLEN(nilstr); /* Disable quotes around (nil) */ flags &= ~(unsigned int)FLAGS_ALT; } @@ -939,7 +945,7 @@ static bool out_pointer(void *userp, int width = p->width; int flags = p->flags; - width -= (int)(sizeof(nilstr) - 1); + width -= (int)CURL_CSTRLEN(nilstr); if(flags & FLAGS_LEFT) while(width-- > 0) OUTCHAR(' '); @@ -1065,6 +1071,7 @@ static int formatf(void *userp, /* untouched by format(), sent to the break; case MTYPE_DOUBLE: + case MTYPE_LONGDOUBLE: if(out_double(userp, stream, &p, iptr->val.dnum, work, &done)) return done; break; diff --git a/Utilities/cmcurl/lib/mqtt.c b/Utilities/cmcurl/lib/mqtt.c index 8482477b97..29e92ce5c1 100644 --- a/Utilities/cmcurl/lib/mqtt.c +++ b/Utilities/cmcurl/lib/mqtt.c @@ -276,7 +276,7 @@ static CURLcode mqtt_connect(struct Curl_easy *data) size_t start_user = 0; size_t start_pwd = 0; char client_id[MQTT_CLIENTID_LEN + 1] = "curl"; - const size_t clen = strlen("curl"); + const size_t clen = CURL_CSTRLEN("curl"); char *packet = NULL; /* extracting username from request */ @@ -347,8 +347,10 @@ static CURLcode mqtt_connect(struct Curl_easy *data) result = mqtt_send(data, packet, packetlen); end: - if(packet) + if(packet) { + curlx_memzero(packet, packetlen); curlx_free(packet); + } Curl_creds_unlink(&data->state.creds); return result; } @@ -438,7 +440,7 @@ static CURLcode mqtt_get_topic(struct Curl_easy *data, const char *path = data->state.up.path; CURLcode result = CURLE_URL_MALFORMAT; if(strlen(path) > 1) { - result = Curl_urldecode(path + 1, 0, topic, topiclen, REJECT_NADA); + result = Curl_urldecode(path + 1, 0, topic, topiclen, REJECT_CTRL); if(!result && (*topiclen > 0xffff)) { failf(data, "Too long MQTT topic"); result = CURLE_URL_MALFORMAT; @@ -627,7 +629,7 @@ static bool mqtt_decode_len(size_t *lenp, const unsigned char *buf, } #if defined(DEBUGBUILD) && defined(CURLVERBOSE) -static const char *statenames[] = { +static const char * const statenames[] = { "MQTT_FIRST", "MQTT_REMAINING_LENGTH", "MQTT_CONNACK", @@ -975,7 +977,7 @@ static CURLcode mqtts_connecting(struct Curl_easy *data, bool *done) result = Curl_conn_connect(data, FIRSTSOCKET, TRUE, done); if(result) - connclose(conn, "Failed TLS connection"); + connclose(conn); return result; } diff --git a/Utilities/cmcurl/lib/multi.c b/Utilities/cmcurl/lib/multi.c index d6ae111d8e..fbefa6f30d 100644 --- a/Utilities/cmcurl/lib/multi.c +++ b/Utilities/cmcurl/lib/multi.c @@ -49,13 +49,11 @@ #include "bufref.h" /* initial multi->xfers table size for a full multi */ -#define CURL_XFER_TABLE_SIZE 512 +#define CURL_XFER_TABLE_SIZE 128 -/* - CURL_SOCKET_HASH_TABLE_SIZE should be a prime number. Increasing it from 97 - to 911 takes on a 32-bit machine 4 x 804 = 3211 more bytes. Still, every - curl handle takes 6K memory, therefore this 3K are not significant. -*/ +/* CURL_SOCKET_HASH_TABLE_SIZE should be a prime number. Increasing it from 97 + to 911 takes on a 32-bit machine 4 x 804 = 3211 more bytes. Still, every + curl handle takes 6K memory, therefore this 3K are not significant. */ #ifndef CURL_SOCKET_HASH_TABLE_SIZE #define CURL_SOCKET_HASH_TABLE_SIZE 911 #endif @@ -72,29 +70,15 @@ #define CURL_TLS_SESSION_SIZE 25 #endif -#define CURL_MULTI_HANDLE 0x000bab1e - -#ifdef DEBUGBUILD -/* On a debug build, we want to fail hard on multi handles that - * are not NULL, but no longer have the MAGIC touch. This gives - * us early warning on things only discovered by valgrind otherwise. */ -#define GOOD_MULTI_HANDLE(x) \ - (((x) && (x)->magic == CURL_MULTI_HANDLE) ? TRUE : \ - (DEBUGASSERT(!(x)), FALSE)) -#else -#define GOOD_MULTI_HANDLE(x) \ - ((x) && (x)->magic == CURL_MULTI_HANDLE) -#endif - static void move_pending_to_connect(struct Curl_multi *multi, struct Curl_easy *data); static CURLMcode add_next_timeout(const struct curltime *pnow, struct Curl_multi *multi, - struct Curl_easy *d); + struct Curl_easy *data); static void multi_timeout(struct Curl_multi *multi, - struct curltime *expire_time, - long *timeout_ms); -static void process_pending_handles(struct Curl_multi *multi); + timediff_t *pexire_offset_us, + int *timeout_ms); +static void multi_schedule_pending(struct Curl_multi *multi); static void multi_xfer_bufs_free(struct Curl_multi *multi); #ifdef DEBUGBUILD static void multi_xfer_tbl_dump(struct Curl_multi *multi); @@ -159,7 +143,7 @@ static void mstate_enter_completed(struct Curl_easy *data, /* Important: reset the conn pointer so that we do not point to memory that could be freed anytime */ Curl_detach_connection(data); - Curl_expire_clear(data); /* stop all timers */ + Curl_expire_clear_all(data); /* stop all timers */ } /* always use this function to change state, to make debugging easier */ @@ -236,6 +220,8 @@ static void multi_addmsg(struct Curl_multi *multi, struct Curl_message *msg) Curl_llist_append(&multi->msglist, msg, &msg->list); } +static void multi_timeouts_init(struct Curl_easy *data); + struct Curl_multi *Curl_multi_handle(uint32_t xfer_table_size, size_t ev_hashsize, /* event hash */ size_t chashsize, /* connection hash */ @@ -247,7 +233,24 @@ struct Curl_multi *Curl_multi_handle(uint32_t xfer_table_size, if(!multi) return NULL; - multi->magic = CURL_MULTI_HANDLE; + multi->magic = CURLMULTI_MAGIC_NUMBER; + + /* Initialisation order is important here! + * easy_init() does a lazy check on curl_global_init() which sets + * up platform specific things we need. For example calling curlx_pnow() + * before this is not safe. */ + multi->admin = curl_easy_init(); + if(!multi->admin) { + curlx_free(multi); + return NULL; + } + multi->admin->multi = multi; + multi->admin->state.internal = TRUE; + + /* Now we can use curlx_* things safely */ + curlx_pnow(&multi->now); + Curl_timeouts_init(&multi->timeouts, &multi->now); + multi_timeouts_init(multi->admin); Curl_dnscache_init(&multi->dnscache, dnssize); Curl_mntfy_init(multi); @@ -268,6 +271,10 @@ struct Curl_multi *Curl_multi_handle(uint32_t xfer_table_size, multi->wakeup_pair[0] = CURL_SOCKET_BAD; multi->wakeup_pair[1] = CURL_SOCKET_BAD; #endif +#ifdef ENABLE_INTERNAL_WAKEUP + multi->wakeup_internal[0] = CURL_SOCKET_BAD; + multi->wakeup_internal[1] = CURL_SOCKET_BAD; +#endif if(Curl_mntfy_resize(multi) || Curl_uint32_bset_resize(&multi->process, xfer_table_size) || @@ -277,14 +284,6 @@ struct Curl_multi *Curl_multi_handle(uint32_t xfer_table_size, Curl_uint32_tbl_resize(&multi->xfers, xfer_table_size)) goto error; - multi->admin = curl_easy_init(); - if(!multi->admin) - goto error; - /* Initialize admin handle to operate inside this multi */ - multi->admin->multi = multi; - multi->admin->state.internal = TRUE; - Curl_llist_init(&multi->admin->state.timeoutlist, NULL); - #ifdef DEBUGBUILD if(getenv("CURL_DEBUG")) multi->admin->set.verbose = TRUE; @@ -295,7 +294,7 @@ struct Curl_multi *Curl_multi_handle(uint32_t xfer_table_size, if(Curl_cshutdn_init(&multi->cshutdn, multi)) goto error; - Curl_cpool_init(&multi->cpool, multi->admin, NULL, chashsize); + Curl_cpool_init(&multi->cpool, NULL, chashsize); #ifdef USE_SSL if(Curl_ssl_scache_create(sesssize, 2, &multi->ssl_scache)) @@ -315,6 +314,10 @@ struct Curl_multi *Curl_multi_handle(uint32_t xfer_table_size, if(Curl_wakeup_init(multi->wakeup_pair, TRUE) < 0) goto error; #endif +#ifdef ENABLE_INTERNAL_WAKEUP + if(Curl_wakeup_init(multi->wakeup_internal, TRUE) < 0) + goto error; +#endif if(Curl_probeipv6(multi)) goto error; @@ -340,7 +343,7 @@ error: Curl_multi_ev_cleanup(multi); Curl_hash_destroy(&multi->proto_hash); Curl_dnscache_destroy(&multi->dnscache); - Curl_cpool_destroy(&multi->cpool); + Curl_cpool_destroy(&multi->cpool, multi->admin); Curl_cshutdn_destroy(&multi->cshutdn, multi->admin); #ifdef USE_SSL Curl_ssl_scache_destroy(multi->ssl_scache); @@ -360,6 +363,9 @@ error: #ifdef ENABLE_WAKEUP Curl_wakeup_destroy(multi->wakeup_pair); #endif +#ifdef ENABLE_INTERNAL_WAKEUP + Curl_wakeup_destroy(multi->wakeup_internal); +#endif curlx_free(multi); return NULL; @@ -395,7 +401,7 @@ bool Curl_is_connecting(struct Curl_easy *data) static CURLMcode multi_assess_wakeup(struct Curl_multi *multi) { -#ifdef ENABLE_WAKEUP +#ifdef ENABLE_INTERNAL_WAKEUP if(multi->socket_cb) return Curl_multi_ev_assess_xfer(multi, multi->admin); #else @@ -417,25 +423,20 @@ static CURLMcode multi_xfers_add(struct Curl_multi *multi, if(capacity < max_capacity) { /* We want `multi->xfers` to have "sufficient" free rows, so that we do * not have to reuse the `mid` from a removed easy right away. - * Since uint_tbl and uint_bset are memory efficient, - * regard less than 25% free as insufficient. - * (for low capacities, e.g. multi_easy, 4 or less). */ + * Check if an 8th of the capacity is still free */ uint32_t used = Curl_uint32_tbl_count(&multi->xfers); uint32_t unused = capacity - used; - uint32_t min_unused = CURLMAX(capacity >> 2, 4); - if(unused <= min_unused) { + uint32_t min_unused = CURLMAX(capacity >> 3, 4); + if(unused < min_unused) { + /* Grow by 50% of current capacity, in range of [128, 2048], + * which means the table grows max by 16kb on 64-bit arch. */ + uint32_t growth = CURLMIN(CURLMAX(capacity >> 1, 128), 2048); /* Make sure the uint arithmetic here works on the corner * cases where we are close to max_capacity or UINT_MAX */ - if((min_unused >= max_capacity) || - ((max_capacity - min_unused) <= capacity) || - ((UINT_MAX - min_unused - 63) <= capacity)) { - new_size = max_capacity; /* can not be larger than this */ - } - else { - /* make it a 64 multiple, since our bitsets grow by that and - * small (easy_multi) grows to at least 64 on first resize. */ - new_size = (((used + min_unused) + 63) / 64) * 64; - } + if((max_capacity - growth) <= capacity) + new_size = max_capacity; + else + new_size = capacity + growth; } } @@ -463,28 +464,16 @@ static CURLMcode multi_xfers_add(struct Curl_multi *multi, return CURLM_OK; } -CURLMcode curl_multi_add_handle(CURLM *m, CURL *curl) +CURLMcode Curl_multi_add_handle(struct Curl_multi *multi, + struct Curl_easy *data) { CURLMcode mresult; - struct Curl_multi *multi = m; - struct Curl_easy *data = curl; - - /* First, make some basic checks that the CURLM handle is a good handle */ - if(!GOOD_MULTI_HANDLE(multi)) - return CURLM_BAD_HANDLE; - - /* Verify that we got a somewhat good easy handle too */ - if(!GOOD_EASY_HANDLE(data)) - return CURLM_BAD_EASY_HANDLE; /* Prevent users from adding same easy handle more than once and prevent adding to more than one multi stack */ if(data->multi) return CURLM_ADDED_ALREADY; - if(multi->in_callback) - return CURLM_RECURSIVE_API_CALL; - if(multi->dead) { /* a "dead" handle cannot get added transfers while any existing easy handles are still alive - but if there are none alive anymore, it is @@ -511,22 +500,22 @@ CURLMcode curl_multi_add_handle(CURLM *m, CURL *curl) if(multi_xfers_add(multi, data)) return CURLM_OUT_OF_MEMORY; - /* Initialize timeout list for this handle */ - Curl_llist_init(&data->state.timeoutlist, NULL); + /* Initialize timeouts for this handle */ + multi_timeouts_init(data); /* - * No failure allowed in this function beyond this point. No modification of - * easy nor multi handle allowed before this except for potential multi's - * connection pool growing which will not be undone in this function no - * matter what. + * No failure allowed in this function beyond this point. No modification + * of easy nor multi handle allowed before this except for potential + * multi's connection pool growing which will not be undone in this + * function no matter what. */ 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. */ + /* make the Curl_easy refer back to this multi handle - before + Curl_expire() is called. */ data->multi = multi; /* set the easy handle */ @@ -578,21 +567,22 @@ CURLMcode curl_multi_add_handle(CURLM *m, CURL *curl) return CURLM_OK; } -#if 0 -/* Debug-function, used like this: - * - * Curl_hash_print(&multi->sockhash, debug_print_sock_hash); - * - * Enable the hash print function first by editing hash.c - */ -static void debug_print_sock_hash(void *p) +CURLMcode curl_multi_add_handle(CURLM *m, CURL *curl) { - struct Curl_sh_entry *sh = (struct Curl_sh_entry *)p; + struct Curl_mapi_guard guard; + CURLMcode mresult; - curl_mfprintf(stderr, " [readers %u][writers %u]", - sh->readers, sh->writers); + if(CURL_MAPI_ENTER(&guard, m, multi_add_handle, &mresult)) { + struct Curl_easy *data = curl; + /* Verify that we got a somewhat good easy handle too */ + if(!GOOD_EASY_HANDLE(data)) + mresult = CURLM_BAD_EASY_HANDLE; + else + mresult = Curl_multi_add_handle(m, data); + } + CURL_MAPI_LEAVE(&guard); + return mresult; } -#endif struct multi_done_ctx { BIT(premature); @@ -656,33 +646,33 @@ static void multi_done_locked(struct connectdata *conn, } data->state.done = TRUE; /* called now! */ - data->state.recent_conn_id = conn->connection_id; Curl_dnscache_prune(data); if(multi_conn_should_close(conn, data, (bool)mdctx->premature)) { - CURL_TRC_M(data, "multi_done, terminating conn #%" FMT_OFF_T " to %s, " + CURL_TRC_M(data, "multi_done, terminating conn #%" FMT_OFF_T " to %s:%u, " "forbid=%d, close=%d, premature=%d, conn_multiplex=%d", - conn->connection_id, conn->destination, + conn->connection_id, conn->origin->user_hostname, + conn->origin->port, data->set.reuse_forbid, conn->bits.close, mdctx->premature, Curl_conn_is_multiplex(conn, FIRSTSOCKET)); - connclose(conn, "disconnecting"); - Curl_conn_terminate(data, conn, (bool)mdctx->premature); + connclose(conn); + Curl_conn_close(data, conn, (bool)mdctx->premature); } else if(!Curl_conn_get_max_concurrent(data, conn, FIRSTSOCKET)) { - CURL_TRC_M(data, "multi_done, conn #%" FMT_OFF_T " to %s was shutdown" + CURL_TRC_M(data, "multi_done, conn #%" FMT_OFF_T " to %s:%u was shutdown" " by server, not reusing", conn->connection_id, - conn->destination); - connclose(conn, "server shutdown"); - Curl_conn_terminate(data, conn, (bool)mdctx->premature); + conn->origin->user_hostname, conn->origin->port); + connclose(conn); + Curl_conn_close(data, conn, (bool)mdctx->premature); } else { /* the connection is no longer in use by any transfer */ if(Curl_cpool_conn_now_idle(data, conn)) { /* connection kept in the cpool */ - data->state.lastconnect_id = conn->connection_id; - infof(data, "Connection #%" FMT_OFF_T " to host %s left intact", - conn->connection_id, conn->destination); + infof(data, "Connection #%" FMT_OFF_T " to host %s:%u left intact", + conn->connection_id, conn->origin->user_hostname, + conn->origin->port); } else { /* connection was removed from the cpool and destroyed. */ @@ -751,7 +741,7 @@ static CURLcode multi_done(struct Curl_easy *data, if(conn) Curl_conn_ev_data_done(data, premature); - process_pending_handles(data->multi); /* connection / multiplex */ + multi_schedule_pending(data->multi); /* connection / multiplex */ if(!result) result = Curl_req_done(&data->req, data, premature); @@ -771,33 +761,14 @@ static CURLcode multi_done(struct Curl_easy *data, return result; } -static void close_connect_only(struct connectdata *conn, - struct Curl_easy *data, - void *userdata) +CURLMcode Curl_multi_remove_handle(struct Curl_multi *multi, + struct Curl_easy *data) { - (void)userdata; - (void)data; - if(conn->bits.connect_only) - connclose(conn, "Removing connect-only easy handle"); -} - -CURLMcode curl_multi_remove_handle(CURLM *m, CURL *curl) -{ - struct Curl_multi *multi = m; - struct Curl_easy *data = curl; + CURLMcode mresult; bool premature; struct Curl_llist_node *e; - CURLMcode mresult; uint32_t mid; - /* First, make some basic checks that the CURLM handle is a good handle */ - if(!GOOD_MULTI_HANDLE(multi)) - return CURLM_BAD_HANDLE; - - /* Verify that we got a somewhat good easy handle too */ - if(!GOOD_EASY_HANDLE(data)) - return CURLM_BAD_EASY_HANDLE; - /* Prevent users from trying to remove same easy handle more than once */ if(!data->multi) return CURLM_OK; /* it is already removed so let's say it is fine! */ @@ -815,38 +786,30 @@ CURLMcode curl_multi_remove_handle(CURLM *m, CURL *curl) return CURLM_INTERNAL_ERROR; } - if(multi->in_callback) - return CURLM_RECURSIVE_API_CALL; - premature = (data->mstate < MSTATE_COMPLETED); - /* If the 'state' is not INIT or COMPLETED, we might need to do something - nice to put the easy_handle in a good known state when this returns. */ - if(data->conn && - data->mstate > MSTATE_DO && - data->mstate < MSTATE_COMPLETED) { - /* Set connection owner so that the DONE function closes it. We can - safely do this here since connection is killed. */ - streamclose(data->conn, "Removed with partial response"); - } - if(data->conn) { + /* If the 'state' is not INIT or COMPLETED, we might need to do something + nice to put the easy_handle in a good known state when this returns. */ + if(premature && (data->mstate > MSTATE_DO)) + streamclose(data->conn); + /* multi_done() clears the association between the easy handle and the connection. - Note that this ignores the return code because there is nothing really useful to do with it anyway! */ (void)multi_done(data, data->result, premature); } - /* The timer must be shut down before data->multi is set to NULL, else the - timenode will remain in the splay tree after curl_easy_cleanup is + /* The timer must be shut down before data->multi is set to NULL, else + data's splaynode would remain in the splay tree after curl_easy_cleanup is called. Do it after multi_done() in case that sets another time! */ - Curl_expire_clear(data); + Curl_expire_clear_all(data); /* If in `msgsent`, it was deducted from `multi->xfers_alive` already. */ if(!Curl_uint32_bset_contains(&multi->msgsent, data->mid)) --multi->xfers_alive; + if(data->state.really_alive) { data->state.really_alive = FALSE; --multi->xfers_really_alive; @@ -864,27 +827,29 @@ CURLMcode curl_multi_remove_handle(CURLM *m, CURL *curl) /* Tell event handling that this transfer is definitely going away */ Curl_multi_ev_xfer_done(multi, data); - if(data->set.connect_only && !data->multi_easy) { - /* This removes a handle that was part the multi interface that used - CONNECT_ONLY, that connection is now left alive but since this handle - has bits.close set nothing can use that transfer anymore and it is - forbidden from reuse. This easy handle cannot find the connection - anymore once removed from the multi handle - - Better close the connection here, at once. - */ - struct connectdata *c; - curl_socket_t s; - s = Curl_getconnectinfo(data, &c); - if((s != CURL_SOCKET_BAD) && c) { - Curl_conn_terminate(data, c, TRUE); + if(data->set.connect_only) { + if(data->multi_easy) { + if(data->state.lastconnect_id != -1) { + /* Mark any connect-only connection for closure */ + struct connectdata *conn; + (void)Curl_getconnectinfo(data, &conn); + if(conn && conn->bits.connect_only) + connclose(conn); + } } - } + else { + /* This removes a handle that was part the multi interface that used + CONNECT_ONLY, that connection is now left alive but since this handle + has bits.close set nothing can use that connection anymore and it is + forbidden from reuse. This easy handle cannot find the connection + anymore once removed from the multi handle - if(data->state.lastconnect_id != -1) { - /* Mark any connect-only connection for closure */ - Curl_cpool_do_by_id(data, data->state.lastconnect_id, - close_connect_only, NULL); + Better close the connection here, at once. */ + struct connectdata *conn; + (void)Curl_getconnectinfo(data, &conn); + if(conn) + Curl_conn_close(data, conn, TRUE); + } } #ifdef USE_LIBPSL @@ -917,10 +882,8 @@ CURLMcode curl_multi_remove_handle(CURLM *m, CURL *curl) data->mid = UINT32_MAX; data->master_mid = UINT32_MAX; - /* NOTE NOTE NOTE - We do not touch the easy handle here! */ - process_pending_handles(multi); - + /* A pending transfer *might* be able to run now. */ + multi_schedule_pending(multi); mresult = Curl_update_timer(multi); if(mresult) return mresult; @@ -937,6 +900,22 @@ CURLMcode curl_multi_remove_handle(CURLM *m, CURL *curl) return CURLM_OK; } +CURLMcode curl_multi_remove_handle(CURLM *m, CURL *curl) +{ + struct Curl_mapi_guard guard; + CURLMcode mresult; + + if(CURL_MAPI_ENTER(&guard, m, multi_remove_handle, &mresult)) { + struct Curl_easy *data = curl; + if(!GOOD_EASY_HANDLE(data)) + mresult = CURLM_BAD_EASY_HANDLE; + else + mresult = Curl_multi_remove_handle(m, data); + } + CURL_MAPI_LEAVE(&guard); + return mresult; +} + /* Return TRUE if the application asked for multiplexing */ bool Curl_multiplex_wanted(const struct Curl_multi *multi) { @@ -967,16 +946,23 @@ void Curl_detach_connection(struct Curl_easy *data) /* * Curl_attach_connection() attaches this transfer to this connection. * - * This is the only function that should assign data->conn + * This is the only function that should assign data->conn. + * `matched == TRUE` means the transfer's properties match this + * connection and it is not a temporary attach for maintenance. */ void Curl_attach_connection(struct Curl_easy *data, - struct connectdata *conn) + struct connectdata *conn, + bool matched) { DEBUGASSERT(data); DEBUGASSERT(!data->conn); DEBUGASSERT(conn); DEBUGASSERT(conn->attached_xfers < UINT32_MAX); data->conn = conn; + if(matched) + data->state.lastconnect_id = conn->connection_id; + else + DEBUGASSERT(!data->mid); /* admin handle */ conn->attached_xfers++; /* all attached transfers must be from the same multi */ if(!conn->attached_multi) @@ -994,11 +980,10 @@ static CURLcode multi_adjust_pollset(struct Curl_easy *data, CURLcode result = CURLE_OK; if(ps->n) { - const struct curltime *pnow = Curl_pgrs_now(data); bool send_blocked, recv_blocked; - recv_blocked = (Curl_rlimit_avail(&data->progress.dl.rlimit, pnow) <= 0); - send_blocked = (Curl_rlimit_avail(&data->progress.ul.rlimit, pnow) <= 0); + recv_blocked = (Curl_rlimit_avail(&data->progress.dl.rlimit, NULL) <= 0); + send_blocked = (Curl_rlimit_avail(&data->progress.ul.rlimit, NULL) <= 0); if(send_blocked || recv_blocked) { int i; for(i = 0; i <= SECONDARYSOCKET; ++i) { @@ -1141,6 +1126,17 @@ static CURLcode mstate_perform_pollset(struct Curl_easy *data, return result; } +#ifdef CURLVERBOSE +static size_t multi_timeouts_count(struct expire_timers *timeouts) +{ + size_t n = 0; + uint8_t eid = timeouts->first; + for(; eid < EXPIRE_LAST; eid = timeouts->next[eid]) + ++n; + return n; +} +#endif + /* Initializes `poll_set` with the current socket poll actions needed * for transfer `data`. */ CURLMcode Curl_multi_pollset(struct Curl_easy *data, @@ -1149,14 +1145,14 @@ CURLMcode Curl_multi_pollset(struct Curl_easy *data, CURLcode result = CURLE_OK; Curl_pollset_reset(ps); -#ifdef ENABLE_WAKEUP +#ifdef ENABLE_INTERNAL_WAKEUP /* The admin handle always listens on the wakeup socket when there * are transfers alive. */ if(data->multi && (data == data->multi->admin) && data->multi->xfers_really_alive) { CURL_TRC_M(data, "adding wakeup, %u xfers really alive", data->multi->xfers_really_alive); - result = Curl_pollset_add_in(data, ps, data->multi->wakeup_pair[0]); + result = Curl_pollset_add_in(data, ps, data->multi->wakeup_internal[0]); } #endif /* If the transfer has no connection, this is fine. Happens when @@ -1172,10 +1168,7 @@ CURLMcode Curl_multi_pollset(struct Curl_easy *data, break; case MSTATE_CONNECTING: - if(data->conn && !data->conn->bits.dns_resolved) - result = Curl_resolv_pollset(data, ps); - if(!result) - result = mstate_connecting_pollset(data, ps); + result = mstate_connecting_pollset(data, ps); break; case MSTATE_PROTOCONNECT: @@ -1224,7 +1217,7 @@ CURLMcode Curl_multi_pollset(struct Curl_easy *data, #ifdef CURLVERBOSE if(CURL_TRC_M_is_verbose(data)) { - size_t timeout_count = Curl_llist_count(&data->state.timeoutlist); + size_t timeout_count = multi_timeouts_count(&data->state.timeouts); switch(ps->n) { case 0: CURL_TRC_M(data, "pollset[], timeouts=%zu, paused %d/%d (r/w)", @@ -1265,54 +1258,55 @@ CURLMcode curl_multi_fdset(CURLM *m, fd_set *read_fd_set, fd_set *write_fd_set, fd_set *exc_fd_set, int *max_fd) { - /* Scan through all the easy handles to get the file descriptors set. - Some easy handles may not have connected to the remote host yet, - and then we must make sure that is done. */ - int this_max_fd = -1; - struct Curl_multi *multi = m; - struct easy_pollset ps; - unsigned int i; - uint32_t mid; - (void)exc_fd_set; + struct Curl_mapi_guard guard; + CURLMcode mresult; - if(!GOOD_MULTI_HANDLE(multi)) - return CURLM_BAD_HANDLE; + if(CURL_MAPI_ENTER(&guard, m, multi_fdset, &mresult)) { + /* Scan through all the easy handles to get the file descriptors set. + Some easy handles may not have connected to the remote host yet, + and then we must make sure that is done. */ + struct Curl_multi *multi = m; + struct easy_pollset ps; + int this_max_fd = -1; + unsigned int i; + uint32_t mid; + (void)exc_fd_set; - if(multi->in_callback) - return CURLM_RECURSIVE_API_CALL; + Curl_pollset_init(&ps); + if(Curl_uint32_bset_first(&multi->process, &mid)) { + do { + struct Curl_easy *data = Curl_multi_get_easy(multi, mid); - Curl_pollset_init(&ps); - if(Curl_uint32_bset_first(&multi->process, &mid)) { - do { - struct Curl_easy *data = Curl_multi_get_easy(multi, mid); - - if(!data) { - DEBUGASSERT(0); - continue; - } - - Curl_multi_pollset(data, &ps); - for(i = 0; i < ps.n; i++) { - if(!FDSET_SOCK(ps.sockets[i])) - /* pretend it does not exist */ + if(!data) { + DEBUGASSERT(0); continue; - if(ps.actions[i] & CURL_POLL_IN) - FD_SET(ps.sockets[i], read_fd_set); - if(ps.actions[i] & CURL_POLL_OUT) - FD_SET(ps.sockets[i], write_fd_set); - if((int)ps.sockets[i] > this_max_fd) - this_max_fd = (int)ps.sockets[i]; - } - } while(Curl_uint32_bset_next(&multi->process, mid, &mid)); + } + + Curl_multi_pollset(data, &ps); + for(i = 0; i < ps.n; i++) { + if(!FDSET_SOCK(ps.sockets[i])) + /* pretend it does not exist */ + continue; + if(ps.actions[i] & CURL_POLL_IN) + FD_SET(ps.sockets[i], read_fd_set); + if(ps.actions[i] & CURL_POLL_OUT) + FD_SET(ps.sockets[i], write_fd_set); + if((int)ps.sockets[i] > this_max_fd) + this_max_fd = (int)ps.sockets[i]; + } + } while(Curl_uint32_bset_next(&multi->process, mid, &mid)); + } + + Curl_cshutdn_setfds(&multi->cshutdn, read_fd_set, write_fd_set, + &this_max_fd); + + *max_fd = this_max_fd; + Curl_pollset_cleanup(&ps); + + mresult = CURLM_OK; } - - Curl_cshutdn_setfds(&multi->cshutdn, multi->admin, - read_fd_set, write_fd_set, &this_max_fd); - - *max_fd = this_max_fd; - Curl_pollset_cleanup(&ps); - - return CURLM_OK; + CURL_MAPI_LEAVE(&guard); + return mresult; } CURLMcode curl_multi_waitfds(CURLM *m, @@ -1320,46 +1314,49 @@ CURLMcode curl_multi_waitfds(CURLM *m, unsigned int size, unsigned int *fd_count) { - struct Curl_waitfds cwfds; - CURLMcode mresult = CURLM_OK; - struct Curl_multi *multi = m; - struct easy_pollset ps; - unsigned int need = 0; - uint32_t mid; + struct Curl_mapi_guard guard; + CURLMcode mresult; - if(!ufds && (size || !fd_count)) - return CURLM_BAD_FUNCTION_ARGUMENT; + if(CURL_MAPI_ENTER(&guard, m, multi_waitfds, &mresult)) { + struct Curl_waitfds cwfds; + struct Curl_multi *multi = m; + struct easy_pollset ps; + unsigned int need = 0; + uint32_t mid; - if(!GOOD_MULTI_HANDLE(multi)) - return CURLM_BAD_HANDLE; + if(!ufds && (size || !fd_count)) { + mresult = CURLM_BAD_FUNCTION_ARGUMENT; + goto out; + } - if(multi->in_callback) - return CURLM_RECURSIVE_API_CALL; + Curl_pollset_init(&ps); + Curl_waitfds_init(&cwfds, ufds, size); + mresult = CURLM_OK; + if(Curl_uint32_bset_first(&multi->process, &mid)) { + do { + struct Curl_easy *data = Curl_multi_get_easy(multi, mid); + if(!data) { + DEBUGASSERT(0); + Curl_uint32_bset_remove(&multi->process, mid); + Curl_uint32_bset_remove(&multi->dirty, mid); + continue; + } + Curl_multi_pollset(data, &ps); + need += Curl_waitfds_add_ps(&cwfds, &ps); + } while(Curl_uint32_bset_next(&multi->process, mid, &mid)); + } - Curl_pollset_init(&ps); - Curl_waitfds_init(&cwfds, ufds, size); - if(Curl_uint32_bset_first(&multi->process, &mid)) { - do { - struct Curl_easy *data = Curl_multi_get_easy(multi, mid); - if(!data) { - DEBUGASSERT(0); - Curl_uint32_bset_remove(&multi->process, mid); - Curl_uint32_bset_remove(&multi->dirty, mid); - continue; - } - Curl_multi_pollset(data, &ps); - need += Curl_waitfds_add_ps(&cwfds, &ps); - } while(Curl_uint32_bset_next(&multi->process, mid, &mid)); + need += Curl_cshutdn_add_waitfds(&multi->cshutdn, &cwfds); + + if(need != cwfds.n && ufds) + mresult = CURLM_OUT_OF_MEMORY; + + if(fd_count) + *fd_count = need; + Curl_pollset_cleanup(&ps); } - - need += Curl_cshutdn_add_waitfds(&multi->cshutdn, multi->admin, &cwfds); - - if(need != cwfds.n && ufds) - mresult = CURLM_OUT_OF_MEMORY; - - if(fd_count) - *fd_count = need; - Curl_pollset_cleanup(&ps); +out: + CURL_MAPI_LEAVE(&guard); return mresult; } @@ -1406,7 +1403,7 @@ static CURLMcode multi_winsock_select(struct Curl_multi *multi, reset_socket_fdwrite(cpfds->pfds[i].fd); } if(mask) { - if(WSAEventSelect(cpfds->pfds[i].fd, multi->wsa_event, mask) != 0) { + if(WSAEventSelect(cpfds->pfds[i].fd, multi->wsa_event, mask)) { mresult = CURLM_OUT_OF_MEMORY; goto out; } @@ -1549,8 +1546,7 @@ static CURLMcode multi_wait(struct Curl_multi *multi, bool extrawait) /* when no socket, wait */ { size_t i; - struct curltime expire_time; - long timeout_internal; + int timeout_internal; int nevents = 0; struct easy_pollset ps; struct pollfd a_few_on_stack[NUM_POLLS_ON_STACK]; @@ -1563,12 +1559,6 @@ static CURLMcode multi_wait(struct Curl_multi *multi, int wakeup_idx = -1; #endif - if(!GOOD_MULTI_HANDLE(multi)) - return CURLM_BAD_HANDLE; - - if(multi->in_callback) - return CURLM_RECURSIVE_API_CALL; - if(timeout_ms < 0) return CURLM_BAD_FUNCTION_ARGUMENT; @@ -1593,7 +1583,7 @@ static CURLMcode multi_wait(struct Curl_multi *multi, } while(Curl_uint32_bset_next(&multi->process, mid, &mid)); } - if(Curl_cshutdn_add_pollfds(&multi->cshutdn, multi->admin, &cpfds)) { + if(Curl_cshutdn_add_pollfds(&multi->cshutdn, &cpfds)) { mresult = CURLM_OUT_OF_MEMORY; goto out; } @@ -1634,12 +1624,12 @@ static CURLMcode multi_wait(struct Curl_multi *multi, * Use the shorter one of the internal and the caller requested timeout. * If we are called with `!extrawait` and multi_timeout() reports no * timeouts exist, do not wait. */ - multi_timeout(multi, &expire_time, &timeout_internal); - if((timeout_internal >= 0) && (timeout_internal < (long)timeout_ms)) - timeout_ms = (int)timeout_internal; + multi_timeout(multi, NULL, &timeout_internal); + if((timeout_internal >= 0) && (timeout_internal < timeout_ms)) + timeout_ms = timeout_internal; if(data) - CURL_TRC_M(data, "multi_wait(fds=%u, timeout=%d) tinternal=%ld", + CURL_TRC_M(data, "multi_wait(fds=%u, timeout=%d) tinternal=%d", cpfds.n, timeout_ms, timeout_internal); #ifdef USE_WINSOCK @@ -1676,7 +1666,14 @@ CURLMcode curl_multi_wait(CURLM *m, int timeout_ms, int *ret) { - return multi_wait(m, extra_fds, extra_nfds, timeout_ms, ret, FALSE); + struct Curl_mapi_guard guard; + CURLMcode mresult; + + if(CURL_MAPI_ENTER(&guard, m, multi_wait, &mresult)) { + mresult = multi_wait(m, extra_fds, extra_nfds, timeout_ms, ret, FALSE); + } + CURL_MAPI_LEAVE(&guard); + return mresult; } CURLMcode curl_multi_poll(CURLM *m, @@ -1685,7 +1682,14 @@ CURLMcode curl_multi_poll(CURLM *m, int timeout_ms, int *ret) { - return multi_wait(m, extra_fds, extra_nfds, timeout_ms, ret, TRUE); + struct Curl_mapi_guard guard; + CURLMcode mresult; + + if(CURL_MAPI_ENTER(&guard, m, multi_poll, &mresult)) { + mresult = multi_wait(m, extra_fds, extra_nfds, timeout_ms, ret, TRUE); + } + CURL_MAPI_LEAVE(&guard); + return mresult; } CURLMcode curl_multi_wakeup(CURLM *m) @@ -1714,6 +1718,18 @@ CURLMcode curl_multi_wakeup(CURLM *m) return mresult; } +#ifdef ENABLE_INTERNAL_WAKEUP +void Curl_multi_wakeup_internal(struct Curl_multi *multi) +{ + /* This is expected to be invocable from another thread which + * does NOT outlive the multi handle. Check for sanity. */ + if(GOOD_MULTI_HANDLE(multi)) + Curl_wakeup_signal(multi->wakeup_internal); + else + DEBUGASSERT(0); +} +#endif + /* * multi_ischanged() is called * @@ -1752,24 +1768,21 @@ CURLMcode Curl_multi_add_perform(struct Curl_multi *multi, { CURLMcode mresult; - if(multi->in_callback) - return CURLM_RECURSIVE_API_CALL; - - mresult = curl_multi_add_handle(multi, data); + mresult = Curl_multi_add_handle(multi, data); if(!mresult) { CURLcode result; /* pass in NULL for 'conn' here since we do not want to init the connection, only this transfer */ - result = Curl_init_do(data, NULL); + result = Curl_init_transfer(data, NULL); if(result) { - curl_multi_remove_handle(multi, data); + Curl_multi_remove_handle(multi, data); return CURLM_INTERNAL_ERROR; } /* take this handle to the perform state right away */ multistate(data, MSTATE_PERFORMING); - Curl_attach_connection(data, conn); + Curl_attach_connection(data, conn, TRUE); CURL_REQ_SET_RECV(data); } return mresult; @@ -1815,44 +1828,41 @@ static CURLcode multi_do_more(struct Curl_easy *data, domore *complete) * Check whether a timeout occurred, and handle it if it did */ static bool multi_handle_timeout(struct Curl_easy *data, + const struct curltime *pnow, bool *stream_error, CURLcode *result) { timediff_t timeout_ms; - timeout_ms = Curl_timeleft_ms(data); + timeout_ms = Curl_timeleft_now_ms(data, pnow); if(timeout_ms < 0) { /* Handle timed out */ - struct curltime since; - if(Curl_is_connecting(data)) - since = data->progress.t_startsingle; - else - since = data->progress.t_startop; + timerid base_timer = Curl_is_connecting(data) ? + TIMER_STARTSINGLE : TIMER_STARTOP; + timediff_t elapsed_ms = Curl_pgrs_since_ms(data, NULL, base_timer); if(data->mstate == MSTATE_CONNECTING) failf(data, "%s timed out after %" FMT_TIMEDIFF_T " milliseconds", data->conn->bits.dns_resolved ? "Connection" : "Resolving", - curlx_ptimediff_ms(Curl_pgrs_now(data), &since)); + elapsed_ms); else { struct SingleRequest *k = &data->req; if(k->size != -1) { failf(data, "Operation timed out after %" FMT_TIMEDIFF_T " milliseconds with %" FMT_OFF_T " out of %" FMT_OFF_T " bytes received", - curlx_ptimediff_ms(Curl_pgrs_now(data), &since), - k->bytecount, k->size); + elapsed_ms, k->bytecount, k->size); } else { failf(data, "Operation timed out after %" FMT_TIMEDIFF_T " milliseconds with %" FMT_OFF_T " bytes received", - curlx_ptimediff_ms(Curl_pgrs_now(data), &since), - k->bytecount); + elapsed_ms, k->bytecount); } } *result = CURLE_OPERATION_TIMEDOUT; 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"); + streamclose(data->conn); *stream_error = TRUE; } (void)multi_done(data, *result, TRUE); @@ -1936,11 +1946,6 @@ static CURLcode protocol_connect(struct Curl_easy *data, bool *protocol_done) return CURLE_OK; } -static void set_in_callback(struct Curl_multi *multi, bool value) -{ - multi->in_callback = value; -} - /* * posttransfer() is called immediately after a transfer ends */ @@ -1992,7 +1997,7 @@ static CURLcode mspeed_check(struct Curl_easy *data) if(data->mstate != MSTATE_RATELIMITING) { multistate(data, MSTATE_RATELIMITING); } - Curl_expire(data, CURLMAX(send_ms, recv_ms), EXPIRE_TOOFAST); + Curl_expire_set(data, EXPIRE_TOOFAST, CURLMAX(send_ms, recv_ms), pnow); Curl_multi_clear_dirty(data); CURL_TRC_M(data, "[RLIMIT] waiting %" FMT_TIMEDIFF_T "ms", CURLMAX(send_ms, recv_ms)); @@ -2007,7 +2012,7 @@ static CURLcode mspeed_check(struct Curl_easy *data) timediff_t next_ms = CURLMIN(send_ms, recv_ms); if(!next_ms) next_ms = CURLMAX(send_ms, recv_ms); - Curl_expire(data, next_ms, EXPIRE_TOOFAST); + Curl_expire_set(data, EXPIRE_TOOFAST, next_ms, pnow); CURL_TRC_M(data, "[RLIMIT] next token update in %" FMT_TIMEDIFF_T "ms", next_ms); } @@ -2062,7 +2067,7 @@ static CURLMcode multistate_performing(struct Curl_easy *data, if(!ret) { infof(data, "Downgrades to HTTP/1.1"); - streamclose(data->conn, "Disconnect HTTP/2 for HTTP/1"); + streamclose(data->conn); data->state.http_neg.wanted = CURL_HTTP_V1x; data->state.http_neg.allowed = CURL_HTTP_V1x; /* clear the error message bit too as we ignore the one we got */ @@ -2097,7 +2102,7 @@ static CURLMcode multistate_performing(struct Curl_easy *data, if(!(data->conn->scheme->flags & PROTOPT_DUAL) && result != CURLE_HTTP2_STREAM) - streamclose(data->conn, "Transfer returned error"); + streamclose(data->conn); multi_posttransfer(data); multi_done(data, result, TRUE); @@ -2167,16 +2172,17 @@ static CURLMcode multistate_do(struct Curl_easy *data, CURLMcode mresult = CURLM_OK; CURLcode result = CURLE_OK; if(data->set.fprereq) { + struct Curl_mapi_guard guard; int prereq_rc; /* call the prerequest callback function */ - Curl_set_in_callback(data, TRUE); + CURL_CBAPI_START(&guard, data, easy_fprereq); prereq_rc = data->set.fprereq(data->set.prereq_userp, data->info.primary.remote_ip, data->info.primary.local_ip, data->info.primary.remote_port, data->info.primary.local_port); - Curl_set_in_callback(data, FALSE); + CURL_CBAPI_END(&guard); if(prereq_rc != CURL_PREREQFUNC_OK) { failf(data, "operation aborted by pre-request callback"); /* failure in pre-request callback - do not do any other processing */ @@ -2307,7 +2313,7 @@ static CURLMcode multistate_ratelimiting(struct Curl_easy *data, if(result) { if(!(data->conn->scheme->flags & PROTOPT_DUAL) && result != CURLE_HTTP2_STREAM) - streamclose(data->conn, "Transfer returned error"); + streamclose(data->conn); multi_posttransfer(data); multi_done(data, result, TRUE); @@ -2341,7 +2347,7 @@ static CURLMcode multistate_connect(struct Curl_multi *multi, return mresult; } else - process_pending_handles(data->multi); + multi_schedule_pending(data->multi); if(!result) { /* after the connect has been sent off, go WAITCONNECT unless the @@ -2353,7 +2359,7 @@ static CURLMcode multistate_connect(struct Curl_multi *multi, if(!data->conn->bits.reuse && Curl_conn_is_multiplex(data->conn, FIRSTSOCKET)) { /* new connection, can multiplex, wake pending handles */ - process_pending_handles(data->multi); + multi_schedule_pending(data->multi); } multistate(data, MSTATE_PROTOCONNECT); } @@ -2368,6 +2374,7 @@ static CURLMcode multistate_connect(struct Curl_multi *multi, /* returns the possibly updated result */ static CURLcode is_finished(struct Curl_multi *multi, struct Curl_easy *data, + const struct curltime *pnow, bool stream_error, CURLcode result) { @@ -2382,7 +2389,7 @@ static CURLcode is_finished(struct Curl_multi *multi, connection detach and termination happens only here */ /* Check if we can move pending requests to send pipe */ - process_pending_handles(multi); /* connection */ + multi_schedule_pending(multi); /* connection */ if(data->conn) { if(stream_error) { @@ -2394,7 +2401,7 @@ static CURLcode is_finished(struct Curl_multi *multi, We do not have to do this in every case block above where a failure is detected */ Curl_detach_connection(data); - Curl_conn_terminate(data, conn, dead_connection); + Curl_conn_close(data, conn, dead_connection); } } else if(data->mstate == MSTATE_CONNECT) { @@ -2408,11 +2415,11 @@ static CURLcode is_finished(struct Curl_multi *multi, } /* if there is still a connection to use, call the progress function */ else if(data->conn && Curl_conn_is_connected(data->conn, FIRSTSOCKET)) { - result = Curl_pgrsUpdate(data); + result = Curl_pgrsUpdateX(data, pnow); if(result) { /* aborted due to progress callback return code must close the connection */ - streamclose(data->conn, "Aborted by callback"); + streamclose(data->conn); /* if not yet in DONE state, go there, otherwise COMPLETED */ multistate(data, (data->mstate < MSTATE_DONE) ? @@ -2429,20 +2436,16 @@ static void handle_completed(struct Curl_multi *multi, CURLcode result) { if(data->master_mid != UINT32_MAX) { - /* A sub transfer, not for msgsent to application */ - struct Curl_easy *mdata; + /* A sub transfer, not for msgsent to application. Is anyone still + * interested in processing its results? */ + if(data->sub_xfer_done) { + struct Curl_easy *master = Curl_multi_get_easy(multi, data->master_mid); - CURL_TRC_M(data, "sub xfer done for master %u", data->master_mid); - mdata = Curl_multi_get_easy(multi, data->master_mid); - if(mdata) { - if(mdata->sub_xfer_done) - mdata->sub_xfer_done(mdata, data, result); + CURL_TRC_M(data, "sub xfer done for master %u", data->master_mid); + if(master) + data->sub_xfer_done(data, master, result); else - CURL_TRC_M(data, "master easy %u without sub_xfer_done callback.", - data->master_mid); - } - else { - CURL_TRC_M(data, "master easy %u already gone.", data->master_mid); + CURL_TRC_M(data, "master easy %u already gone.", data->master_mid); } } else { @@ -2500,14 +2503,16 @@ static CURLMcode multistate_init(struct Curl_easy *data, CURLcode *result) static CURLMcode multistate_setup(struct Curl_easy *data) { - Curl_pgrsTime(data, TIMER_STARTSINGLE); + const struct curltime *pnow = Curl_pgrs_now(data); + Curl_pgrsTimeWas(data, TIMER_STARTSINGLE, *pnow); if(data->set.timeout) - Curl_expire(data, data->set.timeout, EXPIRE_TIMEOUT); + Curl_expire_set(data, EXPIRE_TIMEOUT, data->set.timeout, pnow); 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); + Curl_expire_set(data, EXPIRE_CONNECTTIMEOUT, + data->set.connecttimeout, pnow); multistate(data, MSTATE_CONNECT); return CURLM_CALL_MULTI_PERFORM; @@ -2530,7 +2535,7 @@ static CURLMcode multistate_connecting(struct Curl_easy *data, if(!data->conn->bits.reuse && Curl_conn_is_multiplex(data->conn, FIRSTSOCKET)) { /* new connection, can multiplex, wake pending handles */ - process_pending_handles(data->multi); + multi_schedule_pending(data->multi); } multistate(data, MSTATE_PROTOCONNECT); return CURLM_CALL_MULTI_PERFORM; @@ -2666,13 +2671,21 @@ static CURLMcode multistate_did(struct Curl_multi *multi, DEBUGASSERT(data->conn); if(data->conn->bits.multiplex) /* Check if we can move pending requests to send pipe */ - process_pending_handles(multi); /* multiplexed */ + multi_schedule_pending(multi); /* multiplexed */ /* Only perform the transfer if there is a good socket to work with. Having both BAD is a signal to skip immediately to DONE */ if(CONN_SOCK_IDX_VALID(data->conn->recv_idx) || - CONN_SOCK_IDX_VALID(data->conn->send_idx)) + CONN_SOCK_IDX_VALID(data->conn->send_idx)) { multistate(data, MSTATE_PERFORMING); + /* Do not return CURLM_CALL_MULTI_PERFORM to give other transfers + * a chance to send off their requests. + * Note: Some SFTP handlers do not seem to like this. + * Restrict it to HTTP families. */ + return ((multi->xfers_alive > 1) && + (data->conn->scheme->protocol & PROTO_FAMILY_HTTP)) ? + CURLM_OK : CURLM_CALL_MULTI_PERFORM; + } else { #ifndef CURL_DISABLE_FTP if(data->state.wildcardmatch && @@ -2681,8 +2694,8 @@ static CURLMcode multistate_did(struct Curl_multi *multi, } #endif multistate(data, MSTATE_DONE); + return CURLM_CALL_MULTI_PERFORM; } - return CURLM_CALL_MULTI_PERFORM; } static CURLMcode multistate_done(struct Curl_easy *data, CURLcode *presult) @@ -2718,11 +2731,9 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, struct Curl_easy *data, struct Curl_sigpipe_ctx *sigpipe_ctx) { - CURLMcode mresult; + CURLMcode mresult = CURLM_OK; CURLcode result = CURLE_OK; - - if(!GOOD_EASY_HANDLE(data)) - return CURLM_BAD_EASY_HANDLE; + const struct curltime *pnow = NULL; if(multi->dead) { /* a multi-level callback returned error before, meaning every individual @@ -2740,16 +2751,16 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, Curl_uint32_bset_remove(&multi->dirty, data->mid); if(data == multi->admin) { -#ifdef ENABLE_WAKEUP +#ifdef ENABLE_INTERNAL_WAKEUP /* Consume any pending wakeup signals before processing. * This is necessary for event based processing. See #21547 */ - (void)Curl_wakeup_consume(multi->wakeup_pair, TRUE); + (void)Curl_wakeup_consume(multi->wakeup_internal, TRUE); #endif #ifdef USE_RESOLV_THREADED Curl_async_thrdd_multi_process(multi); #endif - Curl_cshutdn_perform(&multi->cshutdn, multi->admin, sigpipe_ctx); - return CURLM_OK; + Curl_cshutdn_perform(&multi->cshutdn, sigpipe_ctx); + goto out; } sigpipe_apply(data, sigpipe_ctx); @@ -2758,26 +2769,32 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, (HTTP/2), or the full connection for older protocols */ bool stream_error = FALSE; mresult = CURLM_OK; + pnow = NULL; if(multi_ischanged(multi, TRUE)) { CURL_TRC_M(data, "multi changed, check CONNECT_PEND queue"); - process_pending_handles(multi); /* multiplexed */ + multi_schedule_pending(multi); /* multiplexed */ } if(data->mstate > MSTATE_CONNECT && data->mstate < MSTATE_COMPLETED) { /* Make sure we set the connection's current owner */ DEBUGASSERT(data->conn); - if(!data->conn) - return CURLM_INTERNAL_ERROR; + if(!data->conn) { + mresult = CURLM_INTERNAL_ERROR; + goto out; + } } /* 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, &stream_error, &result)) - /* Skip the statemachine and go directly to error handling section. */ - goto statemachine_end; + if((data->mstate >= MSTATE_CONNECT) && (data->mstate < MSTATE_COMPLETED)) { + pnow = Curl_pgrs_now(data); + if(multi_handle_timeout(data, pnow, &stream_error, &result)) + /* Skip the statemachine and go directly to error handling section. */ + goto statemachine_end; + pnow = NULL; + } switch(data->mstate) { case MSTATE_INIT: @@ -2851,7 +2868,8 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, break; default: - return CURLM_INTERNAL_ERROR; + mresult = CURLM_INTERNAL_ERROR; + goto out; } if(data->mstate >= MSTATE_CONNECT && @@ -2864,22 +2882,26 @@ 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, &stream_error, &result); + pnow = Curl_pgrs_now(data); + multi_handle_timeout(data, pnow, &stream_error, &result); } statemachine_end: - - result = is_finished(multi, data, stream_error, result); + if(!pnow) + pnow = Curl_pgrs_now(data); + result = is_finished(multi, data, pnow, stream_error, result); if(result) mresult = CURLM_CALL_MULTI_PERFORM; if(MSTATE_COMPLETED == data->mstate) { handle_completed(multi, data, result); - return CURLM_OK; + mresult = CURLM_OK; + goto out; } } while((mresult == CURLM_CALL_MULTI_PERFORM) || multi_ischanged(multi, FALSE)); +out: data->result = result; return mresult; } @@ -2892,12 +2914,6 @@ static CURLMcode multi_perform(struct Curl_multi *multi, uint32_t mid; struct Curl_sigpipe_ctx sigpipe_ctx; - if(multi->in_callback) - return CURLM_RECURSIVE_API_CALL; - - if(multi->in_ntfy_callback) - return CURLM_RECURSIVE_API_CALL; - sigpipe_init(&sigpipe_ctx); if(Curl_uint32_bset_first(&multi->process, &mid)) { @@ -2920,7 +2936,7 @@ static CURLMcode multi_perform(struct Curl_multi *multi, sigpipe_restore(&sigpipe_ctx); if(multi_ischanged(multi, TRUE)) - process_pending_handles(multi); + multi_schedule_pending(multi); if(!returncode && CURL_MNTFY_HAS_ENTRIES(multi)) returncode = Curl_mntfy_dispatch_all(multi); @@ -2935,28 +2951,27 @@ static CURLMcode multi_perform(struct Curl_multi *multi, * then and then we risk this loop to remove timers that actually have not * been handled! */ - if(multi->timetree) { - struct Curl_tree *t = NULL; - do { - multi->timetree = Curl_splaygetbest(&start, multi->timetree, &t); - if(t) { - /* the removed may have another timeout in queue */ - struct Curl_easy *data = Curl_splayget(t); - (void)add_next_timeout(&start, multi, data); - if(data->mstate == MSTATE_PENDING) { - bool stream_unused; - CURLcode result_unused; - if(multi_handle_timeout(data, &stream_unused, &result_unused)) { - infof(data, "PENDING handle timeout"); - move_pending_to_connect(multi, data); - } - } + while(Curl_timeouts_remove_expired(&multi->timeouts, &start, &mid)) { + /* the removed may have another timeout in queue */ + struct Curl_easy *data = Curl_multi_get_easy(multi, mid); + if(!data) { + DEBUGASSERT(0); + continue; + } + (void)add_next_timeout(&start, multi, data); + if(data->mstate == MSTATE_PENDING) { + bool stream_unused; + CURLcode result_unused; + if(multi_handle_timeout(data, multi_now(multi), + &stream_unused, &result_unused)) { + infof(data, "PENDING handle timeout"); + move_pending_to_connect(multi, data); } - } while(t); + } } if(running_handles) { - unsigned int running = Curl_multi_xfers_running(multi); + uint32_t running = Curl_multi_xfers_running(multi); *running_handles = (running < INT_MAX) ? (int)running : INT_MAX; } @@ -2968,32 +2983,35 @@ static CURLMcode multi_perform(struct Curl_multi *multi, CURLMcode curl_multi_perform(CURLM *m, int *running_handles) { - struct Curl_multi *multi = m; + struct Curl_mapi_guard guard; + CURLMcode mresult; - if(!GOOD_MULTI_HANDLE(multi)) - return CURLM_BAD_HANDLE; - - return multi_perform(multi, running_handles); + if(CURL_MAPI_ENTER(&guard, m, multi_perform, &mresult)) { + mresult = multi_perform(m, running_handles); + } + CURL_MAPI_LEAVE(&guard); + return mresult; } CURLMcode curl_multi_cleanup(CURLM *m) { - struct Curl_multi *multi = m; - if(GOOD_MULTI_HANDLE(multi)) { + struct Curl_mapi_guard guard; + CURLMcode mresult; + + if(CURL_MAPI_ENTER(&guard, m, multi_cleanup, &mresult)) { + struct Curl_multi *multi = m; void *entry; uint32_t mid; - if(multi->in_callback) - return CURLM_RECURSIVE_API_CALL; - if(multi->in_ntfy_callback) - return CURLM_RECURSIVE_API_CALL; /* First remove all remaining easy handles, * close internal ones. admin handle is special */ if(Curl_uint32_tbl_first(&multi->xfers, &mid, &entry)) { do { struct Curl_easy *data = entry; - if(!GOOD_EASY_HANDLE(data)) - return CURLM_BAD_HANDLE; + if(!GOOD_EASY_HANDLE(data)) { + mresult = CURLM_BAD_HANDLE; + goto out; + } #ifdef DEBUGBUILD if(mid != data->mid) { @@ -3026,7 +3044,7 @@ CURLMcode curl_multi_cleanup(CURLM *m) #ifdef USE_RESOLV_THREADED Curl_async_thrdd_multi_destroy(multi, !multi->quick_exit); #endif - Curl_cpool_destroy(&multi->cpool); + Curl_cpool_destroy(&multi->cpool, multi->admin); Curl_cshutdn_destroy(&multi->cshutdn, multi->admin); if(multi->admin) { CURL_TRC_M(multi->admin, "multi_cleanup, closing admin handle, done"); @@ -3051,6 +3069,9 @@ CURLMcode curl_multi_cleanup(CURLM *m) #ifdef ENABLE_WAKEUP Curl_wakeup_destroy(multi->wakeup_pair); #endif +#ifdef ENABLE_INTERNAL_WAKEUP + Curl_wakeup_destroy(multi->wakeup_internal); +#endif multi_xfer_bufs_free(multi); Curl_mntfy_cleanup(multi); @@ -3065,11 +3086,14 @@ CURLMcode curl_multi_cleanup(CURLM *m) Curl_uint32_bset_destroy(&multi->pending); Curl_uint32_bset_destroy(&multi->msgsent); Curl_uint32_tbl_destroy(&multi->xfers); + curlx_memzero(multi, sizeof(*multi)); curlx_free(multi); - return CURLM_OK; + mresult = CURLM_OK; } - return CURLM_BAD_HANDLE; +out: + CURL_MAPI_LEAVE(&guard); + return mresult; } /* @@ -3084,30 +3108,32 @@ CURLMcode curl_multi_cleanup(CURLM *m) CURLMsg *curl_multi_info_read(CURLM *m, int *msgs_in_queue) { - struct Curl_message *msg; - struct Curl_multi *multi = m; + struct Curl_mapi_guard guard; + CURLMsg *msg_result = NULL; *msgs_in_queue = 0; /* default to none */ + if(CURL_MAPI_ENTER(&guard, m, multi_info_read, NULL)) { + struct Curl_multi *multi = m; + if(Curl_llist_count(&multi->msglist)) { + /* there is one or more messages in the list */ + struct Curl_llist_node *e; + struct Curl_message *msg; - if(GOOD_MULTI_HANDLE(multi) && - !multi->in_callback && - Curl_llist_count(&multi->msglist)) { - /* there is one or more messages in the list */ - struct Curl_llist_node *e; + /* extract the head of the list to return */ + e = Curl_llist_head(&multi->msglist); - /* extract the head of the list to return */ - e = Curl_llist_head(&multi->msglist); + msg = Curl_node_elem(e); - msg = Curl_node_elem(e); + /* remove the extracted entry */ + Curl_node_remove(e); - /* remove the extracted entry */ - Curl_node_remove(e); + *msgs_in_queue = curlx_uztosi(Curl_llist_count(&multi->msglist)); - *msgs_in_queue = curlx_uztosi(Curl_llist_count(&multi->msglist)); - - return &msg->extmsg; + msg_result = &msg->extmsg; + } } - return NULL; + CURL_MAPI_LEAVE(&guard); + return msg_result; } void Curl_multi_will_close(struct Curl_easy *data, curl_socket_t s) @@ -3121,57 +3147,41 @@ void Curl_multi_will_close(struct Curl_easy *data, curl_socket_t s) } } +static void multi_timeouts_init(struct Curl_easy *data) +{ + data->state.timeouts.first = EXPIRE_LAST; + data->state.timeouts.splaynode.registered = FALSE; +} + /* - * add_next_timeout() - * * Each Curl_easy has a list of timeouts. The add_next_timeout() is called * when it has been removed from the splay tree because the timeout has * expired. This function is then to advance in the list to pick the next * timeout to use (skip the already expired ones) and add this node back to * the splay tree again. * - * The splay tree only has each sessionhandle as a single node and the nearest + * The splay tree only has each Curl_easy as a single node and the nearest * timeout is used to sort it on. */ static CURLMcode add_next_timeout(const struct curltime *pnow, struct Curl_multi *multi, - struct Curl_easy *d) + struct Curl_easy *data) { - struct curltime *tv = &d->state.expiretime; - struct Curl_llist *list = &d->state.timeoutlist; - struct Curl_llist_node *e; + struct expire_timers *timeouts = &data->state.timeouts; + timediff_t now_us = Curl_timeouts_offset_us(&multi->timeouts, pnow); - /* move over the timeout list for this specific handle and remove all - timeouts that are now passed tense and store the next pending - timeout in *tv */ - for(e = Curl_llist_head(list); e;) { - struct Curl_llist_node *n = Curl_node_next(e); - struct time_node *node = Curl_node_elem(e); - timediff_t diff = curlx_ptimediff_us(&node->time, pnow); - if(diff <= 0) - /* remove outdated entry */ - Curl_node_remove(e); - else - /* the list is sorted so get out on the first mismatch */ + while(timeouts->first < EXPIRE_LAST) { + if(timeouts->offset_us[timeouts->first] <= now_us) /* already expired */ + timeouts->first = timeouts->next[timeouts->first]; + else /* timeouts are sorted, first is first in the future now */ break; - e = n; } - e = Curl_llist_head(list); - if(!e) { - /* clear the expire times within the handles that we remove from the - splay tree */ - tv->tv_sec = 0; - tv->tv_usec = 0; - } - else { - struct time_node *node = Curl_node_elem(e); - /* copy the first entry to 'tv' */ - memcpy(tv, &node->time, sizeof(*tv)); + if(timeouts->first < EXPIRE_LAST) { /* Insert this node again into the splay. Keep the timer in the list in case we need to recompute future timers. */ - multi->timetree = Curl_splayinsert(tv, multi->timetree, - &d->state.timenode); + Curl_timeouts_add(&multi->timeouts, data, + timeouts->offset_us[timeouts->first]); } return CURLM_OK; } @@ -3180,29 +3190,25 @@ static void multi_mark_expired_as_dirty(struct Curl_multi *multi, const struct curltime *ts) { struct Curl_easy *data = NULL; - struct Curl_tree *t = NULL; + uint32_t mid; /* * The loop following here will go on as long as there are expire-times left * to process (compared to `ts`) in the splay and 'data' will be * re-assigned for every expired handle we deal with. */ - while(1) { + while(Curl_timeouts_remove_expired(&multi->timeouts, ts, &mid)) { /* Check if there is one (more) expired timer to deal with! This function extracts a matching node if there is one */ - multi->timetree = Curl_splaygetbest(ts, multi->timetree, &t); - if(!t) - return; - - data = Curl_splayget(t); /* assign this for next loop */ - if(!data) + data = Curl_multi_get_easy(multi, mid); + if(!data) { + DEBUGASSERT(0); continue; + } #ifdef CURLVERBOSE if(CURL_TRC_TIMER_is_verbose(data)) { - struct Curl_llist_node *e = Curl_llist_head(&data->state.timeoutlist); - if(e) { - struct time_node *n = Curl_node_elem(e); - CURL_TRC_TIMER(data, n->eid, "has expired"); + if(data->state.timeouts.first < EXPIRE_LAST) { + CURL_TRC_TIMER(data, data->state.timeouts.first, "has expired"); } } #endif @@ -3287,7 +3293,7 @@ static CURLMcode multi_socket(struct Curl_multi *multi, if the same timeout is still the one to run after this call. That handles the case when the application asks libcurl to run the timeout prematurely. */ - memset(&multi->last_expire_ts, 0, sizeof(multi->last_expire_ts)); + multi->last_expire_offset_us = 0; /* Applications may set `socket_cb` *after* having added transfers * first. *Then* kick off processing with a @@ -3317,13 +3323,13 @@ out: sigpipe_restore(&pipe_ctx); if(multi_ischanged(multi, TRUE)) - process_pending_handles(multi); + multi_schedule_pending(multi); if(!mresult && CURL_MNTFY_HAS_ENTRIES(multi)) mresult = Curl_mntfy_dispatch_all(multi); if(running_handles) { - unsigned int running = Curl_multi_xfers_running(multi); + uint32_t running = Curl_multi_xfers_running(multi); *running_handles = (running < INT_MAX) ? (int)running : INT_MAX; } @@ -3335,126 +3341,125 @@ out: #undef curl_multi_setopt CURLMcode curl_multi_setopt(CURLM *m, CURLMoption option, ...) { + struct Curl_mapi_guard guard; CURLMcode mresult = CURLM_OK; - va_list param; - unsigned long uarg; - struct Curl_multi *multi = m; - if(!GOOD_MULTI_HANDLE(multi)) - return CURLM_BAD_HANDLE; + if(CURL_MAPI_ENTER(&guard, m, multi_setopt, &mresult)) { + struct Curl_multi *multi = m; + va_list param; + unsigned long uarg; - if(multi->in_callback) - return CURLM_RECURSIVE_API_CALL; + va_start(param, option); - va_start(param, option); - - switch(option) { - case CURLMOPT_SOCKETFUNCTION: - multi->socket_cb = va_arg(param, curl_socket_callback); - break; - case CURLMOPT_SOCKETDATA: - multi->socket_userp = va_arg(param, void *); - break; - case CURLMOPT_PUSHFUNCTION: - multi->push_cb = va_arg(param, curl_push_callback); - break; - case CURLMOPT_PUSHDATA: - multi->push_userp = va_arg(param, void *); - break; - case CURLMOPT_PIPELINING: - multi->multiplexing = va_arg(param, long) & CURLPIPE_MULTIPLEX ? 1 : 0; - break; - case CURLMOPT_TIMERFUNCTION: - multi->timer_cb = va_arg(param, curl_multi_timer_callback); - break; - case CURLMOPT_TIMERDATA: - multi->timer_userp = va_arg(param, void *); - break; - case CURLMOPT_MAXCONNECTS: - uarg = va_arg(param, unsigned long); - if(uarg <= UINT_MAX) - multi->maxconnects = (unsigned int)uarg; - break; - case CURLMOPT_MAX_HOST_CONNECTIONS: - if(!curlx_sltouz(va_arg(param, long), &multi->max_host_connections)) - mresult = CURLM_BAD_FUNCTION_ARGUMENT; - break; - case CURLMOPT_MAX_TOTAL_CONNECTIONS: - if(!curlx_sltouz(va_arg(param, long), &multi->max_total_connections)) - mresult = CURLM_BAD_FUNCTION_ARGUMENT; - break; - /* options formerly used for pipelining */ - case CURLMOPT_MAX_PIPELINE_LENGTH: - break; - case CURLMOPT_CONTENT_LENGTH_PENALTY_SIZE: - break; - case CURLMOPT_CHUNK_LENGTH_PENALTY_SIZE: - break; - case CURLMOPT_PIPELINING_SITE_BL: - break; - case CURLMOPT_PIPELINING_SERVER_BL: - break; - case CURLMOPT_MAX_CONCURRENT_STREAMS: { - long streams = va_arg(param, long); - if((streams < 1) || (streams > INT_MAX)) - streams = 100; - multi->max_concurrent_streams = (unsigned int)streams; - break; - } - case CURLMOPT_NETWORK_CHANGED: { - long val = va_arg(param, long); - if(val & CURLMNWC_CLEAR_ALL) - /* In the beginning, all values available to set were 1 by mistake. We - converted this to mean "all", thus setting all the bits - automatically */ - val = CURLMNWC_CLEAR_DNS | CURLMNWC_CLEAR_CONNS; - if(val & CURLMNWC_CLEAR_DNS) { - Curl_dnscache_clear(multi->admin); - } - if(val & CURLMNWC_CLEAR_CONNS) { - Curl_cpool_nw_changed(multi->admin); - } - break; - } - case CURLMOPT_NOTIFYFUNCTION: - multi->ntfy.ntfy_cb = va_arg(param, curl_notify_callback); - break; - case CURLMOPT_NOTIFYDATA: - multi->ntfy.ntfy_cb_data = va_arg(param, void *); - break; - case CURLMOPT_RESOLVE_THREADS_MAX: -#ifdef USE_RESOLV_THREADED - uarg = va_arg(param, long); - if((uarg <= 0) || (uarg > UINT32_MAX)) - mresult = CURLM_BAD_FUNCTION_ARGUMENT; - else { - CURLcode result = Curl_async_thrdd_multi_set_props( - multi, 0, (uint32_t)uarg, 2000); - switch(result) { - case CURLE_OK: - mresult = CURLM_OK; - break; - case CURLE_BAD_FUNCTION_ARGUMENT: + switch(option) { + case CURLMOPT_SOCKETFUNCTION: + multi->socket_cb = va_arg(param, curl_socket_callback); + break; + case CURLMOPT_SOCKETDATA: + multi->socket_userp = va_arg(param, void *); + break; + case CURLMOPT_PUSHFUNCTION: + multi->push_cb = va_arg(param, curl_push_callback); + break; + case CURLMOPT_PUSHDATA: + multi->push_userp = va_arg(param, void *); + break; + case CURLMOPT_PIPELINING: + multi->multiplexing = va_arg(param, long) & CURLPIPE_MULTIPLEX ? 1 : 0; + break; + case CURLMOPT_TIMERFUNCTION: + multi->timer_cb = va_arg(param, curl_multi_timer_callback); + break; + case CURLMOPT_TIMERDATA: + multi->timer_userp = va_arg(param, void *); + break; + case CURLMOPT_MAXCONNECTS: + uarg = va_arg(param, unsigned long); + if(uarg <= UINT_MAX) + multi->maxconnects = (unsigned int)uarg; + break; + case CURLMOPT_MAX_HOST_CONNECTIONS: + if(!curlx_sltouz(va_arg(param, long), &multi->max_host_connections)) mresult = CURLM_BAD_FUNCTION_ARGUMENT; - break; - case CURLE_OUT_OF_MEMORY: - mresult = CURLM_OUT_OF_MEMORY; - break; - default: - mresult = CURLM_INTERNAL_ERROR; - break; - } + break; + case CURLMOPT_MAX_TOTAL_CONNECTIONS: + if(!curlx_sltouz(va_arg(param, long), &multi->max_total_connections)) + mresult = CURLM_BAD_FUNCTION_ARGUMENT; + break; + /* options formerly used for pipelining */ + case CURLMOPT_MAX_PIPELINE_LENGTH: + break; + case CURLMOPT_CONTENT_LENGTH_PENALTY_SIZE: + break; + case CURLMOPT_CHUNK_LENGTH_PENALTY_SIZE: + break; + case CURLMOPT_PIPELINING_SITE_BL: + break; + case CURLMOPT_PIPELINING_SERVER_BL: + break; + case CURLMOPT_MAX_CONCURRENT_STREAMS: { + long streams = va_arg(param, long); + if((streams < 1) || (streams > INT_MAX)) + streams = 100; + multi->max_concurrent_streams = (unsigned int)streams; + break; } + case CURLMOPT_NETWORK_CHANGED: { + long val = va_arg(param, long); + if(val & CURLMNWC_CLEAR_ALL) + /* In the beginning, all values available to set were 1 by mistake. We + converted this to mean "all", thus setting all the bits + automatically */ + val = CURLMNWC_CLEAR_DNS | CURLMNWC_CLEAR_CONNS; + if(val & CURLMNWC_CLEAR_DNS) { + Curl_dnscache_clear(multi->admin); + } + if(val & CURLMNWC_CLEAR_CONNS) { + Curl_cpool_nw_changed(&multi->cpool, multi->admin); + } + break; + } + case CURLMOPT_NOTIFYFUNCTION: + multi->ntfy.ntfy_cb = va_arg(param, curl_notify_callback); + break; + case CURLMOPT_NOTIFYDATA: + multi->ntfy.ntfy_cb_data = va_arg(param, void *); + break; + case CURLMOPT_RESOLVE_THREADS_MAX: +#ifdef USE_RESOLV_THREADED + uarg = va_arg(param, long); + if((uarg <= 0) || (uarg > UINT32_MAX)) + mresult = CURLM_BAD_FUNCTION_ARGUMENT; + else { + CURLcode result = Curl_async_thrdd_multi_set_props( + multi, 0, (uint32_t)uarg, 2000); + switch(result) { + case CURLE_OK: + mresult = CURLM_OK; + break; + case CURLE_BAD_FUNCTION_ARGUMENT: + mresult = CURLM_BAD_FUNCTION_ARGUMENT; + break; + case CURLE_OUT_OF_MEMORY: + mresult = CURLM_OUT_OF_MEMORY; + break; + default: + mresult = CURLM_INTERNAL_ERROR; + break; + } + } #endif - break; - case CURLMOPT_QUICK_EXIT: - multi->quick_exit = va_arg(param, long) ? 1 : 0; - break; - default: - mresult = CURLM_UNKNOWN_OPTION; - break; + break; + case CURLMOPT_QUICK_EXIT: + multi->quick_exit = va_arg(param, long) ? 1 : 0; + break; + default: + mresult = CURLM_UNKNOWN_OPTION; + break; + } + va_end(param); } - va_end(param); + CURL_MAPI_LEAVE(&guard); return mresult; } @@ -3463,33 +3468,39 @@ CURLMcode curl_multi_setopt(CURLM *m, CURLMoption option, ...) CURLMcode curl_multi_socket(CURLM *m, curl_socket_t s, int *running_handles) { - struct Curl_multi *multi = m; - if(multi->in_callback) - return CURLM_RECURSIVE_API_CALL; - if(multi->in_ntfy_callback) - return CURLM_RECURSIVE_API_CALL; - return multi_socket(multi, FALSE, s, 0, running_handles); + struct Curl_mapi_guard guard; + CURLMcode mresult; + + if(CURL_MAPI_ENTER(&guard, m, multi_socket, &mresult)) { + mresult = multi_socket(m, FALSE, s, 0, running_handles); + } + CURL_MAPI_LEAVE(&guard); + return mresult; } CURLMcode curl_multi_socket_action(CURLM *m, curl_socket_t s, int ev_bitmask, int *running_handles) { - struct Curl_multi *multi = m; - if(multi->in_callback) - return CURLM_RECURSIVE_API_CALL; - if(multi->in_ntfy_callback) - return CURLM_RECURSIVE_API_CALL; - return multi_socket(multi, FALSE, s, ev_bitmask, running_handles); + struct Curl_mapi_guard guard; + CURLMcode mresult; + + if(CURL_MAPI_ENTER(&guard, m, multi_socket_action, &mresult)) { + mresult = multi_socket(m, FALSE, s, ev_bitmask, running_handles); + } + CURL_MAPI_LEAVE(&guard); + return mresult; } CURLMcode curl_multi_socket_all(CURLM *m, int *running_handles) { - struct Curl_multi *multi = m; - if(multi->in_callback) - return CURLM_RECURSIVE_API_CALL; - if(multi->in_ntfy_callback) - return CURLM_RECURSIVE_API_CALL; - return multi_socket(multi, TRUE, CURL_SOCKET_BAD, 0, running_handles); + struct Curl_mapi_guard guard; + CURLMcode mresult; + + if(CURL_MAPI_ENTER(&guard, m, multi_socket_all, &mresult)) { + mresult = multi_socket(m, TRUE, CURL_SOCKET_BAD, 0, running_handles); + } + CURL_MAPI_LEAVE(&guard); + return mresult; } static bool multi_has_dirties(struct Curl_multi *multi) @@ -3514,81 +3525,57 @@ static bool multi_has_dirties(struct Curl_multi *multi) } static void multi_timeout(struct Curl_multi *multi, - struct curltime *expire_time, - long *timeout_ms) + timediff_t *pexire_offset_us, + int *timeout_ms) { - static const struct curltime tv_zero = { 0, 0 }; - VERBOSE(struct Curl_easy *data = NULL); - if(multi->dead) { + if(pexire_offset_us) + *pexire_offset_us = 0; *timeout_ms = 0; return; } if(multi_has_dirties(multi)) { - *expire_time = *multi_now(multi); + if(pexire_offset_us) + *pexire_offset_us = Curl_timeouts_offset_us(&multi->timeouts, + multi_now(multi)); *timeout_ms = 0; return; } - else if(multi->timetree) { - const struct curltime *pnow = multi_now(multi); - /* splay the lowest to the bottom */ - multi->timetree = Curl_splay(&tv_zero, multi->timetree); - /* this will not return NULL from a non-empty tree, but some compilers - * are not convinced of that. Analyzers are hard. */ - *expire_time = multi->timetree ? multi->timetree->key : tv_zero; - - /* 'multi->timetree' will be non-NULL here but the compilers sometimes - yell at us if we assume so */ - if(multi->timetree && - curlx_ptimediff_us(&multi->timetree->key, pnow) > 0) { - /* some time left before expiration */ - timediff_t diff_ms = - curlx_timediff_ceil_ms(multi->timetree->key, *pnow); - VERBOSE(data = Curl_splayget(multi->timetree)); - /* this should be safe even on 32-bit archs, as we do not use that - overly long timeouts */ - *timeout_ms = (long)diff_ms; - } - else { - if(multi->timetree) - VERBOSE(data = Curl_splayget(multi->timetree)); - /* 0 means immediately */ - *timeout_ms = 0; - } - } else { - *expire_time = tv_zero; - *timeout_ms = -1; - } + const struct curltime *pnow = multi_now(multi); + uint32_t mid; + *timeout_ms = Curl_timeouts_next_ms(&multi->timeouts, pnow, + pexire_offset_us, &mid); #ifdef CURLVERBOSE - if(CURL_TRC_TIMER_is_verbose(data)) { - struct Curl_llist_node *e = Curl_llist_head(&data->state.timeoutlist); - if(e) { - struct time_node *n = Curl_node_elem(e); - CURL_TRC_TIMER(data, n->eid, "gives multi timeout in %ldms", - *timeout_ms); + if(mid != UINT32_MAX) { + struct Curl_easy *data = Curl_multi_get_easy(multi, mid); + if(data && CURL_TRC_TIMER_is_verbose(data) && + (data->state.timeouts.first < EXPIRE_LAST)) { + CURL_TRC_TIMER(data, data->state.timeouts.first, + "gives multi timeout in %dms", *timeout_ms); + } } - } #endif + } } CURLMcode curl_multi_timeout(CURLM *m, long *timeout_ms) { - struct curltime expire_time; - struct Curl_multi *multi = m; + struct Curl_mapi_guard guard; + CURLMcode mresult; - /* First, make some basic checks that the CURLM handle is a good handle */ - if(!GOOD_MULTI_HANDLE(multi)) - return CURLM_BAD_HANDLE; + if(CURL_MAPI_ENTER(&guard, m, multi_timeout, &mresult)) { + int itimeout_ms; - if(multi->in_callback) - return CURLM_RECURSIVE_API_CALL; - - multi_timeout(multi, &expire_time, timeout_ms); - return CURLM_OK; + multi_timeout(m, NULL, &itimeout_ms); + *timeout_ms = (long)itimeout_ms; + mresult = CURLM_OK; + } + CURL_MAPI_LEAVE(&guard); + return mresult; } /* @@ -3597,14 +3584,14 @@ CURLMcode curl_multi_timeout(CURLM *m, */ CURLMcode Curl_update_timer(struct Curl_multi *multi) { - struct curltime expire_ts; - long timeout_ms; + timediff_t timeouts_offset_us = 0; + int timeout_ms; int rc; bool set_value = FALSE; if(!multi->timer_cb || multi->dead) return CURLM_OK; - multi_timeout(multi, &expire_ts, &timeout_ms); + multi_timeout(multi, &timeouts_offset_us, &timeout_ms); if(timeout_ms < 0 && multi->last_timeout_ms < 0) { /* nothing to do */ @@ -3616,14 +3603,14 @@ CURLMcode Curl_update_timer(struct Curl_multi *multi) set_value = TRUE; } else if(multi->last_timeout_ms < 0) { - CURL_TRC_M(multi->admin, "[TIMER] set %ldms, none before", timeout_ms); + CURL_TRC_M(multi->admin, "[TIMER] set %dms, none before", timeout_ms); set_value = TRUE; } - else if(curlx_ptimediff_us(&multi->last_expire_ts, &expire_ts)) { + else if(multi->last_expire_offset_us != timeouts_offset_us) { /* We had a timeout before and have one now, the absolute timestamp * differs. The relative timeout_ms may be the same, but the starting * point differs. Let the application restart its timer. */ - CURL_TRC_M(multi->admin, "[TIMER] set %ldms, replace previous", + CURL_TRC_M(multi->admin, "[TIMER] set %dms, replace previous", timeout_ms); set_value = TRUE; } @@ -3634,11 +3621,13 @@ CURLMcode Curl_update_timer(struct Curl_multi *multi) } if(set_value) { - multi->last_expire_ts = expire_ts; + struct Curl_mapi_guard guard; + + multi->last_expire_offset_us = timeouts_offset_us; multi->last_timeout_ms = timeout_ms; - set_in_callback(multi, TRUE); + CURL_CBAPI_MULTI_START(&guard, multi, multi_timer_cb); rc = multi->timer_cb(multi, timeout_ms, multi->timer_userp); - set_in_callback(multi, FALSE); + CURL_CBAPI_MULTI_END(&guard); if(rc == -1) { multi->dead = TRUE; return CURLM_ABORTED_BY_CALLBACK; @@ -3647,130 +3636,109 @@ CURLMcode Curl_update_timer(struct Curl_multi *multi) return CURLM_OK; } +#ifdef DEBUGBUILD +static bool multi_timeouts_check(struct Curl_easy *data) +{ + struct expire_timers *timeouts = &data->state.timeouts; + uint8_t id; + int i = 0; + for(id = timeouts->first; id < EXPIRE_LAST; id = timeouts->next[id]) { + if(++i >= EXPIRE_LAST) { + failf(data, "expire timeouts looped: %d iterations and no end", i); + return FALSE; + } + if(id == timeouts->next[id]) { + failf(data, "expire timeouts wrong: %d points to itself", (int)id); + return FALSE; + } + if((timeouts->next[id] < EXPIRE_LAST) && + (timeouts->offset_us[id] > timeouts->offset_us[timeouts->next[id]])) { + failf(data, "expire timeouts not sorted: %d happens after %d but " + "is listed before", (int)id, (int)timeouts->next[id]); + return FALSE; + } + } + return TRUE; +} +#endif + /* - * multi_deltimeout() - * * Remove a given timestamp from the list of timeouts. */ -static void multi_deltimeout(struct Curl_easy *data, expire_id eid) +static void multi_clear_timeout(struct Curl_easy *data, expire_id eid) { - struct Curl_llist_node *e; - struct Curl_llist *timeoutlist = &data->state.timeoutlist; - /* find and remove the specific node from the list */ - for(e = Curl_llist_head(timeoutlist); e; e = Curl_node_next(e)) { - struct time_node *n = Curl_node_elem(e); - if(n->eid == eid) { - Curl_node_remove(e); + struct expire_timers *timeouts = &data->state.timeouts; + uint8_t orig_first = timeouts->first; + uint8_t *anchor = &timeouts->first; + uint8_t id = (uint8_t)eid; + + if((unsigned)eid >= EXPIRE_LAST) { + DEBUGASSERT(0); + return; + } + + while(*anchor < EXPIRE_LAST) { + if(*anchor == id) { + *anchor = timeouts->next[id]; + break; + } + anchor = &timeouts->next[*anchor]; + } + DEBUGASSERT(multi_timeouts_check(data)); + if(Curl_timeouts_has(data)) { + struct Curl_multi *multi = data->multi; + + if(!multi) { + DEBUGASSERT(0); return; } + if((timeouts->first >= EXPIRE_LAST) || /* no more timeouts */ + (timeouts->first != orig_first)) { /* active timeout changed */ + Curl_timeouts_remove(&multi->timeouts, data); + } + if((timeouts->first < EXPIRE_LAST) && !Curl_timeouts_has(data)) { + Curl_timeouts_add(&multi->timeouts, data, + timeouts->offset_us[timeouts->first]); + } } } /* - * multi_addtimeout() - * * Add a timestamp to the list of timeouts. Keep the list sorted so that head * of list is always the timeout nearest in time. - * */ -static CURLMcode multi_addtimeout(struct Curl_easy *data, - struct curltime *stamp, - expire_id eid) +static CURLMcode multi_set_timeout(struct Curl_easy *data, + const struct curltime *stamp, + expire_id eid) { - struct Curl_llist_node *e; - struct time_node *node; - struct Curl_llist_node *prev = NULL; - size_t n; - struct Curl_llist *timeoutlist = &data->state.timeoutlist; + struct expire_timers *timeouts = &data->state.timeouts; + uint8_t *anchor = &timeouts->first; + uint8_t id = (uint8_t)eid; - node = &data->state.expires[eid]; - - /* copy the timestamp and id */ - memcpy(&node->time, stamp, sizeof(*stamp)); - node->eid = eid; /* also marks it as in use */ - - n = Curl_llist_count(timeoutlist); - if(n) { - /* find the correct spot in the list */ - for(e = Curl_llist_head(timeoutlist); e; e = Curl_node_next(e)) { - struct time_node *check = Curl_node_elem(e); - timediff_t diff = curlx_ptimediff_ms(&check->time, &node->time); - if(diff > 0) - break; - prev = e; - } + if((unsigned)eid >= EXPIRE_LAST) { + DEBUGASSERT(0); + return CURLM_BAD_FUNCTION_ARGUMENT; } - /* else - this is the first timeout on the list */ + /* remove from list, store time and re-insert */ + multi_clear_timeout(data, eid); + timeouts->offset_us[id] = + Curl_timeouts_offset_us(&data->multi->timeouts, stamp); - Curl_llist_insert_next(timeoutlist, prev, node, &node->list); - CURL_TRC_TIMER(data, eid, "set for %" FMT_TIMEDIFF_T "ns", - curlx_ptimediff_us(&node->time, Curl_pgrs_now(data))); + while(*anchor < EXPIRE_LAST) { + if(timeouts->offset_us[*anchor] > timeouts->offset_us[id]) + break; + anchor = &timeouts->next[*anchor]; + } + timeouts->next[eid] = *anchor; + timeouts->next[eid] = *anchor; + *anchor = id; + DEBUGASSERT(multi_timeouts_check(data)); + CURL_TRC_TIMER(data, eid, "set for %" FMT_TIMEDIFF_T "us", + curlx_ptimediff_us(stamp, Curl_pgrs_now(data))); return CURLM_OK; } -void Curl_expire_ex(struct Curl_easy *data, - timediff_t milli, expire_id id) -{ - struct Curl_multi *multi = data->multi; - struct curltime *curr_expire = &data->state.expiretime; - struct curltime set; - - /* this is only interesting while there is still an associated multi struct - remaining! */ - if(!multi) - return; - - DEBUGASSERT(id < EXPIRE_LAST); - - set = *Curl_pgrs_now(data); - set.tv_sec += (time_t)(milli / 1000); /* may be a 64 to 32-bit conversion */ - set.tv_usec += (int)(milli % 1000) * 1000; - - if(set.tv_usec >= 1000000) { - set.tv_sec++; - set.tv_usec -= 1000000; - } - - /* Remove any timer with the same id */ - multi_deltimeout(data, id); - - /* Add it to the timer list. It must stay in the list until it has expired - in case we need to recompute the minimum timer later. */ - multi_addtimeout(data, &set, id); - - if(curr_expire->tv_sec || curr_expire->tv_usec) { - /* This means that the struct is added as a node in the splay tree. - Compare if the new time is earlier, and only remove-old/add-new if it - is. */ - timediff_t diff = curlx_ptimediff_ms(&set, curr_expire); - int rc; - - if(diff > 0) { - /* The current splay tree entry is sooner than this new expiry time. - We do not need to update our splay tree entry. */ - return; - } - - /* Since this is an updated time, we must remove the previous entry from - the splay tree first and then re-add the new value */ - rc = Curl_splayremove(multi->timetree, &data->state.timenode, - &multi->timetree); - if(rc) - infof(data, "Internal error removing splay node = %d", rc); - } - - /* Indicate that we are in the splay tree and insert the new timer expiry - value since it is our local minimum. */ - *curr_expire = set; - Curl_splayset(&data->state.timenode, data); - multi->timetree = Curl_splayinsert(curr_expire, multi->timetree, - &data->state.timenode); -} - /* - * Curl_expire() - * * given a number of milliseconds from now to use to set the 'act before * this'-time for the transfer, to be extracted by curl_multi_timeout() * @@ -3779,68 +3747,104 @@ void Curl_expire_ex(struct Curl_easy *data, * * Expire replaces a former timeout using the same id if already set. */ -void Curl_expire(struct Curl_easy *data, timediff_t milli, expire_id id) +void Curl_expire_set(struct Curl_easy *data, + expire_id eid, timediff_t ms, + const struct curltime *pnow) { - Curl_expire_ex(data, milli, id); + struct Curl_multi *multi = data->multi; + struct expire_timers *timeouts = &data->state.timeouts; + uint8_t prev_id = timeouts->first; + struct curltime set; + + /* this is only interesting while there is still an associated multi struct + remaining! */ + if(!multi) + return; + DEBUGASSERT(eid < EXPIRE_LAST); + if(ms > INT_MAX) + /* Cap ridiculous timeouts, 31-bit ms is still 3.5 weeks. When the time + goes to the user, it must fit in this size. */ + ms = INT_MAX; + + set = *pnow; + set.tv_sec += (time_t)(ms / 1000); /* may be a 64 to 32-bit conversion */ + set.tv_usec += (int)(ms % 1000) * 1000; + if(set.tv_usec >= 1000000) { + set.tv_sec++; + set.tv_usec -= 1000000; + } + + /* Add the timeout, will replace any previous value for this timer. */ + multi_set_timeout(data, &set, eid); + DEBUGASSERT(timeouts->first < EXPIRE_LAST); + + if(Curl_timeouts_has(data)) { + /* data has already a timeout registered. If the first timer + * was NOT the one we just set AND is still the first one, + * nothing changed from the timeouts point of view. The + * set timer triggers after the one already registered. Leave. */ + if((prev_id != eid) && (prev_id == timeouts->first)) + return; + + /* Since this is an updated time, we must remove data from + * timeouts and then add it again. */ + Curl_timeouts_remove(&multi->timeouts, data); + } + + /* Insert the new timer expiry since it is our local minimum. */ + Curl_timeouts_add(&multi->timeouts, data, + timeouts->offset_us[timeouts->first]); +} + +void Curl_expire(struct Curl_easy *data, + timediff_t milli, expire_id eid) +{ + Curl_expire_set(data, eid, milli, Curl_pgrs_now(data)); } /* - * Curl_expire_done() - * * Removes the expire timer. Marks it as done. - * */ -void Curl_expire_done(struct Curl_easy *data, expire_id id) +void Curl_expire_clear(struct Curl_easy *data, expire_id eid) { /* remove the timer, if there */ - multi_deltimeout(data, id); - CURL_TRC_TIMER(data, id, "cleared"); + multi_clear_timeout(data, eid); + CURL_TRC_TIMER(data, eid, "cleared"); } /* - * Curl_expire_clear() - * * Clear ALL timeout values for this handle. */ -void Curl_expire_clear(struct Curl_easy *data) +void Curl_expire_clear_all(struct Curl_easy *data) { struct Curl_multi *multi = data->multi; - struct curltime *nowp = &data->state.expiretime; /* this is only interesting while there is still an associated multi struct remaining! */ if(!multi) return; - if(nowp->tv_sec || nowp->tv_usec) { + if(Curl_timeouts_remove(&multi->timeouts, data)) { /* Since this is an cleared time, we must remove the previous entry from the splay tree */ - struct Curl_llist *list = &data->state.timeoutlist; - int rc; - - rc = Curl_splayremove(multi->timetree, &data->state.timenode, - &multi->timetree); - if(rc) - infof(data, "Internal error clearing splay node = %d", rc); - - /* clear the timeout list too */ - Curl_llist_destroy(list, NULL); + multi_timeouts_init(data); if(data->id >= 0) CURL_TRC_M(data, "[TIMEOUT] all cleared"); - nowp->tv_sec = 0; - nowp->tv_usec = 0; } } CURLMcode curl_multi_assign(CURLM *m, curl_socket_t sockfd, void *sockp) { - struct Curl_multi *multi = m; - if(!GOOD_MULTI_HANDLE(multi)) - return CURLM_BAD_HANDLE; + struct Curl_mapi_guard guard; + CURLMcode mresult; - return Curl_multi_ev_assign(multi, sockfd, sockp); + if(CURL_MAPI_ENTER(&guard, m, multi_assign, &mresult)) { + mresult = Curl_multi_ev_assign(m, sockfd, sockp); + } + CURL_MAPI_LEAVE(&guard); + return mresult; } static void move_pending_to_connect(struct Curl_multi *multi, @@ -3856,7 +3860,7 @@ static void move_pending_to_connect(struct Curl_multi *multi, Curl_multi_mark_dirty(data); /* make it run */ } -/* process_pending_handles() moves a handle from PENDING back into the process +/* multi_schedule_pending() moves a handle from PENDING back into the process list and change state to CONNECT. We do not move all transfers because that can be a significant amount. @@ -3868,9 +3872,8 @@ static void move_pending_to_connect(struct Curl_multi *multi, 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) + more pipewait rechecks than others. */ +static void multi_schedule_pending(struct Curl_multi *multi) { uint32_t mid = multi->last_pending_mid; @@ -3906,17 +3909,6 @@ static void process_pending_handles(struct Curl_multi *multi) } } -void Curl_set_in_callback(struct Curl_easy *data, bool value) -{ - if(data && data->multi) - data->multi->in_callback = value; -} - -bool Curl_is_in_callback(struct Curl_easy *data) -{ - return data && data->multi && data->multi->in_callback; -} - unsigned int Curl_multi_max_concurrent_streams(struct Curl_multi *multi) { DEBUGASSERT(multi); @@ -3925,24 +3917,31 @@ unsigned int Curl_multi_max_concurrent_streams(struct Curl_multi *multi) CURL **curl_multi_get_handles(CURLM *m) { - struct Curl_multi *multi = m; - void *entry; - size_t count = Curl_uint32_tbl_count(&multi->xfers); - CURL **a = curlx_malloc(sizeof(struct Curl_easy *) * (count + 1)); - if(a) { - unsigned int i = 0; - uint32_t mid; + struct Curl_mapi_guard guard; + CURL **a = NULL; - if(Curl_uint32_tbl_first(&multi->xfers, &mid, &entry)) { - do { - struct Curl_easy *data = entry; - DEBUGASSERT(i < count); - if(!data->state.internal) - a[i++] = data; - } while(Curl_uint32_tbl_next(&multi->xfers, mid, &mid, &entry)); + if(CURL_MAPI_ENTER(&guard, m, multi_get_handles, NULL)) { + struct Curl_multi *multi = m; + void *entry; + size_t count = Curl_uint32_tbl_count(&multi->xfers); + + a = curlx_malloc(sizeof(struct Curl_easy *) * (count + 1)); + if(a) { + unsigned int i = 0; + uint32_t mid; + + if(Curl_uint32_tbl_first(&multi->xfers, &mid, &entry)) { + do { + struct Curl_easy *data = entry; + DEBUGASSERT(i < count); + if(!data->state.internal) + a[i++] = data; + } while(Curl_uint32_tbl_next(&multi->xfers, mid, &mid, &entry)); + } + a[i] = NULL; /* last entry is a NULL */ } - a[i] = NULL; /* last entry is a NULL */ } + CURL_MAPI_LEAVE(&guard); return a; } @@ -3950,40 +3949,49 @@ CURLMcode curl_multi_get_offt(CURLM *m, CURLMinfo_offt info, curl_off_t *pvalue) { - struct Curl_multi *multi = m; - uint32_t n; + struct Curl_mapi_guard guard; + CURLMcode mresult = CURLM_OK; - if(!GOOD_MULTI_HANDLE(multi)) - return CURLM_BAD_HANDLE; - if(!pvalue) - return CURLM_BAD_FUNCTION_ARGUMENT; + if(CURL_MAPI_ENTER(&guard, m, multi_get_offt, &mresult)) { + struct Curl_multi *multi = m; + uint32_t n; - switch(info) { - case CURLMINFO_XFERS_CURRENT: - n = Curl_uint32_tbl_count(&multi->xfers); - if(n && multi->admin) - --n; - *pvalue = (curl_off_t)n; - return CURLM_OK; - case CURLMINFO_XFERS_RUNNING: - n = Curl_uint32_bset_count(&multi->process); - if(n && Curl_uint32_bset_contains(&multi->process, multi->admin->mid)) - --n; - *pvalue = (curl_off_t)n; - return CURLM_OK; - case CURLMINFO_XFERS_PENDING: - *pvalue = (curl_off_t)Curl_uint32_bset_count(&multi->pending); - return CURLM_OK; - case CURLMINFO_XFERS_DONE: - *pvalue = (curl_off_t)Curl_uint32_bset_count(&multi->msgsent); - return CURLM_OK; - case CURLMINFO_XFERS_ADDED: - *pvalue = multi->xfers_total_ever; - return CURLM_OK; - default: - *pvalue = -1; - return CURLM_UNKNOWN_OPTION; + if(!pvalue) { + mresult = CURLM_BAD_FUNCTION_ARGUMENT; + goto out; + } + + switch(info) { + case CURLMINFO_XFERS_CURRENT: + n = Curl_uint32_tbl_count(&multi->xfers); + if(n && multi->admin) + --n; + *pvalue = (curl_off_t)n; + break; + case CURLMINFO_XFERS_RUNNING: + n = Curl_uint32_bset_count(&multi->process); + if(n && Curl_uint32_bset_contains(&multi->process, multi->admin->mid)) + --n; + *pvalue = (curl_off_t)n; + break; + case CURLMINFO_XFERS_PENDING: + *pvalue = (curl_off_t)Curl_uint32_bset_count(&multi->pending); + break; + case CURLMINFO_XFERS_DONE: + *pvalue = (curl_off_t)Curl_uint32_bset_count(&multi->msgsent); + break; + case CURLMINFO_XFERS_ADDED: + *pvalue = multi->xfers_total_ever; + break; + default: + *pvalue = -1; + mresult = CURLM_UNKNOWN_OPTION; + break; + } } +out: + CURL_MAPI_LEAVE(&guard); + return mresult; } CURLcode Curl_multi_xfer_buf_borrow(struct Curl_easy *data, @@ -4167,14 +4175,30 @@ struct Curl_easy *Curl_multi_get_easy(struct Curl_multi *multi, return NULL; } -unsigned int Curl_multi_xfers_running(struct Curl_multi *multi) +bool Curl_multi_knows_easy(struct Curl_multi *multi, struct Curl_easy *data) { - DEBUGASSERT(multi); - if(!multi) + return Curl_uint32_tbl_get(&multi->xfers, data->mid) == data; +} + +uint32_t Curl_multi_xfers_running(struct Curl_multi *multi) +{ + if(!multi) { + DEBUGASSERT(0); return 0; + } return multi->xfers_alive; } +uint32_t Curl_multi_xfers_attached(struct Curl_multi *multi) +{ + if(!multi || !multi->admin) { + DEBUGASSERT(0); + return 0; + } + /* Discount the admin handle */ + return Curl_uint32_tbl_count(&multi->xfers) - 1; +} + void Curl_multi_mark_dirty(struct Curl_easy *data) { if(data->multi && data->mid != UINT32_MAX) @@ -4189,20 +4213,26 @@ void Curl_multi_clear_dirty(struct Curl_easy *data) CURLMcode curl_multi_notify_enable(CURLM *m, unsigned int notification) { - struct Curl_multi *multi = m; + struct Curl_mapi_guard guard; + CURLMcode mresult = CURLM_OK; - if(!GOOD_MULTI_HANDLE(multi)) - return CURLM_BAD_HANDLE; - return Curl_mntfy_enable(multi, notification); + if(CURL_MAPI_ENTER(&guard, m, multi_notify_enable, &mresult)) { + mresult = Curl_mntfy_enable(m, notification); + } + CURL_MAPI_LEAVE(&guard); + return mresult; } CURLMcode curl_multi_notify_disable(CURLM *m, unsigned int notification) { - struct Curl_multi *multi = m; + struct Curl_mapi_guard guard; + CURLMcode mresult = CURLM_OK; - if(!GOOD_MULTI_HANDLE(multi)) - return CURLM_BAD_HANDLE; - return Curl_mntfy_disable(multi, notification); + if(CURL_MAPI_ENTER(&guard, m, multi_notify_disable, &mresult)) { + mresult = Curl_mntfy_disable(m, notification); + } + CURL_MAPI_LEAVE(&guard); + return mresult; } #ifdef DEBUGBUILD diff --git a/Utilities/cmcurl/lib/multi_ev.c b/Utilities/cmcurl/lib/multi_ev.c index 0da5b0e904..3b7eeb0dc3 100644 --- a/Utilities/cmcurl/lib/multi_ev.c +++ b/Utilities/cmcurl/lib/multi_ev.c @@ -34,12 +34,6 @@ #include "uint-spbset.h" #include "multihandle.h" - -static void mev_in_callback(struct Curl_multi *multi, bool value) -{ - multi->in_callback = value; -} - #ifdef DEBUGBUILD #define SH_ENTRY_MAGIC 0x570091d #endif @@ -207,13 +201,17 @@ static CURLMcode mev_forget_socket(struct Curl_multi *multi, /* We managed this socket before, tell the socket callback to forget it. */ if(entry->announced && multi->socket_cb) { + struct Curl_mapi_guard guard; + NOVERBOSE((void)cause); CURL_TRC_M(data, "ev %s, call(fd=%" FMT_SOCKET_T ", ev=REMOVE)", cause, s); - mev_in_callback(multi, TRUE); + CURL_CBAPI_MULTI_START(&guard, multi, multi_socket_cb); rc = multi->socket_cb(data, s, CURL_POLL_REMOVE, multi->socket_userp, entry->user_data); - mev_in_callback(multi, FALSE); - entry->announced = FALSE; + CURL_CBAPI_END(&guard); + entry = mev_sh_entry_get(&multi->ev.sh_entries, s); + if(entry) + entry->announced = FALSE; } mev_sh_entry_kill(multi, s); @@ -281,10 +279,13 @@ static CURLMcode mev_sh_entry_update(struct Curl_multi *multi, CURL_TRC_M(data, "ev update call(fd=%" FMT_SOCKET_T ", ev=%s%s)", s, (comboaction & CURL_POLL_IN) ? "IN" : "", (comboaction & CURL_POLL_OUT) ? "OUT" : ""); - mev_in_callback(multi, TRUE); - rc = multi->socket_cb(data, s, comboaction, multi->socket_userp, - entry->user_data); - mev_in_callback(multi, FALSE); + { + struct Curl_mapi_guard guard; + CURL_CBAPI_MULTI_START(&guard, multi, multi_socket_cb); + rc = multi->socket_cb(data, s, comboaction, multi->socket_userp, + entry->user_data); + CURL_CBAPI_MULTI_END(&guard); + } if(rc == -1) { multi->dead = TRUE; return CURLM_ABORTED_BY_CALLBACK; diff --git a/Utilities/cmcurl/lib/multi_ntfy.c b/Utilities/cmcurl/lib/multi_ntfy.c index 1319aaec07..48c3723951 100644 --- a/Utilities/cmcurl/lib/multi_ntfy.c +++ b/Utilities/cmcurl/lib/multi_ntfy.c @@ -179,8 +179,13 @@ void Curl_mntfy_add(struct Curl_easy *data, unsigned int type) CURLMcode Curl_mntfy_dispatch_all(struct Curl_multi *multi) { - DEBUGASSERT(!multi->in_ntfy_callback); - multi->in_ntfy_callback = TRUE; + struct Curl_mapi_guard guard; + + if(!multi) + return CURLM_BAD_FUNCTION_ARGUMENT; + + CURL_CBAPI_MULTI_START(&guard, multi, multi_ntfy_cb); + while(multi->ntfy.head && !multi->ntfy.failure) { struct mntfy_chunk *chunk = multi->ntfy.head; /* this may cause new notifications to be added! */ @@ -194,7 +199,8 @@ CURLMcode Curl_mntfy_dispatch_all(struct Curl_multi *multi) multi->ntfy.head = chunk->next; mnfty_chunk_destroy(chunk); } - multi->in_ntfy_callback = FALSE; + + CURL_CBAPI_MULTI_END(&guard); if(multi->ntfy.failure) { CURLMcode mresult = multi->ntfy.failure; diff --git a/Utilities/cmcurl/lib/multihandle.h b/Utilities/cmcurl/lib/multihandle.h index 19dd2ffcdf..2a8fba1583 100644 --- a/Utilities/cmcurl/lib/multihandle.h +++ b/Utilities/cmcurl/lib/multihandle.h @@ -23,18 +23,20 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ +#include "api.h" #include "llist.h" #include "hash.h" #include "conncache.h" #include "cshutdn.h" -#include "dnscache.h" #include "multi_ev.h" #include "multi_ntfy.h" #include "psl.h" #include "socketpair.h" +#include "splay.h" #include "uint-bset.h" #include "uint-spbset.h" #include "uint-table.h" +#include "vdns/dnscache.h" struct connectdata; struct Curl_easy; @@ -73,21 +75,27 @@ typedef enum { #if !defined(CURL_DISABLE_SOCKETPAIR) && !defined(USE_WINSOCK) #define ENABLE_WAKEUP #endif +#if !defined(CURL_DISABLE_SOCKETPAIR) && \ + defined(USE_RESOLV_THREADED) && \ + !defined(USE_WINSOCK) +#define ENABLE_INTERNAL_WAKEUP +#endif /* value for MAXIMUM CONCURRENT STREAMS upper limit */ #define INITIAL_MAX_CONCURRENT_STREAMS ((1U << 31) - 1) /* This is the struct known as CURLM on the outside */ struct Curl_multi { - /* First a simple identifier to easier detect if a user mix up - this multi handle with an easy handle. Set this to CURL_MULTI_HANDLE. */ - unsigned int magic; + /* First a simple identifier to more easily detect if a user mixes up + this multi handle with an easy handle. + Set this to CURLMULTI_MAGIC_NUMBER. */ + uint32_t magic; + uint32_t xfers_alive; /* amount of added transfers that have + not yet reached COMPLETE state */ + uint32_t xfers_really_alive; /* amount of added transfers that have + passed INIT state but are not COMPLETE yet */ + uint32_t max_concurrent_streams; - unsigned int xfers_alive; /* amount of added transfers that have - not yet reached COMPLETE state */ - unsigned int xfers_really_alive; /* amount of added transfers that have - passed INIT state but are not COMPLETE yet */ - curl_off_t xfers_total_ever; /* total of added transfers, ever. */ struct uint32_tbl xfers; /* transfers added to this multi */ /* Each transfer's mid may be present in at most one of these */ struct uint32_bset process; /* transfer being processed */ @@ -95,8 +103,12 @@ struct Curl_multi { struct uint32_bset pending; /* transfers in waiting (conn limit etc.) */ struct uint32_bset msgsent; /* transfers done with message for application */ + struct Curl_mapi_stack callstack; /* multi api calls ongoing */ + struct Curl_llist msglist; /* a list of messages from completed transfers */ + curl_off_t xfers_total_ever; /* total of added transfers, ever. */ + struct Curl_easy *admin; /* internal easy handle for admin operations. gets assigned `mid` 0 on multi init */ @@ -121,9 +133,8 @@ struct Curl_multi { /* current time for transfers running in this multi handle */ struct curltime now; - /* timetree points to the splay-tree of time nodes to figure out expire - times of all currently set timers */ - struct Curl_tree *timetree; + /* expiration times for all attached easy handles */ + struct Curl_timeouts timeouts; /* buffer used for transfer data, lazy initialized */ char *xfer_buf; /* the actual buffer */ @@ -150,6 +161,7 @@ struct Curl_multi { struct cshutdn cshutdn; /* connection shutdown handling */ struct cpool cpool; /* connection pool (bundles) */ + timediff_t last_expire_offset_us; /* times offset of last expiry */ size_t max_host_connections; /* if >0, a fixed limit of the maximum number of connections per host */ @@ -159,8 +171,7 @@ struct Curl_multi { /* timer callback and user data pointer for the *socket() API */ curl_multi_timer_callback timer_cb; void *timer_userp; - long last_timeout_ms; /* the last timeout value set via timer_cb */ - struct curltime last_expire_ts; /* timestamp of last expiry */ + int last_timeout_ms; /* the last timeout value set via timer_cb */ #ifdef USE_WINSOCK WSAEVENT wsa_event; /* Winsock event used for waits */ @@ -168,9 +179,14 @@ struct Curl_multi { #ifdef ENABLE_WAKEUP curl_socket_t wakeup_pair[2]; /* eventfd()/pipe()/socketpair() used for wakeup 0 is used for read, 1 is used - for write */ + for write. Used by curl_multi_wakeup() */ +#endif +#ifdef ENABLE_INTERNAL_WAKEUP + curl_socket_t wakeup_internal[2]; /* eventfd()/pipe()/socketpair() used for + wakeup 0 is used for read, 1 is used + for write. Used for internal wakeups, + e.g. threaded resolver. */ #endif - unsigned int max_concurrent_streams; unsigned int maxconnects; /* if >0, a fixed limit of the maximum number of entries we are allowed to grow the connection cache to */ @@ -182,8 +198,6 @@ struct Curl_multi { BIT(ipv6_works); BIT(multiplexing); /* multiplexing wanted */ BIT(recheckstate); /* see Curl_multi_connchanged */ - BIT(in_callback); /* true while executing a callback */ - BIT(in_ntfy_callback); /* true while dispatching notifications */ #ifdef USE_OPENSSL BIT(ssl_seeded); #endif diff --git a/Utilities/cmcurl/lib/multiif.h b/Utilities/cmcurl/lib/multiif.h index 039db269e0..d3ad8fffb3 100644 --- a/Utilities/cmcurl/lib/multiif.h +++ b/Utilities/cmcurl/lib/multiif.h @@ -27,23 +27,28 @@ * Prototypes for library-wide functions provided by multi.c */ -void Curl_expire(struct Curl_easy *data, timediff_t milli, expire_id id); -void Curl_expire_ex(struct Curl_easy *data, - timediff_t milli, expire_id id); -void Curl_expire_clear(struct Curl_easy *data); -void Curl_expire_done(struct Curl_easy *data, expire_id id); +void Curl_expire(struct Curl_easy *data, timediff_t milli, expire_id eid); +void Curl_expire_set(struct Curl_easy *data, + expire_id eid, timediff_t ms, + const struct curltime *pnow); +void Curl_expire_clear(struct Curl_easy *data, expire_id eid); +void Curl_expire_clear_all(struct Curl_easy *data); CURLMcode Curl_update_timer(struct Curl_multi *multi) WARN_UNUSED_RESULT; void Curl_attach_connection(struct Curl_easy *data, - struct connectdata *conn); + struct connectdata *conn, + bool matched); 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 *data); CURLcode Curl_preconnect(struct Curl_easy *data); bool Curl_is_connecting(struct Curl_easy *data); void Curl_multi_connchanged(struct Curl_multi *multi); +CURLMcode Curl_multi_add_handle(struct Curl_multi *multi, + struct Curl_easy *data); +CURLMcode Curl_multi_remove_handle(struct Curl_multi *multi, + struct Curl_easy *data); + /* Internal version of curl_multi_init() accepts size parameters for the socket, connection and dns hashes */ struct Curl_multi *Curl_multi_handle(uint32_t xfer_table_size, @@ -148,13 +153,20 @@ void Curl_multi_xfer_sockbuf_release(struct Curl_easy *data, char *buf); /** * Get the easy handle for the given mid. - * Returns NULL if not found. + * Returns NULL if not found or not a GOOD_EASY_HANDLE() */ struct Curl_easy *Curl_multi_get_easy(struct Curl_multi *multi, uint32_t mid); +/* TRUE if multi knows about data via its `mid` */ +bool Curl_multi_knows_easy(struct Curl_multi *multi, struct Curl_easy *data); + +/* Get the # of transfers attached to the multi, without the internal + * admin handle. */ +uint32_t Curl_multi_xfers_attached(struct Curl_multi *multi); + /* Get the # of transfers current in process/pending. */ -unsigned int Curl_multi_xfers_running(struct Curl_multi *multi); +uint32_t Curl_multi_xfers_running(struct Curl_multi *multi); /* Mark a transfer as dirty, e.g. to be rerun at earliest convenience. * A cheap operation, can be done many times repeatedly. */ @@ -164,4 +176,8 @@ void Curl_multi_clear_dirty(struct Curl_easy *data); void Curl_multi_set_now(struct Curl_multi *multi); +#ifdef ENABLE_INTERNAL_WAKEUP +void Curl_multi_wakeup_internal(struct Curl_multi *multi); +#endif + #endif /* HEADER_CURL_MULTIIF_H */ diff --git a/Utilities/cmcurl/lib/openldap.c b/Utilities/cmcurl/lib/openldap.c index 8ec6bb27cd..887336e5ca 100644 --- a/Utilities/cmcurl/lib/openldap.c +++ b/Utilities/cmcurl/lib/openldap.c @@ -164,7 +164,7 @@ static CURLcode oldap_map_error(int rc, CURLcode result) static CURLcode oldap_url_parse(struct Curl_easy *data, LDAPURLDesc **ludp) { CURLcode result = CURLE_OK; - int rc = LDAP_URL_ERR_BADURL; + int rc; static const char * const url_errs[] = { "success", "out of memory", @@ -180,9 +180,16 @@ static CURLcode oldap_url_parse(struct Curl_easy *data, LDAPURLDesc **ludp) }; *ludp = NULL; - if(!data->state.up.user && !data->state.up.password && - !data->state.up.options) + /* `ldap_url_parse() seems to be terrible with urls + * that have user/pass/options in it. So when we have options or + * creds from the url, fail without calling the function. + * Yes, this is super-weird code and I do not like it. */ + if((data->state.creds && (data->state.creds->source == CREDS_URL)) || + data->state.up.options) + rc = LDAP_URL_ERR_BADURL; + else rc = ldap_url_parse(Curl_bufref_ptr(&data->state.url), ludp); + if(rc != LDAP_URL_SUCCESS) { const char *msg = "url parsing problem"; @@ -605,7 +612,7 @@ static CURLcode oldap_connect(struct Curl_easy *data, bool *done) if(result) goto out; - li->proto = ldap_pvt_url_scheme2proto(data->state.up.scheme); + li->proto = ldap_pvt_url_scheme2proto(data->state.origin->scheme->name); /* Initialize the SASL storage */ Curl_sasl_init(&li->sasl, data, &saslldap); @@ -614,10 +621,11 @@ static CURLcode oldap_connect(struct Curl_easy *data, bool *done) if(result) goto out; - hosturl = curl_maprintf("%s://%s:%d", + hosturl = curl_maprintf("%s://%s:%u", conn->scheme->name, - (data->state.up.hostname[0] == '[') ? - data->state.up.hostname : conn->origin->hostname, + conn->origin->ipv6 ? + conn->origin->user_hostname : + conn->origin->hostname, conn->origin->port); if(!hosturl) { result = CURLE_OUT_OF_MEMORY; @@ -783,8 +791,19 @@ static CURLcode oldap_state_sasl_resp(struct Curl_easy *data, } else { result = Curl_sasl_continue(&li->sasl, data, code, &progress); - if(!result && progress != SASL_INPROGRESS) - oldap_state(data, li, OLDAP_STOP); + if(!result) { + switch(progress) { + case SASL_DONE: + oldap_state(data, li, OLDAP_STOP); /* Authenticated */ + break; + case SASL_IDLE: /* No mechanism left after cancellation */ + failf(data, "Authentication cancelled"); + result = CURLE_LOGIN_DENIED; + break; + default: + break; + } + } } if(li->servercred) @@ -1081,7 +1100,7 @@ static CURLcode client_write(struct Curl_easy *data, return result; } -static CURLcode oldap_recv(struct Curl_easy *data, int sockindex, char *buf, +static CURLcode oldap_recv(struct Curl_easy *data, int8_t sockindex, char *buf, size_t len, size_t *pnread) { struct connectdata *conn = data->conn; @@ -1187,9 +1206,11 @@ static CURLcode oldap_recv(struct Curl_easy *data, int sockindex, char *buf, break; if(!binary) { - /* check for leading or trailing whitespace */ + /* check for a leading ':' or '<' (not a SAFE-INIT-CHAR per RFC + 2849) or leading or trailing whitespace */ if(bvals[i].bv_len && - (ISBLANK(bvals[i].bv_val[0]) || + ((bvals[i].bv_val[0] == ':') || (bvals[i].bv_val[0] == '<') || + ISBLANK(bvals[i].bv_val[0]) || ISBLANK(bvals[i].bv_val[bvals[i].bv_len - 1]))) binval = TRUE; else { diff --git a/Utilities/cmcurl/lib/parsedate.c b/Utilities/cmcurl/lib/parsedate.c index b19423169a..ec9f21296b 100644 --- a/Utilities/cmcurl/lib/parsedate.c +++ b/Utilities/cmcurl/lib/parsedate.c @@ -27,59 +27,57 @@ #include "curlx/strparse.h" #include "curlx/strcopy.h" -/* - A brief summary of the date string formats this parser groks: +/* A brief summary of the date string formats this parser groks: - RFC 2616 3.3.1 + RFC 2616 3.3.1 - Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123 - Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036 - Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format + Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123 + Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036 + Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format - we support dates without week day name: + we support dates without week day name: - 06 Nov 1994 08:49:37 GMT - 06-Nov-94 08:49:37 GMT - Nov 6 08:49:37 1994 + 06 Nov 1994 08:49:37 GMT + 06-Nov-94 08:49:37 GMT + Nov 6 08:49:37 1994 - without the time zone: + without the time zone: - 06 Nov 1994 08:49:37 - 06-Nov-94 08:49:37 + 06 Nov 1994 08:49:37 + 06-Nov-94 08:49:37 - weird order: + weird order: - 1994 Nov 6 08:49:37 (GNU date fails) - GMT 08:49:37 06-Nov-94 Sunday - 94 6 Nov 08:49:37 (GNU date fails) + 1994 Nov 6 08:49:37 (GNU date fails) + GMT 08:49:37 06-Nov-94 Sunday + 94 6 Nov 08:49:37 (GNU date fails) - time left out: + time left out: - 1994 Nov 6 - 06-Nov-94 - Sun Nov 6 94 + 1994 Nov 6 + 06-Nov-94 + Sun Nov 6 94 - unusual separators: + unusual separators: - 1994.Nov.6 - Sun/Nov/6/94/GMT + 1994.Nov.6 + Sun/Nov/6/94/GMT - commonly used time zone names: + commonly used time zone names: - Sun, 06 Nov 1994 08:49:37 CET - 06 Nov 1994 08:49:37 EST + Sun, 06 Nov 1994 08:49:37 CET + 06 Nov 1994 08:49:37 EST - time zones specified using RFC822 style: + time zones specified using RFC822 style: - Sun, 12 Sep 2004 15:05:58 -0700 - Sat, 11 Sep 2004 21:32:11 +0200 + Sun, 12 Sep 2004 15:05:58 -0700 + Sat, 11 Sep 2004 21:32:11 +0200 - compact numerical date strings: + compact numerical date strings: - 20040912 15:05:58 -0700 - 20040911 +0200 - -*/ + 20040912 15:05:58 -0700 + 20040911 +0200 + */ #if !defined(CURL_DISABLE_PARSEDATE) || !defined(CURL_DISABLE_FTP) || \ !defined(CURL_DISABLE_FILE) || defined(USE_GNUTLS) @@ -193,8 +191,7 @@ static const struct tzinfo tz[] = { /* returns: -1 no day 0 monday - 6 sunday -*/ - + */ static int checkday(const char *check, size_t len) { int i; diff --git a/Utilities/cmcurl/lib/peer.c b/Utilities/cmcurl/lib/peer.c index 44bcb52f23..4a7a12d8cf 100644 --- a/Utilities/cmcurl/lib/peer.c +++ b/Utilities/cmcurl/lib/peer.c @@ -74,6 +74,7 @@ #include "peer.h" #include "urldata.h" #include "url.h" +#include "urlapi-int.h" #include "vtls/vtls.h" struct peer_parse { @@ -350,31 +351,28 @@ bool Curl_peer_same_destination(struct Curl_peer *p1, struct Curl_peer *p2) CURLcode Curl_peer_from_url(CURLU *uh, struct Curl_easy *data, uint16_t port_override, uint32_t scopeid_override, - struct urlpieces *up, struct Curl_peer **ppeer) { struct peer_parse pp; - char *zoneid = NULL; + char *zoneid = NULL, *scheme = NULL, *hostname = NULL; CURLUcode uc; CURLcode result; Curl_peer_unlink(ppeer); memset(&pp, 0, sizeof(pp)); - curlx_safefree(up->scheme); - uc = curl_url_get(uh, CURLUPART_SCHEME, &up->scheme, 0); + uc = curl_url_get(uh, CURLUPART_SCHEME, &scheme, 0); if(uc) return Curl_uc_to_curlcode(uc); - pp.scheme = Curl_get_scheme(up->scheme); + pp.scheme = Curl_get_scheme(scheme); if(!pp.scheme) { - failf(data, "Protocol \"%s\" not supported%s", up->scheme, + failf(data, "Protocol \"%s\" not supported%s", scheme, data->state.this_is_a_follow ? " (in redirect)" : ""); result = CURLE_UNSUPPORTED_PROTOCOL; goto out; } - curlx_safefree(up->hostname); - uc = curl_url_get(uh, CURLUPART_HOST, &up->hostname, 0); + uc = curl_url_get(uh, CURLUPART_HOST, &hostname, 0); if(uc) { if((uc == CURLUE_NO_HOST) && (pp.scheme->flags & PROTOPT_NONETWORK)) ; /* acceptable */ @@ -383,13 +381,13 @@ CURLcode Curl_peer_from_url(CURLU *uh, struct Curl_easy *data, goto out; } } - else if(strlen(up->hostname) > MAX_URL_LEN) { + else if(strlen(hostname) > MAX_URL_LEN) { failf(data, "Too long hostname (maximum is %d)", MAX_URL_LEN); result = CURLE_URL_MALFORMAT; goto out; } - pp.host_user.str = up->hostname ? up->hostname : ""; + pp.host_user.str = hostname ? hostname : ""; pp.host_user.len = strlen(pp.host_user.str); if(pp.host_user.len) { result = peer_parse_host(data, &pp, FALSE); @@ -399,7 +397,6 @@ CURLcode Curl_peer_from_url(CURLU *uh, struct Curl_easy *data, else pp.host = pp.host_user; - curlx_safefree(up->port); if(port_override) { /* if set, we use this instead of the port possibly given in the URL */ char portbuf[16]; @@ -413,7 +410,7 @@ CURLcode Curl_peer_from_url(CURLU *uh, struct Curl_easy *data, pp.port = port_override; } else { - uc = curl_url_get(uh, CURLUPART_PORT, &up->port, CURLU_DEFAULT_PORT); + uc = Curl_url_get_port(uh, &pp.port); if(uc) { if(uc == CURLUE_OUT_OF_MEMORY) { result = CURLE_OUT_OF_MEMORY; @@ -425,13 +422,6 @@ CURLcode Curl_peer_from_url(CURLU *uh, struct Curl_easy *data, } /* no port ok when not a network scheme */ } - else { - const char *p = up->port; - curl_off_t offt; - if(curlx_str_number(&p, &offt, 0xffff)) - return CURLE_URL_MALFORMAT; - pp.port = (uint16_t)offt; - } } if(scopeid_override) @@ -456,6 +446,8 @@ CURLcode Curl_peer_from_url(CURLU *uh, struct Curl_easy *data, out: peer_parse_clear(&pp); + curlx_free(scheme); + curlx_free(hostname); curlx_free(zoneid); return result; } diff --git a/Utilities/cmcurl/lib/peer.h b/Utilities/cmcurl/lib/peer.h index 1ceb230739..a0a3054cd8 100644 --- a/Utilities/cmcurl/lib/peer.h +++ b/Utilities/cmcurl/lib/peer.h @@ -25,7 +25,6 @@ ***************************************************************************/ struct Curl_scheme; -struct urlpieces; /* if peer hostname starts with this, the peer is a unix domain socket * path, e.g. the remainder after 'localhost'. */ @@ -34,9 +33,9 @@ struct urlpieces; struct Curl_peer { const struct Curl_scheme *scheme; /* url scheme */ char *hostname; /* normalized hostname (IDN decoded when supported) */ - char *zoneid; /* NULL or ipv6 zone identifier */ + char *zoneid; /* NULL or IPv6 zone identifier */ uint32_t refcount; /* created with 1, freed when dropping to 0 */ - uint32_t scopeid; /* != 0, ipv6 scope to use */ + uint32_t scopeid; /* != 0, IPv6 scope to use */ uint16_t port; BIT(unix_socket); /* hostname is a UDS path without the prefix */ BIT(abstract_uds); /* only TRUE when `unix_socket` also TRUE */ @@ -48,12 +47,12 @@ struct Curl_peer { * - `peer->user_hostname` is the passed `hostname` * - `peer->hostname` is the normalized `hostname` via * + IDN conversion if it has non-ASCII characters - * + stripping of surrounding '[]' for URL formatted ipv6 addresses + * + stripping of surrounding '[]' for URL formatted IPv6 addresses * + the path alone in case of a unix domain socket, e.g. hostname * starts with CURL_PEER_UDS_PREFIX and is longer * Scans for IPv6 addresses even without surrounding '[]'. - * - `zoneid` ipv6 zone identifier or NULL - * - `scopeid` ipv6 scopeid of zoneid, when known. + * - `zoneid` IPv6 zone identifier or NULL + * - `scopeid` IPv6 scopeid of zoneid, when known. */ CURLcode Curl_peer_create(struct Curl_easy *data, const struct Curl_scheme *scheme, @@ -84,7 +83,6 @@ bool Curl_peer_same_destination(struct Curl_peer *p1, struct Curl_peer *p2); CURLcode Curl_peer_from_url(CURLU *uh, struct Curl_easy *data, uint16_t port_override, uint32_t scopeid_override, - struct urlpieces *up, struct Curl_peer **ppeer); CURLcode Curl_peer_from_connect_to(struct Curl_easy *data, diff --git a/Utilities/cmcurl/lib/pingpong.c b/Utilities/cmcurl/lib/pingpong.c index b40d968b3f..b365772fb7 100644 --- a/Utilities/cmcurl/lib/pingpong.c +++ b/Utilities/cmcurl/lib/pingpong.c @@ -222,7 +222,7 @@ CURLcode Curl_pp_sendf(struct Curl_easy *data, struct pingpong *pp, } static CURLcode pingpong_read(struct Curl_easy *data, - int sockindex, + int8_t sockindex, char *buffer, size_t buflen, size_t *nread) @@ -236,7 +236,7 @@ static CURLcode pingpong_read(struct Curl_easy *data, * Reads a piece of a server response. */ CURLcode Curl_pp_readresp(struct Curl_easy *data, - int sockindex, + int8_t sockindex, struct pingpong *pp, int *code, /* return the server code if done */ size_t *size) /* size of the response */ diff --git a/Utilities/cmcurl/lib/pingpong.h b/Utilities/cmcurl/lib/pingpong.h index 02f4961230..09dca414eb 100644 --- a/Utilities/cmcurl/lib/pingpong.h +++ b/Utilities/cmcurl/lib/pingpong.h @@ -132,7 +132,7 @@ CURLcode Curl_pp_vsendf(struct Curl_easy *data, * Reads a piece of a server response. */ CURLcode Curl_pp_readresp(struct Curl_easy *data, - int sockindex, + int8_t sockindex, struct pingpong *pp, int *code, /* return the server code if done */ size_t *size); /* size of the response */ diff --git a/Utilities/cmcurl/lib/pop3.c b/Utilities/cmcurl/lib/pop3.c index d609b4c2b3..b32663ce14 100644 --- a/Utilities/cmcurl/lib/pop3.c +++ b/Utilities/cmcurl/lib/pop3.c @@ -58,7 +58,6 @@ #include "sendf.h" #include "curl_trc.h" -#include "hostip.h" #include "progress.h" #include "transfer.h" #include "escape.h" @@ -251,7 +250,7 @@ static CURLcode pop3_parse_custom_request(struct Curl_easy *data) { CURLcode result = CURLE_OK; struct POP3 *pop3 = Curl_meta_get(data, CURL_META_POP3_EASY); - const char *custom = data->set.str[STRING_CUSTOMREQUEST]; + const char *custom = CURL_EASY_STR(data, STRING_CUSTOMREQUEST); if(!pop3) return CURLE_FAILED_INIT; @@ -620,7 +619,8 @@ static CURLcode pop3_perform_auth(struct Curl_easy *data, if(ir) { /* AUTH ... */ /* Send the AUTH command with the initial response */ - result = Curl_pp_sendf(data, &pop3c->pp, "AUTH %s %s", mech, ir); + result = Curl_pp_sendf(data, &pop3c->pp, "AUTH %s %s", + mech, *ir ? ir : "="); } else { /* Send the AUTH command */ @@ -1406,7 +1406,7 @@ static const struct SASLproto saslpop3 = { pop3_continue_auth, /* Send authentication continuation */ pop3_cancel_auth, /* Send authentication cancellation */ pop3_get_message, /* Get SASL response message */ - 255 - 8, /* Max line len - strlen("AUTH ") - 1 space - crlf */ + 255 - 8, /* Max line len - strlen("AUTH ") - 1 space - CRLF */ '*', /* Code received when continuation is expected */ '+', /* Code to receive upon authentication success */ SASL_AUTH_DEFAULT, /* Default mechanisms */ @@ -1477,7 +1477,8 @@ static CURLcode pop3_done(struct Curl_easy *data, CURLcode status, return CURLE_OK; if(status) { - connclose(data->conn, "POP3 done with bad status"); + CURL_TRC_M(data, "POP3 done with bad status"); + connclose(data->conn); result = status; /* use the already set error code */ } diff --git a/Utilities/cmcurl/lib/progress.c b/Utilities/cmcurl/lib/progress.c index 969b29a275..fcb8bf2422 100644 --- a/Utilities/cmcurl/lib/progress.c +++ b/Utilities/cmcurl/lib/progress.c @@ -33,8 +33,7 @@ #ifndef CURL_DISABLE_PROGRESS_METER /* Provide a string that is 7 letters long (plus the zero byte). - @unittest 1636 -*/ + @unittest 1636 */ UNITTEST void time2str(char *r, size_t rsize, curl_off_t seconds); UNITTEST void time2str(char *r, size_t rsize, curl_off_t seconds) { @@ -83,8 +82,7 @@ UNITTEST void time2str(char *r, size_t rsize, curl_off_t seconds) but never longer than 6 columns (+ one zero byte). Add suffix k, M, G when suitable... - @unittest 1636 -*/ + @unittest 1636 */ UNITTEST char *max6out(curl_off_t bytes, char *max6, size_t mlen); UNITTEST char *max6out(curl_off_t bytes, char *max6, size_t mlen) { @@ -93,7 +91,7 @@ UNITTEST char *max6out(curl_off_t bytes, char *max6, size_t mlen) if(bytes < 100000) curl_msnprintf(max6, mlen, "%6" CURL_FORMAT_CURL_OFF_T, bytes); else { - const char unit[] = { 'k', 'M', 'G', 'T', 'P', 'E', 0 }; + static const char unit[] = { 'k', 'M', 'G', 'T', 'P', 'E', 0 }; int k = 0; curl_off_t nbytes; curl_off_t rest; @@ -165,21 +163,18 @@ UNITTEST CURLcode pgrs_speedcheck(struct Curl_easy *data, /* since low speed limit is enabled, set the expire timer to make this connection's speed get checked again in a second */ - Curl_expire(data, 1000, EXPIRE_SPEEDCHECK); + Curl_expire_set(data, EXPIRE_SPEEDCHECK, 1000, pnow); return CURLE_OK; } const struct curltime *Curl_pgrs_now(struct Curl_easy *data) { - struct curltime *pnow = data->multi ? - &data->multi->now : &data->progress.now; - curlx_pnow(pnow); - return pnow; + curlx_pnow(&data->progress.now); + return &data->progress.now; } -/* - New proposed interface, 9th of February 2000: +/* New proposed interface, 9th of February 2000: pgrsStartNow() - sets start time pgrsSetDownloadSize(x) - known expected download size @@ -188,12 +183,12 @@ const struct curltime *Curl_pgrs_now(struct Curl_easy *data) pgrsSetUploadCounter() - amount of data currently uploaded pgrsUpdate() - show progress pgrsDone() - transfer complete -*/ + */ int Curl_pgrsDone(struct Curl_easy *data) { int rc; - data->progress.lastshow = 0; + data->progress.delta.lastshow_us = -1; rc = Curl_pgrsUpdate(data); /* the final (forced) update */ if(rc) return rc; @@ -252,7 +247,6 @@ static const char * const pgrs_timer_names[] = { "PGRS-PRETRANSFER", "PGRS-STARTTRANSFER", "PGRS-POSTRANSFER", - "PGRS-STARTACCEPT", "PGRS-REDIRECT", }; @@ -279,34 +273,33 @@ void Curl_pgrsTimeWas(struct Curl_easy *data, timerid timer, break; case TIMER_STARTOP: /* This is set at the start of a transfer */ - data->progress.t_startop = timestamp; - data->progress.t_startqueue = timestamp; - data->progress.t_postqueue = 0; + data->progress.delta.startop_us = + curlx_ptimediff_us(×tamp, &data->progress.start); + data->progress.delta.startqueue_us = data->progress.delta.startop_us; break; case TIMER_STARTSINGLE: /* This is set at the start of each single transfer */ - data->progress.t_startsingle = timestamp; - data->progress.is_t_startransfer_set = FALSE; + data->progress.delta.startsingle_us = + curlx_ptimediff_us(×tamp, &data->progress.start); + data->progress.startransfer_added = FALSE; break; case TIMER_POSTQUEUE: /* Queue time is accumulative from all involved redirects */ - data->progress.t_postqueue += - curlx_ptimediff_us(×tamp, &data->progress.t_startqueue); - break; - case TIMER_STARTACCEPT: - data->progress.t_acceptdata = timestamp; + data->progress.total.queued_us += + curlx_ptimediff_us(×tamp, &data->progress.start) - + data->progress.delta.startqueue_us; break; case TIMER_NAMELOOKUP: - delta = &data->progress.t_nslookup; + delta = &data->progress.total.nslookup_us; break; case TIMER_CONNECT: - delta = &data->progress.t_connect; + delta = &data->progress.total.connect_us; break; case TIMER_APPCONNECT: - delta = &data->progress.t_appconnect; + delta = &data->progress.total.appconnect_us; break; case TIMER_PRETRANSFER: - delta = &data->progress.t_pretransfer; + delta = &data->progress.total.pretransfer_us; break; case TIMER_STARTTRANSFER: /* prevent updating t_starttransfer unless: @@ -315,29 +308,31 @@ void Curl_pgrsTimeWas(struct Curl_easy *data, timerid timer, * This prevents repeated invocations of the function from incorrectly * changing the t_starttransfer time. */ - if(data->progress.is_t_startransfer_set) { + if(data->progress.startransfer_added) { CURL_TRC_M(data, "[%s] ignored", pgrs_timer_name(timer)); return; } - data->progress.is_t_startransfer_set = TRUE; - delta = &data->progress.t_starttransfer; + data->progress.startransfer_added = TRUE; + delta = &data->progress.total.starttransfer_us; break; case TIMER_POSTRANSFER: - delta = &data->progress.t_posttransfer; + delta = &data->progress.total.posttransfer_us; break; case TIMER_REDIRECT: - data->progress.t_redirect = curlx_ptimediff_us(×tamp, - &data->progress.start); - data->progress.t_startqueue = timestamp; + data->progress.delta.startredirect_us = + curlx_ptimediff_us(×tamp, &data->progress.start); + /* transfer starts queueing again */ + data->progress.delta.startqueue_us = + data->progress.delta.startredirect_us; break; } if(delta) { - timediff_t us = curlx_ptimediff_us(×tamp, - &data->progress.t_startsingle); + timediff_t us = curlx_ptimediff_us(×tamp, &data->progress.start) - + data->progress.delta.startsingle_us; if(us < 1) us = 1; /* make sure at least one microsecond passed */ *delta += us; - CURL_TRC_M(data, "[%s] added %" FMT_TIMEDIFF_T "ns", + CURL_TRC_M(data, "[%s] added %" FMT_TIMEDIFF_T "us", pgrs_timer_name(timer), us); } else @@ -355,13 +350,17 @@ void Curl_pgrsTime(struct Curl_easy *data, timerid timer) Curl_pgrsTimeWas(data, timer, *Curl_pgrs_now(data)); } -void Curl_pgrsStartNow(struct Curl_easy *data) +void Curl_pgrsStart(struct Curl_easy *data, const struct curltime *pnow) { struct Progress *p = &data->progress; + if(!pnow) + pnow = Curl_pgrs_now(data); p->speeder_c = 0; /* reset the progress meter display */ - p->start = *Curl_pgrs_now(data); - p->is_t_startransfer_set = FALSE; + p->start = *pnow; + memset(&p->delta, 0, sizeof(p->delta)); + memset(&p->total, 0, sizeof(p->total)); + p->startransfer_added = FALSE; p->dl.cur_size = 0; p->ul.cur_size = 0; /* the sizes are unknown at start */ @@ -392,7 +391,7 @@ void Curl_pgrs_download_inc(struct Curl_easy *data, size_t delta) { if(delta) { data->progress.dl.cur_size += delta; - Curl_rlimit_drain(&data->progress.dl.rlimit, delta, Curl_pgrs_now(data)); + Curl_rlimit_drain(&data->progress.dl.rlimit, delta, NULL); } } @@ -400,7 +399,7 @@ void Curl_pgrs_upload_inc(struct Curl_easy *data, size_t delta) { if(delta) { data->progress.ul.cur_size += delta; - Curl_rlimit_drain(&data->progress.ul.rlimit, delta, Curl_pgrs_now(data)); + Curl_rlimit_drain(&data->progress.ul.rlimit, delta, NULL); } } @@ -461,21 +460,22 @@ static bool progress_calc(struct Curl_easy *data, { struct Progress * const p = &data->progress; int i_next, i_oldest, i_latest; - timediff_t duration_us; + timediff_t duration_us, elapsed_us; curl_off_t amount; /* The time spent so far (from the start) in microseconds */ - p->timespent = curlx_ptimediff_us(pnow, &p->start); - p->dl.speed = trspeed(p->dl.cur_size, p->timespent); - p->ul.speed = trspeed(p->ul.cur_size, p->timespent); + elapsed_us = curlx_ptimediff_us(pnow, &p->start); + p->total.spent_us = elapsed_us; + p->dl.speed = trspeed(p->dl.cur_size, p->total.spent_us); + p->ul.speed = trspeed(p->ul.cur_size, p->total.spent_us); if(!p->speeder_c) { /* no previous record exists */ p->speed_amount[0] = p->dl.cur_size + p->ul.cur_size; - p->speed_time[0] = *pnow; + p->speed_time[0] = elapsed_us; p->speeder_c++; /* use the overall average at the start */ p->current_speed = p->ul.speed + p->dl.speed; - p->lastshow = pnow->tv_sec; + p->delta.lastshow_us = elapsed_us; return TRUE; } /* We have at least one record now. Where to put the next and @@ -485,11 +485,11 @@ static bool progress_calc(struct Curl_easy *data, /* Make a new record only when some time has passed. * Too frequent calls otherwise ruin the history. */ - if(curlx_ptimediff_ms(pnow, &p->speed_time[i_latest]) >= 1000) { + if((elapsed_us - p->speed_time[i_latest]) >= (1000 * 1000)) { p->speeder_c++; i_latest = i_next; p->speed_amount[i_latest] = p->dl.cur_size + p->ul.cur_size; - p->speed_time[i_latest] = *pnow; + p->speed_time[i_latest] = elapsed_us; } else if(data->req.done) { /* When a transfer is done, and we did not have a current speed @@ -498,7 +498,7 @@ static bool progress_calc(struct Curl_easy *data, * reported speed since it no longer measures a full second. */ if(!p->current_speed) { p->speed_amount[i_latest] = p->dl.cur_size + p->ul.cur_size; - p->speed_time[i_latest] = *pnow; + p->speed_time[i_latest] = elapsed_us; } } else { @@ -512,8 +512,7 @@ static bool progress_calc(struct Curl_easy *data, /* How much we transferred between oldest and current records */ amount = p->speed_amount[i_latest] - p->speed_amount[i_oldest]; /* How long this took */ - duration_us = curlx_ptimediff_us(&p->speed_time[i_latest], - &p->speed_time[i_oldest]); + duration_us = p->speed_time[i_latest] - p->speed_time[i_oldest]; if(duration_us <= 0) duration_us = 1; @@ -527,9 +526,10 @@ static bool progress_calc(struct Curl_easy *data, p->current_speed = amount * 1000000 / duration_us; } - if((p->lastshow == pnow->tv_sec) && !data->req.done) + if((p->delta.lastshow_us >= 0) && !data->req.done && + ((elapsed_us - p->delta.lastshow_us) < (1000 * 1000))) return FALSE; - p->lastshow = pnow->tv_sec; + p->delta.lastshow_us = elapsed_us; return TRUE; } @@ -574,7 +574,7 @@ static void progress_meter(struct Curl_easy *data) char time_left[8]; char time_total[8]; char time_spent[8]; - curl_off_t cur_secs = (curl_off_t)p->timespent / 1000000; /* seconds */ + curl_off_t cur_secs = (curl_off_t)p->total.spent_us / 1000000; if(!p->headers_out) { if(data->state.resume_from) { @@ -661,13 +661,14 @@ static CURLcode pgrsupdate(struct Curl_easy *data, bool showprogress) int rc; if(data->set.fxferinfo) { /* There is a callback set, call that */ - Curl_set_in_callback(data, TRUE); + struct Curl_mapi_guard guard; + CURL_CBAPI_START(&guard, data, easy_fxferinfo); rc = data->set.fxferinfo(data->set.progress_client, data->progress.dl.total_size, data->progress.dl.cur_size, data->progress.ul.total_size, data->progress.ul.cur_size); - Curl_set_in_callback(data, FALSE); + CURL_CBAPI_END(&guard); if(rc != CURL_PROGRESSFUNC_CONTINUE) { if(rc) { failf(data, "Callback aborted"); @@ -678,13 +679,14 @@ static CURLcode pgrsupdate(struct Curl_easy *data, bool showprogress) } else if(data->set.fprogress) { /* The older deprecated callback is set, call that */ - Curl_set_in_callback(data, TRUE); + struct Curl_mapi_guard guard; + CURL_CBAPI_START(&guard, data, easy_fprogress); rc = data->set.fprogress(data->set.progress_client, (double)data->progress.dl.total_size, (double)data->progress.dl.cur_size, (double)data->progress.ul.total_size, (double)data->progress.ul.cur_size); - Curl_set_in_callback(data, FALSE); + CURL_CBAPI_END(&guard); if(rc != CURL_PROGRESSFUNC_CONTINUE) { if(rc) { failf(data, "Callback aborted"); @@ -713,16 +715,28 @@ CURLcode Curl_pgrsUpdate(struct Curl_easy *data) return pgrs_update(data, Curl_pgrs_now(data)); } -CURLcode Curl_pgrsCheck(struct Curl_easy *data) +CURLcode Curl_pgrsUpdateX(struct Curl_easy *data, + const struct curltime *pnow) +{ + return pgrs_update(data, pnow); +} + +CURLcode Curl_pgrsCheckX(struct Curl_easy *data, + const struct curltime *pnow) { CURLcode result; - result = pgrs_update(data, Curl_pgrs_now(data)); + result = pgrs_update(data, pnow); if(!result && !data->req.done) - result = pgrs_speedcheck(data, Curl_pgrs_now(data)); + result = pgrs_speedcheck(data, pnow); return result; } +CURLcode Curl_pgrsCheck(struct Curl_easy *data) +{ + return Curl_pgrsCheckX(data, Curl_pgrs_now(data)); +} + /* * Update all progress, do not do progress meter/callbacks. */ @@ -734,5 +748,24 @@ void Curl_pgrsUpdate_nometer(struct Curl_easy *data) void Curl_pgrsCompleted(struct Curl_easy *data) { struct Progress * const p = &data->progress; - p->timespent = curlx_ptimediff_us(Curl_pgrs_now(data), &p->start); + p->total.spent_us = curlx_ptimediff_us(Curl_pgrs_now(data), &p->start); +} + +timediff_t Curl_pgrs_since_ms(struct Curl_easy *data, + const struct curltime *pnow, + timerid timer) +{ + if(!pnow) + pnow = Curl_pgrs_now(data); + switch(timer) { + case TIMER_STARTOP: + return (curlx_ptimediff_us(pnow, &data->progress.start) - + data->progress.delta.startop_us) / 1000; + case TIMER_STARTSINGLE: + return (curlx_ptimediff_us(pnow, &data->progress.start) - + data->progress.delta.startsingle_us) / 1000; + default: + DEBUGASSERT(0); + return 0; + } } diff --git a/Utilities/cmcurl/lib/progress.h b/Utilities/cmcurl/lib/progress.h index 1f2a4e71c3..19bb09517c 100644 --- a/Utilities/cmcurl/lib/progress.h +++ b/Utilities/cmcurl/lib/progress.h @@ -38,7 +38,6 @@ typedef enum { TIMER_PRETRANSFER, TIMER_STARTTRANSFER, TIMER_POSTRANSFER, - TIMER_STARTACCEPT, TIMER_REDIRECT, TIMER_LAST /* must be last */ } timerid; @@ -47,7 +46,7 @@ typedef enum { const struct curltime *Curl_pgrs_now(struct Curl_easy *data); int Curl_pgrsDone(struct Curl_easy *data); -void Curl_pgrsStartNow(struct Curl_easy *data); +void Curl_pgrsStart(struct Curl_easy *data, const struct curltime *pnow); void Curl_pgrsSetDownloadSize(struct Curl_easy *data, curl_off_t size); void Curl_pgrsSetUploadSize(struct Curl_easy *data, curl_off_t size); CURLcode Curl_pgrs_deliver_check(struct Curl_easy *data, size_t delta); @@ -58,10 +57,12 @@ void Curl_pgrsSetUploadCounter(struct Curl_easy *data, curl_off_t size); /* perform progress update, invoking callbacks at intervals */ CURLcode Curl_pgrsUpdate(struct Curl_easy *data); +CURLcode Curl_pgrsUpdateX(struct Curl_easy *data, const struct curltime *pnow); /* perform progress update, no callbacks invoked */ void Curl_pgrsUpdate_nometer(struct Curl_easy *data); /* perform progress update with callbacks and speed checks */ CURLcode Curl_pgrsCheck(struct Curl_easy *data); +CURLcode Curl_pgrsCheckX(struct Curl_easy *data, const struct curltime *pnow); /* Inform progress/speedcheck about receive/send pausing */ void Curl_pgrsRecvPause(struct Curl_easy *data, bool enable); @@ -85,4 +86,8 @@ void Curl_pgrsEarlyData(struct Curl_easy *data, curl_off_t sent); void Curl_pgrsCompleted(struct Curl_easy *data); +timediff_t Curl_pgrs_since_ms(struct Curl_easy *data, + const struct curltime *pnow, + timerid timer); + #endif /* HEADER_CURL_PROGRESS_H */ diff --git a/Utilities/cmcurl/lib/protocol.c b/Utilities/cmcurl/lib/protocol.c index 8d57c058c6..3bfcd88ebe 100644 --- a/Utilities/cmcurl/lib/protocol.c +++ b/Utilities/cmcurl/lib/protocol.c @@ -462,72 +462,198 @@ const struct Curl_scheme Curl_scheme_wss = { PORT_HTTPS /* defport */ }; +static const struct Curl_scheme *two_letter_scheme(const char *scheme) +{ + if((Curl_raw_tolower(scheme[0]) == 'w') && + (Curl_raw_tolower(scheme[1]) == 's')) + return &Curl_scheme_ws; + return NULL; +} + +static const struct Curl_scheme *three_letter_scheme(const char *scheme) +{ + char s0 = Curl_raw_tolower(scheme[0]); + char s1 = Curl_raw_tolower(scheme[1]); + char s2 = Curl_raw_tolower(scheme[2]); + if(s0 == 'f') { + if(s1 == 't' && s2 == 'p') + return &Curl_scheme_ftp; + } + else if(s0 == 'w') { + if(s1 == 's' && s2 == 's') + return &Curl_scheme_wss; + } + else if(s0 == 's') { + if(s1 == 'c' && s2 == 'p') + return &Curl_scheme_scp; + if(s1 == 'm' && s2 == 'b') + return &Curl_scheme_smb; + } + return NULL; +} + +static const struct Curl_scheme *four_letter_scheme(const char *scheme) +{ + char s0 = Curl_raw_tolower(scheme[0]); + char s1 = Curl_raw_tolower(scheme[1]); + char s2 = Curl_raw_tolower(scheme[2]); + char s3 = Curl_raw_tolower(scheme[3]); + if(s3 == 'p') { + if(s0 == 'h') { + if(s1 == 't' && s2 == 't') + return &Curl_scheme_http; + } + else if(s0 == 'i') { + if(s1 == 'm' && s2 == 'a') + return &Curl_scheme_imap; + } + else if(s0 == 'l') { + if(s1 == 'd' && s2 == 'a') + return &Curl_scheme_ldap; + } + else if(s0 == 'r') { + if(s1 == 't' && s2 == 's') + return &Curl_scheme_rtsp; + } + else if(s0 == 't') { + if(s1 == 'f' && s2 == 't') + return &Curl_scheme_tftp; + } + else if(s0 == 's') { + if(s1 == 'f' && s2 == 't') + return &Curl_scheme_sftp; + if(s1 == 'm' && s2 == 't') + return &Curl_scheme_smtp; + } + } + else if(s0 == 'f') { + if(s1 == 't' && s2 == 'p' && s3 == 's') + return &Curl_scheme_ftps; + if(s1 == 'i' && s2 == 'l' && s3 == 'e') + return &Curl_scheme_file; + } + else if(s0 == 'm') { + if(s1 == 'q' && s2 == 't' && s3 == 't') + return &Curl_scheme_mqtt; + } + else if(s0 == 'p') { + if(s1 == 'o' && s2 == 'p' && s3 == '3') + return &Curl_scheme_pop3; + } + else if(s0 == 'd') { + if(s1 == 'i' && s2 == 'c' && s3 == 't') + return &Curl_scheme_dict; + } + else if(s0 == 's') { + if(s1 == 'm' && s2 == 'b' && s3 == 's') + return &Curl_scheme_smbs; + } + return NULL; +} + +static const struct Curl_scheme *five_letter_scheme(const char *scheme) +{ + char s4 = Curl_raw_tolower(scheme[4]); + if(s4 == 's') { + char s0 = Curl_raw_tolower(scheme[0]); + char s1 = Curl_raw_tolower(scheme[1]); + char s2 = Curl_raw_tolower(scheme[2]); + char s3 = Curl_raw_tolower(scheme[3]); + if(s3 == 'p') { + switch(s0) { + case 'h': + if(s1 == 't' && s2 == 't') + return &Curl_scheme_https; + break; + case 'l': + if(s1 == 'd' && s2 == 'a') + return &Curl_scheme_ldaps; + break; + case 'i': + if(s1 == 'm' && s2 == 'a') + return &Curl_scheme_imaps; + break; + case 's': + if(s1 == 'm' && s2 == 't') + return &Curl_scheme_smtps; + break; + default: + break; + } + } + else if(s0 == 'p') { + if(s1 == 'o' && s2 == 'p' && s3 == '3') + return &Curl_scheme_pop3s; + } + else if(s0 == 'm') { + if(s1 == 'q' && s2 == 't' && s3 == 't') + return &Curl_scheme_mqtts; + } + else if(s0 == 's') { + if(s1 == 'o' && s2 == 'c' && s3 == 'k') + return &Curl_scheme_socks; + } + } + return NULL; +} + +static const struct Curl_scheme *six_letter_scheme(const char *scheme) +{ + char s0 = Curl_raw_tolower(scheme[0]); + switch(s0) { + case 's': + if(curl_strnequal("ocks4", &scheme[1], 5)) + return &Curl_scheme_socks4; + if(curl_strnequal("ocks5", &scheme[1], 5)) + return &Curl_scheme_socks5; + break; + case 'g': + if(curl_strnequal("opher", &scheme[1], 5)) + return &Curl_scheme_gopher; + break; + case 't': + if(curl_strnequal("elnet", &scheme[1], 5)) + return &Curl_scheme_telnet; + break; + } + return NULL; +} + +static const struct Curl_scheme *seven_letter_scheme(const char *scheme) +{ + char s0 = Curl_raw_tolower(scheme[0]); + if(s0 == 's') { + if(curl_strnequal("ocks4a", &scheme[1], 6)) + return &Curl_scheme_socks4a; + if(curl_strnequal("ocks5h", &scheme[1], 6)) + return &Curl_scheme_socks5h; + } + else if(s0 == 'g') { + if(curl_strnequal("ophers", &scheme[1], 6)) + return &Curl_scheme_gophers; + } + return NULL; +} + /* Returns a struct scheme pointer if the name is a known scheme. Check the ->run struct field for non-NULL to figure out if an implementation is present. */ const struct Curl_scheme *Curl_getn_scheme(const char *scheme, size_t len) { - /* table generated by schemetable.c: - 1. gcc schemetable.c && ./a.out - 2. check how small the table gets - 3. tweak the hash algorithm, then rerun from 1 - 4. when the table is good enough - 5. copy the table into this source code - 6. make sure this function uses the same hash function that worked for - schemetable.c - */ - static const struct Curl_scheme * const all_schemes[59] = { NULL, - &Curl_scheme_pop3, NULL, - &Curl_scheme_smtps, - &Curl_scheme_socks, - &Curl_scheme_socks4, - &Curl_scheme_socks5, NULL, NULL, - &Curl_scheme_gophers, - &Curl_scheme_ws, - &Curl_scheme_sftp, - &Curl_scheme_socks4a, - &Curl_scheme_scp, - &Curl_scheme_rtsp, - &Curl_scheme_dict, NULL, NULL, - &Curl_scheme_gopher, NULL, NULL, NULL, - &Curl_scheme_wss, NULL, - &Curl_scheme_smb, NULL, - &Curl_scheme_ldap, - &Curl_scheme_ldaps, - &Curl_scheme_imap, NULL, NULL, NULL, - &Curl_scheme_imaps, - &Curl_scheme_https, - &Curl_scheme_tftp, - &Curl_scheme_telnet, NULL, NULL, NULL, - &Curl_scheme_file, - &Curl_scheme_smtp, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - &Curl_scheme_ftp, - &Curl_scheme_mqtt, NULL, - &Curl_scheme_socks5h, - &Curl_scheme_http, - &Curl_scheme_pop3s, NULL, - &Curl_scheme_mqtts, NULL, - &Curl_scheme_smbs, - &Curl_scheme_ftps, + typedef const struct Curl_scheme *(*letterfunc)(const char *ptr); + static const letterfunc parse[] = { + two_letter_scheme, + three_letter_scheme, + four_letter_scheme, + five_letter_scheme, + six_letter_scheme, + seven_letter_scheme }; - if(len && (len <= 7)) { - const char *s = scheme; - size_t l = len; - const struct Curl_scheme *h; - unsigned int c = 443; - while(l) { - c <<= 5; - c += (unsigned int)Curl_raw_tolower(*s); - s++; - l--; - } + if(len < 2 || len > 7) + return NULL; - h = all_schemes[c % 59]; - if(h && curl_strnequal(scheme, h->name, len) && !h->name[len]) - return h; - } - return NULL; + return parse[len - 2](scheme); } const struct Curl_scheme *Curl_get_scheme(const char *scheme) diff --git a/Utilities/cmcurl/lib/protocol.h b/Utilities/cmcurl/lib/protocol.h index 50e320d0f6..031bb834e4 100644 --- a/Utilities/cmcurl/lib/protocol.h +++ b/Utilities/cmcurl/lib/protocol.h @@ -209,8 +209,7 @@ struct Curl_protocol { #define PROTOPT_CLOSEACTION (1 << 2) /* need action before socket close */ /* some protocols will have to call the underlying functions without regard to what exact state the socket signals. IE even if the socket says "readable", - the send function might need to be called while uploading, or vice versa. -*/ + the send function might need to be called while uploading, or vice versa. */ #define PROTOPT_DIRLOCK (1 << 3) #define PROTOPT_NONETWORK (1 << 4) /* protocol does not use the network! */ #define PROTOPT_NEEDSPWD (1 << 5) /* needs a password, and if none is set it diff --git a/Utilities/cmcurl/lib/proxy.c b/Utilities/cmcurl/lib/proxy.c index 920dc45598..5d7c02579d 100644 --- a/Utilities/cmcurl/lib/proxy.c +++ b/Utilities/cmcurl/lib/proxy.c @@ -146,7 +146,7 @@ static bool match_host(const char *token, size_t tokenlen, /* A: example.com matches 'example.com' B: www.example.com matches 'example.com' C: nonexample.com DOES NOT match 'example.com' - */ + */ if(tokenlen == namelen) /* case A, exact match */ match = curl_strnequal(token, name, namelen); @@ -282,8 +282,6 @@ UNITTEST bool proxy_check_noproxy(const char *name, const char *no_proxy) static char *proxy_detect_proxy(struct Curl_easy *data, const struct Curl_scheme *scheme) { - char *proxy = NULL; - /* If proxy was not specified, we check for default proxy environment * variables, to enable i.e Lynx compliance: * @@ -301,58 +299,63 @@ static char *proxy_detect_proxy(struct Curl_easy *data, * For compatibility, the all-uppercase versions of these variables are * checked if the lowercase versions do not exist. */ - char proxy_env[20]; - const char *envp; - VERBOSE(envp = proxy_env); + const char *env_name = NULL; + char *proxy = NULL; + char name_buf[20]; - curl_msnprintf(proxy_env, sizeof(proxy_env), "%s_proxy", scheme->name); + /* Try scheme specific env var first, unless http(s). + * lowercase first, then uppercase. */ + if((scheme != &Curl_scheme_https) && (scheme != &Curl_scheme_http)) { + curl_msnprintf(name_buf, sizeof(name_buf), "%s_proxy", scheme->name); + env_name = name_buf; + proxy = curl_getenv(env_name); + if(!proxy) { + Curl_strntoupper(name_buf, name_buf, sizeof(name_buf)); + proxy = curl_getenv(env_name); + } + } - /* read the protocol proxy: */ - proxy = curl_getenv(proxy_env); - - /* - * We do not try the uppercase version of HTTP_PROXY because of - * security reasons: - * - * When curl is used in a webserver application - * environment (cgi or php), this environment variable can - * be controlled by the web server user by setting the - * http header 'Proxy:' to some value. - * - * This can cause 'internal' http/ftp requests to be - * arbitrarily redirected by any external attacker. - */ - if(!proxy && !curl_strequal("http_proxy", proxy_env)) { - /* There was no lowercase variable, try the uppercase version: */ - Curl_strntoupper(proxy_env, proxy_env, sizeof(proxy_env)); - proxy = curl_getenv(proxy_env); + if(!proxy && + ((scheme == &Curl_scheme_https) || (scheme == &Curl_scheme_wss))) { + /* Not found, check 'https' env vars, also for 'wss'. + * Again, first lowercase then uppercase. */ + env_name = "https_proxy"; + proxy = curl_getenv(env_name); + if(!proxy) { + env_name = "HTTPS_PROXY"; + proxy = curl_getenv(env_name); + } + } + else if(!proxy && + ((scheme == &Curl_scheme_http) || (scheme == &Curl_scheme_ws))) { + /* Not found, check 'http' env vars, also for 'ws'. + * We do NOT try the uppercase version 'HTTP_PROXY' because of + * security reasons: + * + * When curl is used in a webserver application + * environment (cgi or php), this environment variable can + * be controlled by the web server user by setting the + * http header 'Proxy:' to some value. + * + * This can cause 'internal' http/ftp requests to be + * arbitrarily redirected by any external attacker. + */ + env_name = "http_proxy"; + proxy = curl_getenv(env_name); } if(!proxy) { -#ifndef CURL_DISABLE_WEBSOCKETS - /* websocket proxy fallbacks */ - if(curl_strequal("ws_proxy", proxy_env)) { - proxy = curl_getenv("http_proxy"); - } - else if(curl_strequal("wss_proxy", proxy_env)) { - proxy = curl_getenv("https_proxy"); - if(!proxy) - proxy = curl_getenv("HTTPS_PROXY"); - } + /* still not found, last resort checks. */ + env_name = "all_proxy"; + proxy = curl_getenv(env_name); if(!proxy) { -#endif - envp = "all_proxy"; - proxy = curl_getenv(envp); /* default proxy to use */ - if(!proxy) { - envp = "ALL_PROXY"; - proxy = curl_getenv(envp); - } -#ifndef CURL_DISABLE_WEBSOCKETS + env_name = "ALL_PROXY"; + proxy = curl_getenv(env_name); } -#endif } + if(proxy) - infof(data, "Uses proxy env variable %s == '%s'", envp, proxy); + infof(data, "Uses proxy env variable %s == '%s'", env_name, proxy); return proxy; } @@ -448,21 +451,21 @@ static CURLcode parse_proxy(struct Curl_easy *data, if(proxyuser || proxypasswd) { result = Curl_creds_create(proxyuser, proxypasswd, NULL, NULL, - data->set.str[STRING_PROXY_SERVICE_NAME], + CURL_EASY_STR(data, STRING_PROXY_SERVICE_NAME), CREDS_URL, &proxyinfo->creds); if(result) goto error; } else if(!for_pre_proxy && - (data->set.str[STRING_PROXYUSERNAME] || - data->set.str[STRING_PROXYPASSWORD] || - data->set.str[STRING_PROXY_SERVICE_NAME])) { + (CURL_EASY_STR(data, STRING_PROXYUSERNAME) || + CURL_EASY_STR(data, STRING_PROXYPASSWORD) || + CURL_EASY_STR(data, STRING_PROXY_SERVICE_NAME))) { /* No user/passwd in URL, if this is not a pre-proxy, the * CURLOPT_PROXY* settings apply. */ - result = Curl_creds_create(data->set.str[STRING_PROXYUSERNAME], - data->set.str[STRING_PROXYPASSWORD], + result = Curl_creds_create(CURL_EASY_STR(data, STRING_PROXYUSERNAME), + CURL_EASY_STR(data, STRING_PROXYPASSWORD), NULL, NULL, - data->set.str[STRING_PROXY_SERVICE_NAME], + CURL_EASY_STR(data, STRING_PROXY_SERVICE_NAME), CREDS_OPTION, &proxyinfo->creds); } else @@ -495,7 +498,7 @@ static bool proxy_do_not_proxy(struct Curl_easy *data) if(data->state.origin->scheme->flags & PROTOPT_NONETWORK) return TRUE; - no_proxy = data->set.str[STRING_NOPROXY]; + no_proxy = CURL_EASY_STR(data, STRING_NOPROXY); if(!no_proxy) { const char *p = "no_proxy"; env_no_proxy = curl_getenv(p); @@ -518,6 +521,7 @@ CURLcode Curl_proxy_init_conn(struct Curl_easy *data, { char *proxy = NULL; char *pre_proxy = NULL; + const char *str = NULL; bool do_env_detect = TRUE; CURLcode result = CURLE_OK; @@ -533,9 +537,10 @@ CURLcode Curl_proxy_init_conn(struct Curl_easy *data, * Detect what (if any) proxy to use *************************************************************/ /* the empty config strings disable proxy use and env detects */ - if(data->set.str[STRING_PROXY]) { - if(*data->set.str[STRING_PROXY]) { - proxy = curlx_strdup(data->set.str[STRING_PROXY]); + str = CURL_EASY_STR(data, STRING_PROXY); + if(str) { + if(*str) { + proxy = curlx_strdup(str); /* if global proxy is set, this is it */ if(!proxy) { failf(data, "memory shortage"); @@ -547,9 +552,10 @@ CURLcode Curl_proxy_init_conn(struct Curl_easy *data, do_env_detect = FALSE; } - if(data->set.str[STRING_PRE_PROXY]) { - if(*data->set.str[STRING_PRE_PROXY]) { - pre_proxy = curlx_strdup(data->set.str[STRING_PRE_PROXY]); + str = CURL_EASY_STR(data, STRING_PRE_PROXY); + if(str) { + if(*str) { + pre_proxy = curlx_strdup(str); /* if global socks proxy is set, this is it */ if(!pre_proxy) { failf(data, "memory shortage"); diff --git a/Utilities/cmcurl/lib/psl.c b/Utilities/cmcurl/lib/psl.c index 195841f3a3..1e3efbb0fa 100644 --- a/Utilities/cmcurl/lib/psl.c +++ b/Utilities/cmcurl/lib/psl.c @@ -55,7 +55,8 @@ const psl_ctx_t *Curl_psl_use(struct Curl_easy *easy) Curl_share_lock(easy, CURL_LOCK_DATA_PSL, CURL_LOCK_ACCESS_SHARED); now_sec = Curl_pgrs_now(easy)->tv_sec; if(!pslcache->psl || pslcache->expires <= now_sec) { - /* Let a chance to other threads to do the job: avoids deadlock. */ + /* Release the shared lock so another thread can refresh the cache and + avoid deadlock. */ Curl_share_unlock(easy, CURL_LOCK_DATA_PSL); /* Update cache: this needs an exclusive lock. */ diff --git a/Utilities/cmcurl/lib/rand.c b/Utilities/cmcurl/lib/rand.c index 3260fe3345..d0527aa3c0 100644 --- a/Utilities/cmcurl/lib/rand.c +++ b/Utilities/cmcurl/lib/rand.c @@ -33,23 +33,7 @@ #include "rand.h" #include "escape.h" -#ifdef _WIN32 -#include -#ifndef STATUS_SUCCESS -#define STATUS_SUCCESS ((NTSTATUS)0x00000000L) -#endif - -CURLcode Curl_win32_random(unsigned char *entropy, size_t length) -{ - memset(entropy, 0, length); - - if(BCryptGenRandom(NULL, entropy, (ULONG)length, - BCRYPT_USE_SYSTEM_PREFERRED_RNG) != STATUS_SUCCESS) - return CURLE_FAILED_INIT; - - return CURLE_OK; -} -#endif +#include "curlx/winapi.h" #ifndef USE_SSL /* ---- possibly non-cryptographic version following ---- */ @@ -64,7 +48,7 @@ static CURLcode weak_random(struct Curl_easy *data, #ifdef _WIN32 (void)data; { - CURLcode result = Curl_win32_random(entropy, length); + CURLcode result = curlx_win32_random(entropy, length); if(result != CURLE_NOT_BUILT_IN) return result; } @@ -224,7 +208,7 @@ CURLcode Curl_rand_alnum(struct Curl_easy *data, unsigned char *rnd, size_t num) { CURLcode result = CURLE_OK; - const unsigned int alnumspace = sizeof(alnum) - 1; + const unsigned int alnumspace = CURL_CSTRLEN(alnum); unsigned int r; DEBUGASSERT(num > 1); diff --git a/Utilities/cmcurl/lib/rand.h b/Utilities/cmcurl/lib/rand.h index a02717074f..452c1b67ef 100644 --- a/Utilities/cmcurl/lib/rand.h +++ b/Utilities/cmcurl/lib/rand.h @@ -49,10 +49,4 @@ CURLcode Curl_rand_hex(struct Curl_easy *data, unsigned char *rnd, size_t num); CURLcode Curl_rand_alnum(struct Curl_easy *data, unsigned char *rnd, size_t num); -#ifdef _WIN32 -/* Random generator shared between the Schannel vtls and Curl_rand*() - functions */ -CURLcode Curl_win32_random(unsigned char *entropy, size_t length); -#endif - #endif /* HEADER_CURL_RAND_H */ diff --git a/Utilities/cmcurl/lib/ratelimit.c b/Utilities/cmcurl/lib/ratelimit.c index 97af01c788..a5efc7c11a 100644 --- a/Utilities/cmcurl/lib/ratelimit.c +++ b/Utilities/cmcurl/lib/ratelimit.c @@ -200,9 +200,15 @@ bool Curl_rlimit_is_blocked(struct Curl_rlimit *r) int64_t Curl_rlimit_avail(struct Curl_rlimit *r, const struct curltime *pts) { + struct curltime ts; + if(r->blocked) return 0; else if(r->rate_per_step) { + if(!pts) { + curlx_pnow(&ts); + pts = &ts; + } rlimit_update(r, pts); return r->tokens; } @@ -210,13 +216,18 @@ int64_t Curl_rlimit_avail(struct Curl_rlimit *r, return INT64_MAX; } -void Curl_rlimit_drain(struct Curl_rlimit *r, - size_t tokens, +void Curl_rlimit_drain(struct Curl_rlimit *r, size_t tokens, const struct curltime *pts) { + struct curltime ts; + if(r->blocked || !r->rate_per_step) return; + if(!pts) { + curlx_pnow(&ts); + pts = &ts; + } rlimit_update(r, pts); #if 8 <= SIZEOF_SIZE_T if(tokens > INT64_MAX) { diff --git a/Utilities/cmcurl/lib/ratelimit.h b/Utilities/cmcurl/lib/ratelimit.h index 3c3e38b895..3548f55608 100644 --- a/Utilities/cmcurl/lib/ratelimit.h +++ b/Utilities/cmcurl/lib/ratelimit.h @@ -90,14 +90,15 @@ bool Curl_rlimit_active(struct Curl_rlimit *r); bool Curl_rlimit_is_blocked(struct Curl_rlimit *r); int64_t Curl_rlimit_per_step(struct Curl_rlimit *r); -/* Return how many tokens are available to spend, may be negative */ +/* Return how many tokens are available to spend, may be negative. + * Pass timestamp or NULL. */ int64_t Curl_rlimit_avail(struct Curl_rlimit *r, const struct curltime *pts); /* Drain tokens from the ratelimit, give an estimate of how many tokens - * remain to be drained in the future (-1 for unknown). */ -void Curl_rlimit_drain(struct Curl_rlimit *r, - size_t tokens, + * remain to be drained in the future (-1 for unknown). + * Pass timestamp or NULL. */ +void Curl_rlimit_drain(struct Curl_rlimit *r, size_t tokens, const struct curltime *pts); /* Block/unblock ratelimiting. A blocked ratelimit has 0 tokens available. */ diff --git a/Utilities/cmcurl/lib/request.c b/Utilities/cmcurl/lib/request.c index 56dd2c4a15..51672bfe00 100644 --- a/Utilities/cmcurl/lib/request.c +++ b/Utilities/cmcurl/lib/request.c @@ -26,7 +26,6 @@ #include "urldata.h" #include "cfilters.h" #include "curlx/dynbuf.h" -#include "doh.h" #include "progress.h" #include "request.h" #include "sendf.h" diff --git a/Utilities/cmcurl/lib/request.h b/Utilities/cmcurl/lib/request.h index e67865a984..a7c0a93dd3 100644 --- a/Utilities/cmcurl/lib/request.h +++ b/Utilities/cmcurl/lib/request.h @@ -95,16 +95,22 @@ struct SingleRequest { first one */ int httpcode; /* error code from the 'HTTP/1.? XXX' or 'RTSP/1.? XXX' line */ - unsigned char httpversion_sent; /* Version in request (09, 10, 11, etc.) */ - unsigned char httpversion; /* Version in response (09, 10, 11, etc.) */ - enum upgrade101 upgr101; /* 101 upgrade state */ + uint8_t httpversion_sent; /* Version in request (09, 10, 11, etc.) */ + uint8_t httpversion; /* Version in response (09, 10, 11, etc.) */ + uint8_t upgr101; /* 101 upgrade state */ + uint8_t io_flags; /* REQ_IO_RECV | REQ_IO_SEND */ /* Client Writer stack, handles transfer- and content-encodings, protocol * checks, pausing by client callbacks. */ - struct Curl_cwriter *writer_stack; + struct { + struct Curl_cwriter *stack; + BIT(paused); + } writer; /* Client Reader stack, handles transfer- and content-encodings, protocol * checks, pausing by client callbacks. */ - struct Curl_creader *reader_stack; + struct { + struct Curl_creader *stack; + } reader; struct bufq sendbuf; /* data which needs to be send to the server */ size_t sendbuf_hds_len; /* amount of header bytes in sendbuf */ time_t timeofdoc; @@ -112,7 +118,6 @@ struct SingleRequest { header data */ char *newurl; /* Set to the new URL to use when a redirect or a retry is wanted */ - uint8_t io_flags; /* REQ_IO_RECV | REQ_IO_SEND */ char *hd_auth; /* Authorization header, full HTTP/1.x line */ #ifndef CURL_DISABLE_PROXY diff --git a/Utilities/cmcurl/lib/rtsp.c b/Utilities/cmcurl/lib/rtsp.c index 8c5cdd5643..fabdda3d3e 100644 --- a/Utilities/cmcurl/lib/rtsp.c +++ b/Utilities/cmcurl/lib/rtsp.c @@ -195,7 +195,7 @@ static CURLcode rtsp_done(struct Curl_easy *data, } static CURLcode rtsp_setup_body(struct Curl_easy *data, - Curl_RtspReq rtspreq, + unsigned char rtspreq, struct dynbuf *reqp) { CURLcode result; @@ -270,26 +270,171 @@ static CURLcode rtsp_setup_body(struct Curl_easy *data, return result; } +struct rtspselect { + const char *method; + bool no_body; +}; + +static CURLcode pick_method(struct Curl_easy *data, + const unsigned char rtspreq, + const char **p) +{ + static const struct rtspselect req[] = { + { "OPTIONS", TRUE }, + { "DESCRIBE", FALSE }, + { "ANNOUNCE", TRUE }, + { "SETUP", TRUE }, + { "PLAY", TRUE }, + { "PAUSE", TRUE }, + { "TEARDOWN", TRUE }, + { "GET_PARAMETER", FALSE }, + { "SET_PARAMETER", TRUE }, + { "RECORD", TRUE }, + { "", FALSE }, /* RECEIVE: treat interleaved RTP as body */ + }; + /* this is verified already in setopt, this is just added precaution */ + DEBUGASSERT((rtspreq > RTSPREQ_NONE) && (rtspreq < RTSPREQ_LAST)); + if((rtspreq <= RTSPREQ_NONE) || (rtspreq >= RTSPREQ_LAST)) + return CURLE_BAD_FUNCTION_ARGUMENT; + *p = req[rtspreq - 1].method; + data->req.no_body = req[rtspreq - 1].no_body; + return CURLE_OK; +} + +/* Allocate and store a header string. */ +static CURLcode rtsp_header_alloc(const char *header_name, + const char *value, + char **target) +{ + if(!value) + return CURLE_OK; + curlx_free(*target); + *target = curl_maprintf("%s: %s\r\n", header_name, value); + if(!*target) + return CURLE_OUT_OF_MEMORY; + return CURLE_OK; +} + +struct rtsp_blocks { + const char *request; + const char *session_id; + const char *accept; + const char *range; + const char *stream_uri; + const char *hd_proxy_auth; + const char *hd_auth; + char *referrer; + char *accept_encoding; + char *transport; + BIT(transport_alloc); /* if 'transport' is allocated */ +}; + +static CURLcode rtsp_setup_request(struct Curl_easy *data, + struct rtsp_blocks *b, + const unsigned char rtspreq) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + + b->session_id = CURL_EASY_STR(data, STRING_RTSP_SESSION_ID); + + /* Stream URI. Default to server '*' if not specified */ + if(CURL_EASY_STR(data, STRING_RTSP_STREAM_URI)) + b->stream_uri = CURL_EASY_STR(data, STRING_RTSP_STREAM_URI); + else + b->stream_uri = "*"; + + /* Transport Header for SETUP requests */ + b->transport = Curl_checkheaders(data, STRCONST("Transport")); + if(rtspreq == RTSPREQ_SETUP && !b->transport) { + /* New Transport: setting? */ + if(CURL_EASY_STR(data, STRING_RTSP_TRANSPORT)) { + result = rtsp_header_alloc( + "Transport", CURL_EASY_STR(data, STRING_RTSP_TRANSPORT), + &b->transport); + if(result) + return result; + b->transport_alloc = TRUE; + } + else { + failf(data, + "Refusing to issue an RTSP SETUP without a Transport: header."); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + } + + /* Accept Headers for DESCRIBE requests */ + if(rtspreq == RTSPREQ_DESCRIBE) { + /* Accept Header */ + b->accept = Curl_checkheaders(data, STRCONST("Accept")) ? + NULL : "Accept: application/sdp\r\n"; + + /* Accept-Encoding header */ + if(!Curl_checkheaders(data, STRCONST("Accept-Encoding")) && + CURL_EASY_STR(data, STRING_ENCODING)) { + result = rtsp_header_alloc("Accept-Encoding", + CURL_EASY_STR(data, STRING_ENCODING), + &b->accept_encoding); + if(result) + return result; + } + } + + /* setup the authentication headers */ + result = Curl_http_output_auth(data, conn, b->request, HTTPREQ_GET, + b->stream_uri, NULL, FALSE); + if(result) + return result; + +#ifndef CURL_DISABLE_PROXY + b->hd_proxy_auth = data->req.hd_proxy_auth; +#endif + b->hd_auth = data->req.hd_auth; + + /* Referrer */ + if(Curl_bufref_ptr(&data->state.referer) && + !Curl_checkheaders(data, STRCONST("Referer"))) { + b->referrer = + curl_maprintf("Referer: %s\r\n", Curl_bufref_ptr(&data->state.referer)); + if(!b->referrer) + result = CURLE_OUT_OF_MEMORY; + } + + /* + * Range Header + * Only applies to PLAY, PAUSE, RECORD + * + * Go ahead and use the Range stuff supplied for HTTP + */ + if(!result && + data->state.use_range && + ((rtspreq == RTSPREQ_PLAY) || + (rtspreq == RTSPREQ_PAUSE) || + (rtspreq == RTSPREQ_RECORD))) { + + /* Check to see if there is a range set in the custom headers */ + if(!Curl_checkheaders(data, STRCONST("Range")) && data->state.range) { + result = rtsp_header_alloc("Range", + data->state.range, + &data->state.rangeline); + if(!result) + b->range = data->state.rangeline; + } + } + return result; +} + +#define HTTPVERSION 11 /* RTSP is close to HTTP/1.1, sort of... */ + static CURLcode rtsp_do(struct Curl_easy *data, bool *done) { - struct connectdata *conn = data->conn; CURLcode result = CURLE_OK; - const Curl_RtspReq rtspreq = data->set.rtspreq; + const unsigned char rtspreq = data->set.rtspreq; + const char *str; struct RTSP *rtsp = Curl_meta_get(data, CURL_META_RTSP_EASY); struct dynbuf req_buffer; - const unsigned char httpversion = 11; /* RTSP is close to HTTP/1.1, sort - of... */ - const char *p_request = NULL; - const char *p_session_id = NULL; - const char *p_accept = NULL; - const char *p_accept_encoding = NULL; - const char *p_range = NULL; - const char *p_referrer = NULL; - const char *p_stream_uri = NULL; - const char *p_transport = NULL; - const char *p_uagent = NULL; - const char *p_hd_proxy_auth = NULL; - const char *p_hd_auth = NULL; + struct rtsp_blocks block; + memset(&block, 0, sizeof(block)); *done = TRUE; if(!rtsp) @@ -301,177 +446,19 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) rtsp->CSeq_sent = data->state.rtsp_next_client_CSeq; rtsp->CSeq_recv = 0; - /* Setup the 'p_request' pointer to the proper p_request string - * Since all RTSP requests are included here, there is no need to - * support custom requests like HTTP. - **/ - data->req.no_body = TRUE; /* most requests do not contain a body */ - switch(rtspreq) { - default: - failf(data, "Got invalid RTSP request"); - return CURLE_BAD_FUNCTION_ARGUMENT; - case RTSPREQ_OPTIONS: - p_request = "OPTIONS"; - break; - case RTSPREQ_DESCRIBE: - p_request = "DESCRIBE"; - data->req.no_body = FALSE; - break; - case RTSPREQ_ANNOUNCE: - p_request = "ANNOUNCE"; - break; - case RTSPREQ_SETUP: - p_request = "SETUP"; - break; - case RTSPREQ_PLAY: - p_request = "PLAY"; - break; - case RTSPREQ_PAUSE: - p_request = "PAUSE"; - break; - case RTSPREQ_TEARDOWN: - p_request = "TEARDOWN"; - break; - case RTSPREQ_GET_PARAMETER: - /* GET_PARAMETER's no_body status is determined later */ - p_request = "GET_PARAMETER"; - data->req.no_body = FALSE; - break; - case RTSPREQ_SET_PARAMETER: - p_request = "SET_PARAMETER"; - break; - case RTSPREQ_RECORD: - p_request = "RECORD"; - break; - case RTSPREQ_RECEIVE: - p_request = ""; - /* Treat interleaved RTP as body */ - data->req.no_body = FALSE; - break; - case RTSPREQ_LAST: - failf(data, "Got invalid RTSP request: RTSPREQ_LAST"); - return CURLE_BAD_FUNCTION_ARGUMENT; - } + /* Setup the 'p_request' pointer to the proper method. */ + result = pick_method(data, rtspreq, &block.request); + if(result) + goto out; if(rtspreq == RTSPREQ_RECEIVE) { Curl_xfer_setup_recv(data, FIRSTSOCKET, -1); goto out; } - p_session_id = data->set.str[STRING_RTSP_SESSION_ID]; - if(!p_session_id && - (rtspreq & ~(Curl_RtspReq)(RTSPREQ_OPTIONS | - RTSPREQ_DESCRIBE | - RTSPREQ_SETUP))) { - failf(data, "Refusing to issue an RTSP request [%s] without a session ID.", - p_request); - result = CURLE_BAD_FUNCTION_ARGUMENT; - goto out; - } - - /* Stream URI. Default to server '*' if not specified */ - if(data->set.str[STRING_RTSP_STREAM_URI]) { - p_stream_uri = data->set.str[STRING_RTSP_STREAM_URI]; - } - else { - p_stream_uri = "*"; - } - - /* Transport Header for SETUP requests */ - p_transport = Curl_checkheaders(data, STRCONST("Transport")); - if(rtspreq == RTSPREQ_SETUP && !p_transport) { - /* New Transport: setting? */ - if(data->set.str[STRING_RTSP_TRANSPORT]) { - curlx_free(data->state.aptr.rtsp_transport); - data->state.aptr.rtsp_transport = - curl_maprintf("Transport: %s\r\n", - data->set.str[STRING_RTSP_TRANSPORT]); - if(!data->state.aptr.rtsp_transport) - return CURLE_OUT_OF_MEMORY; - } - else { - failf(data, - "Refusing to issue an RTSP SETUP without a Transport: header."); - result = CURLE_BAD_FUNCTION_ARGUMENT; - goto out; - } - - p_transport = data->state.aptr.rtsp_transport; - } - - /* Accept Headers for DESCRIBE requests */ - if(rtspreq == RTSPREQ_DESCRIBE) { - /* Accept Header */ - p_accept = Curl_checkheaders(data, STRCONST("Accept")) ? - NULL : "Accept: application/sdp\r\n"; - - /* Accept-Encoding header */ - if(!Curl_checkheaders(data, STRCONST("Accept-Encoding")) && - data->set.str[STRING_ENCODING]) { - curlx_free(data->state.aptr.accept_encoding); - data->state.aptr.accept_encoding = - curl_maprintf("Accept-Encoding: %s\r\n", - data->set.str[STRING_ENCODING]); - - if(!data->state.aptr.accept_encoding) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - p_accept_encoding = data->state.aptr.accept_encoding; - } - } - - /* The User-Agent string might have been allocated already, because - it might have been used in the proxy connect, but if we have got a header - with the user-agent string specified, we erase the previously made string - here. */ - if(Curl_checkheaders(data, STRCONST("User-Agent")) && - data->state.aptr.uagent) { - curlx_safefree(data->state.aptr.uagent); - } - else if(!Curl_checkheaders(data, STRCONST("User-Agent")) && - data->set.str[STRING_USERAGENT]) { - p_uagent = data->state.aptr.uagent; - } - - /* setup the authentication headers */ - result = Curl_http_output_auth(data, conn, p_request, HTTPREQ_GET, - p_stream_uri, NULL, FALSE); + result = rtsp_setup_request(data, &block, rtspreq); if(result) goto out; - -#ifndef CURL_DISABLE_PROXY - p_hd_proxy_auth = data->req.hd_proxy_auth; -#endif - p_hd_auth = data->req.hd_auth; - - /* Referrer */ - curlx_safefree(data->state.aptr.ref); - if(Curl_bufref_ptr(&data->state.referer) && - !Curl_checkheaders(data, STRCONST("Referer"))) - data->state.aptr.ref = - curl_maprintf("Referer: %s\r\n", Curl_bufref_ptr(&data->state.referer)); - - p_referrer = data->state.aptr.ref; - - /* - * Range Header - * Only applies to PLAY, PAUSE, RECORD - * - * Go ahead and use the Range stuff supplied for HTTP - */ - if(data->state.use_range && - (rtspreq & (RTSPREQ_PLAY | RTSPREQ_PAUSE | RTSPREQ_RECORD))) { - - /* Check to see if there is a range set in the custom headers */ - if(!Curl_checkheaders(data, STRCONST("Range")) && data->state.range) { - curlx_free(data->state.aptr.rangeline); - data->state.aptr.rangeline = curl_maprintf("Range: %s\r\n", - data->state.range); - p_range = data->state.aptr.rangeline; - } - } - /* * Sanity check the custom headers */ @@ -490,7 +477,7 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) curlx_dyn_addf(&req_buffer, "%s %s RTSP/1.0\r\n" /* Request Stream-URI RTSP/1.0 */ "CSeq: %u\r\n", /* CSeq */ - p_request, p_stream_uri, rtsp->CSeq_sent); + block.request, block.stream_uri, rtsp->CSeq_sent); if(result) goto out; @@ -498,8 +485,8 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) * Rather than do a normal alloc line, keep the session_id unformatted * to make comparison easier */ - if(p_session_id) { - result = curlx_dyn_addf(&req_buffer, "Session: %s\r\n", p_session_id); + if(block.session_id) { + result = curlx_dyn_addf(&req_buffer, "Session: %s\r\n", block.session_id); if(result) goto out; } @@ -513,19 +500,25 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) "%s" /* accept-encoding */ "%s" /* range */ "%s" /* referrer */ - "%s" /* user-agent */ - "%s" /* hd_proxy_auth */ - "%s" /* hd_auth */ , - p_transport ? p_transport : "", - p_accept ? p_accept : "", - p_accept_encoding ? p_accept_encoding : "", - p_range ? p_range : "", - p_referrer ? p_referrer : "", - p_uagent ? p_uagent : "", - p_hd_proxy_auth ? p_hd_proxy_auth : "", - p_hd_auth ? p_hd_auth : ""); + block.transport ? block.transport : "", + block.accept ? block.accept : "", + block.accept_encoding ? block.accept_encoding : "", + block.range ? block.range : "", + block.referrer ? block.referrer : ""); + str = CURL_EASY_STR(data, STRING_USERAGENT); + if(!result && str && *str && + !Curl_checkheaders(data, STRCONST("User-Agent"))) + result = curlx_dyn_addf(&req_buffer, + "User-Agent: %s\r\n", str); + + if(!result) + result = curlx_dyn_addf(&req_buffer, + "%s" /* hd_proxy_auth */ + "%s", /* hd_auth */ + block.hd_proxy_auth ? block.hd_proxy_auth : "", + block.hd_auth ? block.hd_auth : ""); if(result) goto out; @@ -535,7 +528,7 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) goto out; } - result = Curl_add_custom_headers(data, FALSE, httpversion, &req_buffer); + result = Curl_add_custom_headers(data, FALSE, HTTPVERSION, &req_buffer); if(result) goto out; @@ -551,7 +544,7 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) Curl_xfer_setup_sendrecv(data, FIRSTSOCKET, -1); /* issue the request */ - result = Curl_req_send(data, &req_buffer, httpversion); + result = Curl_req_send(data, &req_buffer, HTTPVERSION); if(result) { failf(data, "Failed sending RTSP request"); goto out; @@ -567,6 +560,10 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) result = Curl_pgrsUpdate(data); } out: + if(block.transport_alloc) + curlx_free(block.transport); + curlx_free(block.accept_encoding); + curlx_free(block.referrer); curlx_dyn_free(&req_buffer); return result; } @@ -598,6 +595,7 @@ static CURLcode rtp_write_body_junk(struct Curl_easy *data, static CURLcode rtp_client_write(struct Curl_easy *data, const char *ptr, size_t len) { + struct Curl_mapi_guard guard; size_t wrote; curl_write_callback writeit; void *user_ptr; @@ -620,9 +618,9 @@ static CURLcode rtp_client_write(struct Curl_easy *data, const char *ptr, user_ptr = data->set.out; } - Curl_set_in_callback(data, TRUE); + CURL_CBAPI_START(&guard, data, easy_fwrite_rtp); wrote = writeit((char *)CURL_UNCONST(ptr), 1, len, user_ptr); - Curl_set_in_callback(data, FALSE); + CURL_CBAPI_END(&guard); if(wrote == CURL_WRITEFUNC_PAUSE) { failf(data, "Cannot pause RTP"); @@ -978,7 +976,7 @@ CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, const char *header) data->state.rtsp_CSeq_recv = rtsp->CSeq_recv = (uint32_t)CSeq; } else if(checkprefix("Session:", header)) { - const char *start, *end; + const char *start, *end, *str; size_t idlen; /* Find the first non-space letter */ @@ -1001,24 +999,24 @@ CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, const char *header) end++; idlen = end - start; - if(data->set.str[STRING_RTSP_SESSION_ID]) { + str = CURL_EASY_STR(data, STRING_RTSP_SESSION_ID); + if(str) { /* If the Session ID is set, then compare */ - if(strlen(data->set.str[STRING_RTSP_SESSION_ID]) != idlen || - strncmp(start, data->set.str[STRING_RTSP_SESSION_ID], idlen)) { + if(strlen(str) != idlen || + strncmp(start, str, idlen)) { failf(data, "Got RTSP Session ID Line [%s], but wanted ID [%s]", - start, data->set.str[STRING_RTSP_SESSION_ID]); + start, str); return CURLE_RTSP_SESSION_ERROR; } } else { /* If the Session ID is not set, and we find it in a response, then set * it. - */ - - /* Copy the id substring into a new buffer */ - data->set.str[STRING_RTSP_SESSION_ID] = curlx_memdup0(start, idlen); - if(!data->set.str[STRING_RTSP_SESSION_ID]) + * Copy the id substring into a new buffer */ + void *mem = curlx_memdup0(start, idlen); + if(!mem || + CURL_EASY_STR_SETN(data, STRING_RTSP_SESSION_ID, mem)) return CURLE_OUT_OF_MEMORY; } } diff --git a/Utilities/cmcurl/lib/rtsp.h b/Utilities/cmcurl/lib/rtsp.h index dd5df3ff73..791cbff337 100644 --- a/Utilities/cmcurl/lib/rtsp.h +++ b/Utilities/cmcurl/lib/rtsp.h @@ -30,4 +30,18 @@ extern const struct Curl_protocol Curl_protocol_rtsp; #define Curl_rtsp_parseheader(x, y) CURLE_NOT_BUILT_IN #endif +#define RTSPREQ_NONE CURL_RTSPREQ_NONE +#define RTSPREQ_OPTIONS CURL_RTSPREQ_OPTIONS +#define RTSPREQ_DESCRIBE CURL_RTSPREQ_DESCRIBE +#define RTSPREQ_ANNOUNCE CURL_RTSPREQ_ANNOUNCE +#define RTSPREQ_SETUP CURL_RTSPREQ_SETUP +#define RTSPREQ_PLAY CURL_RTSPREQ_PLAY +#define RTSPREQ_PAUSE CURL_RTSPREQ_PAUSE +#define RTSPREQ_TEARDOWN CURL_RTSPREQ_TEARDOWN +#define RTSPREQ_GET_PARAMETER CURL_RTSPREQ_GET_PARAMETER +#define RTSPREQ_SET_PARAMETER CURL_RTSPREQ_SET_PARAMETER +#define RTSPREQ_RECORD CURL_RTSPREQ_RECORD +#define RTSPREQ_RECEIVE CURL_RTSPREQ_RECEIVE +#define RTSPREQ_LAST CURL_RTSPREQ_LAST + #endif /* HEADER_CURL_RTSP_H */ diff --git a/Utilities/cmcurl/lib/select.c b/Utilities/cmcurl/lib/select.c index 888490f2c9..76b56bc042 100644 --- a/Utilities/cmcurl/lib/select.c +++ b/Utilities/cmcurl/lib/select.c @@ -23,10 +23,6 @@ ***************************************************************************/ #include "curl_setup.h" -#if !defined(HAVE_SELECT) && !defined(HAVE_POLL) -#error "We cannot compile without select() or poll() support." -#endif - #ifdef HAVE_SYS_SELECT_H #include #elif defined(HAVE_UNISTD_H) @@ -40,7 +36,7 @@ #include "curlx/timediff.h" #include "curlx/wait.h" -#ifndef HAVE_POLL +#ifndef HAVE_POLL /* use select() */ /* * This is a wrapper around select() to aid in Windows compatibility. A * negative timeout value makes this function wait indefinitely, unless no @@ -287,12 +283,10 @@ int Curl_poll(struct pollfd ufds[], unsigned int nfds, timediff_t timeout_ms) } } - /* - Note also that Winsock ignores the first argument, so we do not worry + /* Note also that Winsock ignores the first argument, so we do not worry about the fact that maxfd is computed incorrectly with Winsock (since curl_socket_t is unsigned in such cases and thus -1 is the largest - value). - */ + value). */ r = our_select(maxfd, &fds_read, &fds_write, &fds_err, timeout_ms); if(r <= 0) { if((r == -1) && (SOCKERRNO == SOCKEINTR)) diff --git a/Utilities/cmcurl/lib/select.h b/Utilities/cmcurl/lib/select.h index dbbace4527..8fe519aee6 100644 --- a/Utilities/cmcurl/lib/select.h +++ b/Utilities/cmcurl/lib/select.h @@ -80,10 +80,8 @@ int Curl_socket_check(curl_socket_t readfd0, int Curl_poll(struct pollfd ufds[], unsigned int nfds, timediff_t timeout_ms); -/* - With Winsock the valid range is [0..INVALID_SOCKET-1] according to - https://learn.microsoft.com/windows/win32/winsock/socket-data-type-2 -*/ +/* With Winsock the valid range is [0..INVALID_SOCKET-1] according to + https://learn.microsoft.com/windows/win32/winsock/socket-data-type-2 */ #ifdef USE_WINSOCK #define VALID_SOCK(s) ((s) < INVALID_SOCKET) #define FDSET_SOCK(x) 1 diff --git a/Utilities/cmcurl/lib/sendf.c b/Utilities/cmcurl/lib/sendf.c index 7559f64f84..abd2e3fdbf 100644 --- a/Utilities/cmcurl/lib/sendf.c +++ b/Utilities/cmcurl/lib/sendf.c @@ -46,24 +46,25 @@ static void cl_reset_writer(struct Curl_easy *data) { - struct Curl_cwriter *writer = data->req.writer_stack; + struct Curl_cwriter *writer = data->req.writer.stack; while(writer) { - data->req.writer_stack = writer->next; + data->req.writer.stack = writer->next; writer->cwt->do_close(data, writer); curlx_free(writer); - writer = data->req.writer_stack; + writer = data->req.writer.stack; } + data->req.writer.paused = FALSE; } static void cl_reset_reader(struct Curl_easy *data) { - struct Curl_creader *reader = data->req.reader_stack; + struct Curl_creader *reader = data->req.reader.stack; data->req.reader_started = FALSE; while(reader) { - data->req.reader_stack = reader->next; + data->req.reader.stack = reader->next; reader->crt->do_close(data, reader); curlx_free(reader); - reader = data->req.reader_stack; + reader = data->req.reader.stack; } } @@ -95,7 +96,7 @@ void Curl_client_reset(struct Curl_easy *data) CURLcode Curl_client_start(struct Curl_easy *data) { if(data->req.rewind_read) { - struct Curl_creader *r = data->req.reader_stack; + struct Curl_creader *r = data->req.reader.stack; CURLcode result = CURLE_OK; CURL_TRC_READ(data, "client start, rewind readers"); @@ -124,16 +125,6 @@ void Curl_creader_set_rewind(struct Curl_easy *data, bool enable) data->req.rewind_read = !!enable; } -/* 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) -{ - if(!writer) - return CURLE_WRITE_ERROR; - return writer->cwt->do_write(data, writer, type, buf, nbytes); -} - CURLcode Curl_cwriter_def_init(struct Curl_easy *data, struct Curl_cwriter *writer) { @@ -149,6 +140,12 @@ CURLcode Curl_cwriter_def_write(struct Curl_easy *data, return Curl_cwriter_write(data, writer->next, type, buf, nbytes); } +CURLcode Curl_cwriter_def_flush(struct Curl_easy *data, + struct Curl_cwriter *writer) +{ + return Curl_cwriter_flush(data, writer->next); +} + void Curl_cwriter_def_close(struct Curl_easy *data, struct Curl_cwriter *writer) { @@ -192,6 +189,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; +#ifdef DEBUGBUILD + if(is_connect && getenv("CURL_DBG_SUPPRESS_CONNECT_HDS")) + return CURLE_OK; +#endif result = Curl_cwriter_write(data, writer->next, type, buf, nbytes); CURL_TRC_WRITE(data, "download_write header(type=%x, blen=%zu) -> %d", (unsigned int)type, nbytes, (int)result); @@ -213,7 +214,7 @@ 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"); + streamclose(data->conn); CURL_TRC_WRITE(data, "download_write body(type=%x, blen=%zu), " "did not want a BODY", (unsigned int)type, nbytes); data->req.download_done = TRUE; @@ -280,7 +281,7 @@ static CURLcode cw_download_write(struct Curl_easy *data, ", bytecount = %" FMT_OFF_T, excess_len, data->req.size, data->req.maxdownload, data->req.bytecount); - connclose(data->conn, "excess found in a read"); + connclose(data->conn); } } else if((nwrite < nbytes) && !data->req.ignorebody) { @@ -296,8 +297,10 @@ static CURLcode cw_download_write(struct Curl_easy *data, static const struct Curl_cwtype cw_download = { "protocol", NULL, + 0, Curl_cwriter_def_init, cw_download_write, + Curl_cwriter_def_flush, Curl_cwriter_def_close, sizeof(struct cw_download_ctx) }; @@ -317,53 +320,47 @@ static CURLcode cw_raw_write(struct Curl_easy *data, static const struct Curl_cwtype cw_raw = { "raw", NULL, + 0, Curl_cwriter_def_init, cw_raw_write, + Curl_cwriter_def_flush, Curl_cwriter_def_close, sizeof(struct Curl_cwriter) }; +static void cwriter_add(struct Curl_easy *data, + struct Curl_cwriter *writer) +{ + struct Curl_cwriter **anchor = &data->req.writer.stack; + + /* Insert the writer as first in its phase. + * Skip existing writers of lower phases. */ + while(*anchor && (*anchor)->phase < writer->phase) + anchor = &(*anchor)->next; + writer->next = *anchor; + *anchor = writer; +} + static CURLcode do_init_writer_stack(struct Curl_easy *data) { struct Curl_cwriter *writer; CURLcode result; - DEBUGASSERT(!data->req.writer_stack); - result = Curl_cwriter_create(&data->req.writer_stack, + DEBUGASSERT(!data->req.writer.stack); + result = Curl_cwriter_create(&data->req.writer.stack, data, &Curl_cwt_out, CURL_CW_CLIENT); if(result) return result; - /* This places the "pause" writer behind the "download" writer that - * is added below. Meaning the "download" can do checks on content length - * and other things *before* write outs are buffered for paused transfers. */ - result = Curl_cwriter_create(&writer, data, &Curl_cwt_pause, - CURL_CW_PROTOCOL); - if(!result) { - result = Curl_cwriter_add(data, writer); - if(result) - Curl_cwriter_free(data, writer); - } - if(result) - return result; - result = Curl_cwriter_create(&writer, data, &cw_download, CURL_CW_PROTOCOL); - if(!result) { - result = Curl_cwriter_add(data, writer); - if(result) - Curl_cwriter_free(data, writer); - } if(result) return result; + cwriter_add(data, writer); result = Curl_cwriter_create(&writer, data, &cw_raw, CURL_CW_RAW); - if(!result) { - result = Curl_cwriter_add(data, writer); - if(result) - Curl_cwriter_free(data, writer); - } if(result) return result; + cwriter_add(data, writer); return result; } @@ -388,19 +385,35 @@ CURLcode Curl_client_write(struct Curl_easy *data, int type, const char *buf, DEBUGASSERT(!(type & CLIENTWRITE_INFO) || ((type & ~(CLIENTWRITE_INFO | CLIENTWRITE_EOS)) == 0)); - if(!data->req.writer_stack) { + if(!data->req.writer.stack) { result = do_init_writer_stack(data); if(result) return result; - DEBUGASSERT(data->req.writer_stack); + DEBUGASSERT(data->req.writer.stack); } - result = Curl_cwriter_write(data, data->req.writer_stack, type, buf, len); + result = Curl_cwriter_write(data, data->req.writer.stack, type, buf, len); CURL_TRC_WRITE(data, "client_write(type=%x, len=%zu) -> %d", (unsigned int)type, len, (int)result); return result; } +CURLcode Curl_client_flush(struct Curl_easy *data) +{ + CURLcode result; + + if(!data->req.writer.stack) { + result = do_init_writer_stack(data); + if(result) + return result; + DEBUGASSERT(data->req.writer.stack); + } + + result = Curl_cwriter_flush(data, data->req.writer.stack); + CURL_TRC_WRITE(data, "client_flush() -> %d", (int)result); + return result; +} + /* Create an unencoding writer stage using the given handler. */ CURLcode Curl_cwriter_create(struct Curl_cwriter **pwriter, struct Curl_easy *data, @@ -443,18 +456,33 @@ size_t Curl_cwriter_count(struct Curl_easy *data, Curl_cwriter_phase phase) struct Curl_cwriter *w; size_t n = 0; - for(w = data->req.writer_stack; w; w = w->next) { + for(w = data->req.writer.stack; w; w = w->next) { if(w->phase == phase) ++n; } return n; } +static CURLcode cwriter_ensure_pause_writer(struct Curl_easy *data) +{ + struct Curl_cwriter *writer = + Curl_cwriter_get_by_type(data, &Curl_cwt_pause); + CURLcode result = CURLE_OK; + + if(!writer) { + result = Curl_cwriter_create(&writer, data, &Curl_cwt_pause, + CURL_CW_BEFORE_DECODE); + if(!result) + cwriter_add(data, writer); + } + return result; +} + CURLcode Curl_cwriter_add(struct Curl_easy *data, struct Curl_cwriter *writer) { CURLcode result; - struct Curl_cwriter **anchor = &data->req.writer_stack; + struct Curl_cwriter **anchor = &data->req.writer.stack; if(!*anchor) { result = do_init_writer_stack(data); @@ -462,12 +490,16 @@ CURLcode Curl_cwriter_add(struct Curl_easy *data, return result; } - /* Insert the writer as first in its phase. - * Skip existing writers of lower phases. */ - while(*anchor && (*anchor)->phase < writer->phase) - anchor = &((*anchor)->next); - writer->next = *anchor; - *anchor = writer; + if(writer->cwt->flags & CURL_CW_FLAG_BLOWUP) { + /* On adding a writer that may blow up write sizes, e.g. zip bombs, + * add the pause writer. Do this first as any failure will make the + * caller destroy the writer again. */ + result = cwriter_ensure_pause_writer(data); + if(result) + return result; + } + + cwriter_add(data, writer); return CURLE_OK; } @@ -475,7 +507,7 @@ struct Curl_cwriter *Curl_cwriter_get_by_name(struct Curl_easy *data, const char *name) { struct Curl_cwriter *writer; - for(writer = data->req.writer_stack; writer; writer = writer->next) { + for(writer = data->req.writer.stack; writer; writer = writer->next) { if(!strcmp(name, writer->cwt->name)) return writer; } @@ -486,31 +518,17 @@ struct Curl_cwriter *Curl_cwriter_get_by_type(struct Curl_easy *data, const struct Curl_cwtype *cwt) { struct Curl_cwriter *writer; - for(writer = data->req.writer_stack; writer; writer = writer->next) { + for(writer = data->req.writer.stack; writer; writer = writer->next) { if(writer->cwt == cwt) return writer; } return NULL; } -bool Curl_cwriter_is_content_decoding(struct Curl_easy *data) -{ - struct Curl_cwriter *writer; - for(writer = data->req.writer_stack; writer; writer = writer->next) { - if(writer->phase == CURL_CW_CONTENT_DECODE) - return TRUE; - } - return FALSE; -} - -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); + data->req.writer.paused = FALSE; + return Curl_cwriter_flush(data, data->req.writer.stack); } CURLcode Curl_creader_read(struct Curl_easy *data, @@ -666,9 +684,10 @@ static CURLcode cr_in_read(struct Curl_easy *data, } nread = 0; if(ctx->read_cb && blen) { - Curl_set_in_callback(data, TRUE); + struct Curl_mapi_guard guard; + CURL_CBAPI_START(&guard, data, easy_cr_in_read); nread = ctx->read_cb(buf, 1, blen, ctx->cb_user_data); - Curl_set_in_callback(data, FALSE); + CURL_CBAPI_END(&guard); ctx->has_used_cb = TRUE; } @@ -766,9 +785,10 @@ static CURLcode cr_in_resume_from(struct Curl_easy *data, return CURLE_READ_ERROR; if(data->set.seek_func) { - Curl_set_in_callback(data, TRUE); + struct Curl_mapi_guard guard; + CURL_CBAPI_START(&guard, data, easy_seek_func); seekerr = data->set.seek_func(data->set.seek_client, offset, SEEK_SET); - Curl_set_in_callback(data, FALSE); + CURL_CBAPI_END(&guard); } if(seekerr != CURL_SEEKFUNC_OK) { @@ -780,6 +800,7 @@ static CURLcode cr_in_resume_from(struct Curl_easy *data, } /* when seekerr == CURL_SEEKFUNC_CANTSEEK (cannot seek to offset) */ do { + struct Curl_mapi_guard guard; char scratch[4 * 1024]; size_t readthisamountnow = (offset - passed > (curl_off_t)sizeof(scratch)) ? @@ -787,10 +808,10 @@ static CURLcode cr_in_resume_from(struct Curl_easy *data, curlx_sotouz(offset - passed); size_t actuallyread; - Curl_set_in_callback(data, TRUE); + CURL_CBAPI_START(&guard, data, easy_cr_in_resume_from); actuallyread = ctx->read_cb(scratch, 1, readthisamountnow, ctx->cb_user_data); - Curl_set_in_callback(data, FALSE); + CURL_CBAPI_END(&guard); passed += actuallyread; if((actuallyread == 0) || (actuallyread > readthisamountnow)) { @@ -826,11 +847,12 @@ static CURLcode cr_in_rewind(struct Curl_easy *data, return CURLE_OK; if(data->set.seek_func) { + struct Curl_mapi_guard guard; int err; - Curl_set_in_callback(data, TRUE); - err = (data->set.seek_func)(data->set.seek_client, 0, SEEK_SET); - Curl_set_in_callback(data, FALSE); + CURL_CBAPI_START(&guard, data, easy_seek_func); + err = data->set.seek_func(data->set.seek_client, 0, SEEK_SET); + CURL_CBAPI_END(&guard); CURL_TRC_READ(data, "cr_in, rewind via set.seek_func -> %d", err); if(err) { failf(data, "seek callback returned error %d", err); @@ -838,12 +860,13 @@ static CURLcode cr_in_rewind(struct Curl_easy *data, } } else if(data->set.ioctl_func) { + struct Curl_mapi_guard guard; curlioerr err; - Curl_set_in_callback(data, TRUE); - err = (data->set.ioctl_func)(data, CURLIOCMD_RESTARTREAD, - data->set.ioctl_client); - Curl_set_in_callback(data, FALSE); + CURL_CBAPI_START(&guard, data, easy_ioctl_func); + err = data->set.ioctl_func(data, CURLIOCMD_RESTARTREAD, + data->set.ioctl_client); + CURL_CBAPI_END(&guard); 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); @@ -1105,17 +1128,19 @@ static CURLcode do_init_reader_stack(struct Curl_easy *data, DEBUGASSERT(r); DEBUGASSERT(r->crt); DEBUGASSERT(r->phase == CURL_CR_CLIENT); - DEBUGASSERT(!data->req.reader_stack); + DEBUGASSERT(!data->req.reader.stack); - data->req.reader_stack = r; + data->req.reader.stack = r; clen = r->crt->total_length(data, r); - /* if we do not have 0 length init, and crlf conversion is wanted, + /* if we do not have 0 length init, and CRLF conversion is wanted, * add the reader for it */ - if(clen && (data->set.crlf + if(clen && #ifdef CURL_PREFER_LF_LINEENDS - || data->state.prefer_ascii + (data->set.crlf || data->state.prefer_ascii) +#else + data->set.crlf #endif - )) { + ) { result = cr_lc_add(data); if(result) return result; @@ -1148,7 +1173,7 @@ CURLcode Curl_creader_add(struct Curl_easy *data, struct Curl_creader *reader) { CURLcode result; - struct Curl_creader **anchor = &data->req.reader_stack; + struct Curl_creader **anchor = &data->req.reader.stack; if(!*anchor) { result = Curl_creader_set_fread(data, data->state.infilesize); @@ -1159,7 +1184,7 @@ CURLcode Curl_creader_add(struct Curl_easy *data, /* Insert the writer as first in its phase. * Skip existing readers of lower phases. */ while(*anchor && (*anchor)->phase < reader->phase) - anchor = &((*anchor)->next); + anchor = &(*anchor)->next; reader->next = *anchor; *anchor = reader; return CURLE_OK; @@ -1191,11 +1216,11 @@ CURLcode Curl_client_read(struct Curl_easy *data, char *buf, size_t blen, DEBUGASSERT(eos); *nread = 0; - if(!data->req.reader_stack) { + if(!data->req.reader.stack) { result = Curl_creader_set_fread(data, data->state.infilesize); if(result) return result; - DEBUGASSERT(data->req.reader_stack); + DEBUGASSERT(data->req.reader.stack); } if(!data->req.reader_started) { Curl_rlimit_start(&data->progress.ul.rlimit, Curl_pgrs_now(data), -1); @@ -1203,8 +1228,7 @@ CURLcode Curl_client_read(struct Curl_easy *data, char *buf, size_t blen, } if(Curl_rlimit_active(&data->progress.ul.rlimit)) { - curl_off_t ul_avail = Curl_rlimit_avail(&data->progress.ul.rlimit, - Curl_pgrs_now(data)); + curl_off_t ul_avail = Curl_rlimit_avail(&data->progress.ul.rlimit, NULL); if(ul_avail <= 0) { result = CURLE_OK; *eos = FALSE; @@ -1213,7 +1237,7 @@ CURLcode Curl_client_read(struct Curl_easy *data, char *buf, size_t blen, if(ul_avail < (curl_off_t)blen) blen = (size_t)ul_avail; } - result = Curl_creader_read(data, data->req.reader_stack, buf, blen, + result = Curl_creader_read(data, data->req.reader.stack, buf, blen, nread, eos); out: @@ -1224,7 +1248,7 @@ out: bool Curl_creader_needs_rewind(struct Curl_easy *data) { - struct Curl_creader *reader = data->req.reader_stack; + struct Curl_creader *reader = data->req.reader.stack; while(reader) { if(reader->crt->needs_rewind(data, reader)) { CURL_TRC_READ(data, "client reader needs rewind before next request"); @@ -1410,13 +1434,13 @@ out: curl_off_t Curl_creader_total_length(struct Curl_easy *data) { - struct Curl_creader *r = data->req.reader_stack; + struct Curl_creader *r = data->req.reader.stack; return r ? r->crt->total_length(data, r) : -1; } curl_off_t Curl_creader_client_length(struct Curl_easy *data) { - struct Curl_creader *r = data->req.reader_stack; + struct Curl_creader *r = data->req.reader.stack; while(r && r->phase != CURL_CR_CLIENT) r = r->next; return r ? r->crt->total_length(data, r) : -1; @@ -1424,7 +1448,7 @@ curl_off_t Curl_creader_client_length(struct Curl_easy *data) CURLcode Curl_creader_resume_from(struct Curl_easy *data, curl_off_t offset) { - struct Curl_creader *r = data->req.reader_stack; + struct Curl_creader *r = data->req.reader.stack; while(r && r->phase != CURL_CR_CLIENT) r = r->next; return r ? r->crt->resume_from(data, r, offset) : CURLE_READ_ERROR; @@ -1432,7 +1456,7 @@ CURLcode Curl_creader_resume_from(struct Curl_easy *data, curl_off_t offset) CURLcode Curl_creader_unpause(struct Curl_easy *data) { - struct Curl_creader *reader = data->req.reader_stack; + struct Curl_creader *reader = data->req.reader.stack; CURLcode result = CURLE_OK; while(reader) { @@ -1447,7 +1471,7 @@ CURLcode Curl_creader_unpause(struct Curl_easy *data) bool Curl_creader_is_paused(struct Curl_easy *data) { - struct Curl_creader *reader = data->req.reader_stack; + struct Curl_creader *reader = data->req.reader.stack; while(reader) { if(reader->crt->is_paused(data, reader)) @@ -1459,7 +1483,7 @@ bool Curl_creader_is_paused(struct Curl_easy *data) void Curl_creader_done(struct Curl_easy *data, int premature) { - struct Curl_creader *reader = data->req.reader_stack; + struct Curl_creader *reader = data->req.reader.stack; while(reader) { reader->crt->done(data, reader, premature); reader = reader->next; @@ -1470,7 +1494,7 @@ struct Curl_creader *Curl_creader_get_by_type(struct Curl_easy *data, const struct Curl_crtype *crt) { struct Curl_creader *r; - for(r = data->req.reader_stack; r; r = r->next) { + for(r = data->req.reader.stack; r; r = r->next) { if(r->crt == crt) return r; } diff --git a/Utilities/cmcurl/lib/sendf.h b/Utilities/cmcurl/lib/sendf.h index 787fe7ff69..9863c5b0dc 100644 --- a/Utilities/cmcurl/lib/sendf.h +++ b/Utilities/cmcurl/lib/sendf.h @@ -24,6 +24,7 @@ * ***************************************************************************/ #include "curl_setup.h" +#include "cw-out.h" /** * Type of data that is being written to the client (application) @@ -61,6 +62,8 @@ struct Curl_easy; CURLcode Curl_client_write(struct Curl_easy *data, int type, const char *buf, size_t len) WARN_UNUSED_RESULT; +CURLcode Curl_client_flush(struct Curl_easy *data); + /** * Free all resources related to client writing. */ @@ -102,19 +105,26 @@ typedef enum { CURL_CW_RAW, /* raw data written, before any decoding */ CURL_CW_TRANSFER_DECODE, /* remove transfer-encodings */ CURL_CW_PROTOCOL, /* after transfer, but before content decoding */ + CURL_CW_BEFORE_DECODE, /* after protocol, but before content decoding */ CURL_CW_CONTENT_DECODE, /* remove content-encodings */ CURL_CW_CLIENT /* data written to client */ } Curl_cwriter_phase; +/* writer may blow up size of write data, e.g. zip bombs */ +#define CURL_CW_FLAG_BLOWUP (1U << 0) + /* Client Writer Type, provides the implementation */ struct Curl_cwtype { const char *name; /* writer name. */ const char *alias; /* writer name alias, maybe NULL. */ + uint8_t flags; /* flags for writer behaviour */ CURLcode (*do_init)(struct Curl_easy *data, struct Curl_cwriter *writer); CURLcode (*do_write)(struct Curl_easy *data, struct Curl_cwriter *writer, int type, const char *buf, size_t nbytes); + CURLcode (*do_flush)(struct Curl_easy *data, + struct Curl_cwriter *writer); void (*do_close)(struct Curl_easy *data, struct Curl_cwriter *writer); size_t cwriter_size; /* sizeof() allocated struct Curl_cwriter */ @@ -172,20 +182,16 @@ struct Curl_cwriter *Curl_cwriter_get_by_type(struct Curl_easy *data, struct Curl_cwriter *Curl_cwriter_get_by_name(struct Curl_easy *data, const char *name); -/** - * Convenience method for calling `writer->do_write()` that - * checks for NULL writer. - */ -CURLcode Curl_cwriter_write(struct Curl_easy *data, - struct Curl_cwriter *writer, int type, - const char *buf, size_t nbytes); +/* Convenience method for calling `writer->do_write()` that + * checks for NULL writer. */ +#define Curl_cwriter_write(d, w, t, b, n) \ + ((w) ? (w)->cwt->do_write((d), (w), (t), (b), (n)) : CURLE_WRITE_ERROR) -/** - * Return TRUE iff client writer is paused. - */ -bool Curl_cwriter_is_paused(struct Curl_easy *data); +#define Curl_cwriter_flush(d, w) \ + ((w) ? (w)->cwt->do_flush((d), (w)) : CURLE_WRITE_ERROR) -bool Curl_cwriter_is_content_decoding(struct Curl_easy *data); +/* TRUE if client writer is paused. */ +#define Curl_cwriter_is_paused(d) ((bool)(d)->req.writer.paused) /** * Unpause client writer and flush any buffered date to the client. @@ -201,6 +207,8 @@ CURLcode Curl_cwriter_def_init(struct Curl_easy *data, CURLcode Curl_cwriter_def_write(struct Curl_easy *data, struct Curl_cwriter *writer, int type, const char *buf, size_t nbytes); +CURLcode Curl_cwriter_def_flush(struct Curl_easy *data, + struct Curl_cwriter *writer); void Curl_cwriter_def_close(struct Curl_easy *data, struct Curl_cwriter *writer); diff --git a/Utilities/cmcurl/lib/setopt.c b/Utilities/cmcurl/lib/setopt.c index eb9ff2e396..0e15980b8f 100644 --- a/Utilities/cmcurl/lib/setopt.c +++ b/Utilities/cmcurl/lib/setopt.c @@ -41,7 +41,6 @@ #include "curl_share.h" #include "vtls/vtls.h" #include "curl_trc.h" -#include "hostip.h" #include "setopt.h" #include "altsvc.h" #include "hsts.h" @@ -79,23 +78,15 @@ static CURLcode setopt_set_timeout_ms(timediff_t *ptimeout_ms, long ms) return CURLE_OK; } -CURLcode Curl_setstropt(char **charp, const char *s) +CURLcode Curl_setstropt(struct Curl_easy *data, + enum dupstring id, const char *s) { - /* Release the previous storage at `charp' and replace by a dynamic storage - copy of `s'. Return CURLE_OK or CURLE_OUT_OF_MEMORY. */ + size_t slen = s ? strlen(s) : 0; + DEBUGASSERT((unsigned)id <= UINT8_MAX); + if(s && (slen > CURL_MAX_INPUT_LENGTH)) + return CURLE_BAD_FUNCTION_ARGUMENT; - curlx_safefree(*charp); - - if(s) { - if(strlen(s) > CURL_MAX_INPUT_LENGTH) - return CURLE_BAD_FUNCTION_ARGUMENT; - - *charp = curlx_strdup(s); - if(!*charp) - return CURLE_OUT_OF_MEMORY; - } - - return CURLE_OK; + return CURL_EASY_STR_SET(data, (uint8_t)id, s, slen); } CURLcode Curl_setblobopt(struct curl_blob **blobp, @@ -154,40 +145,41 @@ static CURLcode setstropt_userpwd(const char *option, char **userp, curlx_free(*userp); *userp = user; + curlx_strzero(*passwdp); curlx_free(*passwdp); *passwdp = passwd; return CURLE_OK; } -static CURLcode setstropt_interface(char *option, char **devp, - char **ifacep, char **hostp) +static CURLcode setstropt_interface(struct Curl_easy *data, char *option) { char *dev = NULL; char *iface = NULL; char *host = NULL; CURLcode result; - DEBUGASSERT(devp); - DEBUGASSERT(ifacep); - DEBUGASSERT(hostp); - if(option) { /* Parse the interface details if set, otherwise clear them all */ result = Curl_parse_interface(option, &dev, &iface, &host); if(result) return result; } - curlx_free(*devp); - *devp = dev; - curlx_free(*ifacep); - *ifacep = iface; - - curlx_free(*hostp); - *hostp = host; - - return CURLE_OK; + result = CURL_EASY_STR_SETN(data, STRING_DEVICE, dev); + dev = NULL; + if(!result) { + result = CURL_EASY_STR_SETN(data, STRING_INTERFACE, iface); + iface = NULL; + } + if(!result) { + result = CURL_EASY_STR_SETN(data, STRING_BINDHOST, host); + host = NULL; + } + curlx_free(dev); + curlx_free(iface); + curlx_free(host); + return result; } #ifdef USE_SSL @@ -355,46 +347,10 @@ static CURLcode setopt_RTSP_REQUEST(struct Curl_easy *data, long arg) * Set the RTSP request method (OPTIONS, SETUP, PLAY, etc...) Would this be * better if the RTSPREQ_* were moved into here? */ - Curl_RtspReq rtspreq = RTSPREQ_NONE; - switch(arg) { - case CURL_RTSPREQ_OPTIONS: - rtspreq = RTSPREQ_OPTIONS; - break; - case CURL_RTSPREQ_DESCRIBE: - rtspreq = RTSPREQ_DESCRIBE; - break; - case CURL_RTSPREQ_ANNOUNCE: - rtspreq = RTSPREQ_ANNOUNCE; - break; - case CURL_RTSPREQ_SETUP: - rtspreq = RTSPREQ_SETUP; - break; - case CURL_RTSPREQ_PLAY: - rtspreq = RTSPREQ_PLAY; - break; - case CURL_RTSPREQ_PAUSE: - rtspreq = RTSPREQ_PAUSE; - break; - case CURL_RTSPREQ_TEARDOWN: - rtspreq = RTSPREQ_TEARDOWN; - break; - case CURL_RTSPREQ_GET_PARAMETER: - rtspreq = RTSPREQ_GET_PARAMETER; - break; - case CURL_RTSPREQ_SET_PARAMETER: - rtspreq = RTSPREQ_SET_PARAMETER; - break; - case CURL_RTSPREQ_RECORD: - rtspreq = RTSPREQ_RECORD; - break; - case CURL_RTSPREQ_RECEIVE: - rtspreq = RTSPREQ_RECEIVE; - break; - default: + if((arg <= CURL_RTSPREQ_NONE) || (arg >= CURL_RTSPREQ_LAST)) return CURLE_BAD_FUNCTION_ARGUMENT; - } - data->set.rtspreq = rtspreq; + data->set.rtspreq = (unsigned char)arg; return CURLE_OK; } #endif /* !CURL_DISABLE_RTSP */ @@ -988,7 +944,7 @@ static CURLcode setopt_long_ssl(struct Curl_easy *data, CURLoption option, case CURLOPT_SSL_ENABLE_NPN: break; case CURLOPT_SSLENGINE_DEFAULT: - curlx_safefree(s->str[STRING_SSL_ENGINE]); + CURL_EASY_STR_CLEAR(data, STRING_SSL_ENGINE); result = Curl_ssl_set_engine_default(data); break; default: @@ -1098,11 +1054,24 @@ static CURLcode setopt_long_http(struct Curl_easy *data, CURLoption option, case CURLOPT_STREAM_WEIGHT: #if defined(USE_HTTP2) || defined(USE_HTTP3) if((arg >= 1) && (arg <= 256)) - s->priority.weight = (int)arg; + s->weight = (int)arg; break; #else result = CURLE_NOT_BUILT_IN; break; +#endif +#ifndef CURL_DISABLE_HTTPSIG + case CURLOPT_HTTPSIG_ALGORITHM: + if(arg != CURLHTTPSIG_NONE && + arg != CURLHTTPSIG_ED25519 && + arg != CURLHTTPSIG_HMAC_SHA256) + return CURLE_BAD_FUNCTION_ARGUMENT; + s->httpsig_algorithm = (uint8_t)arg; + if(arg) + s->httpauth = (uint32_t)CURLAUTH_HTTPSIG; + else + s->httpauth &= ~(uint32_t)CURLAUTH_HTTPSIG; + break; #endif default: return CURLE_UNKNOWN_OPTION; @@ -1234,9 +1203,8 @@ static CURLcode setopt_long_misc(struct Curl_easy *data, CURLoption option, case CURLOPT_POSTFIELDSIZE: if(arg < -1) return CURLE_BAD_FUNCTION_ARGUMENT; - if(s->postfieldsize < arg && - s->postfields == s->str[STRING_COPYPOSTFIELDS]) { - curlx_safefree(s->str[STRING_COPYPOSTFIELDS]); + if(s->postfieldsize < arg && s->str_copypostfields) { + curlx_safefree(s->str_copypostfields); s->postfields = NULL; } s->postfieldsize = arg; @@ -1449,6 +1417,26 @@ static CURLcode setopt_mimepost(struct Curl_easy *data, curl_mime *mimep) #endif /* !CURL_DISABLE_MIME */ #endif /* !CURL_DISABLE_HTTP || !CURL_DISABLE_SMTP || !CURL_DISABLE_IMAP */ +static CURLcode setopt_share(struct Curl_easy *data, struct Curl_share *set) +{ + CURLcode result; + + if(data->conn) { + /* As this handle already has a connection attached, changing share now + would be complicated and error-prone */ + infof(data, "Cannot change share object while in use"); + result = CURLE_BAD_FUNCTION_ARGUMENT; + } + else { + /* disconnect from old share, if any and possible */ + result = Curl_share_easy_unlink(data); + if(!result && GOOD_SHARE_HANDLE(set)) + /* use new share if it set */ + result = Curl_share_easy_link(data, set); + } + return result; +} + /* assorted pointer type arguments */ static CURLcode setopt_pointers(struct Curl_easy *data, CURLoption option, va_list param) @@ -1461,7 +1449,7 @@ static CURLcode setopt_pointers(struct Curl_easy *data, CURLoption option, * pass CURLU to set URL */ Curl_bufref_free(&data->state.url); - curlx_safefree(s->str[STRING_SET_URL]); + CURL_EASY_STR_CLEAR(data, STRING_SET_URL); s->uh = va_arg(param, CURLU *); break; #ifndef CURL_DISABLE_HTTP @@ -1496,30 +1484,8 @@ static CURLcode setopt_pointers(struct Curl_easy *data, CURLoption option, if(!s->err) s->err = stderr; break; - case CURLOPT_SHARE: { - struct Curl_share *set = va_arg(param, struct Curl_share *); - - /* disconnect from old share, if any and possible */ - result = Curl_share_easy_unlink(data); - if(result) - return result; - - /* use new share if it set */ - if(GOOD_SHARE_HANDLE(set)) { - result = Curl_share_easy_link(data, set); - if(result) - return result; - } - break; - } - -#ifdef USE_HTTP2 - case CURLOPT_STREAM_DEPENDS: - case CURLOPT_STREAM_DEPENDS_E: - /* not doing stream dependencies any longer, but accept options - * for backward compatibility */ - break; -#endif + case CURLOPT_SHARE: + return setopt_share(data, va_arg(param, struct Curl_share *)); default: return CURLE_UNKNOWN_OPTION; @@ -1552,7 +1518,9 @@ static CURLcode cookielist(struct Curl_easy *data, const char *ptr) } else if(curl_strequal(ptr, "RELOAD")) { /* reload cookies from file */ - return Curl_cookie_loadfiles(data); + return Curl_cookie_loadfiles(data, COOKIE_NOPSL | + (data->set.cookiesession ? + COOKIE_NOSESSION : 0)); } else { if(!data->cookies) { @@ -1567,15 +1535,20 @@ static CURLcode cookielist(struct Curl_easy *data, const char *ptr) if(strlen(ptr) > CURL_MAX_INPUT_LENGTH) return CURLE_BAD_FUNCTION_ARGUMENT; + /* Adding these cookies without the PSL check, because the PSL is not + initialized until *perform() time, and this might be called before + that */ Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); if(checkprefix("Set-Cookie:", ptr)) /* HTTP Header format line */ - result = Curl_cookie_add(data, data->cookies, TRUE, FALSE, ptr + 11, - NULL, NULL, TRUE); + result = Curl_cookie_add(data, data->cookies, ptr + 11, + NULL, NULL, + COOKIE_HTTPHEADER | COOKIE_SECURE | + COOKIE_NOPSL); else /* Netscape format line */ - result = Curl_cookie_add(data, data->cookies, FALSE, FALSE, ptr, NULL, - NULL, TRUE); + result = Curl_cookie_add(data, data->cookies, ptr, NULL, + NULL, COOKIE_SECURE | COOKIE_NOPSL); Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); } return result; @@ -1622,13 +1595,14 @@ static CURLcode cookiefile(struct Curl_easy *data, const char *ptr) #ifndef CURL_DISABLE_PROXY static CURLcode setproxy(struct Curl_easy *data, const char *proxy) { - if((data->set.str[STRING_PROXY] && proxy) && + const char *str = CURL_EASY_STR(data, STRING_PROXY); + if(str && proxy && /* there was one set, is this a new one? */ - !strcmp(data->set.str[STRING_PROXY], proxy)) + !strcmp(str, proxy)) return CURLE_OK; /* same one as before */ changeproxy(data); - return Curl_setstropt(&data->set.str[STRING_PROXY], proxy); + return Curl_setstropt(data, STRING_PROXY, proxy); } static CURLcode setopt_cptr_proxy(struct Curl_easy *data, CURLoption option, @@ -1647,16 +1621,23 @@ static CURLcode setopt_cptr_proxy(struct Curl_easy *data, CURLoption option, /* URL decode the components */ if(!result) { - curlx_safefree(s->str[STRING_PROXYUSERNAME]); - curlx_safefree(s->str[STRING_PROXYPASSWORD]); - if(u) - result = Curl_urldecode(u, 0, &s->str[STRING_PROXYUSERNAME], NULL, - REJECT_ZERO); + char *str = NULL; + CURL_EASY_STR_CLEAR(data, STRING_PROXYUSERNAME); + CURL_EASY_STR_CLEAR(data, STRING_PROXYPASSWORD); + if(u) { + result = Curl_urldecode(u, 0, &str, NULL, REJECT_ZERO); + if(!result) + result = Curl_u8_strset_setn(&s->strings, STRING_PROXYUSERNAME, str); + } + if(!result && p) { + str = NULL; + result = Curl_urldecode(p, 0, &str, NULL, REJECT_ZERO); + if(!result) + result = Curl_u8_strset_setn(&s->strings, STRING_PROXYPASSWORD, str); + } } - if(!result && p) - result = Curl_urldecode(p, 0, &s->str[STRING_PROXYPASSWORD], NULL, - REJECT_ZERO); curlx_free(u); + curlx_strzero(p); curlx_free(p); break; } @@ -1664,55 +1645,55 @@ static CURLcode setopt_cptr_proxy(struct Curl_easy *data, CURLoption option, /* * authentication username to use in the operation */ - return Curl_setstropt(&s->str[STRING_PROXYUSERNAME], ptr); + return Curl_setstropt(data, STRING_PROXYUSERNAME, ptr); case CURLOPT_PROXYPASSWORD: /* * authentication password to use in the operation */ - return Curl_setstropt(&s->str[STRING_PROXYPASSWORD], ptr); + return Curl_setstropt(data, STRING_PROXYPASSWORD, ptr); case CURLOPT_NOPROXY: /* * proxy exception list */ - return Curl_setstropt(&s->str[STRING_NOPROXY], ptr); + return Curl_setstropt(data, STRING_NOPROXY, ptr); case CURLOPT_PROXY_SSLCERT: /* * String that holds filename of the SSL certificate to use for proxy */ - return Curl_setstropt(&s->str[STRING_CERT_PROXY], ptr); + return Curl_setstropt(data, STRING_CERT_PROXY, ptr); case CURLOPT_PROXY_SSLCERTTYPE: /* * String that holds file type of the SSL certificate to use for proxy */ - return Curl_setstropt(&s->str[STRING_CERT_TYPE_PROXY], ptr); + return Curl_setstropt(data, STRING_CERT_TYPE_PROXY, ptr); case CURLOPT_PROXY_SSLKEY: /* * String that holds filename of the SSL key to use for proxy */ - return Curl_setstropt(&s->str[STRING_KEY_PROXY], ptr); + return Curl_setstropt(data, STRING_KEY_PROXY, ptr); case CURLOPT_PROXY_KEYPASSWD: /* * String that holds the SSL private key password for proxy. */ - return Curl_setstropt(&s->str[STRING_KEY_PASSWD_PROXY], ptr); + return Curl_setstropt(data, STRING_KEY_PASSWD_PROXY, ptr); case CURLOPT_PROXY_SSLKEYTYPE: /* * String that holds file type of the SSL key to use for proxy */ - return Curl_setstropt(&s->str[STRING_KEY_TYPE_PROXY], ptr); + return Curl_setstropt(data, STRING_KEY_TYPE_PROXY, ptr); case CURLOPT_PROXY_SSL_CIPHER_LIST: if(Curl_ssl_supports(data, SSLSUPP_CIPHER_LIST)) { /* set a list of cipher we want to use in the SSL connection for proxy */ - return Curl_setstropt(&s->str[STRING_SSL_CIPHER_LIST_PROXY], ptr); + return Curl_setstropt(data, STRING_SSL_CIPHER_LIST_PROXY, ptr); } else return CURLE_NOT_BUILT_IN; case CURLOPT_PROXY_TLS13_CIPHERS: if(Curl_ssl_supports(data, SSLSUPP_TLS13_CIPHERSUITES)) /* set preferred list of TLS 1.3 cipher suites for proxy */ - return Curl_setstropt(&s->str[STRING_SSL_CIPHER13_LIST_PROXY], ptr); + return Curl_setstropt(data, STRING_SSL_CIPHER13_LIST_PROXY, ptr); else return CURLE_NOT_BUILT_IN; case CURLOPT_PROXY: @@ -1734,13 +1715,13 @@ static CURLcode setopt_cptr_proxy(struct Curl_easy *data, CURLoption option, * If the proxy is set to "" or NULL we explicitly say that we do not want * to use the socks proxy. */ - return Curl_setstropt(&s->str[STRING_PRE_PROXY], ptr); + return Curl_setstropt(data, STRING_PRE_PROXY, ptr); case CURLOPT_SOCKS5_GSSAPI_SERVICE: case CURLOPT_PROXY_SERVICE_NAME: /* * Set proxy authentication service name for Kerberos 5 and SPNEGO */ - return Curl_setstropt(&s->str[STRING_PROXY_SERVICE_NAME], ptr); + return Curl_setstropt(data, STRING_PROXY_SERVICE_NAME, ptr); case CURLOPT_PROXY_PINNEDPUBLICKEY: /* * Set pinned public key for SSL connection. @@ -1748,7 +1729,7 @@ static CURLcode setopt_cptr_proxy(struct Curl_easy *data, CURLoption option, */ #ifdef USE_SSL if(Curl_ssl_supports(data, SSLSUPP_PINNEDPUBKEY)) - return Curl_setstropt(&s->str[STRING_SSL_PINNEDPUBLICKEY_PROXY], ptr); + return Curl_setstropt(data, STRING_SSL_PINNEDPUBLICKEY_PROXY, ptr); #endif return CURLE_NOT_BUILT_IN; @@ -1756,18 +1737,19 @@ static CURLcode setopt_cptr_proxy(struct Curl_easy *data, CURLoption option, /* * Set the client IP to send through HAProxy PROXY protocol */ - result = Curl_setstropt(&s->str[STRING_HAPROXY_CLIENT_IP], ptr); + result = Curl_setstropt(data, STRING_HAPROXY_CLIENT_IP, ptr); /* enable the HAProxy protocol if an IP is provided */ - s->haproxyprotocol = !!s->str[STRING_HAPROXY_CLIENT_IP]; + s->haproxyprotocol = !!CURL_EASY_STR(data, STRING_HAPROXY_CLIENT_IP); break; case CURLOPT_PROXY_CAINFO: /* * Set CA info SSL connection for proxy. Specify filename of the * CA certificate */ - result = Curl_setstropt(&s->str[STRING_SSL_CAFILE_PROXY], ptr); - s->proxy_ssl.custom_cafile = !!s->str[STRING_SSL_CAFILE_PROXY]; + result = Curl_setstropt(data, STRING_SSL_CAFILE_PROXY, ptr); + s->proxy_ssl.custom_cafile = + !!CURL_EASY_STR(data, STRING_SSL_CAFILE_PROXY); return result; case CURLOPT_PROXY_CRLFILE: /* @@ -1775,14 +1757,14 @@ static CURLcode setopt_cptr_proxy(struct Curl_easy *data, CURLoption option, * CRL to check certificates revocation */ if(Curl_ssl_supports(data, SSLSUPP_CRLFILE)) - return Curl_setstropt(&s->str[STRING_SSL_CRLFILE_PROXY], ptr); + return Curl_setstropt(data, STRING_SSL_CRLFILE_PROXY, ptr); return CURLE_NOT_BUILT_IN; case CURLOPT_PROXY_ISSUERCERT: /* * Set Issuer certificate file to check certificates issuer */ if(Curl_ssl_supports(data, SSLSUPP_ISSUERCERT)) - return Curl_setstropt(&s->str[STRING_SSL_ISSUERCERT_PROXY], ptr); + return Curl_setstropt(data, STRING_SSL_ISSUERCERT_PROXY, ptr); return CURLE_NOT_BUILT_IN; case CURLOPT_PROXY_CAPATH: /* @@ -1792,8 +1774,9 @@ static CURLcode setopt_cptr_proxy(struct Curl_easy *data, CURLoption option, #ifdef USE_SSL if(Curl_ssl_supports(data, SSLSUPP_CA_PATH)) { /* This does not work on Windows. */ - result = Curl_setstropt(&s->str[STRING_SSL_CAPATH_PROXY], ptr); - s->proxy_ssl.custom_capath = !!s->str[STRING_SSL_CAPATH_PROXY]; + result = Curl_setstropt(data, STRING_SSL_CAPATH_PROXY, ptr); + s->proxy_ssl.custom_capath = + !!CURL_EASY_STR(data, STRING_SSL_CAPATH_PROXY); return result; } #endif @@ -1816,8 +1799,16 @@ static CURLcode setopt_copypostfields(const char *ptr, struct UserDefined *s) CURLcode result = CURLE_OK; if(s->postfieldsize < -1) return CURLE_BAD_FUNCTION_ARGUMENT; - if(!ptr || s->postfieldsize == -1) - result = Curl_setstropt(&s->str[STRING_COPYPOSTFIELDS], ptr); + if(!ptr || s->postfieldsize == -1) { + if(ptr && (strlen(ptr) > CURL_MAX_INPUT_LENGTH)) + return CURLE_BAD_FUNCTION_ARGUMENT; + curlx_safefree(s->str_copypostfields); + if(ptr) { + s->str_copypostfields = curlx_strdup(ptr); + if(!s->str_copypostfields) + return CURLE_OUT_OF_MEMORY; + } + } else { size_t pflen = curlx_sotouz_range(s->postfieldsize, 0, SIZE_MAX); if(pflen == SIZE_MAX) @@ -1825,19 +1816,18 @@ static CURLcode setopt_copypostfields(const char *ptr, struct UserDefined *s) else { /* Allocate even when size == 0. This satisfies the need of possible later address compare to detect the COPYPOSTFIELDS mode, and to mark - that postfields is used rather than read function or form data. - */ + that postfields is used rather than read function or form data. */ char *p = curlx_memdup0(ptr, pflen); if(!p) return CURLE_OUT_OF_MEMORY; else { - curlx_free(s->str[STRING_COPYPOSTFIELDS]); - s->str[STRING_COPYPOSTFIELDS] = p; + curlx_free(s->str_copypostfields); + s->str_copypostfields = p; } } } - s->postfields = s->str[STRING_COPYPOSTFIELDS]; + s->postfields = s->str_copypostfields; s->method = HTTPREQ_POST; return result; } @@ -1865,12 +1855,12 @@ static CURLcode setopt_ech(struct Curl_easy *data, const char *ptr) else if(plen > 4 && !strncmp(ptr, "ecl:", 4)) { if(!s->tls_ech) s->tls_ech = CURLECH_HARD; - result = Curl_setstropt(&s->str[STRING_ECH_CONFIG], ptr + 4); + result = Curl_setstropt(data, STRING_ECH_CONFIG, ptr + 4); } else if(plen > 3 && !strncmp(ptr, "pn:", 3)) { if(!s->tls_ech) s->tls_ech = CURLECH_HARD; - result = Curl_setstropt(&s->str[STRING_ECH_PUBLIC], ptr + 3); + result = Curl_setstropt(data, STRING_ECH_PUBLIC, ptr + 3); } else result = CURLE_BAD_FUNCTION_ARGUMENT; @@ -1879,7 +1869,7 @@ static CURLcode setopt_ech(struct Curl_easy *data, const char *ptr) return result; } #else -#define setopt_ech(x,y) CURLE_NOT_BUILT_IN +#define setopt_ech(x, y) CURLE_NOT_BUILT_IN #endif #if defined(USE_SSL) || defined(USE_SSH) @@ -1888,22 +1878,21 @@ static CURLcode setopt_cptr_ssl(struct Curl_easy *data, CURLoption option, char *ptr) { CURLcode result = CURLE_OK; - struct UserDefined *s = &data->set; switch(option) { case CURLOPT_KEYPASSWD: /* * String that holds the SSL or SSH private key password. */ - result = Curl_setstropt(&s->str[STRING_KEY_PASSWD], ptr); + result = Curl_setstropt(data, STRING_KEY_PASSWD, ptr); break; #ifdef USE_SSL case CURLOPT_CAINFO: /* * Set CA info for SSL connection. Specify filename of the CA certificate */ - result = Curl_setstropt(&s->str[STRING_SSL_CAFILE], ptr); - s->ssl.custom_cafile = !!s->str[STRING_SSL_CAFILE]; + result = Curl_setstropt(data, STRING_SSL_CAFILE, ptr); + data->set.ssl.custom_cafile = !!CURL_EASY_STR(data, STRING_SSL_CAFILE); return result; case CURLOPT_CAPATH: /* @@ -1912,8 +1901,8 @@ static CURLcode setopt_cptr_ssl(struct Curl_easy *data, CURLoption option, */ if(Curl_ssl_supports(data, SSLSUPP_CA_PATH)) { /* This does not work on Windows. */ - result = Curl_setstropt(&s->str[STRING_SSL_CAPATH], ptr); - s->ssl.custom_capath = !!s->str[STRING_SSL_CAPATH]; + result = Curl_setstropt(data, STRING_SSL_CAPATH, ptr); + data->set.ssl.custom_capath = !!CURL_EASY_STR(data, STRING_SSL_CAPATH); return result; } return CURLE_NOT_BUILT_IN; @@ -1923,18 +1912,18 @@ static CURLcode setopt_cptr_ssl(struct Curl_easy *data, CURLoption option, * to check certificates revocation */ if(Curl_ssl_supports(data, SSLSUPP_CRLFILE)) - return Curl_setstropt(&s->str[STRING_SSL_CRLFILE], ptr); + return Curl_setstropt(data, STRING_SSL_CRLFILE, ptr); return CURLE_NOT_BUILT_IN; case CURLOPT_SSL_CIPHER_LIST: if(Curl_ssl_supports(data, SSLSUPP_CIPHER_LIST)) /* set a list of cipher we want to use in the SSL connection */ - return Curl_setstropt(&s->str[STRING_SSL_CIPHER_LIST], ptr); + return Curl_setstropt(data, STRING_SSL_CIPHER_LIST, ptr); else return CURLE_NOT_BUILT_IN; case CURLOPT_TLS13_CIPHERS: if(Curl_ssl_supports(data, SSLSUPP_TLS13_CIPHERSUITES)) /* set preferred list of TLS 1.3 cipher suites */ - return Curl_setstropt(&s->str[STRING_SSL_CIPHER13_LIST], ptr); + return Curl_setstropt(data, STRING_SSL_CIPHER13_LIST, ptr); else return CURLE_NOT_BUILT_IN; case CURLOPT_RANDOM_FILE: @@ -1946,7 +1935,7 @@ static CURLcode setopt_cptr_ssl(struct Curl_easy *data, CURLoption option, * Set an SSL_CTX callback parameter pointer */ if(Curl_ssl_supports(data, SSLSUPP_SSL_CTX)) { - s->ssl.fsslctxp = ptr; + data->set.ssl.fsslctxp = ptr; break; } else @@ -1955,28 +1944,28 @@ static CURLcode setopt_cptr_ssl(struct Curl_easy *data, CURLoption option, /* * String that holds filename of the SSL certificate to use */ - return Curl_setstropt(&s->str[STRING_CERT], ptr); + return Curl_setstropt(data, STRING_CERT, ptr); case CURLOPT_SSLCERTTYPE: /* * String that holds file type of the SSL certificate to use */ - return Curl_setstropt(&s->str[STRING_CERT_TYPE], ptr); + return Curl_setstropt(data, STRING_CERT_TYPE, ptr); case CURLOPT_SSLKEY: /* * String that holds filename of the SSL key to use */ - return Curl_setstropt(&s->str[STRING_KEY], ptr); + return Curl_setstropt(data, STRING_KEY, ptr); case CURLOPT_SSLKEYTYPE: /* * String that holds file type of the SSL key to use */ - return Curl_setstropt(&s->str[STRING_KEY_TYPE], ptr); + return Curl_setstropt(data, STRING_KEY_TYPE, ptr); case CURLOPT_SSLENGINE: /* * String that holds the SSL crypto engine. */ if(ptr && ptr[0]) { - result = Curl_setstropt(&s->str[STRING_SSL_ENGINE], ptr); + result = Curl_setstropt(data, STRING_SSL_ENGINE, ptr); if(!result) { result = Curl_ssl_set_engine(data, ptr); } @@ -1988,7 +1977,7 @@ static CURLcode setopt_cptr_ssl(struct Curl_easy *data, CURLoption option, * to check certificates issuer */ if(Curl_ssl_supports(data, SSLSUPP_ISSUERCERT)) - return Curl_setstropt(&s->str[STRING_SSL_ISSUERCERT], ptr); + return Curl_setstropt(data, STRING_SSL_ISSUERCERT, ptr); return CURLE_NOT_BUILT_IN; case CURLOPT_SSL_EC_CURVES: /* @@ -1996,7 +1985,7 @@ static CURLcode setopt_cptr_ssl(struct Curl_easy *data, CURLoption option, * Specify colon-delimited list of curve algorithm names. */ if(Curl_ssl_supports(data, SSLSUPP_SSL_EC_CURVES)) - return Curl_setstropt(&s->str[STRING_SSL_EC_CURVES], ptr); + return Curl_setstropt(data, STRING_SSL_EC_CURVES, ptr); return CURLE_NOT_BUILT_IN; case CURLOPT_SSL_SIGNATURE_ALGORITHMS: /* @@ -2004,7 +1993,7 @@ static CURLcode setopt_cptr_ssl(struct Curl_easy *data, CURLoption option, * Specify colon-delimited list of signature scheme names. */ if(Curl_ssl_supports(data, SSLSUPP_SIGNATURE_ALGORITHMS)) - return Curl_setstropt(&s->str[STRING_SSL_SIGNATURE_ALGORITHMS], ptr); + return Curl_setstropt(data, STRING_SSL_SIGNATURE_ALGORITHMS, ptr); return CURLE_NOT_BUILT_IN; case CURLOPT_PINNEDPUBLICKEY: /* @@ -2012,7 +2001,7 @@ static CURLcode setopt_cptr_ssl(struct Curl_easy *data, CURLoption option, * Specify filename of the public key in DER format. */ if(Curl_ssl_supports(data, SSLSUPP_PINNEDPUBKEY)) - return Curl_setstropt(&s->str[STRING_SSL_PINNEDPUBLICKEY], ptr); + return Curl_setstropt(data, STRING_SSL_PINNEDPUBLICKEY, ptr); return CURLE_NOT_BUILT_IN; case CURLOPT_ECH: return setopt_ech(data, ptr); @@ -2041,7 +2030,7 @@ static CURLcode setopt_cptr_http_mqtt(struct Curl_easy *data, */ s->postfields = ptr; /* Release old copied data. */ - curlx_safefree(s->str[STRING_COPYPOSTFIELDS]); + curlx_safefree(s->str_copypostfields); s->method = HTTPREQ_POST; break; @@ -2056,20 +2045,18 @@ static CURLcode setopt_cptr_http_mqtt(struct Curl_easy *data, * If the encoding is set to "" we use an Accept-Encoding header that * encompasses all the encodings we support. * If the encoding is set to NULL we do not send an Accept-Encoding header - * and ignore an received Content-Encoding header. + * and ignore any received Content-Encoding header. * */ if(ptr && !*ptr) { ptr = Curl_get_content_encodings(); - if(ptr) { - curlx_free(s->str[STRING_ENCODING]); - s->str[STRING_ENCODING] = ptr; - } + if(ptr) + result = CURL_EASY_STR_SETN(data, STRING_ENCODING, ptr); else result = CURLE_OUT_OF_MEMORY; return result; } - return Curl_setstropt(&s->str[STRING_ENCODING], ptr); + return Curl_setstropt(data, STRING_ENCODING, ptr); #ifndef CURL_DISABLE_AWS case CURLOPT_AWS_SIGV4: @@ -2077,34 +2064,51 @@ static CURLcode setopt_cptr_http_mqtt(struct Curl_easy *data, * String that is merged to some authentication * parameters are used by the algorithm. */ - result = Curl_setstropt(&s->str[STRING_AWS_SIGV4], ptr); + result = Curl_setstropt(data, STRING_AWS_SIGV4, ptr); /* - * Basic been set by default it need to be unset here + * Basic has been set by default; it needs to be unset here. */ - if(s->str[STRING_AWS_SIGV4]) + if(CURL_EASY_STR(data, STRING_AWS_SIGV4)) s->httpauth = CURLAUTH_AWS_SIGV4; + else + s->httpauth &= ~(uint32_t)CURLAUTH_AWS_SIGV4; break; #endif - case CURLOPT_REFERER: +#ifndef CURL_DISABLE_HTTPSIG + case CURLOPT_HTTPSIG_KEY: + result = Curl_setstropt(data, STRING_HTTPSIG_KEY, ptr); + break; + case CURLOPT_HTTPSIG_KEYID: + result = Curl_setstropt(data, STRING_HTTPSIG_KEYID, ptr); + break; + case CURLOPT_HTTPSIG_HEADERS: + result = Curl_setstropt(data, STRING_HTTPSIG_HEADERS, ptr); + break; +#endif + case CURLOPT_REFERER: { /* * String to set in the HTTP Referer: field. */ - Curl_bufref_free(&data->state.referer); - result = Curl_setstropt(&s->str[STRING_SET_REFERER], ptr); + struct bufref *oldref = &data->state.referer; + /* free the old after the storing the new in case the input is actually + pointing back to this */ + result = Curl_setstropt(data, STRING_SET_REFERER, ptr); + Curl_bufref_free(oldref); break; + } case CURLOPT_USERAGENT: /* * String to use in the HTTP User-Agent field */ - return Curl_setstropt(&s->str[STRING_USERAGENT], ptr); + return Curl_setstropt(data, STRING_USERAGENT, ptr); #ifndef CURL_DISABLE_COOKIES case CURLOPT_COOKIE: /* * Cookie string to send to the remote server in the request. */ - return Curl_setstropt(&s->str[STRING_COOKIE], ptr); + return Curl_setstropt(data, STRING_COOKIE, ptr); case CURLOPT_COOKIEFILE: return cookiefile(data, ptr); @@ -2113,7 +2117,7 @@ static CURLcode setopt_cptr_http_mqtt(struct Curl_easy *data, /* * Set cookie filename to dump all cookies to when we are done. */ - result = Curl_setstropt(&s->str[STRING_COOKIEJAR], ptr); + result = Curl_setstropt(data, STRING_COOKIEJAR, ptr); if(!result) { /* * Activate the cookie parser. This may or may not already @@ -2150,12 +2154,12 @@ static CURLcode setopt_cptr_ssh(struct Curl_easy *data, CURLoption option, /* * Use this file instead of the $HOME/.ssh/id_dsa.pub file */ - return Curl_setstropt(&s->str[STRING_SSH_PUBLIC_KEY], ptr); + return Curl_setstropt(data, STRING_SSH_PUBLIC_KEY, ptr); case CURLOPT_SSH_PRIVATE_KEYFILE: /* * Use this file instead of the $HOME/.ssh/id_dsa file */ - return Curl_setstropt(&s->str[STRING_SSH_PRIVATE_KEY], ptr); + return Curl_setstropt(data, STRING_SSH_PRIVATE_KEY, ptr); case CURLOPT_SSH_KEYDATA: /* * Custom client data to pass to the SSH keyfunc callback @@ -2167,18 +2171,18 @@ static CURLcode setopt_cptr_ssh(struct Curl_easy *data, CURLoption option, * Option to allow for the MD5 of the host public key to be checked * for validation purposes. */ - return Curl_setstropt(&s->str[STRING_SSH_HOST_PUBLIC_KEY_MD5], ptr); + return Curl_setstropt(data, STRING_SSH_HOST_PUBLIC_KEY_MD5, ptr); case CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256: /* * Option to allow for the SHA256 of the host public key to be checked * for validation purposes. */ - return Curl_setstropt(&s->str[STRING_SSH_HOST_PUBLIC_KEY_SHA256], ptr); + return Curl_setstropt(data, STRING_SSH_HOST_PUBLIC_KEY_SHA256, ptr); case CURLOPT_SSH_KNOWNHOSTS: /* * Store the filename to read known hosts from. */ - return Curl_setstropt(&s->str[STRING_SSH_KNOWNHOSTS], ptr); + return Curl_setstropt(data, STRING_SSH_KNOWNHOSTS, ptr); #ifdef USE_LIBSSH2 case CURLOPT_SSH_HOSTKEYDATA: /* @@ -2205,15 +2209,15 @@ static CURLcode setopt_cptr_ftp(struct Curl_easy *data, CURLoption option, /* * Use FTP PORT, this also specifies which IP address to use */ - result = Curl_setstropt(&s->str[STRING_FTPPORT], ptr); - s->ftp_use_port = !!(s->str[STRING_FTPPORT]); + result = Curl_setstropt(data, STRING_FTPPORT, ptr); + s->ftp_use_port = !!CURL_EASY_STR(data, STRING_FTPPORT); break; case CURLOPT_FTP_ACCOUNT: - return Curl_setstropt(&s->str[STRING_FTP_ACCOUNT], ptr); + return Curl_setstropt(data, STRING_FTP_ACCOUNT, ptr); case CURLOPT_FTP_ALTERNATIVE_TO_USER: - return Curl_setstropt(&s->str[STRING_FTP_ALTERNATIVE_TO_USER], ptr); + return Curl_setstropt(data, STRING_FTP_ALTERNATIVE_TO_USER, ptr); case CURLOPT_KRBLEVEL: return CURLE_NOT_BUILT_IN; /* removed in 8.17.0 */ @@ -2233,44 +2237,41 @@ static CURLcode setopt_cptr_ftp(struct Curl_easy *data, CURLoption option, static CURLcode setopt_cptr_net(struct Curl_easy *data, CURLoption option, char *ptr) { - struct UserDefined *s = &data->set; switch(option) { case CURLOPT_INTERFACE: /* * Set what interface or address/hostname to bind the socket to when * performing an operation and thus what from-IP your connection will use. */ - return setstropt_interface(ptr, - &s->str[STRING_DEVICE], - &s->str[STRING_INTERFACE], - &s->str[STRING_BINDHOST]); + return setstropt_interface(data, ptr); + #ifdef USE_RESOLV_ARES case CURLOPT_DNS_SERVERS: - return Curl_setstropt(&s->str[STRING_DNS_SERVERS], ptr); + return Curl_setstropt(data, STRING_DNS_SERVERS, ptr); case CURLOPT_DNS_INTERFACE: - return Curl_setstropt(&s->str[STRING_DNS_INTERFACE], ptr); + return Curl_setstropt(data, STRING_DNS_INTERFACE, ptr); case CURLOPT_DNS_LOCAL_IP4: - return Curl_setstropt(&s->str[STRING_DNS_LOCAL_IP4], ptr); + return Curl_setstropt(data, STRING_DNS_LOCAL_IP4, ptr); case CURLOPT_DNS_LOCAL_IP6: - return Curl_setstropt(&s->str[STRING_DNS_LOCAL_IP6], ptr); + return Curl_setstropt(data, STRING_DNS_LOCAL_IP6, ptr); #endif #ifdef USE_UNIX_SOCKETS case CURLOPT_UNIX_SOCKET_PATH: - s->abstract_unix_socket = FALSE; - return Curl_setstropt(&s->str[STRING_UNIX_SOCKET_PATH], ptr); + data->set.abstract_unix_socket = FALSE; + return Curl_setstropt(data, STRING_UNIX_SOCKET_PATH, ptr); case CURLOPT_ABSTRACT_UNIX_SOCKET: - s->abstract_unix_socket = TRUE; - return Curl_setstropt(&s->str[STRING_UNIX_SOCKET_PATH], ptr); + data->set.abstract_unix_socket = TRUE; + return Curl_setstropt(data, STRING_UNIX_SOCKET_PATH, ptr); #endif #ifndef CURL_DISABLE_DOH case CURLOPT_DOH_URL: { - CURLcode result = Curl_setstropt(&s->str[STRING_DOH], ptr); - s->doh = !!(s->str[STRING_DOH]); + CURLcode result = Curl_setstropt(data, STRING_DOH, ptr); + data->set.doh = !!CURL_EASY_STR(data, STRING_DOH); return result; } #endif @@ -2287,19 +2288,19 @@ static CURLcode setopt_cptr_misc(struct Curl_easy *data, CURLoption option, switch(option) { case CURLOPT_REQUEST_TARGET: - return Curl_setstropt(&s->str[STRING_TARGET], ptr); + return Curl_setstropt(data, STRING_TARGET, ptr); #ifndef CURL_DISABLE_NETRC case CURLOPT_NETRC_FILE: - return Curl_setstropt(&s->str[STRING_NETRC_FILE], ptr); + return Curl_setstropt(data, STRING_NETRC_FILE, ptr); #endif case CURLOPT_CUSTOMREQUEST: - return Curl_setstropt(&s->str[STRING_CUSTOMREQUEST], ptr); + return Curl_setstropt(data, STRING_CUSTOMREQUEST, ptr); /* we do not set s->method = HTTPREQ_CUSTOM; here, we continue as if we were using the already set type and this changes the actual request keyword */ case CURLOPT_SERVICE_NAME: - return Curl_setstropt(&s->str[STRING_SERVICE_NAME], ptr); + return Curl_setstropt(data, STRING_SERVICE_NAME, ptr); case CURLOPT_HEADERDATA: s->writeheader = ptr; @@ -2341,27 +2342,40 @@ static CURLcode setopt_cptr_misc(struct Curl_easy *data, CURLoption option, s->errorbuffer = ptr; break; case CURLOPT_URL: - result = Curl_setstropt(&s->str[STRING_SET_URL], ptr); - Curl_bufref_set(&data->state.url, s->str[STRING_SET_URL], 0, NULL); + result = Curl_setstropt(data, STRING_SET_URL, ptr); + Curl_bufref_set(&data->state.url, + CURL_EASY_STR(data, STRING_SET_URL), 0, NULL); break; - case CURLOPT_USERPWD: - return setstropt_userpwd(ptr, &s->str[STRING_USERNAME], - &s->str[STRING_PASSWORD]); + case CURLOPT_USERPWD: { + char *u = NULL, *p = NULL; + result = setstropt_userpwd(ptr, &u, &p); + if(!result) { + result = CURL_EASY_STR_SETN(data, STRING_USERNAME, u); + u = NULL; + } + if(!result) { + result = CURL_EASY_STR_SETN(data, STRING_PASSWORD, p); + p = NULL; + } + curlx_free(u); + curlx_free(p); + return result; + } case CURLOPT_USERNAME: - return Curl_setstropt(&s->str[STRING_USERNAME], ptr); + return Curl_setstropt(data, STRING_USERNAME, ptr); case CURLOPT_PASSWORD: - return Curl_setstropt(&s->str[STRING_PASSWORD], ptr); + return Curl_setstropt(data, STRING_PASSWORD, ptr); case CURLOPT_LOGIN_OPTIONS: - return Curl_setstropt(&s->str[STRING_OPTIONS], ptr); + return Curl_setstropt(data, STRING_OPTIONS, ptr); case CURLOPT_XOAUTH2_BEARER: - return Curl_setstropt(&s->str[STRING_BEARER], ptr); + return Curl_setstropt(data, STRING_BEARER, ptr); case CURLOPT_RANGE: - return Curl_setstropt(&s->str[STRING_SET_RANGE], ptr); + return Curl_setstropt(data, STRING_SET_RANGE, ptr); case CURLOPT_PRIVATE: s->private_data = ptr; break; @@ -2389,49 +2403,36 @@ static CURLcode setopt_cptr_misc(struct Curl_easy *data, CURLoption option, break; case CURLOPT_DEFAULT_PROTOCOL: /* Set the protocol to use when the URL does not include any protocol */ - return Curl_setstropt(&s->str[STRING_DEFAULT_PROTOCOL], ptr); + return Curl_setstropt(data, STRING_DEFAULT_PROTOCOL, ptr); #ifndef CURL_DISABLE_SMTP case CURLOPT_MAIL_FROM: /* Set the SMTP mail originator */ - return Curl_setstropt(&s->str[STRING_MAIL_FROM], ptr); + return Curl_setstropt(data, STRING_MAIL_FROM, ptr); case CURLOPT_MAIL_AUTH: /* Set the SMTP auth originator */ - return Curl_setstropt(&s->str[STRING_MAIL_AUTH], ptr); + return Curl_setstropt(data, STRING_MAIL_AUTH, ptr); #endif case CURLOPT_SASL_AUTHZID: /* Authorization identity (identity to act as) */ - return Curl_setstropt(&s->str[STRING_SASL_AUTHZID], ptr); + return Curl_setstropt(data, STRING_SASL_AUTHZID, ptr); #ifndef CURL_DISABLE_RTSP case CURLOPT_RTSP_SESSION_ID: - return Curl_setstropt(&s->str[STRING_RTSP_SESSION_ID], ptr); + return Curl_setstropt(data, STRING_RTSP_SESSION_ID, ptr); case CURLOPT_RTSP_STREAM_URI: - return Curl_setstropt(&s->str[STRING_RTSP_STREAM_URI], ptr); + return Curl_setstropt(data, STRING_RTSP_STREAM_URI, ptr); case CURLOPT_RTSP_TRANSPORT: - return Curl_setstropt(&s->str[STRING_RTSP_TRANSPORT], ptr); + return Curl_setstropt(data, STRING_RTSP_TRANSPORT, ptr); case CURLOPT_INTERLEAVEDATA: s->rtp_out = ptr; break; #endif /* !CURL_DISABLE_RTSP */ -#ifdef USE_TLS_SRP case CURLOPT_TLSAUTH_USERNAME: - return Curl_setstropt(&s->str[STRING_TLSAUTH_USERNAME], ptr); case CURLOPT_TLSAUTH_PASSWORD: - return Curl_setstropt(&s->str[STRING_TLSAUTH_PASSWORD], ptr); case CURLOPT_TLSAUTH_TYPE: - if(ptr && !curl_strequal(ptr, "SRP")) - result = CURLE_BAD_FUNCTION_ARGUMENT; - break; -#ifndef CURL_DISABLE_PROXY case CURLOPT_PROXY_TLSAUTH_USERNAME: - return Curl_setstropt(&s->str[STRING_TLSAUTH_USERNAME_PROXY], ptr); case CURLOPT_PROXY_TLSAUTH_PASSWORD: - return Curl_setstropt(&s->str[STRING_TLSAUTH_PASSWORD_PROXY], ptr); case CURLOPT_PROXY_TLSAUTH_TYPE: - if(ptr && !curl_strequal(ptr, "SRP")) - result = CURLE_BAD_FUNCTION_ARGUMENT; - break; -#endif -#endif + return CURLE_NOT_BUILT_IN; #ifndef CURL_DISABLE_HSTS case CURLOPT_HSTSREADDATA: s->hsts_read_userp = ptr; @@ -2447,7 +2448,7 @@ static CURLcode setopt_cptr_misc(struct Curl_easy *data, CURLoption option, return CURLE_OUT_OF_MEMORY; } if(ptr) { - result = Curl_setstropt(&s->str[STRING_HSTS], ptr); + result = Curl_setstropt(data, STRING_HSTS, ptr); if(result) return result; /* this needs to build a list of filenames to read from, so that it can @@ -2479,7 +2480,7 @@ static CURLcode setopt_cptr_misc(struct Curl_easy *data, CURLoption option, if(!data->asi) return CURLE_OUT_OF_MEMORY; } - result = Curl_setstropt(&s->str[STRING_ALTSVC], ptr); + result = Curl_setstropt(data, STRING_ALTSVC, ptr); if(result) break; if(ptr) @@ -2499,24 +2500,25 @@ static CURLcode setopt_cptr(struct Curl_easy *data, CURLoption option, { typedef CURLcode (*ptrfunc)(struct Curl_easy *data, CURLoption option, char *ptr); + /* Order by likeliness */ static const ptrfunc setopt_call[] = { + setopt_cptr_misc, +#if defined(USE_SSL) || defined(USE_SSH) + setopt_cptr_ssl, +#endif #ifndef CURL_DISABLE_PROXY setopt_cptr_proxy, #endif -#if defined(USE_SSL) || defined(USE_SSH) - setopt_cptr_ssl, + setopt_cptr_net, +#ifndef CURL_DISABLE_FTP + setopt_cptr_ftp, #endif #ifdef USE_SSH setopt_cptr_ssh, #endif -#ifndef CURL_DISABLE_FTP - setopt_cptr_ftp, -#endif #if !defined(CURL_DISABLE_HTTP) || !defined(CURL_DISABLE_MQTT) setopt_cptr_http_mqtt, #endif - setopt_cptr_net, - setopt_cptr_misc, }; size_t i; @@ -2739,10 +2741,9 @@ static CURLcode setopt_offt(struct Curl_easy *data, CURLoption option, if(offt < -1) return CURLE_BAD_FUNCTION_ARGUMENT; - if(s->postfieldsize < offt && - s->postfields == s->str[STRING_COPYPOSTFIELDS]) { + if(s->postfieldsize < offt && s->str_copypostfields) { /* Previous CURLOPT_COPYPOSTFIELDS is no longer valid. */ - curlx_safefree(s->str[STRING_COPYPOSTFIELDS]); + curlx_safefree(s->str_copypostfields); s->postfields = NULL; } s->postfieldsize = offt; @@ -2906,10 +2907,11 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) case CURLOPT_MIMEPOST: /* curl_mime * */ case CURLOPT_STDERR: /* FILE * */ case CURLOPT_SHARE: /* CURLSH * */ - case CURLOPT_STREAM_DEPENDS: /* CURL * */ - case CURLOPT_STREAM_DEPENDS_E: /* CURL * */ case CURLOPT_CURLU: /* CURLU * */ return setopt_pointers(data, option, param); + case CURLOPT_STREAM_DEPENDS: /* CURL * */ + case CURLOPT_STREAM_DEPENDS_E: /* CURL * */ + return CURLE_OK; default: break; } @@ -2930,23 +2932,24 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) * NOTE: This is one of few API functions that are allowed to be called from * within a callback. */ - #undef curl_easy_setopt CURLcode curl_easy_setopt(CURL *curl, CURLoption option, ...) { - va_list arg; + struct Curl_eapi_guard guard; CURLcode result; - struct Curl_easy *data = curl; - if(!data) - return CURLE_BAD_FUNCTION_ARGUMENT; + if(CURL_EAPI_ENTER(&guard, curl, easy_setopt, &result)) { + struct Curl_easy *data = curl; + va_list arg; - va_start(arg, option); + va_start(arg, option); - result = Curl_vsetopt(data, option, arg); + result = Curl_vsetopt(data, option, arg); - va_end(arg); - if(result == CURLE_BAD_FUNCTION_ARGUMENT) - failf(data, "setopt 0x%x got bad argument", (unsigned int)option); + va_end(arg); + if(result == CURLE_BAD_FUNCTION_ARGUMENT) + failf(data, "setopt 0x%x got bad argument", (unsigned int)option); + } + CURL_EAPI_LEAVE(&guard); return result; } diff --git a/Utilities/cmcurl/lib/setopt.h b/Utilities/cmcurl/lib/setopt.h index c421f5c5e5..e386262f88 100644 --- a/Utilities/cmcurl/lib/setopt.h +++ b/Utilities/cmcurl/lib/setopt.h @@ -31,7 +31,8 @@ CURLcode Curl_setopt_SSLVERSION(struct Curl_easy *data, CURLoption option, #define Curl_setopt_SSLVERSION(a, b, c) CURLE_NOT_BUILT_IN #endif -CURLcode Curl_setstropt(char **charp, const char *s) WARN_UNUSED_RESULT; +CURLcode Curl_setstropt(struct Curl_easy *data, + enum dupstring id, const char *s) WARN_UNUSED_RESULT; CURLcode Curl_setblobopt(struct curl_blob **blobp, const struct curl_blob *blob) WARN_UNUSED_RESULT; CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) diff --git a/Utilities/cmcurl/lib/sha256.c b/Utilities/cmcurl/lib/sha256.c index 047119044b..89b96017bd 100644 --- a/Utilities/cmcurl/lib/sha256.c +++ b/Utilities/cmcurl/lib/sha256.c @@ -32,7 +32,7 @@ #ifdef USE_MBEDTLS #include #if MBEDTLS_VERSION_NUMBER < 0x03020000 -#error "mbedTLS 3.2.0 or later required" +#error "mbedTLS 3.2.0 or greater required" #endif #include #endif @@ -61,12 +61,12 @@ typedef struct ossl_sha256_ctx my_sha256_ctx; static CURLcode my_sha256_init(void *in) { my_sha256_ctx *ctx = (my_sha256_ctx *)in; - ctx->openssl_ctx = EVP_MD_CTX_create(); + ctx->openssl_ctx = EVP_MD_CTX_new(); if(!ctx->openssl_ctx) return CURLE_OUT_OF_MEMORY; if(!EVP_DigestInit_ex(ctx->openssl_ctx, EVP_sha256(), NULL)) { - EVP_MD_CTX_destroy(ctx->openssl_ctx); + EVP_MD_CTX_free(ctx->openssl_ctx); return CURLE_FAILED_INIT; } return CURLE_OK; @@ -84,7 +84,7 @@ static void my_sha256_final(unsigned char *digest, void *in) { my_sha256_ctx *ctx = (my_sha256_ctx *)in; EVP_DigestFinal_ex(ctx->openssl_ctx, digest, NULL); - EVP_MD_CTX_destroy(ctx->openssl_ctx); + EVP_MD_CTX_free(ctx->openssl_ctx); } #elif defined(USE_WOLFSSL) @@ -148,8 +148,9 @@ typedef psa_hash_operation_t my_sha256_ctx; static CURLcode my_sha256_init(void *ctx) { - memset(ctx, 0, sizeof(my_sha256_ctx)); - if(psa_hash_setup(ctx, PSA_ALG_SHA_256) != PSA_SUCCESS) + psa_hash_operation_t *pctx = (psa_hash_operation_t *)ctx; + *pctx = psa_hash_operation_init(); + if(psa_hash_setup(pctx, PSA_ALG_SHA_256) != PSA_SUCCESS) return CURLE_OUT_OF_MEMORY; return CURLE_OK; } @@ -381,12 +382,11 @@ static CURLcode my_sha256_init(void *in) return CURLE_OK; } -/* - Process a block of memory though the hash +/* Process a block of memory though the hash @param md The hash state @param in The data to hash @param inlen The length of the data (octets) -*/ + */ static void my_sha256_update(void *ctx, const unsigned char *in, unsigned int len) @@ -421,12 +421,11 @@ static void my_sha256_update(void *ctx, } } -/* - Terminate the hash to get the digest +/* Terminate the hash to get the digest @param md The hash state @param out [out] The destination of the hash (32 bytes) @return 0 if successful -*/ + */ static void my_sha256_final(unsigned char *out, void *ctx) { struct sha256_state *md = ctx; diff --git a/Utilities/cmcurl/lib/smb.c b/Utilities/cmcurl/lib/smb.c index 70af8d5970..4299190ff5 100644 --- a/Utilities/cmcurl/lib/smb.c +++ b/Utilities/cmcurl/lib/smb.c @@ -654,9 +654,10 @@ static CURLcode smb_send_negotiate(struct Curl_easy *data, struct smb_conn *smbc, struct smb_request *req) { - const char *msg = "\x00\x0c\x00\x02NT LM 0.12"; + static const char msg[] = "\x00\x0c\x00\x02NT LM 0.12"; - return smb_send_message(data, smbc, req, SMB_COM_NEGOTIATE, msg, 15); + return smb_send_message(data, smbc, req, SMB_COM_NEGOTIATE, msg, + sizeof(msg)); } static CURLcode smb_send_setup(struct Curl_easy *data) @@ -678,7 +679,7 @@ static CURLcode smb_send_setup(struct Curl_easy *data) byte_count = sizeof(lm) + sizeof(nt) + strlen(smbc->user) + strlen(smbc->domain) + - strlen(CURL_OS) + strlen(CLIENTNAME) + 4; /* 4 null chars */ + CURL_CSTRLEN(CURL_OS) + CURL_CSTRLEN(CLIENTNAME) + 4; /* 4 null chars */ if(byte_count > sizeof(msg.bytes)) return CURLE_FILESIZE_EXCEEDED; @@ -724,7 +725,7 @@ static CURLcode smb_send_tree_connect(struct Curl_easy *data, char *p = msg.bytes; const size_t byte_count = strlen(conn->origin->hostname) + strlen(smbc->share) + - strlen(SERVICENAME) + 5; /* 2 nulls and 3 backslashes */ + CURL_CSTRLEN(SERVICENAME) + 5; /* 2 nulls and 3 backslashes */ if(byte_count > sizeof(msg.bytes)) return CURLE_FILESIZE_EXCEEDED; @@ -912,7 +913,8 @@ static CURLcode smb_connection_state(struct Curl_easy *data, bool *done) result = smb_send_negotiate(data, smbc, req); if(result) { - connclose(conn, "SMB: failed to send negotiate message"); + CURL_TRC_M(data, "SMB: failed to send negotiate message"); + connclose(conn); return result; } @@ -922,7 +924,8 @@ static CURLcode smb_connection_state(struct Curl_easy *data, bool *done) /* Send the previous message and check for a response */ result = smb_send_and_recv(data, smbc, &msg); if(result && result != CURLE_AGAIN) { - connclose(conn, "SMB: failed to communicate"); + CURL_TRC_M(data, "SMB: failed to communicate"); + connclose(conn); return result; } @@ -935,7 +938,8 @@ static CURLcode smb_connection_state(struct Curl_easy *data, bool *done) case SMB_NEGOTIATE: if((smbc->got < sizeof(*nrsp) + sizeof(smbc->challenge) - 1) || h->status) { - connclose(conn, "SMB: negotiation failed"); + CURL_TRC_M(data, "SMB: negotiation failed"); + connclose(conn); return CURLE_COULDNT_CONNECT; } nrsp = msg; @@ -952,7 +956,8 @@ static CURLcode smb_connection_state(struct Curl_easy *data, bool *done) smbc->session_key = smb_swap32(nrsp->session_key); result = smb_send_setup(data); if(result) { - connclose(conn, "SMB: failed to send setup message"); + CURL_TRC_M(data, "SMB: failed to send setup message"); + connclose(conn); return result; } conn_state(data, smbc, SMB_SETUP); @@ -960,7 +965,8 @@ static CURLcode smb_connection_state(struct Curl_easy *data, bool *done) case SMB_SETUP: if(h->status) { - connclose(conn, "SMB: authentication failed"); + CURL_TRC_M(data, "SMB: authentication failed"); + connclose(conn); return CURLE_LOGIN_DENIED; } smbc->uid = smb_swap16(h->uid); @@ -1025,7 +1031,8 @@ static CURLcode smb_request_state(struct Curl_easy *data, bool *done) if(req->state == SMB_REQUESTING) { result = smb_send_tree_connect(data, smbc, req); if(result) { - connclose(conn, "SMB: failed to send tree connect message"); + CURL_TRC_M(data, "SMB: failed to send tree connect message"); + connclose(conn); return result; } @@ -1035,7 +1042,8 @@ static CURLcode smb_request_state(struct Curl_easy *data, bool *done) /* Send the previous message and check for a response */ result = smb_send_and_recv(data, smbc, &msg); if(result && result != CURLE_AGAIN) { - connclose(conn, "SMB: failed to communicate"); + CURL_TRC_M(data, "SMB: failed to communicate"); + connclose(conn); return result; } @@ -1180,7 +1188,8 @@ static CURLcode smb_request_state(struct Curl_easy *data, bool *done) } if(result) { - connclose(conn, "SMB: failed to send message"); + CURL_TRC_M(data, "SMB: failed to send message"); + connclose(conn); return result; } diff --git a/Utilities/cmcurl/lib/smtp.c b/Utilities/cmcurl/lib/smtp.c index 2283f5e87d..50e2af400a 100644 --- a/Utilities/cmcurl/lib/smtp.c +++ b/Utilities/cmcurl/lib/smtp.c @@ -60,7 +60,6 @@ #include "sendf.h" #include "curl_trc.h" -#include "hostip.h" #include "progress.h" #include "transfer.h" #include "escape.h" @@ -208,7 +207,7 @@ static CURLcode smtp_parse_custom_request(struct Curl_easy *data, struct SMTP *smtp) { CURLcode result = CURLE_OK; - const char *custom = data->set.str[STRING_CUSTOMREQUEST]; + const char *custom = CURL_EASY_STR(data, STRING_CUSTOMREQUEST); /* URL decode the custom request */ if(custom) @@ -250,16 +249,26 @@ static CURLcode smtp_parse_custom_request(struct Curl_easy *data, * calling function deems it to be) then the input will be returned in * the address part with the hostname being NULL. */ -static CURLcode smtp_parse_address(const char *fqma, char **address, - struct hostname *host, const char **suffix) +static CURLcode smtp_parse_address(struct Curl_easy *data, const char *fqma, + char **address, struct hostname *host, + const char **suffix) { CURLcode result = CURLE_OK; size_t length; char *addressend; + char *dup; + + /* A CR or LF in the address ends up verbatim in the MAIL FROM/RCPT TO + command line, so a crafted address could smuggle further SMTP commands + onto the wire. Reject it before the command is built. */ + if(strpbrk(fqma, "\r\n")) { + failf(data, "Refusing to send email address with a CR or LF"); + return CURLE_BAD_FUNCTION_ARGUMENT; + } /* Duplicate the fully qualified email address so we can manipulate it, ensuring it does not contain the delimiters if specified */ - char *dup = curlx_strdup(fqma[0] == '<' ? fqma + 1 : fqma); + dup = curlx_strdup(fqma[0] == '<' ? fqma + 1 : fqma); if(!dup) return CURLE_OUT_OF_MEMORY; @@ -736,7 +745,8 @@ static CURLcode smtp_perform_auth(struct Curl_easy *data, if(ir) { /* AUTH ... */ /* Send the AUTH command with the initial response */ - result = Curl_pp_sendf(data, &smtpc->pp, "AUTH %s %s", mech, ir); + result = Curl_pp_sendf(data, &smtpc->pp, "AUTH %s %s", + mech, *ir ? ir : "="); } else { /* Send the AUTH command */ @@ -842,7 +852,7 @@ static CURLcode smtp_perform_command(struct Curl_easy *data, /* Parse the mailbox to verify into the local address and hostname parts, converting the hostname to an IDN A-label if necessary */ - result = smtp_parse_address(smtp->rcpt->data, + result = smtp_parse_address(data, smtp->rcpt->data, &address, &host, &suffix); if(result) return result; @@ -868,7 +878,7 @@ static CURLcode smtp_perform_command(struct Curl_easy *data, else { /* Establish whether we should report that we support SMTPUTF8 for EXPN commands to the server as per RFC-6531 sect. 3.1 point 6 */ - utf8 = (smtpc->utf8_supported) && (!strcmp(smtp->custom, "EXPN")); + utf8 = smtpc->utf8_supported && !strcmp(smtp->custom, "EXPN"); /* Send the custom recipient based command such as the EXPN command */ result = Curl_pp_sendf(data, &smtpc->pp, @@ -902,6 +912,7 @@ static CURLcode smtp_perform_mail(struct Curl_easy *data, char *from = NULL; char *auth = NULL; char *size = NULL; + const char *str; CURLcode result = CURLE_OK; /* We notify the server we are sending UTF-8 data if a) it supports the @@ -911,15 +922,15 @@ static CURLcode smtp_perform_mail(struct Curl_easy *data, bool utf8 = FALSE; /* Calculate the FROM parameter */ - if(data->set.str[STRING_MAIL_FROM]) { + str = CURL_EASY_STR(data, STRING_MAIL_FROM); + if(str) { char *address = NULL; struct hostname host = { NULL, NULL, NULL, NULL }; const char *suffix = ""; /* Parse the FROM mailbox into the local address and hostname parts, converting the hostname to an IDN A-label if necessary */ - result = smtp_parse_address(data->set.str[STRING_MAIL_FROM], - &address, &host, &suffix); + result = smtp_parse_address(data, str, &address, &host, &suffix); if(result) goto out; @@ -952,16 +963,16 @@ static CURLcode smtp_perform_mail(struct Curl_easy *data, } /* Calculate the optional AUTH parameter */ - if(data->set.str[STRING_MAIL_AUTH] && smtpc->sasl.authused) { - if(data->set.str[STRING_MAIL_AUTH][0] != '\0') { + str = CURL_EASY_STR(data, STRING_MAIL_AUTH); + if(str && smtpc->sasl.authused) { + if(str[0] != '\0') { char *address = NULL; struct hostname host = { NULL, NULL, NULL, NULL }; const char *suffix = ""; /* Parse the AUTH mailbox into the local address and hostname parts, converting the hostname to an IDN A-label if necessary */ - result = smtp_parse_address(data->set.str[STRING_MAIL_AUTH], - &address, &host, &suffix); + result = smtp_parse_address(data, str, &address, &host, &suffix); if(result) goto out; @@ -1097,7 +1108,7 @@ static CURLcode smtp_perform_rcpt_to(struct Curl_easy *data, /* Parse the recipient mailbox into the local address and hostname parts, converting the hostname to an IDN A-label if necessary */ - result = smtp_parse_address(smtp->rcpt->data, + result = smtp_parse_address(data, smtp->rcpt->data, &address, &host, &suffix); if(result) return result; @@ -1412,8 +1423,8 @@ static CURLcode smtp_state_rcpt_resp(struct Curl_easy *data, is_smtp_err = (smtpcode / 100 != 2); - /* If there is multiple RCPT TO to be issued, it is possible to ignore errors - and proceed with only the valid addresses. */ + /* If there are multiple RCPT TO commands to issue, it is possible to + ignore errors and proceed with only the valid addresses. */ is_smtp_blocking_err = (is_smtp_err && !data->set.mail_rcpt_allowfails); if(is_smtp_err) { @@ -1646,7 +1657,7 @@ static const struct SASLproto saslsmtp = { smtp_continue_auth, /* Send authentication continuation */ smtp_cancel_auth, /* Cancel authentication */ smtp_get_message, /* Get SASL response message */ - 512 - 8, /* Max line len - strlen("AUTH ") - 1 space - crlf */ + 512 - 8, /* Max line len - strlen("AUTH ") - 1 space - CRLF */ 334, /* Code received when continuation is expected */ 235, /* Code to receive upon authentication success */ SASL_AUTH_DEFAULT, /* Default mechanisms */ @@ -1728,7 +1739,8 @@ static CURLcode smtp_done(struct Curl_easy *data, CURLcode status, curlx_safefree(smtp->custom); if(status) { - connclose(conn, "SMTP done with bad status"); /* marked for closure */ + CURL_TRC_M(data, "SMTP done with bad status"); + connclose(conn); /* marked for closure */ result = status; /* use the already set error code */ } else if(!data->set.connect_only && data->set.mail_rcpt && diff --git a/Utilities/cmcurl/lib/socketpair.c b/Utilities/cmcurl/lib/socketpair.c index 39a0e319b0..d4ac66e95e 100644 --- a/Utilities/cmcurl/lib/socketpair.c +++ b/Utilities/cmcurl/lib/socketpair.c @@ -96,14 +96,14 @@ static int wakeup_socketpair(curl_socket_t socks[2], bool nonblocking) #ifdef SOCK_CLOEXEC type |= SOCK_CLOEXEC; #endif -#ifdef SOCK_NONBLOCK +#ifdef CURL_USE_SOCK_NONBLOCK if(nonblocking) type |= SOCK_NONBLOCK; #endif if(CURL_SOCKETPAIR(AF_UNIX, type, 0, socks)) return -1; -#ifndef SOCK_NONBLOCK +#ifndef CURL_USE_SOCK_NONBLOCK if(nonblocking) { if(curlx_nonblock(socks[0], TRUE) < 0 || curlx_nonblock(socks[1], TRUE) < 0) { diff --git a/Utilities/cmcurl/lib/socks.c b/Utilities/cmcurl/lib/socks.c index 6c458c505d..949f20c135 100644 --- a/Utilities/cmcurl/lib/socks.c +++ b/Utilities/cmcurl/lib/socks.c @@ -38,9 +38,9 @@ #include "curl_trc.h" #include "select.h" #include "cfilters.h" -#include "cf-dns.h" #include "connect.h" #include "socks.h" +#include "vdns/cf-dns.h" #include "curlx/inet_pton.h" /* for the (SOCKS) connect state machine */ @@ -105,7 +105,6 @@ struct socks_ctx { uint8_t proxy_type; unsigned char version; BIT(resolve_local); - BIT(start_resolving); BIT(socks4a); }; @@ -318,32 +317,19 @@ static CURLproxycode socks4_resolving(struct socks_ctx *sx, const struct Curl_addrinfo *ai = NULL; CURLcode result; size_t nwritten; - bool dns_done; *done = FALSE; - if(sx->start_resolving) { - /* need to resolve hostname to add destination address */ - sx->start_resolving = FALSE; - result = Curl_cf_dns_insert_after( - cf, data, Curl_resolv_dns_queries(data, sx->ip_version), - sx->dest, TRNSPRT_TCP, TRUE); - if(result) { - failf(data, "unable to create DNS filter for socks"); - return CURLPX_UNKNOWN_FAIL; - } - } - - /* resolve the hostname by connecting the DNS filter */ - result = Curl_conn_cf_connect(cf->next, data, &dns_done); + result = Curl_conn_dns_addr_result(cf->conn, cf->sockindex, sx->dest); if(result) { - failf(data, "Failed to resolve \"%s\" for SOCKS4 connect.", - sx->dest->hostname); - return CURLPX_RESOLVE_HOST; - } - else if(!dns_done) + if(result != CURLE_AGAIN) { + failf(data, "error %d resolving SOCKS destination %s:%u", + (int)result, sx->dest->hostname, sx->dest->port); + return CURLPX_RESOLVE_HOST; + } return CURLPX_OK; + } - ai = Curl_cf_dns_get_ai(cf->next, data, sx->dest, AF_INET, 0); + ai = Curl_conn_dns_get_ai(data, sx->dest, cf->sockindex, AF_INET, 0); if(ai) { struct sockaddr_in *saddr_in; char ipbuf[64]; @@ -361,7 +347,7 @@ static CURLproxycode socks4_resolving(struct socks_ctx *sx, return CURLPX_SEND_REQUEST; } else { - /* No ipv4 address resolved */ + /* No IPv4 address resolved */ failf(data, "SOCKS4 connection to %s not supported", sx->dest->hostname); return CURLPX_RESOLVE_HOST; } @@ -476,9 +462,7 @@ process_state: case SOCKS4_ST_START: Curl_bufq_reset(&sx->iobuf); - sx->start_resolving = FALSE; sx->socks4a = (sx->proxy_type == CURLPROXY_SOCKS4A); - sx->resolve_local = !sx->socks4a; sx->presult = CURLPX_OK; /* SOCKS4 can only do IPv4, insist! */ @@ -526,7 +510,6 @@ process_state: sxstate(sx, cf, data, SOCKS4_ST_SEND); goto process_state; } - sx->start_resolving = TRUE; sxstate(sx, cf, data, SOCKS4_ST_RESOLVING); FALLTHROUGH(); @@ -835,37 +818,24 @@ static CURLproxycode socks5_resolving(struct socks_ctx *sx, CURLcode result; CURLproxycode presult = CURLPX_OK; size_t nwritten; - bool dns_done; *done = FALSE; - if(sx->start_resolving) { - /* need to resolve hostname to add destination address */ - sx->start_resolving = FALSE; - result = Curl_cf_dns_insert_after( - cf, data, Curl_resolv_dns_queries(data, sx->ip_version), - sx->dest, TRNSPRT_TCP, TRUE); - if(result) { - failf(data, "unable to create DNS filter for socks"); - return CURLPX_UNKNOWN_FAIL; - } - } - - /* resolve the hostname by connecting the DNS filter */ - result = Curl_conn_cf_connect(cf->next, data, &dns_done); + result = Curl_conn_dns_addr_result(cf->conn, cf->sockindex, sx->dest); if(result) { - failf(data, "Failed to resolve \"%s\" for SOCKS5 connect.", - sx->dest->hostname); - return CURLPX_RESOLVE_HOST; - } - else if(!dns_done) + if(result != CURLE_AGAIN) { + failf(data, "error %d resolving SOCKS destination %s:%u", + (int)result, sx->dest->hostname, sx->dest->port); + return CURLPX_RESOLVE_HOST; + } return CURLPX_OK; + } #ifdef USE_IPV6 if(data->set.ipver != CURL_IPRESOLVE_V4) - ai = Curl_cf_dns_get_ai(cf->next, data, sx->dest, AF_INET6, 0); + ai = Curl_conn_dns_get_ai(data, sx->dest, cf->sockindex, AF_INET6, 0); #endif if(!ai) - ai = Curl_cf_dns_get_ai(cf->next, data, sx->dest, AF_INET, 0); + ai = Curl_conn_dns_get_ai(data, sx->dest, cf->sockindex, AF_INET, 0); if(!ai) { failf(data, "Failed to resolve \"%s\" for SOCKS5 connect.", @@ -962,7 +932,7 @@ static CURLproxycode socks5_recv_resp1(struct socks_ctx *sx, o IPv4 address: 0x01, BND.ADDR = 4-byte o domain name: 0x03, BND.ADDR = [ 1-byte length, string ] o IPv6 address: 0x04, BND.ADDR = 16-byte - */ + */ if(resp[0] != 5) { /* version */ failf(data, "SOCKS5 reply has wrong version, version should be 5."); return CURLPX_BAD_VERSION; @@ -1037,7 +1007,6 @@ process_state: switch(sx->state) { case SOCKS_ST_INIT: sx->version = 5; - sx->resolve_local = (sx->proxy_type == CURLPROXY_SOCKS5); sxstate(sx, cf, data, SOCKS5_ST_START); FALLTHROUGH(); @@ -1126,7 +1095,6 @@ process_state: sxstate(sx, cf, data, SOCKS5_ST_REQ1_SEND); goto process_state; } - sx->start_resolving = TRUE; sxstate(sx, cf, data, SOCKS5_ST_RESOLVING); FALLTHROUGH(); @@ -1191,9 +1159,7 @@ static void socks_proxy_ctx_free(struct socks_ctx *ctx) the next magic steps. If 'done' is not set TRUE, it is not done yet and must be called again. - Note: this function's sub-functions call failf() - -*/ + Note: this function's sub-functions call failf() */ static CURLcode socks_proxy_cf_connect(struct Curl_cfilter *cf, struct Curl_easy *data, bool *done) @@ -1230,7 +1196,7 @@ static CURLcode socks_proxy_cf_connect(struct Curl_cfilter *cf, if(pxresult) { result = CURLE_PROXY; - data->info.pxcode = pxresult; + data->info.pxcode = (uint8_t)pxresult; goto out; } else if(ctx->state != SOCKS_ST_SUCCESS) @@ -1351,6 +1317,8 @@ CURLcode Curl_cf_socks_proxy_insert_after(struct Curl_cfilter *cf_at, { struct Curl_cfilter *cf; struct socks_ctx *ctx; + bool resolve_local = FALSE; + uint8_t dns_queries = Curl_resolv_dns_queries(data, ip_version); CURLcode result; if(!dest) @@ -1358,10 +1326,16 @@ CURLcode Curl_cf_socks_proxy_insert_after(struct Curl_cfilter *cf_at, switch(proxy_type) { case CURLPROXY_SOCKS5: + resolve_local = TRUE; + break; case CURLPROXY_SOCKS5_HOSTNAME: + break; case CURLPROXY_SOCKS4: + resolve_local = TRUE; + dns_queries = (uint8_t)(dns_queries & ~CURL_DNSQ_AAAA); + break; case CURLPROXY_SOCKS4A: - break; /* all supported */ + break; default: failf(data, "unknown proxytype %d option given", proxy_type); return CURLE_COULDNT_CONNECT; @@ -1376,13 +1350,21 @@ CURLcode Curl_cf_socks_proxy_insert_after(struct Curl_cfilter *cf_at, Curl_peer_link(&ctx->dest, dest); ctx->ip_version = ip_version; ctx->proxy_type = proxy_type; + ctx->resolve_local = resolve_local; Curl_creds_link(&ctx->creds, creds); Curl_bufq_init2(&ctx->iobuf, SOCKS_CHUNK_SIZE, SOCKS_CHUNKS, BUFQ_OPT_SOFT_LIMIT); result = Curl_cf_create(&cf, &Curl_cft_socks_proxy, ctx); - if(!result) + if(!result) { Curl_conn_cf_insert_after(cf_at, cf); + if(ctx->resolve_local) { + result = Curl_conn_dns_add_addr_resolve(data, cf_at->conn, + cf_at->sockindex, + ctx->dest, dns_queries, + TRNSPRT_TCP); + } + } else socks_proxy_ctx_free(ctx); return result; diff --git a/Utilities/cmcurl/lib/socks_gssapi.c b/Utilities/cmcurl/lib/socks_gssapi.c index e10899c7e7..c1cfba5b71 100644 --- a/Utilities/cmcurl/lib/socks_gssapi.c +++ b/Utilities/cmcurl/lib/socks_gssapi.c @@ -35,7 +35,7 @@ #include "socks.h" #include "curlx/strdup.h" -#if defined(CURL_HAVE_DIAG) && defined(__APPLE__) +#if defined(CURL_HAVE_DIAG) && defined(__APPLE__) && !defined(HAVE_GSSAPPLE) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" #endif @@ -176,7 +176,8 @@ static CURLcode socks5_gss_auth_loop(struct Curl_cfilter *cf, gss_token, &gss_send_token, TRUE, - gss_ret_flags); + gss_ret_flags, + GSS_C_NO_CREDENTIAL); if(gss_token != GSS_C_NO_BUFFER) { curlx_safefree(gss_recv_token.value); @@ -593,7 +594,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, return result; } -#if defined(CURL_HAVE_DIAG) && defined(__APPLE__) +#if defined(CURL_HAVE_DIAG) && defined(__APPLE__) && !defined(HAVE_GSSAPPLE) #pragma GCC diagnostic pop #endif diff --git a/Utilities/cmcurl/lib/socks_sspi.c b/Utilities/cmcurl/lib/socks_sspi.c index 10a8be1773..576d6aef5b 100644 --- a/Utilities/cmcurl/lib/socks_sspi.c +++ b/Utilities/cmcurl/lib/socks_sspi.c @@ -37,7 +37,7 @@ #include "curlx/multibyte.h" /* - * Helper sspi error functions. + * Helper SSPI error functions. */ static int check_sspi_err(struct Curl_easy *data, SECURITY_STATUS status, @@ -77,8 +77,7 @@ static CURLcode socks5_sspi_setup(struct Curl_cfilter *cf, return CURLE_OUT_OF_MEMORY; status = - Curl_pSecFn->AcquireCredentialsHandle(NULL, - (TCHAR *)CURL_UNCONST(TEXT("Kerberos")), + Curl_pSecFn->AcquireCredentialsHandle(NULL, CURL_UNCONST(TEXT("Kerberos")), SECPKG_CRED_OUTBOUND, NULL, NULL, NULL, NULL, cred_handle, NULL); diff --git a/Utilities/cmcurl/lib/splay.c b/Utilities/cmcurl/lib/splay.c index ddab7a4d6e..788da215ee 100644 --- a/Utilities/cmcurl/lib/splay.c +++ b/Utilities/cmcurl/lib/splay.c @@ -23,173 +23,267 @@ ***************************************************************************/ #include "curl_setup.h" +#include "urldata.h" #include "splay.h" -/* - * This macro compares two node keys i and j and returns: - * - * negative value: when i is smaller than j - * zero : when i is equal to j - * positive when : when i is larger than j - */ -#define splay_compare(i, j) curlx_ptimediff_us(i, j) + +void Curl_timeouts_init(struct Curl_timeouts *timeouts, + const struct curltime *ptime_base) +{ + timeouts->tree = NULL; + timeouts->time_base = ptime_base ? *ptime_base : curlx_now(); +} + +bool Curl_timeouts_has(struct Curl_easy *data) +{ + struct Curl_tree *node = data ? &data->state.timeouts.splaynode : NULL; + return node && node->registered; +} + +timediff_t Curl_timeouts_offset_us(struct Curl_timeouts *timeouts, + const struct curltime *pts) +{ + return curlx_ptimediff_us(pts, &timeouts->time_base); +} + +int Curl_timeouts_next_ms(struct Curl_timeouts *timeouts, + const struct curltime *pnow, + timediff_t *pexpire_offset_us, + uint32_t *pmid) +{ + if(timeouts->tree) { /* splay the lowest key to the root */ + timeouts->tree = Curl_splay(TIMEDIFF_T_MIN, timeouts->tree); + } + + if(timeouts->tree) { + timediff_t elapsed_us = Curl_timeouts_offset_us(timeouts, pnow); + timediff_t delta_us = timeouts->tree->key - elapsed_us; + if(pmid) + *pmid = timeouts->tree->id; + if(pexpire_offset_us) + *pexpire_offset_us = timeouts->tree->key; + if(delta_us > 0) { /* expires in the future */ + timediff_t ms = curlx_us_to_ceil_ms(delta_us); + return (ms > INT_MAX) ? INT_MAX : (int)ms; + } + else /* has expired */ + return 0; + } + if(pmid) + *pmid = UINT32_MAX; + if(pexpire_offset_us) + *pexpire_offset_us = 0; + return -1; +} + +bool Curl_timeouts_remove_expired(struct Curl_timeouts *timeouts, + const struct curltime *ts, + uint32_t *pmid) +{ + if(timeouts->tree) { + struct Curl_tree *t = NULL; + timediff_t elapsed_us = Curl_timeouts_offset_us(timeouts, ts); + timeouts->tree = Curl_splaygetbest(elapsed_us, timeouts->tree, &t); + if(t) { + *pmid = t->id; + return TRUE; + } + } + *pmid = UINT32_MAX; + return FALSE; +} + +void Curl_timeouts_add(struct Curl_timeouts *timeouts, + struct Curl_easy *data, + timediff_t offset_us) +{ + struct Curl_tree *node = &data->state.timeouts.splaynode; + DEBUGASSERT(!node->registered); + timeouts->tree = Curl_splayinsert(offset_us, timeouts->tree, + node, data->mid); +} + +bool Curl_timeouts_remove(struct Curl_timeouts *timeouts, + struct Curl_easy *data) +{ + struct Curl_tree *node = &data->state.timeouts.splaynode; + if(node->registered) { + int rc = Curl_splayremove(timeouts->tree, node, &timeouts->tree); +#ifdef DEBUGBUILD + if(rc) + curl_mfprintf(stderr, "Internal error removing splay node = %d\n", rc); +#else + (void)rc; +#endif + return TRUE; + } + return FALSE; +} /* - * Splay using the key i (which may or may not be in the tree.) The starting - * root is t. + * Splay using the key i (which may or may not be in the tree). + * This rotates the tree, so: + * - root->smaller has all nodes smaller than `key` + * - root->larger has all nodes larger than `key` + * - root->key may equal `key` or not + * */ -struct Curl_tree *Curl_splay(const struct curltime *pkey, - struct Curl_tree *t) +struct Curl_tree *Curl_splay(timediff_t key, + struct Curl_tree *root) { struct Curl_tree N, *l, *r, *y; - if(!t) + if(!root) return NULL; N.smaller = N.larger = NULL; l = r = &N; for(;;) { - timediff_t comp = splay_compare(pkey, &t->key); - if(comp < 0) { - if(!t->smaller) + if(key < root->key) { + /* key is somewhere in root->smaller branch */ + if(!root->smaller) /* which is empty, done */ break; - if(splay_compare(pkey, &t->smaller->key) < 0) { - y = t->smaller; /* rotate smaller */ - t->smaller = y->larger; - y->larger = t; - t = y; - if(!t->smaller) + if(key < root->smaller->key) { + /* key is somewhere in root->smaller->smaller, make a "Zig step" */ + y = root->smaller; + root->smaller = y->larger; + y->larger = root; + root = y; + if(!root->smaller) break; } - r->smaller = t; /* link smaller */ - r = t; - t = t->smaller; + /* Making root->smaller the new root, the old root is no longer + * referenced. Remember it in the N tree's `r`ight/larger side. + * Everything in old root is smaller than what the right side + * of N already has, so it gets added to r->smaller. */ + r->smaller = root; + r = root; + root = root->smaller; } - else if(comp > 0) { - if(!t->larger) + else if(key > root->key) { + /* key is somewhere in root->larger branch */ + if(!root->larger) /* which is empty, done */ break; - if(splay_compare(pkey, &t->larger->key) > 0) { - y = t->larger; /* rotate larger */ - t->larger = y->smaller; - y->smaller = t; - t = y; - if(!t->larger) + if(key > root->larger->key) { + /* key is somewhere in root->larger->larger, make a "Zig step" */ + y = root->larger; + root->larger = y->smaller; + y->smaller = root; + root = y; + if(!root->larger) break; } - l->larger = t; /* link larger */ - l = t; - t = t->larger; + /* Making root->larger the new root, the old root is no longer + * referenced. Remember it in the N tree's `l`eft/smaller side. + * Everything in old root is larger than what the left side + * of N already has, so it gets added to l->larger. */ + l->larger = root; + l = root; + root = root->larger; } - else + else /* exact match, root is key, done */ break; } - l->larger = t->smaller; /* assemble */ - r->smaller = t->larger; - t->smaller = N.larger; - t->larger = N.smaller; + /* Put it all together again. + * root->smaller has everything larger than current `l`. + * root->larger has everything smaller than current `r`. */ + l->larger = root->smaller; + r->smaller = root->larger; + root->smaller = N.larger; + root->larger = N.smaller; - return t; + return root; } -static const struct curltime SPLAY_SUBNODE = { - ~0, -1 -}; - /* Insert key i into the tree t. Return a pointer to the resulting tree or * NULL if something went wrong. * * @unittest: 1309 */ -struct Curl_tree *Curl_splayinsert(const struct curltime *pkey, - struct Curl_tree *t, - struct Curl_tree *node) +struct Curl_tree *Curl_splayinsert(timediff_t key, + struct Curl_tree *root, + struct Curl_tree *node, + uint32_t id) { DEBUGASSERT(node); - if(t) { - t = Curl_splay(pkey, t); - DEBUGASSERT(t); - if(splay_compare(pkey, &t->key) == 0) { - /* There already exists a node in the tree with the same key. Build a - doubly-linked circular list of nodes. We add the new 'node' struct to - the end of this list. */ - - node->key = SPLAY_SUBNODE; /* identify this node as a subnode */ - node->samen = t; - node->samep = t->samep; - t->samep->samen = node; - t->samep = node; - - return t; /* the root node always stays the same */ + node->key = key; + node->id = id; + node->same = NULL; + node->registered = TRUE; + if(root) { + root = Curl_splay(key, root); + DEBUGASSERT(root); + if(key == root->key) { + /* There already exists a node in the tree with the same key. + Append the new node to the `same` list. */ + struct Curl_tree **panchor = &root->same; + while(*panchor) + panchor = &(*panchor)->same; + *panchor = node; + return root; /* the root node always stays the same */ } } - if(!t) { + /* node becomes the new root. Insert old root as sub-branch. */ + if(!root) { node->smaller = node->larger = NULL; } - else if(splay_compare(pkey, &t->key) < 0) { - node->smaller = t->smaller; - node->larger = t; - t->smaller = NULL; + else if(key < root->key) { + node->smaller = root->smaller; + node->larger = root; + root->smaller = NULL; } else { - node->larger = t->larger; - node->smaller = t; - t->larger = NULL; + node->larger = root->larger; + node->smaller = root; + root->larger = NULL; } - node->key = *pkey; - /* no identical nodes (yet), we are the only one in the list of nodes */ - node->samen = node; - node->samep = node; return node; } /* Finds and deletes the best-fit node from the tree. Return a pointer to the resulting tree. best-fit means the smallest node if it is not larger than the key */ -struct Curl_tree *Curl_splaygetbest(const struct curltime *pkey, - struct Curl_tree *t, +struct Curl_tree *Curl_splaygetbest(timediff_t key, + struct Curl_tree *root, struct Curl_tree **removed) { - static const struct curltime tv_zero = { 0, 0 }; struct Curl_tree *x; - if(!t) { + if(!root) { *removed = NULL; /* none removed since there was no root */ return NULL; } /* find smallest */ - t = Curl_splay(&tv_zero, t); - DEBUGASSERT(t); - if(splay_compare(pkey, &t->key) < 0) { + root = Curl_splay(TIMEDIFF_T_MIN, root); + DEBUGASSERT(root); + if(key < root->key) { /* even the smallest is too big */ *removed = NULL; - return t; + return root; } /* FIRST! Check if there is a list with identical keys */ - x = t->samen; - if(x != t) { - /* there is, pick one from the list */ - - /* 'x' is the new root node */ - - x->key = t->key; - x->larger = t->larger; - x->smaller = t->smaller; - x->samep = t->samep; - t->samep->samen = x; - - *removed = t; + if(root->same) { + x = root->same; + DEBUGASSERT(x->key == root->key); + /* 'x' becomes the new root node */ + x->larger = root->larger; + x->smaller = root->smaller; + root->same = NULL; + root->registered = FALSE; + *removed = root; return x; /* new root */ } /* we splayed the tree to the smallest element, there is no smaller */ - x = t->larger; - *removed = t; + x = root->larger; + root->registered = FALSE; + *removed = root; return x; } @@ -205,87 +299,79 @@ struct Curl_tree *Curl_splaygetbest(const struct curltime *pkey, * * @unittest: 1309 */ -int Curl_splayremove(struct Curl_tree *t, +int Curl_splayremove(struct Curl_tree *root, struct Curl_tree *removenode, struct Curl_tree **newroot) { struct Curl_tree *x; - if(!t) + if(!root) return 1; DEBUGASSERT(removenode); - - if(splay_compare(&SPLAY_SUBNODE, &removenode->key) == 0) { - /* It is a subnode within a 'same' linked list and thus we can unlink it - easily. */ - DEBUGASSERT(removenode->samen != removenode); - if(removenode->samen == removenode) - /* A non-subnode should never be set to SPLAY_SUBNODE */ - return 3; - - removenode->samep->samen = removenode->samen; - removenode->samen->samep = removenode->samep; - - /* Ensures that double-remove gets caught. */ - removenode->samen = removenode; - - *newroot = t; /* return the same root */ - return 0; - } - - t = Curl_splay(&removenode->key, t); - DEBUGASSERT(t); - - /* First make sure that we got the same root node as the one we want - to remove, as otherwise we might be trying to remove a node that - is not actually in the tree. - - We cannot compare the keys here as a double remove in quick - succession of a node with key != SPLAY_SUBNODE && same != NULL - could return the same key but a different node. */ - DEBUGASSERT(t == removenode); - if(t != removenode) + if(!removenode->registered) return 2; - /* Check if there is a list with identical sizes, as then we are trying to - remove the root node of a list of nodes with identical keys. */ - x = t->samen; - if(x != t) { + root = Curl_splay(removenode->key, root); + DEBUGASSERT(root); + + /* First make sure that we got the same root key as the one we want + to remove, as otherwise we might be trying to remove a node that + is not actually in the tree. */ + if(root->key != removenode->key) { + DEBUGASSERT(0); + return 2; + } + + if(root != removenode) { + /* Should be in the root->same list then */ + struct Curl_tree **panchor; + for(panchor = &root->same; *panchor; panchor = &(*panchor)->same) { + if(*panchor == removenode) { + *panchor = removenode->same; + removenode->same = NULL; + removenode->registered = FALSE; + *newroot = root; + return 0; + } + } + /* not found in same list, error */ + DEBUGASSERT(0); + return 2; + } + /* removing the root node */ + if(root->same) { /* 'x' is the new root node, we make it use the root node's smaller/larger links */ - - x->key = t->key; - x->larger = t->larger; - x->smaller = t->smaller; - x->samep = t->samep; - t->samep->samen = x; + x = root->same; + x->larger = root->larger; + x->smaller = root->smaller; + root->same = NULL; } else { /* Remove the root node */ - if(!t->smaller) - x = t->larger; + if(!root->smaller) + x = root->larger; else { - x = Curl_splay(&removenode->key, t->smaller); + x = Curl_splay(removenode->key, root->smaller); DEBUGASSERT(x); - x->larger = t->larger; + x->larger = root->larger; } } - - *newroot = x; /* store new root pointer */ - + removenode->registered = FALSE; + *newroot = x; /* return new root */ return 0; } /* set and get the custom payload for this tree node */ -void Curl_splayset(struct Curl_tree *node, void *payload) +void Curl_splayset(struct Curl_tree *node, uint32_t id) { DEBUGASSERT(node); - node->ptr = payload; + node->id = id; } -void *Curl_splayget(struct Curl_tree *node) +uint32_t Curl_splayget(struct Curl_tree *node) { DEBUGASSERT(node); - return node->ptr; + return node->id; } diff --git a/Utilities/cmcurl/lib/splay.h b/Utilities/cmcurl/lib/splay.h index c6623f2ef0..3f9e66297c 100644 --- a/Utilities/cmcurl/lib/splay.h +++ b/Utilities/cmcurl/lib/splay.h @@ -27,33 +27,66 @@ #include "curlx/timeval.h" +struct Curl_easy; + /* only use function calls to access this struct */ struct Curl_tree { struct Curl_tree *smaller; /* smaller node */ struct Curl_tree *larger; /* larger node */ - struct Curl_tree *samen; /* points to the next node with identical key */ - struct Curl_tree *samep; /* points to the prev node with identical key */ - struct curltime key; /* this node's "sort" key */ - void *ptr; /* data the splay code does not care about */ + struct Curl_tree *same; /* points to the next node with identical key */ + timediff_t key; /* this node's "sort" key */ + uint32_t id; /* provided id for this node */ + BIT(registered); /* node is registered in splay tree */ }; -struct Curl_tree *Curl_splay(const struct curltime *pkey, - struct Curl_tree *t); +struct Curl_timeouts { + struct Curl_tree *tree; + struct curltime time_base; +}; -struct Curl_tree *Curl_splayinsert(const struct curltime *pkey, - struct Curl_tree *t, - struct Curl_tree *node); +void Curl_timeouts_init(struct Curl_timeouts *timeouts, + const struct curltime *ptime_base); -struct Curl_tree *Curl_splaygetbest(const struct curltime *pkey, - struct Curl_tree *t, +bool Curl_timeouts_has(struct Curl_easy *data); + +timediff_t Curl_timeouts_offset_us(struct Curl_timeouts *timeouts, + const struct curltime *pts); + +int Curl_timeouts_next_ms(struct Curl_timeouts *timeouts, + const struct curltime *pnow, + timediff_t *pexpire_offset_us, + uint32_t *pmid); + +bool Curl_timeouts_remove_expired(struct Curl_timeouts *timeouts, + const struct curltime *ts, + uint32_t *pmid); + +void Curl_timeouts_add(struct Curl_timeouts *timeouts, + struct Curl_easy *data, + timediff_t offset_us); + +/* Returns TRUE if data was registered in timeouts before */ +bool Curl_timeouts_remove(struct Curl_timeouts *timeouts, + struct Curl_easy *data); + +struct Curl_tree *Curl_splay(timediff_t key, + struct Curl_tree *root); + +struct Curl_tree *Curl_splayinsert(timediff_t key, + struct Curl_tree *root, + struct Curl_tree *node, + uint32_t id); + +struct Curl_tree *Curl_splaygetbest(timediff_t key, + struct Curl_tree *root, struct Curl_tree **removed); -int Curl_splayremove(struct Curl_tree *t, +int Curl_splayremove(struct Curl_tree *root, struct Curl_tree *removenode, struct Curl_tree **newroot); /* set and get the custom payload for this tree node */ -void Curl_splayset(struct Curl_tree *node, void *payload); -void *Curl_splayget(struct Curl_tree *node); +void Curl_splayset(struct Curl_tree *node, uint32_t id); +uint32_t Curl_splayget(struct Curl_tree *node); #endif /* HEADER_CURL_SPLAY_H */ diff --git a/Utilities/cmcurl/lib/strcase.c b/Utilities/cmcurl/lib/strcase.c index 9f70f41bd6..6c3597cc37 100644 --- a/Utilities/cmcurl/lib/strcase.c +++ b/Utilities/cmcurl/lib/strcase.c @@ -26,7 +26,7 @@ #include "strcase.h" /* Mapping table to go from lowercase to uppercase for plain ASCII.*/ -static const unsigned char touppermap[256] = { +const unsigned char Curl_touppermap[256] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, @@ -48,7 +48,7 @@ static const unsigned char touppermap[256] = { }; /* Mapping table to go from uppercase to lowercase for plain ASCII.*/ -static const unsigned char tolowermap[256] = { +const unsigned char Curl_tolowermap[256] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, @@ -69,20 +69,6 @@ static const unsigned char tolowermap[256] = { 255 }; -/* Portable, consistent toupper. Do not use toupper() because its behavior is - altered by the current locale. */ -char Curl_raw_toupper(char in) -{ - return (char)touppermap[(unsigned char)in]; -} - -/* Portable, consistent tolower. Do not use tolower() because its behavior is - altered by the current locale. */ -char Curl_raw_tolower(char in) -{ - return (char)tolowermap[(unsigned char)in]; -} - /* Copy an upper case version of the string from src to dest. The * strings may overlap. No more than n characters of the string are copied * (including any NUL) and the destination string will NOT be diff --git a/Utilities/cmcurl/lib/strcase.h b/Utilities/cmcurl/lib/strcase.h index 54299812be..559db875bd 100644 --- a/Utilities/cmcurl/lib/strcase.h +++ b/Utilities/cmcurl/lib/strcase.h @@ -25,8 +25,24 @@ ***************************************************************************/ #include "curl_setup.h" -char Curl_raw_toupper(char in); -char Curl_raw_tolower(char in); +/* Mapping tables for plain ASCII case conversion, defined in strcase.c. + Declared here so the conversions below inline at every call site without + relying on LTO or a unity build: casecompare() invokes one of them twice + per byte compared, where the call costs more than the lookup itself. */ +extern const unsigned char Curl_touppermap[256]; +extern const unsigned char Curl_tolowermap[256]; + +/* Portable, consistent toupper/tolower. Do not use toupper()/tolower() from + , whose behavior is altered by the current locale. */ +static CURL_INLINE char Curl_raw_toupper(char in) +{ + return (char)Curl_touppermap[(unsigned char)in]; +} + +static CURL_INLINE char Curl_raw_tolower(char in) +{ + return (char)Curl_tolowermap[(unsigned char)in]; +} /* checkprefix() is a shorter version of the above, used when the first argument is the string literal */ diff --git a/Utilities/cmcurl/lib/strerror.c b/Utilities/cmcurl/lib/strerror.c index 7b6dc02127..6fc801dc61 100644 --- a/Utilities/cmcurl/lib/strerror.c +++ b/Utilities/cmcurl/lib/strerror.c @@ -452,34 +452,34 @@ const char *curl_url_strerror(CURLUcode error) return "An unknown part ID was passed to a URL API function"; case CURLUE_NO_SCHEME: - return "No scheme part in the URL"; + return "No scheme present"; case CURLUE_NO_USER: - return "No user part in the URL"; + return "No user present"; case CURLUE_NO_PASSWORD: - return "No password part in the URL"; + return "No password present"; case CURLUE_NO_OPTIONS: - return "No options part in the URL"; + return "No options present"; case CURLUE_NO_HOST: - return "No host part in the URL"; + return "No host present"; case CURLUE_NO_PORT: - return "No port part in the URL"; + return "No port number present"; case CURLUE_NO_QUERY: - return "No query part in the URL"; + return "No query present"; case CURLUE_NO_FRAGMENT: - return "No fragment part in the URL"; + return "No fragment present"; case CURLUE_NO_ZONEID: - return "No zoneid part in the URL"; + return "No zoneid present"; case CURLUE_BAD_LOGIN: - return "Bad login part"; + return "Bad login"; case CURLUE_BAD_IPV6: return "Bad IPv6 address"; @@ -517,6 +517,9 @@ const char *curl_url_strerror(CURLUcode error) case CURLUE_TOO_LARGE: return "A value or data field is larger than allowed"; + case CURLUE_BACKSLASH: + return "Found a backslash where a forward slash was expected"; + case CURLUE_LAST: break; } diff --git a/Utilities/cmcurl/lib/system_win32.c b/Utilities/cmcurl/lib/system_win32.c index 1b052357b5..4cbc2c25e9 100644 --- a/Utilities/cmcurl/lib/system_win32.c +++ b/Utilities/cmcurl/lib/system_win32.c @@ -38,33 +38,9 @@ CURLcode Curl_win32_init(long flags) should take place after this block. */ if(flags & CURL_GLOBAL_WIN32) { #ifdef USE_WINSOCK - WORD wVersionRequested; - WSADATA wsaData; - int res; - - wVersionRequested = MAKEWORD(2, 2); - res = WSAStartup(wVersionRequested, &wsaData); - - if(res) - /* Tell the user that we could not find a usable */ - /* winsock.dll. */ + WSADATA wsa; + if(WSAStartup(MAKEWORD(2, 2), &wsa)) return CURLE_FAILED_INIT; - - /* Confirm that the Windows Sockets DLL supports what we need.*/ - /* Note that if the DLL supports versions greater */ - /* than wVersionRequested, it will still return */ - /* wVersionRequested in wVersion. wHighVersion contains the */ - /* highest supported version. */ - - if(LOBYTE(wsaData.wVersion) != LOBYTE(wVersionRequested) || - HIBYTE(wsaData.wVersion) != HIBYTE(wVersionRequested)) { - /* Tell the user that we could not find a usable */ - - /* winsock.dll. */ - WSACleanup(); - return CURLE_FAILED_INIT; - } - /* The Windows Sockets DLL is acceptable. Proceed. */ #elif defined(USE_LWIPSOCK) lwip_init(); #endif diff --git a/Utilities/cmcurl/lib/system_win32.h b/Utilities/cmcurl/lib/system_win32.h index 8a51f09670..d504b563db 100644 --- a/Utilities/cmcurl/lib/system_win32.h +++ b/Utilities/cmcurl/lib/system_win32.h @@ -26,8 +26,6 @@ #include "curl_setup.h" #ifdef _WIN32 -extern LARGE_INTEGER Curl_freq; - CURLcode Curl_win32_init(long flags); void Curl_win32_cleanup(long init_flags); #else diff --git a/Utilities/cmcurl/lib/telnet.c b/Utilities/cmcurl/lib/telnet.c index 2870d7e2c9..735fa66156 100644 --- a/Utilities/cmcurl/lib/telnet.c +++ b/Utilities/cmcurl/lib/telnet.c @@ -59,7 +59,7 @@ #define SUBBUFSIZE 512 -#define CURL_SB_CLEAR(x) x->subpointer = (x)->subbuffer +#define CURL_SB_CLEAR(x) (x)->subpointer = (x)->subbuffer #define CURL_SB_TERM(x) \ do { \ (x)->subend = (x)->subpointer; \ @@ -1267,7 +1267,7 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) } /* Tell Winsock what events we want to listen to */ - if(WSAEventSelect(sockfd, event_handle, FD_READ | FD_CLOSE) != 0) { + if(WSAEventSelect(sockfd, event_handle, FD_READ | FD_CLOSE)) { WSACloseEvent(event_handle); return CURLE_RECV_ERROR; } diff --git a/Utilities/cmcurl/lib/tftp.c b/Utilities/cmcurl/lib/tftp.c index 00e89752bd..d95da69dc3 100644 --- a/Utilities/cmcurl/lib/tftp.c +++ b/Utilities/cmcurl/lib/tftp.c @@ -278,7 +278,7 @@ static CURLcode tftp_parse_option_ack(struct tftp_conn *state, infof(data, "got option=(%s) value=(%s)", option, value); - if((strlen(TFTP_OPTION_BLKSIZE) == olen) && + if((CURL_CSTRLEN(TFTP_OPTION_BLKSIZE) == olen) && checkprefix(TFTP_OPTION_BLKSIZE, option)) { curl_off_t blksize; if(curlx_str_number(&value, &blksize, TFTP_BLKSIZE_MAX)) { @@ -308,7 +308,7 @@ static CURLcode tftp_parse_option_ack(struct tftp_conn *state, infof(data, "blksize parsed from OACK (%u) requested (%u)", state->blksize, state->requested_blksize); } - else if((strlen(TFTP_OPTION_TSIZE) == olen) && + else if((CURL_CSTRLEN(TFTP_OPTION_TSIZE) == olen) && checkprefix(TFTP_OPTION_TSIZE, option)) { curl_off_t tsize = 0; /* tsize should be ignored on upload: Who cares about the size of the @@ -942,7 +942,7 @@ static CURLcode tftp_connect(struct Curl_easy *data, bool *done) /* we do not keep TFTP connections up because there is none or little gain * for UDP */ - connclose(conn, "TFTP"); + connclose(conn); state->data = data; state->sockfd = conn->sock[FIRSTSOCKET]; @@ -957,7 +957,7 @@ static CURLcode tftp_connect(struct Curl_easy *data, bool *done) return CURLE_FAILED_INIT; ((struct sockaddr *)&state->local_addr)->sa_family = - (CURL_SA_FAMILY_T)(remote_addr->family); + (CURL_SA_FAMILY_T)remote_addr->family; result = tftp_set_timeouts(state); if(result) @@ -988,8 +988,6 @@ static CURLcode tftp_connect(struct Curl_easy *data, bool *done) conn->bits.bound = TRUE; } - Curl_pgrsStartNow(data); - *done = TRUE; return CURLE_OK; diff --git a/Utilities/cmcurl/lib/thrdpool.c b/Utilities/cmcurl/lib/thrdpool.c index e8c7d36435..900fd1cfc0 100644 --- a/Utilities/cmcurl/lib/thrdpool.c +++ b/Utilities/cmcurl/lib/thrdpool.c @@ -28,6 +28,7 @@ #include "llist.h" #include "curl_threads.h" #include "curlx/timeval.h" +#include "curlx/strparse.h" #include "thrdpool.h" #ifdef CURLVERBOSE #include "curl_trc.h" @@ -49,7 +50,7 @@ struct thrdslot { }; struct curl_thrdpool { - char *name; + const char *name; uint64_t refcount; curl_mutex_t lock; curl_cond_t await; @@ -64,6 +65,9 @@ struct curl_thrdpool { uint32_t max_threads; uint32_t idle_time_ms; uint32_t next_id; +#ifdef DEBUGBUILD + int dbg_fail_starts; /* fail this many thread starts */ +#endif BIT(aborted); BIT(detached); }; @@ -166,7 +170,14 @@ static CURLcode thrdslot_start(struct curl_thrdpool *tpool) tpool->refcount++; tslot->running = TRUE; - tslot->thread = Curl_thread_create(thrdslot_run, tslot); +#ifdef DEBUGBUILD + if(tpool->dbg_fail_starts > 0) { + --tpool->dbg_fail_starts; + tslot->thread = curl_thread_t_null; + } + else +#endif + tslot->thread = Curl_thread_create(thrdslot_run, tslot); if(tslot->thread == curl_thread_t_null) { /* never started */ tslot->running = FALSE; thrdpool_unlink(tpool, TRUE); @@ -225,7 +236,6 @@ static bool thrdpool_unlink(struct curl_thrdpool *tpool, bool locked) thrdpool_join_zombies(tpool); if(locked) Curl_mutex_release(&tpool->lock); - curlx_free(tpool->name); Curl_cond_destroy(&tpool->await); Curl_mutex_destroy(&tpool->lock); curlx_free(tpool); @@ -301,6 +311,7 @@ CURLcode Curl_thrdpool_create(struct curl_thrdpool **ptpool, { struct curl_thrdpool *tpool; CURLcode result = CURLE_OUT_OF_MEMORY; + DEBUGASSERT(name); tpool = curlx_calloc(1, sizeof(*tpool)); if(!tpool) @@ -316,9 +327,19 @@ CURLcode Curl_thrdpool_create(struct curl_thrdpool **ptpool, tpool->fn_return = fn_return; tpool->fn_user_data = user_data; - tpool->name = curlx_strdup(name); - if(!tpool->name) - goto out; + /* a const string that remains */ + tpool->name = name; + +#ifdef DEBUGBUILD + { + const char *p = getenv("CURL_DBG_THRDPOOL_FAIL_STARTS"); + if(p) { + curl_off_t l; + if(!curlx_str_number(&p, &l, INT_MAX)) + tpool->dbg_fail_starts = (int)l; + } + } +#endif result = Curl_thrdpool_set_props(tpool, min_threads, max_threads, idle_time_ms); diff --git a/Utilities/cmcurl/lib/thrdqueue.c b/Utilities/cmcurl/lib/thrdqueue.c index f68f8e1797..b009405905 100644 --- a/Utilities/cmcurl/lib/thrdqueue.c +++ b/Utilities/cmcurl/lib/thrdqueue.c @@ -37,7 +37,7 @@ struct curl_thrdq { - char *name; + const char *name; curl_mutex_t lock; curl_cond_t await; struct Curl_llist sendq; @@ -47,7 +47,6 @@ struct curl_thrdq { Curl_thrdq_item_process_cb *fn_process; Curl_thrdq_ev_cb *fn_event; void *fn_user_data; - uint32_t send_max_len; BIT(aborted); }; @@ -188,7 +187,6 @@ static void thrdq_unlink(struct curl_thrdq *tqueue, bool locked, bool join) Curl_llist_destroy(&tqueue->sendq, NULL); Curl_llist_destroy(&tqueue->recvq, NULL); - curlx_free(tqueue->name); Curl_cond_destroy(&tqueue->await); if(locked) Curl_mutex_release(&tqueue->lock); @@ -198,7 +196,6 @@ static void thrdq_unlink(struct curl_thrdq *tqueue, bool locked, bool join) CURLcode Curl_thrdq_create(struct curl_thrdq **ptqueue, const char *name, - uint32_t max_len, uint32_t min_threads, uint32_t max_threads, uint32_t idle_time_ms, @@ -209,6 +206,7 @@ CURLcode Curl_thrdq_create(struct curl_thrdq **ptqueue, { struct curl_thrdq *tqueue; CURLcode result = CURLE_OUT_OF_MEMORY; + DEBUGASSERT(name); tqueue = curlx_calloc(1, sizeof(*tqueue)); if(!tqueue) @@ -222,11 +220,9 @@ CURLcode Curl_thrdq_create(struct curl_thrdq **ptqueue, tqueue->fn_process = fn_process; tqueue->fn_event = fn_event; tqueue->fn_user_data = user_data; - tqueue->send_max_len = max_len; - tqueue->name = curlx_strdup(name); - if(!tqueue->name) - goto out; + /* a const string that remains */ + tqueue->name = name; result = Curl_thrdpool_create(&tqueue->tpool, name, min_threads, max_threads, idle_time_ms, @@ -253,11 +249,18 @@ void Curl_thrdq_destroy(struct curl_thrdq *tqueue, bool join) thrdq_unlink(tqueue, TRUE, join); } +static uint32_t thrdq_get_signals(struct curl_thrdq *tqueue) +{ + size_t qlen = Curl_llist_count(&tqueue->sendq); + return (qlen <= UINT32_MAX) ? (uint32_t)qlen : UINT32_MAX; +} + CURLcode Curl_thrdq_send(struct curl_thrdq *tqueue, void *item, const char *description, timediff_t timeout_ms) { - CURLcode result = CURLE_AGAIN; - size_t signals = 0; + struct thrdq_item *qitem; + CURLcode result = CURLE_OK; + uint32_t signals = 0; Curl_mutex_acquire(&tqueue->lock); if(tqueue->aborted) { @@ -270,19 +273,13 @@ CURLcode Curl_thrdq_send(struct curl_thrdq *tqueue, void *item, goto out; } - if(!tqueue->send_max_len || - (Curl_llist_count(&tqueue->sendq) < tqueue->send_max_len)) { - struct thrdq_item *qitem = thrdq_item_create(tqueue, item, description, - timeout_ms); - if(!qitem) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - item = NULL; - Curl_llist_append(&tqueue->sendq, qitem, &qitem->node); - signals = Curl_llist_count(&tqueue->sendq); - result = CURLE_OK; + qitem = thrdq_item_create(tqueue, item, description, timeout_ms); + if(!qitem) { + result = CURLE_OUT_OF_MEMORY; + goto out; } + Curl_llist_append(&tqueue->sendq, qitem, &qitem->node); + signals = thrdq_get_signals(tqueue); out: Curl_mutex_release(&tqueue->lock); @@ -295,10 +292,22 @@ out: return result; } +bool Curl_thrdq_check_started(struct curl_thrdq *tqueue) +{ + size_t unprocessed; + + Curl_mutex_acquire(&tqueue->lock); + unprocessed = tqueue->aborted ? 0 : Curl_llist_count(&tqueue->sendq); + Curl_mutex_release(&tqueue->lock); + return !unprocessed || + !Curl_thrdpool_signal(tqueue->tpool, (uint32_t)unprocessed); +} + CURLcode Curl_thrdq_recv(struct curl_thrdq *tqueue, void **pitem) { CURLcode result = CURLE_AGAIN; struct Curl_llist_node *e; + uint32_t signals = 0; *pitem = NULL; Curl_mutex_acquire(&tqueue->lock); @@ -316,8 +325,18 @@ CURLcode Curl_thrdq_recv(struct curl_thrdq *tqueue, void **pitem) thrdq_item_destroy(qitem); result = CURLE_OK; } + else + signals = thrdq_get_signals(tqueue); + out: Curl_mutex_release(&tqueue->lock); + /* Signal thread pool unlocked to avoid deadlocks. If items await + * processing while nothing was ready, make sure the pool has a + * thread to work on them. An earlier thread start may have failed, + * which `Curl_thrdq_send()` cannot report to its caller. Without + * this, such items would sit unprocessed until the next send. */ + if(signals) + (void)Curl_thrdpool_signal(tqueue->tpool, (uint32_t)signals); return result; } @@ -363,17 +382,15 @@ UNITTEST CURLcode thrdq_await_done(struct curl_thrdq *tqueue, #endif CURLcode Curl_thrdq_set_props(struct curl_thrdq *tqueue, - uint32_t max_len, uint32_t min_threads, uint32_t max_threads, uint32_t idle_time_ms) { CURLcode result; - size_t signals; + uint32_t signals; Curl_mutex_acquire(&tqueue->lock); - tqueue->send_max_len = max_len; - signals = Curl_llist_count(&tqueue->sendq); + signals = thrdq_get_signals(tqueue); Curl_mutex_release(&tqueue->lock); result = Curl_thrdpool_set_props(tqueue->tpool, min_threads, diff --git a/Utilities/cmcurl/lib/thrdqueue.h b/Utilities/cmcurl/lib/thrdqueue.h index 1144bb4185..0d84f6f6f1 100644 --- a/Utilities/cmcurl/lib/thrdqueue.h +++ b/Utilities/cmcurl/lib/thrdqueue.h @@ -54,7 +54,6 @@ typedef void Curl_thrdq_item_free_cb(void *item); */ CURLcode Curl_thrdq_create(struct curl_thrdq **ptqueue, const char *name, - uint32_t max_len, /* 0 for unlimited */ uint32_t min_threads, uint32_t max_threads, uint32_t idle_time_ms, @@ -74,7 +73,6 @@ void Curl_thrdq_destroy(struct curl_thrdq *tqueue, bool join); * to "item" on success, e.g. the queue takes ownership. * `description` is an optional string describing the item for tracing * purposes. It needs to have the same lifetime as `item`. - * Returns CURLE_AGAIN when the queue has already been full. * * With`timeout_ms` != 0, items that get stuck that long in the send * queue are removed and added to the receive queue right away. @@ -86,10 +84,21 @@ CURLcode Curl_thrdq_send(struct curl_thrdq *tqueue, void *item, * The caller takes ownership of the item received, e.g. the queue * relinquishes all references to item. * Returns CURLE_AGAIN when there is no processed item, setting `pitem` - * to NULL. + * to NULL. When nothing has been processed while items await sending, + * the pool is signalled to make sure a worker thread exists: an + * earlier thread start may have failed. */ CURLcode Curl_thrdq_recv(struct curl_thrdq *tqueue, void **pitem); +/* Check that items awaiting processing have a worker thread to run + * them, signalling the pool to start one when needed -- an earlier + * thread start may have failed. Returns TRUE when everything is ok: + * no items are waiting or the pool took the signal. FALSE when a + * thread start just failed again; callers may want to check again + * soon rather than wait indefinitely. + */ +bool Curl_thrdq_check_started(struct curl_thrdq *tqueue); + /* Return TRUE if the passed "item" matches. */ typedef bool Curl_thrdq_item_match_cb(void *item, void *match_data); @@ -104,7 +113,6 @@ CURLcode Curl_thrdq_await_done(struct curl_thrdq *tqueue, uint32_t timeout_ms); CURLcode Curl_thrdq_set_props(struct curl_thrdq *tqueue, - uint32_t max_len, /* 0 for unlimited */ uint32_t min_threads, uint32_t max_threads, uint32_t idle_time_ms); diff --git a/Utilities/cmcurl/lib/transfer.c b/Utilities/cmcurl/lib/transfer.c index 1a4138f6c1..3b6c5c03dd 100644 --- a/Utilities/cmcurl/lib/transfer.c +++ b/Utilities/cmcurl/lib/transfer.c @@ -51,15 +51,13 @@ #endif #ifndef HAVE_SOCKET -#error "We cannot compile without socket() support!" +#error "We cannot compile without socket() support" #endif #include "urldata.h" -#include "hostip.h" #include "cfilters.h" #include "cw-out.h" -#include "dnscache.h" #include "transfer.h" #include "sendf.h" #include "curl_trc.h" @@ -73,6 +71,7 @@ #include "setopt.h" #include "headers.h" #include "bufref.h" +#include "rtsp.h" #if !defined(CURL_DISABLE_HTTP) || !defined(CURL_DISABLE_SMTP) || \ !defined(CURL_DISABLE_IMAP) @@ -155,7 +154,7 @@ static bool xfer_recv_shutdown_started(struct Curl_easy *data) { if(!data || !data->conn) return FALSE; - return Curl_shutdown_started(data, data->conn->recv_idx); + return Curl_shutdown_started(data->conn, data->conn->recv_idx); } CURLcode Curl_xfer_send_shutdown(struct Curl_easy *data, bool *done) @@ -250,8 +249,7 @@ static CURLcode sendrecv_dl(struct Curl_easy *data, bytestoread = xfer_blen; if(bytestoread && Curl_rlimit_active(&data->progress.dl.rlimit)) { - curl_off_t dl_avail = Curl_rlimit_avail(&data->progress.dl.rlimit, - Curl_pgrs_now(data)); + curl_off_t dl_avail = Curl_rlimit_avail(&data->progress.dl.rlimit, NULL); #if 0 DEBUGF(infof(data, "dl_rlimit, available=%" FMT_OFF_T, dl_avail)); #endif @@ -356,6 +354,7 @@ static CURLcode sendrecv_ul(struct Curl_easy *data) CURLcode Curl_sendrecv(struct Curl_easy *data) { struct SingleRequest *k = &data->req; + const struct curltime *pnow = NULL; CURLcode result = CURLE_OK; if(Curl_xfer_is_blocked(data)) { @@ -378,25 +377,20 @@ CURLcode Curl_sendrecv(struct Curl_easy *data) goto out; } - result = Curl_pgrsCheck(data); - if(result) - goto out; - + pnow = Curl_pgrs_now(data); if(CURL_REQ_WANT_IO(data)) { - if(Curl_timeleft_ms(data) < 0) { + if(Curl_timeleft_now_ms(data, pnow) < 0) { if(k->size != -1) { failf(data, "Operation timed out after %" FMT_TIMEDIFF_T " milliseconds with %" FMT_OFF_T " out of %" FMT_OFF_T " bytes received", - curlx_ptimediff_ms(Curl_pgrs_now(data), - &data->progress.t_startsingle), + Curl_pgrs_since_ms(data, NULL, TIMER_STARTSINGLE), k->bytecount, k->size); } else { failf(data, "Operation timed out after %" FMT_TIMEDIFF_T " milliseconds with %" FMT_OFF_T " bytes received", - curlx_ptimediff_ms(Curl_pgrs_now(data), - &data->progress.t_startsingle), + Curl_pgrs_since_ms(data, NULL, TIMER_STARTSINGLE), k->bytecount); } result = CURLE_OPERATION_TIMEDOUT; @@ -408,7 +402,7 @@ CURLcode Curl_sendrecv(struct Curl_easy *data) * The transfer has been performed. Make some general checks before * returning. */ - if(!(data->req.no_body) && (k->size != -1) && + if(!data->req.no_body && (k->size != -1) && (k->bytecount != k->size) && !k->newurl) { failf(data, "transfer closed with %" FMT_OFF_T " bytes remaining to read", k->size - k->bytecount); @@ -421,7 +415,7 @@ CURLcode Curl_sendrecv(struct Curl_easy *data) if(!CURL_REQ_WANT_IO(data)) data->req.done = TRUE; - result = Curl_pgrsUpdate(data); + result = Curl_pgrsCheckX(data, pnow); out: if(result) @@ -455,7 +449,7 @@ CURLcode Curl_pretransfer(struct Curl_easy *data) * By resetting it here, we ensure each new request starts fresh. */ data->state.retrycount = 0; - if(!data->set.str[STRING_SET_URL] && !data->set.uh) { + if(!CURL_EASY_STR(data, STRING_SET_URL) && !data->set.uh) { /* we cannot do anything without URL */ failf(data, "No URL set"); return CURLE_URL_MALFORMAT; @@ -464,19 +458,22 @@ CURLcode Curl_pretransfer(struct Curl_easy *data) /* CURLOPT_CURLU overrides CURLOPT_URL and the contents of the CURLU handle is allowed to be changed by the user between transfers */ if(data->set.uh) { + char *url = NULL; CURLUcode uc; - curlx_free(data->set.str[STRING_SET_URL]); - uc = curl_url_get(data->set.uh, - CURLUPART_URL, &data->set.str[STRING_SET_URL], 0); + uc = curl_url_get(data->set.uh, CURLUPART_URL, &url, 0); if(uc) { /* clear the pointer to not point to freed memory anymore */ Curl_bufref_set(&data->state.url, NULL, 0, NULL); failf(data, "No URL set"); return CURLE_URL_MALFORMAT; } + result = CURL_EASY_STR_SETN(data, STRING_SET_URL, url); + if(result) + return result; } - Curl_bufref_set(&data->state.url, data->set.str[STRING_SET_URL], 0, NULL); + Curl_bufref_set(&data->state.url, CURL_EASY_STR(data, STRING_SET_URL), + 0, NULL); if(data->set.postfields && data->set.set_resume_from) { /* we cannot */ @@ -509,9 +506,9 @@ CURLcode Curl_pretransfer(struct Curl_easy *data) Curl_data_priority_clear_state(data); if(data->set.http_auto_referer) Curl_bufref_free(&data->state.referer); - if(data->set.str[STRING_SET_REFERER]) - Curl_bufref_set(&data->state.referer, data->set.str[STRING_SET_REFERER], - 0, NULL); + if(CURL_EASY_STR(data, STRING_SET_REFERER)) + Curl_bufref_set(&data->state.referer, + CURL_EASY_STR(data, STRING_SET_REFERER), 0, NULL); else Curl_bufref_free(&data->state.referer); @@ -527,7 +524,9 @@ CURLcode Curl_pretransfer(struct Curl_easy *data) data->state.infilesize = 0; /* If there is a list of cookie files to read, do it now! */ - result = Curl_cookie_loadfiles(data); + result = Curl_cookie_loadfiles(data, + data->set.cookiesession ? + COOKIE_NOSESSION : 0); if(!result) Curl_cookie_run(data); /* activate */ @@ -555,7 +554,7 @@ CURLcode Curl_pretransfer(struct Curl_easy *data) Curl_initinfo(data); /* reset session-specific information "variables" */ Curl_pgrsResetTransferSizes(data); - Curl_pgrsStartNow(data); + Curl_pgrsStart(data, NULL); /* In case the handle is reused and an authentication method was picked in the session we need to make sure we only use the one(s) we now @@ -585,18 +584,6 @@ CURLcode Curl_pretransfer(struct Curl_easy *data) result = Curl_hsts_loadcb(data, data->hsts); } - /* - * Set user-agent. Used for HTTP, but since we can attempt to tunnel - * anything through an HTTP proxy we cannot limit this based on protocol. - */ - if(!result && data->set.str[STRING_USERAGENT]) { - curlx_free(data->state.aptr.uagent); - data->state.aptr.uagent = - curl_maprintf("User-Agent: %s\r\n", data->set.str[STRING_USERAGENT]); - if(!data->state.aptr.uagent) - return CURLE_OUT_OF_MEMORY; - } - data->req.headerbytecount = 0; Curl_headers_cleanup(data); return result; @@ -659,7 +646,7 @@ CURLcode Curl_retry_request(struct Curl_easy *data, char **url) if(!*url) return CURLE_OUT_OF_MEMORY; - connclose(conn, "retry"); /* close this connection */ + connclose(conn); /* close this connection */ conn->bits.retry = TRUE; /* mark this as a connection we are about to retry. Marking it this way should prevent i.e HTTP transfers to return error because nothing @@ -671,8 +658,8 @@ CURLcode Curl_retry_request(struct Curl_easy *data, char **url) static void xfer_setup( struct Curl_easy *data, /* transfer */ - int send_idx, /* sockindex to send on or -1 */ - int recv_idx, /* sockindex to receive on or -1 */ + int8_t send_idx, /* sockindex to send on or -1 */ + int8_t recv_idx, /* sockindex to receive on or -1 */ curl_off_t recv_size /* how much to receive, -1 if unknown */ ) { @@ -720,20 +707,20 @@ void Curl_xfer_setup_nop(struct Curl_easy *data) } void Curl_xfer_setup_sendrecv(struct Curl_easy *data, - int sockindex, + int8_t sockindex, curl_off_t recv_size) { xfer_setup(data, sockindex, sockindex, recv_size); } void Curl_xfer_setup_send(struct Curl_easy *data, - int sockindex) + int8_t sockindex) { xfer_setup(data, sockindex, -1, -1); } void Curl_xfer_setup_recv(struct Curl_easy *data, - int sockindex, + int8_t sockindex, curl_off_t recv_size) { xfer_setup(data, -1, sockindex, recv_size); diff --git a/Utilities/cmcurl/lib/transfer.h b/Utilities/cmcurl/lib/transfer.h index 7507ce27bd..0ad0d549ed 100644 --- a/Utilities/cmcurl/lib/transfer.h +++ b/Utilities/cmcurl/lib/transfer.h @@ -69,12 +69,12 @@ void Curl_xfer_setup_nop(struct Curl_easy *data); /* The transfer sends data on the given socket index */ void Curl_xfer_setup_send(struct Curl_easy *data, - int sockindex); + int8_t sockindex); /* The transfer receives data on the given socket index, the * amount to receive (or -1 if unknown). */ void Curl_xfer_setup_recv(struct Curl_easy *data, - int sockindex, + int8_t sockindex, curl_off_t recv_size); /* *After* Curl_xfer_setup_xxx(), tell the transfer to shutdown the @@ -89,7 +89,7 @@ void Curl_xfer_set_shutdown(struct Curl_easy *data, * the amount to receive or -1 if unknown. */ void Curl_xfer_setup_sendrecv(struct Curl_easy *data, - int sockindex, + int8_t sockindex, curl_off_t recv_size); /** @@ -147,4 +147,8 @@ CURLcode Curl_xfer_pause_recv(struct Curl_easy *data, bool enable); * use a forward proxy. */ bool Curl_xfer_is_secure(struct Curl_easy *data); +/* Internal variant of the API function */ +CURLcode Curl_easy_recv(struct Curl_easy *data, + void *buffer, size_t buflen, size_t *n); + #endif /* HEADER_CURL_TRANSFER_H */ diff --git a/Utilities/cmcurl/lib/uint-hashset.c b/Utilities/cmcurl/lib/uint-hashset.c new file mode 100644 index 0000000000..84e9e5ec7b --- /dev/null +++ b/Utilities/cmcurl/lib/uint-hashset.c @@ -0,0 +1,311 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * 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" + +#include "uint-hashset.h" +#include "curlx/strdup.h" + +/* random patterns for API verification */ +#ifdef DEBUGBUILD +#define CURL_U8_STRSET_MAGIC 0x7117e783 +#endif + +#define CURL_U8_STRSET_DEBUG 0 + +#define CURL_SWAP(a, b) (((a) ^= (b)), ((b) ^= (a)), ((a) ^= (b))) + +static const uint8_t u8_smask[] = { + 0x00U, + 0x01U, + 0x03U, + 0x07U, + 0x0FU, + 0x1FU, + 0x3FU, + 0x7FU, + 0xFFU, +}; + +#define CURL_U8_SET_SLOT_IDX(s, i) (uint8_t)((i) & u8_smask[(s)->slotbits]) +#define CURL_U8_SLOT_CNT(i) ((uint16_t)u8_smask[(i)] + 1) +#define CURL_U8_SET_SLOT_CNT(s) CURL_U8_SLOT_CNT((s)->slotbits) + +/* A hashset for tuples (id, string) using Robin Hood Hashing. + * + * The basic idea here to handle collisions by robbing "rich" entries and + * giving to the "poor": + * - We have an array: (id, string) are ideally placed at index "id % size". + * - If slot at index is already occupied, we have a collision. + * - A simple collision strategy would look at the next index, and the + * next until finding an empty slot. + * - The drawback is that this may lead to many checks on lookups, as it + * will need to also look at subsequent slots until it finds the match. + * The amount of lookups is the "probe sequence length" (psl) and this + * may vary greatly between entries. + * - Robin Hood Hashing balances the 'psl's of all entries more evenly: + * - psl == 0 means an entry is in exactly the right slot + * - psl == 1 means it is in the slot right after. psl == 2 is the slot + * after that, etc. + * - when inserting a new entry, track its psl. Finding a slot where + * the existing entry has a lower psl makes a swap. Put the new entry + * and its psl there, take the previous entry and its psl and find + * the next best slot for the previous entry. */ +void Curl_u8_strset_init(struct u8_strset *set) +{ +#if defined(__GNUC__) && __GNUC__ >= 13 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Warray-bounds" +#endif + memset(set, 0, sizeof(*set)); +#if defined(__GNUC__) && __GNUC__ >= 13 +#pragma GCC diagnostic pop +#endif + set->data = set->sdata; + set->ids = set->sids; + set->psl = set->spsl; + set->slotbits = CURL_U8_STRSET_START_BITS; + set->count = 0; +#ifdef DEBUGBUILD + set->init = CURL_U8_STRSET_MAGIC; +#endif +} + +void Curl_u8_strset_clear(struct u8_strset *set) +{ + uint16_t i; + DEBUGASSERT(set->init == CURL_U8_STRSET_MAGIC); + for(i = 0; i < CURL_U8_SET_SLOT_CNT(set); ++i) + curlx_safefree(set->data[i]); + + if(set->data != set->sdata) + curlx_safefree(set->data); + Curl_u8_strset_init(set); +} + +static void u8_strset_addn(struct u8_strset *set, uint8_t id, char *val) +{ + uint8_t i = CURL_U8_SET_SLOT_IDX(set, id); + uint8_t psl = 0; + while(set->data[i]) { + if(psl > set->psl[i]) { /* SWAP */ + char *tmpdata; + tmpdata = set->data[i]; + set->data[i] = val; + val = tmpdata; + CURL_SWAP(set->psl[i], psl); + CURL_SWAP(set->ids[i], id); + } + i = CURL_U8_SET_SLOT_IDX(set, i + 1); + ++psl; + } + set->ids[i] = id; + set->data[i] = val; + set->psl[i] = psl; + ++set->count; +} + +static bool u8_strset_grow(struct u8_strset *set) +{ + uint8_t i, *prev_ids, nslotbits; + uint16_t prev_slots; + char **prev_data; + size_t nslots; + void *d; + + if(set->slotbits >= 8) + return FALSE; + nslotbits = (uint8_t)(set->slotbits + 1); +#if CURL_U8_STRSET_DEBUG + curl_mfprintf(stderr, "u8_strset_grow from %d to %d\n", + set->slotbits, nslotbits); +#endif + nslots = CURL_U8_SLOT_CNT(nslotbits); + d = curlx_calloc(1, (nslots * sizeof(char *)) + (2 * nslots)); + if(!d) + return FALSE; + + prev_data = set->data; + prev_ids = set->ids; + prev_slots = set->slotbits; +#if defined(__GNUC__) && __GNUC__ >= 13 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wanalyzer-allocation-size" +#endif + set->data = (char **)d; +#if defined(__GNUC__) && __GNUC__ >= 13 +#pragma GCC diagnostic pop +#endif + set->ids = (uint8_t *)d + (nslots * sizeof(char *)); + set->psl = set->ids + nslots; + set->slotbits = nslotbits; + set->count = 0; + /* re-add previous entries */ + for(i = 0; i < CURL_U8_SLOT_CNT(prev_slots); ++i) { + if(prev_data[i]) + u8_strset_addn(set, prev_ids[i], prev_data[i]); + } + if(prev_data != set->sdata) + curlx_free(prev_data); + return TRUE; +} + +static bool u8_strset_get_index(struct u8_strset *set, + uint8_t id, uint8_t *pindex) +{ + uint8_t i = CURL_U8_SET_SLOT_IDX(set, id); + uint8_t psl = 0; + while(set->data[i] && (psl <= set->psl[i])) { + if(set->ids[i] == id) { + *pindex = i; +#if CURL_U8_STRSET_DEBUG + curl_mfprintf(stderr, "u8_strset_index %d=%s\n", id, set->data[i]); +#endif + return TRUE; + } + i = CURL_U8_SET_SLOT_IDX(set, i + 1); + ++psl; + } +#if CURL_U8_STRSET_DEBUG + curl_mfprintf(stderr, "u8_strset_index %d not found\n", id); +#endif + *pindex = 0; + return FALSE; +} + +uint16_t Curl_u8_strset_count(struct u8_strset *set) +{ + return set->count; +} + +const char *Curl_u8_strset_get(struct u8_strset *set, uint8_t id) +{ + uint8_t i; + DEBUGASSERT(set->init == CURL_U8_STRSET_MAGIC); + if(u8_strset_get_index(set, id, &i)) + return set->data[i]; + return NULL; +} + +CURLcode Curl_u8_strset_setn(struct u8_strset *set, + uint8_t id, char *str) +{ + uint8_t i; + + DEBUGASSERT(set->init == CURL_U8_STRSET_MAGIC); +#if CURL_U8_STRSET_DEBUG + curl_mfprintf(stderr, "u8_strset_setn %d=%s\n", id, str); +#endif + if(!str) { + Curl_u8_strset_unset(set, id); + return CURLE_OK; + } + + if(u8_strset_get_index(set, id, &i)) { + /* `id` is in set, replace value */ + curlx_free(set->data[i]); + set->data[i] = str; + return CURLE_OK; + } + /* `id` not in set yet, grow if full */ + if((set->count >= CURL_U8_SET_SLOT_CNT(set)) && !u8_strset_grow(set)) { + curlx_free(str); + return CURLE_OUT_OF_MEMORY; + } + + u8_strset_addn(set, id, str); + return CURLE_OK; +} + +CURLcode Curl_u8_strset_setx(struct u8_strset *set, + uint8_t id, const char *str, size_t slen) +{ + char *val; + + DEBUGASSERT(set->init == CURL_U8_STRSET_MAGIC); + if(!str) { + Curl_u8_strset_unset(set, id); + return CURLE_OK; + } + + val = curlx_memdup0(str, slen); + if(!val) + return CURLE_OUT_OF_MEMORY; + return Curl_u8_strset_setn(set, id, val); +} + +CURLcode Curl_u8_strset_set(struct u8_strset *set, + uint8_t id, const char *str) +{ + return Curl_u8_strset_setx(set, id, str, str ? strlen(str) : 0); +} + +static void u8_strset_unset(struct u8_strset *set, uint8_t id, bool zero) +{ + uint8_t i, j; + + DEBUGASSERT(set->init == CURL_U8_STRSET_MAGIC); + if(u8_strset_get_index(set, id, &i)) { + /* `id` is in set */ + if(zero) + curlx_strzero(set->data[i]); + curlx_safefree(set->data[i]); + set->ids[i] = set->psl[i] = 0; + --set->count; + j = CURL_U8_SET_SLOT_IDX(set, i + 1); + /* shift all entries with positive psl "down" */ + while(set->data[j] && set->psl[j]) { + set->data[i] = set->data[j]; + set->ids[i] = set->ids[j]; + set->psl[i] = (uint8_t)(set->psl[j] - 1); + set->data[j] = NULL; + set->ids[j] = set->psl[j] = 0; + i = j; + j = CURL_U8_SET_SLOT_IDX(set, i + 1); + } + } +} + +void Curl_u8_strset_unset(struct u8_strset *set, uint8_t id) +{ + u8_strset_unset(set, id, FALSE); +} + +void Curl_u8_strset_unset0(struct u8_strset *set, uint8_t id) +{ + u8_strset_unset(set, id, TRUE); +} + +CURLcode Curl_u8_strset_copy(struct u8_strset *dest, struct u8_strset *src) +{ + CURLcode result = CURLE_OK; + uint16_t i; + + DEBUGASSERT(src->init == CURL_U8_STRSET_MAGIC); + Curl_u8_strset_clear(dest); + for(i = 0; !result && (i < CURL_U8_SET_SLOT_CNT(src)); ++i) { + if(src->data[i]) + result = Curl_u8_strset_set(dest, src->ids[i], src->data[i]); + } + return result; +} diff --git a/Utilities/cmcurl/lib/uint-hashset.h b/Utilities/cmcurl/lib/uint-hashset.h new file mode 100644 index 0000000000..e5a1e02f00 --- /dev/null +++ b/Utilities/cmcurl/lib/uint-hashset.h @@ -0,0 +1,82 @@ +#ifndef HEADER_CURL_UINT_HASHSET_H +#define HEADER_CURL_UINT_HASHSET_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * 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" + +/* How large should the initial set be? + * Measuring our test suite with set growth force fail, gives + * BITS RESULT + * 1 1261 tests out of 1951 reported OK: 64% + * 2 1792 tests out of 1951 reported OK: 91% + * 3 1944 tests out of 1951 reported OK: 99% + * 4 1949 tests out of 1951 reported OK: 99% + * 5 single fail of 3211, unit test for u8_strset + * meaning 91% of our tests to not set more than 4 strings and + * 99% do not set more than 8. + */ +#define CURL_U8_STRSET_START_BITS 3 +#define CURL_U8_STRSET_START_DIM (1U << CURL_U8_STRSET_START_BITS) + +/* A set that can hold up to 256 strings identified by an `id'. + * Setting a string for an existing id replaces the previous one. + * Getting the string for an id not in the set returns NULL. + * Setting an id to NULL unsets the id. + */ +struct u8_strset { + char **data; /* #slots array of null-terminated strings */ + uint8_t *ids; /* #slots array of `id` values */ + uint8_t *psl; /* #slots array of "probe sequence length" values */ + char *sdata[CURL_U8_STRSET_START_DIM]; + uint8_t sids[CURL_U8_STRSET_START_DIM]; + uint8_t spsl[CURL_U8_STRSET_START_DIM]; + uint16_t count; + uint8_t slotbits; +#ifdef DEBUGBUILD + int32_t init; +#endif +}; + +void Curl_u8_strset_init(struct u8_strset *set); +void Curl_u8_strset_clear(struct u8_strset *set); + +uint16_t Curl_u8_strset_count(struct u8_strset *set); +const char *Curl_u8_strset_get(struct u8_strset *set, uint8_t id); + +/* Set string for id, makes a copy. */ +CURLcode Curl_u8_strset_set(struct u8_strset *set, + uint8_t id, const char *str); +CURLcode Curl_u8_strset_setx(struct u8_strset *set, + uint8_t id, const char *str, size_t slen); +/* Set string for id, takes ownership of `str` even on failure. */ +CURLcode Curl_u8_strset_setn(struct u8_strset *set, + uint8_t id, char *str); +void Curl_u8_strset_unset(struct u8_strset *set, uint8_t id); + +/* Remove the string if in the set and zero its memory */ +void Curl_u8_strset_unset0(struct u8_strset *set, uint8_t id); + +CURLcode Curl_u8_strset_copy(struct u8_strset *dest, struct u8_strset *src); + +#endif /* HEADER_CURL_UINT_HASHSET_H */ diff --git a/Utilities/cmcurl/lib/uint-spbset.c b/Utilities/cmcurl/lib/uint-spbset.c index d4e6c8e70b..66b0fcb670 100644 --- a/Utilities/cmcurl/lib/uint-spbset.c +++ b/Utilities/cmcurl/lib/uint-spbset.c @@ -74,90 +74,142 @@ uint32_t Curl_uint32_spbset_count(struct uint32_spbset *bset) return n; } +static bool uint32_spbset_empty_chunk(struct uint32_spbset_chunk *chunk) +{ + uint32_t i; + for(i = 0; i < CURL_UINT32_SPBSET_CH_SLOTS; ++i) { + if(chunk->slots[i]) + return FALSE; + } + return TRUE; +} + +static struct uint32_spbset_chunk *uint32_spbset_unlink_empty( + struct uint32_spbset *bset, uint32_t for_offset) +{ + struct uint32_spbset_chunk *chunk, **panchor = NULL; + for(chunk = &bset->head; chunk; + panchor = &chunk->next, chunk = chunk->next) { + if(uint32_spbset_empty_chunk(chunk)) + break; + } + if(chunk) { + if(chunk == &bset->head) { /* head chunk is empty */ + if(!bset->head.next || (for_offset < bset->head.next->offset)) { + return &bset->head; + } + /* swap head and next, unlink */ + chunk = bset->head.next; + memcpy(&bset->head, chunk, sizeof(bset->head)); + memset(chunk, 0, sizeof(*chunk)); + } + else { + *panchor = chunk->next; /* unlink */ + memset(chunk, 0, sizeof(*chunk)); + } + } + return chunk; +} + +static struct uint32_spbset_chunk *uint32_spbset_insert_chunk( + struct uint32_spbset *bset, struct uint32_spbset_chunk *nchunk) +{ + struct uint32_spbset_chunk *chunk, **panchor; + + /* insert nchunk into set's ordered chunk list */ + if(nchunk->offset < bset->head.offset) { + /* swap chunk and head */ + uint32_t offset = nchunk->offset; + memcpy(nchunk, &bset->head, sizeof(*nchunk)); + memset(&bset->head, 0, sizeof(bset->head)); + bset->head.next = nchunk; + bset->head.offset = offset; + return &bset->head; + } + DEBUGASSERT(nchunk->offset > bset->head.offset); + panchor = &bset->head.next; + for(chunk = *panchor; chunk; + panchor = &chunk->next, chunk = chunk->next) { + if(chunk->offset > nchunk->offset) { /* insert before this chunk */ + nchunk->next = chunk; + *panchor = nchunk; + return nchunk; + } + } + /* no chunk with larger offset, append */ + *panchor = nchunk; + return nchunk; +} + static struct uint32_spbset_chunk *uint32_spbset_get_chunk( struct uint32_spbset *bset, uint32_t i, bool grow) { - struct uint32_spbset_chunk *chunk, **panchor = NULL; + struct uint32_spbset_chunk *chunk; uint32_t i_offset = (i & ~CURL_UINT32_SPBSET_CH_MASK); if(!bset) return NULL; - for(chunk = &bset->head; chunk; - panchor = &chunk->next, chunk = chunk->next) { - if(chunk->offset == i_offset) { + for(chunk = &bset->head; chunk; chunk = chunk->next) { + if(chunk->offset == i_offset) return chunk; - } - else if(chunk->offset > i_offset) { - /* need new chunk here */ - chunk = NULL; - break; - } + else if(chunk->offset > i_offset) + break; /* need new chunk here */ } - - if(!grow) + if(!grow) /* just a check if the chunk exists */ return NULL; - /* need a new one */ - chunk = curlx_calloc(1, sizeof(*chunk)); - if(!chunk) - return NULL; + /* Is there an empty chunk to reuse? */ + chunk = uint32_spbset_unlink_empty(bset, i_offset); + if(chunk) { + chunk->offset = i_offset; + if(chunk == &bset->head) /* head chunk is empty, stayed linked */ + return &bset->head; + /* was really unlinked, need to insert below */ + } + else { + /* need a new one */ + chunk = curlx_calloc(1, sizeof(*chunk)); + if(!chunk) + return NULL; + chunk->offset = i_offset; + } - if(panchor) { /* insert between panchor and *panchor */ - chunk->next = *panchor; - *panchor = chunk; - } - else { /* prepend to head, switching places */ - memcpy(chunk, &bset->head, sizeof(*chunk)); - memset(&bset->head, 0, sizeof(bset->head)); - bset->head.next = chunk; - } - chunk->offset = i_offset; - return chunk; + return uint32_spbset_insert_chunk(bset, chunk); } bool Curl_uint32_spbset_add(struct uint32_spbset *bset, uint32_t i) { - struct uint32_spbset_chunk *chunk; - uint32_t i_chunk; - - chunk = uint32_spbset_get_chunk(bset, i, TRUE); + struct uint32_spbset_chunk *chunk = uint32_spbset_get_chunk(bset, i, TRUE); if(!chunk) return FALSE; DEBUGASSERT(i >= chunk->offset); - i_chunk = (i - chunk->offset); - DEBUGASSERT((i_chunk / 64) < CURL_UINT32_SPBSET_CH_SLOTS); - chunk->slots[(i_chunk / 64)] |= ((uint64_t)1 << (i_chunk % 64)); + i -= chunk->offset; + DEBUGASSERT(i < (CURL_UINT32_SPBSET_CH_SLOTS * 64)); + chunk->slots[(i / 64)] |= ((uint64_t)1 << (i % 64)); return TRUE; } void Curl_uint32_spbset_remove(struct uint32_spbset *bset, uint32_t i) { - struct uint32_spbset_chunk *chunk; - uint32_t i_chunk; - - chunk = uint32_spbset_get_chunk(bset, i, FALSE); + struct uint32_spbset_chunk *chunk = uint32_spbset_get_chunk(bset, i, FALSE); if(chunk) { DEBUGASSERT(i >= chunk->offset); - i_chunk = (i - chunk->offset); - DEBUGASSERT((i_chunk / 64) < CURL_UINT32_SPBSET_CH_SLOTS); - chunk->slots[(i_chunk / 64)] &= ~((uint64_t)1 << (i_chunk % 64)); + i -= chunk->offset; + DEBUGASSERT(i < (CURL_UINT32_SPBSET_CH_SLOTS * 64)); + chunk->slots[(i / 64)] &= ~((uint64_t)1 << (i % 64)); } } bool Curl_uint32_spbset_contains(struct uint32_spbset *bset, uint32_t i) { - struct uint32_spbset_chunk *chunk; - uint32_t i_chunk; - - chunk = uint32_spbset_get_chunk(bset, i, FALSE); + struct uint32_spbset_chunk *chunk = uint32_spbset_get_chunk(bset, i, FALSE); if(chunk) { DEBUGASSERT(i >= chunk->offset); - i_chunk = (i - chunk->offset); - DEBUGASSERT((i_chunk / 64) < CURL_UINT32_SPBSET_CH_SLOTS); - return (chunk->slots[i_chunk / 64] & - ((uint64_t)1 << (i_chunk % 64))) != 0; + i -= chunk->offset; + DEBUGASSERT(i < (CURL_UINT32_SPBSET_CH_SLOTS * 64)); + return (chunk->slots[i / 64] & ((uint64_t)1 << (i % 64))) != 0; } return FALSE; } diff --git a/Utilities/cmcurl/lib/url.c b/Utilities/cmcurl/lib/url.c index 505e08a7e1..6eeae43d0d 100644 --- a/Utilities/cmcurl/lib/url.c +++ b/Utilities/cmcurl/lib/url.c @@ -55,7 +55,7 @@ #endif #ifndef HAVE_SOCKET -#error "We cannot compile without socket() support!" +#error "We cannot compile without socket() support" #endif #if defined(HAVE_IF_NAMETOINDEX) && defined(USE_WINSOCK) @@ -70,7 +70,6 @@ #include "bufref.h" #include "vtls/vtls.h" #include "vssh/vssh.h" -#include "hostip.h" #include "transfer.h" #include "curl_addrinfo.h" #include "curl_trc.h" @@ -84,7 +83,6 @@ #include "getinfo.h" #include "pop3.h" #include "urlapi-int.h" -#include "system_win32.h" #include "hsts.h" #include "proxy.h" #include "cfilters.h" @@ -149,12 +147,17 @@ static curl_prot_t get_protocol_family(const struct Curl_scheme *s) void Curl_freeset(struct Curl_easy *data) { /* Free all dynamic strings stored in the data->set substructure. */ - enum dupstring i; enum dupblob j; - for(i = (enum dupstring)0; i < STRING_LAST; i++) { - curlx_safefree(data->set.str[i]); - } + CURL_EASY_STR_CLEAR0(data, STRING_PASSWORD); + CURL_EASY_STR_CLEAR0(data, STRING_KEY_PASSWD); + CURL_EASY_STR_CLEAR0(data, STRING_BEARER); +#ifndef CURL_DISABLE_PROXY + CURL_EASY_STR_CLEAR0(data, STRING_PROXYPASSWORD); + CURL_EASY_STR_CLEAR0(data, STRING_KEY_PASSWD_PROXY); +#endif + Curl_u8_strset_clear(&data->set.strings); + curlx_safefree(data->set.str_copypostfields); for(j = (enum dupblob)0; j < BLOB_LAST; j++) { curlx_safefree(data->set.blobs[j]); @@ -178,11 +181,6 @@ void Curl_freeset(struct Curl_easy *data) static void up_free(struct Curl_easy *data) { struct urlpieces *up = &data->state.up; - curlx_safefree(up->scheme); - curlx_safefree(up->hostname); - curlx_safefree(up->port); - curlx_safefree(up->user); - curlx_safefree(up->password); curlx_safefree(up->options); curlx_safefree(up->path); curlx_safefree(up->query); @@ -192,11 +190,10 @@ static void up_free(struct Curl_easy *data) /* * This is the internal function curl_easy_cleanup() calls. This should - * cleanup and free all resources associated with this sessionhandle. + * cleanup and free all resources associated with this Curl_easy. * * We ignore SIGPIPE when this is called from curl_easy_cleanup. */ - CURLcode Curl_close(struct Curl_easy **datap) { struct Curl_easy *data; @@ -211,7 +208,7 @@ CURLcode Curl_close(struct Curl_easy **datap) /* This handle is still part of a multi handle, take care of this first and detach this handle from there. This detaches the connection. */ - curl_multi_remove_handle(data->multi, data); + Curl_multi_remove_handle(data->multi, data); } else { /* Detach connection if any is left. This should not be normal, but can be @@ -226,7 +223,7 @@ CURLcode Curl_close(struct Curl_easy **datap) } DEBUGASSERT(!data->conn || data->state.internal); - Curl_expire_clear(data); /* shut off any timers left */ + Curl_expire_clear_all(data); /* shut off any timers left */ if(data->state.rangestringalloc) curlx_free(data->state.range); @@ -255,11 +252,11 @@ CURLcode Curl_close(struct Curl_easy **datap) curlx_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_save(data, data->asi, CURL_EASY_STR(data, STRING_ALTSVC)); Curl_altsvc_cleanup(&data->asi); #endif #ifndef CURL_DISABLE_HSTS - Curl_hsts_save(data, data->hsts, data->set.str[STRING_HSTS]); + Curl_hsts_save(data, data->hsts, CURL_EASY_STR(data, STRING_HSTS)); if(!data->share || !data->share->hsts) Curl_hsts_cleanup(&data->hsts); curl_slist_free_all(data->state.hstslist); /* clean up list */ @@ -277,17 +274,13 @@ CURLcode Curl_close(struct Curl_easy **datap) Curl_hash_destroy(&data->meta_hash); Curl_creds_unlink(&data->state.creds); - curlx_safefree(data->state.aptr.uagent); - curlx_safefree(data->state.aptr.accept_encoding); - curlx_safefree(data->state.aptr.rangeline); - curlx_safefree(data->state.aptr.ref); - curlx_safefree(data->state.aptr.host); +#ifndef CURL_DISABLE_HTTP + curlx_safefree(data->state.rangeline); + curlx_safefree(data->state.http_host); +#endif #ifndef CURL_DISABLE_COOKIES curlx_safefree(data->req.cookiehost); #endif -#ifndef CURL_DISABLE_RTSP - curlx_safefree(data->state.aptr.rtsp_transport); -#endif #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_FORM_API) Curl_mime_cleanpart(data->state.formp); @@ -306,6 +299,7 @@ CURLcode Curl_close(struct Curl_easy **datap) #ifndef CURL_DISABLE_PROXY Curl_ssl_config_cleanup(&data->set.proxy_ssl.primary); #endif + curlx_memzero(data, sizeof(*data)); curlx_free(data); return CURLE_OK; } @@ -322,6 +316,8 @@ void Curl_init_userdefined(struct Curl_easy *data) set->in_set = stdin; /* default input from stdin */ set->err = stderr; /* default stderr to stderr */ + Curl_u8_strset_init(&data->set.strings); + #if defined(__clang__) && __clang_major__ >= 16 #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wcast-function-type-strict" @@ -426,7 +422,7 @@ void Curl_init_userdefined(struct Curl_easy *data) set->http09_allowed = FALSE; set->httpwant = CURL_HTTP_VERSION_NONE; #if defined(USE_HTTP2) || defined(USE_HTTP3) - memset(&set->priority, 0, sizeof(set->priority)); + set->weight = 0; #endif set->quick_exit = 0L; #ifndef CURL_DISABLE_WEBSOCKETS @@ -449,11 +445,10 @@ static void easy_meta_freeentry(void *p) /** * Curl_open() * - * @param curl is a pointer to a sessionhandle pointer that gets set by this + * @param curl is a pointer to a Curl_easy pointer that gets set by this * function. * @return CURLcode */ - CURLcode Curl_open(struct Curl_easy **curl) { struct Curl_easy *data; @@ -469,16 +464,16 @@ CURLcode Curl_open(struct Curl_easy **curl) data->magic = CURLEASY_MAGIC_NUMBER; /* most recent connection is not yet defined */ data->state.lastconnect_id = -1; - data->state.recent_conn_id = -1; /* and not assigned an id yet */ data->id = -1; data->mid = UINT32_MAX; data->master_mid = UINT32_MAX; data->progress.hide = TRUE; - data->state.current_speed = -1; /* init to negative == impossible */ Curl_hash_init(&data->meta_hash, 23, Curl_hash_str, curlx_str_key_compare, easy_meta_freeentry); + DEBUGASSERT(STRING_LAST <= UINT8_MAX); + Curl_u8_strset_init(&data->set.strings); curlx_dyn_init(&data->state.headerb, CURL_MAX_HTTP_HEADER); Curl_bufref_init(&data->state.url); Curl_bufref_init(&data->state.referer); @@ -496,7 +491,7 @@ CURLcode Curl_open(struct Curl_easy **curl) void Curl_conn_free(struct Curl_easy *data, struct connectdata *conn) { - size_t i; + int8_t i; DEBUGASSERT(conn); @@ -504,8 +499,8 @@ void Curl_conn_free(struct Curl_easy *data, struct connectdata *conn) !conn->bits.shutdown_handler) conn->scheme->run->disconnect(data, conn, TRUE); - for(i = 0; i < CURL_ARRAYSIZE(conn->cfilter); ++i) { - Curl_conn_cf_discard_all(data, conn, (int)i); + for(i = 0; i < (int8_t)CURL_ARRAYSIZE(conn->cfilter); ++i) { + Curl_conn_cf_discard_all(data, conn, i); } #ifndef CURL_DISABLE_PROXY @@ -569,110 +564,6 @@ static bool proxy_info_matches(const struct proxy_info *data, } #endif -/* A connection has to have been idle for less than 'conn_max_idle_ms' - (the success rate is too low after this), or created less than - 'conn_max_age_ms' ago, to be subject for reuse. */ -static bool conn_maxage(struct Curl_easy *data, - struct connectdata *conn, - struct curltime now) -{ - timediff_t age_ms; - - if(data->set.conn_max_idle_ms) { - age_ms = curlx_ptimediff_ms(&now, &conn->lastused); - if(age_ms > data->set.conn_max_idle_ms) { - infof(data, "Too old connection (%" FMT_TIMEDIFF_T - " ms idle, max idle is %" FMT_TIMEDIFF_T " ms), disconnect it", - age_ms, data->set.conn_max_idle_ms); - return TRUE; - } - } - - if(data->set.conn_max_age_ms) { - age_ms = curlx_ptimediff_ms(&now, &conn->created); - if(age_ms > data->set.conn_max_age_ms) { - infof(data, - "Too old connection (created %" FMT_TIMEDIFF_T - " ms ago, max lifetime is %" FMT_TIMEDIFF_T " ms), disconnect it", - age_ms, data->set.conn_max_age_ms); - return TRUE; - } - } - - return FALSE; -} - -/* - * Return TRUE iff the given connection is considered dead. - */ -bool Curl_conn_seems_dead(struct connectdata *conn, - struct Curl_easy *data) -{ - DEBUGASSERT(!data->conn); - if(!CONN_INUSE(conn)) { - /* The check for a dead socket makes sense only if the connection is not in - use */ - bool dead; - - if(conn_maxage(data, conn, *Curl_pgrs_now(data))) { - /* avoid check if already too old */ - dead = TRUE; - } - else if(conn->scheme->run->connection_is_dead) { - /* The protocol has a special method for checking the state of the - connection. Use it to check if the connection is dead. */ - /* briefly attach the connection for the check */ - Curl_attach_connection(data, conn); - dead = conn->scheme->run->connection_is_dead(data, conn); - Curl_detach_connection(data); - } - else { - bool input_pending = FALSE; - - Curl_attach_connection(data, conn); - dead = !Curl_conn_is_alive(data, conn, &input_pending); - if(input_pending) { - /* For reuse, we want a "clean" connection state. This includes - * that we expect - in general - no waiting input data. Input - * waiting might be a TLS Notify Close, for example. We reject - * that. - * For protocols where data from other end may arrive at - * any time (HTTP/2 PING for example), the protocol handler needs - * to install its own `connection_check` callback. - */ - DEBUGF(infof(data, "connection has input pending, not reusable")); - dead = TRUE; - } - Curl_detach_connection(data); - } - - if(dead) { - /* remove connection from cpool */ - infof(data, "Connection %" FMT_OFF_T " seems to be dead", - conn->connection_id); - return TRUE; - } - } - return FALSE; -} - -CURLcode Curl_conn_upkeep(struct Curl_easy *data, - struct connectdata *conn) -{ - CURLcode result = CURLE_OK; - if(curlx_ptimediff_ms(Curl_pgrs_now(data), &conn->keepalive) <= - data->set.upkeep_interval_ms) - return result; - - /* briefly attach for action */ - Curl_attach_connection(data, conn); - result = Curl_conn_keep_alive(data, conn, FIRSTSOCKET); - Curl_detach_connection(data); - - conn->keepalive = *Curl_pgrs_now(data); - return result; -} - #ifdef USE_SSH static bool ssh_config_matches(struct connectdata *one, struct connectdata *two) @@ -681,8 +572,8 @@ static bool ssh_config_matches(struct connectdata *one, sshc1 = Curl_conn_meta_get(one, CURL_META_SSH_CONN); sshc2 = Curl_conn_meta_get(two, CURL_META_SSH_CONN); - return sshc1 && sshc2 && Curl_safecmp(sshc1->rsa, sshc2->rsa) && - Curl_safecmp(sshc1->rsa_pub, sshc2->rsa_pub); + return sshc1 && sshc2 && Curl_safecmp(sshc1->priv_key, sshc2->priv_key) && + Curl_safecmp(sshc1->pub_key, sshc2->pub_key); } #endif @@ -690,6 +581,7 @@ struct url_conn_match { struct connectdata *found; struct Curl_easy *data; struct connectdata *needle; + struct curltime now; BIT(may_multiplex); BIT(want_ntlm_http); BIT(want_proxy_ntlm_http); @@ -729,8 +621,7 @@ static bool url_match_connect_config(struct connectdata *conn, it would take a lot of processing to make it really accurate. Instead, this matching will assume that reuses of bound connections will most likely also reuse the exact same binding parameters and missing out a - few edge cases should not hurt anyone much. - */ + few edge cases should not hurt anyone much. */ if((conn->localport != m->needle->localport) || (conn->localportrange != m->needle->localportrange) || (m->needle->localdev && @@ -831,6 +722,10 @@ static bool url_match_ssl_use(struct connectdata *conn, if(!(m->needle->scheme->flags & PROTOPT_SSL_REUSE) || (get_protocol_family(conn->scheme) != m->needle->scheme->protocol)) return FALSE; + /* We may reuse this as an auto-TLS upgrade, but only if the SSL + * config parameters match. */ + if(!Curl_ssl_conn_config_match(m->data, conn, FALSE)) + return FALSE; } else if(m->require_tls) /* a clear-text STARTTLS protocol with required TLS */ @@ -941,7 +836,7 @@ static bool url_match_proto_config(struct connectdata *conn, #endif #ifndef CURL_DISABLE_FTP else if(get_protocol_family(m->needle->scheme) & PROTO_FAMILY_FTP) { - if(!ftp_conns_match(m->needle, conn)) + if(!Curl_ftp_conns_match(m->needle, conn)) return FALSE; } #endif @@ -1006,39 +901,34 @@ static bool url_match_ssl_config(struct connectdata *conn, static bool url_match_auth_ntlm(struct connectdata *conn, struct url_conn_match *m) { - /* If we are looking for an HTTP+NTLM connection, check if this is - already authenticating with the right credentials. If not, keep - looking so that we can reuse NTLM connections if - possible. (Especially we must not reuse the same connection if - partway through a handshake!) */ - if(m->want_ntlm_http) { + if(conn->http_ntlm_state != NTLMSTATE_NONE) { + /* Connection is using NTLM. We cannot reuse if transfer + * has different Auth input parameters. */ + if(!m->want_ntlm_http || + !Curl_creds_same(conn->creds, m->data->state.creds) || + !Curl_peer_equal(conn->creds_origin, m->data->state.origin)) + return FALSE; + } + else if(m->want_ntlm_http) { + /* Transfer wants NTLM, connection is not using it. + * Do not reuse when connection has credentials and they differ. */ if(conn->creds && (!Curl_creds_same(conn->creds, m->data->state.creds) || - !Curl_peer_equal(conn->creds_origin, m->data->state.origin))) { - /* connection credentials in play and not the same or not for the - * same origin. */ + !Curl_peer_equal(conn->creds_origin, m->data->state.origin))) return FALSE; - } - } - else if(conn->http_ntlm_state != NTLMSTATE_NONE) { - /* Connection is using NTLM auth but we do not want NTLM */ - return FALSE; } #ifndef CURL_DISABLE_PROXY /* Same for Proxy NTLM authentication */ - if(m->want_proxy_ntlm_http) { - /* Both conn->http_proxy.user and conn->http_proxy.passwd can be - * NULL */ - if(!conn->http_proxy.creds) - return FALSE; - - if(!Curl_creds_same(m->needle->http_proxy.creds, conn->http_proxy.creds)) + if(conn->proxy_ntlm_state != NTLMSTATE_NONE) { + if(!m->want_proxy_ntlm_http || + !Curl_creds_same(m->needle->http_proxy.creds, conn->http_proxy.creds)) return FALSE; } - else if(conn->proxy_ntlm_state != NTLMSTATE_NONE) { - /* Proxy connection is using NTLM auth but we do not want NTLM */ - return FALSE; + else if(m->want_proxy_ntlm_http) { + if(conn->http_proxy.creds && + !Curl_creds_same(m->needle->http_proxy.creds, conn->http_proxy.creds)) + return FALSE; } #endif if(m->want_ntlm_http || m->want_proxy_ntlm_http) { @@ -1069,34 +959,34 @@ static bool url_match_auth_ntlm(struct connectdata *conn, static bool url_match_auth_nego(struct connectdata *conn, struct url_conn_match *m) { - /* If we are looking for an HTTP+Negotiate connection, check if this is - already authenticating with the right credentials. If not, keep looking - so that we can reuse Negotiate connections if possible. */ - if(m->want_nego_http) { + if(conn->http_negotiate_state != GSS_AUTHNONE) { + /* Connection is using Negotiate. We cannot reuse if transfer + * has different Auth input parameters. */ + if(!m->want_nego_http || + !Curl_creds_same(conn->creds, m->data->state.creds) || + !Curl_peer_equal(conn->creds_origin, m->data->state.origin)) + return FALSE; + } + else if(m->want_nego_http) { + /* Transfer wants Negotiate, connection is not using it. + * Do not reuse when connection has credentials and they differ. */ if(conn->creds && (!Curl_creds_same(conn->creds, m->data->state.creds) || !Curl_peer_equal(conn->creds_origin, m->data->state.origin))) return FALSE; } - else if(conn->http_negotiate_state != GSS_AUTHNONE) { - /* Connection is using Negotiate auth but we do not want Negotiate */ - return FALSE; - } #ifndef CURL_DISABLE_PROXY /* Same for Proxy Negotiate authentication */ - if(m->want_proxy_nego_http) { - /* Both conn->http_proxy.user and conn->http_proxy.passwd can be - * NULL */ - if(!conn->http_proxy.creds) - return FALSE; - - if(!Curl_creds_same(m->needle->http_proxy.creds, conn->http_proxy.creds)) + if(conn->proxy_negotiate_state != GSS_AUTHNONE) { + if(!m->want_proxy_nego_http || + !Curl_creds_same(m->needle->http_proxy.creds, conn->http_proxy.creds)) return FALSE; } - else if(conn->proxy_negotiate_state != GSS_AUTHNONE) { - /* Proxy connection is using Negotiate auth but we do not want Negotiate */ - return FALSE; + else if(m->want_proxy_nego_http) { + if(conn->http_proxy.creds && + !Curl_creds_same(m->needle->http_proxy.creds, conn->http_proxy.creds)) + return FALSE; } #endif if(m->want_nego_http || m->want_proxy_nego_http) { @@ -1150,7 +1040,7 @@ static bool url_match_conn(struct connectdata *conn, void *userdata) if(!url_match_http_multiplex(conn, m)) return FALSE; else if(m->wait_pipe) - /* we decided to wait on PIPELINING */ + /* wait on multiplexing */ return TRUE; if(!url_match_auth(conn, m)) @@ -1172,9 +1062,23 @@ static bool url_match_conn(struct connectdata *conn, void *userdata) if(!url_match_multiplex_limits(conn, m)) return FALSE; - if(!CONN_INUSE(conn) && Curl_conn_seems_dead(conn, m->data)) { - /* remove and disconnect. */ - Curl_conn_terminate(m->data, conn, FALSE); + if(m->data->set.conn_max_age_ms > 0) { + timediff_t age_ms = curlx_ptimediff_ms(&m->now, &conn->created); + if(age_ms > m->data->set.conn_max_age_ms) { + /* Transfer is looking for a younger connection. */ + if(!CONN_INUSE(conn)) + Curl_conn_close(m->data, conn, FALSE); + return FALSE; + } + } + + /* If we are going to pick an idle connection, do an extra + * health check before we reuse it. */ + if(!CONN_INUSE(conn) && + !Curl_cpool_conn_seems_healthy(conn, m->data, &m->now)) { + infof(m->data, "Connection %" FMT_OFF_T " seems to be dead, terminating", + conn->connection_id); + Curl_conn_close(m->data, conn, FALSE); return FALSE; } @@ -1189,7 +1093,7 @@ static bool url_match_result(void *userdata) if(match->found) { /* Attach it now while still under lock, so the connection does * no longer appear idle and can be reaped. */ - Curl_attach_connection(match->data, match->found); + Curl_attach_connection(match->data, match->found, TRUE); return TRUE; } else if(match->seen_single_use_conn && !match->seen_multiplex_conn) { @@ -1220,15 +1124,20 @@ static bool url_attach_existing(struct Curl_easy *data, struct connectdata *needle, bool *waitpipe) { + struct cpool *cpool = Curl_cpool_get_instance(data); struct url_conn_match match; bool success; DEBUGASSERT(!data->conn); + memset(&match, 0, sizeof(match)); match.data = data; match.needle = needle; + match.now = *Curl_pgrs_now(data); match.may_multiplex = xfer_may_multiplex(data, needle); + Curl_cpool_prune_dead(cpool, data); + #ifdef USE_NTLM match.want_ntlm_http = (data->state.authhost.want & CURLAUTH_NTLM) && @@ -1284,11 +1193,8 @@ static struct connectdata *allocate_conn(struct Curl_easy *data) conn->connection_id = -1; /* no ID */ conn->attached_xfers = 0; - /* Store creation time to help future close decision making */ - conn->created = *Curl_pgrs_now(data); - - /* Store current time to give a baseline to keepalive connection times. */ - conn->keepalive = conn->created; + /* Remember time this connection started */ + conn->lastused = conn->lastupkeep = conn->created = *Curl_pgrs_now(data); #ifndef CURL_DISABLE_FTP conn->bits.ftp_use_epsv = data->set.ftp_use_epsv; @@ -1299,8 +1205,8 @@ static struct connectdata *allocate_conn(struct Curl_easy *data) conn->transport_wanted = TRNSPRT_TCP; /* most of them are TCP streams */ /* Store the local bind parameters that will be used for this connection */ - if(data->set.str[STRING_DEVICE]) { - conn->localdev = curlx_strdup(data->set.str[STRING_DEVICE]); + if(CURL_EASY_STR(data, STRING_DEVICE)) { + conn->localdev = curlx_strdup(CURL_EASY_STR(data, STRING_DEVICE)); if(!conn->localdev) goto error; } @@ -1313,7 +1219,6 @@ static struct connectdata *allocate_conn(struct Curl_easy *data) it may live on without (this specific) Curl_easy */ conn->fclosesocket = data->set.fclosesocket; conn->closesocket_client = data->set.closesocket_client; - conn->lastused = conn->created; #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) conn->gssapi_delegation = data->set.gssapi_delegation; #endif @@ -1374,7 +1279,6 @@ static CURLcode hsts_upgrade(struct Curl_easy *data, CURLUcode uc; CURLcode result; - curlx_safefree(data->state.up.scheme); uc = curl_url_set(uh, CURLUPART_SCHEME, "https", 0); if(uc) return Curl_uc_to_curlcode(uc); @@ -1386,7 +1290,7 @@ static CURLcode hsts_upgrade(struct Curl_easy *data, Curl_bufref_set(&data->state.url, url, 0, curl_free); result = Curl_peer_from_url(uh, data, port_override, scope_id, - &data->state.up, &data->state.origin); + &data->state.origin); if(result) return result; infof(data, "Switched from HTTP to HTTPS due to HSTS => %s", url); @@ -1397,7 +1301,6 @@ static CURLcode hsts_upgrade(struct Curl_easy *data, #define hsts_upgrade(x, y, z, a) CURLE_OK #endif -#ifndef CURL_DISABLE_NETRC static bool str_has_ctrl(const char *input) { if(input) { @@ -1411,6 +1314,7 @@ static bool str_has_ctrl(const char *input) return FALSE; } +#ifndef CURL_DISABLE_NETRC /* * Override the login details from the URL with that in the CURLOPT_USERPWD * option or a .netrc file, if applicable. @@ -1457,7 +1361,7 @@ static CURLcode url_set_data_creds_netrc(struct Curl_easy *data, ret = Curl_netrc_scan(data, &data->state.netrc, data->state.origin->hostname, Curl_creds_user(ncreds_in), - data->set.str[STRING_NETRC_FILE], + CURL_EASY_STR(data, STRING_NETRC_FILE), &ncreds_out); DEBUGASSERT(!ret || !ncreds_out); if(ret == NETRC_OUT_OF_MEMORY) { @@ -1468,8 +1372,8 @@ static CURLcode url_set_data_creds_netrc(struct Curl_easy *data, (data->set.use_netrc == CURL_NETRC_OPTIONAL))) { infof(data, "Could not find host %s in the %s file; using defaults", data->state.origin->hostname, - (data->set.str[STRING_NETRC_FILE] ? - data->set.str[STRING_NETRC_FILE] : ".netrc")); + (CURL_EASY_STR(data, STRING_NETRC_FILE) ? + CURL_EASY_STR(data, STRING_NETRC_FILE) : ".netrc")); } else if(ret) { const char *m = Curl_netrc_strerror(ret); @@ -1523,45 +1427,57 @@ static CURLcode url_set_data_creds(struct Curl_easy *data, CURLU *uh) struct Curl_creds *newcreds = NULL; CURLcode result = CURLE_OK; - if((data->set.str[STRING_USERNAME] || - data->set.str[STRING_PASSWORD] || - data->set.str[STRING_BEARER] || - data->set.str[STRING_SASL_AUTHZID] || - data->set.str[STRING_SERVICE_NAME]) && + if((CURL_EASY_STR(data, STRING_USERNAME) || + CURL_EASY_STR(data, STRING_PASSWORD) || + CURL_EASY_STR(data, STRING_BEARER) || + CURL_EASY_STR(data, STRING_SASL_AUTHZID) || + CURL_EASY_STR(data, STRING_SERVICE_NAME)) && Curl_auth_allowed_to_origin(data, data->state.origin)) { - result = Curl_creds_create(data->set.str[STRING_USERNAME], - data->set.str[STRING_PASSWORD], - data->set.str[STRING_BEARER], - data->set.str[STRING_SASL_AUTHZID], - data->set.str[STRING_SERVICE_NAME], + result = Curl_creds_create(CURL_EASY_STR(data, STRING_USERNAME), + CURL_EASY_STR(data, STRING_PASSWORD), + CURL_EASY_STR(data, STRING_BEARER), + CURL_EASY_STR(data, STRING_SASL_AUTHZID), + CURL_EASY_STR(data, STRING_SERVICE_NAME), CREDS_OPTION, &newcreds); if(result) goto out; + if(newcreds && + !(data->state.origin->scheme->flags & PROTOPT_USERPWDCTRL) && + (str_has_ctrl(Curl_creds_user(newcreds)) || + str_has_ctrl(Curl_creds_passwd(newcreds)))) { + /* if the protocol cannot handle control codes in credentials, make + sure there are none */ + failf(data, "control code detected in credentials"); + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; + } } /* Extract credentials from the URL only if there are none OR * if no CURLOPT_USER was set. */ if(!newcreds || !Curl_creds_has_user(newcreds)) { + char *user = NULL; + char *passwd = NULL; char *udecoded = NULL; char *pdecoded = NULL; CURLUcode uc; - uc = curl_url_get(uh, CURLUPART_USER, &data->state.up.user, 0); + uc = curl_url_get(uh, CURLUPART_USER, &user, 0); if(uc && (uc != CURLUE_NO_USER)) result = Curl_uc_to_curlcode(uc); if(!result) { - uc = curl_url_get(uh, CURLUPART_PASSWORD, &data->state.up.password, 0); + uc = curl_url_get(uh, CURLUPART_PASSWORD, &passwd, 0); if(uc && (uc != CURLUE_NO_PASSWORD)) result = Curl_uc_to_curlcode(uc); } - if(!result && data->state.up.user) { - result = Curl_urldecode(data->state.up.user, 0, &udecoded, NULL, + if(!result && user) { + result = Curl_urldecode(user, 0, &udecoded, NULL, (data->state.origin->scheme->flags & PROTOPT_USERPWDCTRL) ? REJECT_ZERO : REJECT_CTRL); } - if(!result && data->state.up.password) { - result = Curl_urldecode(data->state.up.password, 0, &pdecoded, NULL, + if(!result && passwd) { + result = Curl_urldecode(passwd, 0, &pdecoded, NULL, (data->state.origin->scheme->flags & PROTOPT_USERPWDCTRL) ? REJECT_ZERO : REJECT_CTRL); @@ -1572,6 +1488,8 @@ static CURLcode url_set_data_creds(struct Curl_easy *data, CURLU *uh) curlx_free(udecoded); curlx_free(pdecoded); + curlx_free(passwd); + curlx_free(user); if(result) { failf(data, "error extracting credentials from URL"); goto out; @@ -1606,8 +1524,8 @@ static CURLcode url_set_conn_origin_etc(struct Curl_easy *data, goto out; /* set the connection options */ - if(data->set.str[STRING_OPTIONS]) { - conn->options = curlx_strdup(data->set.str[STRING_OPTIONS]); + if(CURL_EASY_STR(data, STRING_OPTIONS)) { + conn->options = curlx_strdup(CURL_EASY_STR(data, STRING_OPTIONS)); if(!conn->options) { result = CURLE_OUT_OF_MEMORY; goto out; @@ -1621,11 +1539,6 @@ static CURLcode url_set_conn_origin_etc(struct Curl_easy *data, } } -#ifdef USE_IPV6 - conn->scope_id = data->set.scope_id ? - data->set.scope_id : data->state.origin->scopeid; -#endif - out: return result; } @@ -1638,14 +1551,14 @@ static CURLcode setup_range(struct Curl_easy *data) { struct UrlState *s = &data->state; s->resume_from = data->set.set_resume_from; - if(s->resume_from || data->set.str[STRING_SET_RANGE]) { + if(s->resume_from || CURL_EASY_STR(data, STRING_SET_RANGE)) { if(s->rangestringalloc) curlx_free(s->range); if(s->resume_from) s->range = curl_maprintf("%" FMT_OFF_T "-", s->resume_from); else - s->range = curlx_strdup(data->set.str[STRING_SET_RANGE]); + s->range = curlx_strdup(CURL_EASY_STR(data, STRING_SET_RANGE)); if(!s->range) return CURLE_OUT_OF_MEMORY; @@ -1704,6 +1617,17 @@ static CURLcode setup_connection_internals(struct Curl_easy *data, Curl_strntolower(conn->destination, conn->destination, strlen(conn->destination)); +#ifdef USE_IPV6 + if(data->set.scope_id) + conn->scope_id = data->set.scope_id; + else { + struct Curl_peer *first = Curl_conn_get_first_peer(conn, FIRSTSOCKET); + if(!first) + return CURLE_FAILED_INIT; + conn->scope_id = first->scopeid; + } +#endif + return CURLE_OK; } @@ -1851,14 +1775,16 @@ static CURLcode parse_connect_to_string(struct Curl_easy *data, /* check whether the URL's hostname matches. Use the URL hostname * when it was an IPv6 address. Otherwise use the connection's hostname * that has IDN conversion. */ - const char *hostname_to_match = (dest->user_hostname[0] == '[') ? - dest->user_hostname : dest->hostname; - size_t hlen = strlen(hostname_to_match); - host_match = curl_strnequal(ptr, hostname_to_match, hlen); - ptr += hlen; - - host_match = host_match && *ptr == ':'; - ptr++; + size_t hlen = strlen(dest->hostname); + host_match = curl_strnequal(ptr, dest->hostname, hlen); + if(!host_match && (dest->user_hostname != dest->hostname)) { + /* hostname was normalized, could be IPv6 or IDN */ + hlen = strlen(dest->user_hostname); + host_match = curl_strnequal(ptr, dest->user_hostname, hlen); + } + host_match = host_match && ptr[hlen] == ':'; + if(host_match) + ptr += hlen + 1; } if(host_match) { @@ -1894,7 +1820,7 @@ static CURLcode url_set_conn_peer(struct Curl_easy *data, struct connectdata *conn) { CURLcode result = CURLE_OK; - const struct Curl_peer *origin = conn->origin; + struct Curl_peer *origin = conn->origin; struct Curl_peer *via_peer = NULL; struct curl_slist *conn_to_entry = data->set.connect_to; @@ -1946,8 +1872,7 @@ static CURLcode url_set_conn_peer(struct Curl_easy *data, if(!hit && (neg->wanted & CURL_HTTP_V3x)) { srcalpnid = ALPN_h3; hit = Curl_altsvc_lookup(data->asi, - ALPN_h3, origin->hostname, - origin->port, /* from */ + origin, ALPN_h3, /* from */ &as /* to */, allowed_alpns, &same_dest); } @@ -1957,8 +1882,7 @@ static CURLcode url_set_conn_peer(struct Curl_easy *data, !neg->h2_prior_knowledge) { srcalpnid = ALPN_h2; hit = Curl_altsvc_lookup(data->asi, - ALPN_h2, origin->hostname, - origin->port, /* from */ + origin, ALPN_h2, /* from */ &as /* to */, allowed_alpns, &same_dest); } @@ -1967,8 +1891,7 @@ static CURLcode url_set_conn_peer(struct Curl_easy *data, !neg->only_10) { srcalpnid = ALPN_h1; hit = Curl_altsvc_lookup(data->asi, - ALPN_h1, origin->hostname, - origin->port, /* from */ + origin, ALPN_h1, /* from */ &as /* to */, allowed_alpns, &same_dest); } @@ -2085,10 +2008,8 @@ static CURLcode url_create_needle(struct Curl_easy *data, CURLcode result = CURLE_OK; bool network_scheme = TRUE; /* almost all are */ - /* First, split up the current URL in parts so that we can use the - parts for checking against the already present connections. In order - to not have to modify everything at once, we allocate a temporary - connection data struct and fill in for comparison purposes. */ + /* Allocate a temporary connection data struct (needle) and fill in for + comparison purposes. */ needle = allocate_conn(data); if(!needle) { result = CURLE_OUT_OF_MEMORY; @@ -2114,11 +2035,10 @@ static CURLcode url_create_needle(struct Curl_easy *data, /************************************************************* * Set UDS first. It overrides "via_peer" and proxy settings. *************************************************************/ - if(network_scheme && data->set.str[STRING_UNIX_SOCKET_PATH]) { - result = Curl_peer_uds_create(needle->origin->scheme, - data->set.str[STRING_UNIX_SOCKET_PATH], - (bool)data->set.abstract_unix_socket, - &needle->via_peer); + if(network_scheme && CURL_EASY_STR(data, STRING_UNIX_SOCKET_PATH)) { + result = Curl_peer_uds_create( + needle->origin->scheme, CURL_EASY_STR(data, STRING_UNIX_SOCKET_PATH), + (bool)data->set.abstract_unix_socket, &needle->via_peer); if(result) goto out; } @@ -2231,10 +2151,10 @@ static CURLcode url_set_data_origin_and_creds(struct Curl_easy *data) /* Calculate the *real* URL this transfer uses, applying defaults * where information is missing. */ - if(data->set.str[STRING_DEFAULT_PROTOCOL] && + if(CURL_EASY_STR(data, STRING_DEFAULT_PROTOCOL) && !Curl_is_absolute_url(Curl_bufref_ptr(&data->state.url), NULL, 0, TRUE)) { char *url = curl_maprintf("%s://%s", - data->set.str[STRING_DEFAULT_PROTOCOL], + CURL_EASY_STR(data, STRING_DEFAULT_PROTOCOL), Curl_bufref_ptr(&data->state.url)); if(!url) { result = CURLE_OUT_OF_MEMORY; @@ -2272,7 +2192,7 @@ static CURLcode url_set_data_origin_and_creds(struct Curl_easy *data) /* `uh` is now as the connection should use it, probably. */ result = Curl_peer_from_url(uh, data, port_override, scope_id, - &data->state.up, &data->state.origin); + &data->state.origin); if(result) goto out; /* The origin might get changed when HSTS applies */ @@ -2345,21 +2265,21 @@ static CURLcode url_find_or_create_conn(struct Curl_easy *data) DEBUGASSERT(needle->scheme->run->connect_it); data->info.conn_scheme = needle->scheme->name; /* conn_protocol can only provide "old" protocols */ - data->info.conn_protocol = (needle->scheme->protocol) & CURLPROTO_MASK; + data->info.conn_protocol = needle->scheme->protocol & CURLPROTO_MASK; result = needle->scheme->run->connect_it(data, &done); if(result) goto out; /* Setup a "faked" transfer that will do nothing */ - Curl_attach_connection(data, needle); + result = Curl_cpool_add(data, needle); + Curl_attach_connection(data, needle, TRUE); needle = NULL; - result = Curl_cpool_add(data, data->conn); if(!result) { /* Setup whatever necessary for a resumed transfer */ result = setup_range(data); if(!result) { Curl_xfer_setup_nop(data); - result = Curl_init_do(data, data->conn); + result = Curl_init_transfer(data, data->conn); } } @@ -2377,9 +2297,6 @@ static CURLcode url_find_or_create_conn(struct Curl_easy *data) if(result) goto out; - /* Get rid of any dead connections so limit are easier kept. */ - Curl_cpool_prune_dead(data); - /************************************************************* * Reuse of existing connection is not allowed when * - connect_only is set or @@ -2430,7 +2347,7 @@ static CURLcode url_find_or_create_conn(struct Curl_easy *data) goto out; } else { - switch(Curl_cpool_check_limits(data, needle)) { + switch(Curl_cpool_check_limits(data, needle, &needle->created)) { case CPOOL_LIMIT_DEST: infof(data, "No more connections allowed to host"); result = CURLE_NO_CONNECTION_AVAILABLE; @@ -2458,11 +2375,12 @@ static CURLcode url_find_or_create_conn(struct Curl_easy *data) DEBUGF(curl_mfprintf(stderr, "Error: init connection SSL config\n")); goto out; } - /* attach it and no longer own it */ - Curl_attach_connection(data, needle); - needle = NULL; - result = Curl_cpool_add(data, data->conn); + /* Add needle to conn pool, which assigns the connection id. + * Attach regardless of result, for correct handling. */ + result = Curl_cpool_add(data, needle); + Curl_attach_connection(data, needle, TRUE); + needle = NULL; if(result) goto out; @@ -2487,7 +2405,7 @@ static CURLcode url_find_or_create_conn(struct Curl_easy *data) } /* Setup and init stuff before DO starts, in preparing for the transfer. */ - result = Curl_init_do(data, data->conn); + result = Curl_init_transfer(data, data->conn); if(result) goto out; @@ -2499,7 +2417,7 @@ static CURLcode url_find_or_create_conn(struct Curl_easy *data) /* persist the scheme and handler the transfer is using */ data->info.conn_scheme = data->conn->scheme->name; /* conn_protocol can only provide "old" protocols */ - data->info.conn_protocol = (data->conn->scheme->protocol) & CURLPROTO_MASK; + data->info.conn_protocol = data->conn->scheme->protocol & CURLPROTO_MASK; data->info.used_proxy = #ifdef CURL_DISABLE_PROXY 0 @@ -2573,23 +2491,22 @@ out: /* We are not allowed to return failure with memory left allocated in the connectdata struct, free those here */ Curl_detach_connection(data); - Curl_conn_terminate(data, conn, TRUE); + Curl_conn_close(data, conn, TRUE); } return result; } /* - * Curl_init_do() inits the readwrite session. This is inited each time (in - * the DO function before the protocol-specific DO functions are invoked) for - * a transfer, sometimes multiple times on the same Curl_easy. Make sure - * nothing in here depends on stuff that are setup dynamically for the - * transfer. + * Curl_init_transfer() is called each time before the transfer starts - to + * prepare for a transfer, sometimes multiple times on the same Curl_easy. + * Make sure nothing in here depends on stuff that is setup dynamically for + * the transfer. * * Allow this function to get called with 'conn' set to NULL. */ -CURLcode Curl_init_do(struct Curl_easy *data, struct connectdata *conn) +CURLcode Curl_init_transfer(struct Curl_easy *data, struct connectdata *conn) { CURLcode result; @@ -2620,7 +2537,7 @@ CURLcode Curl_init_do(struct Curl_easy *data, struct connectdata *conn) void Curl_data_priority_clear_state(struct Curl_easy *data) { - memset(&data->state.priority, 0, sizeof(data->state.priority)); + data->state.weight = 0; } #endif /* USE_HTTP2 || USE_HTTP3 */ @@ -2646,6 +2563,31 @@ void *Curl_conn_meta_get(struct connectdata *conn, const char *key) return Curl_hash_pick(&conn->meta_hash, CURL_UNCONST(key), strlen(key) + 1); } +struct Curl_easy *Curl_get_admin(struct Curl_easy *data) +{ + struct Curl_easy *admin; + + if(!data->mid) /* already an admin handle */ + admin = data; + else if(data->multi) + admin = data->multi->admin; + else if(data->multi_easy) + admin = data->multi_easy->admin; + else { + DEBUGASSERT(0); /* we do not want this. does it happen? */ + admin = data; + } + if(admin != data) { + admin->set.conn_max_idle_ms = data->set.conn_max_idle_ms; + admin->set.conn_max_age_ms = data->set.conn_max_age_ms; + admin->set.upkeep_interval_ms = data->set.upkeep_interval_ms; + admin->set.timeout = data->set.timeout; + admin->set.server_response_timeout = data->set.server_response_timeout; + admin->set.no_signal = data->set.no_signal; + } + return admin; +} + CURLcode Curl_1st_fatal(CURLcode r1, CURLcode r2) { if(r1 && (r1 != CURLE_AGAIN)) diff --git a/Utilities/cmcurl/lib/url.h b/Utilities/cmcurl/lib/url.h index ebbe9d53c4..43ee63cfc5 100644 --- a/Utilities/cmcurl/lib/url.h +++ b/Utilities/cmcurl/lib/url.h @@ -32,7 +32,7 @@ * Prototypes for library-wide functions */ -CURLcode Curl_init_do(struct Curl_easy *data, struct connectdata *conn); +CURLcode Curl_init_transfer(struct Curl_easy *data, struct connectdata *conn); CURLcode Curl_open(struct Curl_easy **curl); void Curl_init_userdefined(struct Curl_easy *data); @@ -67,26 +67,18 @@ CURLcode Curl_conn_meta_set(struct connectdata *conn, const char *key, void Curl_conn_meta_remove(struct connectdata *conn, const char *key); void *Curl_conn_meta_get(struct connectdata *conn, const char *key); +/* Get an admin handle for internal operations from the given + * easy handle, if possible. The admin handle inherits certain + * properties from `data`. If no admin handle is available (not multi + * or share attached), the easy handle itself is returned. */ +struct Curl_easy *Curl_get_admin(struct Curl_easy *data); + #define CURL_DEFAULT_PROXY_PORT 1080 /* default proxy port unless specified */ #define CURL_DEFAULT_HTTPS_PROXY_PORT 443 /* default https proxy port unless specified */ -/** - * Return TRUE iff the given connection is considered dead. - */ -bool Curl_conn_seems_dead(struct connectdata *conn, - struct Curl_easy *data); - -/** - * Perform upkeep operations on the connection. - */ -CURLcode Curl_conn_upkeep(struct Curl_easy *data, - struct connectdata *conn); - -/** - * Always eval all arguments, return the first - * result != (CURLE_OK | CURLE_AGAIN) or `r1`. - */ +/* Always eval all arguments, return the first + * result != (CURLE_OK | CURLE_AGAIN) or `r1`. */ CURLcode Curl_1st_fatal(CURLcode r1, CURLcode r2); #if defined(USE_HTTP2) || defined(USE_HTTP3) diff --git a/Utilities/cmcurl/lib/urlapi-int.h b/Utilities/cmcurl/lib/urlapi-int.h index 4d8f2c1cb8..cf1fe6e7cc 100644 --- a/Utilities/cmcurl/lib/urlapi-int.h +++ b/Utilities/cmcurl/lib/urlapi-int.h @@ -34,11 +34,11 @@ struct Curl_URL { char *options; /* IMAP only? */ char *host; char *zoneid; /* for numerical IPv6 addresses */ - char *port; char *path; char *query; char *fragment; - unsigned short portnum; /* the numerical version (if 'port' is set) */ + uint16_t portnum; /* the numerical port if present */ + BIT(port_present); /* to support missing port */ BIT(query_present); /* to support blank */ BIT(fragment_present); /* to support blank */ BIT(guessed_scheme); /* when a URL without scheme is parsed */ @@ -65,4 +65,6 @@ CURLUcode Curl_junkscan(const char *url, size_t *urllen, bool allowspace); bool Curl_url_same_origin(CURLU *base, CURLU *href); +CURLUcode Curl_url_get_port(CURLU *u, uint16_t *pport); + #endif /* HEADER_CURL_URLAPI_INT_H */ diff --git a/Utilities/cmcurl/lib/urlapi.c b/Utilities/cmcurl/lib/urlapi.c index 05c79dfb0d..734efbc35e 100644 --- a/Utilities/cmcurl/lib/urlapi.c +++ b/Utilities/cmcurl/lib/urlapi.c @@ -53,6 +53,10 @@ /* scheme is not URL encoded, the longest libcurl supported ones are... */ #define MAX_SCHEME_LEN 40 +#define MAX_ZONEID_LEN 16 + +/* characters not allowed in hostnames */ +#define HOSTNAME_INVALID_CHARS " \r\n\t/:#?!@{}[]\\$\'\"^`*<>=;,+&()%|" /* * If USE_IPV6 is disabled, we still want to parse IPv6 addresses, so make @@ -69,11 +73,11 @@ static void free_urlhandle(struct Curl_URL *u) { curlx_free(u->scheme); curlx_free(u->user); + curlx_strzero(u->password); curlx_free(u->password); curlx_free(u->options); curlx_free(u->host); curlx_free(u->zoneid); - curlx_free(u->port); curlx_free(u->path); curlx_free(u->query); curlx_free(u->fragment); @@ -206,7 +210,7 @@ size_t Curl_is_absolute_url(const char *url, char *buf, size_t buflen, if(s && (ISALNUM(s) || (s == '+') || (s == '-') || (s == '.'))) { /* RFC 3986 3.1 explains: scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) - */ + */ } else { break; @@ -322,6 +326,7 @@ UNITTEST CURLUcode parse_hostname_login(struct Curl_URL *u, } if(passwdp) { + curlx_strzero(u->password); curlx_free(u->password); u->password = passwdp; } @@ -338,9 +343,11 @@ UNITTEST CURLUcode parse_hostname_login(struct Curl_URL *u, out: curlx_free(userp); + curlx_strzero(passwdp); curlx_free(passwdp); curlx_free(optionsp); curlx_safefree(u->user); + curlx_strzero(u->password); curlx_safefree(u->password); curlx_safefree(u->options); @@ -358,6 +365,8 @@ UNITTEST CURLUcode parse_port(struct Curl_URL *u, struct dynbuf *host, /* * Find the end of an IPv6 address on the ']' ending bracket. */ + u->portnum = 0; + u->port_present = FALSE; if(hostname[0] == '[') { portptr = strchr(hostname, ']'); if(!portptr) @@ -377,28 +386,30 @@ UNITTEST CURLUcode parse_port(struct Curl_URL *u, struct dynbuf *host, if(portptr) { curl_off_t port; size_t keep = portptr - hostname; + int rc; /* Browser behavior adaptation. If there is a colon with no digits after, cut off the name there which makes us ignore the colon and use the default port. Firefox, Chrome and Safari all do that. Do not do it if the URL has no scheme, to make something that looks like - a scheme not work! - */ + a scheme not work! */ curlx_dyn_setlen(host, keep); portptr++; if(!*portptr) return has_scheme ? CURLUE_OK : CURLUE_BAD_PORT_NUMBER; - - if(curlx_str_number(&portptr, &port, 0xffff) || *portptr) + if(*portptr == '\\') + return CURLUE_BACKSLASH; + rc = curlx_str_number(&portptr, &port, 0xffff); + if(rc) + return CURLUE_BAD_PORT_NUMBER; + else if(*portptr == '\\') + return CURLUE_BACKSLASH; + else if(*portptr) return CURLUE_BAD_PORT_NUMBER; - u->portnum = (unsigned short)port; - /* generate a new port number string to get rid of leading zeroes etc */ - curlx_free(u->port); - u->port = curl_maprintf("%" CURL_FORMAT_CURL_OFF_T, port); - if(!u->port) - return CURLUE_OUT_OF_MEMORY; + u->portnum = (uint16_t)port; + u->port_present = TRUE; } return CURLUE_OK; @@ -428,13 +439,13 @@ UNITTEST CURLUcode ipv6_parse(struct Curl_URL *u, char *hostname, hlen = len; if(hostname[len] == '%') { /* this could now be '%[zone id]' */ - char zoneid[16]; + char zoneid[MAX_ZONEID_LEN]; int i = 0; char *h = &hostname[len + 1]; /* pass '25' if present and is a URL encoded percent sign */ if(!strncmp(h, "25", 2) && h[2] && (h[2] != ']')) h += 2; - while(*h && (*h != ']') && (i < 15)) + while(*h && (*h != ']') && (i < (MAX_ZONEID_LEN - 1))) zoneid[i++] = *h++; if(!i || (']' != *h)) return CURLUE_BAD_IPV6; @@ -456,7 +467,7 @@ UNITTEST CURLUcode ipv6_parse(struct Curl_URL *u, char *hostname, hostname[hlen] = 0; /* end the address there */ if(curlx_inet_pton(AF_INET6, hostname, dest) != 1) return CURLUE_BAD_IPV6; - if(curlx_inet_ntop(AF_INET6, dest, hostname, hlen + 1)) { + if(!curlx_inet_ntop(AF_INET6, dest, hostname, hlen + 1)) { hlen = strlen(hostname); /* might be shorter now */ hostname[hlen + 1] = 0; } @@ -477,7 +488,7 @@ static CURLUcode hostname_check(struct Curl_URL *u, char *hostname, return ipv6_parse(u, hostname, hlen); else { /* letters from the second string are not ok */ - len = strcspn(hostname, " \r\n\t/:#?!@{}[]\\$\'\"^`*<>=;,+&()%|"); + len = strcspn(hostname, HOSTNAME_INVALID_CHARS); if(hlen != len) /* hostname with bad content */ return CURLUE_BAD_HOSTNAME; @@ -583,7 +594,7 @@ UNITTEST int ipv4_normalize(struct dynbuf *host) return HOST_NAME; curlx_dyn_reset(host); result = curlx_dyn_addf(host, "%u.%u.%u.%u", - (parts[0]), + parts[0], ((parts[1] >> 16) & 0xff), ((parts[1] >> 8) & 0xff), (parts[1] & 0xff)); @@ -593,8 +604,8 @@ UNITTEST int ipv4_normalize(struct dynbuf *host) return HOST_NAME; curlx_dyn_reset(host); result = curlx_dyn_addf(host, "%u.%u.%u.%u", - (parts[0]), - (parts[1]), + parts[0], + parts[1], ((parts[2] >> 8) & 0xff), (parts[2] & 0xff)); break; @@ -604,10 +615,10 @@ UNITTEST int ipv4_normalize(struct dynbuf *host) return HOST_NAME; curlx_dyn_reset(host); result = curlx_dyn_addf(host, "%u.%u.%u.%u", - (parts[0]), - (parts[1]), - (parts[2]), - (parts[3])); + parts[0], + parts[1], + parts[2], + parts[3]); break; } if(result) @@ -666,12 +677,11 @@ static CURLUcode parse_authority(struct Curl_URL *u, } uc = parse_port(u, host, has_scheme); - if(uc) - return uc; if(!curlx_dyn_len(host)) + /* this makes no-host errors override port number problems */ uc = CURLUE_NO_HOST; - else + if(!uc) uc = urldecode_host(host); if(uc) return uc; @@ -740,6 +750,29 @@ static bool is_dot(const char **str, size_t *clen) #define ISSLASH(x) ((x) == '/') +/* prescan the string to see if it needs work */ +static bool needs_dedotdot(const char *p, size_t pn) +{ + /* a single byte path cannot be cleaned up */ + if(pn < 2) + return FALSE; + while(pn) { + if(is_dot(&p, &pn)) { + /* "./" or dot before end of string */ + if(!pn || ISSLASH(*p)) + return TRUE; + /* "../" or ".." before end of string */ + else if(is_dot(&p, &pn) && (!pn || ISSLASH(*p))) + return TRUE; + } + else { + p++; + pn--; + } + } + return FALSE; +} + /* * dedotdotify() * @@ -751,7 +784,8 @@ static bool is_dot(const char **str, size_t *clen) * * RETURNS * - * Zero for success and 'out' set to an allocated dedotdotified string. + * Zero for success and 'out' set to an allocated string (or NULL if there's + * nothing to do). * * @unittest 1395 */ @@ -766,8 +800,7 @@ UNITTEST int dedotdotify(const char *input, size_t clen, char **outp) size_t dlen = clen; *outp = NULL; - /* a single byte path cannot be cleaned up */ - if(clen < 2) + if(!needs_dedotdot(input, clen)) return 0; curlx_dyn_init(&out, clen + 1); @@ -964,22 +997,30 @@ static CURLUcode parse_scheme(const char *url, CURLU *u, char *schemebuf, const char *schemep = NULL; if(schemelen) { - int i = 0; + int num_slashes = 0; const char *p = &url[schemelen + 1]; - while((*p == '/') && (i < 4)) { - p++; - i++; + if(!Curl_get_scheme(schemebuf) && !(flags & CURLU_NON_SUPPORT_SCHEME)) + return CURLUE_UNSUPPORTED_SCHEME; + + if(!ISSLASH(*p)) + /* less than one */ + return CURLUE_BAD_SLASHES; + if((flags & CURLU_NO_AUTHORITY)) { + while(ISSLASH(*p) && (num_slashes < 2)) { + p++; + num_slashes++; + } + } + else { + while(ISSLASH(*p) && (num_slashes < 4)) { + p++; + num_slashes++; + } + if(num_slashes > 3) + return CURLUE_BAD_SLASHES; } schemep = schemebuf; - if(!Curl_get_scheme(schemep) && - !(flags & CURLU_NON_SUPPORT_SCHEME)) - return CURLUE_UNSUPPORTED_SCHEME; - - if((i < 1) || (i > 3)) - /* less than one or more than three slashes */ - return CURLUE_BAD_SLASHES; - *hostpp = p; /* hostname starts here */ } else { @@ -1270,16 +1311,16 @@ static CURLUcode redirect_url(const char *base, const char *relurl, case '#': /* fragment-only change */ - if(u->fragment) + if(u->fragment_present) cutoff = strchr(protsep, '#'); break; default: /* path or query-only change */ - if(u->query && u->query[0]) + if(u->query_present) /* remove existing query */ cutoff = strchr(protsep, '?'); - else if(u->fragment && u->fragment[0]) + else if(u->fragment_present) /* Remove existing fragment */ cutoff = strchr(protsep, '#'); @@ -1344,12 +1385,12 @@ CURLU *curl_url_dup(const CURLU *in) DUP(u, in, password); DUP(u, in, options); DUP(u, in, host); - DUP(u, in, port); DUP(u, in, path); DUP(u, in, query); DUP(u, in, fragment); DUP(u, in, zoneid); u->portnum = in->portnum; + u->port_present = in->port_present; u->fragment_present = in->fragment_present; u->query_present = in->query_present; } @@ -1484,7 +1525,7 @@ static CURLUcode urlget_url(const CURLU *u, char **part, unsigned int flags) else { const char *scheme; char *options = u->options; - char *port = u->port; + char *port = NULL; const struct Curl_scheme *h = NULL; char schemebuf[MAX_SCHEME_LEN + 5]; if(u->scheme) @@ -1494,19 +1535,26 @@ static CURLUcode urlget_url(const CURLU *u, char **part, unsigned int flags) else return CURLUE_NO_SCHEME; + if(u->port_present) { + curl_msnprintf(portbuf, sizeof(portbuf), "%u", u->portnum); + port = portbuf; + } + h = Curl_get_scheme(scheme); if(h) { - if(!port && (flags & CURLU_DEFAULT_PORT)) { + if(!u->port_present && (flags & CURLU_DEFAULT_PORT)) { /* there is no stored port number, but asked to deliver a default one for the scheme */ curl_msnprintf(portbuf, sizeof(portbuf), "%u", h->defport); port = portbuf; } - else if(port && (h->defport == u->portnum) && - (flags & CURLU_NO_DEFAULT_PORT)) + else if(u->port_present && (h->defport == u->portnum) && + (flags & CURLU_NO_DEFAULT_PORT)) { /* there is a stored port number, but asked to inhibit if it matches the default port for the scheme */ port = NULL; + } + if(!(h->flags & PROTOPT_URLOPTIONS)) options = NULL; } @@ -1614,10 +1662,24 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what, ifmissing = CURLUE_NO_ZONEID; break; case CURLUPART_PORT: - ptr = u->port; + ptr = NULL; ifmissing = CURLUE_NO_PORT; flags &= ~U_CURLU_URLDECODE; /* never for port */ - if(!ptr && (flags & CURLU_DEFAULT_PORT) && u->scheme) { + if(u->port_present) { + const struct Curl_scheme *h = u->scheme ? + Curl_get_scheme(u->scheme) : NULL; + /* there is a stored port number, but ask to inhibit if + it matches the default one for the scheme */ + if(h && (h->defport == u->portnum) && + (flags & CURLU_NO_DEFAULT_PORT)) { + ptr = NULL; + } + else { + curl_msnprintf(portbuf, sizeof(portbuf), "%u", u->portnum); + ptr = portbuf; + } + } + else if((flags & CURLU_DEFAULT_PORT) && u->scheme) { /* there is no stored port number, but asked to deliver a default one for the scheme */ const struct Curl_scheme *h = Curl_get_scheme(u->scheme); @@ -1626,14 +1688,6 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what, ptr = portbuf; } } - else if(ptr && u->scheme) { - /* there is a stored port number, but ask to inhibit if - it matches the default one for the scheme */ - const struct Curl_scheme *h = Curl_get_scheme(u->scheme); - if(h && (h->defport == u->portnum) && - (flags & CURLU_NO_DEFAULT_PORT)) - ptr = NULL; - } break; case CURLUPART_PATH: ptr = u->path; @@ -1645,7 +1699,7 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what, ifmissing = CURLUE_NO_QUERY; plusdecode = flags & CURLU_URLDECODE; if(ptr && !ptr[0] && !(flags & CURLU_GET_EMPTY)) - /* there was a blank query and the user do not ask for it */ + /* there was a blank query and the user does not ask for it */ ptr = NULL; break; case CURLUPART_FRAGMENT: @@ -1700,7 +1754,6 @@ static CURLUcode set_url_scheme(CURLU *u, const char *scheme, static CURLUcode set_url_port(CURLU *u, const char *provided_port) { - char *tmp; curl_off_t port; if(!ISDIGIT(provided_port[0])) /* not a number */ @@ -1708,12 +1761,8 @@ static CURLUcode set_url_port(CURLU *u, const char *provided_port) if(curlx_str_number(&provided_port, &port, 0xffff) || *provided_port) /* weirdly provided number, not good! */ return CURLUE_BAD_PORT_NUMBER; - tmp = curl_maprintf("%" CURL_FORMAT_CURL_OFF_T, port); - if(!tmp) - return CURLUE_OUT_OF_MEMORY; - curlx_free(u->port); - u->port = tmp; - u->portnum = (unsigned short)port; + u->portnum = (uint16_t)port; + u->port_present = TRUE; return CURLUE_OK; } @@ -1755,7 +1804,10 @@ static CURLUcode set_url(CURLU *u, const char *url, size_t part_size, /* if the old URL is incomplete (we cannot get an absolute URL in 'oldurl'), replace the existing with the new. Always include "scheme://" to make the URL "complete" */ - uc = curl_url_get(u, CURLUPART_URL, &oldurl, flags& ~CURLU_NO_GUESS_SCHEME); + /* Preserve empty query/fragment separators: they affect where relative + references splice into the base URL. */ + uc = curl_url_get(u, CURLUPART_URL, &oldurl, + (flags & ~CURLU_NO_GUESS_SCHEME) | CURLU_GET_EMPTY); if(uc == CURLUE_OUT_OF_MEMORY) return uc; else if(uc) @@ -1783,6 +1835,7 @@ static CURLUcode urlset_clear(CURLU *u, CURLUPart what) curlx_safefree(u->user); break; case CURLUPART_PASSWORD: + curlx_strzero(u->password); curlx_safefree(u->password); break; case CURLUPART_OPTIONS: @@ -1796,7 +1849,7 @@ static CURLUcode urlset_clear(CURLU *u, CURLUPart what) break; case CURLUPART_PORT: u->portnum = 0; - curlx_safefree(u->port); + u->port_present = FALSE; break; case CURLUPART_PATH: curlx_safefree(u->path); @@ -1952,7 +2005,7 @@ static CURLUcode url_sethost(CURLU *u, struct dynbuf *encp, bad = TRUE; curlx_free(decoded); } - else if(hostname_check(u, (char *)CURL_UNCONST(newp), n)) + else if(hostname_check(u, newp, n)) bad = TRUE; if(bad) { curlx_dyn_free(encp); @@ -2059,6 +2112,8 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, if(status) return status; + if(what == CURLUPART_PASSWORD) + curlx_strzero(*storep); curlx_free(*storep); *storep = (char *)CURL_UNCONST(newp); } @@ -2077,26 +2132,42 @@ bool Curl_url_same_origin(CURLU *base, CURLU *href) if(href->host) { if(!curl_strequal(base->host, href->host)) return FALSE; - if(!curl_strequal(base->zoneid ? base->zoneid : "", - href->zoneid ? href->zoneid : "")) - return FALSE; - if(!curl_strequal(base->port, href->port)) { - /* This may still match if only one has an explicit port - * and it is the default for the scheme. */ - if(base->port && href->port) - return FALSE; + if(base->port_present != href->port_present) { + /* one is present, one is not */ s = Curl_get_scheme(base->scheme); if(!s) /* Cannot match default port for unknown scheme */ return FALSE; - - /* The port which is set must be the default one */ - if((base->port && (base->portnum != s->defport)) || - (href->port && (href->portnum != s->defport))) + /* to match, the present one must be the default port */ + if((base->port_present && (base->portnum != s->defport)) || + (href->port_present && (href->portnum != s->defport))) return FALSE; } + else if(base->portnum != href->portnum) /* both present or missing */ + return FALSE; + + if(!curl_strequal(base->zoneid ? base->zoneid : "", + href->zoneid ? href->zoneid : "")) + return FALSE; } - else if(href->port) /* no host in href, then there must be no port */ + else if(href->port_present) /* no host in href, then there must be no port */ return FALSE; return TRUE; } + +CURLUcode Curl_url_get_port(CURLU *u, uint16_t *pport) +{ + if(u->port_present) { + *pport = u->portnum; + return CURLUE_OK; + } + else if(u->scheme) { + const struct Curl_scheme *s = Curl_get_scheme(u->scheme); + if(s && s->defport) { + *pport = s->defport; + return CURLUE_OK; + } + } + *pport = 0; + return CURLUE_NO_PORT; +} diff --git a/Utilities/cmcurl/lib/urldata.h b/Utilities/cmcurl/lib/urldata.h index d4d336d8db..1c0101847b 100644 --- a/Utilities/cmcurl/lib/urldata.h +++ b/Utilities/cmcurl/lib/urldata.h @@ -37,7 +37,7 @@ #define DEFAULT_CONNCACHE_SIZE 5 -/* length of longest IPv6 address string including the trailing null */ +/* length of longest IPv6 address string including the null-terminator */ #define MAX_IPADR_LEN sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255") /* Max string input length is a precaution against abuse and to detect junk @@ -53,13 +53,12 @@ #include "curlx/timeval.h" -#include "asyn.h" +#include "api.h" #include "cookie.h" #include "creds.h" #include "psl.h" #include "formdata.h" #include "http_chunks.h" /* for the structs and enum stuff */ -#include "hostip.h" #include "hash.h" #include "peer.h" #include "proxy.h" @@ -70,11 +69,14 @@ #include "request.h" #include "ratelimit.h" #include "netrc.h" +#include "uint-hashset.h" +#include "vdns/asyn.h" +#include "vdns/hostip.h" #include "vtls/vtls_config.h" /* On error return, the value of `pnwritten` has no meaning */ typedef CURLcode (Curl_send)(struct Curl_easy *data, /* transfer */ - int sockindex, /* socketindex */ + int8_t sockindex, /* socketindex */ const uint8_t *buf, /* data to write */ size_t len, /* amount to send */ bool eos, /* last chunk */ @@ -82,7 +84,7 @@ typedef CURLcode (Curl_send)(struct Curl_easy *data, /* transfer */ /* On error return, the value of `pnread` has no meaning */ typedef CURLcode (Curl_recv)(struct Curl_easy *data, /* transfer */ - int sockindex, /* socketindex */ + int8_t sockindex, /* socketindex */ char *buf, /* store data here */ size_t len, /* max amount to read */ size_t *pnread); /* how much received */ @@ -98,7 +100,10 @@ typedef CURLcode (Curl_recv)(struct Curl_easy *data, /* transfer */ #include "cf-socket.h" #ifdef HAVE_GSSAPI -# ifdef HAVE_GSSGNU +# ifdef HAVE_GSSAPPLE +# include +# include +# elif defined(HAVE_GSSGNU) # include # elif defined(HAVE_GSSAPI_H) # include @@ -129,19 +134,6 @@ typedef CURLcode (Curl_recv)(struct Curl_easy *data, /* transfer */ #define UPLOADBUFFER_MAX (2 * 1024 * 1024) #define UPLOADBUFFER_MIN CURL_MAX_WRITE_SIZE -#define CURLEASY_MAGIC_NUMBER 0xc0dedbadU -#ifdef DEBUGBUILD -/* On a debug build, we want to fail hard on easy handles that - * are not NULL, but no longer have the MAGIC touch. This gives - * us early warning on things only discovered by valgrind otherwise. */ -#define GOOD_EASY_HANDLE(x) \ - (((x) && ((x)->magic == CURLEASY_MAGIC_NUMBER)) ? TRUE : \ - (DEBUGASSERT(!(x)), FALSE)) -#else -#define GOOD_EASY_HANDLE(x) \ - ((x) && ((x)->magic == CURLEASY_MAGIC_NUMBER)) -#endif - #ifdef USE_WINDOWS_SSPI #include "curl_sspi.h" #endif @@ -268,73 +260,8 @@ struct ip_quadruple { * unique for an entire connection. */ struct connectdata { - struct Curl_llist_node cpool_node; /* conncache lists */ - struct Curl_llist_node cshutdn_node; /* cshutdn list */ - - curl_closesocket_callback fclosesocket; /* function closing the socket(s) */ - void *closesocket_client; - - /* This is used by the connection pool logic. If this returns TRUE, this - handle is still used by one or more easy handles and can only used by any - other easy handle without careful consideration (== only for - multiplexing) and it cannot be used by another multi handle! */ -#define CONN_INUSE(c) (!!(c)->attached_xfers) - - /**** Fields set when inited and not modified again */ curl_off_t connection_id; /* Contains a unique number to make it easier to track the connections in the log output */ - char *destination; /* string carrying normalized hostname+port+scope */ - - /* `meta_hash` is a general key-value store for implementations - * with the lifetime of the connection. - * Elements need to be added with their own destructor to be invoked when - * the connection is cleaned up (see Curl_hash_add2()).*/ - struct Curl_hash meta_hash; - - /* Who the connection is talking to, ultimately */ - struct Curl_peer *origin; /* connection ultimately talks to this */ - struct Curl_peer *via_peer; /* if set, connection really talks to this */ - struct Curl_peer *origin2; /* origin of SECONDARYSOCKET */ - struct Curl_peer *via_peer2; /* peer of SECONDARYSOCKET */ -#ifndef CURL_DISABLE_PROXY - struct proxy_info socks_proxy; - struct proxy_info http_proxy; -#endif - struct Curl_creds *creds; /* When connection itself is tied to credentials */ - struct Curl_peer *creds_origin; /* origin tied credentials are for */ - char *options; /* options string, allocated */ - struct curltime created; /* creation time */ - struct curltime lastused; /* when returned to the connection pool as idle */ - - /* A connection can have one or two sockets and connection filters. - * The protocol using the 2nd one is FTP for CONTROL+DATA sockets */ - curl_socket_t sock[2]; - struct Curl_cfilter *cfilter[2]; /* connection filters */ - Curl_recv *recv[2]; - Curl_send *send[2]; - int recv_idx; /* on which socket index to receive, default 0 */ - int send_idx; /* on which socket index to send, default 0 */ - -#define CONN_SOCK_IDX_VALID(i) (((i) >= 0) && ((i) < 2)) - - struct { - struct curltime start[2]; /* when filter shutdown started */ - timediff_t timeout_ms; /* 0 means no timeout */ - } shutdown; - - struct ssl_primary_config ssl_config; -#ifndef CURL_DISABLE_PROXY - struct ssl_primary_config proxy_ssl_config; -#endif - struct ConnectBits bits; /* various state-flags for this connection */ - - const struct Curl_scheme *scheme; /* Connection's protocol handler */ - const struct Curl_scheme *given; /* The protocol first given */ - - /* Protocols can use a custom keepalive mechanism to keep connections alive. - This allows those protocols to track the last time the keepalive mechanism - was used on this connection. */ - struct curltime keepalive; /* A connection cache from a SHARE might be used in several multi handles. * We MUST not reuse connections that are running in another multi, @@ -344,9 +271,62 @@ struct connectdata { * NEVER call anything on this multi, check for equality. */ struct Curl_multi *attached_multi; + /* Who the connection is talking to, ultimately */ + struct Curl_peer *origin; /* connection ultimately talks to this */ + struct Curl_peer *via_peer; /* if set, connection really talks to this */ + struct Curl_peer *origin2; /* origin of SECONDARYSOCKET */ + struct Curl_peer *via_peer2; /* peer of SECONDARYSOCKET */ + struct Curl_creds *creds; /* When connection itself is tied to credentials */ + struct Curl_peer *creds_origin; /* origin tied credentials are for */ + const struct Curl_scheme *scheme; /* Connection's real protocol handler */ + const struct Curl_scheme *given; /* The protocol first given */ + + /* `meta_hash` is a general key-value store for implementations + * with the lifetime of the connection. + * Elements need to be added with their own destructor to be invoked when + * the connection is cleaned up (see Curl_hash_add2()).*/ + struct Curl_hash meta_hash; + + struct Curl_llist_node cpool_node; /* conncache lists */ + struct Curl_llist_node cshutdn_node; /* cshutdn list */ + char *destination; /* hostname+port, used in conncache */ + + struct curltime created; /* creation time */ + struct curltime lastused; /* when returned to the connection pool as idle */ + struct curltime lastchecked; /* when last checked alive status */ + struct curltime lastupkeep; /* when last done conn_upkeep */ + +#ifndef CURL_DISABLE_PROXY + struct proxy_info socks_proxy; + struct proxy_info http_proxy; +#endif + + struct Curl_cfilter *cfilter[2]; /* connection filters */ + Curl_recv *recv[2]; + Curl_send *send[2]; + /* A connection can have one or two sockets and connection filters. + * The protocol using the 2nd one is FTP for CONTROL+DATA sockets */ + curl_socket_t sock[2]; + +#define CONN_SOCK_IDX_VALID(i) (((i) >= 0) && ((i) < 2)) + + struct { + struct curltime start[2]; /* when filter shutdown started */ + timediff_t timeout_ms; /* 0 means no timeout */ + } shutdown; + + curl_closesocket_callback fclosesocket; /* function closing the socket(s) */ + void *closesocket_client; + + struct ssl_primary_config ssl_config; +#ifndef CURL_DISABLE_PROXY + struct ssl_primary_config proxy_ssl_config; +#endif + char *options; /* options string, allocated */ + /*************** Request - specific items ************/ -#if defined(USE_WINDOWS_SSPI) && defined(SECPKG_ATTR_ENDPOINT_BINDINGS) - CtxtHandle *sslContext; /* mingw-w64 v9+, MS SDK 7.0A/VS2010+ */ +#ifdef USE_WINDOWS_SSPI + CtxtHandle *sslContext; #endif #ifdef USE_NTLM @@ -365,9 +345,16 @@ struct connectdata { that subsequent bound-requested connections are not accidentally reusing wrong connections. */ char *localdev; + struct ConnectBits bits; /* various state-flags for this connection */ #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) int socks5_gssapi_enctype; #endif + + /* This is used by the connection pool logic. If this returns TRUE, this + handle is still used by one or more easy handles and can only used by any + other easy handle without careful consideration (== only for + multiplexing) and it cannot be used by another multi handle! */ +#define CONN_INUSE(c) (!!(c)->attached_xfers) uint32_t attached_xfers; /* # of attached easy handles */ #ifdef USE_IPV6 @@ -375,6 +362,8 @@ struct connectdata { #endif uint16_t localportrange; uint16_t localport; + int8_t recv_idx; /* on which socket index to receive, default 0 */ + int8_t send_idx; /* on which socket index to send, default 0 */ uint8_t transport_wanted; /* one of the TRNSPRT_* defines. Not necessarily the transport the connection ends using due to Alt-Svc and happy eyeballing. Use Curl_conn_get_transport() for actual value once the @@ -384,6 +373,7 @@ struct connectdata { * 0 at start, then one of 09, 10, 11, etc. */ uint8_t httpversion_seen; uint8_t gssapi_delegation; /* inherited from set.gssapi_delegation */ + }; #ifndef CURL_DISABLE_PROXY @@ -404,22 +394,6 @@ struct connectdata { * All variables in this struct must be initialized/reset in Curl_initinfo(). */ struct PureInfo { - int httpcode; /* Recent HTTP, FTP, RTSP or SMTP response code */ - int httpproxycode; /* response code from proxy when received separate */ - int httpversion; /* the http version number X.Y = X*10+Y */ - time_t filetime; /* If requested, this is might get set. Set to -1 if the - time was unretrievable. */ - curl_off_t request_size; /* the amount of bytes sent in the request(s) */ - curl_off_t numconnects; /* how many new connections libcurl created */ - uint32_t proxyauthavail; /* what proxy auth types were announced */ - uint32_t httpauthavail; /* what host auth types were announced */ - uint32_t proxyauthpicked; /* selected proxy auth type */ - uint32_t httpauthpicked; /* selected host auth type */ - char *contenttype; /* the content type of the object */ - char *wouldredirect; /* URL this would have been redirected to if asked to */ - curl_off_t retry_after; /* info from Retry-After: header */ - uint32_t header_size; /* size of read header(s) in bytes */ - /* PureInfo primary ip_quadruple is copied over from the connectdata struct in order to allow curl_easy_getinfo() to return this information even when the session handle is no longer associated with a connection, @@ -427,11 +401,26 @@ struct PureInfo { session handle without disturbing information which is still alive, and that might be reused, in the connection pool. */ struct ip_quadruple primary; - const char *conn_scheme; - uint32_t conn_protocol; struct curl_certinfo certs; /* info about the certs. Asked for with CURLOPT_CERTINFO / CURLINFO_CERTINFO */ - CURLproxycode pxcode; + time_t filetime; /* If requested, this is might get set. Set to -1 if the + time was unretrievable. */ + curl_off_t request_size; /* the amount of bytes sent in the request(s) */ + curl_off_t numconnects; /* how many new connections libcurl created */ + char *contenttype; /* the content type of the object */ + char *wouldredirect; /* URL this would have been redirected to if asked to */ + curl_off_t retry_after; /* info from Retry-After: header */ + const char *conn_scheme; + int httpcode; /* Recent HTTP, FTP, RTSP or SMTP response code */ + int httpproxycode; /* response code from proxy when received separate */ + int httpversion; /* the http version number X.Y = X*10+Y */ + uint32_t conn_protocol; + uint32_t proxyauthavail; /* what proxy auth types were announced */ + uint32_t httpauthavail; /* what host auth types were announced */ + uint32_t proxyauthpicked; /* selected proxy auth type */ + uint32_t httpauthpicked; /* selected host auth type */ + uint32_t header_size; /* size of read header(s) in bytes */ + uint8_t pxcode; /* holds a CURLproxycode */ BIT(timecond); /* set to TRUE if the time condition did not match, which thus made the document NOT get fetched */ BIT(used_proxy); /* the transfer used a proxy */ @@ -446,61 +435,45 @@ struct pgrs_dir { struct Progress { struct curltime now; /* current time of processing */ - time_t lastshow; /* time() of the last displayed progress meter or NULL to - force redraw at next call */ + struct curltime start; /* when transfer was initialized, set once */ + struct pgrs_dir ul; struct pgrs_dir dl; curl_off_t deliver; /* amount of data delivered to application */ - curl_off_t current_speed; /* uses the currently fastest transfer */ curl_off_t earlydata_sent; - timediff_t timespent; - - timediff_t t_postqueue; - timediff_t t_nslookup; - timediff_t t_connect; - timediff_t t_appconnect; - timediff_t t_pretransfer; - timediff_t t_posttransfer; - timediff_t t_starttransfer; - timediff_t t_redirect; - - struct curltime start; - struct curltime t_startsingle; - struct curltime t_startop; - struct curltime t_startqueue; - struct curltime t_acceptdata; + struct { + timediff_t startop_us; /* since start when operations started */ + timediff_t startsingle_us; /* since start when last request started */ + timediff_t startqueue_us; /* since start when last entered queueing */ + timediff_t startredirect_us; /* since start when last redirected */ + timediff_t lastshow_us; /* since start when last progress shown */ + } delta; + struct { + timediff_t spent_us; /* all time spent since start */ + timediff_t queued_us; /* time spent since startsingle's for queueing */ + timediff_t nslookup_us; /* same for name resolves */ + timediff_t connect_us; /* same for connects */ + timediff_t appconnect_us; /* same for application connects, e.g. TLS */ + timediff_t pretransfer_us; /* same until requests were sent */ + timediff_t starttransfer_us; /* same until responses started */ + timediff_t posttransfer_us; /* same until responses ended */ + } total; #define CURL_SPEED_RECORDS (5 + 1) /* 6 entries for 5 seconds */ curl_off_t speed_amount[CURL_SPEED_RECORDS]; - struct curltime speed_time[CURL_SPEED_RECORDS]; + timediff_t speed_time[CURL_SPEED_RECORDS]; uint32_t speeder_c; BIT(hide); BIT(ul_size_known); BIT(dl_size_known); BIT(headers_out); /* when the headers have been written */ BIT(callback); /* set when progress callback is used */ - BIT(is_t_startransfer_set); + BIT(startransfer_added); }; -typedef enum { - RTSPREQ_NONE, /* first in list */ - RTSPREQ_OPTIONS, - RTSPREQ_DESCRIBE, - RTSPREQ_ANNOUNCE, - RTSPREQ_SETUP, - RTSPREQ_PLAY, - RTSPREQ_PAUSE, - RTSPREQ_TEARDOWN, - RTSPREQ_GET_PARAMETER, - RTSPREQ_SET_PARAMETER, - RTSPREQ_RECORD, - RTSPREQ_RECEIVE, - RTSPREQ_LAST /* last in list */ -} Curl_RtspReq; - struct auth { uint32_t want; /* Bitmask set to the authentication methods wanted by app (with CURLOPT_HTTPAUTH or CURLOPT_PROXYAUTH). */ @@ -520,22 +493,11 @@ struct Curl_data_prio_node { }; #endif -/** - * Priority information for an easy handle in relation to others - * on the same connection. - */ -struct Curl_data_priority { - int weight; -}; - /* Timers */ typedef enum { EXPIRE_100_TIMEOUT, EXPIRE_ASYNC_NAME, EXPIRE_CONNECTTIMEOUT, - EXPIRE_DNS_PER_NAME, /* family1 */ - EXPIRE_DNS_PER_NAME2, /* family2 */ - EXPIRE_HAPPY_EYEBALLS_DNS, /* See asyn-ares.c */ EXPIRE_HAPPY_EYEBALLS, EXPIRE_MULTI_PENDING, EXPIRE_SPEEDCHECK, @@ -548,42 +510,23 @@ typedef enum { EXPIRE_LAST /* not an actual timer, used as a marker only */ } expire_id; -/* - * One instance for each timeout an easy handle can set. - */ -struct time_node { - struct Curl_llist_node list; - struct curltime time; - expire_id eid; +struct expire_timers { + struct Curl_tree splaynode; /* for the splay stuff */ + /* microsecond offset from Curl_timeouts base timestamp */ + timediff_t offset_us[EXPIRE_LAST]; + uint8_t next[EXPIRE_LAST]; + uint8_t first; }; /* individual pieces of the URL */ struct urlpieces { - char *scheme; - char *hostname; - char *port; - char *user; - char *password; char *options; char *path; char *query; }; struct UrlState { - /* buffers to store authentication data in, as parsed from input options */ - struct curltime keeps_speed; /* for the progress meter really */ - - curl_off_t lastconnect_id; /* The last connection, -1 if undefined */ - curl_off_t recent_conn_id; /* The most recent connection used, might no - * longer exist */ - struct dynbuf headerb; /* buffer to store headers in */ -#ifndef CURL_DISABLE_HSTS - struct curl_slist *hstslist; /* list of HSTS files set by - curl_easy_setopt(HSTS) calls */ -#endif - curl_off_t current_speed; /* the ProgressShow() function sets this, - bytes / second */ - + curl_off_t lastconnect_id; /* The last assigned connection or -1 */ /* Origin of the initial (e.g. not followed) request of a transfer. Credentials from CURLOPT_* are only valid for this origin. Always set once a transfer starts searching for connections. */ @@ -592,6 +535,13 @@ struct UrlState { * requests. */ struct Curl_peer *origin; + struct curltime keeps_speed; /* for the progress meter really */ + struct dynbuf headerb; /* buffer to store headers in */ +#ifndef CURL_DISABLE_HSTS + struct curl_slist *hstslist; /* list of HSTS files set by + curl_easy_setopt(HSTS) calls */ +#endif + int os_errno; /* filled in with errno whenever an error occurs */ int requests; /* request counter: redirects + authentication retakes */ #ifdef HAVE_SIGNAL @@ -618,35 +568,16 @@ struct UrlState { void *baseprov; void *libctx; char *propq; /* for a provider */ - - BIT(provider_loaded); #endif /* USE_OPENSSL */ - struct curltime expiretime; /* set this with Curl_expire() only */ - struct Curl_tree timenode; /* for the splay stuff */ - struct Curl_llist timeoutlist; /* list of pending timeouts */ - struct time_node expires[EXPIRE_LAST]; /* nodes for each expire type */ + struct expire_timers timeouts; /* expire timeouts */ /* a place to store the most recently set (S)FTP entrypath */ char *most_recent_ftp_entrypath; char *range; /* range, if used. See README for detailed specification on this syntax. */ curl_off_t resume_from; /* continue [ftp] transfer from here */ - -#ifndef CURL_DISABLE_RTSP - /* This RTSP state information survives requests and connections */ - uint32_t rtsp_next_client_CSeq; /* the session's next client CSeq */ - uint32_t rtsp_next_server_CSeq; /* the session's next server CSeq */ - uint32_t rtsp_CSeq_recv; /* most recent CSeq received */ - uint8_t rtp_channel_mask[32]; /* for the correctness checking of the - interleaved data */ -#endif - curl_off_t infilesize; /* size of file to upload, -1 means unknown. Copied from set.filesize at start of operation */ -#if defined(USE_HTTP2) || defined(USE_HTTP3) - struct Curl_data_priority priority; /* shallow copy of data->set */ -#endif - curl_read_callback fread_func; /* read callback/function */ void *in; /* CURLOPT_READDATA */ CURLU *uh; /* URL handle for the current parsed URL */ @@ -661,12 +592,8 @@ struct UrlState { curl_mimepart *formp; /* storage for old API form-posting, allocated on demand */ #endif - size_t trailers_bytes_sent; - struct dynbuf trailers_buf; /* a buffer containing the compiled trailing - headers */ struct Curl_llist httphdrs; /* received headers */ struct curl_header headerout[2]; /* for external purposes */ - struct Curl_header_store *prevhead; /* the latest added header */ #endif #ifndef CURL_DISABLE_COOKIES struct curl_slist *cookielist; /* list of cookie files set by @@ -683,20 +610,21 @@ struct UrlState { struct Curl_creds *creds; /* Credentials for the origin only */ - /* Dynamically allocated strings, MUST be freed before this struct is - killed. */ - struct dynamically_allocated_data { - char *uagent; - char *accept_encoding; - char *rangeline; - char *ref; - char *host; -#ifndef CURL_DISABLE_RTSP - char *rtsp_transport; -#endif - } aptr; #ifndef CURL_DISABLE_HTTP + char *rangeline; /* allocated */ + char *http_host; /* allocated */ struct http_negotiation http_neg; +#endif +#ifndef CURL_DISABLE_RTSP + /* This RTSP state information survives requests and connections */ + uint8_t rtp_channel_mask[32]; /* for the correctness checking of the + interleaved data */ + uint32_t rtsp_next_client_CSeq; /* the session's next client CSeq */ + uint32_t rtsp_next_server_CSeq; /* the session's next server CSeq */ + uint32_t rtsp_CSeq_recv; /* most recent CSeq received */ +#endif +#if defined(USE_HTTP2) || defined(USE_HTTP3) + int weight; /* shallow copy of data->set */ #endif uint16_t followlocation; /* redirect counter */ uint8_t retrycount; /* number of retries on a new connection, up to @@ -704,6 +632,9 @@ struct UrlState { uint8_t httpreq; /* Curl_HttpReq; what kind of HTTP request (if any) is this */ +#ifdef USE_OPENSSL + BIT(provider_loaded); +#endif /* USE_OPENSSL */ BIT(really_alive); /* transfer is really alive in multi, passed INIT */ BIT(this_is_a_follow); /* this is a followed Location: request */ BIT(refused_stream); /* this was refused, try again */ @@ -719,9 +650,9 @@ struct UrlState { 417 response */ BIT(use_range); BIT(rangestringalloc); /* the range string is malloc()'ed */ - 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(done); /* set to FALSE when Curl_init_transfer() is called and set to + TRUE when multi_done() is called, to prevent multi_done() from + being invoked twice. */ #ifndef CURL_DISABLE_COOKIES BIT(cookie_engine); #endif @@ -836,14 +767,6 @@ enum dupstring { #ifndef CURL_DISABLE_SMTP STRING_MAIL_FROM, STRING_MAIL_AUTH, -#endif -#ifdef USE_TLS_SRP - STRING_TLSAUTH_USERNAME, /* TLS auth */ - STRING_TLSAUTH_PASSWORD, /* TLS auth */ -#ifndef CURL_DISABLE_PROXY - STRING_TLSAUTH_USERNAME_PROXY, /* TLS auth */ - STRING_TLSAUTH_PASSWORD_PROXY, /* TLS auth */ -#endif #endif STRING_BEARER, /* , if used */ #ifdef USE_UNIX_SOCKETS @@ -870,6 +793,11 @@ enum dupstring { #ifndef CURL_DISABLE_AWS STRING_AWS_SIGV4, /* Parameters for V4 signature */ #endif +#ifndef CURL_DISABLE_HTTPSIG + STRING_HTTPSIG_KEY, /* hex-encoded key data */ + STRING_HTTPSIG_KEYID, /* key identifier */ + STRING_HTTPSIG_HEADERS, /* space-separated components to sign */ +#endif #ifndef CURL_DISABLE_PROXY STRING_HAPROXY_CLIENT_IP, /* CURLOPT_HAPROXY_CLIENT_IP */ #endif @@ -877,14 +805,6 @@ enum dupstring { STRING_ECH_PUBLIC, /* CURLOPT_ECH_PUBLIC */ STRING_SSL_SIGNATURE_ALGORITHMS, /* CURLOPT_SSL_SIGNATURE_ALGORITHMS */ - /* -- end of null-terminated strings -- */ - - STRING_LASTZEROTERMINATED, - - /* -- below this are pointers to binary data that cannot be strdup'ed. --- */ - - STRING_COPYPOSTFIELDS, /* if POST, set the fields' values here */ - STRING_LAST /* not used, an end-of-list marker */ }; @@ -912,6 +832,7 @@ struct UserDefined { uint32_t httpauth; /* kind of HTTP authentication to use (bitmask) */ uint32_t proxyauth; /* kind of proxy authentication to use (bitmask) */ void *postfields; /* if POST, set the fields' values here */ + char *str_copypostfields; /* CURLOPT_COPYPOSTFIELDS value */ curl_seek_callback seek_func; /* function that seeks the input */ curl_off_t postfieldsize; /* if POST, this might have a size to use instead of strlen(), and then the data *may* be binary @@ -990,9 +911,6 @@ struct UserDefined { curl_off_t max_filesize; /* Maximum file size to download */ #ifndef CURL_DISABLE_FTP timediff_t accepttimeout; /* in milliseconds, 0 means no timeout */ - uint8_t ftp_filemethod; /* how to get to a file: curl_ftpfile */ - uint8_t ftpsslauth; /* what AUTH XXX to try: curl_ftpauth */ - uint8_t ftp_ccc; /* FTP CCC options: curl_ftpccc */ #endif #if !defined(CURL_DISABLE_FTP) || defined(USE_SSH) struct curl_slist *quote; /* after connection is established */ @@ -1009,9 +927,9 @@ struct UserDefined { uint32_t ssh_auth_types; /* allowed SSH auth types */ uint32_t new_directory_perms; /* when creating remote dirs */ #endif - uint32_t new_file_perms; /* when creating remote files */ - char *str[STRING_LAST]; /* array of strings, pointing to allocated memory */ + struct u8_strset strings; struct curl_blob *blobs[BLOB_LAST]; + uint32_t new_file_perms; /* when creating remote files */ #ifdef USE_IPV6 uint32_t scope_id; /* Scope id for IPv6 */ #endif @@ -1019,8 +937,6 @@ struct UserDefined { curl_prot_t redir_protocols; #ifndef CURL_DISABLE_RTSP void *rtp_out; /* write RTP to this if non-NULL */ - /* Common RTSP header options */ - Curl_RtspReq rtspreq; /* RTSP request type */ #endif #ifndef CURL_DISABLE_FTP curl_chunk_bgn_callback chunk_bgn; /* called before part of transfer @@ -1038,13 +954,6 @@ struct UserDefined { timediff_t happy_eyeballs_timeout; /* ms, 0 is a valid value */ timediff_t server_response_timeout; /* ms, 0 means no timeout */ timediff_t shutdowntimeout; /* ms, 0 means default timeout */ - int tcp_keepidle; /* seconds in idle before sending keepalive probe */ - int tcp_keepintvl; /* seconds between TCP keepalive probes */ - int tcp_keepcnt; /* maximum number of keepalive probes */ - -#if defined(USE_HTTP2) || defined(USE_HTTP3) - struct Curl_data_priority priority; -#endif curl_resolver_start_callback resolver_start; /* optional callback called before resolver start */ void *resolver_start_client; /* pointer to pass to resolver start callback */ @@ -1058,7 +967,16 @@ struct UserDefined { #ifndef CURL_DISABLE_SMTP struct curl_slist *mail_rcpt; /* linked list of mail recipients */ #endif + int tcp_keepidle; /* seconds in idle before sending keepalive probe */ + int tcp_keepintvl; /* seconds between TCP keepalive probes */ + int tcp_keepcnt; /* maximum number of keepalive probes */ + uint32_t maxconnects; /* Max idle connections in the connection cache */ +#if defined(USE_HTTP2) || defined(USE_HTTP3) + /* Priority information for an easy handle in relation to others on the same + connection. */ + int weight; +#endif short maxredirs; /* maximum no. of http(s) redirects to follow, set to -1 for infinity */ uint16_t expect_100_timeout; /* in milliseconds */ @@ -1073,6 +991,9 @@ struct UserDefined { #ifndef CURL_DISABLE_TFTP uint16_t tftp_blksize; /* in bytes, 0 means use default */ #endif +#ifndef CURL_DISABLE_RTSP + uint8_t rtspreq; /* RTSP request type */ +#endif #ifdef USE_ECH uint8_t tls_ech; /* TLS ECH configuration */ #endif @@ -1083,9 +1004,15 @@ struct UserDefined { /* Despite the name, ftp_create_missing_dirs is for FTP(S) and SFTP 1 - create directories that do not exist 2 - the same but also allow MKD to fail once - */ + */ uint8_t ftp_create_missing_dirs; #endif +#ifndef CURL_DISABLE_FTP + uint8_t ftp_filemethod; /* how to get to a file: curl_ftpfile */ + uint8_t ftpsslauth; /* what AUTH XXX to try: curl_ftpauth */ + uint8_t ftp_ccc; /* FTP CCC options: curl_ftpccc */ +#endif + uint8_t httpsig_algorithm; /* CURLHTTPSIG_* algorithm for RFC 9421 */ uint8_t use_ssl; /* if AUTH TLS is to be attempted etc, for FTP or IMAP or POP3 or others! (type: curl_usessl)*/ uint8_t timecondition; /* kind of time comparison: curl_TimeCond */ @@ -1213,10 +1140,10 @@ struct UserDefined { #define IS_MIME_POST(a) FALSE #endif -/* callback that gets called when a sub easy (data->master_mid set) is - DONE. Called on the master easy. */ -typedef void multi_sub_xfer_done_cb(struct Curl_easy *master_easy, - struct Curl_easy *sub_easy, +/* callback that gets called when the transfer `data` is done and + * `data->master_mid` is set to an existing easy handle. */ +typedef void multi_sub_xfer_done_cb(struct Curl_easy *data, + struct Curl_easy *master, CURLcode result); /* @@ -1230,37 +1157,38 @@ typedef void multi_sub_xfer_done_cb(struct Curl_easy *master_easy, */ struct Curl_easy { - /* First a simple identifier to easier detect if a user mix up this easy - handle with a multi handle. Set this to CURLEASY_MAGIC_NUMBER */ + /* First a simple identifier to more easily detect if a user mixes up this + easy handle with a multi handle. Set this to CURLEASY_MAGIC_NUMBER */ uint32_t magic; - /* once an easy handle is tied to a connection pool a non-negative number to - distinguish this transfer from other using the same pool. For easier - tracking in log output. This may wrap around after LONG_MAX to 0 again, - so it has no uniqueness guarantee for large processings. Note: it has no - uniqueness either IFF more than one connection pool is used by the - libcurl application. */ - curl_off_t id; /* once an easy handle is added to a multi, either explicitly by the * libcurl application or implicitly during `curl_easy_perform()`, * a unique identifier inside this one multi instance. */ uint32_t mid; - uint32_t master_mid; /* if set, this transfer belongs to a master */ - multi_sub_xfer_done_cb *sub_xfer_done; - - struct connectdata *conn; - CURLMstate mstate; /* the handle's state */ CURLcode result; /* previous result */ - struct Curl_message msg; /* A single posted message. */ - + struct connectdata *conn; struct Curl_multi *multi; /* if non-NULL, points to the multi handle struct to which this "belongs" when used by the multi interface */ + struct Curl_eapi_stack callstack; /* easy api calls ongoing */ + + struct Curl_share *share; /* Share, handles global variable mutexing */ + struct Curl_multi *multi_easy; /* if non-NULL, points to the multi handle struct to which this "belongs" when used by the easy interface */ - struct Curl_share *share; /* Share, handles global variable mutexing */ + struct Curl_message msg; /* A single posted message. */ + + /* once an easy handle is tied to a connection pool a non-negative number to + distinguish this transfer from other using the same pool. For easier + tracking in log output. This may wrap around after CURL_OFF_T_MAX to 0 + again, so it has no uniqueness guarantee for large processings. Note: it + has no uniqueness either IFF more than one connection pool is used by the + libcurl application. */ + curl_off_t id; + uint32_t master_mid; /* if set, this transfer belongs to a master */ + multi_sub_xfer_done_cb *sub_xfer_done; /* `meta_hash` is a general key-value store for implementations * with the lifetime of the easy handle. @@ -1296,6 +1224,17 @@ struct Curl_easy { valid after a client has asked for it */ }; +#define CURL_EASY_STR(d, id) \ + Curl_u8_strset_get(&(d)->set.strings, (uint8_t)(id)) +#define CURL_EASY_STR_SET(d, id, s, slen) \ + Curl_u8_strset_setx(&(d)->set.strings, (uint8_t)(id), (s), (slen)) +#define CURL_EASY_STR_SETN(d, id, s) \ + Curl_u8_strset_setn(&(d)->set.strings, (uint8_t)(id), (s)) +#define CURL_EASY_STR_CLEAR(d, id) \ + Curl_u8_strset_unset(&(d)->set.strings, (uint8_t)(id)) +#define CURL_EASY_STR_CLEAR0(d, id) \ + Curl_u8_strset_unset0(&(d)->set.strings, (uint8_t)(id)) + #define LIBCURL_NAME "libcurl" #endif /* HEADER_CURL_URLDATA_H */ diff --git a/Utilities/cmcurl/lib/vauth/digest.c b/Utilities/cmcurl/lib/vauth/digest.c index 1818fc96c7..748c0d8347 100644 --- a/Utilities/cmcurl/lib/vauth/digest.c +++ b/Utilities/cmcurl/lib/vauth/digest.c @@ -179,7 +179,7 @@ static char *auth_digest_string_quoted(const char *s) } /* Retrieves the value for a corresponding key from the challenge string - * returns TRUE if the key could be found, FALSE if it does not exists + * returns TRUE if the key could be found, FALSE if it does not exist */ static bool auth_digest_get_key_value(const char *chlg, const char *key, char *buf, size_t buflen) @@ -751,16 +751,15 @@ static CURLcode auth_create_digest_http_message( convert_to_ascii(hashbuf, (unsigned char *)userh); } - /* - If the algorithm is "MD5" or unspecified (which then defaults to MD5): + /* If the algorithm is "MD5" or unspecified (which then defaults to MD5): - A1 = unq(username-value) ":" unq(realm-value) ":" passwd + A1 = unq(username-value) ":" unq(realm-value) ":" passwd - If the algorithm is "MD5-sess" then: + If the algorithm is "MD5-sess" then: - A1 = H(unq(username-value) ":" unq(realm-value) ":" passwd) ":" - unq(nonce-value) ":" unq(cnonce-value) - */ + A1 = H(unq(username-value) ":" unq(realm-value) ":" passwd) ":" + unq(nonce-value) ":" unq(cnonce-value) + */ hashthis = curl_maprintf("%s:%s:%s", userp, digest->realm ? digest->realm : "", passwdp); @@ -790,18 +789,17 @@ static CURLcode auth_create_digest_http_message( convert_to_ascii(hashbuf, ha1); } - /* - If the "qop" directive's value is "auth" or is unspecified, then A2 is: + /* If the "qop" directive's value is "auth" or is unspecified, then A2 is: - A2 = Method ":" digest-uri-value + A2 = Method ":" digest-uri-value - If the "qop" value is "auth-int", then A2 is: + If the "qop" value is "auth-int", then A2 is: - A2 = Method ":" digest-uri-value ":" H(entity-body) + A2 = Method ":" digest-uri-value ":" H(entity-body) - (The "Method" value is the HTTP request method as specified in section - 5.1.1 of RFC 2616) - */ + (The "Method" value is the HTTP request method as specified in section + 5.1.1 of RFC 2616) + */ uri_quoted = auth_digest_string_quoted((const char *)uripath); if(!uri_quoted) { @@ -872,7 +870,7 @@ static CURLcode auth_create_digest_http_message( web-safe characters. uri is already percent encoded. nc is 8 hex characters. algorithm and qop with standard values only contain web-safe characters. - */ + */ userp_quoted = auth_digest_string_quoted(digest->userhash ? userh : userp); if(!userp_quoted) { result = CURLE_OUT_OF_MEMORY; diff --git a/Utilities/cmcurl/lib/vauth/digest_sspi.c b/Utilities/cmcurl/lib/vauth/digest_sspi.c index 305e367e17..e3648e37b8 100644 --- a/Utilities/cmcurl/lib/vauth/digest_sspi.c +++ b/Utilities/cmcurl/lib/vauth/digest_sspi.c @@ -54,7 +54,7 @@ bool Curl_auth_is_digest_supported(void) /* Query the security package for Digest */ status = Curl_pSecFn->QuerySecurityPackageInfo( - (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_DIGEST)), + CURL_UNCONST(TEXT(SP_NAME_DIGEST)), &SecurityPackage); /* Release the package buffer as it is not required anymore */ @@ -95,8 +95,8 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, CredHandle credentials; CtxtHandle context; PSecPkgInfo SecurityPackage; - SEC_WINNT_AUTH_IDENTITY identity; - SEC_WINNT_AUTH_IDENTITY *p_identity; + SEC_WINNT_AUTH_IDENTITY_EX identity; + SEC_WINNT_AUTH_IDENTITY_EX *p_identity; SecBuffer chlg_buf; SecBuffer resp_buf; SecBufferDesc chlg_desc; @@ -115,7 +115,7 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, /* Query the security package for DigestSSP */ status = Curl_pSecFn->QuerySecurityPackageInfo( - (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_DIGEST)), + CURL_UNCONST(TEXT(SP_NAME_DIGEST)), &SecurityPackage); if(status != SEC_E_OK) { failf(data, "SSPI: could not get auth info"); @@ -158,7 +158,7 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, /* Acquire our credentials handle */ status = Curl_pSecFn->AcquireCredentialsHandle(NULL, - (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_DIGEST)), + CURL_UNCONST(TEXT(SP_NAME_DIGEST)), SECPKG_CRED_OUTBOUND, NULL, p_identity, NULL, NULL, &credentials, NULL); @@ -243,7 +243,7 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, * Returns CURLE_OK on success. */ CURLcode Curl_override_sspi_http_realm(const char *chlg, - SEC_WINNT_AUTH_IDENTITY *identity) + SEC_WINNT_AUTH_IDENTITY_EX *identity) { xcharp_u domain, dup_domain; @@ -402,7 +402,7 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, /* Query the security package for DigestSSP */ status = Curl_pSecFn->QuerySecurityPackageInfo( - (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_DIGEST)), + CURL_UNCONST(TEXT(SP_NAME_DIGEST)), &SecurityPackage); if(status != SEC_E_OK) { failf(data, "SSPI: could not get auth info"); @@ -465,8 +465,8 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, if(!digest->http_context) { CredHandle credentials; - SEC_WINNT_AUTH_IDENTITY identity; - SEC_WINNT_AUTH_IDENTITY *p_identity; + SEC_WINNT_AUTH_IDENTITY_EX identity; + SEC_WINNT_AUTH_IDENTITY_EX *p_identity; SecBuffer resp_buf; SecBufferDesc resp_desc; unsigned long attrs; @@ -502,7 +502,7 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, /* Acquire our credentials handle */ status = Curl_pSecFn->AcquireCredentialsHandle(NULL, - (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_DIGEST)), + CURL_UNCONST(TEXT(SP_NAME_DIGEST)), SECPKG_CRED_OUTBOUND, NULL, p_identity, NULL, NULL, &credentials, NULL); diff --git a/Utilities/cmcurl/lib/vauth/gsasl.c b/Utilities/cmcurl/lib/vauth/gsasl.c index 10a83fdb09..37adba593d 100644 --- a/Utilities/cmcurl/lib/vauth/gsasl.c +++ b/Utilities/cmcurl/lib/vauth/gsasl.c @@ -33,7 +33,7 @@ #include #if GSASL_VERSION_NUMBER < 0x010600 -#error "requires libgsasl 1.6.0+" +#error "libgsasl 1.6.0 or greater required" #endif bool Curl_auth_gsasl_is_supported(struct Curl_easy *data, diff --git a/Utilities/cmcurl/lib/vauth/krb5_gssapi.c b/Utilities/cmcurl/lib/vauth/krb5_gssapi.c index 738ce9a744..6d9a125177 100644 --- a/Utilities/cmcurl/lib/vauth/krb5_gssapi.c +++ b/Utilities/cmcurl/lib/vauth/krb5_gssapi.c @@ -33,7 +33,7 @@ #include "curl_gssapi.h" #include "curl_trc.h" -#if defined(CURL_HAVE_DIAG) && defined(__APPLE__) +#if defined(CURL_HAVE_DIAG) && defined(__APPLE__) && !defined(HAVE_GSSAPPLE) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" #endif @@ -136,7 +136,8 @@ CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, &input_token, &output_token, mutual_auth, - NULL); + NULL, + GSS_C_NO_CREDENTIAL); if(GSS_ERROR(major_status)) { if(output_token.value) @@ -318,7 +319,7 @@ void Curl_auth_cleanup_gssapi(struct kerberos5data *krb5) } } -#if defined(CURL_HAVE_DIAG) && defined(__APPLE__) +#if defined(CURL_HAVE_DIAG) && defined(__APPLE__) && !defined(HAVE_GSSAPPLE) #pragma GCC diagnostic pop #endif diff --git a/Utilities/cmcurl/lib/vauth/krb5_sspi.c b/Utilities/cmcurl/lib/vauth/krb5_sspi.c index 543a6fe284..24d2421be6 100644 --- a/Utilities/cmcurl/lib/vauth/krb5_sspi.c +++ b/Utilities/cmcurl/lib/vauth/krb5_sspi.c @@ -46,7 +46,7 @@ bool Curl_auth_is_gssapi_supported(void) /* Query the security package for Kerberos */ status = Curl_pSecFn->QuerySecurityPackageInfo( - (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_KERBEROS)), + CURL_UNCONST(TEXT(SP_NAME_KERBEROS)), &SecurityPackage); /* Release the package buffer as it is not required anymore */ @@ -109,7 +109,7 @@ CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, if(!krb5->output_token) { /* Query the security package for Kerberos */ status = Curl_pSecFn->QuerySecurityPackageInfo( - (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_KERBEROS)), + CURL_UNCONST(TEXT(SP_NAME_KERBEROS)), &SecurityPackage); if(status != SEC_E_OK) { failf(data, "SSPI: could not get auth info"); @@ -150,7 +150,7 @@ CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, /* Acquire our credentials handle */ status = Curl_pSecFn->AcquireCredentialsHandle(NULL, - (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_KERBEROS)), + CURL_UNCONST(TEXT(SP_NAME_KERBEROS)), SECPKG_CRED_OUTBOUND, NULL, krb5->p_identity, NULL, NULL, krb5->credentials, NULL); diff --git a/Utilities/cmcurl/lib/vauth/ntlm.c b/Utilities/cmcurl/lib/vauth/ntlm.c index 2803c05d9f..3f1ffb50ce 100644 --- a/Utilities/cmcurl/lib/vauth/ntlm.c +++ b/Utilities/cmcurl/lib/vauth/ntlm.c @@ -301,7 +301,7 @@ static CURLcode ntlm_decode_type2_target(struct Curl_easy *data, 2. A 'short' containing the allocated space for the buffer in bytes. 3. A 'long' containing the offset to the start of the buffer in bytes, from the beginning of the NTLM message. -*/ + */ /* * Curl_auth_is_ntlm_supported() @@ -341,7 +341,7 @@ CURLcode Curl_auth_decode_ntlm_type2_message(struct Curl_easy *data, /* NTLM type-2 message structure: Index Description Content - 0 NTLMSSP Signature Null-terminated ASCII "NTLMSSP" + 0 NTLMSSP Signature null-terminated ASCII "NTLMSSP" (0x4e544c4d53535000) 8 NTLM Message Type long (0x02000000) 12 Target Name security buffer @@ -430,7 +430,7 @@ CURLcode Curl_auth_create_ntlm_type1_message(struct Curl_easy *data, /* NTLM type-1 message structure: Index Description Content - 0 NTLMSSP Signature Null-terminated ASCII "NTLMSSP" + 0 NTLMSSP Signature null-terminated ASCII "NTLMSSP" (0x4e544c4d53535000) 8 NTLM Message Type long (0x01000000) 12 Flags long @@ -549,13 +549,13 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, /* NTLM type-3 message structure: Index Description Content - 0 NTLMSSP Signature Null-terminated ASCII "NTLMSSP" + 0 NTLMSSP Signature null-terminated ASCII "NTLMSSP" (0x4e544c4d53535000) 8 NTLM Message Type long (0x03000000) 12 LM/LMv2 Response security buffer 20 NTLM/NTLMv2 Response security buffer 28 Target Name security buffer - 36 username security buffer + 36 Username security buffer 44 Workstation Name security buffer (52) Session Key security buffer (*) (60) Flags long (*) diff --git a/Utilities/cmcurl/lib/vauth/ntlm_sspi.c b/Utilities/cmcurl/lib/vauth/ntlm_sspi.c index 2e98e86e5f..85c7256025 100644 --- a/Utilities/cmcurl/lib/vauth/ntlm_sspi.c +++ b/Utilities/cmcurl/lib/vauth/ntlm_sspi.c @@ -46,7 +46,7 @@ bool Curl_auth_is_ntlm_supported(void) /* Query the security package for NTLM */ status = Curl_pSecFn->QuerySecurityPackageInfo( - (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_NTLM)), + CURL_UNCONST(TEXT(SP_NAME_NTLM)), &SecurityPackage); /* Release the package buffer as it is not required anymore */ @@ -95,7 +95,7 @@ CURLcode Curl_auth_create_ntlm_type1_message(struct Curl_easy *data, /* Query the security package for NTLM */ status = Curl_pSecFn->QuerySecurityPackageInfo( - (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_NTLM)), + CURL_UNCONST(TEXT(SP_NAME_NTLM)), &SecurityPackage); if(status != SEC_E_OK) { failf(data, "SSPI: could not get auth info"); @@ -135,7 +135,7 @@ CURLcode Curl_auth_create_ntlm_type1_message(struct Curl_easy *data, /* Acquire our credentials handle */ status = Curl_pSecFn->AcquireCredentialsHandle(NULL, - (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_NTLM)), + CURL_UNCONST(TEXT(SP_NAME_NTLM)), SECPKG_CRED_OUTBOUND, NULL, ntlm->p_identity, NULL, NULL, ntlm->credentials, NULL); @@ -242,6 +242,7 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, SecBufferDesc type_3_desc; SECURITY_STATUS status; unsigned long attrs; + SecPkgContext_Bindings pkgBindings = { 0, NULL }; (void)creds; @@ -253,7 +254,6 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, type_2_bufs[0].pvBuffer = ntlm->input_token; type_2_bufs[0].cbBuffer = curlx_uztoul(ntlm->input_token_len); -#ifdef SECPKG_ATTR_ENDPOINT_BINDINGS /* SSL context comes from schannel. * When extended protection is used in IIS server, * we have to pass a second SecBuffer to the SecBufferDesc @@ -262,9 +262,6 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, * https://learn.microsoft.com/security-updates/SecurityAdvisories/2009/973811 */ if(ntlm->sslContext) { - SEC_CHANNEL_BINDINGS channelBindings; - SecPkgContext_Bindings pkgBindings; - pkgBindings.Bindings = &channelBindings; status = Curl_pSecFn->QueryContextAttributes( ntlm->sslContext, SECPKG_ATTR_ENDPOINT_BINDINGS, @@ -277,7 +274,6 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, type_2_bufs[1].pvBuffer = pkgBindings.Bindings; } } -#endif /* Setup the type-3 "output" security buffer */ type_3_desc.ulVersion = SECBUFFER_VERSION; @@ -296,6 +292,10 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, 0, ntlm->context, &type_3_desc, &attrs, NULL); + + if(pkgBindings.Bindings) + Curl_pSecFn->FreeContextBuffer(pkgBindings.Bindings); + if(status != SEC_E_OK) { infof(data, "NTLM handshake failure (type-3 message): Status=0x%08lx", (unsigned long)status); diff --git a/Utilities/cmcurl/lib/vauth/spnego_gssapi.c b/Utilities/cmcurl/lib/vauth/spnego_gssapi.c index 869a27fdc9..b2bc28fb3f 100644 --- a/Utilities/cmcurl/lib/vauth/spnego_gssapi.c +++ b/Utilities/cmcurl/lib/vauth/spnego_gssapi.c @@ -32,7 +32,7 @@ #include "curl_gssapi.h" #include "curl_trc.h" -#if defined(CURL_HAVE_DIAG) && defined(__APPLE__) +#if defined(CURL_HAVE_DIAG) && defined(__APPLE__) && !defined(HAVE_GSSAPPLE) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" #endif @@ -158,6 +158,57 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, } #endif +#ifdef HAVE_GSS_SET_NEG_MECHS + /* Acquire explicit credentials and restrict SPNEGO sub-mechanisms to + * exclude NTLM. We enumerate all available mechanisms and filter out + * the NTLMSSP OID, matching SSPI's "!ntlm". */ + if(nego->cred == GSS_C_NO_CREDENTIAL) { + /* OID 1.3.6.1.4.1.311.2.2.10 (NTLMSSP) */ + static const gss_OID_desc ntlmssp_oid = { + 10, CURL_UNCONST("\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a") + }; + gss_OID_set available_mechs = GSS_C_NO_OID_SET; + gss_OID_set filtered_mechs = GSS_C_NO_OID_SET; + + /* Acquire default credentials for SPNEGO */ + major_status = Curl_gss_acquire_cred(&minor_status, GSS_C_NO_NAME, + GSS_C_INDEFINITE, GSS_C_NO_OID_SET, + GSS_C_INITIATE, &nego->cred, NULL, NULL); + if(GSS_ERROR(major_status)) { + Curl_gss_log_error(data, "gss_acquire_cred() failed: ", + major_status, minor_status); + curlx_safefree(input_token.value); + return CURLE_AUTH_ERROR; + } + + /* Get all available mechanisms */ + major_status = Curl_gss_indicate_mechs(&minor_status, &available_mechs); + if(!GSS_ERROR(major_status)) { + /* Build a set excluding NTLMSSP */ + major_status = gss_create_empty_oid_set(&minor_status, &filtered_mechs); + if(!GSS_ERROR(major_status)) { + size_t i; + for(i = 0; i < available_mechs->count; i++) { + gss_OID oid = &available_mechs->elements[i]; + if(oid->length != ntlmssp_oid.length || + memcmp(oid->elements, ntlmssp_oid.elements, oid->length)) { + gss_add_oid_set_member(&minor_status, oid, &filtered_mechs); + } + } + /* Restrict SPNEGO to only use non-NTLM mechanisms */ + major_status = Curl_gss_set_neg_mechs(&minor_status, nego->cred, + filtered_mechs); + if(GSS_ERROR(major_status)) { + Curl_gss_log_error(data, "gss_set_neg_mechs() failed: ", + major_status, minor_status); + } + gss_release_oid_set(&minor_status, &filtered_mechs); + } + gss_release_oid_set(&minor_status, &available_mechs); + } + } +#endif /* HAVE_GSS_SET_NEG_MECHS */ + /* Generate our challenge-response message */ major_status = Curl_gss_init_sec_context(data, &minor_status, @@ -168,7 +219,8 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, &input_token, &output_token, TRUE, - NULL); + NULL, + nego->cred); /* Free the decoded challenge as it is not required anymore */ curlx_safefree(input_token.value); @@ -191,6 +243,29 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, return CURLE_AUTH_ERROR; } + /* Check if NTLM was selected and is disallowed */ + if(nego->context != GSS_C_NO_CONTEXT) { + /* OID 1.3.6.1.4.1.311.2.2.10 (NTLMSSP) */ + static const gss_OID_desc ntlmssp_oid = { + 10, CURL_UNCONST("\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a") + }; + OM_uint32 inquire_major, inquire_minor; + gss_OID mech_type = GSS_C_NO_OID; + + inquire_major = Curl_gss_inquire_context(&inquire_minor, + nego->context, + &mech_type); + if(!GSS_ERROR(inquire_major) && mech_type && + mech_type->length == ntlmssp_oid.length && + !memcmp(mech_type->elements, ntlmssp_oid.elements, + ntlmssp_oid.length)) { + infof(data, "SPNEGO chose NTLM, but NTLM is not allowed"); + gss_release_buffer(&unused_status, &output_token); + Curl_auth_cleanup_spnego(nego); + return CURLE_AUTH_ERROR; + } + } + /* Free previous token */ if(nego->output_token.length && nego->output_token.value) gss_release_buffer(&unused_status, &nego->output_token); @@ -280,6 +355,12 @@ void Curl_auth_cleanup_spnego(struct negotiatedata *nego) nego->spn = GSS_C_NO_NAME; } + /* Free our credentials */ + if(nego->cred != GSS_C_NO_CREDENTIAL) { + Curl_gss_release_cred(&minor_status, &nego->cred); + nego->cred = GSS_C_NO_CREDENTIAL; + } + /* Reset any variables */ nego->status = 0; nego->noauthpersist = FALSE; @@ -288,7 +369,7 @@ void Curl_auth_cleanup_spnego(struct negotiatedata *nego) nego->havemultiplerequests = FALSE; } -#if defined(CURL_HAVE_DIAG) && defined(__APPLE__) +#if defined(CURL_HAVE_DIAG) && defined(__APPLE__) && !defined(HAVE_GSSAPPLE) #pragma GCC diagnostic pop #endif diff --git a/Utilities/cmcurl/lib/vauth/spnego_sspi.c b/Utilities/cmcurl/lib/vauth/spnego_sspi.c index b7d82c04dd..4babd4510d 100644 --- a/Utilities/cmcurl/lib/vauth/spnego_sspi.c +++ b/Utilities/cmcurl/lib/vauth/spnego_sspi.c @@ -48,7 +48,7 @@ bool Curl_auth_is_spnego_supported(void) /* Query the security package for Negotiate */ status = Curl_pSecFn->QuerySecurityPackageInfo( - (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_NEGOTIATE)), + CURL_UNCONST(TEXT(SP_NAME_NEGOTIATE)), &SecurityPackage); /* Release the package buffer as it is not required anymore */ @@ -93,6 +93,7 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, SecBufferDesc chlg_desc; SecBufferDesc resp_desc; unsigned long attrs; + SecPkgContext_Bindings pkgBindings = { 0, NULL }; if(nego->context && nego->status == SEC_E_OK) { /* We finished successfully our part of authentication, but server @@ -114,7 +115,7 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, if(!nego->output_token) { /* Query the security package for Negotiate */ nego->status = Curl_pSecFn->QuerySecurityPackageInfo( - (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_NEGOTIATE)), + CURL_UNCONST(TEXT(SP_NAME_NEGOTIATE)), &SecurityPackage); if(nego->status != SEC_E_OK) { failf(data, "SSPI: could not get auth info"); @@ -148,6 +149,21 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, /* Use the current Windows user */ nego->p_identity = NULL; + /* Exclude NTLM from SPNEGO negotiation via the PackageList field */ + if(!nego->p_identity) { + memset(&nego->identity, 0, sizeof(nego->identity)); + nego->identity.Version = SEC_WINNT_AUTH_IDENTITY_VERSION; + nego->identity.Length = sizeof(nego->identity); + nego->identity.Flags = CURL_SEC_WINNT_AUTH_IDENTITY; + nego->p_identity = &nego->identity; + } + + /* Use the special name "!ntlm" to prevent NTLM from being used: + * https://learn.microsoft.com/windows/win32/api/sspi/ns-sspi-sec_winnt_auth_identity_exa + */ + nego->identity.PackageList = CURL_UNCONST(TEXT("!ntlm")); + nego->identity.PackageListLength = 5; + /* Allocate our credentials handle */ nego->credentials = curlx_calloc(1, sizeof(CredHandle)); if(!nego->credentials) @@ -155,7 +171,7 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, /* Acquire our credentials handle */ nego->status = Curl_pSecFn->AcquireCredentialsHandle(NULL, - (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_NEGOTIATE)), + CURL_UNCONST(TEXT(SP_NAME_NEGOTIATE)), SECPKG_CRED_OUTBOUND, NULL, nego->p_identity, NULL, NULL, nego->credentials, NULL); @@ -170,6 +186,10 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, return CURLE_OUT_OF_MEMORY; } + chlg_desc.ulVersion = SECBUFFER_VERSION; + chlg_desc.cBuffers = 0; + chlg_desc.pBuffers = chlg_buf; + if(chlg64 && *chlg64) { /* Decode the base-64 encoded challenge message */ if(*chlg64 != '=') { @@ -185,37 +205,30 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, } /* Setup the challenge "input" security buffer */ - chlg_desc.ulVersion = SECBUFFER_VERSION; chlg_desc.cBuffers = 1; - chlg_desc.pBuffers = &chlg_buf[0]; chlg_buf[0].BufferType = SECBUFFER_TOKEN; chlg_buf[0].pvBuffer = chlg; chlg_buf[0].cbBuffer = curlx_uztoul(chlglen); + } -#ifdef SECPKG_ATTR_ENDPOINT_BINDINGS - /* SSL context comes from Schannel. - * When extended protection is used in IIS server, - * we have to pass a second SecBuffer to the SecBufferDesc - * otherwise IIS does not pass the authentication (401 response). - * Minimum supported version is Windows 7. - * https://learn.microsoft.com/security-updates/SecurityAdvisories/2009/973811 - */ - if(nego->sslContext) { - SEC_CHANNEL_BINDINGS channelBindings; - SecPkgContext_Bindings pkgBindings; - pkgBindings.Bindings = &channelBindings; - nego->status = Curl_pSecFn->QueryContextAttributes( - nego->sslContext, - SECPKG_ATTR_ENDPOINT_BINDINGS, - &pkgBindings); - if(nego->status == SEC_E_OK) { - chlg_desc.cBuffers++; - chlg_buf[1].BufferType = SECBUFFER_CHANNEL_BINDINGS; - chlg_buf[1].cbBuffer = pkgBindings.BindingsLength; - chlg_buf[1].pvBuffer = pkgBindings.Bindings; - } + /* SSL context comes from Schannel. + * When extended protection is used in IIS server, pass its channel + * bindings on the initial call too. HTTP Negotiate can create and send a + * Kerberos token before receiving a challenge from the server. + * Minimum supported version is Windows 7. + * https://learn.microsoft.com/security-updates/SecurityAdvisories/2009/973811 + */ + if(nego->sslContext) { + nego->status = Curl_pSecFn->QueryContextAttributes( + nego->sslContext, + SECPKG_ATTR_ENDPOINT_BINDINGS, + &pkgBindings); + if(nego->status == SEC_E_OK) { + SecBuffer *binding_buf = &chlg_buf[chlg_desc.cBuffers++]; + binding_buf->BufferType = SECBUFFER_CHANNEL_BINDINGS; + binding_buf->cbBuffer = pkgBindings.BindingsLength; + binding_buf->pvBuffer = pkgBindings.Bindings; } -#endif } /* Setup the response "output" security buffer */ @@ -237,11 +250,15 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, nego->spn, sspi_flags, 0, SECURITY_NATIVE_DREP, - chlg ? &chlg_desc : NULL, + chlg_desc.cBuffers ? + &chlg_desc : NULL, 0, nego->context, &resp_desc, &attrs, NULL); } + if(pkgBindings.Bindings) + Curl_pSecFn->FreeContextBuffer(pkgBindings.Bindings); + /* Free the decoded challenge as it is not required anymore */ curlx_free(chlg); diff --git a/Utilities/cmcurl/lib/vauth/vauth.h b/Utilities/cmcurl/lib/vauth/vauth.h index 0f82f92945..bddd55f8d5 100644 --- a/Utilities/cmcurl/lib/vauth/vauth.h +++ b/Utilities/cmcurl/lib/vauth/vauth.h @@ -164,13 +164,11 @@ struct ntlmdata { /* The sslContext is used for the Schannel bindings. The * api is available on the Windows 7 SDK and later. */ -#ifdef SECPKG_ATTR_ENDPOINT_BINDINGS CtxtHandle *sslContext; -#endif CredHandle *credentials; CtxtHandle *context; - SEC_WINNT_AUTH_IDENTITY identity; - SEC_WINNT_AUTH_IDENTITY *p_identity; + SEC_WINNT_AUTH_IDENTITY_EX identity; + SEC_WINNT_AUTH_IDENTITY_EX *p_identity; size_t token_max; BYTE *output_token; BYTE *input_token; @@ -236,8 +234,8 @@ struct kerberos5data { CredHandle *credentials; CtxtHandle *context; TCHAR *spn; - SEC_WINNT_AUTH_IDENTITY identity; - SEC_WINNT_AUTH_IDENTITY *p_identity; + SEC_WINNT_AUTH_IDENTITY_EX identity; + SEC_WINNT_AUTH_IDENTITY_EX *p_identity; size_t token_max; BYTE *output_token; #else @@ -291,20 +289,19 @@ struct negotiatedata { OM_uint32 status; gss_ctx_id_t context; gss_name_t spn; + gss_cred_id_t cred; gss_buffer_desc output_token; #ifdef GSS_C_CHANNEL_BOUND_FLAG struct dynbuf channel_binding_data; #endif #else #ifdef USE_WINDOWS_SSPI -#ifdef SECPKG_ATTR_ENDPOINT_BINDINGS CtxtHandle *sslContext; -#endif SECURITY_STATUS status; CredHandle *credentials; CtxtHandle *context; - SEC_WINNT_AUTH_IDENTITY identity; - SEC_WINNT_AUTH_IDENTITY *p_identity; + SEC_WINNT_AUTH_IDENTITY_EX identity; + SEC_WINNT_AUTH_IDENTITY_EX *p_identity; TCHAR *spn; size_t token_max; BYTE *output_token; diff --git a/Utilities/cmcurl/lib/asyn-ares.c b/Utilities/cmcurl/lib/vdns/asyn-ares.c similarity index 82% rename from Utilities/cmcurl/lib/asyn-ares.c rename to Utilities/cmcurl/lib/vdns/asyn-ares.c index d17a6038e2..dfb0875995 100644 --- a/Utilities/cmcurl/lib/asyn-ares.c +++ b/Utilities/cmcurl/lib/vdns/asyn-ares.c @@ -49,7 +49,6 @@ #include "cfilters.h" #include "curl_addrinfo.h" #include "curl_trc.h" -#include "hostip.h" #include "url.h" #include "multiif.h" #include "curlx/inet_pton.h" @@ -57,16 +56,17 @@ #include "select.h" #include "progress.h" #include "curlx/timediff.h" -#include "httpsrr.h" +#include "vdns/hostip.h" +#include "vdns/httpsrr.h" #include #if ARES_VERSION < 0x011000 -#error "requires c-ares 1.16.0 or newer" +#error "c-ares 1.16.0 or greater required" #endif #ifdef USE_HTTPSRR #if ARES_VERSION < 0x011c00 -#error "requires c-ares 1.28.0 or newer for HTTPSRR" +#error "c-ares 1.28.0 or greater required for HTTPSRR" #endif #define HTTPSRR_WORKS #endif @@ -148,7 +148,7 @@ static CURLcode async_ares_init(struct Curl_easy *data, if c-ares >= 1.24.0, user can set the timeout via /etc/resolv.conf to overwrite c-ares' timeout. - */ + */ DEBUGASSERT(ares_ver); if(ares_ver < 0x011400) { options.timeout = CARES_TIMEOUT_PER_ATTEMPT; @@ -188,25 +188,6 @@ out: return result; } -/* - * async_ares_cleanup() cleans up async resolver data. - */ -static void async_ares_cleanup(struct Curl_resolv_async *async) -{ - struct async_ares_ctx *ares = &async->ares; - if(ares->res_A) { - Curl_freeaddrinfo(ares->res_A); - ares->res_A = NULL; - } - if(ares->res_AAAA) { - Curl_freeaddrinfo(ares->res_AAAA); - ares->res_AAAA = NULL; - } -#ifdef USE_HTTPSRR - Curl_httpsrr_cleanup(&ares->hinfo); -#endif -} - void Curl_async_ares_shutdown(struct Curl_easy *data, struct Curl_resolv_async *async) { @@ -227,7 +208,6 @@ void Curl_async_ares_destroy(struct Curl_easy *data, ares_destroy(ares->channel); ares->channel = NULL; } - async_ares_cleanup(async); } CURLcode Curl_async_pollset(struct Curl_easy *data, @@ -259,6 +239,7 @@ CURLcode Curl_async_take_result(struct Curl_easy *data, struct Curl_dns_entry **pdns) { struct async_ares_ctx *ares = &async->ares; + struct Curl_dns_entry *dns = NULL; CURLcode result = CURLE_OK; DEBUGASSERT(pdns); @@ -279,47 +260,52 @@ CURLcode Curl_async_take_result(struct Curl_easy *data, /* all c-ares operations done, what is the result to report? */ result = ares->result; if(ares->ares_status == ARES_SUCCESS && !result) { - struct Curl_dns_entry *dns = - Curl_dnscache_mk_entry2(data, async->dns_queries, - &ares->res_AAAA, &ares->res_A, - async->hostname, async->port); - if(!dns) { - result = CURLE_OUT_OF_MEMORY; - goto out; + + if(CURL_DNSQ_IS_ADDR(async->dns_queries)) { + dns = Curl_dnsc_mk_addr2(data, async->dns_queries, + &async->ai_AAAA, &async->ai_A, + async->peer); + if(!dns) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } } + #ifdef HTTPSRR_WORKS - if(async->dns_queries & CURL_DNSQ_HTTPS) { - if(ares->hinfo.complete) { - struct Curl_https_rrinfo *lhrr = Curl_httpsrr_dup_move(&ares->hinfo); - if(!lhrr) - result = CURLE_OUT_OF_MEMORY; - else - Curl_dns_entry_set_https_rr(dns, lhrr); + if(!dns && (async->dns_queries & CURL_DNSQ_HTTPS)) { + dns = Curl_dnsc_mk_https(data, &async->httpsrr, async->peer); + if(!dns) { + result = CURLE_OUT_OF_MEMORY; + goto out; } - else - Curl_dns_entry_set_https_rr(dns, NULL); } #endif - if(!result) { - *pdns = dns; - } } /* if we have not found anything, report the proper * CURLE_COULDNT_RESOLVE_* code */ - if(!result && !*pdns) { + if(!result && !dns) { const char *msg = NULL; + /* only an authoritative "does not exist" answer from every query + may be cached as a negative entry, not transient failures like + timeouts or server troubles */ + async->negative_answer = !ares->transient_err && + ((ares->ares_status == ARES_ENOTFOUND) || + (ares->ares_status == ARES_ENODATA)); if(ares->ares_status != ARES_SUCCESS) msg = ares_strerror(ares->ares_status); result = Curl_async_failed(data, async, msg); } - CURL_TRC_DNS(data, "ares: is_resolved() result=%d, dns=%sfound", - (int)result, *pdns ? "" : "not "); - async_ares_cleanup(async); + CURL_TRC_DNS(data, "[%s] ares_take_result, result=%d, ares_result=%d, " + "ares_status=%d, dns=%sfound", + Curl_resolv_query_str(async->dns_queries), + (int)result, (int)ares->result, ares->ares_status, + dns ? "" : "not "); out: if(result != CURLE_AGAIN) ares->result = result; + *pdns = result ? NULL : dns; return result; } @@ -349,67 +335,6 @@ static timediff_t async_ares_poll_timeout(struct async_ares_ctx *ares, return 1000; } -static const struct Curl_addrinfo *async_ares_get_ai( - const struct Curl_addrinfo *ai, - int ai_family, - unsigned int index) -{ - unsigned int i = 0; - for(i = 0; ai; ai = ai->ai_next) { - if(ai->ai_family == ai_family) { - if(i == index) - return ai; - ++i; - } - } - return NULL; -} - -const struct Curl_addrinfo *Curl_async_get_ai(struct Curl_easy *data, - struct Curl_resolv_async *async, - int ai_family, - unsigned int index) -{ - struct async_ares_ctx *ares = &async->ares; - - (void)data; - switch(ai_family) { - case AF_INET: - if(ares->res_A) - return async_ares_get_ai(ares->res_A, ai_family, index); - break; - case AF_INET6: - if(ares->res_AAAA) - return async_ares_get_ai(ares->res_AAAA, ai_family, index); - break; - default: - break; - } - return NULL; -} - -#ifdef USE_HTTPSRR -const struct Curl_https_rrinfo *Curl_async_get_https( - struct Curl_easy *data, - struct Curl_resolv_async *async) -{ - if(Curl_async_knows_https(data, async)) - return &async->ares.hinfo; - return NULL; -} - -bool Curl_async_knows_https(struct Curl_easy *data, - struct Curl_resolv_async *async) -{ - (void)data; - if(async->dns_queries & CURL_DNSQ_HTTPS) - return ((async->dns_responses & CURL_DNSQ_HTTPS) || - !async->queries_ongoing); - return TRUE; /* we know it will never come */ -} - -#endif /* USE_HTTPSRR */ - /* * Curl_async_await() * @@ -566,11 +491,15 @@ static void async_ares_A_cb(void *user_data, int status, int timeouts, async->done = !async->queries_ongoing; if(status == ARES_SUCCESS) { ares->ares_status = ARES_SUCCESS; - ares->res_A = async_ares_node2addr(ares_ai->nodes); + async->ai_A = async_ares_node2addr(ares_ai->nodes); ares_freeaddrinfo(ares_ai); } - else if(ares->ares_status != ARES_SUCCESS) /* do not overwrite success */ - ares->ares_status = status; + else { + if((status != ARES_ENOTFOUND) && (status != ARES_ENODATA)) + ares->transient_err = TRUE; + if(ares->ares_status != ARES_SUCCESS) /* do not overwrite success */ + ares->ares_status = status; + } } #ifdef CURLRES_IPV6 @@ -589,11 +518,15 @@ static void async_ares_AAAA_cb(void *user_data, int status, int timeouts, async->done = !async->queries_ongoing; if(status == ARES_SUCCESS) { ares->ares_status = ARES_SUCCESS; - ares->res_AAAA = async_ares_node2addr(ares_ai->nodes); + async->ai_AAAA = async_ares_node2addr(ares_ai->nodes); ares_freeaddrinfo(ares_ai); } - else if(ares->ares_status != ARES_SUCCESS) /* do not overwrite success */ - ares->ares_status = status; + else { + if((status != ARES_ENOTFOUND) && (status != ARES_ENODATA)) + ares->transient_err = TRUE; + if(ares->ares_status != ARES_SUCCESS) /* do not overwrite success */ + ares->ares_status = status; + } } #endif /* CURLRES_IPV6 */ @@ -612,9 +545,12 @@ static void async_ares_rr_done(void *user_data, ares_status_t status, async->dns_responses |= CURL_DNSQ_HTTPS; async->queries_ongoing--; async->done = !async->queries_ongoing; + ares->ares_status = status; + if((status != ARES_ENOTFOUND) && (status != ARES_ENODATA)) + ares->transient_err = TRUE; if((ARES_SUCCESS != status) || !dnsrec) return; - ares->result = Curl_httpsrr_from_ares(dnsrec, &ares->hinfo); + ares->result = Curl_httpsrr_from_ares(dnsrec, &async->httpsrr); } #endif /* USE_HTTPSRR */ @@ -653,7 +589,7 @@ CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, } #endif - curl_msnprintf(service, sizeof(service), "%d", async->port); + curl_msnprintf(service, sizeof(service), "%d", async->peer->port); socktype = (Curl_conn_get_transport(data, data->conn) == TRNSPRT_TCP) ? SOCK_STREAM : SOCK_DGRAM; @@ -663,12 +599,13 @@ CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, struct ares_addrinfo_hints hints; memset(&hints, 0, sizeof(hints)); - CURL_TRC_DNS(data, "ares: query AAAA records for %s", async->hostname); + CURL_TRC_DNS(data, "[AAAA] ares: query records for %s", + async->peer->hostname); hints.ai_family = PF_INET6; hints.ai_socktype = socktype; hints.ai_flags = ARES_AI_NUMERICSERV; async->queries_ongoing++; - ares_getaddrinfo(ares->channel, async->hostname, + ares_getaddrinfo(ares->channel, async->peer->hostname, service, &hints, async_ares_AAAA_cb, async); } #endif /* CURLRES_IPV6 */ @@ -677,32 +614,33 @@ CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, struct ares_addrinfo_hints hints; memset(&hints, 0, sizeof(hints)); - CURL_TRC_DNS(data, "ares: query A records for %s", async->hostname); + CURL_TRC_DNS(data, "[A] ares: query records for %s", + async->peer->hostname); hints.ai_family = PF_INET; hints.ai_socktype = socktype; hints.ai_flags = ARES_AI_NUMERICSERV; async->queries_ongoing++; - ares_getaddrinfo(ares->channel, async->hostname, + ares_getaddrinfo(ares->channel, async->peer->hostname, service, &hints, async_ares_A_cb, async); } #ifdef USE_HTTPSRR - memset(&ares->hinfo, 0, sizeof(ares->hinfo)); if(async->dns_queries & CURL_DNSQ_HTTPS) { - char *rrname = NULL; - if(async->port != 443) { - rrname = curl_maprintf("_%d._https.%s", async->port, async->hostname); - if(!rrname) + char *https_name = NULL; + if(async->peer->port != 443) { + https_name = curl_maprintf("_%u._https.%s", + async->peer->port, async->peer->hostname); + if(!https_name) return CURLE_OUT_OF_MEMORY; } - CURL_TRC_DNS(data, "ares: query HTTPS records for %s", - rrname ? rrname : async->hostname); - ares->hinfo.rrname = rrname; + CURL_TRC_DNS(data, "[HTTPS] ares: query records for %s", + https_name ? https_name : async->peer->hostname); async->queries_ongoing++; ares_query_dnsrec(ares->channel, - rrname ? rrname : async->hostname, + https_name ? https_name : async->peer->hostname, ARES_CLASS_IN, ARES_REC_TYPE_HTTPS, async_ares_rr_done, async, NULL); + curlx_free(https_name); } #endif /* USE_HTTPSRR */ @@ -722,7 +660,7 @@ static CURLcode async_ares_set_dns_servers(struct Curl_easy *data, { struct async_ares_ctx *ares = async ? &async->ares : NULL; CURLcode result = CURLE_NOT_BUILT_IN; - const char *servers = data->set.str[STRING_DNS_SERVERS]; + const char *servers = CURL_EASY_STR(data, STRING_DNS_SERVERS); int ares_result = ARES_SUCCESS; #ifdef DEBUGBUILD @@ -758,7 +696,7 @@ static CURLcode async_ares_set_dns_interface(struct Curl_easy *data, struct Curl_resolv_async *async) { struct async_ares_ctx *ares = async ? &async->ares : NULL; - const char *interf = data->set.str[STRING_DNS_INTERFACE]; + const char *interf = CURL_EASY_STR(data, STRING_DNS_INTERFACE); if(!interf) interf = ""; @@ -775,7 +713,7 @@ static CURLcode async_ares_set_dns_local_ip4(struct Curl_easy *data, { struct async_ares_ctx *ares = async ? &async->ares : NULL; struct in_addr a4; - const char *local_ip4 = data->set.str[STRING_DNS_LOCAL_IP4]; + const char *local_ip4 = CURL_EASY_STR(data, STRING_DNS_LOCAL_IP4); if(!local_ip4 || (local_ip4[0] == 0)) { a4.s_addr = 0; /* disabled: do not bind to a specific address */ @@ -800,7 +738,7 @@ static CURLcode async_ares_set_dns_local_ip6(struct Curl_easy *data, #ifdef USE_IPV6 struct async_ares_ctx *ares = async ? &async->ares : NULL; unsigned char a6[INET6_ADDRSTRLEN]; - const char *local_ip6 = data->set.str[STRING_DNS_LOCAL_IP6]; + const char *local_ip6 = CURL_EASY_STR(data, STRING_DNS_LOCAL_IP6); if(!local_ip6 || (local_ip6[0] == 0)) { /* disabled: do not bind to a specific address */ diff --git a/Utilities/cmcurl/lib/asyn-base.c b/Utilities/cmcurl/lib/vdns/asyn-base.c similarity index 92% rename from Utilities/cmcurl/lib/asyn-base.c rename to Utilities/cmcurl/lib/vdns/asyn-base.c index 62cb0effe4..f3db691c8c 100644 --- a/Utilities/cmcurl/lib/asyn-base.c +++ b/Utilities/cmcurl/lib/vdns/asyn-base.c @@ -43,12 +43,14 @@ #include "urldata.h" #include "connect.h" +#include "curl_addrinfo.h" #include "curl_trc.h" -#include "hostip.h" #include "multiif.h" #include "progress.h" #include "select.h" #include "url.h" +#include "vdns/hostip.h" +#include "vdns/httpsrr.h" /*********************************************************************** * Only for builds using asynchronous name resolves @@ -69,7 +71,7 @@ timediff_t Curl_async_timeleft_ms(struct Curl_easy *data, #ifdef USE_ARES #if ARES_VERSION < 0x011000 -#error "requires c-ares 1.16.0 or newer" +#error "c-ares 1.16.0 or greater required" #endif /* @@ -238,6 +240,14 @@ void Curl_async_destroy(struct Curl_easy *data, #ifndef CURL_DISABLE_DOH Curl_doh_cleanup(data, async); #endif + if(async->ai_A) + Curl_freeaddrinfo(async->ai_A); + if(async->ai_AAAA) + Curl_freeaddrinfo(async->ai_AAAA); +#ifdef USE_HTTPSRR + Curl_httpsrr_destroy(async->httpsrr); +#endif + Curl_peer_unlink(&async->peer); curlx_safefree(async); } } @@ -256,9 +266,10 @@ CURLcode Curl_async_failed(struct Curl_easy *data, } #endif - failf(data, "Could not resolve %s: %s%s%s%s", - host_or_proxy, async->hostname, - detail ? " (" : "", detail ? detail : "", detail ? ")" : ""); + if(async->dns_queries & (CURL_DNSQ_A | CURL_DNSQ_AAAA)) + failf(data, "Could not resolve %s: %s%s%s%s", + host_or_proxy, async->peer->hostname, + detail ? " (" : "", detail ? detail : "", detail ? ")" : ""); return result; } diff --git a/Utilities/cmcurl/lib/asyn-thrdd.c b/Utilities/cmcurl/lib/vdns/asyn-thrdd.c similarity index 69% rename from Utilities/cmcurl/lib/asyn-thrdd.c rename to Utilities/cmcurl/lib/vdns/asyn-thrdd.c index aeadec11a4..1ca217a79e 100644 --- a/Utilities/cmcurl/lib/asyn-thrdd.c +++ b/Utilities/cmcurl/lib/vdns/asyn-thrdd.c @@ -52,8 +52,6 @@ #include "cfilters.h" #include "curl_addrinfo.h" #include "curl_trc.h" -#include "hostip.h" -#include "httpsrr.h" #include "url.h" #include "multiif.h" #include "curl_threads.h" @@ -61,6 +59,8 @@ #include "rand.h" #include "select.h" #include "thrdqueue.h" +#include "vdns/hostip.h" +#include "vdns/httpsrr.h" #include "curlx/strparse.h" #include "curlx/wait.h" @@ -117,9 +117,11 @@ struct async_thrdd_item { uint16_t port; uint8_t transport; uint8_t dns_queries; + BIT(negative); /* resolver answered that the name does not exist */ #ifdef DEBUGBUILD uint32_t delay_ms; uint32_t delay_fail_ms; + BIT(dbg_negative); #endif char hostname[1]; }; @@ -182,6 +184,8 @@ static struct async_thrdd_item *async_thrdd_item_create( item->delay_fail_ms = (uint32_t)l + c; } } + if(getenv("CURL_DBG_RESOLV_FAIL_NEGATIVE")) + item->dbg_negative = TRUE; } #endif @@ -205,49 +209,54 @@ static void async_thrdd_rr_done(void *user_data, ares_status_t status, async->queries_ongoing--; async->done = !async->queries_ongoing; if((ARES_SUCCESS == status) && dnsrec) - async->result = Curl_httpsrr_from_ares(dnsrec, &thrdd->rr.hinfo); + async->result = Curl_httpsrr_from_ares(dnsrec, &async->httpsrr); } static CURLcode async_rr_start(struct Curl_easy *data, struct Curl_resolv_async *async) { struct async_thrdd_ctx *thrdd = &async->thrdd; + char *https_name = NULL; int status; - char *rrname = NULL; + CURLcode result = CURLE_OK; DEBUGASSERT(!thrdd->rr.channel); - if(async->port != 443) { - rrname = curl_maprintf("_%d_.https.%s", async->port, async->hostname); - if(!rrname) - return CURLE_OUT_OF_MEMORY; + if(async->peer->port != 443) { + https_name = curl_maprintf("_%u._https.%s", + async->peer->port, async->peer->hostname); + if(!https_name) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } } status = ares_init_options(&thrdd->rr.channel, NULL, 0); if(status != ARES_SUCCESS) { thrdd->rr.channel = NULL; - curlx_free(rrname); - return CURLE_FAILED_INIT; + result = CURLE_FAILED_INIT; + goto out; } #ifdef DEBUGBUILD if(getenv("CURL_DNS_SERVER")) { const char *servers = getenv("CURL_DNS_SERVER"); status = ares_set_servers_ports_csv(thrdd->rr.channel, servers); if(status) { - curlx_free(rrname); - return CURLE_FAILED_INIT; + result = CURLE_FAILED_INIT; + goto out; } } #endif - memset(&thrdd->rr.hinfo, 0, sizeof(thrdd->rr.hinfo)); - thrdd->rr.hinfo.rrname = rrname; async->queries_ongoing++; ares_query_dnsrec(thrdd->rr.channel, - rrname ? rrname : async->hostname, ARES_CLASS_IN, - ARES_REC_TYPE_HTTPS, + https_name ? https_name : async->peer->hostname, + ARES_CLASS_IN, ARES_REC_TYPE_HTTPS, async_thrdd_rr_done, async, NULL); - CURL_TRC_DNS(data, "[HTTPS-RR] initiated request for %s", - rrname ? rrname : async->hostname); - return CURLE_OK; + CURL_TRC_DNS(data, "[HTTPS] query records for %s", + https_name ? https_name : async->peer->hostname); + +out: + curlx_free(https_name); + return result; } #endif @@ -287,7 +296,6 @@ void Curl_async_thrdd_destroy(struct Curl_easy *data, ares_destroy(async->thrdd.rr.channel); async->thrdd.rr.channel = NULL; } - Curl_httpsrr_cleanup(&async->thrdd.rr.hinfo); #endif async_thrdd_item_destroy(async->thrdd.res_A); async->thrdd.res_A = NULL; @@ -305,32 +313,56 @@ CURLcode Curl_async_await(struct Curl_easy *data, uint32_t resolv_id, struct Curl_resolv_async *async = Curl_async_get(data, resolv_id); struct async_thrdd_ctx *thrdd = async ? &async->thrdd : NULL; timediff_t milli, ms; + CURLcode result = CURLE_AGAIN; if(!thrdd) return CURLE_FAILED_INIT; - while(async->queries_ongoing && !async->done) { - Curl_async_thrdd_multi_process(data->multi); - if(async->done) - break; + while(result == CURLE_AGAIN) { + while(async->queries_ongoing && !async->done) { + Curl_async_thrdd_multi_process(data->multi); + if(async->done) + break; - ms = curlx_ptimediff_ms(Curl_pgrs_now(data), &async->start); - if(ms < 3) - milli = 0; - else if(ms <= 50) - milli = ms / 3; - else if(ms <= 250) - milli = 50; - else - milli = 200; - CURL_TRC_DNS(data, "await, waiting %" FMT_TIMEDIFF_T "ms", milli); - curlx_wait_ms(milli); + ms = curlx_ptimediff_ms(Curl_pgrs_now(data), &async->start); + if(ms < 3) + milli = 0; + else if(ms <= 50) + milli = ms / 3; + else if(ms <= 250) + milli = 50; + else + milli = 200; + CURL_TRC_DNS(data, "await, waiting %" FMT_TIMEDIFF_T "ms", milli); + curlx_wait_ms(milli); + } + result = Curl_async_take_result(data, async, pdns); } - return Curl_async_take_result(data, async, pdns); + return result; } #ifdef HAVE_GETADDRINFO +/* Was the getaddrinfo() failure an authoritative negative answer, + i.e. the resolver responded that the name (or its data) does not + exist? Transient failures like EAI_AGAIN and local troubles must + not count as negative answers. */ +static bool gai_negative(int rc) +{ + switch(rc) { +#ifdef EAI_NONAME + case EAI_NONAME: +#endif +#if defined(EAI_NODATA) && \ + (!defined(EAI_NONAME) || (EAI_NODATA != EAI_NONAME)) + case EAI_NODATA: +#endif + return TRUE; + default: + return FALSE; + } +} + /* Process the item, using Curl_getaddrinfo_ex() */ static void async_thrdd_item_process(void *arg) { @@ -346,6 +378,7 @@ static void async_thrdd_item_process(void *arg) } if(item->delay_fail_ms) { curlx_wait_ms(item->delay_fail_ms); + item->negative = item->dbg_negative; return; } #endif @@ -375,6 +408,7 @@ static void async_thrdd_item_process(void *arg) item->sockerr = SOCKERRNO ? SOCKERRNO : rc; if(item->sockerr == 0) item->sockerr = RESOLVER_ENOMEM; + item->negative = gai_negative(rc); } else { Curl_addrinfo_set_port(item->res, item->port); @@ -394,6 +428,7 @@ static void async_thrdd_item_process(void *arg) } if(item->delay_fail_ms) { curlx_wait_ms(item->delay_fail_ms); + item->negative = item->dbg_negative; return; } #endif @@ -402,12 +437,15 @@ static void async_thrdd_item_process(void *arg) item->sockerr = SOCKERRNO; if(item->sockerr == 0) item->sockerr = RESOLVER_ENOMEM; + /* this resolver cannot tell a transient failure from an + authoritative negative answer, treat it as before */ + item->negative = TRUE; } } #endif /* HAVE_GETADDRINFO */ -#ifdef ENABLE_WAKEUP +#ifdef ENABLE_INTERNAL_WAKEUP static void async_thrdd_event(const struct curl_thrdq *tqueue, Curl_thrdq_event ev, void *user_data) @@ -416,7 +454,7 @@ static void async_thrdd_event(const struct curl_thrdq *tqueue, (void)tqueue; switch(ev) { case CURL_THRDQ_EV_ITEM_DONE: - (void)curl_multi_wakeup(multi); + Curl_multi_wakeup_internal(multi); break; default: break; @@ -439,7 +477,7 @@ CURLcode Curl_async_thrdd_multi_init(struct Curl_multi *multi, { CURLcode result; DEBUGASSERT(!multi->resolv_thrdq); - result = Curl_thrdq_create(&multi->resolv_thrdq, "DNS", 0, + result = Curl_thrdq_create(&multi->resolv_thrdq, "DNS", min_threads, max_threads, idle_time_ms, async_thrdd_item_free, async_thrdd_item_process, @@ -533,28 +571,23 @@ void Curl_async_thrdd_multi_process(struct Curl_multi *multi) if(async) { struct async_thrdd_item **pdest = &async->thrdd.res_A; - async->dns_responses |= item->dns_queries; - --async->queries_ongoing; - async->done = !async->queries_ongoing; - #ifdef CURLRES_IPV6 if(item->dns_queries & CURL_DNSQ_AAAA) pdest = &async->thrdd.res_AAAA; #endif if(!*pdest) { - VERBOSE(async_thrdd_report_item(data, item)); *pdest = item; item = NULL; } else DEBUGASSERT(0); /* should not receive duplicates here */ + + --async->queries_ongoing; Curl_multi_mark_dirty(data); } async_thrdd_item_free(item); } -#ifdef CURLVERBOSE - Curl_thrdq_trace(multi->resolv_thrdq, multi->admin); -#endif + VERBOSE(Curl_thrdq_trace(multi->resolv_thrdq, multi->admin)); } CURLcode Curl_async_thrdd_multi_set_props(struct Curl_multi *multi, @@ -562,7 +595,7 @@ CURLcode Curl_async_thrdd_multi_set_props(struct Curl_multi *multi, uint32_t max_threads, uint32_t idle_time_ms) { - return Curl_thrdq_set_props(multi->resolv_thrdq, 0, + return Curl_thrdq_set_props(multi->resolv_thrdq, min_threads, max_threads, idle_time_ms); } @@ -574,7 +607,7 @@ static CURLcode async_thrdd_query(struct Curl_easy *data, CURLcode result; item = async_thrdd_item_create(data, async->id, dns_queries, - async->hostname, async->port, + async->peer->hostname, async->peer->port, async->transport); if(!item) { result = CURLE_OUT_OF_MEMORY; @@ -618,7 +651,7 @@ CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, return result; #ifdef CURLRES_IPV6 - /* Do not start an AAAA query for an ipv4 address when + /* Do not start an AAAA query for an IPv4 address when * we will start an A query for it. */ if((async->dns_queries & CURL_DNSQ_AAAA) && !(async->is_ipv4addr && (async->dns_queries & CURL_DNSQ_A))) { @@ -638,9 +671,12 @@ CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, #endif out: + if(!async->queries_ongoing) + async->done = TRUE; + if(result) CURL_TRC_DNS(data, "error queueing query %s:%d -> %d", - async->hostname, async->port, (int)result); + async->peer->hostname, async->peer->port, (int)result); return result; } @@ -663,9 +699,10 @@ CURLcode Curl_async_pollset(struct Curl_easy *data, #endif if(!async->done) { -#ifndef ENABLE_WAKEUP + const struct curltime *pnow = Curl_pgrs_now(data); +#ifndef ENABLE_INTERNAL_WAKEUP timediff_t stutter_ms, elapsed_ms; - elapsed_ms = curlx_ptimediff_ms(Curl_pgrs_now(data), &async->start); + elapsed_ms = curlx_ptimediff_ms(pnow, &async->start); if(elapsed_ms < 3) stutter_ms = 1; else if(elapsed_ms <= 50) @@ -675,12 +712,128 @@ CURLcode Curl_async_pollset(struct Curl_easy *data, else stutter_ms = 200; timeout_ms = CURLMIN(stutter_ms, timeout_ms); +#else + if(async->queries_ongoing && + !Curl_thrdq_check_started(data->multi->resolv_thrdq)) { + /* The queue has items but starting a worker thread to process + them just failed again; expire soon to check once more, + instead of sleeping on the full resolve timeout. */ + CURL_TRC_DNS(data, "resolver thread start failed again, retrying"); + timeout_ms = CURLMIN(100, timeout_ms); + } #endif - Curl_expire(data, timeout_ms, EXPIRE_ASYNC_NAME); + Curl_expire_set(data, EXPIRE_ASYNC_NAME, timeout_ms, pnow); } return CURLE_OK; } +#if defined(USE_IPV6) && defined(HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID) +static bool async_thrdd_item_missing_scope(struct Curl_easy *data, + struct async_thrdd_item *item) +{ + const struct Curl_addrinfo *ai; + + /* scope id already globally set */ + if(data->conn && data->conn->scope_id) + return FALSE; + + for(ai = item->res; ai; ai = ai->ai_next) { + if(ai->ai_family == AF_INET6) { + struct sockaddr_in6 *sa6 = (void *)ai->ai_addr; + if(IN6_IS_ADDR_LINKLOCAL(&sa6->sin6_addr) && !sa6->sin6_scope_id) + return TRUE; + } + } + return FALSE; +} + +static void async_thrdd_item_strip_results(struct async_thrdd_item *item, + int ai_family) +{ + struct Curl_addrinfo *ai = item->res, **panchor = &item->res; + while(ai) { + if(ai->ai_family == ai_family) { + *panchor = ai->ai_next; + ai->ai_next = NULL; + Curl_freeaddrinfo(ai); + ai = *panchor; + } + else { + panchor = &ai->ai_next; + ai = ai->ai_next; + } + } +} + +#endif /* USE_IPV6 && HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID */ + +static CURLcode async_thrdd_check_done(struct Curl_easy *data, + struct Curl_resolv_async *async) +{ + struct async_thrdd_ctx *thrdd = &async->thrdd; + + (void)data; + if(thrdd->res_A && !thrdd->processed_A) { + VERBOSE(async_thrdd_report_item(data, thrdd->res_A)); + /* move addrinfos to the async result member */ + async->dns_responses |= thrdd->res_A->dns_queries; + async->ai_A = thrdd->res_A->res; + thrdd->res_A->res = NULL; + thrdd->processed_A = TRUE; + } + + if(thrdd->res_AAAA && !thrdd->processed_AAAA) { +#if defined(USE_IPV6) && defined(HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID) + /* do we accept the incoming AAAA response? */ + if(!(thrdd->res_AAAA->dns_queries & CURL_DNSQ_A) && + async_thrdd_item_missing_scope(data, thrdd->res_AAAA)) { + /* We queried "only" AF_INET6. This may be problematic when the + * result has ipv6 link-local addresses and did not give + * any scope id for it. glibc has a long outstanding bug + * + * that gives scope ids only on AF_UNSPEC queries. */ + struct async_thrdd_item *item = thrdd->res_AAAA; + CURLcode result; + + /* Reuse the item and queue it again, this time with added + * CURL_DNSQ_A which resolves using AF_UNSPEC. */ + thrdd->res_AAAA = NULL; + item->dns_queries |= CURL_DNSQ_A; + if(item->res) { + Curl_freeaddrinfo(item->res); + item->res = NULL; + } + + CURL_TRC_DNS(data, "re-queueing query %s for AF_UNSPEC resolve", + item->description); + result = Curl_thrdq_send(data->multi->resolv_thrdq, item, + async_item_description(item), + async->timeout_ms); + if(result) { + async_thrdd_item_free(item); + return result; + } + async->queries_ongoing++; + return CURLE_AGAIN; + } + /* accepting the AAAA result, strip it of any AF_INET entries, + * as we might have resolved it with AF_UNSPEC. */ + async_thrdd_item_strip_results(thrdd->res_AAAA, AF_INET); +#endif /* USE_IPV6 && HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID */ + VERBOSE(async_thrdd_report_item(data, thrdd->res_AAAA)); + /* move addrinfos to the async result member */ + async->dns_responses |= thrdd->res_AAAA->dns_queries; + async->ai_AAAA = thrdd->res_AAAA->res; + thrdd->res_AAAA->res = NULL; + thrdd->processed_AAAA = TRUE; + } + + if(async->queries_ongoing) + return CURLE_AGAIN; + async->done = TRUE; + return CURLE_OK; +} + /* * Curl_async_take_result() is called repeatedly to check if a previous * name resolve request has completed. It should also make sure to time-out if @@ -696,56 +849,63 @@ CURLcode Curl_async_take_result(struct Curl_easy *data, DEBUGASSERT(pdns); *pdns = NULL; - if(!async->queries_ongoing && !async->done) { - DEBUGASSERT(0); - return CURLE_FAILED_INIT; - } #ifdef USE_HTTPSRR_ARES /* best effort, ignore errors */ if(thrdd->rr.channel) (void)Curl_ares_perform(thrdd->rr.channel, 0); #endif -#ifndef ENABLE_WAKEUP +#ifndef ENABLE_INTERNAL_WAKEUP Curl_async_thrdd_multi_process(data->multi); #endif - if(!async->done) - return CURLE_AGAIN; + result = async_thrdd_check_done(data, async); + if(result) + return result; + + Curl_expire_clear(data, EXPIRE_ASYNC_NAME); + + /* A failure is an authoritative negative answer, eligible for + negative caching, only when every A/AAAA query performed came + back answering that the name does not exist. A query that + failed transiently or never returned is not an answer. */ + { + const uint8_t ip_queries = + async->dns_queries & (CURL_DNSQ_A | CURL_DNSQ_AAAA); + bool negative = (async->dns_responses & ip_queries) == ip_queries; + if(thrdd->res_A && (async->ai_A || !thrdd->res_A->negative)) + negative = FALSE; + if(thrdd->res_AAAA && (async->ai_AAAA || !thrdd->res_AAAA->negative)) + negative = FALSE; + if(!thrdd->res_A && !thrdd->res_AAAA) + negative = FALSE; + async->negative_answer = negative; + } - Curl_expire_done(data, EXPIRE_ASYNC_NAME); if(async->result) { result = async->result; goto out; } - if((thrdd->res_A && thrdd->res_A->res) || - (thrdd->res_AAAA && thrdd->res_AAAA->res)) { - dns = Curl_dnscache_mk_entry2( - data, async->dns_queries, - thrdd->res_A ? &thrdd->res_A->res : NULL, - thrdd->res_AAAA ? &thrdd->res_AAAA->res : NULL, - async->hostname, async->port); + if(async->ai_A || async->ai_AAAA) { + dns = Curl_dnsc_mk_addr2( + data, async->dns_queries, &async->ai_A, &async->ai_AAAA, async->peer); if(!dns) { result = CURLE_OUT_OF_MEMORY; goto out; } + } #ifdef USE_HTTPSRR_ARES - if(thrdd->rr.channel) { - struct Curl_https_rrinfo *lhrr = NULL; - if(thrdd->rr.hinfo.complete) { - lhrr = Curl_httpsrr_dup_move(&thrdd->rr.hinfo); - if(!lhrr) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - } - Curl_httpsrr_trace(data, lhrr); - Curl_dns_entry_set_https_rr(dns, lhrr); + if(!dns && thrdd->rr.channel) { + Curl_httpsrr_trace(data, async->httpsrr); + dns = Curl_dnsc_mk_https(data, &async->httpsrr, async->peer); + if(!dns) { + result = CURLE_OUT_OF_MEMORY; + goto out; } -#endif } +#endif if(dns) { *pdns = dns; @@ -764,75 +924,9 @@ out: (result != CURLE_COULDNT_RESOLVE_HOST) && (result != CURLE_COULDNT_RESOLVE_PROXY)) { CURL_TRC_DNS(data, "Error %d resolving %s:%d", - (int)result, async->hostname, async->port); + (int)result, async->peer->hostname, async->peer->port); } return result; } -static const struct Curl_addrinfo *async_thrdd_get_ai( - const struct Curl_addrinfo *ai, - int ai_family, unsigned int index) -{ - unsigned int i = 0; - for(i = 0; ai; ai = ai->ai_next) { - if(ai->ai_family == ai_family) { - if(i == index) - return ai; - ++i; - } - } - return NULL; -} - -const struct Curl_addrinfo *Curl_async_get_ai(struct Curl_easy *data, - struct Curl_resolv_async *async, - int ai_family, - unsigned int index) -{ - struct async_thrdd_ctx *thrdd = &async->thrdd; - - (void)data; - switch(ai_family) { - case AF_INET: - if(thrdd->res_A) - return async_thrdd_get_ai(thrdd->res_A->res, ai_family, index); - break; -#ifdef USE_IPV6 - case AF_INET6: - if(thrdd->res_AAAA) - return async_thrdd_get_ai(thrdd->res_AAAA->res, ai_family, index); - break; -#endif - default: - break; - } - return NULL; -} - -#ifdef USE_HTTPSRR -const struct Curl_https_rrinfo *Curl_async_get_https( - struct Curl_easy *data, - struct Curl_resolv_async *async) -{ -#ifdef USE_HTTPSRR_ARES - if(Curl_async_knows_https(data, async)) - return &async->thrdd.rr.hinfo; -#else - (void)data; - (void)async; -#endif - return NULL; -} - -bool Curl_async_knows_https(struct Curl_easy *data, - struct Curl_resolv_async *async) -{ - (void)data; - if(async->dns_queries & CURL_DNSQ_HTTPS) - return ((async->dns_responses & CURL_DNSQ_HTTPS) || async->done); - return TRUE; /* we know it will never come */ -} - -#endif /* USE_HTTPSRR */ - #endif /* USE_RESOLV_THREADED */ diff --git a/Utilities/cmcurl/lib/asyn.h b/Utilities/cmcurl/lib/vdns/asyn.h similarity index 90% rename from Utilities/cmcurl/lib/asyn.h rename to Utilities/cmcurl/lib/vdns/asyn.h index ed50933654..7eade656a5 100644 --- a/Utilities/cmcurl/lib/asyn.h +++ b/Utilities/cmcurl/lib/vdns/asyn.h @@ -26,7 +26,7 @@ #include "curl_setup.h" #if defined(USE_HTTPSRR) && defined(USE_ARES) -#include "httpsrr.h" +#include "vdns/httpsrr.h" #endif struct Curl_easy; @@ -82,15 +82,7 @@ void Curl_async_global_cleanup(void); CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, struct Curl_resolv_async *async); -const struct Curl_addrinfo *Curl_async_get_ai(struct Curl_easy *data, - struct Curl_resolv_async *async, - int ai_family, - unsigned int index); - #ifdef USE_HTTPSRR -const struct Curl_https_rrinfo *Curl_async_get_https( - struct Curl_easy *data, - struct Curl_resolv_async *async); bool Curl_async_knows_https(struct Curl_easy *data, struct Curl_resolv_async *async); #endif /* USE_HTTPSRR */ @@ -114,14 +106,10 @@ int Curl_ares_perform(ares_channel channel, timediff_t timeout_ms); /* async resolving implementation using c-ares alone */ struct async_ares_ctx { ares_channel channel; - struct Curl_addrinfo *res_A; - struct Curl_addrinfo *res_AAAA; int ares_status; /* ARES_SUCCESS, ARES_ENOTFOUND, etc. */ CURLcode result; /* CURLE_OK or error handling response */ - struct curltime happy_eyeballs_dns_time; /* when this timer started, or 0 */ -#ifdef USE_HTTPSRR - struct Curl_https_rrinfo hinfo; -#endif + BIT(transient_err); /* an A/AAAA query failed without the resolver + answering that the name does not exist */ }; void Curl_async_ares_shutdown(struct Curl_easy *data, @@ -137,14 +125,15 @@ struct async_thrdd_item; /* Context for threaded resolver */ struct async_thrdd_ctx { - struct async_thrdd_item *res_A; /* ipv4 result */ - struct async_thrdd_item *res_AAAA; /* ipv6 result */ + struct async_thrdd_item *res_A; /* IPv4 final result */ + struct async_thrdd_item *res_AAAA; /* IPv6 final result */ #if defined(USE_HTTPSRR) && defined(USE_ARES) struct { ares_channel channel; - struct Curl_https_rrinfo hinfo; } rr; #endif + BIT(processed_A); + BIT(processed_AAAA); }; void Curl_async_thrdd_shutdown(struct Curl_easy *data, @@ -210,11 +199,9 @@ CURLcode Curl_async_pollset(struct Curl_easy *data, /* convert these functions if an asynch resolver is not used */ #define Curl_async_global_init() CURLE_OK #define Curl_async_global_cleanup() Curl_nop_stmt -#define Curl_async_get_ai(a, b, c, d) NULL #define Curl_async_await(a, b, c) CURLE_COULDNT_RESOLVE_HOST #define Curl_async_take_result(x, y, z) CURLE_COULDNT_RESOLVE_HOST #define Curl_async_pollset(x, y, z) CURLE_OK -#define Curl_async_get_https(x, y) NULL #define Curl_async_knows_https(x, y) TRUE #endif /* !CURLRES_ASYNCH */ @@ -226,6 +213,12 @@ CURLcode Curl_async_pollset(struct Curl_easy *data, struct Curl_resolv_async { struct Curl_resolv_async *next; + struct Curl_peer *peer; + struct Curl_addrinfo *ai_A; + struct Curl_addrinfo *ai_AAAA; +#ifdef USE_HTTPSRR + struct Curl_https_rrinfo *httpsrr; +#endif #ifdef USE_RESOLV_ARES struct async_ares_ctx ares; #elif defined(USE_RESOLV_THREADED) @@ -240,8 +233,6 @@ struct Curl_resolv_async { CURLcode result; uint32_t poll_interval; uint32_t id; /* unique id per easy handle of the resolve operation */ - /* what is being resolved */ - uint16_t port; uint8_t dns_queries; /* what queries are being performed */ uint8_t dns_responses; /* what queries had responses so far. */ uint8_t transport; @@ -251,7 +242,10 @@ struct Curl_resolv_async { BIT(for_proxy); BIT(done); BIT(shutdown); - char hostname[1]; + BIT(negative_answer); /* resolver answered that the name does not + exist. Only such failures may be cached as + negative entries, not transient or local + resolver failures. */ }; timediff_t Curl_async_timeleft_ms(struct Curl_easy *data, diff --git a/Utilities/cmcurl/lib/cf-dns.c b/Utilities/cmcurl/lib/vdns/cf-dns.c similarity index 53% rename from Utilities/cmcurl/lib/cf-dns.c rename to Utilities/cmcurl/lib/vdns/cf-dns.c index 631c987be2..8a08912d93 100644 --- a/Utilities/cmcurl/lib/cf-dns.c +++ b/Utilities/cmcurl/lib/vdns/cf-dns.c @@ -27,24 +27,29 @@ #include "curl_addrinfo.h" #include "cfilters.h" #include "connect.h" -#include "dnscache.h" -#include "httpsrr.h" #include "curl_trc.h" +#include "multiif.h" #include "progress.h" #include "url.h" -#include "cf-dns.h" +#include "vdns/cf-dns.h" +#include "vdns/dnscache.h" +#include "vdns/httpsrr.h" +#include "curlx/strparse.h" +/* Max time to wait for AAAA before connecting sub-filters, e.g. + * letting cf-ip-happy.c do its work. */ +#define CURL_HE_AAAA_AWAIT_MS 25 struct cf_dns_ctx { struct Curl_dns_entry *dns; struct Curl_peer *peer; CURLcode resolv_result; + timediff_t he_aaaa_await_ms; uint32_t resolv_id; uint8_t dns_queries; uint8_t transport; BIT(started); BIT(announced); - BIT(complete_resolve); BIT(for_proxy); }; @@ -52,8 +57,7 @@ static struct cf_dns_ctx *cf_dns_ctx_create(struct Curl_easy *data, struct Curl_peer *peer, uint8_t dns_queries, uint8_t transport, - bool for_proxy, - bool complete_resolve) + bool for_proxy) { struct cf_dns_ctx *ctx; @@ -65,10 +69,22 @@ static struct cf_dns_ctx *cf_dns_ctx_create(struct Curl_easy *data, ctx->dns_queries = dns_queries; ctx->transport = transport; ctx->for_proxy = for_proxy; - ctx->complete_resolve = complete_resolve; + ctx->he_aaaa_await_ms = CURL_HE_AAAA_AWAIT_MS; +#ifdef DEBUGBUILD + { + const char *p = getenv("CURL_DBG_HE_AAAA_AWAIT_MS"); + if(p) { + curl_off_t l; + if(!curlx_str_number(&p, &l, UINT32_MAX)) { + ctx->he_aaaa_await_ms = (uint32_t)l; + } + } + } +#endif - CURL_TRC_DNS(data, "created DNS filter for %s:%u, transport=%x, queries=%x", - peer->hostname, peer->port, ctx->transport, ctx->dns_queries); + CURL_TRC_DNS(data, "[%s] created DNS filter for %s:%u, transport=%x", + Curl_resolv_query_str(ctx->dns_queries), + peer->hostname, peer->port, ctx->transport); return ctx; } @@ -131,22 +147,28 @@ static void cf_dns_report(struct Curl_cfilter *cf, } else { curlx_dyn_init(&tmp, 1024); - infof(data, "Host %s:%u was resolved.", dns->hostname, dns->port); + if(CURL_DNSQ_IS_ADDR(ctx->dns_queries)) { + infof(data, "Host %s:%u was resolved.", dns->hostname, dns->port); #ifdef CURLRES_IPV6 - cf_dns_report_addr(data, &tmp, "IPv6: ", AF_INET6, dns->addr); + cf_dns_report_addr(data, &tmp, "IPv6: ", AF_INET6, dns->addr); #endif - cf_dns_report_addr(data, &tmp, "IPv4: ", AF_INET, dns->addr); + cf_dns_report_addr(data, &tmp, "IPv4: ", AF_INET, dns->addr); + } #ifdef USE_HTTPSRR - if(!dns->hinfo) - infof(data, "HTTPS-RR: -"); - else if(!Curl_httpsrr_applicable(data, dns->hinfo)) - infof(data, "HTTPS-RR: not applicable"); - else { - CURLcode result = Curl_httpsrr_print(&tmp, dns->hinfo); - if(!result) - infof(data, "HTTPS-RR: %s", curlx_dyn_ptr(&tmp)); - else - infof(data, "Error printing HTTPS-RR information"); + else if(ctx->dns_queries & CURL_DNSQ_HTTPS) { + if(!dns->hinfo) + infof(data, "HTTPS-RR %s:%u: -", dns->hostname, dns->port); + else if(!Curl_httpsrr_applicable(data, dns->hinfo)) + infof(data, "HTTPS-RR %s:%u: not applicable", + dns->hostname, dns->port); + else { + CURLcode result = Curl_httpsrr_print(&tmp, dns->hinfo); + if(!result) + infof(data, "HTTPS-RR %s:%u: %s", + dns->hostname, dns->port, curlx_dyn_ptr(&tmp)); + else + infof(data, "Error printing HTTPS-RR information"); + } } #endif curlx_dyn_free(&tmp); @@ -165,25 +187,40 @@ static CURLcode cf_dns_start(struct Curl_cfilter *cf, { struct cf_dns_ctx *ctx = cf->ctx; timediff_t timeout_ms = Curl_timeleft_ms(data); - CURLcode result; + CURLcode result = CURLE_OK; *pdns = NULL; - CURL_TRC_CF(data, cf, "cf_dns_start %s %s:%u", + CURL_TRC_CF(data, cf, "[%s] cf_dns_start %s %s:%u", + Curl_resolv_query_str(ctx->dns_queries), ctx->peer->unix_socket ? "unix-domain-socket" : "host", ctx->peer->hostname, ctx->peer->port); if(ctx->peer->unix_socket) - ctx->dns_queries = 0; - else if(Curl_is_ipv4addr(ctx->peer->hostname)) - ctx->dns_queries |= CURL_DNSQ_A; + ctx->dns_queries = CURL_DNSQ_A; /* treat it like an A resolve */ + + if(CURL_DNSQ_IS_ADDR(ctx->dns_queries)) { + if(Curl_is_ipv4addr(ctx->peer->hostname)) + ctx->dns_queries |= CURL_DNSQ_A; #ifdef USE_IPV6 - else if(ctx->peer->ipv6) - ctx->dns_queries |= CURL_DNSQ_AAAA; + else if(ctx->peer->ipv6) + ctx->dns_queries |= CURL_DNSQ_AAAA; #endif - result = Curl_resolv(data, ctx->peer, ctx->dns_queries, ctx->transport, - (bool)ctx->for_proxy, timeout_ms, - &ctx->resolv_id, pdns); + result = Curl_resolv(data, ctx->peer, ctx->dns_queries, ctx->transport, + (bool)ctx->for_proxy, timeout_ms, + &ctx->resolv_id, pdns); + } +#ifdef USE_HTTPSRR + else if(ctx->dns_queries == CURL_DNSQ_HTTPS) { + result = Curl_resolv_https(data, ctx->peer, (bool)ctx->for_proxy, + timeout_ms, &ctx->resolv_id, pdns); + } +#endif + else { + failf(data, "unsupported DNS queries %x", ctx->dns_queries); + return CURLE_FAILED_INIT; + } + DEBUGASSERT(!result || !*pdns); if(!result) { /* resolved right away, either sync or from dnscache */ DEBUGASSERT(*pdns); @@ -195,19 +232,17 @@ static CURLcode cf_dns_start(struct Curl_cfilter *cf, else if(result == CURLE_OPERATION_TIMEDOUT) { /* took too long */ failf(data, "Failed to resolve '%s' with timeout after %" FMT_TIMEDIFF_T " ms", ctx->peer->hostname, - curlx_ptimediff_ms(Curl_pgrs_now(data), - &data->progress.t_startsingle)); + Curl_pgrs_since_ms(data, NULL, TIMER_STARTSINGLE)); return CURLE_OPERATION_TIMEDOUT; } else { DEBUGASSERT(result); - failf(data, "Could not resolve: %s", ctx->peer->hostname); + if(ctx->dns_queries & (CURL_DNSQ_A | CURL_DNSQ_AAAA)) + failf(data, "Could not resolve: %s", ctx->peer->hostname); return result; } } -#define CURL_HEV3_RESOLVE_DELAY_MS 50 - static bool cf_dns_ready_to_connect(struct Curl_cfilter *cf, struct Curl_easy *data) { @@ -218,21 +253,29 @@ static bool cf_dns_ready_to_connect(struct Curl_cfilter *cf, else if(ctx->dns) return TRUE; #ifdef USE_CURL_ASYNC - else { - /* We want AAAA answer as we prefer ipv6. If a sub-filter desires - * HTTPS-RR, we check for that query as well. */ - uint8_t wanted_answers = CURL_DNSQ_AAAA; - if(Curl_conn_cf_wants_httpsrr(cf, data)) - wanted_answers |= CURL_DNSQ_HTTPS; - - /* Note: if a query was never started, it is considered to have + else if(CURL_DNSQ_IS_ADDR(ctx->dns_queries)) { + const struct curltime *pnow; + timediff_t remain_ms; + /* For Happy Eyeballing, we can start on either A or AAAA resolves, + * but AAAA is preferred. We enforce a small delay for missing + * AAAA to arrive, then we let the connect continue. + * Note: if AAAA was never started (-4), it is considered to have * an answer (e.g. a negative one). */ - if(Curl_resolv_has_answers(data, ctx->resolv_id, wanted_answers)) + if(Curl_resolv_has_answers(data, ctx->resolv_id, CURL_DNSQ_AAAA)) return TRUE; - /* If the wanted answers are not available after a delay, - * we let the connect attempts start anyway. */ - return Curl_resolv_elapsed_ms(data, ctx->resolv_id) >= - CURL_HEV3_RESOLVE_DELAY_MS; + pnow = Curl_pgrs_now(data); + remain_ms = ctx->he_aaaa_await_ms - + Curl_resolv_elapsed_ms(data, ctx->resolv_id, pnow); + if(remain_ms <= 0) + return TRUE; + CURL_TRC_CF(data, cf, "[%s] still waiting %" FMT_TIMEDIFF_T + "ms for AAAA result", + Curl_resolv_query_str(ctx->dns_queries), remain_ms); + Curl_expire_set(data, EXPIRE_HAPPY_EYEBALLS, remain_ms, pnow); + return FALSE; + } + else { + return TRUE; } #else (void)data; @@ -246,6 +289,7 @@ static CURLcode cf_dns_connect(struct Curl_cfilter *cf, bool *done) { struct cf_dns_ctx *ctx = cf->ctx; + bool ip_query = (ctx->dns_queries & (CURL_DNSQ_A | CURL_DNSQ_AAAA)); if(cf->connected) { *done = TRUE; @@ -263,20 +307,24 @@ static CURLcode cf_dns_connect(struct Curl_cfilter *cf, Curl_resolv_take_result(data, ctx->resolv_id, &ctx->dns); } - if(ctx->resolv_result) { - CURL_TRC_CF(data, cf, "error resolving: %d", (int)ctx->resolv_result); + if(ctx->resolv_result && ip_query) { + /* failing A|AAAA resolves is a hard failure. */ + CURL_TRC_CF(data, cf, "[%s] error resolving: %d", + Curl_resolv_query_str(ctx->dns_queries), + (int)ctx->resolv_result); return ctx->resolv_result; } if(ctx->dns && !ctx->announced) { ctx->announced = TRUE; - if(cf->sockindex == FIRSTSOCKET) { + if((cf->sockindex == FIRSTSOCKET) && ip_query) { cf->conn->bits.dns_resolved = TRUE; Curl_pgrsTime(data, TIMER_NAMELOOKUP); } cf_dns_report(cf, data, ctx->dns); } + /* Delay connection sub-filters when we are still waiting for AAAA */ if(!cf_dns_ready_to_connect(cf, data)) { return CURLE_OK; } @@ -289,13 +337,9 @@ static CURLcode cf_dns_connect(struct Curl_cfilter *cf, DEBUGASSERT(sub_done); } - /* sub filter chain is connected */ + /* sub filter chain is connected, this means the filter has done + * its work and is connected as well, if it has results or not. */ CURL_TRC_CF(data, cf, "connected filter chain below"); - if(ctx->complete_resolve && !ctx->dns && !ctx->resolv_result) { - /* This filter only connects when it has resolved everything. */ - CURL_TRC_CF(data, cf, "delay connect until resolve complete"); - return CURLE_OK; - } *done = TRUE; cf->connected = TRUE; Curl_resolv_destroy(data, ctx->resolv_id); @@ -315,8 +359,24 @@ static CURLcode cf_dns_adjust_pollset(struct Curl_cfilter *cf, struct easy_pollset *ps) { #ifdef USE_CURL_ASYNC - if(!cf->connected) + if(!cf->connected) { + /* The pollset only works when we have started the resolving. + * Otherwise we might wait on the WAKEUP socketpair forever. + * Since DNS filters can be added in the middle of a connect + * attempt, they are not always started that way. */ + struct cf_dns_ctx *ctx = cf->ctx; + if(!ctx->started) { + CURL_TRC_CF(data, cf, "adjust_pollset, starting %s:%u queries=%s", + ctx->peer->hostname, ctx->peer->port, + Curl_resolv_query_str(ctx->dns_queries)); + ctx->started = TRUE; + ctx->resolv_result = cf_dns_start(cf, data, &ctx->dns); + if(ctx->resolv_result || ctx->dns) { + Curl_multi_mark_dirty(data); + } + } return Curl_resolv_pollset(data, ps); + } #else (void)cf; (void)data; @@ -351,7 +411,7 @@ static CURLcode cf_dns_cntrl(struct Curl_cfilter *cf, struct Curl_cftype Curl_cft_dns = { "DNS", - CF_TYPE_SETUP, + CF_TYPE_SETUP | CF_TYPE_DNS, CURL_LOG_LVL_NONE, cf_dns_destroy, cf_dns_connect, @@ -371,16 +431,14 @@ static CURLcode cf_dns_create(struct Curl_cfilter **pcf, struct Curl_peer *peer, uint8_t dns_queries, uint8_t transport, - bool for_proxy, - bool complete_resolve) + bool for_proxy) { struct Curl_cfilter *cf = NULL; struct cf_dns_ctx *ctx; CURLcode result = CURLE_OK; (void)data; - ctx = cf_dns_ctx_create(data, peer, dns_queries, transport, - for_proxy, complete_resolve); + ctx = cf_dns_ctx_create(data, peer, dns_queries, transport, for_proxy); if(!ctx) { result = CURLE_OUT_OF_MEMORY; goto out; @@ -397,12 +455,12 @@ out: /* Adds a "resolv" filter at the top of the connection's filter chain. * The filter will resolve the peer on the first connect attempt. */ -CURLcode Curl_cf_dns_add(struct Curl_easy *data, - struct connectdata *conn, - int sockindex, - struct Curl_peer *peer, - uint8_t dns_queries, - uint8_t transport) +static CURLcode cf_dns_add(struct Curl_easy *data, + struct connectdata *conn, + int8_t sockindex, + struct Curl_peer *peer, + uint8_t dns_queries, + uint8_t transport) { struct Curl_cfilter *cf = NULL; bool for_proxy = FALSE; @@ -415,8 +473,7 @@ CURLcode Curl_cf_dns_add(struct Curl_easy *data, (peer == conn->http_proxy.peer); #endif - result = cf_dns_create(&cf, data, peer, dns_queries, transport, - for_proxy, FALSE); + result = cf_dns_create(&cf, data, peer, dns_queries, transport, for_proxy); if(result) goto out; Curl_conn_cf_add(data, conn, sockindex, cf); @@ -430,18 +487,16 @@ out: * See socks.c on how this is used to make a non-blocking DNS * resolve during connect. */ -CURLcode Curl_cf_dns_insert_after(struct Curl_cfilter *cf_at, - struct Curl_easy *data, - uint8_t dns_queries, - struct Curl_peer *peer, - uint8_t transport, - bool complete_resolve) +static CURLcode cf_dns_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data, + struct Curl_peer *peer, + uint8_t dns_queries, + uint8_t transport) { struct Curl_cfilter *cf; CURLcode result; - result = cf_dns_create(&cf, data, peer, dns_queries, transport, - FALSE, complete_resolve); + result = cf_dns_create(&cf, data, peer, dns_queries, transport, FALSE); if(result) return result; @@ -449,89 +504,93 @@ CURLcode Curl_cf_dns_insert_after(struct Curl_cfilter *cf_at, return CURLE_OK; } -/* Return the resolv result from the first "resolv" filter, starting - * the given filter `cf` downwards. - */ -static CURLcode cf_dns_result(struct Curl_cfilter *cf, - struct Curl_peer *peer) +static CURLcode cf_dns_add_resolve(struct Curl_easy *data, + struct connectdata *conn, + int8_t sockindex, + struct Curl_peer *peer, + uint8_t dns_queries, + uint8_t transport) { - for(; cf; cf = cf->next) { + struct Curl_cfilter *cf = data->conn->cfilter[sockindex]; + struct Curl_cfilter *cf_dns = NULL; + bool is_addr = CURL_DNSQ_IS_ADDR(dns_queries); + + if((dns_queries & CURL_DNSQ_HTTPS) && + Curl_is_ipaddr(peer->hostname)) { + dns_queries = (uint8_t)(dns_queries & ~CURL_DNSQ_HTTPS); + } + + for(; cf && dns_queries; cf = cf->next) { if(cf->cft == &Curl_cft_dns) { struct cf_dns_ctx *ctx = cf->ctx; + cf_dns = cf; if(Curl_peer_same_destination(ctx->peer, peer)) { - if(ctx->dns || ctx->resolv_result) - return ctx->resolv_result; - return CURLE_AGAIN; + /* subtract queries already being scheduled/ongoing */ + dns_queries = (uint8_t)(~ctx->dns_queries & dns_queries); + if(!dns_queries) { /* already there */ + return CURLE_OK; + } + else if(is_addr && !ctx->started && + CURL_DNSQ_IS_ADDR(ctx->dns_queries)) { + ctx->dns_queries |= dns_queries; + CURL_TRC_DNS(data, "[%s] added queries=%s for %s:%u", + Curl_resolv_query_str(ctx->dns_queries), + Curl_resolv_query_str(dns_queries), + peer->hostname, peer->port); + return CURLE_OK; + } } - return CURLE_OK; /* ok, but no results */ } } - return CURLE_FAILED_INIT; + + if(dns_queries) { + /* No existing filter is handling these, add a new DNS filter + * (after the last one if there was one already. FCFS. */ + if(cf_dns) + return cf_dns_insert_after(cf_dns, data, peer, dns_queries, transport); + else + return cf_dns_add(data, conn, sockindex, peer, dns_queries, transport); + } + return CURLE_OK; } -/* Return the result of the DNS resolution for peer. Searches for a "resolv" - * filter from the top of the filter chain down. Returns +CURLcode Curl_conn_dns_add_addr_resolve(struct Curl_easy *data, + struct connectdata *conn, + int8_t sockindex, + struct Curl_peer *peer, + uint8_t dns_queries, + uint8_t transport) +{ + /* should only have address query bits */ + DEBUGASSERT(!(dns_queries & ~CURL_DNSQ_ADDR)); + return cf_dns_add_resolve(data, conn, sockindex, peer, + (dns_queries & CURL_DNSQ_ADDR), transport); +} + +/* Return the result of the DNS address resolution for peer. + * Searches for a DNS filter from the top of the filter chain down. Returns * - CURLE_AGAIN when not done yet * - CURLE_OK when DNS was successfully resolved * - CURLR_FAILED_INIT when no resolv filter was found * - error returned by the DNS resolv */ -CURLcode Curl_conn_dns_result(struct connectdata *conn, - int sockindex, - struct Curl_peer *peer) +CURLcode Curl_conn_dns_addr_result(struct connectdata *conn, + int8_t sockindex, + struct Curl_peer *peer) { - return cf_dns_result(conn->cfilter[sockindex], peer); -} - -static const struct Curl_addrinfo *cf_dns_get_nth_ai( - struct Curl_cfilter *cf, - const struct Curl_addrinfo *ai, - int ai_family, unsigned int index) -{ - struct cf_dns_ctx *ctx = cf->ctx; - unsigned int i = 0; - - if((ai_family == AF_INET) && !(ctx->dns_queries & CURL_DNSQ_A)) - return NULL; -#ifdef USE_IPV6 - if((ai_family == AF_INET6) && !(ctx->dns_queries & CURL_DNSQ_AAAA)) - return NULL; -#endif - for(i = 0; ai; ai = ai->ai_next) { - if(ai->ai_family == ai_family) { - if(i == index) - return ai; - ++i; - } - } - return NULL; -} - -/* Return the addrinfo at `index` for the given `family` from the - * first "resolve" filter underneath `cf`. If the DNS resolving is - * not done yet or if no address for the family exists, returns NULL. - */ -const struct Curl_addrinfo *Curl_cf_dns_get_ai(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct Curl_peer *peer, - int ai_family, - unsigned int index) -{ - (void)data; + struct Curl_cfilter *cf = conn->cfilter[sockindex]; for(; cf; cf = cf->next) { if(cf->cft == &Curl_cft_dns) { struct cf_dns_ctx *ctx = cf->ctx; - if(Curl_peer_same_destination(ctx->peer, peer)) { - if(ctx->resolv_result) - return NULL; - else if(ctx->dns) - return cf_dns_get_nth_ai(cf, ctx->dns->addr, ai_family, index); - else - return Curl_resolv_get_ai(data, ctx->resolv_id, ai_family, index); + if(Curl_peer_same_destination(ctx->peer, peer) && + (ctx->dns_queries & (CURL_DNSQ_A | CURL_DNSQ_AAAA))) { + if(ctx->dns || ctx->resolv_result) + return ctx->resolv_result; + return CURLE_AGAIN; } } } - return NULL; + return CURLE_FAILED_INIT; /* no one is resolving */ } /* Return the addrinfo at `index` for the given `family` from the @@ -540,30 +599,65 @@ const struct Curl_addrinfo *Curl_cf_dns_get_ai(struct Curl_cfilter *cf, */ const struct Curl_addrinfo *Curl_conn_dns_get_ai(struct Curl_easy *data, struct Curl_peer *peer, - int sockindex, + int8_t sockindex, int ai_family, unsigned int index) { struct connectdata *conn = data->conn; - return Curl_cf_dns_get_ai(conn->cfilter[sockindex], data, peer, - ai_family, index); + struct Curl_cfilter *cf = conn->cfilter[sockindex]; + + for(; cf; cf = cf->next) { + if(cf->cft == &Curl_cft_dns) { + struct cf_dns_ctx *ctx = cf->ctx; + if(Curl_peer_same_destination(ctx->peer, peer) && + (ctx->dns_queries & (CURL_DNSQ_A | CURL_DNSQ_AAAA))) { + CURL_TRC_CF(data, cf, "get %uth result for %s:%u, family=%d, dns=%d", + index, peer->hostname, peer->port, ai_family, !!ctx->dns); + if(ctx->resolv_result) + return NULL; + else if(ctx->dns) { + /* A cached DNS entry may contain address families that we + * here never queried for. We want to give no results for those. */ + if((ai_family == AF_INET) && !(ctx->dns_queries & CURL_DNSQ_A)) + return NULL; +#ifdef USE_IPV6 + if((ai_family == AF_INET6) && !(ctx->dns_queries & CURL_DNSQ_AAAA)) + return NULL; +#endif + return Curl_addrinfo_get(ctx->dns->addr, ai_family, index); + } + else + return Curl_resolv_get_ai(data, ctx->resolv_id, ai_family, index); + } + } + } + return NULL; } #ifdef USE_HTTPSRR +CURLcode Curl_conn_dns_add_https_resolve(struct Curl_easy *data, + struct connectdata *conn, + int8_t sockindex, + struct Curl_peer *peer) +{ + return cf_dns_add_resolve(data, conn, sockindex, peer, + CURL_DNSQ_HTTPS, conn->transport_wanted); +} + /* Return the HTTPS-RR info from the first "resolve" filter at the * connection. If the DNS resolving is not done yet or if there * is no HTTPS-RR info, returns NULL. */ -const struct Curl_https_rrinfo * -Curl_conn_dns_get_https(struct Curl_easy *data, - int sockindex, - struct Curl_peer *peer) +const struct Curl_https_rrinfo *Curl_conn_dns_get_https(struct Curl_easy *data, + int8_t sockindex, + struct Curl_peer *peer) { struct Curl_cfilter *cf = data->conn->cfilter[sockindex]; for(; cf; cf = cf->next) { if(cf->cft == &Curl_cft_dns) { struct cf_dns_ctx *ctx = cf->ctx; - if(Curl_peer_same_destination(ctx->peer, peer)) { + if(Curl_peer_same_destination(ctx->peer, peer) && + (ctx->dns_queries & CURL_DNSQ_HTTPS)) { if(ctx->dns) return ctx->dns->hinfo; else @@ -575,14 +669,15 @@ Curl_conn_dns_get_https(struct Curl_easy *data, } bool Curl_conn_dns_resolved_https(struct Curl_easy *data, - int sockindex, + int8_t sockindex, struct Curl_peer *peer) { struct Curl_cfilter *cf = data->conn->cfilter[sockindex]; for(; cf; cf = cf->next) { if(cf->cft == &Curl_cft_dns) { struct cf_dns_ctx *ctx = cf->ctx; - if(Curl_peer_same_destination(ctx->peer, peer)) { + if(Curl_peer_same_destination(ctx->peer, peer) && + (ctx->dns_queries & CURL_DNSQ_HTTPS)) { if(ctx->dns) return TRUE; else diff --git a/Utilities/cmcurl/lib/cf-dns.h b/Utilities/cmcurl/lib/vdns/cf-dns.h similarity index 60% rename from Utilities/cmcurl/lib/cf-dns.h rename to Utilities/cmcurl/lib/vdns/cf-dns.h index 891b1efea6..6226d2161a 100644 --- a/Utilities/cmcurl/lib/cf-dns.h +++ b/Utilities/cmcurl/lib/vdns/cf-dns.h @@ -31,43 +31,35 @@ struct Curl_dns_entry; struct Curl_addrinfo; struct Curl_peer; -CURLcode Curl_cf_dns_add(struct Curl_easy *data, - struct connectdata *conn, - int sockindex, - struct Curl_peer *peer, - uint8_t dns_queries, - uint8_t transport); +CURLcode Curl_conn_dns_add_addr_resolve(struct Curl_easy *data, + struct connectdata *conn, + int8_t sockindex, + struct Curl_peer *peer, + uint8_t dns_queries, + uint8_t transport); -CURLcode Curl_cf_dns_insert_after(struct Curl_cfilter *cf_at, - struct Curl_easy *data, - uint8_t dns_queries, - struct Curl_peer *peer, - uint8_t transport, - bool complete_resolve); - -CURLcode Curl_conn_dns_result(struct connectdata *conn, - int sockindex, - struct Curl_peer *peer); +CURLcode Curl_conn_dns_addr_result(struct connectdata *conn, + int8_t sockindex, + struct Curl_peer *peer); const struct Curl_addrinfo *Curl_conn_dns_get_ai(struct Curl_easy *data, struct Curl_peer *peer, - int sockindex, + int8_t sockindex, int ai_family, unsigned int index); -const struct Curl_addrinfo *Curl_cf_dns_get_ai(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct Curl_peer *peer, - int ai_family, - unsigned int index); - #ifdef USE_HTTPSRR +CURLcode Curl_conn_dns_add_https_resolve(struct Curl_easy *data, + struct connectdata *conn, + int8_t sockindex, + struct Curl_peer *peer); + const struct Curl_https_rrinfo * Curl_conn_dns_get_https(struct Curl_easy *data, - int sockindex, + int8_t sockindex, struct Curl_peer *peer); bool Curl_conn_dns_resolved_https(struct Curl_easy *data, - int sockindex, + int8_t sockindex, struct Curl_peer *peer); #else #define Curl_conn_dns_get_https(a, b, c) NULL diff --git a/Utilities/cmcurl/lib/dnscache.c b/Utilities/cmcurl/lib/vdns/dnscache.c similarity index 66% rename from Utilities/cmcurl/lib/dnscache.c rename to Utilities/cmcurl/lib/vdns/dnscache.c index 9c8341e56f..bfd935ed47 100644 --- a/Utilities/cmcurl/lib/dnscache.c +++ b/Utilities/cmcurl/lib/vdns/dnscache.c @@ -44,13 +44,13 @@ #include "curl_addrinfo.h" #include "curl_share.h" #include "curl_trc.h" -#include "dnscache.h" #include "hash.h" -#include "hostip.h" -#include "httpsrr.h" #include "progress.h" #include "rand.h" #include "strcase.h" +#include "vdns/dnscache.h" +#include "vdns/hostip.h" +#include "vdns/httpsrr.h" #include "curlx/inet_ntop.h" #include "curlx/inet_pton.h" #include "curlx/strcopy.h" @@ -60,33 +60,57 @@ #define MAX_DNS_CACHE_SIZE 29999 -static void dnscache_entry_free(struct Curl_dns_entry *dns) +struct dnsc_id { + struct Curl_str name; + uint16_t port; + char type; +}; + +static void dnsc_peer2id(struct dnsc_id *pid, char type, + struct Curl_peer *peer) { - Curl_freeaddrinfo(dns->addr); -#ifdef USE_HTTPSRR - if(dns->hinfo) { - Curl_httpsrr_cleanup(dns->hinfo); - curlx_free(dns->hinfo); - } -#endif - curlx_free(dns); + curlx_str_assign(&pid->name, peer->hostname, strlen(peer->hostname)); + pid->port = peer->port; + pid->type = type; } +static void dnsc_str2id(struct dnsc_id *pid, char type, + struct Curl_str *name, uint16_t port) +{ + pid->name = *name; + pid->port = port; + pid->type = type; +} + +struct dnsc_key { + char data[MAX_HOSTCACHE_LEN]; + size_t len; +}; + /* * Create a hostcache id string for the provided host + port, to be used by * the DNS caching. Without alloc. Return length of the id string. */ -static size_t create_dnscache_id(const char *name, - size_t nlen, /* 0 or actual name length */ - uint16_t port, char *ptr, size_t buflen) +static void dnsc_id2key(struct dnsc_key *key, struct dnsc_id *id) { - size_t len = nlen ? nlen : strlen(name); - DEBUGASSERT(buflen >= MAX_HOSTCACHE_LEN); - if(len > (buflen - 7)) - len = buflen - 7; + size_t namelen = curlx_strlen(&id->name); + if(namelen > (sizeof(key->data) - 8)) + namelen = sizeof(key->data) - 8; /* store and lower case the name */ - Curl_strntolower(ptr, name, len); - return curl_msnprintf(&ptr[len], 7, ":%u", port) + len; + key->data[0] = id->type; + Curl_strntolower(key->data + 1, curlx_str(&id->name), namelen); + /* include the terminating 0 in key length */ + key->len = namelen + 2 + + curl_msnprintf(&key->data[namelen + 1], 7, ":%u", id->port); +} + +static void dnscache_entry_free(struct Curl_dns_entry *dns) +{ + Curl_freeaddrinfo(dns->addr); +#ifdef USE_HTTPSRR + Curl_httpsrr_destroy(dns->hinfo); +#endif + curlx_free(dns); } struct dnscache_prune_data { @@ -212,32 +236,36 @@ void Curl_dnscache_clear(struct Curl_easy *data) static CURLcode fetch_addr(struct Curl_easy *data, struct Curl_dnscache *dnscache, uint8_t dns_queries, - const char *hostname, - uint16_t port, + struct Curl_peer *peer, struct Curl_dns_entry **pdns) { struct Curl_dns_entry *dns = NULL; - char entry_id[MAX_HOSTCACHE_LEN]; - size_t entry_len; + struct dnsc_id id; + struct dnsc_key key; + char type = CURL_DNSQ_IS_ADDR(dns_queries) ? + CURL_DNST_ADDR : CURL_DNST_HTTPS; CURLcode result = CURLE_OK; *pdns = NULL; if(!dnscache) return CURLE_OK; - /* Create an entry id, based upon the hostname and port */ - entry_len = create_dnscache_id(hostname, 0, port, - entry_id, sizeof(entry_id)); + dnsc_peer2id(&id, type, peer); + dnsc_id2key(&key, &id); /* See if it is already in our dns cache */ - dns = Curl_hash_pick(&dnscache->entries, entry_id, entry_len + 1); + dns = Curl_hash_pick(&dnscache->entries, key.data, key.len); /* No entry found in cache, check if we might have a wildcard entry */ - if(!dns && data->state.wildcard_resolve) { - entry_len = create_dnscache_id("*", 1, port, entry_id, sizeof(entry_id)); + if(!dns && (type == CURL_DNST_ADDR) && data->state.wildcard_resolve) { + struct Curl_str wildname; + + curlx_str_assign(&wildname, "*", 1); + dnsc_str2id(&id, CURL_DNST_ADDR, &wildname, peer->port); + dnsc_id2key(&key, &id); /* See if it is already in our dns cache */ - dns = Curl_hash_pick(&dnscache->entries, entry_id, entry_len + 1); + dns = Curl_hash_pick(&dnscache->entries, key.data, key.len); } if(dns && (data->set.dns_cache_timeout_ms != -1)) { @@ -251,13 +279,15 @@ static CURLcode fetch_addr(struct Curl_easy *data, if(dnscache_entry_is_stale(&user, dns)) { infof(data, "Hostname in DNS cache was stale, zapped"); dns = NULL; /* the memory deallocation is being handled by the hash */ - Curl_hash_delete(&dnscache->entries, entry_id, entry_len + 1); + Curl_hash_delete(&dnscache->entries, key.data, key.len); } } - if(dns) { - if((dns->dns_queries & dns_queries) != dns_queries) { - /* The entry does not cover all wanted DNS queries, a miss. */ + /* We need to cache address information and HTTPS-RR separately. */ + if(dns && CURL_DNSQ_IS_ADDR(dns_queries)) { + if((uint8_t)(dns->dns_queries & dns_queries) != + (uint8_t)(dns_queries & CURL_DNSQ_ADDR)) { + /* The entry does not cover all wanted address queries, a miss. */ dns = NULL; } else if(!(dns->dns_responses & dns_queries)) { @@ -267,12 +297,12 @@ static CURLcode fetch_addr(struct Curl_easy *data, dns = NULL; result = CURLE_COULDNT_RESOLVE_HOST; } + else if(dns && !dns->addr) { /* negative entry */ + dns = NULL; + result = CURLE_COULDNT_RESOLVE_HOST; + } } - if(dns && !dns->addr) { /* negative entry */ - dns = NULL; - result = CURLE_COULDNT_RESOLVE_HOST; - } *pdns = dns; return result; } @@ -293,8 +323,7 @@ static CURLcode fetch_addr(struct Curl_easy *data, */ CURLcode Curl_dnscache_get(struct Curl_easy *data, uint8_t dns_queries, - const char *hostname, - uint16_t port, + struct Curl_peer *peer, struct Curl_dns_entry **pentry) { struct Curl_dnscache *dnscache = dnscache_get(data); @@ -302,7 +331,7 @@ CURLcode Curl_dnscache_get(struct Curl_easy *data, CURLcode result = CURLE_OK; dnscache_lock(data, dnscache); - result = fetch_addr(data, dnscache, dns_queries, hostname, port, &dns); + result = fetch_addr(data, dnscache, dns_queries, peer, &dns); if(!result && dns) dns->refcount++; /* we pass out a reference */ else if(result) { @@ -311,6 +340,9 @@ CURLcode Curl_dnscache_get(struct Curl_easy *data, } dnscache_unlock(data, dnscache); + CURL_TRC_DNS(data, "cache lookup %s:%u queries=%s -> %d %sfound", + peer->hostname, peer->port, Curl_resolv_query_str(dns_queries), + (int)result, dns ? "" : "not "); *pentry = dns; return result; } @@ -397,8 +429,7 @@ UNITTEST CURLcode dns_shuffle_addr(struct Curl_easy *data, } #endif -static bool dnscache_ai_has_family(struct Curl_addrinfo *ai, - int ai_family) +static bool dnscache_ai_has_family(struct Curl_addrinfo *ai, int ai_family) { for(; ai; ai = ai->ai_next) { if(ai->ai_family == ai_family) @@ -407,28 +438,23 @@ static bool dnscache_ai_has_family(struct Curl_addrinfo *ai, return FALSE; } -static struct Curl_dns_entry *dnscache_entry_create( - struct Curl_easy *data, - uint8_t dns_queries, - struct Curl_addrinfo **paddr1, - struct Curl_addrinfo **paddr2, - const char *hostname, - size_t hostlen, - uint16_t port, - bool permanent) +static struct Curl_dns_entry *dnsc_entry_create(struct Curl_easy *data, + struct dnsc_id *pid, + bool permanent) { struct Curl_dns_entry *dns = NULL; /* Create a new cache entry, struct already has the hostname NUL */ - dns = curlx_calloc(1, sizeof(struct Curl_dns_entry) + hostlen); + dns = curlx_calloc(1, sizeof(struct Curl_dns_entry) + + curlx_strlen(&pid->name)); if(!dns) goto out; dns->refcount = 1; /* the cache has the first reference */ - dns->dns_queries = dns_queries; - dns->port = port; - if(hostlen) - memcpy(dns->hostname, hostname, hostlen); + dns->hostlen = curlx_strlen(&pid->name); + dns->port = pid->port; + if(dns->hostlen) + memcpy(dns->hostname, curlx_str(&pid->name), dns->hostlen); if(permanent) { dns->timestamp.tv_sec = 0; /* an entry that never goes stale */ @@ -438,6 +464,28 @@ static struct Curl_dns_entry *dnscache_entry_create( dns->timestamp = *Curl_pgrs_now(data); } +out: + return dns; +} + +static struct Curl_dns_entry *dnsc_entry_assign_addr( + struct Curl_easy *data, + struct Curl_dns_entry *dns, + uint8_t dns_queries, + struct Curl_addrinfo **paddr1, + struct Curl_addrinfo **paddr2) +{ + if(!dns) + goto out; + /* only do this when this is the only reference */ + DEBUGASSERT(dns->refcount == 1); + DEBUGASSERT(dns->type == CURL_DNST_INIT); + + dns->type = CURL_DNST_ADDR; + /* queries should only be about addresses */ + DEBUGASSERT(!(dns_queries & ~CURL_DNSQ_ADDR)); + dns->dns_queries = (dns_queries & CURL_DNSQ_ADDR); + /* Take the given address lists into the entry */ if(paddr1 && *paddr1) { dns->addr = *paddr1; @@ -488,79 +536,160 @@ out: return dns; } -struct Curl_dns_entry *Curl_dnscache_mk_entry(struct Curl_easy *data, - uint8_t dns_queries, - struct Curl_addrinfo **paddr, - const char *hostname, - uint16_t port) +struct Curl_dns_entry *Curl_dnsc_mk_addr(struct Curl_easy *data, + uint8_t dns_queries, + struct Curl_addrinfo **paddr, + struct Curl_peer *peer) { - return dnscache_entry_create(data, dns_queries, paddr, NULL, hostname, - hostname ? strlen(hostname) : 0, - port, FALSE); + struct dnsc_id id; + struct Curl_dns_entry *dns; + + dnsc_peer2id(&id, CURL_DNST_ADDR, peer); + dns = dnsc_entry_create(data, &id, FALSE); + dns = dnsc_entry_assign_addr(data, dns, dns_queries, paddr, NULL); + return dns; } -struct Curl_dns_entry *Curl_dnscache_mk_entry2(struct Curl_easy *data, - uint8_t dns_queries, - struct Curl_addrinfo **paddr1, - struct Curl_addrinfo **paddr2, - const char *hostname, - uint16_t port) +struct Curl_dns_entry *Curl_dnsc_mk_addr2(struct Curl_easy *data, + uint8_t dns_queries, + struct Curl_addrinfo **paddr1, + struct Curl_addrinfo **paddr2, + struct Curl_peer *peer) { - return dnscache_entry_create(data, dns_queries, paddr1, paddr2, hostname, - hostname ? strlen(hostname) : 0, - port, FALSE); + struct dnsc_id id; + struct Curl_dns_entry *dns; + + dnsc_peer2id(&id, CURL_DNST_ADDR, peer); + dns = dnsc_entry_create(data, &id, FALSE); + dns = dnsc_entry_assign_addr(data, dns, dns_queries, paddr1, paddr2); + return dns; } #ifdef USE_HTTPSRR -void Curl_dns_entry_set_https_rr(struct Curl_dns_entry *dns, - struct Curl_https_rrinfo *hinfo) +static struct Curl_dns_entry *dnsc_entry_assign_https( + struct Curl_dns_entry *dns, + struct Curl_https_rrinfo **phinfo) { + if(!dns) + goto out; /* only do this when this is the only reference */ DEBUGASSERT(dns->refcount == 1); - /* it should have been in the queries */ - DEBUGASSERT(dns->dns_queries & CURL_DNSQ_HTTPS); + DEBUGASSERT(dns->type == CURL_DNST_INIT); + if(dns->hinfo) { - Curl_httpsrr_cleanup(dns->hinfo); - curlx_free(dns->hinfo); + Curl_httpsrr_destroy(dns->hinfo); + dns->hinfo = NULL; } - dns->hinfo = hinfo; - dns->dns_responses |= CURL_DNSQ_HTTPS; + dns->type = CURL_DNST_HTTPS; + dns->dns_responses = dns->dns_queries = CURL_DNSQ_HTTPS; + if(phinfo) { + dns->hinfo = *phinfo; + *phinfo = NULL; + } +out: + if(phinfo && *phinfo) { + Curl_httpsrr_destroy(*phinfo); + *phinfo = NULL; + } + return dns; } -#endif /* USE_HTTPSRR */ -static struct Curl_dns_entry *dnscache_add_addr(struct Curl_easy *data, - struct Curl_dnscache *dnscache, - uint8_t dns_queries, - struct Curl_addrinfo **paddr, - const char *hostname, - size_t hlen, - uint16_t port, - bool permanent) +struct Curl_dns_entry *Curl_dnsc_mk_https(struct Curl_easy *data, + struct Curl_https_rrinfo **phinfo, + struct Curl_peer *peer) { - char entry_id[MAX_HOSTCACHE_LEN]; - size_t entry_len; + struct dnsc_id id; struct Curl_dns_entry *dns; - struct Curl_dns_entry *dns2; - dns = dnscache_entry_create(data, dns_queries, paddr, NULL, - hostname, hlen, port, permanent); + dnsc_peer2id(&id, CURL_DNST_HTTPS, peer); + dns = dnsc_entry_create(data, &id, FALSE); + dns = dnsc_entry_assign_https(dns, phinfo); + return dns; +} + +static struct Curl_dns_entry *dnsc_add_https(struct Curl_easy *data, + struct Curl_dnscache *dnscache, + struct Curl_https_rrinfo **phinfo, + struct dnsc_id *id, + bool permanent) +{ + struct Curl_dns_entry *dns, *dns2; + struct dnsc_key key; + + dns = dnsc_entry_create(data, id, permanent); + dns = dnsc_entry_assign_https(dns, phinfo); if(!dns) return NULL; - /* Create an entry id, based upon the hostname and port */ - entry_len = create_dnscache_id(hostname, hlen, port, - entry_id, sizeof(entry_id)); - /* Store the resolved data in our DNS cache. */ - dns2 = Curl_hash_add(&dnscache->entries, entry_id, entry_len + 1, - (void *)dns); + dnsc_id2key(&key, id); + dns2 = Curl_hash_add(&dnscache->entries, key.data, key.len, (void *)dns); if(!dns2) { dnscache_entry_free(dns); return NULL; } dns = dns2; - dns->refcount++; /* mark entry as in-use */ + dns->refcount++; /* mark entry as in-use */ + return dns; +} +#endif /* USE_HTTPSRR */ + +static struct Curl_dns_entry *dnsc_add_addr(struct Curl_easy *data, + struct Curl_dnscache *dnscache, + uint8_t dns_queries, + struct Curl_addrinfo **paddr, + struct dnsc_id *id, + struct dnsc_key *key, + bool permanent) +{ + struct Curl_dns_entry *dns; + struct Curl_dns_entry *dns2; + + dns = dnsc_entry_create(data, id, permanent); + dns = dnsc_entry_assign_addr(data, dns, dns_queries, paddr, NULL); + if(!dns) + return NULL; + + /* Store the resolved data in our DNS cache. */ + dns2 = Curl_hash_add(&dnscache->entries, key->data, key->len, (void *)dns); + if(!dns2) { + dnscache_entry_free(dns); + return NULL; + } + + dns = dns2; + dns->refcount++; /* mark entry as in-use */ + return dns; +} + +static struct Curl_dns_entry *dnsc_add_peer_addr( + struct Curl_easy *data, + struct Curl_dnscache *dnscache, + uint8_t dns_queries, + struct Curl_addrinfo **paddr, + struct dnsc_id *id, + bool permanent) +{ + struct Curl_dns_entry *dns; + struct Curl_dns_entry *dns2; + struct dnsc_key key; + + dns = dnsc_entry_create(data, id, permanent); + dns = dnsc_entry_assign_addr(data, dns, dns_queries, paddr, NULL); + if(!dns) + return NULL; + + /* Store the resolved data in our DNS cache. */ + dnsc_id2key(&key, id); + dns2 = Curl_hash_add(&dnscache->entries, key.data, key.len, (void *)dns); + if(!dns2) { + dnscache_entry_free(dns); + return NULL; + } + + dns = dns2; + dns->refcount++; /* mark entry as in-use */ return dns; } @@ -568,52 +697,78 @@ CURLcode Curl_dnscache_add(struct Curl_easy *data, struct Curl_dns_entry *entry) { struct Curl_dnscache *dnscache = dnscache_get(data); - char id[MAX_HOSTCACHE_LEN]; - size_t idlen; + struct Curl_str name; + struct dnsc_id id; + struct dnsc_key key; if(!dnscache) return CURLE_FAILED_INIT; - /* Create an entry id, based upon the hostname and port */ - idlen = create_dnscache_id(entry->hostname, 0, entry->port, id, sizeof(id)); + if(!entry || (entry->type == CURL_DNST_INIT)) + return CURLE_BAD_FUNCTION_ARGUMENT; + + curlx_str_assign(&name, entry->hostname, entry->hostlen); + dnsc_str2id(&id, entry->type, &name, entry->port); + dnsc_id2key(&key, &id); /* Store the resolved data in our DNS cache and up ref count */ dnscache_lock(data, dnscache); - if(!Curl_hash_add(&dnscache->entries, id, idlen + 1, (void *)entry)) { + if(!Curl_hash_add(&dnscache->entries, key.data, key.len, (void *)entry)) { dnscache_unlock(data, dnscache); return CURLE_OUT_OF_MEMORY; } entry->refcount++; dnscache_unlock(data, dnscache); + CURL_TRC_DNS(data, "cached entry for %s:%u queries=%s", + entry->hostname, entry->port, + Curl_resolv_query_str(entry->dns_queries)); return CURLE_OK; } CURLcode Curl_dnscache_add_negative(struct Curl_easy *data, uint8_t dns_queries, - const char *host, - uint16_t port) + struct Curl_peer *peer) { struct Curl_dnscache *dnscache = dnscache_get(data); - struct Curl_dns_entry *dns; + struct Curl_dns_entry *dns = NULL; + struct dnsc_id id; + CURLcode result = CURLE_OK; + DEBUGASSERT(dnscache); if(!dnscache) return CURLE_FAILED_INIT; dnscache_lock(data, dnscache); - /* put this new host in the cache */ - dns = dnscache_add_addr(data, dnscache, dns_queries, NULL, - host, strlen(host), port, FALSE); + if(dns_queries & CURL_DNSQ_ADDR) { + /* put this new host in the cache */ + dnsc_peer2id(&id, CURL_DNST_ADDR, peer); + dns = dnsc_add_peer_addr(data, dnscache, dns_queries, NULL, &id, FALSE); + if(!dns) + result = CURLE_OUT_OF_MEMORY; + } +#ifdef USE_HTTPSRR + else if(dns_queries == CURL_DNSQ_HTTPS) { + dnsc_peer2id(&id, CURL_DNST_HTTPS, peer); + dns = dnsc_add_https(data, dnscache, NULL, &id, FALSE); + if(!dns) + result = CURLE_OUT_OF_MEMORY; + } +#endif + else { + /* a query we do not know, just cache nothing */ + DEBUGASSERT(0); + } + if(dns) { /* release the returned reference; the cache itself will keep the * entry alive: */ dns->refcount--; - dnscache_unlock(data, dnscache); CURL_TRC_DNS(data, "cache negative name resolve for %s:%d type=%s", - host, port, Curl_resolv_query_str(dns_queries)); - return CURLE_OK; + peer->hostname, peer->port, + Curl_resolv_query_str(dns_queries)); } dnscache_unlock(data, dnscache); - return CURLE_OUT_OF_MEMORY; + return result; } /* @@ -665,6 +820,8 @@ CURLcode Curl_loadhostpairs(struct Curl_easy *data) { struct Curl_dnscache *dnscache = dnscache_get(data); struct curl_slist *hostp; + struct dnsc_id id; + struct dnsc_key key; if(!dnscache) return CURLE_FAILED_INIT; @@ -673,14 +830,12 @@ CURLcode Curl_loadhostpairs(struct Curl_easy *data) data->state.wildcard_resolve = FALSE; for(hostp = data->state.resolve; hostp; hostp = hostp->next) { - char entry_id[MAX_HOSTCACHE_LEN]; const char *host = hostp->data; struct Curl_str source; if(!host) continue; if(*host == '-') { curl_off_t num = 0; - size_t entry_len; host++; if(!curlx_str_single(&host, '[')) { if(curlx_str_until(&host, &source, MAX_IPADR_LEN, ']') || @@ -697,19 +852,17 @@ CURLcode Curl_loadhostpairs(struct Curl_easy *data) if(!curlx_str_number(&host, &num, 0xffff)) { /* Create an entry id, based upon the hostname and port */ - entry_len = create_dnscache_id(curlx_str(&source), - curlx_strlen(&source), (uint16_t)num, - entry_id, sizeof(entry_id)); + dnsc_str2id(&id, CURL_DNST_ADDR, &source, (uint16_t)num); + dnsc_id2key(&key, &id); dnscache_lock(data, dnscache); /* delete entry, ignore if it did not exist */ - Curl_hash_delete(&dnscache->entries, entry_id, entry_len + 1); + Curl_hash_delete(&dnscache->entries, key.data, key.len); dnscache_unlock(data, dnscache); } } else { struct Curl_dns_entry *dns; struct Curl_addrinfo *head = NULL, *tail = NULL; - size_t entry_len; char address[64]; curl_off_t tmpofft = 0; uint16_t port = 0; @@ -801,19 +954,16 @@ err: return CURLE_SETOPT_OPTION_SYNTAX; } - /* Create an entry id, based upon the hostname and port */ - entry_len = create_dnscache_id(curlx_str(&source), curlx_strlen(&source), - port, entry_id, sizeof(entry_id)); - + dnsc_str2id(&id, CURL_DNST_ADDR, &source, port); + dnsc_id2key(&key, &id); dnscache_lock(data, dnscache); /* See if it is already in our dns cache */ - dns = Curl_hash_pick(&dnscache->entries, entry_id, entry_len + 1); + dns = Curl_hash_pick(&dnscache->entries, key.data, key.len); if(dns) { infof(data, "RESOLVE %.*s:%u - old addresses discarded", - (int)curlx_strlen(&source), - curlx_str(&source), port); + (int)curlx_strlen(&source), curlx_str(&source), port); /* delete old entry, there are two reasons for this 1. old entry may have different addresses. 2. even if entry with correct addresses is already in the cache, @@ -825,13 +975,12 @@ err: 4. when adding a non-permanent entry, we want it to get a "fresh" timeout that starts _now_. */ - Curl_hash_delete(&dnscache->entries, entry_id, entry_len + 1); + Curl_hash_delete(&dnscache->entries, key.data, key.len); } - /* put this new host in the cache, an override for ALL dns queries */ - dns = dnscache_add_addr(data, dnscache, CURL_DNSQ_ALL, - &head, curlx_str(&source), - curlx_strlen(&source), port, permanent); + /* put this new host in the cache, override all address queries */ + dns = dnsc_add_addr(data, dnscache, CURL_DNSQ_ADDR, &head, + &id, &key, permanent); if(dns) /* release the returned reference; the cache itself will keep the * entry alive: */ @@ -842,9 +991,9 @@ err: if(!dns) return CURLE_OUT_OF_MEMORY; - infof(data, "Added %.*s:%u:%s to DNS cache%s", - (int)curlx_strlen(&source), curlx_str(&source), port, addresses, - permanent ? "" : " (non-permanent)"); + infof(data, "[DNS] added %.*s:%u:%s to cache%s", + (int)curlx_strlen(&id.name), curlx_str(&id.name), id.port, + addresses, permanent ? "" : " (non-permanent)"); /* Wildcard hostname */ if(curlx_str_casecompare(&source, "*")) { diff --git a/Utilities/cmcurl/lib/dnscache.h b/Utilities/cmcurl/lib/vdns/dnscache.h similarity index 78% rename from Utilities/cmcurl/lib/dnscache.h rename to Utilities/cmcurl/lib/vdns/dnscache.h index 9239977cf8..73dcff60d7 100644 --- a/Utilities/cmcurl/lib/dnscache.h +++ b/Utilities/cmcurl/lib/vdns/dnscache.h @@ -35,6 +35,11 @@ struct connectdata; struct easy_pollset; struct Curl_https_rrinfo; struct Curl_multi; +struct Curl_peer; + +#define CURL_DNST_INIT '\0' +#define CURL_DNST_ADDR 'A' +#define CURL_DNST_HTTPS 'H' struct Curl_dns_entry { struct Curl_addrinfo *addr; @@ -43,10 +48,12 @@ struct Curl_dns_entry { #endif /* timestamp == 0 -- permanent CURLOPT_RESOLVE entry (does not time out) */ struct curltime timestamp; + size_t hostlen; /* reference counter, entry is freed on reaching 0 */ uint32_t refcount; /* hostname port number that resolved to addr. */ uint16_t port; + char type; /* CURL_DNST_ADDR or CURL_DNST_HTTPS */ uint8_t dns_queries; /* CURL_DNSQ_* type of queries performed for this */ uint8_t dns_responses; /* CURL_DNSQ_* type this entry has responses for */ /* hostname that resolved to addr. may be NULL (Unix domain sockets). */ @@ -62,22 +69,21 @@ struct Curl_dns_entry { * * Returns entry or NULL on OOM. */ -struct Curl_dns_entry *Curl_dnscache_mk_entry(struct Curl_easy *data, - uint8_t dns_queries, - struct Curl_addrinfo **paddr, - const char *hostname, - uint16_t port); +struct Curl_dns_entry *Curl_dnsc_mk_addr(struct Curl_easy *data, + uint8_t dns_queries, + struct Curl_addrinfo **paddr, + struct Curl_peer *peer); -struct Curl_dns_entry *Curl_dnscache_mk_entry2(struct Curl_easy *data, - uint8_t dns_queries, - struct Curl_addrinfo **paddr1, - struct Curl_addrinfo **paddr2, - const char *hostname, - uint16_t port); +struct Curl_dns_entry *Curl_dnsc_mk_addr2(struct Curl_easy *data, + uint8_t dns_queries, + struct Curl_addrinfo **paddr1, + struct Curl_addrinfo **paddr2, + struct Curl_peer *peer); #ifdef USE_HTTPSRR -void Curl_dns_entry_set_https_rr(struct Curl_dns_entry *dns, - struct Curl_https_rrinfo *hinfo); +struct Curl_dns_entry *Curl_dnsc_mk_https(struct Curl_easy *data, + struct Curl_https_rrinfo **phinfo, + struct Curl_peer *peer); #endif /* USE_HTTPSRR */ /* unlink a dns entry, frees all resources if it was the last reference. @@ -112,8 +118,7 @@ void Curl_dnscache_clear(struct Curl_easy *data); */ CURLcode Curl_dnscache_get(struct Curl_easy *data, uint8_t dns_queries, - const char *hostname, - uint16_t port, + struct Curl_peer *peer, struct Curl_dns_entry **pentry); /* @@ -127,8 +132,7 @@ CURLcode Curl_dnscache_add(struct Curl_easy *data, * it could not be resolved. */ CURLcode Curl_dnscache_add_negative(struct Curl_easy *data, uint8_t dns_queries, - const char *host, - uint16_t port); + struct Curl_peer *peer); /* * Populate the cache with specified entries from CURLOPT_RESOLVE. diff --git a/Utilities/cmcurl/lib/doh.c b/Utilities/cmcurl/lib/vdns/doh.c similarity index 69% rename from Utilities/cmcurl/lib/doh.c rename to Utilities/cmcurl/lib/vdns/doh.c index 8b643aa0b4..3b52ed92f3 100644 --- a/Utilities/cmcurl/lib/doh.c +++ b/Utilities/cmcurl/lib/vdns/doh.c @@ -27,12 +27,12 @@ #include "urldata.h" #include "curl_addrinfo.h" -#include "doh.h" #include "curl_trc.h" -#include "httpsrr.h" #include "multiif.h" #include "url.h" #include "connect.h" +#include "vdns/doh.h" +#include "vdns/httpsrr.h" #include "curlx/strdup.h" #include "curlx/dynbuf.h" #include "escape.h" /* for Curl_hexencode() */ @@ -44,7 +44,7 @@ static void doh_close(struct Curl_easy *data, struct Curl_resolv_async *async); #ifdef CURLVERBOSE -static const char * const errors[] = { +static const char * const doh_code_str[] = { "", "Bad label", "Out of range", @@ -58,16 +58,35 @@ static const char * const errors[] = { "Unexpected CLASS", "No content", "Bad ID", - "Name too long" + "Name too long", + "No such name", + "Transport failed", + "Out Of Memory" }; static const char *doh_strerror(DOHcode code) { - if((code >= DOH_OK) && (code <= DOH_DNS_NAME_TOO_LONG)) - return errors[code]; + if((size_t)code < CURL_ARRAYSIZE(doh_code_str)) + return doh_code_str[code]; return "bad error code"; } +static const char *doh_type2name(DNStype dnstype) +{ + switch(dnstype) { + case CURL_DNS_TYPE_A: + return "A"; + case CURL_DNS_TYPE_AAAA: + return "AAAA"; +#ifdef USE_HTTPSRR + case CURL_DNS_TYPE_HTTPS: + return "HTTPS"; +#endif + default: + return "unknown"; + } +} + #endif /* CURLVERBOSE */ /* @unittest 1655 @@ -189,88 +208,8 @@ static size_t doh_probe_write_cb(char *contents, size_t size, size_t nmemb, return realsize; } -#if defined(USE_HTTPSRR) && defined(DEBUGBUILD) && defined(CURLVERBOSE) - -/* doh_print_buf truncates if the hex string will be more than this */ -#define LOCAL_PB_HEXMAX 400 - -static void doh_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); -} -#endif - -/* called from multi when a sub transfer, e.g. doh probe, is done. - * This looks up the probe response at its meta CURL_EZM_DOH_PROBE - * and copies the response body over to the struct at the master's - * meta at CURL_EZM_DOH_MASTER. */ -static void doh_probe_done(struct Curl_easy *data, - struct Curl_easy *doh, CURLcode result) -{ - struct Curl_resolv_async *async = NULL; - struct doh_probes *dohp = NULL; - struct doh_request *doh_req = NULL; - int i; - - doh_req = Curl_meta_get(doh, CURL_EZM_DOH_PROBE); - if(!doh_req) { - DEBUGASSERT(0); - return; - } - - async = Curl_async_get(data, doh_req->resolv_id); - if(!async) { - CURL_TRC_DNS(data, "[%u] ignoring outdated DoH response", - doh_req->resolv_id); - return; - } - dohp = async->doh; - - for(i = 0; i < DOH_SLOT_COUNT; ++i) { - if(dohp->probe_resp[i].probe_mid == doh->mid) - break; - } - /* We really should have found the slot where to store the response */ - if(i >= DOH_SLOT_COUNT) { - DEBUGASSERT(0); - failf(data, "DoH: unknown sub request done"); - return; - } - - dohp->pending--; - infof(doh, "a DoH request is completed, %u to go", dohp->pending); - dohp->probe_resp[i].result = result; - /* We expect either the meta data still to exist or the sub request - * to have already failed. */ - if(!result) { - dohp->probe_resp[i].dnstype = doh_req->dnstype; - result = curlx_dyn_addn(&dohp->probe_resp[i].body, - curlx_dyn_ptr(&doh_req->resp_body), - curlx_dyn_len(&doh_req->resp_body)); - } - Curl_meta_remove(doh, CURL_EZM_DOH_PROBE); - - if(result) - infof(doh, "DoH request %s", curl_easy_strerror(result)); - - if(!dohp->pending) { - /* DoH completed, run the transfer picking up the results */ - Curl_multi_mark_dirty(data); - } -} - +static void doh_probe_done(struct Curl_easy *doh, + struct Curl_easy *master, CURLcode result); static void doh_probe_dtor(void *key, size_t klen, void *e) { (void)key; @@ -304,6 +243,7 @@ static CURLcode doh_probe_run(struct Curl_easy *data, timediff_t timeout_ms; struct doh_request *doh_req; DOHcode d; + bool maybe_https = !curl_strnequal(url, STRCONST("http:")); *pmid = UINT32_MAX; @@ -343,7 +283,7 @@ static CURLcode doh_probe_run(struct Curl_easy *data, /* pass in the struct pointer via a local variable to please coverity and the gcc typecheck helpers */ - VERBOSE(doh->state.feat = &Curl_trc_feat_dns); + VERBOSE(doh->state.feat = &Curl_trc_feat_doh); ERROR_CHECK_SETOPT(CURLOPT_URL, url); ERROR_CHECK_SETOPT(CURLOPT_DEFAULT_PROTOCOL, "https"); ERROR_CHECK_SETOPT(CURLOPT_WRITEFUNCTION, doh_probe_write_cb); @@ -352,8 +292,10 @@ static CURLcode doh_probe_run(struct Curl_easy *data, ERROR_CHECK_SETOPT(CURLOPT_POSTFIELDSIZE, (long)doh_req->req_body_len); ERROR_CHECK_SETOPT(CURLOPT_HTTPHEADER, doh_req->req_hds); #ifdef USE_HTTP2 - ERROR_CHECK_SETOPT(CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS); - ERROR_CHECK_SETOPT(CURLOPT_PIPEWAIT, 1L); + if(maybe_https) { + ERROR_CHECK_SETOPT(CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS); + ERROR_CHECK_SETOPT(CURLOPT_PIPEWAIT, 1L); + } #endif #ifndef DEBUGBUILD /* enforce HTTPS if not debug */ @@ -366,62 +308,68 @@ static CURLcode doh_probe_run(struct Curl_easy *data, ERROR_CHECK_SETOPT(CURLOPT_SHARE, (CURLSH *)data->share); if(data->set.err && data->set.err != stderr) ERROR_CHECK_SETOPT(CURLOPT_STDERR, data->set.err); - if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_dns)) + if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_doh)) ERROR_CHECK_SETOPT(CURLOPT_VERBOSE, 1L); if(data->set.no_signal) ERROR_CHECK_SETOPT(CURLOPT_NOSIGNAL, 1L); - - ERROR_CHECK_SETOPT(CURLOPT_SSL_VERIFYHOST, - data->set.doh_verifyhost ? 2L : 0L); - ERROR_CHECK_SETOPT(CURLOPT_SSL_VERIFYPEER, - data->set.doh_verifypeer ? 1L : 0L); - ERROR_CHECK_SETOPT(CURLOPT_SSL_VERIFYSTATUS, - data->set.doh_verifystatus ? 1L : 0L); - - /* Inherit *some* SSL options from the user's transfer. This is a - best-guess as to which options are needed for compatibility. #3661 - - Note DoH does not inherit the user's proxy server so proxy SSL settings - have no effect and are not inherited. If that changes then two new - options should be added to check doh proxy insecure separately, - CURLOPT_DOH_PROXY_SSL_VERIFYHOST and CURLOPT_DOH_PROXY_SSL_VERIFYPEER. - */ - doh->set.ssl.custom_cafile = data->set.ssl.custom_cafile; - doh->set.ssl.custom_capath = data->set.ssl.custom_capath; - doh->set.ssl.custom_cablob = data->set.ssl.custom_cablob; - if(data->set.str[STRING_SSL_CAFILE]) { - ERROR_CHECK_SETOPT(CURLOPT_CAINFO, data->set.str[STRING_SSL_CAFILE]); - } - if(data->set.blobs[BLOB_CAINFO]) { - ERROR_CHECK_SETOPT(CURLOPT_CAINFO_BLOB, data->set.blobs[BLOB_CAINFO]); - } - if(data->set.str[STRING_SSL_CAPATH]) { - ERROR_CHECK_SETOPT(CURLOPT_CAPATH, data->set.str[STRING_SSL_CAPATH]); - } - if(data->set.str[STRING_SSL_CRLFILE]) { - ERROR_CHECK_SETOPT(CURLOPT_CRLFILE, data->set.str[STRING_SSL_CRLFILE]); - } - if(data->set.ssl.certinfo) - ERROR_CHECK_SETOPT(CURLOPT_CERTINFO, 1L); - if(data->set.ssl.fsslctx) - ERROR_CHECK_SETOPT(CURLOPT_SSL_CTX_FUNCTION, data->set.ssl.fsslctx); - if(data->set.ssl.fsslctxp) - ERROR_CHECK_SETOPT(CURLOPT_SSL_CTX_DATA, data->set.ssl.fsslctxp); if(data->set.fdebug) ERROR_CHECK_SETOPT(CURLOPT_DEBUGFUNCTION, data->set.fdebug); if(data->set.debugdata) ERROR_CHECK_SETOPT(CURLOPT_DEBUGDATA, data->set.debugdata); - if(data->set.str[STRING_SSL_EC_CURVES]) { - ERROR_CHECK_SETOPT(CURLOPT_SSL_EC_CURVES, - data->set.str[STRING_SSL_EC_CURVES]); - } - (void)curl_easy_setopt(doh, CURLOPT_SSL_OPTIONS, - ((long)data->set.ssl.primary.ssl_options & - ~CURLSSLOPT_AUTO_CLIENT_CERT)); + if(maybe_https) { + ERROR_CHECK_SETOPT(CURLOPT_SSL_VERIFYHOST, + data->set.doh_verifyhost ? 2L : 0L); + ERROR_CHECK_SETOPT(CURLOPT_SSL_VERIFYPEER, + data->set.doh_verifypeer ? 1L : 0L); + ERROR_CHECK_SETOPT(CURLOPT_SSL_VERIFYSTATUS, + data->set.doh_verifystatus ? 1L : 0L); + + /* Inherit *some* SSL options from the user's transfer. This is a + best-guess as to which options are needed for compatibility. #3661 + + Note DoH does not inherit the user's proxy server so proxy SSL settings + have no effect and are not inherited. If that changes then two new + options should be added to check doh proxy insecure separately, + CURLOPT_DOH_PROXY_SSL_VERIFYHOST and CURLOPT_DOH_PROXY_SSL_VERIFYPEER. + */ + doh->set.ssl.custom_cafile = data->set.ssl.custom_cafile; + doh->set.ssl.custom_capath = data->set.ssl.custom_capath; + doh->set.ssl.custom_cablob = data->set.ssl.custom_cablob; + if(CURL_EASY_STR(data, STRING_SSL_CAFILE)) { + ERROR_CHECK_SETOPT(CURLOPT_CAINFO, + CURL_EASY_STR(data, STRING_SSL_CAFILE)); + } + if(data->set.blobs[BLOB_CAINFO]) { + ERROR_CHECK_SETOPT(CURLOPT_CAINFO_BLOB, data->set.blobs[BLOB_CAINFO]); + } + if(CURL_EASY_STR(data, STRING_SSL_CAPATH)) { + ERROR_CHECK_SETOPT(CURLOPT_CAPATH, + CURL_EASY_STR(data, STRING_SSL_CAPATH)); + } + if(CURL_EASY_STR(data, STRING_SSL_CRLFILE)) { + ERROR_CHECK_SETOPT(CURLOPT_CRLFILE, + CURL_EASY_STR(data, STRING_SSL_CRLFILE)); + } + if(data->set.ssl.certinfo) + ERROR_CHECK_SETOPT(CURLOPT_CERTINFO, 1L); + if(data->set.ssl.fsslctx) + ERROR_CHECK_SETOPT(CURLOPT_SSL_CTX_FUNCTION, data->set.ssl.fsslctx); + if(data->set.ssl.fsslctxp) + ERROR_CHECK_SETOPT(CURLOPT_SSL_CTX_DATA, data->set.ssl.fsslctxp); + if(CURL_EASY_STR(data, STRING_SSL_EC_CURVES)) { + ERROR_CHECK_SETOPT(CURLOPT_SSL_EC_CURVES, + CURL_EASY_STR(data, STRING_SSL_EC_CURVES)); + } + + (void)curl_easy_setopt(doh, CURLOPT_SSL_OPTIONS, + ((long)data->set.ssl.primary.ssl_options & + ~CURLSSLOPT_AUTO_CLIENT_CERT)); + } doh->state.internal = TRUE; doh->master_mid = data->mid; /* master transfer of this one */ + doh->sub_xfer_done = doh_probe_done; result = Curl_meta_set(doh, CURL_EZM_DOH_PROBE, doh_req, doh_probe_dtor); doh_req = NULL; /* call took ownership */ @@ -434,7 +382,7 @@ static CURLcode doh_probe_run(struct Curl_easy *data, private_data via CURLOPT_PRIVATE if they so choose. */ DEBUGASSERT(!doh->set.private_data); - if(curl_multi_add_handle(multi, doh)) + if(Curl_multi_add_handle(multi, doh)) goto error; *pmid = doh->mid; @@ -460,69 +408,83 @@ CURLcode Curl_doh(struct Curl_easy *data, size_t i; DEBUGASSERT(!async->doh); - DEBUGASSERT(async->hostname[0]); + DEBUGASSERT(async->peer->hostname[0]); if(async->doh) { DEBUGASSERT(0); /* should not happen */ Curl_doh_cleanup(data, async); } + if(!async->dns_queries) + return CURLE_BAD_FUNCTION_ARGUMENT; +#ifdef USE_HTTPSRR + if(CURL_DNSQ_IS_ADDR(async->dns_queries) && + (async->dns_queries & CURL_DNSQ_HTTPS)) { + /* Can't mix those in the same async resolve */ + DEBUGASSERT(0); + return CURLE_BAD_FUNCTION_ARGUMENT; + } +#else + if(async->dns_queries & CURL_DNSQ_HTTPS) { + DEBUGASSERT(0); + return CURLE_NOT_BUILT_IN; + } +#endif + /* start clean, consider allocating this struct on demand */ async->doh = dohp = curlx_calloc(1, sizeof(struct doh_probes)); if(!dohp) return CURLE_OUT_OF_MEMORY; for(i = 0; i < DOH_SLOT_COUNT; ++i) { - dohp->probe_resp[i].probe_mid = UINT32_MAX; - curlx_dyn_init(&dohp->probe_resp[i].body, DYN_DOH_RESPONSE); + dohp->probe_rc[i] = DOH_OK; + dohp->probe_mid[i] = UINT32_MAX; } - dohp->host = async->hostname; - dohp->port = async->port; - /* We are making sub easy handles and want to be called back when - * one is done. */ - data->sub_xfer_done = doh_probe_done; +#ifdef USE_IPV6 + /* AAAA results have preference in happy eyeballing, trigger first */ + if(async->dns_queries & CURL_DNSQ_AAAA) { + /* create IPv6 DoH request */ + result = doh_probe_run(data, CURL_DNS_TYPE_AAAA, + async->peer->hostname, + CURL_EASY_STR(data, STRING_DOH), + data->multi, async->id, + &dohp->probe_mid[DOH_SLOT_IPV6]); + if(result) + goto error; + async->queries_ongoing++; + } +#endif /* create IPv4 DoH request */ if(async->dns_queries & CURL_DNSQ_A) { result = doh_probe_run(data, CURL_DNS_TYPE_A, - async->hostname, data->set.str[STRING_DOH], + async->peer->hostname, + CURL_EASY_STR(data, STRING_DOH), data->multi, async->id, - &dohp->probe_resp[DOH_SLOT_IPV4].probe_mid); + &dohp->probe_mid[DOH_SLOT_IPV4]); if(result) goto error; - dohp->pending++; + async->queries_ongoing++; } -#ifdef USE_IPV6 - if(async->dns_queries & CURL_DNSQ_AAAA) { - /* create IPv6 DoH request */ - result = doh_probe_run(data, CURL_DNS_TYPE_AAAA, - async->hostname, data->set.str[STRING_DOH], - data->multi, async->id, - &dohp->probe_resp[DOH_SLOT_IPV6].probe_mid); - if(result) - goto error; - dohp->pending++; - } -#endif - #ifdef USE_HTTPSRR if(async->dns_queries & CURL_DNSQ_HTTPS) { char *qname = NULL; - if(async->port != PORT_HTTPS) { - qname = curl_maprintf("_%d._https.%s", async->port, async->hostname); + if(async->peer->port != PORT_HTTPS) { + qname = curl_maprintf("_%u._https.%s", + async->peer->port, async->peer->hostname); if(!qname) goto error; } result = doh_probe_run(data, CURL_DNS_TYPE_HTTPS, - qname ? qname : async->hostname, - data->set.str[STRING_DOH], data->multi, + qname ? qname : async->peer->hostname, + CURL_EASY_STR(data, STRING_DOH), data->multi, async->id, - &dohp->probe_resp[DOH_SLOT_HTTPS_RR].probe_mid); + &dohp->probe_mid[DOH_SLOT_HTTPS_RR]); curlx_free(qname); if(result) goto error; - dohp->pending++; + async->queries_ongoing++; } #endif return CURLE_OK; @@ -616,58 +578,7 @@ static DOHcode doh_store_https(const unsigned char *doh, int index, } #endif -static DOHcode doh_store_cname(const unsigned char *doh, size_t dohlen, - unsigned int index, struct dohentry *d) -{ - struct dynbuf *c; - unsigned int loop = 128; /* a valid DNS name can never loop this much */ - unsigned char length; - - if(d->numcname == DOH_MAX_CNAME) - return DOH_OK; /* skip! */ - - c = &d->cname[d->numcname++]; - do { - if(index >= dohlen) - return DOH_DNS_OUT_OF_RANGE; - length = doh[index]; - if((length & 0xc0) == 0xc0) { - int newpos; - /* name pointer, get the new offset (14 bits) */ - if((index + 1) >= dohlen) - return DOH_DNS_OUT_OF_RANGE; - - /* move to the new index */ - newpos = (length & 0x3f) << 8 | doh[index + 1]; - index = (unsigned int)newpos; - continue; - } - else if(length & 0xc0) - return DOH_DNS_BAD_LABEL; /* bad input */ - else - index++; - - if(length) { - if(curlx_dyn_len(c)) { - if(curlx_dyn_addn(c, STRCONST("."))) - return DOH_OUT_OF_MEM; - } - if((index + length) > dohlen) - return DOH_DNS_BAD_LABEL; - - if(curlx_dyn_addn(c, &doh[index], length)) - return DOH_OUT_OF_MEM; - index += length; - } - } while(length && --loop); - - if(!loop) - return DOH_DNS_LABEL_LOOP; - return DOH_OK; -} - static DOHcode doh_rdata(const unsigned char *doh, - size_t dohlen, unsigned short rdlength, unsigned short type, int index, @@ -676,10 +587,7 @@ static DOHcode doh_rdata(const unsigned char *doh, /* RDATA - A (TYPE 1): 4 bytes - AAAA (TYPE 28): 16 bytes - - NS (TYPE 2): N bytes - HTTPS (TYPE 65): N bytes */ - DOHcode rc; - switch(type) { case CURL_DNS_TYPE_A: if(rdlength != 4) @@ -692,22 +600,15 @@ static DOHcode doh_rdata(const unsigned char *doh, doh_store_aaaa(doh, index, d); break; #ifdef USE_HTTPSRR - case CURL_DNS_TYPE_HTTPS: - rc = doh_store_https(doh, index, d, rdlength); + case CURL_DNS_TYPE_HTTPS: { + DOHcode rc = doh_store_https(doh, index, d, rdlength); if(rc) return rc; break; + } #endif - case CURL_DNS_TYPE_CNAME: - rc = doh_store_cname(doh, dohlen, (unsigned int)index, d); - if(rc) - return rc; - break; - case CURL_DNS_TYPE_DNAME: - /* explicit for clarity; skip; rely on synthesized CNAME */ - break; default: - /* unsupported type, skip it */ + /* unsupported type, or type we do not store, skip it */ break; } return DOH_OK; @@ -717,11 +618,8 @@ static DOHcode doh_rdata(const unsigned char *doh, UNITTEST void de_init(struct dohentry *de); UNITTEST void de_init(struct dohentry *de) { - int i; memset(de, 0, sizeof(*de)); de->ttl = INT_MAX; - for(i = 0; i < DOH_MAX_CNAME; i++) - curlx_dyn_init(&de->cname[i], DYN_DOH_CNAME); } /* TTL value cap */ @@ -751,6 +649,8 @@ UNITTEST DOHcode doh_resp_decode(const unsigned char *doh, if(!doh || doh[0] || doh[1]) return DOH_DNS_BAD_ID; /* bad ID */ rcode = doh[3] & 0x0f; + if(rcode == 3) + return DOH_DNS_NXDOMAIN; /* name does not exist */ if(rcode) return DOH_DNS_BAD_RCODE; /* bad rcode */ @@ -781,7 +681,7 @@ UNITTEST DOHcode doh_resp_decode(const unsigned char *doh, if((type != CURL_DNS_TYPE_CNAME) && /* may be synthesized from DNAME */ (type != CURL_DNS_TYPE_DNAME) && /* if present, accept and ignore */ (type != dnstype)) - /* Not the same type as was asked for nor CNAME nor DNAME */ + /* Not the same type as was asked for, nor CNAME nor DNAME */ return DOH_DNS_UNEXPECTED_TYPE; index += 2; @@ -810,9 +710,9 @@ UNITTEST DOHcode doh_resp_decode(const unsigned char *doh, if(dohlen < (index + rdlength)) return DOH_DNS_OUT_OF_RANGE; - rc = doh_rdata(doh, dohlen, rdlength, type, (int)index, d); + rc = doh_rdata(doh, rdlength, type, (int)index, d); if(rc) - return rc; /* bad doh_rdata */ + return rc; index += rdlength; ancount--; } @@ -864,69 +764,10 @@ UNITTEST DOHcode doh_resp_decode(const unsigned char *doh, if(index != dohlen) return DOH_DNS_MALFORMAT; /* something is wrong */ -#ifdef USE_HTTPSRR - if((type != CURL_DNS_TYPE_NS) && !d->numcname && !d->numaddr && - !d->numhttps_rrs) -#else - if((type != CURL_DNS_TYPE_NS) && !d->numcname && !d->numaddr) -#endif - /* nothing stored! */ - return DOH_NO_CONTENT; - return DOH_OK; /* ok */ } -#ifdef CURLVERBOSE -static void doh_show(struct Curl_easy *data, - const struct dohentry *d) -{ - int i; - infof(data, "[DoH] TTL: %u seconds", d->ttl); - for(i = 0; i < d->numaddr; i++) { - const struct dohaddr *a = &d->addr[i]; - if(a->type == CURL_DNS_TYPE_A) { - infof(data, "[DoH] A: %u.%u.%u.%u", - a->ip.v4[0], a->ip.v4[1], - a->ip.v4[2], a->ip.v4[3]); - } - else if(a->type == CURL_DNS_TYPE_AAAA) { - int j; - char buffer[128] = "[DoH] AAAA: "; - size_t len = strlen(buffer); - char *ptr = &buffer[len]; - len = sizeof(buffer) - len; - for(j = 0; j < 16; j += 2) { - size_t l; - curl_msnprintf(ptr, len, "%s%02x%02x", j ? ":" : "", - d->addr[i].ip.v6[j], - d->addr[i].ip.v6[j + 1]); - l = strlen(ptr); - len -= l; - ptr += l; - } - infof(data, "%s", buffer); - } - } -#ifdef USE_HTTPSRR - for(i = 0; i < d->numhttps_rrs; i++) { -#if defined(DEBUGBUILD) && defined(CURLVERBOSE) - doh_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 /* USE_HTTPSRR */ - for(i = 0; i < d->numcname; i++) { - infof(data, "CNAME: %s", curlx_dyn_ptr(&d->cname[i])); - } -} -#else -#define doh_show(x, y) -#endif - /* - * doh2ai() - * * This function returns a pointer to the first element of a newly allocated * Curl_addrinfo struct linked list filled with the data from a set of DoH * lookups. Curl_addrinfo is meant to work like the addrinfo struct does for @@ -936,7 +777,6 @@ static void doh_show(struct Curl_easy *data, * Curl_freeaddrinfo(). For each successful call to this function there * must be an associated call later to Curl_freeaddrinfo(). */ - static CURLcode doh2ai(const struct dohentry *de, const char *hostname, int port, struct Curl_addrinfo **aip) { @@ -947,14 +787,9 @@ static CURLcode doh2ai(const struct dohentry *de, const char *hostname, #ifdef USE_IPV6 struct sockaddr_in6 *addr6; #endif + size_t hostlen = strlen(hostname) + 1; /* include null-terminator */ CURLcode result = CURLE_OK; int i; - size_t hostlen = strlen(hostname) + 1; /* include null-terminator */ - - DEBUGASSERT(de); - - if(!de->numaddr) - return CURLE_COULDNT_RESOLVE_HOST; for(i = 0; i < de->numaddr; i++) { size_t ss_size; @@ -1032,35 +867,16 @@ static CURLcode doh2ai(const struct dohentry *de, const char *hostname, return result; } -#ifdef CURLVERBOSE -static const char *doh_type2name(DNStype dnstype) -{ - switch(dnstype) { - case CURL_DNS_TYPE_A: - return "A"; - case CURL_DNS_TYPE_AAAA: - return "AAAA"; -#ifdef USE_HTTPSRR - case CURL_DNS_TYPE_HTTPS: - return "HTTPS"; -#endif - default: - return "unknown"; - } -} -#endif - /* @unittest 1655 */ UNITTEST void de_cleanup(struct dohentry *d); UNITTEST void de_cleanup(struct dohentry *d) { - int i = 0; - for(i = 0; i < d->numcname; i++) { - curlx_dyn_free(&d->cname[i]); - } #ifdef USE_HTTPSRR + int i = 0; for(i = 0; i < d->numhttps_rrs; i++) curlx_safefree(d->https_rrs[i].val); +#else + (void)d; #endif } @@ -1182,51 +998,149 @@ UNITTEST CURLcode doh_resp_decode_httpsrr(struct Curl_easy *data, len -= plen; expected_min_pcode = pcode + 1; } - DEBUGASSERT(!len); *hrr = lhrr; return CURLE_OK; err: - Curl_httpsrr_cleanup(lhrr); - curlx_safefree(lhrr); + Curl_httpsrr_destroy(lhrr); return result; } -#if defined(DEBUGBUILD) && defined(CURLVERBOSE) -static void doh_print_httpsrr(struct Curl_easy *data, - struct Curl_https_rrinfo *hrr) +#endif /* USE_HTTPSRR */ + +/* called from multi when a sub transfer, e.g. doh probe, is done. + * Parse the response and set the results in the `async` context + * of master, using the id from the probe's CURL_EZM_DOH_PROBE + * meta data. */ +static void doh_probe_done(struct Curl_easy *doh, + struct Curl_easy *master, CURLcode result) { - DEBUGASSERT(hrr); - infof(data, "HTTPS RR: priority %d, target: %s", hrr->priority, hrr->target); - if(hrr->alpns[0] != ALPN_none) - infof(data, "HTTPS RR: alpns %u %u %u %u", - hrr->alpns[0], hrr->alpns[1], hrr->alpns[2], hrr->alpns[3]); - 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) { - doh_print_buf(data, "HTTPS RR: ipv4hints", - hrr->ipv4hints, hrr->ipv4hints_len); + struct Curl_resolv_async *async = NULL; + struct doh_probes *dohp = NULL; + struct doh_request *doh_req = NULL; + struct Curl_addrinfo **pdest_ai; + struct dohentry de; + int slot, httpcode; + + de_init(&de); + doh_req = Curl_meta_get(doh, CURL_EZM_DOH_PROBE); + if(!doh_req) { + /* transfer `doh` is not a DoH probe. */ + DEBUGASSERT(0); + goto out; } - else - infof(data, "HTTPS RR: no ipv4hints"); - if(hrr->echconfiglist) { - doh_print_buf(data, "HTTPS RR: ECHConfigList", - hrr->echconfiglist, hrr->echconfiglist_len); + + async = Curl_async_get(master, doh_req->resolv_id); + if(!async) { + CURL_TRC_DNS(master, "[%u] ignoring outdated DoH response", + doh_req->resolv_id); + goto out; } - else - infof(data, "HTTPS RR: no ECHConfigList"); - if(hrr->ipv6hints) { - doh_print_buf(data, "HTTPS RR: ipv6hint", - hrr->ipv6hints, hrr->ipv6hints_len); + dohp = async->doh; + + for(slot = 0; slot < DOH_SLOT_COUNT; ++slot) { + if(dohp->probe_mid[slot] == doh->mid) + break; } - else - infof(data, "HTTPS RR: no ipv6hints"); -} -# endif + /* We really should have found the slot where to store the response */ + if(slot >= DOH_SLOT_COUNT) { + failf(master, "DoH: unknown sub request done"); + DEBUGASSERT(0); + goto out; + } + + async->queries_ongoing--; + dohp = async->doh; + httpcode = doh->info.httpcode; + switch(slot) { + case DOH_SLOT_IPV4: + async->dns_responses |= CURL_DNSQ_A; + break; +#ifdef USE_IPV6 + case DOH_SLOT_IPV6: + async->dns_responses |= CURL_DNSQ_AAAA; + break; #endif +#ifdef USE_HTTPSRR + case DOH_SLOT_HTTPS_RR: + async->dns_responses |= CURL_DNSQ_HTTPS; + break; +#endif + default: + DEBUGASSERT(0); + break; + } + + if(result) { + dohp->probe_rc[slot] = DOH_HTTP_FAILED; + infof(doh, "[DoH] [%s] error: %s", + doh_type2name(doh_req->dnstype), curl_easy_strerror(result)); + goto out; + } + else if((httpcode < 200) || (httpcode >= 300)) { + dohp->probe_rc[slot] = DOH_HTTP_FAILED; + infof(doh, "[DoH] [%s] error: HTTP status %d", + doh_type2name(doh_req->dnstype), httpcode); + goto out; + } + + dohp->probe_rc[slot] = doh_resp_decode(curlx_dyn_uptr(&doh_req->resp_body), + curlx_dyn_len(&doh_req->resp_body), + doh_req->dnstype, &de); + if(dohp->probe_rc[slot]) { +#ifdef USE_HTTPSRR + if((dohp->probe_rc[slot] == DOH_NO_CONTENT) && + (doh_req->dnstype == CURL_DNS_TYPE_HTTPS)) { + dohp->probe_rc[slot] = DOH_DNS_NXDOMAIN; + } +#endif + infof(doh, "[DoH] [%s] error decoding response: %s", + doh_type2name(doh_req->dnstype), + doh_strerror(dohp->probe_rc[slot])); + goto out; + } + + if(doh_req->dnstype == CURL_DNS_TYPE_A) + pdest_ai = &async->ai_A; + else if(doh_req->dnstype == CURL_DNS_TYPE_AAAA) + pdest_ai = &async->ai_AAAA; + else + pdest_ai = NULL; + + if(pdest_ai && de.numaddr) { + if(*pdest_ai) { + Curl_freeaddrinfo(*pdest_ai); + *pdest_ai = NULL; + } + result = doh2ai(&de, async->peer->hostname, async->peer->port, pdest_ai); + if(result) { /* hard failure on our side, fail completely */ + infof(doh, "[DoH] [%s] error creating addrinfo: %s", + doh_type2name(doh_req->dnstype), curl_easy_strerror(result)); + dohp->probe_rc[slot] = DOH_OOM; + async->result = result; + } + } +#ifdef USE_HTTPSRR + else if((doh_req->dnstype == CURL_DNS_TYPE_HTTPS) && de.numhttps_rrs) { + CURL_TRC_DNS(doh, "[HTTPS] got %d records", de.numhttps_rrs); + result = doh_resp_decode_httpsrr(doh, de.https_rrs->val, + de.https_rrs->len, &async->httpsrr); + if(result) { + dohp->probe_rc[slot] = DOH_HTTP_FAILED; + infof(doh, "[DoH] error decoding HTTPS RR: %s", + curl_easy_strerror(result)); + goto out; + } + } +#endif /* USE_HTTPSRR */ + + /* DoH request complete, run master to act on results */ + infof(doh, "DoH request complete, %u to go", async->queries_ongoing); + +out: + Curl_multi_mark_dirty(master); + de_cleanup(&de); + Curl_meta_remove(doh, CURL_EZM_DOH_PROBE); +} CURLcode Curl_doh_take_result(struct Curl_easy *data, struct Curl_resolv_async *async, @@ -1234,97 +1148,77 @@ CURLcode Curl_doh_take_result(struct Curl_easy *data, { struct doh_probes *dohp = async->doh; CURLcode result = CURLE_OK; - struct dohentry de; *pdns = NULL; /* defaults to no response */ if(!dohp) return CURLE_OUT_OF_MEMORY; - if(dohp->probe_resp[DOH_SLOT_IPV4].probe_mid == UINT32_MAX && - dohp->probe_resp[DOH_SLOT_IPV6].probe_mid == UINT32_MAX) { - failf(data, "Could not DoH-resolve: %s", dohp->host); + async->negative_answer = FALSE; + if(async->result) { + result = async->result; + goto out; + } + + if(CURL_DNSQ_IS_ADDR(async->dns_queries) && + dohp->probe_mid[DOH_SLOT_IPV4] == UINT32_MAX && + dohp->probe_mid[DOH_SLOT_IPV6] == UINT32_MAX) { + failf(data, "Could not DoH-resolve: %s", async->peer->hostname); return async->for_proxy ? CURLE_COULDNT_RESOLVE_PROXY : CURLE_COULDNT_RESOLVE_HOST; } - else if(!dohp->pending) { - DOHcode rc[DOH_SLOT_COUNT]; + else if(!async->queries_ongoing) { + struct Curl_dns_entry *dns = NULL; + bool negative = TRUE; int slot; - memset(rc, 0, sizeof(rc)); /* remove DoH handles from multi handle and close them */ doh_close(data, async); /* parse the responses, create the struct and return it! */ - de_init(&de); for(slot = 0; slot < DOH_SLOT_COUNT; slot++) { - struct doh_response *p = &dohp->probe_resp[slot]; - if(!p->dnstype) - continue; - rc[slot] = doh_resp_decode(curlx_dyn_uptr(&p->body), - curlx_dyn_len(&p->body), - p->dnstype, &de); - if(rc[slot]) { - CURL_TRC_DNS(data, "DoH: %s type %s for %s", doh_strerror(rc[slot]), - doh_type2name(p->dnstype), dohp->host); - } + /* Failing without an NXDOMAIN answer - a SERVFAIL-class rcode or + an undecodable response - says nothing about the name. Such a + failure must not be cached as a negative entry. */ + if(dohp->probe_rc[slot] && (dohp->probe_rc[slot] != DOH_DNS_NXDOMAIN)) + negative = FALSE; } /* next slot */ - if(!rc[DOH_SLOT_IPV4] || !rc[DOH_SLOT_IPV6]) { - /* we have an address, of one kind or other */ - struct Curl_dns_entry *dns; - struct Curl_addrinfo *ai; - - if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_dns)) { - CURL_TRC_DNS(data, "hostname: %s", dohp->host); - doh_show(data, &de); - } - - result = doh2ai(&de, dohp->host, dohp->port, &ai); - if(result) - goto error; - - /* we got a response, create a dns entry. */ - dns = Curl_dnscache_mk_entry(data, async->dns_queries, - &ai, dohp->host, dohp->port); + if(async->ai_A || async->ai_AAAA) { + dns = Curl_dnsc_mk_addr2( + data, async->dns_queries, &async->ai_A, &async->ai_AAAA, async->peer); if(!dns) { result = CURLE_OUT_OF_MEMORY; - goto error; + goto out; } - - /* Now add and HTTPSRR information if we have */ + } #ifdef USE_HTTPSRR - if(de.numhttps_rrs > 0 && result == CURLE_OK) { - struct Curl_https_rrinfo *hrr = NULL; - result = doh_resp_decode_httpsrr(data, de.https_rrs->val, - de.https_rrs->len, &hrr); - if(result) { - infof(data, "Failed to decode HTTPS RR"); - Curl_dns_entry_unlink(data, &dns); - goto error; - } - infof(data, "Some HTTPS RR to process"); -#if defined(DEBUGBUILD) && defined(CURLVERBOSE) - doh_print_httpsrr(data, hrr); -#endif - Curl_dns_entry_set_https_rr(dns, hrr); + else if((async->dns_queries & CURL_DNSQ_HTTPS) && + !dohp->probe_rc[DOH_SLOT_HTTPS_RR]) { + Curl_httpsrr_trace(data, async->httpsrr); + dns = Curl_dnsc_mk_https(data, &async->httpsrr, async->peer); + if(!dns) { + result = CURLE_OUT_OF_MEMORY; + goto out; } + } #endif /* USE_HTTPSRR */ - - /* and add the entry to the cache */ - result = Curl_dnscache_add(data, dns); - *pdns = dns; - } /* address processing done */ else { + /* every query failed. Only NXDOMAIN answers for all of them + make this a negative answer, eligible for caching. */ + async->negative_answer = negative; result = async->for_proxy ? CURLE_COULDNT_RESOLVE_PROXY : CURLE_COULDNT_RESOLVE_HOST; } - } /* !dohp->pending */ + /* and add the entry to the cache */ + if(dns) + result = Curl_dnscache_add(data, dns); + *pdns = dns; + } /* !async->queries_ongoing */ else /* wait for pending DoH transactions to complete */ return CURLE_AGAIN; -error: - de_cleanup(&de); +out: Curl_doh_cleanup(data, async); return result; } @@ -1338,23 +1232,23 @@ static void doh_close(struct Curl_easy *data, uint32_t mid; size_t slot; for(slot = 0; slot < DOH_SLOT_COUNT; slot++) { - mid = doh->probe_resp[slot].probe_mid; + mid = doh->probe_mid[slot]; if(mid == UINT32_MAX) continue; - doh->probe_resp[slot].probe_mid = UINT32_MAX; + doh->probe_mid[slot] = UINT32_MAX; /* should have been called before data is removed from multi handle */ DEBUGASSERT(data->multi); probe_data = data->multi ? Curl_multi_get_easy(data->multi, mid) : NULL; if(!probe_data) { - DEBUGF(infof(data, "Curl_doh_close: xfer for mid=%u not found!", - doh->probe_resp[slot].probe_mid)); + DEBUGF(infof(data, "Curl_doh_close: xfer for mid=%u not found!", mid)); continue; } + probe_data->sub_xfer_done = NULL; /* No longer interested in result */ /* data->multi might already be reset at this time */ - curl_multi_remove_handle(data->multi, probe_data); + Curl_multi_remove_handle(data->multi, probe_data); Curl_close(&probe_data); } - data->sub_xfer_done = NULL; + CURL_TRC_DNS(data, "[DoH] probe done"); } } @@ -1363,11 +1257,7 @@ void Curl_doh_cleanup(struct Curl_easy *data, { struct doh_probes *dohp = async->doh; if(dohp) { - int i; doh_close(data, async); - for(i = 0; i < DOH_SLOT_COUNT; ++i) { - curlx_dyn_free(&dohp->probe_resp[i].body); - } curlx_safefree(async->doh); } } diff --git a/Utilities/cmcurl/lib/doh.h b/Utilities/cmcurl/lib/vdns/doh.h similarity index 91% rename from Utilities/cmcurl/lib/doh.h rename to Utilities/cmcurl/lib/vdns/doh.h index cd2aad9254..a6358027a7 100644 --- a/Utilities/cmcurl/lib/doh.h +++ b/Utilities/cmcurl/lib/vdns/doh.h @@ -39,12 +39,16 @@ typedef enum { DOH_OUT_OF_MEM, /* 5 */ DOH_DNS_RDATA_LEN, /* 6 */ DOH_DNS_MALFORMAT, /* 7 */ - DOH_DNS_BAD_RCODE, /* 8 - no such name */ + DOH_DNS_BAD_RCODE, /* 8 - unsuccessful rcode, not NXDOMAIN */ DOH_DNS_UNEXPECTED_TYPE, /* 9 */ DOH_DNS_UNEXPECTED_CLASS, /* 10 */ DOH_NO_CONTENT, /* 11 */ DOH_DNS_BAD_ID, /* 12 */ - DOH_DNS_NAME_TOO_LONG /* 13 */ + DOH_DNS_NAME_TOO_LONG, /* 13 */ + DOH_DNS_NXDOMAIN, /* 14 - no such name */ + DOH_HTTP_FAILED, /* failure at the HTTP level */ + DOH_OOM, /* out of memory */ + DOH_CODE_LAST /* Not used, limit */ } DOHcode; typedef enum { @@ -97,20 +101,11 @@ struct doh_request { DNStype dnstype; }; -struct doh_response { - uint32_t probe_mid; - struct dynbuf body; - DNStype dnstype; - CURLcode result; -}; - /* each transfer firing off DoH requests has this * as easy meta for CURL_EZM_DOH_MASTER */ struct doh_probes { - struct doh_response probe_resp[DOH_SLOT_COUNT]; - unsigned int pending; /* still outstanding probes */ - uint16_t port; - const char *host; + uint32_t probe_mid[DOH_SLOT_COUNT]; + DOHcode probe_rc[DOH_SLOT_COUNT]; }; /* @@ -125,7 +120,6 @@ CURLcode Curl_doh_take_result(struct Curl_easy *data, struct Curl_dns_entry **pdns); #define DOH_MAX_ADDR 24 -#define DOH_MAX_CNAME 4 #define DOH_MAX_HTTPS 4 struct dohaddr { @@ -152,11 +146,9 @@ struct dohhttps_rr { #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; diff --git a/Utilities/cmcurl/lib/hostip.c b/Utilities/cmcurl/lib/vdns/hostip.c similarity index 78% rename from Utilities/cmcurl/lib/hostip.c rename to Utilities/cmcurl/lib/vdns/hostip.c index a18d9a62bb..8464bd2917 100644 --- a/Utilities/cmcurl/lib/hostip.c +++ b/Utilities/cmcurl/lib/vdns/hostip.c @@ -46,16 +46,16 @@ #include "urldata.h" #include "curl_addrinfo.h" #include "curl_trc.h" -#include "dnscache.h" -#include "hostip.h" -#include "httpsrr.h" #include "url.h" #include "multiif.h" #include "progress.h" -#include "doh.h" #include "select.h" #include "strcase.h" #include "easy_lock.h" +#include "vdns/dnscache.h" +#include "vdns/doh.h" +#include "vdns/hostip.h" +#include "vdns/httpsrr.h" #include "curlx/inet_ntop.h" #include "curlx/inet_pton.h" #include "curlx/strcopy.h" @@ -342,11 +342,11 @@ static bool tailmatch(const char *full, size_t flen, } static CURLcode hostip_resolv_failed(struct Curl_easy *data, - const char *hostname, + struct Curl_peer *peer, bool for_proxy) { failf(data, "Could not resolve %s: %s", - for_proxy ? "proxy" : "host", hostname); + for_proxy ? "proxy" : "host", peer->hostname); return RESOLV_FAIL(for_proxy); } @@ -354,7 +354,7 @@ static bool can_resolve_dns_queries(struct Curl_easy *data, uint8_t dns_queries) { (void)data; - if((CURL_DNSQ_IP(dns_queries) == CURL_DNSQ_AAAA) && !ipv6works(data)) + if((CURL_DNSQ_IS_ADDR(dns_queries) == CURL_DNSQ_AAAA) && !ipv6works(data)) return FALSE; return TRUE; } @@ -363,13 +363,14 @@ CURLcode Curl_resolv_announce_start(struct Curl_easy *data, void *resolver) { if(data->set.resolver_start) { + struct Curl_mapi_guard guard; int rc; CURL_TRC_DNS(data, "announcing resolve to application"); - Curl_set_in_callback(data, TRUE); + CURL_CBAPI_START(&guard, data, easy_resolver_start); rc = data->set.resolver_start(resolver, NULL, data->set.resolver_start_client); - Curl_set_in_callback(data, FALSE); + CURL_CBAPI_END(&guard); if(rc) { CURL_TRC_DNS(data, "application aborted resolve"); return CURLE_ABORTED_BY_CALLBACK; @@ -382,14 +383,12 @@ CURLcode Curl_resolv_announce_start(struct Curl_easy *data, static struct Curl_resolv_async *hostip_async_new(struct Curl_easy *data, uint8_t dns_queries, - const char *hostname, - uint16_t port, + struct Curl_peer *peer, uint8_t transport, bool for_proxy, timediff_t timeout_ms) { struct Curl_resolv_async *async; - size_t hostlen = strlen(hostname); if(!data->multi) { DEBUGASSERT(0); @@ -397,7 +396,7 @@ static struct Curl_resolv_async *hostip_async_new(struct Curl_easy *data, } /* struct size already includes the NUL for hostname */ - async = curlx_calloc(1, sizeof(*async) + hostlen); + async = curlx_calloc(1, sizeof(*async)); if(!async) return NULL; @@ -411,18 +410,15 @@ static struct Curl_resolv_async *hostip_async_new(struct Curl_easy *data, else data->multi->last_resolv_id++; async->id = data->multi->last_resolv_id; + Curl_peer_link(&async->peer, peer); async->dns_queries = dns_queries; - async->port = port; async->transport = transport; async->for_proxy = for_proxy; async->start = *Curl_pgrs_now(data); async->timeout_ms = timeout_ms; - if(hostlen) { - memcpy(async->hostname, hostname, hostlen); - async->is_ipaddr = Curl_is_ipaddr(async->hostname); - if(async->is_ipaddr) - async->is_ipv4addr = Curl_is_ipv4addr(async->hostname); - } + async->is_ipaddr = Curl_is_ipaddr(peer->hostname); + if(async->is_ipaddr) + async->is_ipv4addr = Curl_is_ipv4addr(peer->hostname); return async; } @@ -445,19 +441,28 @@ static CURLcode hostip_resolv_take_result(struct Curl_easy *data, result = Curl_async_take_result(data, async, pdns); if(result == CURLE_AGAIN) { - CURL_TRC_DNS(data, "resolve incomplete, queries=%s, responses=%s, " + CURL_TRC_DNS(data, "[%s] resolve incomplete, responses=%s, " "ongoing=%d for %s:%d", Curl_resolv_query_str(async->dns_queries), Curl_resolv_query_str(async->dns_responses), - async->queries_ongoing, async->hostname, async->port); + async->queries_ongoing, + async->peer->hostname, async->peer->port); result = CURLE_OK; } - else if(result) { + else if(IS_RESOLV_FAIL(result)) { result = Curl_async_failed(data, async, NULL); } + else if(result) { + /* a local failure, not a resolve answer. Keep the error as it + is so it does not get treated as one. */ + CURL_TRC_DNS(data, "[%s] resolve error %d for %s:%u", + Curl_resolv_query_str(async->dns_queries), + (int)result, async->peer->hostname, async->peer->port); + } else { - CURL_TRC_DNS(data, "resolve complete for %s:%u", - async->hostname, async->port); + CURL_TRC_DNS(data, "[%s] resolve complete for %s:%u", + Curl_resolv_query_str(async->dns_queries), + async->peer->hostname, async->peer->port); DEBUGASSERT(*pdns); } @@ -465,12 +470,13 @@ static CURLcode hostip_resolv_take_result(struct Curl_easy *data, } timediff_t Curl_resolv_elapsed_ms(struct Curl_easy *data, - uint32_t resolv_id) + uint32_t resolv_id, + const struct curltime *pnow) { struct Curl_resolv_async *async = Curl_async_get(data, resolv_id); if(!async) return CURL_TIMEOUT_RESOLVE_MS; - return curlx_ptimediff_ms(Curl_pgrs_now(data), &async->start); + return curlx_ptimediff_ms(pnow, &async->start); } bool Curl_resolv_has_answers(struct Curl_easy *data, @@ -495,97 +501,122 @@ const struct Curl_addrinfo *Curl_resolv_get_ai(struct Curl_easy *data, unsigned int index) { struct Curl_resolv_async *async = Curl_async_get(data, resolv_id); - (void)index; - if(!async) - return NULL; - if((ai_family == AF_INET) && !(async->dns_queries & CURL_DNSQ_A)) + if(!async || !CURL_DNSQ_IS_ADDR(async->dns_queries)) return NULL; + switch(ai_family) { + case AF_INET: + return Curl_addrinfo_get(async->ai_A, ai_family, index); #ifdef USE_IPV6 - if((ai_family == AF_INET6) && !(async->dns_queries & CURL_DNSQ_AAAA)) - return NULL; + case AF_INET6: + return Curl_addrinfo_get(async->ai_AAAA, ai_family, index); #endif - return Curl_async_get_ai(data, async, ai_family, index); + default: + return NULL; + } } #ifdef USE_HTTPSRR + +CURLcode Curl_resolv_https(struct Curl_easy *data, + struct Curl_peer *peer, + bool for_proxy, + timediff_t timeout_ms, + uint32_t *presolv_id, + struct Curl_dns_entry **pdns) +{ + return Curl_resolv(data, peer, CURL_DNSQ_HTTPS, TRNSPRT_TCP, + for_proxy, timeout_ms, presolv_id, pdns); +} + const struct Curl_https_rrinfo * Curl_resolv_get_https(struct Curl_easy *data, uint32_t resolv_id) { - struct Curl_resolv_async *async = Curl_async_get(data, resolv_id); - if(!async) + struct Curl_resolv_async *async; + if(!Curl_resolv_knows_https(data, resolv_id)) return NULL; - return Curl_async_get_https(data, async); + async = Curl_async_get(data, resolv_id); + return async ? async->httpsrr : NULL; } bool Curl_resolv_knows_https(struct Curl_easy *data, uint32_t resolv_id) { struct Curl_resolv_async *async = Curl_async_get(data, resolv_id); - if(!async) - return TRUE; - return Curl_async_knows_https(data, async); + if(async && (async->dns_queries & CURL_DNSQ_HTTPS)) + return ((async->dns_responses & CURL_DNSQ_HTTPS) || + !async->queries_ongoing); + return TRUE; /* we know it will never come */ } + #endif /* USE_HTTPSRR */ #endif /* USE_CURL_ASYNC */ +/* Start resolving. `*pnegative` is only meaningful when this returns + a CURLE_COULDNT_RESOLVE_* failure: TRUE when the resolver answered + that the name does not exist, FALSE on transient or local failures + that must not be cached as negative entries. */ static CURLcode hostip_resolv_start(struct Curl_easy *data, uint8_t dns_queries, - const char *hostname, - uint16_t port, + struct Curl_peer *peer, uint8_t transport, bool for_proxy, timediff_t timeout_ms, bool allowDOH, uint32_t *presolv_id, - struct Curl_dns_entry **pdns) + struct Curl_dns_entry **pdns, + bool *pnegative) { #ifdef USE_CURL_ASYNC struct Curl_resolv_async *async = NULL; #endif struct Curl_addrinfo *addr = NULL; size_t hostname_len; + bool addr_queries = (dns_queries & (CURL_DNSQ_A | CURL_DNSQ_AAAA)); CURLcode result = CURLE_OK; + *pnegative = FALSE; + (void)timeout_ms; /* not in all ifdefs */ *presolv_id = 0; *pdns = NULL; /* Check for "known" things to resolve ourselves. */ + if(addr_queries) { #ifndef USE_RESOLVE_ON_IPS - if(Curl_is_ipaddr(hostname)) { - /* test655 verifies that the announce is done, even though there - * is no real resolving. So, keep doing this. */ - result = Curl_resolv_announce_start(data, NULL); - if(result) + if(Curl_is_ipaddr(peer->hostname)) { + /* test655 verifies that the announce is done, even though there + * is no real resolving. So, keep doing this. */ + result = Curl_resolv_announce_start(data, NULL); + if(result) + goto out; + /* shortcut literal IP addresses, if we are not told to resolve them. */ + result = Curl_str2addr(peer->hostname, peer->port, &addr); goto out; - /* shortcut literal IP addresses, if we are not told to resolve them. */ - result = Curl_str2addr(hostname, port, &addr); - goto out; - } + } #endif - hostname_len = strlen(hostname); - if(curl_strequal(hostname, "localhost") || - curl_strequal(hostname, "localhost.") || - tailmatch(hostname, hostname_len, STRCONST(".localhost")) || - tailmatch(hostname, hostname_len, STRCONST(".localhost."))) { - result = Curl_resolv_announce_start(data, NULL); - if(result) + hostname_len = strlen(peer->hostname); + if(curl_strequal(peer->hostname, "localhost") || + curl_strequal(peer->hostname, "localhost.") || + tailmatch(peer->hostname, hostname_len, STRCONST(".localhost")) || + tailmatch(peer->hostname, hostname_len, STRCONST(".localhost."))) { + result = Curl_resolv_announce_start(data, NULL); + if(result) + goto out; + addr = get_localhost(peer->port, peer->hostname); + if(!addr) + result = CURLE_OUT_OF_MEMORY; goto out; - addr = get_localhost(port, hostname); - if(!addr) - result = CURLE_OUT_OF_MEMORY; - goto out; + } } - #ifndef CURL_DISABLE_DOH - if(!Curl_is_ipaddr(hostname) && allowDOH && data->set.doh) { + if(!Curl_is_ipaddr(peer->hostname) && allowDOH && data->set.doh) { result = Curl_resolv_announce_start(data, NULL); if(result) goto out; if(!async) { - async = hostip_async_new(data, dns_queries, hostname, port, - transport, for_proxy, timeout_ms); + async = hostip_async_new(data, dns_queries, peer, transport, + for_proxy, timeout_ms); if(!async) { result = CURLE_OUT_OF_MEMORY; goto out; @@ -607,8 +638,8 @@ static CURLcode hostip_resolv_start(struct Curl_easy *data, #ifdef CURLRES_ASYNCH (void)addr; if(!async) { - async = hostip_async_new(data, dns_queries, hostname, port, - transport, for_proxy, timeout_ms); + async = hostip_async_new(data, dns_queries, peer, transport, + for_proxy, timeout_ms); if(!async) { result = CURLE_OUT_OF_MEMORY; goto out; @@ -627,9 +658,14 @@ static CURLcode hostip_resolv_start(struct Curl_easy *data, result = Curl_resolv_announce_start(data, NULL); if(result) goto out; - addr = Curl_sync_getaddrinfo(data, dns_queries, hostname, port, transport); - if(!addr) + addr = Curl_sync_getaddrinfo(data, dns_queries, peer->hostname, peer->port, + transport); + if(!addr) { result = RESOLV_FAIL(for_proxy); + /* the synchronous resolvers do not tell a transient failure from + an authoritative negative answer, treat it as before */ + *pnegative = TRUE; + } #endif out: @@ -637,7 +673,7 @@ out: if(addr) { /* we got a response, create a dns entry, add to cache, return */ DEBUGASSERT(!*pdns); - *pdns = Curl_dnscache_mk_entry(data, dns_queries, &addr, hostname, port); + *pdns = Curl_dnsc_mk_addr(data, dns_queries, &addr, peer); if(!*pdns) result = CURLE_OUT_OF_MEMORY; } @@ -657,6 +693,7 @@ out: data->state.async = async; } else { + *pnegative = !!async->negative_answer; Curl_async_destroy(data, async); } } @@ -666,8 +703,7 @@ out: static CURLcode hostip_resolv(struct Curl_easy *data, uint8_t dns_queries, - const char *hostname, - uint16_t port, + struct Curl_peer *peer, uint8_t transport, bool for_proxy, timediff_t timeout_ms, @@ -678,6 +714,7 @@ static CURLcode hostip_resolv(struct Curl_easy *data, size_t hostname_len; CURLcode result = RESOLV_FAIL(for_proxy); bool cache_dns = FALSE; + bool negative = FALSE; (void)timeout_ms; /* not used in all ifdefs */ *presolv_id = 0; @@ -688,53 +725,57 @@ static CURLcode hostip_resolv(struct Curl_easy *data, #endif /* We should intentionally error and not resolve .onion TLDs */ - hostname_len = strlen(hostname); + hostname_len = strlen(peer->hostname); DEBUGASSERT(hostname_len); if(hostname_len >= 7 && - (curl_strequal(&hostname[hostname_len - 6], ".onion") || - curl_strequal(&hostname[hostname_len - 7], ".onion."))) { + (curl_strequal(&peer->hostname[hostname_len - 6], ".onion") || + curl_strequal(&peer->hostname[hostname_len - 7], ".onion."))) { failf(data, "Not resolving .onion address (RFC 7686)"); goto out; } #ifdef DEBUGBUILD - CURL_TRC_DNS(data, "hostip_resolv(%s:%u, queries=%s)", - hostname, port, Curl_resolv_query_str(dns_queries)); - if((CURL_DNSQ_IP(dns_queries) == CURL_DNSQ_AAAA) && + CURL_TRC_DNS(data, "[%s] hostip_resolv(%s:%u)", + Curl_resolv_query_str(dns_queries), peer->hostname, peer->port); + if((CURL_DNSQ_IS_ADDR(dns_queries) == CURL_DNSQ_AAAA) && getenv("CURL_DBG_RESOLV_FAIL_IPV6")) { infof(data, "DEBUG fail ipv6 resolve"); - result = hostip_resolv_failed(data, hostname, for_proxy); + result = hostip_resolv_failed(data, peer, for_proxy); goto out; } #endif /* Let's check our DNS cache first */ - result = Curl_dnscache_get(data, dns_queries, hostname, port, pdns); + result = Curl_dnscache_get(data, dns_queries, peer, pdns); if(*pdns) { - infof(data, "Hostname %s was found in DNS cache", hostname); + infof(data, "Hostname %s was found in DNS cache", peer->hostname); result = CURLE_OK; } else if(result) { infof(data, "Negative DNS entry"); - result = hostip_resolv_failed(data, hostname, for_proxy); + result = hostip_resolv_failed(data, peer, for_proxy); } else { /* No luck, we need to start resolving. */ cache_dns = TRUE; - result = hostip_resolv_start(data, dns_queries, hostname, port, - transport, for_proxy, timeout_ms, allowDOH, - presolv_id, pdns); + result = hostip_resolv_start(data, dns_queries, peer, transport, + for_proxy, timeout_ms, allowDOH, + presolv_id, pdns, &negative); + CURL_TRC_DNS(data, "[%s] hostip_resolv started -> %d", + Curl_resolv_query_str(dns_queries), (int)result); } out: if(result && (result != CURLE_AGAIN)) { Curl_dns_entry_unlink(data, pdns); if(IS_RESOLV_FAIL(result)) { - if(cache_dns) - Curl_dnscache_add_negative(data, dns_queries, hostname, port); - failf(data, "Could not resolve: %s:%u", hostname, port); + if(cache_dns && negative) + Curl_dnscache_add_negative(data, dns_queries, peer); + if(dns_queries & (CURL_DNSQ_A | CURL_DNSQ_AAAA)) + failf(data, "Could not resolve: %s:%u", peer->hostname, peer->port); } else { - failf(data, "Error %d resolving %s:%u", (int)result, hostname, port); + failf(data, "Error %d resolving %s:%u", + (int)result, peer->hostname, peer->port); } } else if(cache_dns && *pdns) { @@ -753,13 +794,19 @@ CURLcode Curl_resolv_blocking(struct Curl_easy *data, uint8_t transport, struct Curl_dns_entry **pdns) { + struct Curl_peer *peer = NULL; CURLcode result; uint32_t resolv_id; + DEBUGASSERT(hostname && *hostname); *pdns = NULL; + + result = Curl_peer_create(data, data->conn->scheme, hostname, port, &peer); + if(result) + goto out; + /* We cannot do a blocking resolve using DoH currently */ - result = hostip_resolv(data, dns_queries, - hostname, port, transport, FALSE, 0, FALSE, + result = hostip_resolv(data, dns_queries, peer, transport, FALSE, 0, FALSE, &resolv_id, pdns); switch(result) { case CURLE_OK: @@ -775,6 +822,9 @@ CURLcode Curl_resolv_blocking(struct Curl_easy *data, default: break; } + +out: + Curl_peer_unlink(&peer); return result; } @@ -792,8 +842,7 @@ CURL_NORETURN static void alarmfunc(int sig) static CURLcode resolv_alarm_timeout(struct Curl_easy *data, uint8_t dns_queries, - const char *hostname, - uint16_t port, + struct Curl_peer *peer, uint8_t transport, bool for_proxy, timediff_t timeout_ms, @@ -813,7 +862,7 @@ static CURLcode resolv_alarm_timeout(struct Curl_easy *data, volatile unsigned int prev_alarm = 0; CURLcode result; - DEBUGASSERT(hostname && *hostname); + DEBUGASSERT(peer->hostname && *peer->hostname); DEBUGASSERT(timeout_ms > 0); DEBUGASSERT(!data->set.no_signal); #ifndef CURL_DISABLE_DOH @@ -874,7 +923,7 @@ static CURLcode resolv_alarm_timeout(struct Curl_easy *data, /* Perform the actual name resolution. This might be interrupted by an * alarm if it takes too long. */ - result = hostip_resolv(data, dns_queries, hostname, port, transport, + result = hostip_resolv(data, dns_queries, peer, transport, for_proxy, timeout_ms, FALSE, presolv_id, entry); clean_up: @@ -928,27 +977,26 @@ clean_up: #ifdef USE_UNIX_SOCKETS static CURLcode resolv_unix(struct Curl_easy *data, - const char *unix_path, - bool abstract_path, + struct Curl_peer *peer, struct Curl_dns_entry **pdns) { struct Curl_addrinfo *addr; CURLcode result; - DEBUGASSERT(unix_path); + DEBUGASSERT(peer->unix_socket); *pdns = NULL; - result = Curl_unix2addr(unix_path, abstract_path, &addr); + result = Curl_unix2addr(peer->hostname, (bool)peer->abstract_uds, &addr); if(result) { if(result == CURLE_TOO_LARGE) { /* Long paths are not supported for now */ - failf(data, "Unix socket path too long: '%s'", unix_path); + failf(data, "Unix socket path too long: '%s'", peer->hostname); result = CURLE_COULDNT_RESOLVE_HOST; } return result; } - *pdns = Curl_dnscache_mk_entry(data, 0, &addr, NULL, 0); + *pdns = Curl_dnsc_mk_addr(data, 0, &addr, peer); return *pdns ? CURLE_OK : CURLE_OUT_OF_MEMORY; } #endif /* USE_UNIX_SOCKETS */ @@ -992,22 +1040,23 @@ CURLcode Curl_resolv(struct Curl_easy *data, timeout_ms = CURL_TIMEOUT_RESOLVE_MS; #ifdef USE_UNIX_SOCKETS - if(peer->unix_socket) - return resolv_unix(data, peer->hostname, (bool)peer->abstract_uds, pdns); + if((dns_queries & CURL_DNSQ_ADDR) && peer->unix_socket) + return resolv_unix(data, peer, pdns); #else if(peer->unix_socket) - return hostip_resolv_failed(data, peer->hostname, for_proxy); + return hostip_resolv_failed(data, peer, for_proxy); #endif #ifdef USE_ALARM_TIMEOUT - if(timeout_ms && data->set.no_signal) { - /* Cannot use ALARM when signals are disabled */ - timeout_ms = 0; - } - if(timeout_ms && !Curl_doh_wanted(data)) { - return resolv_alarm_timeout(data, dns_queries, peer->hostname, peer->port, - transport, for_proxy, timeout_ms, presolv_id, - pdns); + if(dns_queries & CURL_DNSQ_ADDR) { + if(timeout_ms && data->set.no_signal) { + /* Cannot use ALARM when signals are disabled */ + timeout_ms = 0; + } + if(timeout_ms && !Curl_doh_wanted(data)) { + return resolv_alarm_timeout(data, dns_queries, peer, transport, + for_proxy, timeout_ms, presolv_id, pdns); + } } #endif /* !USE_ALARM_TIMEOUT */ @@ -1016,9 +1065,8 @@ CURLcode Curl_resolv(struct Curl_easy *data, infof(data, "timeout on name lookup is not supported"); #endif - return hostip_resolv(data, dns_queries, peer->hostname, peer->port, - transport, for_proxy, timeout_ms, TRUE, presolv_id, - pdns); + return hostip_resolv(data, dns_queries, peer, transport, + for_proxy, timeout_ms, TRUE, presolv_id, pdns); } #ifdef USE_CURL_ASYNC @@ -1045,11 +1093,10 @@ CURLcode Curl_resolv_take_result(struct Curl_easy *data, uint32_t resolv_id, return CURLE_FAILED_INIT; /* check if we have the name resolved by now (from someone else) */ - result = Curl_dnscache_get(data, async->dns_queries, - async->hostname, async->port, pdns); + result = Curl_dnscache_get(data, async->dns_queries, async->peer, pdns); if(*pdns) { /* Tell a possibly async resolver we no longer need the results. */ - infof(data, "Hostname '%s' was found in DNS cache", async->hostname); + infof(data, "Hostname '%s' was found in DNS cache", async->peer->hostname); Curl_async_shutdown(data, async); return CURLE_OK; } @@ -1067,13 +1114,20 @@ CURLcode Curl_resolv_take_result(struct Curl_easy *data, uint32_t resolv_id, Curl_dns_entry_unlink(data, pdns); } else if(IS_RESOLV_FAIL(result)) { - Curl_dnscache_add_negative(data, async->dns_queries, - async->hostname, async->port); - failf(data, "Could not resolve: %s:%u", async->hostname, async->port); + /* Only cache the failure when the resolver answered that the + name does not exist. Transient failures, e.g. an unreachable + or overloaded DNS server or local resource shortages, say + nothing about the name and would poison the cache for every + transfer using it. */ + if(async->negative_answer) + Curl_dnscache_add_negative(data, async->dns_queries, async->peer); + if(async->dns_queries & (CURL_DNSQ_A | CURL_DNSQ_AAAA)) + failf(data, "Could not resolve: %s:%u", + async->peer->hostname, async->peer->port); } else if(result) { failf(data, "Error %d resolving %s:%u", - (int)result, async->hostname, async->port); + (int)result, async->peer->hostname, async->peer->port); } return result; } diff --git a/Utilities/cmcurl/lib/hostip.h b/Utilities/cmcurl/lib/vdns/hostip.h similarity index 87% rename from Utilities/cmcurl/lib/hostip.h rename to Utilities/cmcurl/lib/vdns/hostip.h index 45370ec48e..43172101f2 100644 --- a/Utilities/cmcurl/lib/hostip.h +++ b/Utilities/cmcurl/lib/vdns/hostip.h @@ -52,8 +52,8 @@ struct Curl_peer; #define CURL_DNSQ_AAAA (1U << 1) #define CURL_DNSQ_HTTPS (1U << 2) -#define CURL_DNSQ_ALL (CURL_DNSQ_A | CURL_DNSQ_AAAA | CURL_DNSQ_HTTPS) -#define CURL_DNSQ_IP(x) (uint8_t)((x)&(CURL_DNSQ_A | CURL_DNSQ_AAAA)) +#define CURL_DNSQ_ADDR (uint8_t)(CURL_DNSQ_A | CURL_DNSQ_AAAA) +#define CURL_DNSQ_IS_ADDR(x) (uint8_t)((x)&(CURL_DNSQ_ADDR)) #ifdef CURLVERBOSE const char *Curl_resolv_query_str(uint8_t dns_queries); @@ -140,7 +140,8 @@ void Curl_resolv_destroy(struct Curl_easy *data, uint32_t resolv_id); /* How much time has gone by since start of resolve. * Returns CURL_TIMEOUT_RESOLVE_MS if `resolv_id` is no longer valid. */ timediff_t Curl_resolv_elapsed_ms(struct Curl_easy *data, - uint32_t resolv_id); + uint32_t resolv_id, + const struct curltime *pnow); /* Return TRUE if `resolv_id` has answers (positive or negative) to * all queries in `dns_queries`. @@ -153,6 +154,22 @@ const struct Curl_addrinfo *Curl_resolv_get_ai(struct Curl_easy *data, int ai_family, unsigned int index); #ifdef USE_HTTPSRR + +/* Start DNS resolving for HTTPS records. Returns + * - CURLE_OK: `*pdns` is the resolved DNS entry (needs to be unlinked). + * `*presolv_id` is 0. + * - CURLE_AGAIN: resolve is asynchronous and not finished yet. + * `presolv_id` is the identifier for querying results later. + * - other: the operation failed, `*pdns` is NULL, `*presolv_id` is 0. + */ +CURLcode Curl_resolv_https(struct Curl_easy *data, + struct Curl_peer *peer, + bool for_proxy, + timediff_t timeout_ms, + uint32_t *presolv_id, + struct Curl_dns_entry **pdns); + + const struct Curl_https_rrinfo *Curl_resolv_get_https(struct Curl_easy *data, uint32_t resolv_id); bool Curl_resolv_knows_https(struct Curl_easy *data, uint32_t resolv_id); @@ -162,7 +179,7 @@ bool Curl_resolv_knows_https(struct Curl_easy *data, uint32_t resolv_id); #define Curl_resolv_shutdown_all(x) Curl_nop_stmt #define Curl_resolv_destroy_all(x) Curl_nop_stmt #define Curl_resolv_take_result(x, y, z) CURLE_NOT_BUILT_IN -#define Curl_resolv_elapsed_ms(x, y) CURL_TIMEOUT_RESOLVE_MS +#define Curl_resolv_elapsed_ms(x, y, z) CURL_TIMEOUT_RESOLVE_MS #define Curl_resolv_has_answers(x, y, z) TRUE #define Curl_resolv_get_ai(x, y, z, a) NULL #define Curl_resolv_get_https(x, y) NULL diff --git a/Utilities/cmcurl/lib/hostip4.c b/Utilities/cmcurl/lib/vdns/hostip4.c similarity index 99% rename from Utilities/cmcurl/lib/hostip4.c rename to Utilities/cmcurl/lib/vdns/hostip4.c index c8cc4dc340..83896a82b2 100644 --- a/Utilities/cmcurl/lib/hostip4.c +++ b/Utilities/cmcurl/lib/vdns/hostip4.c @@ -45,7 +45,7 @@ #include "urldata.h" #include "curl_addrinfo.h" #include "curl_trc.h" -#include "hostip.h" +#include "vdns/hostip.h" #include "url.h" @@ -91,7 +91,7 @@ struct Curl_addrinfo *Curl_sync_getaddrinfo(struct Curl_easy *data, !defined(CURLRES_AMIGA) /* - * Curl_ipv4_resolve_r() - ipv4 thread-safe resolver function. + * Curl_ipv4_resolve_r() - IPv4 thread-safe resolver function. * * This is used for both synchronous and asynchronous resolver builds, * implying that only thread-safe code and function calls may be used. diff --git a/Utilities/cmcurl/lib/hostip6.c b/Utilities/cmcurl/lib/vdns/hostip6.c similarity index 99% rename from Utilities/cmcurl/lib/hostip6.c rename to Utilities/cmcurl/lib/vdns/hostip6.c index 7412f428a4..e33a154d2f 100644 --- a/Utilities/cmcurl/lib/hostip6.c +++ b/Utilities/cmcurl/lib/vdns/hostip6.c @@ -46,8 +46,8 @@ #include "cfilters.h" #include "curl_addrinfo.h" #include "curl_trc.h" -#include "hostip.h" #include "url.h" +#include "vdns/hostip.h" #include "curlx/inet_pton.h" #include "connect.h" diff --git a/Utilities/cmcurl/lib/httpsrr.c b/Utilities/cmcurl/lib/vdns/httpsrr.c similarity index 89% rename from Utilities/cmcurl/lib/httpsrr.c rename to Utilities/cmcurl/lib/vdns/httpsrr.c index 53647b81ed..98a3c89496 100644 --- a/Utilities/cmcurl/lib/httpsrr.c +++ b/Utilities/cmcurl/lib/vdns/httpsrr.c @@ -26,9 +26,9 @@ #ifdef USE_HTTPSRR #include "urldata.h" -#include "httpsrr.h" #include "connect.h" #include "curl_trc.h" +#include "vdns/httpsrr.h" #include "curlx/strdup.h" #include "curlx/inet_ntop.h" @@ -82,7 +82,7 @@ static CURLcode httpsrr_print_addr(struct dynbuf *dyn, CURLcode result = CURLE_OK; for(i = 0; (i < (total_len / alen)) && !result; ++i) { - if(!curlx_inet_ntop(ai_family, addr + (i * alen), buf, sizeof(buf))) + if(curlx_inet_ntop(ai_family, addr + (i * alen), buf, sizeof(buf))) result = curlx_dyn_add(dyn, ""); else result = curlx_dyn_addf(dyn, "%s%s", sep, buf); @@ -97,16 +97,16 @@ void Curl_httpsrr_trace(struct Curl_easy *data, struct dynbuf tmp; CURLcode result; - if(!rr || !rr->complete) { - CURL_TRC_DNS(data, "[HTTPS-RR] not available"); + if(!rr) { + CURL_TRC_DNS(data, "[HTTPS] no record available"); return; } curlx_dyn_init(&tmp, 1024); result = Curl_httpsrr_print(&tmp, rr); if(!result) - CURL_TRC_DNS(data, "HTTPS-RR: %s", curlx_dyn_ptr(&tmp)); + CURL_TRC_DNS(data, "[HTTPS] record: %s", curlx_dyn_ptr(&tmp)); else - CURL_TRC_DNS(data, "Error printing HTTPS-RR information"); + CURL_TRC_DNS(data, "[HTTPS] error printing information"); curlx_dyn_free(&tmp); } @@ -225,23 +225,15 @@ CURLcode Curl_httpsrr_set(struct Curl_https_rrinfo *rr, return result; } -struct Curl_https_rrinfo *Curl_httpsrr_dup_move( - struct Curl_https_rrinfo *rrinfo) +void Curl_httpsrr_destroy(struct Curl_https_rrinfo *rrinfo) { - struct Curl_https_rrinfo *dup = curlx_memdup(rrinfo, sizeof(*rrinfo)); - if(dup) - memset(rrinfo, 0, sizeof(*rrinfo)); - return dup; -} - -void Curl_httpsrr_cleanup(struct Curl_https_rrinfo *rrinfo) -{ - curlx_safefree(rrinfo->target); - curlx_safefree(rrinfo->echconfiglist); - curlx_safefree(rrinfo->ipv4hints); - curlx_safefree(rrinfo->ipv6hints); - curlx_safefree(rrinfo->rrname); - rrinfo->complete = FALSE; + if(rrinfo) { + curlx_free(rrinfo->target); + curlx_free(rrinfo->echconfiglist); + curlx_free(rrinfo->ipv4hints); + curlx_free(rrinfo->ipv6hints); + curlx_free(rrinfo); + } } bool Curl_httpsrr_applicable(struct Curl_easy *data, @@ -269,11 +261,16 @@ static CURLcode httpsrr_opt(const ares_dns_rr_t *rr, } CURLcode Curl_httpsrr_from_ares(const ares_dns_record_t *dnsrec, - struct Curl_https_rrinfo *hinfo) + struct Curl_https_rrinfo **phinfo) { - CURLcode result = CURLE_OK; + struct Curl_https_rrinfo *hinfo = NULL; + CURLcode result = CURLE_OUT_OF_MEMORY; size_t i; + hinfo = curlx_calloc(1, sizeof(*hinfo)); + if(!hinfo) + goto out; + for(i = 0; i < ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_ANSWER); i++) { const char *target; size_t opt; @@ -297,12 +294,20 @@ CURLcode Curl_httpsrr_from_ares(const ares_dns_record_t *dnsrec, opt++) { result = httpsrr_opt(rr, ARES_RR_HTTPS_PARAMS, opt, hinfo); if(result) - break; + goto out; } } + result = CURLE_OK; + out: - hinfo->complete = !result; - curlx_safefree(hinfo->rrname); + if(result) { + *phinfo = NULL; + Curl_httpsrr_destroy(hinfo); + } + else { + DEBUGASSERT(hinfo); + *phinfo = hinfo; + } return result; } diff --git a/Utilities/cmcurl/lib/httpsrr.h b/Utilities/cmcurl/lib/vdns/httpsrr.h similarity index 90% rename from Utilities/cmcurl/lib/httpsrr.h rename to Utilities/cmcurl/lib/vdns/httpsrr.h index 2ee1beab3e..8d6eed1c93 100644 --- a/Utilities/cmcurl/lib/httpsrr.h +++ b/Utilities/cmcurl/lib/vdns/httpsrr.h @@ -38,7 +38,6 @@ struct Curl_easy; struct dynbuf; struct Curl_https_rrinfo { - char *rrname; /* if NULL, the same as the URL hostname */ /* * Fields from HTTPS RR. The only mandatory fields are priority and target. * See https://datatracker.ietf.org/doc/html/rfc9460#section-14.3.2 @@ -57,16 +56,12 @@ struct Curl_https_rrinfo { BIT(no_def_alpn); /* keytag = 2 */ BIT(mandatory); /* keytag = 0 */ BIT(port_set); /* port value has been assigned */ - BIT(complete); /* values have been successfully assigned */ }; CURLcode Curl_httpsrr_set(struct Curl_https_rrinfo *rr, uint16_t rrkey, const uint8_t *val, size_t vlen); -struct Curl_https_rrinfo *Curl_httpsrr_dup_move( - struct Curl_https_rrinfo *rrinfo); - -void Curl_httpsrr_cleanup(struct Curl_https_rrinfo *rrinfo); +void Curl_httpsrr_destroy(struct Curl_https_rrinfo *rrinfo); /* TRUE if the record is applicable to the transfer and its connection. */ bool Curl_httpsrr_applicable(struct Curl_easy *data, @@ -85,7 +80,7 @@ bool Curl_httpsrr_applicable(struct Curl_easy *data, #ifdef USE_ARES CURLcode Curl_httpsrr_from_ares(const ares_dns_record_t *dnsrec, - struct Curl_https_rrinfo *hinfo); + struct Curl_https_rrinfo **phinfo); #endif /* USE_ARES */ #ifdef CURLVERBOSE diff --git a/Utilities/cmcurl/lib/version.c b/Utilities/cmcurl/lib/version.c index 3943083f3e..e6cf90200e 100644 --- a/Utilities/cmcurl/lib/version.c +++ b/Utilities/cmcurl/lib/version.c @@ -242,7 +242,9 @@ char *curl_version(void) src[i++] = gsasl_buf; #endif #ifdef HAVE_GSSAPI -#ifdef HAVE_GSSGNU +#ifdef HAVE_GSSAPPLE + curl_msnprintf(gss_buf, sizeof(gss_buf), "AppleGSS"); +#elif defined(HAVE_GSSGNU) curl_msnprintf(gss_buf, sizeof(gss_buf), "libgss/%s", GSS_VERSION); #elif defined(CURL_KRB5_VERSION) curl_msnprintf(gss_buf, sizeof(gss_buf), "mit-krb5/%s", CURL_KRB5_VERSION); @@ -467,6 +469,9 @@ static const struct feat features_table[] = { !defined(CURL_DISABLE_HTTP) FEATURE("HTTPS-proxy", https_proxy_present, CURL_VERSION_HTTPS_PROXY), #endif +#ifndef CURL_DISABLE_HTTPSIG + FEATURE("HTTPSIG", NULL, 0), +#endif #ifdef USE_HTTPSRR FEATURE("HTTPSRR", NULL, 0), #endif @@ -519,9 +524,6 @@ static const struct feat features_table[] = { #ifdef GLOBAL_INIT_IS_THREADSAFE FEATURE("threadsafe", NULL, CURL_VERSION_THREADSAFE), #endif -#ifdef USE_TLS_SRP - FEATURE("TLS-SRP", NULL, CURL_VERSION_TLSAUTH_SRP), -#endif #if defined(_WIN32) && defined(UNICODE) && defined(_UNICODE) FEATURE("Unicode", NULL, CURL_VERSION_UNICODE), #endif diff --git a/Utilities/cmcurl/lib/vquic/cf-ngtcp2-cmn.c b/Utilities/cmcurl/lib/vquic/cf-ngtcp2-cmn.c index e1ca18cf69..b1cfb68cbd 100644 --- a/Utilities/cmcurl/lib/vquic/cf-ngtcp2-cmn.c +++ b/Utilities/cmcurl/lib/vquic/cf-ngtcp2-cmn.c @@ -54,7 +54,6 @@ #include "rand.h" #include "multiif.h" #include "cfilters.h" -#include "cf-dns.h" #include "cf-socket.h" #include "connect.h" #include "progress.h" @@ -65,6 +64,7 @@ #include "sockaddr.h" #include "transfer.h" #include "bufref.h" +#include "vdns/cf-dns.h" #include "vquic/vquic.h" #include "vquic/vquic_int.h" #include "vquic/vquic-tls.h" @@ -139,7 +139,7 @@ void Curl_cf_ngtcp2_ctx_cleanup(struct cf_ngtcp2_ctx *ctx) { if(ctx && ctx->initialized) { Curl_vquic_tls_cleanup(&ctx->tls); - vquic_ctx_free(&ctx->q); + Curl_vquic_ctx_free(&ctx->q); Curl_bufcp_free(&ctx->stream_bufcp); curlx_dyn_free(&ctx->scratch); Curl_uint32_hash_destroy(&ctx->streams); @@ -267,19 +267,31 @@ static int cb_ngtcp2_handshake_completed(ngtcp2_conn *tconn, void *user_data) /* In case of earlydata, where we simulate being connected, update * the handshake time when we really did connect */ - if(ctx->use_earlydata) + if(ctx->use_earlydata && !ctx->stats_reported && + !(cf->cft->flags & CF_TYPE_PROXY)) { Curl_pgrsTimeWas(data, TIMER_APPCONNECT, ctx->handshake_at); + ctx->stats_reported = TRUE; + } if(ctx->use_earlydata) { #if defined(USE_OPENSSL) && defined(HAVE_OPENSSL_EARLYDATA) - ctx->earlydata_accepted = - (SSL_get_early_data_status(ctx->tls.ossl.ssl) != - SSL_EARLY_DATA_REJECTED); + /* Check for bug that OpenSSL did not even send the early data. */ + if(SSL_get_early_data_status(ctx->tls.ossl.ssl) == SSL_EARLY_DATA_NOT_SENT) + CURL_TRC_CF(data, cf, "OpenSSL did not send early data"); #endif -#ifdef USE_GNUTLS + +#if NGTCP2_VERSION_NUM >= 0x011700 + ctx->earlydata_accepted = + !ngtcp2_conn_get_tls_early_data_rejected2(ctx->qconn); +#else /* older NGTCP2 */ +#if defined(USE_OPENSSL) && defined(HAVE_OPENSSL_EARLYDATA) + int ossl_early_status = SSL_get_early_data_status(ctx->tls.ossl.ssl); + if(ossl_early_status == SSL_EARLY_DATA_NOT_SENT) + CURL_TRC_CF(data, cf, "OpenSSL did not send early data"); + ctx->earlydata_accepted = (ossl_early_status == SSL_EARLY_DATA_ACCEPTED); +#elif defined(USE_GNUTLS) int flags = gnutls_session_get_flags(ctx->tls.gtls.session); ctx->earlydata_accepted = !!(flags & GNUTLS_SFLAGS_EARLY_DATA); -#endif -#ifdef USE_WOLFSSL +#elif defined(USE_WOLFSSL) #ifdef WOLFSSL_EARLY_DATA ctx->earlydata_accepted = (wolfSSL_get_early_data_status(ctx->tls.wssl.ssl) != @@ -288,7 +300,8 @@ static int cb_ngtcp2_handshake_completed(ngtcp2_conn *tconn, void *user_data) DEBUGASSERT(0); /* should not come here if ED is disabled. */ ctx->earlydata_accepted = FALSE; #endif /* WOLFSSL_EARLY_DATA */ -#endif +#endif /* OPENSSL or GNUTLS or WOLFSSL */ +#endif /* older NGTCP2 */ CURL_TRC_CF(data, cf, "server did%s accept %zu bytes of early data", ctx->earlydata_accepted ? "" : " not", ctx->earlydata_skip); Curl_pgrsEarlyData(data, ctx->earlydata_accepted ? @@ -388,6 +401,42 @@ static int cb_stream_close(ngtcp2_conn *tconn, uint32_t flags, return 0; } +#ifdef NGTCP2_CALLBACKS_V5 /* ngtcp2 v1.25.0+ */ +static int cb_stream_close2(ngtcp2_conn *tconn, uint32_t flags, + int64_t stream_id, + uint64_t rx_app_error_code, + uint64_t tx_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; + uint64_t h3_app_error_code = NGHTTP3_H3_NO_ERROR; + int rv; + + (void)tconn; + (void)tx_app_error_code; + /* stream is closed... */ + if(!data) + data = CF_DATA_CURRENT(cf); + if(!data) + return NGTCP2_ERR_CALLBACK_FAILURE; + + if(flags & NGTCP2_STREAM_CLOSE2_FLAG_RX_APP_ERROR_CODE_SET) + h3_app_error_code = rx_app_error_code; + + rv = nghttp3_conn_close_stream(ctx->h3conn, stream_id, h3_app_error_code); + CURL_TRC_CF(data, cf, "[%" PRId64 "] quic close(app_error=%" + PRIu64 ") -> %d", stream_id, h3_app_error_code, rv); + if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { + Curl_cf_ngtcp2_h3_err_set(cf, data, rv); + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +#endif + static int cb_stream_reset(ngtcp2_conn *tconn, int64_t stream_id, uint64_t final_size, uint64_t app_error_code, void *user_data, void *stream_user_data) @@ -546,6 +595,10 @@ static int cb_recv_rx_key(ngtcp2_conn *tconn, ngtcp2_encryption_level level, return 0; } +#ifdef CURL_HAVE_DIAG +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif static ngtcp2_callbacks ng_callbacks = { ngtcp2_crypto_client_initial_cb, NULL, /* recv_client_initial */ @@ -596,7 +649,16 @@ static ngtcp2_callbacks ng_callbacks = { NULL, /* dcid_status2 */ ngtcp2_crypto_get_path_challenge_data2_cb, /* get_path_challenge_data2 */ #endif +#ifdef NGTCP2_CALLBACKS_V4 /* ngtcp2 v1.24.0+ */ + NULL, /* recv_stop_sending */ +#endif +#ifdef NGTCP2_CALLBACKS_V5 /* ngtcp2 v1.25.0+ */ + cb_stream_close2, /* is called instead of cb_stream_close when set */ +#endif }; +#ifdef CURL_HAVE_DIAG +#pragma GCC diagnostic pop +#endif #if defined(_MSC_VER) && defined(_DLL) #pragma warning(pop) @@ -646,8 +708,8 @@ static int quic_ossl_new_session_cb(SSL *ssl, SSL_SESSION *ssl_sessionid) quic_tp_len = (size_t)tplen; } #endif - Curl_ossl_add_session(cf, data, ctx->ssl_peer.scache_key, ssl_sessionid, - SSL_version(ssl), "h3", quic_tp, quic_tp_len); + Curl_ossl_add_session(cf, data, &ctx->tls.ossl, ctx->ssl_peer.scache_key, + ssl_sessionid, "h3", quic_tp, quic_tp_len, NULL); } return 0; } @@ -718,7 +780,8 @@ static int quic_gtls_handshake_cb(gnutls_session_t session, unsigned int htype, quic_tp_len = (size_t)tplen; } (void)Curl_gtls_cache_session(cf, data, ctx->ssl_peer.scache_key, - session, 0, "h3", quic_tp, quic_tp_len); + session, 0, "h3", quic_tp, quic_tp_len, + NULL); break; } default: @@ -757,7 +820,7 @@ static int wssl_quic_new_session_cb(WOLFSSL *ssl, WOLFSSL_SESSION *session) } (void)Curl_wssl_cache_session(cf, data, ctx->ssl_peer.scache_key, session, wolfSSL_version(ssl), - "h3", quic_tp, quic_tp_len); + "h3", quic_tp, quic_tp_len, NULL); } } return 0; @@ -872,10 +935,10 @@ static CURLcode cf_ngtcp2_on_session_reuse(struct Curl_cfilter *cf, *do_early_data = TRUE; } } - else { /* h3_conn_init set, assume done */ - ctx->use_earlydata = TRUE; - cf->connected = TRUE; - *do_early_data = TRUE; + else { /* init_h3_conn_cb not set, assume done */ + ctx->use_earlydata = TRUE; + cf->connected = TRUE; + *do_early_data = TRUE; } } #else /* not supported in the TLS backend */ @@ -917,7 +980,7 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, ctx->qlogfd = qfd; /* -1 if failure above */ quic_settings(ctx, data, pktx); - result = vquic_ctx_init(data, &ctx->q); + result = Curl_vquic_ctx_init(data, &ctx->q); if(result) return result; @@ -940,17 +1003,6 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, ctx->q.local_addrlen); ngtcp2_addr_init(&ctx->connected_path.remote, &sockaddr->curl_sa_addr, (socklen_t)sockaddr->addrlen); - - rc = ngtcp2_conn_client_new(&ctx->qconn, &ctx->dcid, &ctx->scid, - &ctx->connected_path, - NGTCP2_PROTO_VER_V1, &ng_callbacks, - &ctx->settings, &ctx->transport_params, - Curl_ngtcp2_mem(), cf); - if(rc) - return CURLE_QUIC_CONNECT_ERROR; - - ctx->conn_ref.get_conn = get_conn; - ctx->conn_ref.user_data = cf; } else { /* Tunneled QUIC (e.g. CONNECT-UDP): get remote address @@ -984,19 +1036,19 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, ngtcp2_addr_init(&ctx->connected_path.remote, &remote->curl_sa_addr, (socklen_t)remote->addrlen); - - rc = ngtcp2_conn_client_new(&ctx->qconn, &ctx->dcid, &ctx->scid, - &ctx->connected_path, - NGTCP2_PROTO_VER_V1, &ng_callbacks, - &ctx->settings, &ctx->transport_params, - Curl_ngtcp2_mem(), cf); - if(rc) - return CURLE_QUIC_CONNECT_ERROR; - - ctx->conn_ref.get_conn = get_conn; - ctx->conn_ref.user_data = cf; } + rc = ngtcp2_conn_client_new(&ctx->qconn, &ctx->dcid, &ctx->scid, + &ctx->connected_path, + NGTCP2_PROTO_VER_V1, &ng_callbacks, + &ctx->settings, &ctx->transport_params, + Curl_ngtcp2_mem(), cf); + if(rc) + return CURLE_QUIC_CONNECT_ERROR; + + ctx->conn_ref.get_conn = get_conn; + ctx->conn_ref.user_data = cf; + result = Curl_vquic_tls_init(&ctx->tls, cf, data, &ctx->ssl_peer, &ALPN_SPEC_H3, cf_ngtcp2_tls_ctx_setup, &ctx->tls, @@ -1026,6 +1078,18 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, #error "ngtcp2 TLS backend not defined" #endif +#if defined(USE_OPENSSL) && defined(HAVE_OPENSSL_EARLYDATA) && \ + defined(OPENSSL_QUIC_API2) + /* We need to tell OpenSSL to *really* use Early Data for QUIC and + * this only works *after* ngtcp2 has tweaked all SSL parameters, + * otherwise OpenSSL does not accept it. */ + if(ctx->use_earlydata && + !SSL_set_quic_tls_early_data_enabled(ctx->tls.ossl.ssl, 1)) { + CURL_TRC_CF(data, cf, "OpenSSL refused to use early data"); + ctx->use_earlydata = FALSE; + cf->connected = FALSE; + } +#endif ngtcp2_ccerr_default(&ctx->last_error); return CURLE_OK; @@ -1224,7 +1288,7 @@ CURLcode Curl_cf_ngtcp2_cmn_shutdown(struct Curl_cfilter *cf, if(!Curl_bufq_is_empty(&ctx->q.sendbuf)) { CURL_TRC_CF(data, cf, "shutdown, flushing egress"); - result = vquic_flush(cf, data, &ctx->q); + result = Curl_vquic_flush(cf, data, &ctx->q); if(result == CURLE_AGAIN) { CURL_TRC_CF(data, cf, "sending shutdown packets blocked"); result = CURLE_OK; @@ -1287,26 +1351,26 @@ void Curl_cf_ngtcp2_io_ctx_init(struct cf_ngtcp2_io_ctx *io_ctx, struct Curl_easy *data) { struct cf_ngtcp2_ctx *ctx = cf->ctx; - const struct curltime *pnow = Curl_pgrs_now(data); io_ctx->cf = cf; io_ctx->data = data; + io_ctx->now = *Curl_pgrs_now(data); ngtcp2_path_storage_zero(&io_ctx->ps); - vquic_ctx_set_time(&ctx->q, pnow); - io_ctx->ts = ((ngtcp2_tstamp)pnow->tv_sec * NGTCP2_SECONDS) + - ((ngtcp2_tstamp)pnow->tv_usec * NGTCP2_MICROSECONDS); + Curl_vquic_ctx_set_time(&ctx->q, &io_ctx->now); + io_ctx->ts = ((ngtcp2_tstamp)io_ctx->now.tv_sec * NGTCP2_SECONDS) + + ((ngtcp2_tstamp)io_ctx->now.tv_usec * NGTCP2_MICROSECONDS); } void Curl_cf_ngtcp2_io_ctx_update_time(struct Curl_easy *data, - struct cf_ngtcp2_io_ctx *pktx, + struct cf_ngtcp2_io_ctx *io_ctx, struct Curl_cfilter *cf) { struct cf_ngtcp2_ctx *ctx = cf->ctx; - const struct curltime *pnow = Curl_pgrs_now(data); - vquic_ctx_update_time(&ctx->q, pnow); - pktx->ts = ((ngtcp2_tstamp)pnow->tv_sec * NGTCP2_SECONDS) + - ((ngtcp2_tstamp)pnow->tv_usec * NGTCP2_MICROSECONDS); + io_ctx->now = *Curl_pgrs_now(data); + Curl_vquic_ctx_update_time(&ctx->q, &io_ctx->now); + io_ctx->ts = ((ngtcp2_tstamp)io_ctx->now.tv_sec * NGTCP2_SECONDS) + + ((ngtcp2_tstamp)io_ctx->now.tv_usec * NGTCP2_MICROSECONDS); } #if NGTCP2_VERSION_NUM < 0x011100 @@ -1480,10 +1544,10 @@ CURLcode Curl_cf_ngtcp2_progress_egress(struct Curl_cfilter *cf, ngtcp2_path_storage_zero(&pktx->ps); } - result = vquic_flush(cf, data, &ctx->q); + result = Curl_vquic_flush(cf, data, &ctx->q); if(result) { if(result == CURLE_AGAIN) { - Curl_expire(data, 1, EXPIRE_QUIC); + Curl_expire_set(data, EXPIRE_QUIC, 1, &pktx->now); return CURLE_OK; } return result; @@ -1528,14 +1592,14 @@ CURLcode Curl_cf_ngtcp2_progress_egress(struct Curl_cfilter *cf, } else if(nread > gsolen || (gsolen > path_max_payload_size && nread != gsolen)) { - /* The added packet is a PMTUD *or* the one(s) before the - * added were PMTUD and the last one is smaller. - * Flush the buffer before the last add. */ - result = vquic_send_tail_split(cf, data, &ctx->q, - gsolen, nread, nread); + /* The added packet is a PMTUD *or* the one(s) before the added were + * PMTUD and the last one is smaller. Flush the buffer before the last + * add. */ + result = Curl_vquic_send_tail_split(cf, data, &ctx->q, + gsolen, nread, nread); if(result) { if(result == CURLE_AGAIN) { - Curl_expire(data, 1, EXPIRE_QUIC); + Curl_expire_set(data, EXPIRE_QUIC, 1, &pktx->now); return CURLE_OK; } return result; @@ -1554,10 +1618,10 @@ CURLcode Curl_cf_ngtcp2_progress_egress(struct Curl_cfilter *cf, /* time to send */ CURL_TRC_CF(data, cf, "egress, send collected %zu packets in %zu bytes", pktcnt, Curl_bufq_len(&ctx->q.sendbuf)); - result = vquic_send(cf, data, &ctx->q, gsolen); + result = Curl_vquic_send(cf, data, &ctx->q, gsolen); if(result) { if(result == CURLE_AGAIN) { - Curl_expire(data, 1, EXPIRE_QUIC); + Curl_expire_set(data, EXPIRE_QUIC, 1, &pktx->now); return CURLE_OK; } return result; @@ -1576,7 +1640,7 @@ struct cf_ngtcp2_recv_ctx { static CURLcode cf_ngtcp2_recv_pkts(const unsigned char *buf, size_t buflen, size_t gso_size, struct sockaddr_storage *remote_addr, - socklen_t remote_addrlen, int ecn, + socklen_t remote_addrlen, uint8_t ecn, void *userp) { struct cf_ngtcp2_recv_ctx *rctx = userp; @@ -1594,7 +1658,7 @@ static CURLcode cf_ngtcp2_recv_pkts(const unsigned char *buf, size_t buflen, if(ecn) CURL_TRC_CF(pktx->data, pktx->cf, "vquic_recv(len=%zu, gso=%zu, ecn=%x)", - buflen, gso_size, (unsigned int)ecn); + buflen, gso_size, (unsigned)ecn); ngtcp2_addr_init(&path.local, (struct sockaddr *)&ctx->q.local_addr, ctx->q.local_addrlen); ngtcp2_addr_init(&path.remote, (struct sockaddr *)remote_addr, @@ -1645,8 +1709,8 @@ CURLcode Curl_cf_ngtcp2_progress_ingress(struct Curl_cfilter *cf, if(ctx->q.sockfd != CURL_SOCKET_BAD) { /* Direct UDP socket (via happy eyeballs) */ CURL_TRC_CF(data, cf, "progress_ingress(socket)"); - return vquic_recv_packets(cf, data, &ctx->q, 1000, - cf_ngtcp2_recv_pkts, &rctx); + return Curl_vquic_recv_packets(cf, data, &ctx->q, 1000, + cf_ngtcp2_recv_pkts, &rctx); } else { /* Tunneled QUIC (CONNECT-UDP through proxy) */ @@ -1746,11 +1810,14 @@ CURLcode Curl_cf_ngtcp2_cmn_set_expiry(struct Curl_cfilter *cf, return CURLE_SEND_ERROR; } result = Curl_cf_ngtcp2_progress_ingress(cf, data, pktx); - if(result) - return result; - result = Curl_cf_ngtcp2_progress_egress(cf, data, pktx); - if(result) + if(!result) + result = Curl_cf_ngtcp2_progress_egress(cf, data, pktx); + if(result) { + /* a verify failure during ingress must win over generic errors */ + if(ctx->tls_vrfy_result) + result = ctx->tls_vrfy_result; return result; + } /* ask again, things might have changed */ expiry = ngtcp2_conn_get_expiry(ctx->qconn); } @@ -1760,8 +1827,9 @@ CURLcode Curl_cf_ngtcp2_cmn_set_expiry(struct Curl_cfilter *cf, if(timeout % NGTCP2_MILLISECONDS) { timeout += NGTCP2_MILLISECONDS; } - Curl_expire(data, (timediff_t)(timeout / NGTCP2_MILLISECONDS), - EXPIRE_QUIC); + Curl_expire_set(data, EXPIRE_QUIC, + (timediff_t)(timeout / NGTCP2_MILLISECONDS), + &pktx->now); } } return CURLE_OK; @@ -1823,11 +1891,12 @@ CURLcode Curl_cf_ngtcp2_h3_stream_setup(struct Curl_cfilter *cf, stream->id = -1; stream->rx_offset = 0; stream->rx_offset_max = H3_STREAM_WINDOW_SIZE_INITIAL; + stream->tx_in_flight_ideal = H3_STREAM_SEND_BUF_INITIAL; /* on send, we control how much we put into the buffer */ Curl_bufq_initp(&stream->sendbuf, &ctx->stream_bufcp, H3_STREAM_SEND_CHUNKS, BUFQ_OPT_NONE); - stream->sendbuf_len_in_flight = 0; + stream->tx_in_flight_size = 0; stream->window_size_max = H3_STREAM_WINDOW_SIZE_INITIAL; Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN); @@ -1966,4 +2035,64 @@ CURLcode Curl_cf_ngtcp2_h3_init_ctrls(struct cf_ngtcp2_ctx *ctx, return CURLE_OK; } +CURLcode Curl_cf_ngtcp2_cmn_query(struct Curl_cfilter *cf, + struct Curl_easy *data, + int query, int *pres1, void *pres2) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + + switch(query) { + case CF_QUERY_CONNECT_REPLY_MS: + if((ctx->q.sockfd != CURL_SOCKET_BAD) && ctx->q.got_first_byte) { + timediff_t ms = curlx_ptimediff_ms(&ctx->q.first_byte_at, + &ctx->started_at); + *pres1 = (ms < INT_MAX) ? (int)ms : INT_MAX; + return CURLE_OK; + } + break; + case CF_QUERY_REALLY_CONNECTED: + if(ctx->q.sockfd != CURL_SOCKET_BAD) { + *pres1 = ctx->q.got_first_byte; + return CURLE_OK; + } + break; + default: + break; + } + return cf->next ? + cf->next->cft->query(cf->next, data, query, pres1, pres2) : + CURLE_UNKNOWN_OPTION; +} + +CURLcode Curl_cf_ngtcp2_cmn_cntrl(struct Curl_cfilter *cf, + struct Curl_easy *data, + int event, int arg1, void *arg2) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + + (void)arg1; + (void)arg2; + switch(event) { + case CF_CTRL_REPORT_STATS: + if(cf->connected && !ctx->stats_reported) { + if((cf->cft->flags & CF_TYPE_PROXY) && + (ctx->q.sockfd != CURL_SOCKET_BAD) && ctx->q.got_first_byte) { + Curl_pgrsTimeWas(data, TIMER_CONNECT, ctx->q.first_byte_at); + ctx->stats_reported = TRUE; + } + else if(ctx->handshake_at.tv_sec || ctx->handshake_at.tv_usec) { + if(ctx->q.sockfd != CURL_SOCKET_BAD) + Curl_pgrsTimeWas(data, TIMER_CONNECT, ctx->q.first_byte_at); + Curl_pgrsTimeWas(data, TIMER_APPCONNECT, ctx->handshake_at); + ctx->stats_reported = TRUE; + } + } + break; + default: + break; + } + return result; +} + #endif /* !CURL_DISABLE_HTTP && USE_NGTCP2 && USE_NGHTTP3 */ diff --git a/Utilities/cmcurl/lib/vquic/cf-ngtcp2-cmn.h b/Utilities/cmcurl/lib/vquic/cf-ngtcp2-cmn.h index 88554edfb6..e0c27a95f7 100644 --- a/Utilities/cmcurl/lib/vquic/cf-ngtcp2-cmn.h +++ b/Utilities/cmcurl/lib/vquic/cf-ngtcp2-cmn.h @@ -93,6 +93,9 @@ struct cf_quic_ctx; #define H3_STREAM_SEND_BUFFER_MAX (10 * 1024 * 1024) #define H3_STREAM_SEND_CHUNKS \ (H3_STREAM_SEND_BUFFER_MAX / H3_STREAM_CHUNK_SIZE) +/* How much data we initially want to buffer un-acked */ +#define H3_STREAM_SEND_BUF_INITIAL (32 * 1024) + #define QUIC_TUNNEL_INGRESS_PKT_LIMIT 1000 @@ -142,6 +145,7 @@ struct cf_ngtcp2_ctx { BIT(use_earlydata); /* Using 0RTT data */ BIT(earlydata_accepted); /* 0RTT was accepted by server */ BIT(shutdown_started); /* queued shutdown packets */ + BIT(stats_reported); /* connect statistics reported */ }; /* How to access `call_data` from a cf_ngtcp2 filter */ @@ -164,13 +168,14 @@ struct h3_stream_ctx { int64_t id; /* HTTP/3 stream 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" */ uint64_t error3; /* HTTP/3 stream error code */ curl_off_t upload_left; /* number of request bytes left to upload */ curl_off_t rx_total; /* total number of bytes received */ uint64_t rx_offset; /* current receive offset */ uint64_t rx_offset_max; /* allowed receive offset */ uint64_t window_size_max; /* max flow control window set for stream */ + size_t tx_in_flight_size; /* sendbuf data "in flight" */ + size_t tx_in_flight_ideal; /* ideal amount of un-acked send data */ int status_code; /* HTTP status code */ CURLcode xfer_result; /* result from xfer_resp_write(_hd) */ BIT(resp_hds_complete); /* we have a complete, final response */ @@ -199,6 +204,7 @@ void Curl_cf_ngtcp2_cmn_conn_close(struct Curl_cfilter *cf, struct cf_ngtcp2_io_ctx { struct Curl_cfilter *cf; struct Curl_easy *data; + struct curltime now; ngtcp2_tstamp ts; ngtcp2_path_storage ps; }; @@ -207,7 +213,7 @@ void Curl_cf_ngtcp2_io_ctx_init(struct cf_ngtcp2_io_ctx *io_ctx, struct Curl_cfilter *cf, struct Curl_easy *data); void Curl_cf_ngtcp2_io_ctx_update_time(struct Curl_easy *data, - struct cf_ngtcp2_io_ctx *pktx, + struct cf_ngtcp2_io_ctx *io_ctx, struct Curl_cfilter *cf); CURLcode Curl_cf_ngtcp2_progress_egress(struct Curl_cfilter *cf, @@ -234,6 +240,14 @@ bool Curl_cf_ngtcp2_cmn_conn_is_alive(struct Curl_cfilter *cf, struct Curl_easy *data, bool *input_pending); +CURLcode Curl_cf_ngtcp2_cmn_query(struct Curl_cfilter *cf, + struct Curl_easy *data, + int query, int *pres1, void *pres2); + +CURLcode Curl_cf_ngtcp2_cmn_cntrl(struct Curl_cfilter *cf, + struct Curl_easy *data, + int event, int arg1, void *arg2); + #endif /* !CURL_DISABLE_HTTP && USE_NGTCP2 && USE_NGHTTP3 */ #endif /* HEADER_CURL_VQUIC_CF_NGTCP2_CMN_H */ diff --git a/Utilities/cmcurl/lib/vquic/cf-ngtcp2-proxy.c b/Utilities/cmcurl/lib/vquic/cf-ngtcp2-proxy.c index fd2840890f..89bf19cc27 100644 --- a/Utilities/cmcurl/lib/vquic/cf-ngtcp2-proxy.c +++ b/Utilities/cmcurl/lib/vquic/cf-ngtcp2-proxy.c @@ -214,15 +214,15 @@ static int cb_h3_proxy_acked_req_body(nghttp3_conn *conn, int64_t stream_id, /* The server acknowledged `datalen` of bytes from our request body. * This is a delta. We have kept this data in `sendbuf` for * re-transmissions and can free it now. */ - if(datalen >= (uint64_t)stream->sendbuf_len_in_flight) - skiplen = stream->sendbuf_len_in_flight; + if(datalen >= (uint64_t)stream->tx_in_flight_size) + skiplen = stream->tx_in_flight_size; else skiplen = (size_t)datalen; Curl_bufq_skip(&stream->sendbuf, skiplen); - stream->sendbuf_len_in_flight -= skiplen; + stream->tx_in_flight_size -= skiplen; /* Resume upload processing if we have more data to send */ - if(stream->sendbuf_len_in_flight < Curl_bufq_len(&stream->sendbuf)) { + if(stream->tx_in_flight_size < Curl_bufq_len(&stream->sendbuf)) { int rv = nghttp3_conn_resume_stream(conn, stream_id); if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { return NGHTTP3_ERR_CALLBACK_FAILURE; @@ -281,7 +281,7 @@ static void cf_h3_proxy_upd_rx_win(struct Curl_cfilter *cf, if(!stream->rx_offset) return; - avail = Curl_rlimit_avail(&data->progress.dl.rlimit, Curl_pgrs_now(data)); + avail = Curl_rlimit_avail(&data->progress.dl.rlimit, NULL); if(avail <= 0) { /* nothing available, do not extend the rx offset */ CURL_TRC_CF(data, cf, "[%" PRId64 "] dl rate limit exhausted (%" PRId64 @@ -431,6 +431,9 @@ static int cb_h3_proxy_recv_header(nghttp3_conn *conn, int64_t stream_id, pctx->tunnel.resp = resp; } else { + if(!pctx->tunnel.resp) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } /* store as an HTTP1-style header */ CURL_TRC_CF(data, cf, "[%" PRId64 "] header: %.*s: %.*s", stream_id, (int)h3name.len, h3name.base, (int)h3val.len, h3val.base); @@ -565,21 +568,21 @@ static nghttp3_ssize cb_h3_tunnel_read_data(nghttp3_conn *conn, /* nghttp3 keeps references to the sendbuf data until it is ACKed * by the server (see `cb_h3_proxy_acked_req_body()` for updates). - * `sendbuf_len_in_flight` is the amount of bytes in `sendbuf` + * `tx_in_flight_size` is the amount of bytes in `sendbuf` * that we have already passed to nghttp3, but which have not been * ACKed yet. - * Any amount beyond `sendbuf_len_in_flight` we need still to pass + * Any amount beyond `tx_in_flight_size` we need still to pass * to nghttp3. Do that now, if we can. */ - if(stream->sendbuf_len_in_flight < Curl_bufq_len(&stream->sendbuf)) { + if(stream->tx_in_flight_size < Curl_bufq_len(&stream->sendbuf)) { nvecs = 0; while(nvecs < veccnt) { if(!Curl_bufq_peek_at(&stream->sendbuf, - stream->sendbuf_len_in_flight, + stream->tx_in_flight_size, &buf_base, &vec[nvecs].len)) break; vec[nvecs].base = (uint8_t *)(uintptr_t)buf_base; - stream->sendbuf_len_in_flight += vec[nvecs].len; + stream->tx_in_flight_size += vec[nvecs].len; nwritten += vec[nvecs].len; ++nvecs; } @@ -601,6 +604,10 @@ static nghttp3_ssize cb_h3_tunnel_read_data(nghttp3_conn *conn, return (nghttp3_ssize)nvecs; } +#ifdef CURL_HAVE_DIAG +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif static nghttp3_callbacks ngh3_proxy_callbacks = { cb_h3_proxy_acked_req_body, /* acked_stream_data */ cb_h3_proxy_stream_close, @@ -625,7 +632,13 @@ static nghttp3_callbacks ngh3_proxy_callbacks = { #ifdef NGHTTP3_CALLBACKS_V3 /* nghttp3 v1.14.0+ */ NULL, /* recv_settings2 */ #endif +#ifdef NGHTTP3_CALLBACKS_V4 /* nghttp3 v1.18.0+ */ + NULL, /* stream_close2 */ +#endif }; +#ifdef CURL_HAVE_DIAG +#pragma GCC diagnostic pop +#endif static CURLcode cf_ngtcp2_proxy_h3_init(struct Curl_cfilter *cf, struct Curl_easy *data, @@ -665,7 +678,7 @@ static ssize_t cf_h3_proxy_recv_closed_stream(struct Curl_cfilter *cf, if(stream->error3 == CURL_H3_ERR_REQUEST_REJECTED) { infof(data, "HTTP/3 stream %" PRId64 " refused by server, try again " "on a new connection", stream->id); - connclose(cf->conn, "REFUSED_STREAM"); + connclose(cf->conn); data->state.refused_stream = TRUE; *err = CURLE_RECV_ERROR; goto out; @@ -674,13 +687,13 @@ static ssize_t cf_h3_proxy_recv_closed_stream(struct Curl_cfilter *cf, CURL_TRC_CF(data, cf, "[%" PRId64 "] error after response headers, " "but we did not want a body anyway, ignore error 0x%" PRIx64 " %s", stream->id, stream->error3, - vquic_h3_err_str(stream->error3)); + Curl_vquic_h3_err_str(stream->error3)); nread = 0; goto out; } failf(data, "HTTP/3 stream %" PRId64 " reset by server (error 0x%" PRIx64 " %s)", stream->id, stream->error3, - vquic_h3_err_str(stream->error3)); + Curl_vquic_h3_err_str(stream->error3)); *err = data->req.bytecount ? CURLE_PARTIAL_FILE : CURLE_HTTP3; goto out; } @@ -1216,7 +1229,7 @@ struct Curl_cftype Curl_cft_h3_proxy = { Curl_cf_def_cntrl, Curl_cf_ngtcp2_cmn_conn_is_alive, Curl_cf_def_conn_keep_alive, - Curl_cf_def_query, + Curl_cf_ngtcp2_cmn_query, }; CURLcode Curl_cf_ngtcp2_proxy_create(struct Curl_cfilter **pcf, diff --git a/Utilities/cmcurl/lib/vquic/cf-ngtcp2.c b/Utilities/cmcurl/lib/vquic/cf-ngtcp2.c index be1a3257c3..e3bca8774e 100644 --- a/Utilities/cmcurl/lib/vquic/cf-ngtcp2.c +++ b/Utilities/cmcurl/lib/vquic/cf-ngtcp2.c @@ -32,7 +32,6 @@ #include "rand.h" #include "multiif.h" #include "cfilters.h" -#include "cf-dns.h" #include "cf-socket.h" #include "connect.h" #include "progress.h" @@ -42,6 +41,7 @@ #include "select.h" #include "transfer.h" #include "bufref.h" +#include "vdns/cf-dns.h" #include "vquic/vquic.h" #include "vquic/vquic_int.h" #include "vquic/cf-ngtcp2-cmn.h" @@ -78,7 +78,8 @@ static CURLcode cf_ngtcp2_adjust_pollset(struct Curl_cfilter *cf, c_exhaust = want_send && (!ngtcp2_conn_get_cwnd_left(ctx->qconn) || !ngtcp2_conn_get_max_data_left(ctx->qconn)); s_exhaust = want_send && stream && stream->id >= 0 && - stream->quic_flow_blocked; + (stream->quic_flow_blocked || + stream->tx_in_flight_size >= stream->tx_in_flight_ideal); want_recv = (want_recv || c_exhaust || s_exhaust); want_send = (!s_exhaust && want_send) || !Curl_bufq_is_empty(&ctx->q.sendbuf); @@ -175,7 +176,7 @@ static void cf_ngtcp2_upd_rx_win(struct Curl_cfilter *cf, if(!stream->rx_offset) return; - avail = Curl_rlimit_avail(&data->progress.dl.rlimit, Curl_pgrs_now(data)); + avail = Curl_rlimit_avail(&data->progress.dl.rlimit, NULL); if(avail <= 0) { /* nothing available, do not extend the rx offset */ CURL_TRC_CF(data, cf, "[%" PRId64 "] dl rate limit exhausted (%" PRId64 @@ -386,6 +387,10 @@ static int cb_h3_reset_stream(nghttp3_conn *conn, int64_t stream_id, return 0; } +#ifdef CURL_HAVE_DIAG +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif static nghttp3_callbacks ngh3_callbacks = { cb_h3_acked_req_body, /* acked_stream_data */ cb_h3_stream_close, @@ -410,7 +415,13 @@ static nghttp3_callbacks ngh3_callbacks = { #ifdef NGHTTP3_CALLBACKS_V3 /* nghttp3 v1.14.0+ */ NULL, /* recv_settings2 */ #endif +#ifdef NGHTTP3_CALLBACKS_V4 /* nghttp3 v1.18.0+ */ + NULL, /* stream_close2 */ +#endif }; +#ifdef CURL_HAVE_DIAG +#pragma GCC diagnostic pop +#endif static CURLcode init_ngh3_conn(struct Curl_cfilter *cf, struct Curl_easy *data, @@ -449,7 +460,7 @@ static CURLcode recv_closed_stream(struct Curl_cfilter *cf, if(stream->error3 == CURL_H3_ERR_REQUEST_REJECTED) { infof(data, "HTTP/3 stream %" PRId64 " refused by server, try again " "on a new connection", stream->id); - connclose(cf->conn, "REFUSED_STREAM"); /* do not use this anymore */ + connclose(cf->conn); /* do not use this anymore */ data->state.refused_stream = TRUE; return CURLE_RECV_ERROR; /* trigger Curl_retry_request() later */ } @@ -457,12 +468,12 @@ static CURLcode recv_closed_stream(struct Curl_cfilter *cf, CURL_TRC_CF(data, cf, "[%" PRId64 "] error after response headers, " "but we did not want a body anyway, ignore error 0x%" PRIx64 " %s", stream->id, stream->error3, - vquic_h3_err_str(stream->error3)); + Curl_vquic_h3_err_str(stream->error3)); return CURLE_OK; } failf(data, "HTTP/3 stream %" PRId64 " reset by server (error 0x%" PRIx64 " %s)", stream->id, stream->error3, - vquic_h3_err_str(stream->error3)); + Curl_vquic_h3_err_str(stream->error3)); return data->req.bytecount ? CURLE_PARTIAL_FILE : CURLE_HTTP3; } else if(!stream->resp_hds_complete) { @@ -567,15 +578,15 @@ static int cb_h3_acked_req_body(nghttp3_conn *conn, int64_t stream_id, /* The server acknowledged `datalen` of bytes from our request body. * This is a delta. We have kept this data in `sendbuf` for * re-transmissions and can free it now. */ - if(datalen >= (uint64_t)stream->sendbuf_len_in_flight) - skiplen = stream->sendbuf_len_in_flight; + if(datalen >= (uint64_t)stream->tx_in_flight_size) + skiplen = stream->tx_in_flight_size; else skiplen = (size_t)datalen; Curl_bufq_skip(&stream->sendbuf, skiplen); - stream->sendbuf_len_in_flight -= skiplen; + stream->tx_in_flight_size -= skiplen; /* Resume upload processing if we have more data to send */ - if(stream->sendbuf_len_in_flight < Curl_bufq_len(&stream->sendbuf)) { + if(stream->tx_in_flight_size < Curl_bufq_len(&stream->sendbuf)) { int rv = nghttp3_conn_resume_stream(conn, stream_id); if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { return NGHTTP3_ERR_CALLBACK_FAILURE; @@ -605,19 +616,19 @@ static nghttp3_ssize cb_h3_read_req_body(nghttp3_conn *conn, int64_t stream_id, return NGHTTP3_ERR_CALLBACK_FAILURE; /* nghttp3 keeps references to the sendbuf data until it is ACKed * by the server (see `cb_h3_acked_req_body()` for updates). - * `sendbuf_len_in_flight` is the amount of bytes in `sendbuf` + * `tx_in_flight_size` is the amount of bytes in `sendbuf` * that we have already passed to nghttp3, but which have not been * ACKed yet. - * Any amount beyond `sendbuf_len_in_flight` we need still to pass + * Any amount beyond `tx_in_flight_size` we need still to pass * to nghttp3. Do that now, if we can. */ - if(stream->sendbuf_len_in_flight < Curl_bufq_len(&stream->sendbuf)) { + if(stream->tx_in_flight_size < Curl_bufq_len(&stream->sendbuf)) { nvecs = 0; while(nvecs < veccnt && Curl_bufq_peek_at(&stream->sendbuf, - stream->sendbuf_len_in_flight, + stream->tx_in_flight_size, CURL_UNCONST(&vec[nvecs].base), &vec[nvecs].len)) { - stream->sendbuf_len_in_flight += vec[nvecs].len; + stream->tx_in_flight_size += vec[nvecs].len; nwritten += vec[nvecs].len; ++nvecs; } @@ -679,9 +690,9 @@ static CURLcode h3_stream_open(struct Curl_cfilter *cf, } result = Curl_h1_req_parse_read(&stream->h1, buf, len, NULL, - !data->state.http_ignorecustom ? - data->set.str[STRING_CUSTOMREQUEST] : NULL, - 0, pnwritten); + !data->state.http_ignorecustom ? + CURL_EASY_STR(data, STRING_CUSTOMREQUEST) : NULL, + 0, pnwritten); if(result) goto out; if(!stream->h1.done) { @@ -821,7 +832,7 @@ static CURLcode cf_ngtcp2_send(struct Curl_cfilter *cf, struct Curl_easy *data, CURL_TRC_CF(data, cf, "failed to open stream -> %d", (int)result); goto out; } - VERBOSE(stream = H3_STREAM_CTX(ctx, data)); + stream = H3_STREAM_CTX(ctx, data); } else if(stream->xfer_result) { CURL_TRC_CF(data, cf, "[%" PRId64 "] xfer write failed", stream->id); @@ -852,6 +863,10 @@ static CURLcode cf_ngtcp2_send(struct Curl_cfilter *cf, struct Curl_easy *data, result = CURLE_SEND_ERROR; goto out; } + else if(stream->tx_in_flight_size >= stream->tx_in_flight_ideal) { + result = CURLE_AGAIN; + goto out; + } else { result = Curl_bufq_write(&stream->sendbuf, buf, len, pnwritten); CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_send, add to " @@ -868,6 +883,28 @@ static CURLcode cf_ngtcp2_send(struct Curl_cfilter *cf, struct Curl_easy *data, DEBUGASSERT(!result); result = Curl_cf_ngtcp2_progress_egress(cf, data, &pktx); + if(*pnwritten > 0) { + const size_t delta = (2 * 1024); + if(stream->tx_in_flight_size > (stream->tx_in_flight_ideal + delta)) { + /* ngtcp2 keeps more in flight than we try to provide for, + * increase our "ideal" tx buffer length */ + stream->tx_in_flight_ideal += delta; + CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_send(%zu), added %zu, " + "inflight=%zu, increase tx_win=%zu", + stream->id, len, *pnwritten, + stream->tx_in_flight_size, stream->tx_in_flight_ideal); + } + else if((stream->tx_in_flight_size + delta) < stream->tx_in_flight_ideal) { + /* ngtcp2 keeps less in flight than we try to provide for, + * decrease our "ideal" tx buffer length */ + stream->tx_in_flight_ideal -= delta; + CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_send(%zu), added %zu, " + "inflight=%zu, reduce tx_win=%zu", + stream->id, len, *pnwritten, + stream->tx_in_flight_size, stream->tx_in_flight_ideal); + } + } + out: result = Curl_1st_fatal(result, Curl_cf_ngtcp2_cmn_set_expiry(cf, data, &pktx)); @@ -901,8 +938,6 @@ static CURLcode cf_ngtcp2_cntrl(struct Curl_cfilter *cf, struct cf_call_data save; CF_DATA_SAVE(save, cf, data); - (void)arg1; - (void)arg2; switch(event) { case CF_CTRL_DATA_SETUP: break; @@ -917,7 +952,7 @@ static CURLcode cf_ngtcp2_cntrl(struct Curl_cfilter *cf, if(stream && !stream->send_closed) { stream->send_closed = TRUE; stream->upload_left = Curl_bufq_len(&stream->sendbuf) - - stream->sendbuf_len_in_flight; + stream->tx_in_flight_size; (void)nghttp3_conn_resume_stream(ctx->h3conn, stream->id); } break; @@ -929,6 +964,7 @@ static CURLcode cf_ngtcp2_cntrl(struct Curl_cfilter *cf, } break; default: + result = Curl_cf_ngtcp2_cmn_cntrl(cf, data, event, arg1, arg2); break; } CF_DATA_RESTORE(cf, save); @@ -947,7 +983,7 @@ static void cf_ngtcp2_ctx_close(struct cf_ngtcp2_ctx *ctx) ctx->qlogfd = -1; Curl_vquic_tls_cleanup(&ctx->tls); Curl_ssl_peer_cleanup(&ctx->ssl_peer); - vquic_ctx_free(&ctx->q); + Curl_vquic_ctx_free(&ctx->q); if(ctx->h3conn) { nghttp3_conn_del(ctx->h3conn); ctx->h3conn = NULL; @@ -1025,27 +1061,6 @@ static CURLcode cf_ngtcp2_query(struct Curl_cfilter *cf, CF_DATA_RESTORE(cf, save); return CURLE_OK; } - case CF_QUERY_CONNECT_REPLY_MS: - if(ctx->q.got_first_byte) { - timediff_t ms = curlx_ptimediff_ms(&ctx->q.first_byte_at, - &ctx->started_at); - *pres1 = (ms < INT_MAX) ? (int)ms : INT_MAX; - } - else - *pres1 = -1; - return CURLE_OK; - case CF_QUERY_TIMER_CONNECT: { - struct curltime *when = pres2; - if(ctx->q.got_first_byte) - *when = ctx->q.first_byte_at; - return CURLE_OK; - } - case CF_QUERY_TIMER_APPCONNECT: { - struct curltime *when = pres2; - if(cf->connected) - *when = ctx->handshake_at; - return CURLE_OK; - } case CF_QUERY_HTTP_VERSION: *pres1 = 30; return CURLE_OK; @@ -1066,9 +1081,7 @@ static CURLcode cf_ngtcp2_query(struct Curl_cfilter *cf, default: break; } - return cf->next ? - cf->next->cft->query(cf->next, data, query, pres1, pres2) : - CURLE_UNKNOWN_OPTION; + return Curl_cf_ngtcp2_cmn_query(cf, data, query, pres1, pres2); } struct Curl_cftype Curl_cft_http3 = { diff --git a/Utilities/cmcurl/lib/vquic/cf-quiche.c b/Utilities/cmcurl/lib/vquic/cf-quiche.c index 31a3957ec3..934a56cada 100644 --- a/Utilities/cmcurl/lib/vquic/cf-quiche.c +++ b/Utilities/cmcurl/lib/vquic/cf-quiche.c @@ -32,7 +32,6 @@ #include "uint-hash.h" #include "urldata.h" #include "cfilters.h" -#include "cf-dns.h" #include "cf-socket.h" #include "curl_trc.h" #include "rand.h" @@ -40,8 +39,10 @@ #include "connect.h" #include "progress.h" #include "select.h" +#include "http.h" #include "http1.h" #include "sockaddr.h" +#include "vdns/cf-dns.h" #include "vquic/vquic.h" #include "vquic/vquic_int.h" #include "vquic/vquic-tls.h" @@ -93,6 +94,7 @@ struct cf_quiche_ctx { BIT(goaway); /* got GOAWAY from server */ BIT(x509_store_setup); /* if x509 store has been set up */ BIT(shutdown_started); /* queued shutdown packets */ + BIT(stats_reported); /* connect statistics reported */ }; #ifdef DEBUG_QUICHE @@ -135,7 +137,7 @@ static void cf_quiche_ctx_free(struct cf_quiche_ctx *ctx) ctx->tls.ossl.ssl = NULL; Curl_vquic_tls_cleanup(&ctx->tls); Curl_ssl_peer_cleanup(&ctx->ssl_peer); - vquic_ctx_free(&ctx->q); + Curl_vquic_ctx_free(&ctx->q); Curl_uint32_hash_destroy(&ctx->streams); curlx_dyn_free(&ctx->h1hdr); Curl_bufq_free(&ctx->writebuf); @@ -253,7 +255,6 @@ static bool cf_quiche_do_expire(struct Curl_cfilter *cf, struct h3_stream_ctx *stream, void *user_data) { - (void)stream; (void)user_data; CURL_TRC_CF(sdata, cf, "conn closed, mark as dirty"); stream->xfer_result = CURLE_SEND_ERROR; @@ -579,7 +580,7 @@ static void cf_quiche_process_ev(struct Curl_cfilter *cf, } } -struct cf_quich_disp_ctx { +struct cf_quiche_disp_ctx { uint64_t stream_id; struct Curl_cfilter *cf; struct Curl_multi *multi; @@ -588,7 +589,7 @@ struct cf_quich_disp_ctx { static bool cf_quiche_disp_event(uint32_t mid, void *val, void *user_data) { - struct cf_quich_disp_ctx *dctx = user_data; + struct cf_quiche_disp_ctx *dctx = user_data; struct h3_stream_ctx *stream = val; if(stream->id == dctx->stream_id) { @@ -629,7 +630,7 @@ static CURLcode cf_poll_events(struct Curl_cfilter *cf, else { /* another transfer, do not return errors, as they are not for * the calling transfer */ - struct cf_quich_disp_ctx dctx; + struct cf_quiche_disp_ctx dctx; dctx.stream_id = (uint64_t)rv; dctx.cf = cf; dctx.multi = data->multi; @@ -651,7 +652,7 @@ struct recv_ctx { static CURLcode cf_quiche_recv_pkts(const unsigned char *buf, size_t buflen, size_t gso_size, struct sockaddr_storage *remote_addr, - socklen_t remote_addrlen, int ecn, + socklen_t remote_addrlen, uint8_t ecn, void *userp) { struct recv_ctx *r = userp; @@ -726,8 +727,8 @@ static CURLcode cf_process_ingress(struct Curl_cfilter *cf, rctx.data = data; rctx.pkts = 0; - result = vquic_recv_packets(cf, data, &ctx->q, 1000, - cf_quiche_recv_pkts, &rctx); + result = Curl_vquic_recv_packets(cf, data, &ctx->q, 1000, + cf_quiche_recv_pkts, &rctx); if(result) return result; @@ -796,7 +797,7 @@ static CURLcode cf_flush_egress(struct Curl_cfilter *cf, } } - result = vquic_flush(cf, data, &ctx->q); + result = Curl_vquic_flush(cf, data, &ctx->q); if(result) { if(result == CURLE_AGAIN) { Curl_expire(data, 1, EXPIRE_QUIC); @@ -818,7 +819,7 @@ static CURLcode cf_flush_egress(struct Curl_cfilter *cf, if(result != CURLE_AGAIN) return result; /* Nothing more to add, flush and leave */ - result = vquic_send(cf, data, &ctx->q, gsolen); + result = Curl_vquic_send(cf, data, &ctx->q, gsolen); if(result) { if(result == CURLE_AGAIN) { Curl_expire(data, 1, EXPIRE_QUIC); @@ -831,7 +832,7 @@ static CURLcode cf_flush_egress(struct Curl_cfilter *cf, ++pkt_count; if(nread < gsolen || pkt_count >= MAX_PKT_BURST) { - result = vquic_send(cf, data, &ctx->q, gsolen); + result = Curl_vquic_send(cf, data, &ctx->q, gsolen); if(result) { if(result == CURLE_AGAIN) { Curl_expire(data, 1, EXPIRE_QUIC); @@ -866,7 +867,7 @@ static CURLcode recv_closed_stream(struct Curl_cfilter *cf, if(stream->error3 == CURL_H3_ERR_REQUEST_REJECTED) { infof(data, "HTTP/3 stream %" PRIu64 " refused by server, try again " "on a new connection", stream->id); - connclose(cf->conn, "REFUSED_STREAM"); /* do not use this anymore */ + connclose(cf->conn); /* do not use this anymore */ data->state.refused_stream = TRUE; return CURLE_RECV_ERROR; /* trigger Curl_retry_request() later */ } @@ -874,12 +875,12 @@ static CURLcode recv_closed_stream(struct Curl_cfilter *cf, CURL_TRC_CF(data, cf, "[%" PRIu64 "] error after response headers, " "but we did not want a body anyway, ignore error 0x%" PRIx64 " %s", stream->id, stream->error3, - vquic_h3_err_str(stream->error3)); + Curl_vquic_h3_err_str(stream->error3)); return CURLE_OK; } failf(data, "HTTP/3 stream %" PRIu64 " reset by server (error 0x%" PRIx64 " %s)", stream->id, stream->error3, - vquic_h3_err_str(stream->error3)); + Curl_vquic_h3_err_str(stream->error3)); result = data->req.bytecount ? CURLE_PARTIAL_FILE : CURLE_HTTP3; CURL_TRC_CF(data, cf, "[%" PRIu64 "] cf_recv, was reset -> %d", stream->id, (int)result); @@ -903,7 +904,7 @@ static CURLcode cf_quiche_recv(struct Curl_cfilter *cf, struct Curl_easy *data, *pnread = 0; (void)buf; (void)blen; - vquic_ctx_update_time(&ctx->q, Curl_pgrs_now(data)); + Curl_vquic_ctx_update_time(&ctx->q, Curl_pgrs_now(data)); if(!stream) return CURLE_RECV_ERROR; @@ -1011,9 +1012,9 @@ static CURLcode h3_open_stream(struct Curl_cfilter *cf, DEBUGASSERT(stream); result = Curl_h1_req_parse_read(&stream->h1, buf, blen, NULL, - !data->state.http_ignorecustom ? - data->set.str[STRING_CUSTOMREQUEST] : NULL, - 0, pnwritten); + !data->state.http_ignorecustom ? + CURL_EASY_STR(data, STRING_CUSTOMREQUEST) : NULL, + 0, pnwritten); if(result) goto out; if(!stream->h1.done) { @@ -1114,7 +1115,7 @@ static CURLcode cf_quiche_send(struct Curl_cfilter *cf, struct Curl_easy *data, CURLcode result; *pnwritten = 0; - vquic_ctx_update_time(&ctx->q, Curl_pgrs_now(data)); + Curl_vquic_ctx_update_time(&ctx->q, Curl_pgrs_now(data)); result = cf_process_ingress(cf, data); if(result) @@ -1140,7 +1141,7 @@ static CURLcode 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, "[%" PRIu64 "] discarding data" + CURL_TRC_CF(data, cf, "[%" PRIu64 "] discarding data " "on closed stream with response", stream->id); result = CURLE_OK; *pnwritten = len; @@ -1254,6 +1255,14 @@ static CURLcode cf_quiche_cntrl(struct Curl_cfilter *cf, Curl_conn_set_multiplex(cf->conn); } break; + case CF_CTRL_REPORT_STATS: + if(cf->connected && !ctx->stats_reported && + (ctx->handshake_at.tv_sec || ctx->handshake_at.tv_usec)) { + Curl_pgrsTimeWas(data, TIMER_CONNECT, ctx->q.first_byte_at); + Curl_pgrsTimeWas(data, TIMER_APPCONNECT, ctx->handshake_at); + ctx->stats_reported = TRUE; + } + break; default: break; } @@ -1272,7 +1281,7 @@ static CURLcode cf_quiche_ctx_open(struct Curl_cfilter *cf, DEBUGASSERT(ctx->q.sockfd != CURL_SOCKET_BAD); DEBUGASSERT(ctx->initialized); - result = vquic_ctx_init(data, &ctx->q); + result = Curl_vquic_ctx_init(data, &ctx->q); if(result) return result; @@ -1298,9 +1307,8 @@ static CURLcode cf_quiche_ctx_open(struct Curl_cfilter *cf, 10 * QUIC_MAX_STREAMS * H3_STREAM_WINDOW_SIZE); quiche_config_set_max_stream_window(ctx->cfg, 10 * H3_STREAM_WINDOW_SIZE); quiche_config_set_application_protos(ctx->cfg, - (uint8_t *)CURL_UNCONST(QUICHE_H3_APPLICATION_PROTOCOL), - sizeof(QUICHE_H3_APPLICATION_PROTOCOL) - - 1); + (uint8_t *)CURL_UNCONST(QUICHE_H3_APPLICATION_PROTOCOL), + CURL_CSTRLEN(QUICHE_H3_APPLICATION_PROTOCOL)); result = Curl_vquic_tls_init(&ctx->tls, cf, data, &ctx->ssl_peer, &ALPN_SPEC_H3, NULL, NULL, cf, NULL); @@ -1351,7 +1359,7 @@ static CURLcode cf_quiche_ctx_open(struct Curl_cfilter *cf, unsigned alpn_len, offset = 0; /* Replace each ALPN length prefix by a comma. */ - while(offset < sizeof(alpn_protocols) - 1) { + while(offset < CURL_CSTRLEN(alpn_protocols)) { alpn_len = alpn_protocols[offset]; alpn_protocols[offset] = ','; offset += 1 + alpn_len; @@ -1397,7 +1405,7 @@ static CURLcode cf_quiche_connect(struct Curl_cfilter *cf, return CURLE_OK; } - vquic_ctx_update_time(&ctx->q, Curl_pgrs_now(data)); + Curl_vquic_ctx_update_time(&ctx->q, Curl_pgrs_now(data)); if(!ctx->qconn) { result = cf_quiche_ctx_open(cf, data); @@ -1429,6 +1437,10 @@ static CURLcode cf_quiche_connect(struct Curl_cfilter *cf, result = CURLE_OUT_OF_MEMORY; goto out; } + /* quiche 0.29.3+ rejects response headers larger than 32 KiB by + default. Allow as much as curl itself accepts. */ + quiche_h3_config_set_max_field_section_size(ctx->h3config, + MAX_HTTP_RESP_HEADER_SIZE); /* Create a new HTTP/3 connection on the QUIC connection. */ ctx->h3c = quiche_h3_conn_new_with_transport(ctx->qconn, ctx->h3config); @@ -1479,7 +1491,7 @@ static CURLcode cf_quiche_shutdown(struct Curl_cfilter *cf, int err; ctx->shutdown_started = TRUE; - vquic_ctx_update_time(&ctx->q, Curl_pgrs_now(data)); + Curl_vquic_ctx_update_time(&ctx->q, Curl_pgrs_now(data)); err = quiche_conn_close(ctx->qconn, TRUE, 0, NULL, 0); if(err) { CURL_TRC_CF(data, cf, "error %d adding shutdown packet, " @@ -1497,8 +1509,8 @@ static CURLcode cf_quiche_shutdown(struct Curl_cfilter *cf, } if(Curl_bufq_is_empty(&ctx->q.sendbuf)) { - /* sent everything, quiche does not seem to support a graceful - * shutdown waiting for a reply, so ware done. */ + /* sent everything, quiche does not seem to support a graceful shutdown + * waiting for a reply, so we are done. */ CURL_TRC_CF(data, cf, "shutdown completely sent off, done"); *done = TRUE; } @@ -1547,18 +1559,6 @@ static CURLcode cf_quiche_query(struct Curl_cfilter *cf, else *pres1 = -1; return CURLE_OK; - case CF_QUERY_TIMER_CONNECT: { - struct curltime *when = pres2; - if(ctx->q.got_first_byte) - *when = ctx->q.first_byte_at; - return CURLE_OK; - } - case CF_QUERY_TIMER_APPCONNECT: { - struct curltime *when = pres2; - if(cf->connected) - *when = ctx->handshake_at; - return CURLE_OK; - } case CF_QUERY_HTTP_VERSION: *pres1 = 30; return CURLE_OK; diff --git a/Utilities/cmcurl/lib/vquic/vquic-tls.c b/Utilities/cmcurl/lib/vquic/vquic-tls.c index 58f139306a..75e2f4e1e2 100644 --- a/Utilities/cmcurl/lib/vquic/vquic-tls.c +++ b/Utilities/cmcurl/lib/vquic/vquic-tls.c @@ -166,9 +166,9 @@ CURLcode Curl_vquic_tls_verify_peer(struct curl_tls_ctx *ctx, (void)conn_config; result = Curl_ossl_check_peer_cert(cf, data, &ctx->ossl, peer); #elif defined(USE_GNUTLS) - result = Curl_gtls_verifyserver(cf, data, ctx->gtls.session, - conn_config, &data->set.ssl, peer, - data->set.str[STRING_SSL_PINNEDPUBLICKEY]); + result = Curl_gtls_verifyserver( + cf, data, ctx->gtls.session, conn_config, &data->set.ssl, peer, + CURL_EASY_STR(data, STRING_SSL_PINNEDPUBLICKEY)); if(result) return result; #elif defined(USE_WOLFSSL) diff --git a/Utilities/cmcurl/lib/vquic/vquic.c b/Utilities/cmcurl/lib/vquic/vquic.c index dfb8346c1a..bbc5777eac 100644 --- a/Utilities/cmcurl/lib/vquic/vquic.c +++ b/Utilities/cmcurl/lib/vquic/vquic.c @@ -24,6 +24,7 @@ #include "curl_setup.h" #include "urldata.h" #include "vquic/vquic.h" +#include "vtls/vtls.h" #include "curl_trc.h" @@ -32,15 +33,26 @@ #ifdef HAVE_NETINET_UDP_H #include #endif +#ifdef HAVE_NETINET_IP_H +#include +#endif #ifdef USE_NGHTTP3 #include #endif +#if defined(USE_APPLE_FAST_UDP) && defined(__APPLE__) +#include +#if defined(SYS_recvmsg_x) && defined(SYS_sendmsg_x) +#define HAVE_APPLE_MSG_X +#endif +#endif + #include "bufq.h" #include "curlx/dynbuf.h" #include "curlx/fopen.h" #include "cfilters.h" +#include "vdns/cf-dns.h" #include "vquic/cf-ngtcp2.h" #include "vquic/cf-ngtcp2-cmn.h" #include "vquic/cf-ngtcp2-proxy.h" @@ -56,6 +68,31 @@ #define NW_CHUNK_SIZE (64 * 1024) #define NW_SEND_CHUNKS 1 +#ifdef HAVE_APPLE_MSG_X + +/* this is `struct msghdr` with an additional field at the end */ +struct msghdr_x { + void *msg_name; /* optional address */ + socklen_t msg_namelen; /* size of address */ + struct iovec *msg_iov; /* scatter/gather array */ + int msg_iovlen; /* # elements in msg_iov */ + void *msg_control; /* ancillary data, see below */ + socklen_t msg_controllen; /* ancillary data buffer len */ + int msg_flags; /* flags on received message */ + size_t msg_datalen; /* byte length of buffer in msg_iov */ +}; +#endif + +#ifdef CURLVERBOSE +#ifdef HAVE_APPLE_MSG_X +#define VQUIC_SEND_METHOD "sendmsg_x" +#elif defined(HAVE_SENDMSG) +#define VQUIC_SEND_METHOD "sendmsg" +#else +#define VQUIC_SEND_METHOD "send" +#endif +#endif + int Curl_vquic_init(void) { #if defined(USE_NGTCP2) && defined(OPENSSL_QUIC_API2) @@ -66,6 +103,14 @@ int Curl_vquic_init(void) return 1; } +void Curl_vquic_cleanup(void) +{ +#if defined(USE_NGTCP2) && defined(OPENSSL_QUIC_API2) && \ + (NGTCP2_VERSION_NUM >= 0x011800) + ngtcp2_crypto_ossl_free(); +#endif +} + void Curl_quic_ver(char *p, size_t len) { #if defined(USE_NGTCP2) && defined(USE_NGHTTP3) @@ -75,13 +120,15 @@ void Curl_quic_ver(char *p, size_t len) #endif } -CURLcode vquic_ctx_init(struct Curl_easy *data, - struct cf_quic_ctx *qctx) +CURLcode Curl_vquic_ctx_init(struct Curl_easy *data, + struct cf_quic_ctx *qctx) { Curl_bufq_init2(&qctx->sendbuf, NW_CHUNK_SIZE, NW_SEND_CHUNKS, BUFQ_OPT_SOFT_LIMIT); #if defined(__linux__) && defined(UDP_SEGMENT) && defined(HAVE_SENDMSG) qctx->no_gso = FALSE; +#elif defined(HAVE_APPLE_MSG_X) + qctx->no_gso = FALSE; #else qctx->no_gso = TRUE; #endif @@ -95,28 +142,119 @@ CURLcode vquic_ctx_init(struct Curl_easy *data, } } #endif - vquic_ctx_set_time(qctx, Curl_pgrs_now(data)); + Curl_vquic_ctx_set_time(qctx, Curl_pgrs_now(data)); return CURLE_OK; } -void vquic_ctx_free(struct cf_quic_ctx *qctx) +void Curl_vquic_ctx_free(struct cf_quic_ctx *qctx) { Curl_bufq_free(&qctx->sendbuf); } -void vquic_ctx_set_time(struct cf_quic_ctx *qctx, - const struct curltime *pnow) +void Curl_vquic_ctx_set_time(struct cf_quic_ctx *qctx, + const struct curltime *pnow) { qctx->last_op = *pnow; } -void vquic_ctx_update_time(struct cf_quic_ctx *qctx, - const struct curltime *pnow) +void Curl_vquic_ctx_update_time(struct cf_quic_ctx *qctx, + const struct curltime *pnow) { qctx->last_op = *pnow; } +#ifdef HAVE_APPLE_MSG_X +static CURLcode do_sendmsg(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct cf_quic_ctx *qctx, + const uint8_t *pkt, size_t pktlen, size_t gsolen, + size_t *psent) +{ +#define MSG_X_SNUM 64 + struct iovec msg_iov[MSG_X_SNUM]; + struct msghdr_x mmsg[MSG_X_SNUM]; + char errstr[STRERROR_LEN]; + size_t n, i = 0, sent = 0; + int rc; + CURLcode result = CURLE_OK; + VERBOSE(size_t calls = 0); + + *psent = 0; + if(!pktlen) + return CURLE_OK; + if(!gsolen || (pktlen < gsolen)) + gsolen = pktlen; + n = (pktlen + gsolen - 1) / gsolen; + while(i < n) { + size_t j, batch = CURLMIN(n - i, MSG_X_SNUM), pkts_sent = 0; + + for(j = 0; j < batch; ++j) { + const size_t offset = (i + j) * gsolen; + msg_iov[j].iov_base = CURL_UNCONST(pkt + offset); + msg_iov[j].iov_len = CURLMIN(gsolen, pktlen - offset); + memset(&mmsg[j], 0, sizeof(mmsg[j])); + mmsg[j].msg_iov = &msg_iov[j]; + mmsg[j].msg_iovlen = 1; + mmsg[j].msg_datalen = msg_iov[j].iov_len; + } + +#if defined(CURL_HAVE_DIAG) && defined(__APPLE__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + while((rc = syscall(SYS_sendmsg_x, qctx->sockfd, &mmsg, batch, 0)) == -1 && + (SOCKERRNO == SOCKEINTR)) + ; +#if defined(CURL_HAVE_DIAG) && defined(__APPLE__) +#pragma GCC diagnostic pop +#endif + + if(rc < 0) { + if(SOCK_EAGAIN(SOCKERRNO)) { + CURL_TRC_CF(data, cf, "egress, sendmsg_x -> EAGAIN"); + result = sent ? CURLE_OK : CURLE_AGAIN; + goto out; + } + if(SOCKERRNO != SOCKEMSGSIZE) { + curlx_strerror(SOCKERRNO, errstr, sizeof(errstr)); + failf(data, "QUIC: sendmsg_x() returned %d (errno=%d; %s)", + rc, SOCKERRNO, errstr); + result = CURLE_SEND_ERROR; + goto out; + } + /* Error was SOCKEMSGSIZE. Network stack does not accept the packet + * length(s). This might be a PMTUD. Just drop it into the void + * and fall through to the success handling. */ + pkts_sent = batch; + } + else + pkts_sent = CURLMIN((size_t)rc, batch); + + VERBOSE(++calls); + if(!pkts_sent) { /* no packets of the current batch were sent */ + result = sent ? CURLE_OK : CURLE_AGAIN; + goto out; + } + i += pkts_sent; /* Some have been sent */ + for(j = 0; j < pkts_sent; ++j) + sent += msg_iov[j].iov_len; + if(pkts_sent < batch) + goto out; /* but not all of them */ + } + +out: + *psent = sent; + if(sent || result) + CURL_TRC_CF(data, cf, + "vquic_sendmsg_x(len=%zu, gso=%zu, packets=%zu, " + "calls=%zu) -> %d", + sent, gsolen, n, calls, (int)result); + return result; +} + +#else /* HAVE_APPLE_MSG_X */ + static CURLcode send_packet_no_gso(struct Curl_cfilter *cf, struct Curl_easy *data, struct cf_quic_ctx *qctx, @@ -151,7 +289,7 @@ static CURLcode do_sendmsg(struct Curl_cfilter *cf, * does not seem to like a msg_control of length 0. */ memset(msg_ctrl, 0, sizeof(msg_ctrl)); msg.msg_control = msg_ctrl; - assert(sizeof(msg_ctrl) >= CMSG_SPACE(sizeof(int))); + DEBUGASSERT(sizeof(msg_ctrl) >= CMSG_SPACE(sizeof(int))); msg.msg_controllen = CMSG_SPACE(sizeof(int)); cm = CMSG_FIRSTHDR(&msg); cm->cmsg_level = SOL_UDP; @@ -222,16 +360,13 @@ static CURLcode do_sendmsg(struct Curl_cfilter *cf, (void)cf; out: + CURL_TRC_CF(data, cf, + "vquic_%s(len=%zu, gso=%zu, calls=1) -> %d, sent=%zu", + VQUIC_SEND_METHOD, pktlen, gsolen, (int)result, *psent); return result; } -#ifdef CURLVERBOSE -#ifdef HAVE_SENDMSG -#define VQUIC_SEND_METHOD "sendmsg" -#else -#define VQUIC_SEND_METHOD "send" -#endif -#endif +#endif /* !HAVE_APPLE_MSG_X */ static CURLcode send_packet_no_gso(struct Curl_cfilter *cf, struct Curl_easy *data, @@ -324,17 +459,14 @@ static CURLcode vquic_send_packets(struct Curl_cfilter *cf, } else { result = do_sendmsg(cf, data, qctx, pkt, pktlen, gsolen, psent); - CURL_TRC_CF(data, cf, - "vquic_%s(len=%zu, gso=%zu, calls=1) -> %d, sent=%zu", - VQUIC_SEND_METHOD, pktlen, gsolen, (int)result, *psent); } if(!result) qctx->last_io = qctx->last_op; return result; } -CURLcode vquic_flush(struct Curl_cfilter *cf, struct Curl_easy *data, - struct cf_quic_ctx *qctx) +CURLcode Curl_vquic_flush(struct Curl_cfilter *cf, struct Curl_easy *data, + struct cf_quic_ctx *qctx) { const unsigned char *buf; size_t blen, sent; @@ -380,16 +512,17 @@ CURLcode vquic_flush(struct Curl_cfilter *cf, struct Curl_easy *data, return CURLE_OK; } -CURLcode vquic_send(struct Curl_cfilter *cf, struct Curl_easy *data, - struct cf_quic_ctx *qctx, size_t gsolen) +CURLcode Curl_vquic_send(struct Curl_cfilter *cf, struct Curl_easy *data, + struct cf_quic_ctx *qctx, size_t gsolen) { qctx->gsolen = gsolen; - return vquic_flush(cf, data, qctx); + return Curl_vquic_flush(cf, data, qctx); } -CURLcode vquic_send_tail_split(struct Curl_cfilter *cf, struct Curl_easy *data, - struct cf_quic_ctx *qctx, size_t gsolen, - size_t tail_len, size_t tail_gsolen) +CURLcode Curl_vquic_send_tail_split(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct cf_quic_ctx *qctx, size_t gsolen, + size_t tail_len, size_t tail_gsolen) { DEBUGASSERT(Curl_bufq_len(&qctx->sendbuf) > tail_len); qctx->split_len = Curl_bufq_len(&qctx->sendbuf) - tail_len; @@ -397,10 +530,11 @@ CURLcode vquic_send_tail_split(struct Curl_cfilter *cf, struct Curl_easy *data, qctx->gsolen = tail_gsolen; CURL_TRC_CF(data, cf, "vquic_send_tail_split: [%zu gso=%zu][%zu gso=%zu]", qctx->split_len, qctx->split_gsolen, tail_len, qctx->gsolen); - return vquic_flush(cf, data, qctx); + return Curl_vquic_flush(cf, data, qctx); } -#if defined(HAVE_SENDMMSG) || defined(HAVE_SENDMSG) +#if (defined(HAVE_SENDMMSG) || defined(HAVE_SENDMSG)) && \ + !defined(HAVE_APPLE_MSG_X) static size_t vquic_msghdr_get_udp_gro(struct msghdr *msg) { int gso_size = 0; @@ -423,31 +557,91 @@ static size_t vquic_msghdr_get_udp_gro(struct msghdr *msg) break; } } -#endif +#endif /* linux && UDP_GRO */ (void)msg; return (size_t)gso_size; } +#endif /* (HAVE_SENDMMSG || HAVE_SENDMSG) && !HAVE_APPLE_MSG_X */ + +#if (defined(HAVE_SENDMMSG) || defined(HAVE_SENDMSG) || \ + defined(HAVE_APPLE_MSG_X)) && \ + (defined(IP_RECVTOS) || defined(IP_TOS)) && defined(IPTOS_ECN_MASK) +static uint8_t vquic_msghdr_get_ecn(struct msghdr *msg, int family) +{ + struct cmsghdr *cmsg; + switch(family) { + case AF_INET: + /* Workaround musl CMSG_NXTHDR issue */ +#if defined(__clang__) && !defined(__GLIBC__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wsign-compare" +#pragma clang diagnostic ignored "-Wcast-align" #endif + for(cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) { +#if defined(__clang__) && !defined(__GLIBC__) +#pragma clang diagnostic pop +#endif + if(cmsg->cmsg_level == IPPROTO_IP && +#ifdef __APPLE__ + cmsg->cmsg_type == IP_RECVTOS +#else + cmsg->cmsg_type == IP_TOS +#endif + && cmsg->cmsg_len) { + return *(uint8_t *)(CMSG_DATA(cmsg)) & IPTOS_ECN_MASK; + } + } + break; + case AF_INET6: + /* Workaround musl CMSG_NXTHDR issue */ +#if defined(__clang__) && !defined(__GLIBC__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wsign-compare" +#pragma clang diagnostic ignored "-Wcast-align" +#endif + for(cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) { +#if defined(__clang__) && !defined(__GLIBC__) +#pragma clang diagnostic pop +#endif + if(cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_TCLASS && + cmsg->cmsg_len) { + unsigned int tos; + + memcpy(&tos, CMSG_DATA(cmsg), sizeof(int)); + + return (uint8_t)(tos & IPTOS_ECN_MASK); + } + } + break; + } + return 0; +} +#else +#define vquic_msghdr_get_ecn(a, b) 0 +#endif /* HAVE_SENDMMSG || HAVE_SENDMSG || HAVE_APPLE_MSG_X ... */ #ifdef HAVE_SENDMMSG + static CURLcode recvmmsg_packets(struct Curl_cfilter *cf, struct Curl_easy *data, struct cf_quic_ctx *qctx, size_t max_pkts, - vquic_recv_pkts_cb *recv_cb, void *userp) + Curl_vquic_recv_pkts_cb *recv_cb, void *userp) { #if defined(__linux__) && defined(UDP_GRO) #define MMSG_NUM 16 #define UDP_GRO_CNT_MAX 64 +#define CMSG_PER_MSG_SIZE (2 * CMSG_SPACE(sizeof(int))) #else #define MMSG_NUM 64 #define UDP_GRO_CNT_MAX 1 +#define CMSG_PER_MSG_SIZE CMSG_SPACE(sizeof(int)) #endif #define MSG_BUF_SIZE (UDP_GRO_CNT_MAX * 1500) struct iovec msg_iov[MMSG_NUM]; struct mmsghdr mmsg[MMSG_NUM]; - uint8_t msg_ctrl[MMSG_NUM * CMSG_SPACE(sizeof(int))]; + uint8_t msg_ctrl[MMSG_NUM * CMSG_PER_MSG_SIZE]; struct sockaddr_storage remote_addr[MMSG_NUM]; size_t total_nread = 0, pkts = 0; #ifdef CURLVERBOSE @@ -459,6 +653,7 @@ static CURLcode recvmmsg_packets(struct Curl_cfilter *cf, size_t gso_size; char *sockbuf = NULL; uint8_t (*bufs)[MSG_BUF_SIZE] = NULL; + uint8_t ecn = 0; DEBUGASSERT(max_pkts > 0); result = Curl_multi_xfer_sockbuf_borrow(data, MMSG_NUM * MSG_BUF_SIZE, @@ -478,8 +673,8 @@ static CURLcode recvmmsg_packets(struct Curl_cfilter *cf, mmsg[i].msg_hdr.msg_iovlen = 1; mmsg[i].msg_hdr.msg_name = &remote_addr[i]; mmsg[i].msg_hdr.msg_namelen = sizeof(remote_addr[i]); - mmsg[i].msg_hdr.msg_control = &msg_ctrl[i * CMSG_SPACE(sizeof(int))]; - mmsg[i].msg_hdr.msg_controllen = CMSG_SPACE(sizeof(int)); + mmsg[i].msg_hdr.msg_control = &msg_ctrl[i * CMSG_PER_MSG_SIZE]; + mmsg[i].msg_hdr.msg_controllen = CMSG_PER_MSG_SIZE; } while((mcount = recvmmsg(qctx->sockfd, mmsg, n, 0, NULL)) == -1 && @@ -514,13 +709,14 @@ static CURLcode recvmmsg_packets(struct Curl_cfilter *cf, } total_nread += mmsg[i].msg_len; + ecn = vquic_msghdr_get_ecn(&mmsg[i].msg_hdr, remote_addr[i].ss_family); gso_size = vquic_msghdr_get_udp_gro(&mmsg[i].msg_hdr); if(gso_size == 0) gso_size = mmsg[i].msg_len; result = recv_cb(bufs[i], mmsg[i].msg_len, gso_size, mmsg[i].msg_hdr.msg_name, - mmsg[i].msg_hdr.msg_namelen, 0, userp); + mmsg[i].msg_hdr.msg_namelen, ecn, userp); if(result) goto out; pkts += (mmsg[i].msg_len + gso_size - 1) / gso_size; @@ -536,13 +732,124 @@ out: return result; } +#elif defined(HAVE_APPLE_MSG_X) + +static CURLcode recvmsg_x_packets(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct cf_quic_ctx *qctx, + size_t max_pkts, + Curl_vquic_recv_pkts_cb *recv_cb, + void *userp) +{ +#define MSG_X_NUM 64 +#define MSG_BUF_SIZE (2048) +#define CMSG_PER_MSG_SIZE CMSG_SPACE(sizeof(int)) + struct iovec msg_iov[MSG_X_NUM]; + struct msghdr_x mmsg[MSG_X_NUM]; + uint8_t msg_ctrl[MSG_X_NUM * CMSG_PER_MSG_SIZE]; + struct sockaddr_storage remote_addr[MSG_X_NUM]; + size_t total_nread = 0, pkts = 0; +#ifdef CURLVERBOSE + size_t calls = 0; +#endif + int mcount, i; + char errstr[STRERROR_LEN]; + CURLcode result = CURLE_OK; + size_t gso_size; + char *sockbuf = NULL; + uint8_t (*bufs)[MSG_BUF_SIZE] = NULL; + uint8_t ecn = 0; + + DEBUGASSERT(max_pkts > 0); + result = Curl_multi_xfer_sockbuf_borrow(data, MSG_X_NUM * MSG_BUF_SIZE, + &sockbuf); + if(result) + goto out; + bufs = (uint8_t (*)[MSG_BUF_SIZE])sockbuf; + + total_nread = 0; + while(pkts < max_pkts) { + int n = (int)CURLMIN(CURLMIN(MSG_X_NUM, IOV_MAX), max_pkts); + memset(&mmsg, 0, sizeof(mmsg)); + for(i = 0; i < n; ++i) { + msg_iov[i].iov_base = bufs[i]; + msg_iov[i].iov_len = sizeof(bufs[i]); + mmsg[i].msg_iov = &msg_iov[i]; + mmsg[i].msg_iovlen = 1; + mmsg[i].msg_name = &remote_addr[i]; + mmsg[i].msg_namelen = sizeof(remote_addr[i]); + mmsg[i].msg_control = &msg_ctrl[i * CMSG_PER_MSG_SIZE]; + mmsg[i].msg_controllen = CMSG_PER_MSG_SIZE; + } + +#if defined(CURL_HAVE_DIAG) && defined(__APPLE__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + while((mcount = syscall(SYS_recvmsg_x, qctx->sockfd, mmsg, n, 0)) == -1 && + (SOCKERRNO == SOCKEINTR || SOCKERRNO == SOCKEMSGSIZE)) + ; +#if defined(CURL_HAVE_DIAG) && defined(__APPLE__) +#pragma GCC diagnostic pop +#endif + if(mcount == -1) { + if(SOCK_EAGAIN(SOCKERRNO)) { + CURL_TRC_CF(data, cf, "ingress, recvmsg_x -> EAGAIN"); + goto out; + } + if(!cf->connected && SOCKERRNO == SOCKECONNREFUSED) { + struct ip_quadruple ip; + if(!Curl_cf_socket_peek(cf->next, data, NULL, NULL, &ip)) + failf(data, "QUIC: connection to %s port %u refused", + ip.remote_ip, ip.remote_port); + result = CURLE_COULDNT_CONNECT; + goto out; + } + curlx_strerror(SOCKERRNO, errstr, sizeof(errstr)); + failf(data, "QUIC: recvmsg_x() unexpectedly returned %d (errno=%d; %s)", + mcount, SOCKERRNO, errstr); + result = CURLE_RECV_ERROR; + goto out; + } + + VERBOSE(++calls); + for(i = 0; i < mcount; ++i) { + /* A zero-length UDP packet is no QUIC packet. Ignore. */ + if(!mmsg[i].msg_datalen) { + ++pkts; + continue; + } + total_nread += mmsg[i].msg_datalen; + ecn = vquic_msghdr_get_ecn((struct msghdr *)&mmsg[i], + remote_addr[i].ss_family); + gso_size = mmsg[i].msg_datalen; + + result = recv_cb(bufs[i], mmsg[i].msg_datalen, gso_size, + mmsg[i].msg_name, + mmsg[i].msg_namelen, ecn, userp); + if(result) + goto out; + pkts += (mmsg[i].msg_datalen + gso_size - 1) / gso_size; + } + } + +out: + if(total_nread || result) + CURL_TRC_CF(data, cf, + "vquic_recvmsg_x(len=%zu, packets=%zu, calls=%zu) -> %d", + total_nread, pkts, calls, (int)result); + Curl_multi_xfer_sockbuf_release(data, sockbuf); + return result; +} + #elif defined(HAVE_SENDMSG) static CURLcode recvmsg_packets(struct Curl_cfilter *cf, struct Curl_easy *data, struct cf_quic_ctx *qctx, size_t max_pkts, - vquic_recv_pkts_cb *recv_cb, void *userp) + Curl_vquic_recv_pkts_cb *recv_cb, void *userp) { +#define CMSG_PER_MSG_SIZE CMSG_SPACE(sizeof(int)) struct iovec msg_iov; struct msghdr msg; uint8_t buf[64 * 1024]; @@ -552,8 +859,9 @@ static CURLcode recvmsg_packets(struct Curl_cfilter *cf, size_t nread; char errstr[STRERROR_LEN]; CURLcode result = CURLE_OK; - uint8_t msg_ctrl[CMSG_SPACE(sizeof(int))]; + uint8_t msg_ctrl[CMSG_PER_MSG_SIZE]; size_t gso_size; + uint8_t ecn = 0; DEBUGASSERT(max_pkts > 0); for(pkts = 0, total_nread = 0, calls = 0; pkts < max_pkts;) { @@ -567,7 +875,7 @@ static CURLcode recvmsg_packets(struct Curl_cfilter *cf, msg.msg_control = msg_ctrl; msg.msg_name = &remote_addr; msg.msg_namelen = sizeof(remote_addr); - msg.msg_controllen = sizeof(msg_ctrl); + msg.msg_controllen = CMSG_PER_MSG_SIZE; while((rc = recvmsg(qctx->sockfd, &msg, 0)) == -1 && (SOCKERRNO == SOCKEINTR || SOCKERRNO == SOCKEMSGSIZE)) @@ -600,12 +908,13 @@ static CURLcode recvmsg_packets(struct Curl_cfilter *cf, continue; } + ecn = vquic_msghdr_get_ecn(&msg, remote_addr.ss_family); gso_size = vquic_msghdr_get_udp_gro(&msg); if(gso_size == 0) gso_size = nread; result = recv_cb(buf, nread, gso_size, - msg.msg_name, msg.msg_namelen, 0, userp); + msg.msg_name, msg.msg_namelen, ecn, userp); if(result) goto out; pkts += (nread + gso_size - 1) / gso_size; @@ -624,7 +933,7 @@ static CURLcode recvfrom_packets(struct Curl_cfilter *cf, struct Curl_easy *data, struct cf_quic_ctx *qctx, size_t max_pkts, - vquic_recv_pkts_cb *recv_cb, void *userp) + Curl_vquic_recv_pkts_cb *recv_cb, void *userp) { uint8_t buf[64 * 1024]; int bufsize = (int)sizeof(buf); @@ -685,15 +994,17 @@ out: } #endif /* !HAVE_SENDMMSG && !HAVE_SENDMSG */ -CURLcode vquic_recv_packets(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct cf_quic_ctx *qctx, - size_t max_pkts, - vquic_recv_pkts_cb *recv_cb, void *userp) +CURLcode Curl_vquic_recv_packets(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct cf_quic_ctx *qctx, + size_t max_pkts, + Curl_vquic_recv_pkts_cb *recv_cb, void *userp) { CURLcode result; #ifdef HAVE_SENDMMSG result = recvmmsg_packets(cf, data, qctx, max_pkts, recv_cb, userp); +#elif defined(HAVE_APPLE_MSG_X) + result = recvmsg_x_packets(cf, data, qctx, max_pkts, recv_cb, userp); #elif defined(HAVE_SENDMSG) result = recvmsg_packets(cf, data, qctx, max_pkts, recv_cb, userp); #else @@ -761,19 +1072,36 @@ CURLcode Curl_qlogdir(struct Curl_easy *data, } CURLcode Curl_cf_quic_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data, struct Curl_peer *origin, struct Curl_peer *peer) { + CURLcode result; + + (void)data; /* not used in all cases and compilers are stupid */ #if defined(USE_NGTCP2) && defined(USE_NGHTTP3) - return Curl_cf_ngtcp2_insert_after(cf_at, origin, peer); + result = Curl_cf_ngtcp2_insert_after(cf_at, origin, peer); #elif defined(USE_QUICHE) - return Curl_cf_quiche_insert_after(cf_at, origin, peer); + result = Curl_cf_quiche_insert_after(cf_at, origin, peer); #else (void)cf_at; (void)origin; (void)peer; - return CURLE_NOT_BUILT_IN; + result = CURLE_NOT_BUILT_IN; #endif + +#if defined(USE_HTTPSRR) && defined(USE_ECH) + /* When using ECH, kick off the HTTPS-RR resolve */ + if(!result && (origin->scheme->family == CURLPROTO_HTTP) && + CURLECH_ENABLED(data) && + Curl_ssl_supports(data, SSLSUPP_ECH) && + (data->set.tls_ech != CURLECH_GREASE) && + !CURL_EASY_STR(data, STRING_ECH_CONFIG)) { + result = Curl_conn_dns_add_https_resolve(data, cf_at->conn, + cf_at->sockindex, origin); + } +#endif /* USE_HTTPSRR && USE_ECH */ + return result; } CURLcode Curl_cf_quic_create(struct Curl_cfilter **pcf, @@ -885,7 +1213,7 @@ CURLcode Curl_conn_may_http3(struct Curl_easy *data, } #ifdef CURLVERBOSE -const char *vquic_h3_err_str(uint64_t error_code) +const char *Curl_vquic_h3_err_str(uint64_t error_code) { if(error_code <= UINT_MAX) { switch((unsigned int)error_code) { diff --git a/Utilities/cmcurl/lib/vquic/vquic.h b/Utilities/cmcurl/lib/vquic/vquic.h index 5211a9b33a..f1363e2c0a 100644 --- a/Utilities/cmcurl/lib/vquic/vquic.h +++ b/Utilities/cmcurl/lib/vquic/vquic.h @@ -33,6 +33,7 @@ struct Curl_addrinfo; void Curl_quic_ver(char *p, size_t len); int Curl_vquic_init(void); +void Curl_vquic_cleanup(void); CURLcode Curl_qlogdir(struct Curl_easy *data, unsigned char *scid, @@ -40,6 +41,7 @@ CURLcode Curl_qlogdir(struct Curl_easy *data, int *qlogfdp); CURLcode Curl_cf_quic_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data, struct Curl_peer *origin, struct Curl_peer *peer); @@ -80,6 +82,7 @@ extern struct Curl_cftype Curl_cft_h3_proxy; #else #define Curl_vquic_init() 1 +#define Curl_vquic_cleanup() #endif /* !CURL_DISABLE_HTTP && USE_HTTP3 */ CURLcode Curl_conn_may_http3(struct Curl_easy *data, diff --git a/Utilities/cmcurl/lib/vquic/vquic_int.h b/Utilities/cmcurl/lib/vquic/vquic_int.h index db5183d431..23e1102eb0 100644 --- a/Utilities/cmcurl/lib/vquic/vquic_int.h +++ b/Utilities/cmcurl/lib/vquic/vquic_int.h @@ -53,9 +53,9 @@ typedef enum { } vquic_h3_error; #ifdef CURLVERBOSE -const char *vquic_h3_err_str(uint64_t error_code); +const char *Curl_vquic_h3_err_str(uint64_t error_code); #else -#define vquic_h3_err_str(x) "" +#define Curl_vquic_h3_err_str(x) "" #endif /* CURLVERBOSE */ struct cf_quic_ctx { @@ -80,37 +80,40 @@ struct cf_quic_ctx { #define H3_STREAM_CTX(ctx, data) \ ((data) ? Curl_uint32_hash_get(&(ctx)->streams, (data)->mid) : NULL) -CURLcode vquic_ctx_init(struct Curl_easy *data, - struct cf_quic_ctx *qctx); -void vquic_ctx_free(struct cf_quic_ctx *qctx); +CURLcode Curl_vquic_ctx_init(struct Curl_easy *data, + struct cf_quic_ctx *qctx); +void Curl_vquic_ctx_free(struct cf_quic_ctx *qctx); -void vquic_ctx_set_time(struct cf_quic_ctx *qctx, - const struct curltime *pnow); +void Curl_vquic_ctx_set_time(struct cf_quic_ctx *qctx, + const struct curltime *pnow); -void vquic_ctx_update_time(struct cf_quic_ctx *qctx, - const struct curltime *pnow); +void Curl_vquic_ctx_update_time(struct cf_quic_ctx *qctx, + const struct curltime *pnow); -CURLcode vquic_send(struct Curl_cfilter *cf, struct Curl_easy *data, - struct cf_quic_ctx *qctx, size_t gsolen); +CURLcode Curl_vquic_send(struct Curl_cfilter *cf, struct Curl_easy *data, + struct cf_quic_ctx *qctx, size_t gsolen); -CURLcode vquic_send_tail_split(struct Curl_cfilter *cf, struct Curl_easy *data, - struct cf_quic_ctx *qctx, size_t gsolen, - size_t tail_len, size_t tail_gsolen); +CURLcode Curl_vquic_send_tail_split(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct cf_quic_ctx *qctx, size_t gsolen, + size_t tail_len, size_t tail_gsolen); -CURLcode vquic_flush(struct Curl_cfilter *cf, struct Curl_easy *data, - struct cf_quic_ctx *qctx); +CURLcode Curl_vquic_flush(struct Curl_cfilter *cf, struct Curl_easy *data, + struct cf_quic_ctx *qctx); -typedef CURLcode vquic_recv_pkts_cb(const unsigned char *buf, size_t buflen, - size_t gso_size, - struct sockaddr_storage *remote_addr, - socklen_t remote_addrlen, int ecn, - void *userp); +typedef CURLcode Curl_vquic_recv_pkts_cb(const unsigned char *buf, + size_t buflen, + size_t gso_size, + struct sockaddr_storage *remote_addr, + socklen_t remote_addrlen, uint8_t ecn, + void *userp); -CURLcode vquic_recv_packets(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct cf_quic_ctx *qctx, - size_t max_pkts, - vquic_recv_pkts_cb *recv_cb, void *userp); +CURLcode Curl_vquic_recv_packets(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct cf_quic_ctx *qctx, + size_t max_pkts, + Curl_vquic_recv_pkts_cb *recv_cb, + void *userp); #ifdef USE_NGTCP2 struct ngtcp2_mem; diff --git a/Utilities/cmcurl/lib/vssh/libssh.c b/Utilities/cmcurl/lib/vssh/libssh.c index e4071caa46..03a84d218b 100644 --- a/Utilities/cmcurl/lib/vssh/libssh.c +++ b/Utilities/cmcurl/lib/vssh/libssh.c @@ -45,7 +45,6 @@ #include "urldata.h" #include "sendf.h" #include "curl_trc.h" -#include "hostip.h" #include "progress.h" #include "transfer.h" #include "vssh/ssh.h" @@ -110,10 +109,10 @@ static CURLcode sftp_error_to_CURLE(int err) } /* Multiple options: - * 1. data->set.str[STRING_SSH_HOST_PUBLIC_KEY_SHA256] is set with a SHA256 - * hash. - * 2. data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5] is set with an MD5 - * hash (90s style auth, not sure we should have it here) + * 1. CURL_EASY_STR(data, STRING_SSH_HOST_PUBLIC_KEY_SHA256) is set + * with a SHA256 hash. + * 2. CURL_EASY_STR(data, STRING_SSH_HOST_PUBLIC_KEY_MD5) is set + * with an MD5 hash (90s style auth, not sure we should have it here) * 3. data->set.ssh_keyfunc callback is set. Then we do trust on first * use. We even save on knownhosts if CURLKHSTAT_FINE_ADD_TO_FILE * is returned by it. @@ -144,9 +143,9 @@ static int myssh_is_known(struct Curl_easy *data, struct ssh_conn *sshc) if(rc != SSH_OK) return rc; - if(data->set.str[STRING_SSH_HOST_PUBLIC_KEY_SHA256]) { + if(CURL_EASY_STR(data, STRING_SSH_HOST_PUBLIC_KEY_SHA256)) { const char *pubkey_sha256 = - data->set.str[STRING_SSH_HOST_PUBLIC_KEY_SHA256]; + CURL_EASY_STR(data, STRING_SSH_HOST_PUBLIC_KEY_SHA256); char *fingerprint_b64 = NULL; size_t fingerprint_b64_len; size_t pub_pos = 0; @@ -198,8 +197,9 @@ static int myssh_is_known(struct Curl_easy *data, struct ssh_conn *sshc) goto cleanup; } - if(data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5]) { - const char *pubkey_md5 = data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5]; + if(CURL_EASY_STR(data, STRING_SSH_HOST_PUBLIC_KEY_MD5)) { + const char *pubkey_md5 = + CURL_EASY_STR(data, STRING_SSH_HOST_PUBLIC_KEY_MD5); char md5buffer[33]; int i; @@ -228,7 +228,7 @@ static int myssh_is_known(struct Curl_easy *data, struct ssh_conn *sshc) goto cleanup; } - if(data->set.str[STRING_SSH_KNOWNHOSTS]) { + if(CURL_EASY_STR(data, STRING_SSH_KNOWNHOSTS)) { /* Get the known_key from the known hosts file */ vstate = ssh_session_get_known_hosts_entry(sshc->ssh_session, @@ -290,6 +290,8 @@ static int myssh_is_known(struct Curl_easy *data, struct ssh_conn *sshc) } if(func) { /* use callback to determine action */ + struct Curl_mapi_guard guard; + rc = ssh_pki_export_pubkey_base64(pubkey, &found_base64); if(rc != SSH_OK) goto cleanup; @@ -321,11 +323,11 @@ static int myssh_is_known(struct Curl_easy *data, struct ssh_conn *sshc) goto cleanup; } - Curl_set_in_callback(data, TRUE); + CURL_CBAPI_START(&guard, data, easy_ssh_keyfunc); rc = func(data, knownkeyp, /* from the knownhosts file */ &foundkey, /* from the remote host */ keymatch, data->set.ssh_keyfunc_userp); - Curl_set_in_callback(data, FALSE); + CURL_CBAPI_END(&guard); switch(rc) { case CURLKHSTAT_FINE_ADD_TO_FILE: @@ -832,8 +834,7 @@ static int myssh_in_AUTHLIST(struct Curl_easy *data, /* For public key auth we need either the private key or CURLSSH_AUTH_AGENT. */ if((sshc->auth_methods & SSH_AUTH_METHOD_PUBLICKEY) && - (data->set.str[STRING_SSH_PRIVATE_KEY] || - (data->set.ssh_auth_types & CURLSSH_AUTH_AGENT))) { + (sshc->priv_key || (data->set.ssh_auth_types & CURLSSH_AUTH_AGENT))) { myssh_to(data, sshc, SSH_AUTH_PKEY_INIT); infof(data, "Authentication using SSH public key file"); } @@ -863,7 +864,7 @@ static int myssh_in_AUTH_PKEY_INIT(struct Curl_easy *data, /* Two choices, (1) private key was given on CMD, * (2) use the "default" keys. */ - if(data->set.str[STRING_SSH_PRIVATE_KEY]) { + if(sshc->priv_key) { if(sshc->pubkey && !data->set.ssl.primary.key_passwd) { rc = ssh_userauth_try_publickey(sshc->ssh_session, NULL, sshc->pubkey); if(rc == SSH_AUTH_AGAIN) @@ -875,13 +876,11 @@ static int myssh_in_AUTH_PKEY_INIT(struct Curl_easy *data, } } - rc = ssh_pki_import_privkey_file(data-> - set.str[STRING_SSH_PRIVATE_KEY], + rc = ssh_pki_import_privkey_file(sshc->priv_key, data->set.ssl.primary.key_passwd, NULL, NULL, &sshc->privkey); if(rc != SSH_OK) { - failf(data, "Could not load private key file %s", - data->set.str[STRING_SSH_PRIVATE_KEY]); + failf(data, "Could not load private key file %s", sshc->priv_key); rc = myssh_to_ERROR(data, sshc, CURLE_LOGIN_DENIED); return rc; } @@ -1100,10 +1099,11 @@ static int myssh_in_UPLOAD_INIT(struct Curl_easy *data, int seekerr = CURL_SEEKFUNC_OK; /* Let's read off the proper amount of bytes from the input. */ if(data->set.seek_func) { - Curl_set_in_callback(data, TRUE); + struct Curl_mapi_guard guard; + CURL_CBAPI_START(&guard, data, easy_seek_func); seekerr = data->set.seek_func(data->set.seek_client, data->state.resume_from, SEEK_SET); - Curl_set_in_callback(data, FALSE); + CURL_CBAPI_END(&guard); } if(seekerr != CURL_SEEKFUNC_OK) { @@ -1761,7 +1761,7 @@ static int myssh_in_SFTP_QUOTE_STAT(struct Curl_easy *data, return SSH_NO_ERROR; } -static void conn_forget_socket(struct Curl_easy *data, int sockindex) +static void conn_forget_socket(struct Curl_easy *data, int8_t sockindex) { struct connectdata *conn = data->conn; if(conn && CONN_SOCK_IDX_VALID(sockindex)) { @@ -1916,8 +1916,8 @@ static void sshc_cleanup(struct ssh_conn *sshc) sshc->pubkey = NULL; } - curlx_safefree(sshc->rsa_pub); - curlx_safefree(sshc->rsa); + curlx_safefree(sshc->pub_key); + curlx_safefree(sshc->priv_key); curlx_safefree(sshc->quote_path1); curlx_safefree(sshc->quote_path2); curlx_dyn_free(&sshc->readdir_buf); @@ -2238,7 +2238,7 @@ static CURLcode myssh_in_SESSION_FREE(struct Curl_easy *data, /* the code we are about to return */ result = sshc->actualcode; memset(sshc, 0, sizeof(struct ssh_conn)); - connclose(data->conn, "SSH session free"); + connclose(data->conn); sshc->state = SSH_SESSION_FREE; /* current */ sshc->nextstate = SSH_NO_STATE; myssh_to(data, sshc, SSH_STOP); @@ -2448,36 +2448,6 @@ static CURLcode myssh_statemachine(struct Curl_easy *data, return result; } -/* called by the multi interface to figure out what socket(s) to wait for and - for what actions in the DO_DONE, PERFORM and WAITPERFORM states */ -static CURLcode myssh_pollset(struct Curl_easy *data, - struct easy_pollset *ps) -{ - struct connectdata *conn = data->conn; - struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); - curl_socket_t sock = conn->sock[FIRSTSOCKET]; - int waitfor; - - if(!sshc || (sock == CURL_SOCKET_BAD)) - return CURLE_FAILED_INIT; - - waitfor = sshc->waitfor ? sshc->waitfor : data->req.io_flags; - if(waitfor) { - int flags = 0; - if(waitfor & REQ_IO_RECV) - flags |= CURL_POLL_IN; - if(waitfor & REQ_IO_SEND) - flags |= CURL_POLL_OUT; - DEBUGASSERT(flags); - CURL_TRC_SSH(data, "pollset, flags=%x", (unsigned int)flags); - return Curl_pollset_change(data, ps, sock, flags, 0); - } - /* While we still have a session, we listen incoming data. */ - if(sshc->ssh_session) - return Curl_pollset_change(data, ps, sock, CURL_POLL_IN, 0); - return CURLE_OK; -} - /* called repeatedly until done from multi.c */ static CURLcode myssh_multi_statemach(struct Curl_easy *data, bool *done) @@ -2485,8 +2455,8 @@ static CURLcode myssh_multi_statemach(struct Curl_easy *data, struct connectdata *conn = data->conn; struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); struct SSHPROTO *sshp = Curl_meta_get(data, CURL_META_SSH_EASY); - bool block; /* we store the status and use that to provide a ssh_pollset() - implementation */ + bool block; /* we store the status and use that to provide + a Curl_ssh_pollset() implementation */ CURLcode result; if(!sshc || !sshp) @@ -2577,7 +2547,7 @@ static CURLcode myssh_setup_connection(struct Curl_easy *data, Curl_meta_set(data, CURL_META_SSH_EASY, sshp, myssh_easy_dtor)) return CURLE_OUT_OF_MEMORY; - return CURLE_OK; + return Curl_ssh_setup_pkey(data, sshc); } static Curl_recv scp_recv, sftp_recv; @@ -2615,9 +2585,11 @@ static CURLcode myssh_connect(struct Curl_easy *data, bool *done) return CURLE_FAILED_INIT; } + /* For IPv6 origins, use the `user_hostname` that has the "[]" enclosure. + * Otherwise, use `hostname` that is IDN converted. */ rc = ssh_options_set(sshc->ssh_session, SSH_OPTIONS_HOST, - (data->state.up.hostname[0] == '[') ? - data->state.up.hostname : conn->origin->hostname); + conn->origin->ipv6 ? + conn->origin->user_hostname : conn->origin->hostname); if(rc != SSH_OK) { failf(data, "Could not set remote host"); @@ -2647,15 +2619,16 @@ static CURLcode myssh_connect(struct Curl_easy *data, bool *done) } } - if(data->set.str[STRING_SSH_KNOWNHOSTS]) { - infof(data, "Known hosts: %s", data->set.str[STRING_SSH_KNOWNHOSTS]); + if(CURL_EASY_STR(data, STRING_SSH_KNOWNHOSTS)) { + infof(data, "Known hosts: %s", + CURL_EASY_STR(data, STRING_SSH_KNOWNHOSTS)); rc = ssh_options_set(sshc->ssh_session, SSH_OPTIONS_KNOWNHOSTS, - data->set.str[STRING_SSH_KNOWNHOSTS]); + CURL_EASY_STR(data, STRING_SSH_KNOWNHOSTS)); if(rc == SSH_OK) /* libssh has two separate options for this. Set both to the same file to avoid surprises */ rc = ssh_options_set(sshc->ssh_session, SSH_OPTIONS_GLOBAL_KNOWNHOSTS, - data->set.str[STRING_SSH_KNOWNHOSTS]); + CURL_EASY_STR(data, STRING_SSH_KNOWNHOSTS)); if(rc != SSH_OK) { failf(data, "Could not set known hosts file path"); return CURLE_FAILED_INIT; @@ -2683,9 +2656,8 @@ static CURLcode myssh_connect(struct Curl_easy *data, bool *done) sshc->privkey = NULL; sshc->pubkey = NULL; - if(data->set.str[STRING_SSH_PUBLIC_KEY]) { - rc = ssh_pki_import_pubkey_file(data->set.str[STRING_SSH_PUBLIC_KEY], - &sshc->pubkey); + if(sshc->pub_key) { + rc = ssh_pki_import_pubkey_file(sshc->pub_key, &sshc->pubkey); if(rc != SSH_OK) { failf(data, "Could not load public key file"); return CURLE_FAILED_INIT; @@ -2808,7 +2780,7 @@ static CURLcode scp_done(struct Curl_easy *data, CURLcode status, return myssh_done(data, sshc, status); } -static CURLcode scp_send(struct Curl_easy *data, int sockindex, +static CURLcode scp_send(struct Curl_easy *data, int8_t sockindex, const uint8_t *mem, size_t len, bool eos, size_t *pnwritten) { @@ -2842,7 +2814,7 @@ static CURLcode scp_send(struct Curl_easy *data, int sockindex, return CURLE_OK; } -static CURLcode scp_recv(struct Curl_easy *data, int sockindex, +static CURLcode scp_recv(struct Curl_easy *data, int8_t sockindex, char *mem, size_t len, size_t *pnread) { struct connectdata *conn = data->conn; @@ -2968,7 +2940,7 @@ static CURLcode sftp_done(struct Curl_easy *data, CURLcode status, } /* return number of sent bytes */ -static CURLcode sftp_send(struct Curl_easy *data, int sockindex, +static CURLcode sftp_send(struct Curl_easy *data, int8_t sockindex, const uint8_t *mem, size_t len, bool eos, size_t *pnwritten) { @@ -3049,7 +3021,7 @@ static CURLcode sftp_send(struct Curl_easy *data, int sockindex, * Return number of received (decrypted) bytes * or <0 on error */ -static CURLcode sftp_recv(struct Curl_easy *data, int sockindex, +static CURLcode sftp_recv(struct Curl_easy *data, int8_t sockindex, char *mem, size_t len, size_t *pnread) { struct connectdata *conn = data->conn; @@ -3168,10 +3140,10 @@ const struct Curl_protocol Curl_protocol_scp = { myssh_connect, /* connect_it */ myssh_multi_statemach, /* connecting */ scp_doing, /* doing */ - myssh_pollset, /* proto_pollset */ - myssh_pollset, /* doing_pollset */ + Curl_ssh_pollset, /* proto_pollset */ + Curl_ssh_pollset, /* doing_pollset */ ZERO_NULL, /* domore_pollset */ - myssh_pollset, /* perform_pollset */ + Curl_ssh_pollset, /* perform_pollset */ scp_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ ZERO_NULL, /* write_resp_hd */ @@ -3191,10 +3163,10 @@ const struct Curl_protocol Curl_protocol_sftp = { myssh_connect, /* connect_it */ myssh_multi_statemach, /* connecting */ sftp_doing, /* doing */ - myssh_pollset, /* proto_pollset */ - myssh_pollset, /* doing_pollset */ + Curl_ssh_pollset, /* proto_pollset */ + Curl_ssh_pollset, /* doing_pollset */ ZERO_NULL, /* domore_pollset */ - myssh_pollset, /* perform_pollset */ + Curl_ssh_pollset, /* perform_pollset */ sftp_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ ZERO_NULL, /* write_resp_hd */ diff --git a/Utilities/cmcurl/lib/vssh/libssh2.c b/Utilities/cmcurl/lib/vssh/libssh2.c index b12d69f317..24a651d30e 100644 --- a/Utilities/cmcurl/lib/vssh/libssh2.c +++ b/Utilities/cmcurl/lib/vssh/libssh2.c @@ -44,7 +44,6 @@ #include "urldata.h" #include "sendf.h" #include "curl_trc.h" -#include "hostip.h" #include "progress.h" #include "transfer.h" #include "vssh/ssh.h" @@ -317,7 +316,7 @@ static CURLcode ssh_knownhost(struct Curl_easy *data, int rc = 0; CURLcode result = CURLE_OK; - if(!data->set.str[STRING_SSH_KNOWNHOSTS]) { + if(!CURL_EASY_STR(data, STRING_SSH_KNOWNHOSTS)) { infof(data, "SSH: no knownhosts file configured"); return CURLE_OK; } @@ -330,6 +329,7 @@ static CURLcode ssh_knownhost(struct Curl_easy *data, * What hostname does OpenSSH store in its file if an IDN name is * used? */ + struct Curl_mapi_guard guard; enum curl_khmatch keymatch; curl_sshkeycallback func = data->set.ssh_keyfunc ? data->set.ssh_keyfunc : sshkeycallback; @@ -372,8 +372,8 @@ static CURLcode ssh_knownhost(struct Curl_easy *data, (conn->origin->port != PORT_SSH) ? conn->origin->port : -1, remotekey, keylen, - LIBSSH2_KNOWNHOST_TYPE_PLAIN| - LIBSSH2_KNOWNHOST_KEYENC_RAW| + LIBSSH2_KNOWNHOST_TYPE_PLAIN | + LIBSSH2_KNOWNHOST_KEYENC_RAW | keybit, &host); @@ -402,11 +402,11 @@ static CURLcode ssh_knownhost(struct Curl_easy *data, keymatch = (enum curl_khmatch)keycheck; /* Ask the callback how to behave */ - Curl_set_in_callback(data, TRUE); + CURL_CBAPI_START(&guard, data, easy_ssh_keyfunc); rc = func(data, knownkeyp, /* from the knownhosts file */ &foundkey, /* from the remote host */ keymatch, data->set.ssh_keyfunc_userp); - Curl_set_in_callback(data, FALSE); + CURL_CBAPI_END(&guard); } } else { @@ -460,12 +460,12 @@ static CURLcode ssh_knownhost(struct Curl_easy *data, /* now we write the entire in-memory list of known hosts to the known_hosts file */ int wrc = - libssh2_knownhost_writefile(sshc->kh, - data->set.str[STRING_SSH_KNOWNHOSTS], - LIBSSH2_KNOWNHOST_FILE_OPENSSH); + libssh2_knownhost_writefile( + sshc->kh, CURL_EASY_STR(data, STRING_SSH_KNOWNHOSTS), + LIBSSH2_KNOWNHOST_FILE_OPENSSH); if(wrc) { infof(data, "WARNING: writing %s failed", - data->set.str[STRING_SSH_KNOWNHOSTS]); + CURL_EASY_STR(data, STRING_SSH_KNOWNHOSTS)); } } } @@ -481,8 +481,10 @@ static CURLcode ssh_knownhost(struct Curl_easy *data, static CURLcode ssh_check_fingerprint(struct Curl_easy *data, struct ssh_conn *sshc) { - const char *pubkey_md5 = data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5]; - const char *pubkey_sha256 = data->set.str[STRING_SSH_HOST_PUBLIC_KEY_SHA256]; + const char *pubkey_md5 = + CURL_EASY_STR(data, STRING_SSH_HOST_PUBLIC_KEY_MD5); + const char *pubkey_sha256 = + CURL_EASY_STR(data, STRING_SSH_HOST_PUBLIC_KEY_SHA256); if(pubkey_sha256) { const char *fingerprint = NULL; @@ -595,11 +597,12 @@ static CURLcode ssh_check_fingerprint(struct Curl_easy *data, const char *remotekey = libssh2_session_hostkey(sshc->ssh_session, &keylen, &sshkeytype); if(remotekey) { + struct Curl_mapi_guard guard; enum curl_khtype keytype = convert_ssh2_keytype(sshkeytype); - Curl_set_in_callback(data, TRUE); + CURL_CBAPI_START(&guard, data, easy_ssh_hostkeyfunc); rc = data->set.ssh_hostkeyfunc(data->set.ssh_hostkeyfunc_userp, (int)keytype, remotekey, keylen); - Curl_set_in_callback(data, FALSE); + CURL_CBAPI_END(&guard); if(rc != CURLKHMATCH_OK) { myssh_to(data, sshc, SSH_SESSION_FREE); failf(data, "SSH: callback failed host public key verification"); @@ -645,8 +648,8 @@ static CURLcode ssh_force_knownhost_key_type(struct Curl_easy *data, bool found = FALSE; if(sshc->kh && - !data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5] && - !data->set.str[STRING_SSH_HOST_PUBLIC_KEY_SHA256]) { + !CURL_EASY_STR(data, STRING_SSH_HOST_PUBLIC_KEY_MD5) && + !CURL_EASY_STR(data, STRING_SSH_HOST_PUBLIC_KEY_SHA256)) { struct libssh2_knownhost *store = NULL; struct connectdata *conn = data->conn; /* lets try to find our host in the known hosts file */ @@ -662,7 +665,8 @@ static CURLcode ssh_force_knownhost_key_type(struct Curl_easy *data, const char *kh_name_end = strstr(store->name, "]:"); if(!kh_name_end) { infof(data, "SSH: invalid host pattern %s in %s", - store->name, data->set.str[STRING_SSH_KNOWNHOSTS]); + store->name, + CURL_EASY_STR(data, STRING_SSH_KNOWNHOSTS)); continue; } p = kh_name_end + 2; /* start of port number */ @@ -692,7 +696,8 @@ static CURLcode ssh_force_knownhost_key_type(struct Curl_easy *data, int rc; const char *hostkey_method = NULL; infof(data, "SSH: found host '%s' in '%s'", - conn->origin->hostname, data->set.str[STRING_SSH_KNOWNHOSTS]); + conn->origin->hostname, + CURL_EASY_STR(data, STRING_SSH_KNOWNHOSTS)); switch(store->typemask & LIBSSH2_KNOWNHOST_KEY_MASK) { case LIBSSH2_KNOWNHOST_KEY_ED25519: @@ -737,7 +742,8 @@ static CURLcode ssh_force_knownhost_key_type(struct Curl_easy *data, } else { infof(data, "SSH: did not find host '%s' in '%s'", - conn->origin->hostname, data->set.str[STRING_SSH_KNOWNHOSTS]); + conn->origin->hostname, + CURL_EASY_STR(data, STRING_SSH_KNOWNHOSTS)); } } @@ -1028,10 +1034,11 @@ static CURLcode sftp_upload_init(struct Curl_easy *data, int seekerr = CURL_SEEKFUNC_OK; /* Let's read off the proper amount of bytes from the input. */ if(data->set.seek_func) { - Curl_set_in_callback(data, TRUE); + struct Curl_mapi_guard guard; + CURL_CBAPI_START(&guard, data, easy_seek_func); seekerr = data->set.seek_func(data->set.seek_client, data->state.resume_from, SEEK_SET); - Curl_set_in_callback(data, FALSE); + CURL_CBAPI_END(&guard); } if(seekerr != CURL_SEEKFUNC_OK) { @@ -1043,6 +1050,7 @@ static CURLcode sftp_upload_init(struct Curl_easy *data, } /* seekerr == CURL_SEEKFUNC_CANTSEEK (cannot seek to offset) */ do { + struct Curl_mapi_guard guard; char scratch[4 * 1024]; size_t readthisamountnow = (data->state.resume_from - passed > @@ -1050,11 +1058,11 @@ static CURLcode sftp_upload_init(struct Curl_easy *data, sizeof(scratch) : curlx_sotouz(data->state.resume_from - passed); size_t actuallyread; - Curl_set_in_callback(data, TRUE); + CURL_CBAPI_START(&guard, data, easy_fread_func); actuallyread = data->state.fread_func(scratch, 1, readthisamountnow, data->state.in); - Curl_set_in_callback(data, FALSE); + CURL_CBAPI_END(&guard); passed += actuallyread; if((actuallyread == 0) || (actuallyread > readthisamountnow)) { @@ -1094,8 +1102,7 @@ static CURLcode sftp_upload_init(struct Curl_easy *data, return CURLE_OK; } -static CURLcode ssh_state_pkey_init(struct Curl_easy *data, - struct ssh_conn *sshc) +static void ssh_state_pkey_init(struct Curl_easy *data, struct ssh_conn *sshc) { /* * Check the supported auth types in the order I feel is most secure @@ -1105,88 +1112,13 @@ static CURLcode ssh_state_pkey_init(struct Curl_easy *data, if((data->set.ssh_auth_types & CURLSSH_AUTH_PUBLICKEY) && strstr(sshc->authlist, "publickey")) { - bool out_of_memory = FALSE; - - sshc->rsa_pub = sshc->rsa = NULL; - - if(data->set.str[STRING_SSH_PRIVATE_KEY]) { - sshc->rsa = curlx_strdup(data->set.str[STRING_SSH_PRIVATE_KEY]); - if(!sshc->rsa) - out_of_memory = TRUE; - } - else { - /* To ponder about: should really the lib be messing about with the - HOME environment variable etc? */ - char *home = curl_getenv("HOME"); - curlx_struct_stat sbuf; - - /* If no private key file is specified, try some common paths. */ - if(home) { - /* Try ~/.ssh first. */ - sshc->rsa = curl_maprintf("%s/.ssh/id_rsa", home); - if(!sshc->rsa) - out_of_memory = TRUE; - else if(curlx_stat(sshc->rsa, &sbuf)) { - curlx_free(sshc->rsa); - sshc->rsa = curl_maprintf("%s/.ssh/id_dsa", home); - if(!sshc->rsa) - out_of_memory = TRUE; - else if(curlx_stat(sshc->rsa, &sbuf)) { - curlx_safefree(sshc->rsa); - } - } - curlx_free(home); - } - if(!out_of_memory && !sshc->rsa) { - /* Nothing found; try the current dir. */ - sshc->rsa = curlx_strdup("id_rsa"); - if(sshc->rsa && curlx_stat(sshc->rsa, &sbuf)) { - curlx_free(sshc->rsa); - sshc->rsa = curlx_strdup("id_dsa"); - if(sshc->rsa && curlx_stat(sshc->rsa, &sbuf)) { - curlx_free(sshc->rsa); - /* Out of guesses. Set to the empty string to avoid - * surprising info messages. */ - sshc->rsa = curlx_strdup(""); - } - } - } - } - - /* - * Unless the user explicitly specifies a public key file, let - * libssh2 extract the public key from the private key file. - * This is done by passing sshc->rsa_pub = NULL. - */ - if(!out_of_memory && data->set.str[STRING_SSH_PUBLIC_KEY] && - /* treat empty string the same way as NULL */ - data->set.str[STRING_SSH_PUBLIC_KEY][0]) { - sshc->rsa_pub = curlx_strdup(data->set.str[STRING_SSH_PUBLIC_KEY]); - if(!sshc->rsa_pub) - out_of_memory = TRUE; - } - - if(out_of_memory || !sshc->rsa) { - curlx_safefree(sshc->rsa); - curlx_safefree(sshc->rsa_pub); - myssh_to(data, sshc, SSH_SESSION_FREE); - return CURLE_OUT_OF_MEMORY; - } - - sshc->passphrase = data->set.ssl.primary.key_passwd; - if(!sshc->passphrase) - sshc->passphrase = ""; - - if(sshc->rsa_pub) - infof(data, "SSH: trying public key file '%s'", sshc->rsa_pub); - infof(data, "SSH: trying private key file '%s'", sshc->rsa); - + if(sshc->pub_key) + infof(data, "SSH: trying public key file '%s'", sshc->pub_key); + infof(data, "SSH: trying private key file '%s'", sshc->priv_key); myssh_to(data, sshc, SSH_AUTH_PKEY); } - else { + else myssh_to(data, sshc, SSH_AUTH_PASS_INIT); - } - return CURLE_OK; } static CURLcode sftp_quote_stat(struct Curl_easy *data, @@ -1559,14 +1491,11 @@ static CURLcode ssh_state_auth_pkey(struct Curl_easy *data, libssh2_userauth_publickey_fromfile_ex(sshc->ssh_session, user, curlx_uztoui(strlen(user)), - sshc->rsa_pub, - sshc->rsa, sshc->passphrase); + sshc->pub_key, + sshc->priv_key, sshc->passphrase); if(rc == LIBSSH2_ERROR_EAGAIN) return CURLE_AGAIN; - curlx_safefree(sshc->rsa_pub); - curlx_safefree(sshc->rsa); - if(rc == 0) { sshc->authed = TRUE; infof(data, "SSH: authenticated via publickey"); @@ -1843,7 +1772,7 @@ static CURLcode ssh_state_sftp_realpath(struct Curl_easy *data, return CURLE_FAILED_INIT; rc = libssh2_sftp_symlink_ex(sshc->sftp_session, ".", - curlx_uztoui(strlen(".")), + curlx_uztoui(CURL_CSTRLEN(".")), sshp->readdir_filename, CURL_PATH_MAX, LIBSSH2_SFTP_REALPATH); if(rc == LIBSSH2_ERROR_EAGAIN) @@ -2626,8 +2555,8 @@ static CURLcode sshc_cleanup(struct ssh_conn *sshc, struct Curl_easy *data, DEBUGASSERT(!sshc->kh); DEBUGASSERT(!sshc->ssh_agent); - curlx_safefree(sshc->rsa_pub); - curlx_safefree(sshc->rsa); + curlx_safefree(sshc->pub_key); + curlx_safefree(sshc->priv_key); curlx_safefree(sshc->quote_path1); curlx_safefree(sshc->quote_path2); curlx_safefree(sshc->homedir); @@ -2925,7 +2854,7 @@ static CURLcode ssh_state_session_free(struct Curl_easy *data, if(result) return result; memset(sshc, 0, sizeof(struct ssh_conn)); - connclose(conn, "SSH session free"); + connclose(conn); sshc->state = SSH_SESSION_FREE; /* current */ myssh_to(data, sshc, SSH_STOP); return CURLE_OK; @@ -2968,7 +2897,7 @@ static CURLcode ssh_statemachine(struct Curl_easy *data, break; case SSH_AUTH_PKEY_INIT: - result = ssh_state_pkey_init(data, sshc); + ssh_state_pkey_init(data, sshc); break; case SSH_AUTH_PKEY: @@ -3196,36 +3125,6 @@ static CURLcode ssh_statemachine(struct Curl_easy *data, return result; } -/* called by the multi interface to figure out what socket(s) to wait for and - for what actions in the DO_DONE, PERFORM and WAITPERFORM states */ -static CURLcode ssh_pollset(struct Curl_easy *data, - struct easy_pollset *ps) -{ - struct connectdata *conn = data->conn; - struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); - curl_socket_t sock = conn->sock[FIRSTSOCKET]; - int waitfor; - - if(!sshc || (sock == CURL_SOCKET_BAD)) - return CURLE_FAILED_INIT; - - waitfor = sshc->waitfor ? sshc->waitfor : data->req.io_flags; - if(waitfor) { - int flags = 0; - if(waitfor & REQ_IO_RECV) - flags |= CURL_POLL_IN; - if(waitfor & REQ_IO_SEND) - flags |= CURL_POLL_OUT; - DEBUGASSERT(flags); - CURL_TRC_SSH(data, "pollset, flags=%x", (unsigned int)flags); - return Curl_pollset_change(data, ps, sock, flags, 0); - } - /* While we still have a session, we listen incoming data. */ - if(sshc->ssh_session) - return Curl_pollset_change(data, ps, sock, CURL_POLL_IN, 0); - return CURLE_OK; -} - /* * When one of the libssh2 functions has returned LIBSSH2_ERROR_EAGAIN this * function is used to figure out in what direction and stores this info so @@ -3259,8 +3158,8 @@ static CURLcode ssh_multi_statemach(struct Curl_easy *data, bool *done) struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); struct SSHPROTO *sshp = Curl_meta_get(data, CURL_META_SSH_EASY); CURLcode result = CURLE_OK; - bool block; /* we store the status and use that to provide a ssh_pollset() - implementation */ + bool block; /* we store the status and use that to provide + a Curl_ssh_pollset() implementation */ if(!sshc || !sshp) return CURLE_FAILED_INIT; @@ -3373,7 +3272,7 @@ static CURLcode ssh_setup_connection(struct Curl_easy *data, if(Curl_meta_set(data, CURL_META_SSH_EASY, sshp, myssh_easy_dtor)) return CURLE_OUT_OF_MEMORY; - return CURLE_OK; + return Curl_ssh_setup_pkey(data, sshc); } static Curl_recv scp_recv, sftp_recv; @@ -3384,7 +3283,7 @@ static ssize_t ssh_tls_recv(libssh2_socket_t sock, void *buffer, size_t length, int flags, void **abstract) { struct Curl_easy *data = (struct Curl_easy *)*abstract; - int sockindex = Curl_conn_sockindex(data, sock); + int8_t sockindex = Curl_conn_sockindex(data, sock); size_t nread; CURLcode result; struct connectdata *conn = data->conn; @@ -3412,7 +3311,7 @@ static ssize_t ssh_tls_send(libssh2_socket_t sock, const void *buffer, size_t length, int flags, void **abstract) { struct Curl_easy *data = (struct Curl_easy *)*abstract; - int sockindex = Curl_conn_sockindex(data, sock); + int8_t sockindex = Curl_conn_sockindex(data, sock); size_t nwrite; CURLcode result; struct connectdata *conn = data->conn; @@ -3487,7 +3386,7 @@ static CURLcode ssh_connect(struct Curl_easy *data, bool *done) sock = conn->sock[FIRSTSOCKET]; #endif /* CURL_LIBSSH2_DEBUG */ - /* libcurl MUST to set custom memory functions so that the kbd_callback + /* libcurl MUST set custom memory functions so that the kbd_callback function's memory allocations can be properly freed */ sshc->ssh_session = libssh2_session_init_ex(my_libssh2_malloc, my_libssh2_free, @@ -3508,18 +3407,16 @@ static CURLcode ssh_connect(struct Curl_easy *data, bool *done) #ifndef CURL_DISABLE_PROXY if(conn->http_proxy.proxytype == CURLPROXY_HTTPS) { - /* - Setup libssh2 callbacks to make it read/write TLS from the socket. + /* Setup libssh2 callbacks to make it read/write TLS from the socket. - ssize_t - recvcb(libssh2_socket_t sock, void *buffer, size_t length, - int flags, void **abstract); + ssize_t + recvcb(libssh2_socket_t sock, void *buffer, size_t length, + int flags, void **abstract); - ssize_t - sendcb(libssh2_socket_t sock, const void *buffer, size_t length, - int flags, void **abstract); - - */ + ssize_t + sendcb(libssh2_socket_t sock, const void *buffer, size_t length, + int flags, void **abstract); + */ #if LIBSSH2_VERSION_NUM >= 0x010b01 infof(data, "SSH: using HTTPS proxy"); #if defined(__clang__) && __clang_major__ >= 16 @@ -3583,7 +3480,7 @@ static CURLcode ssh_connect(struct Curl_easy *data, bool *done) infof(data, "SSH: failed to enable compression for session"); } - if(data->set.str[STRING_SSH_KNOWNHOSTS]) { + if(CURL_EASY_STR(data, STRING_SSH_KNOWNHOSTS)) { int rc; sshc->kh = libssh2_knownhost_init(sshc->ssh_session); if(!sshc->kh) { @@ -3593,12 +3490,12 @@ static CURLcode ssh_connect(struct Curl_easy *data, bool *done) } /* read all known hosts from there */ - rc = libssh2_knownhost_readfile(sshc->kh, - data->set.str[STRING_SSH_KNOWNHOSTS], - LIBSSH2_KNOWNHOST_FILE_OPENSSH); + rc = libssh2_knownhost_readfile( + sshc->kh, CURL_EASY_STR(data, STRING_SSH_KNOWNHOSTS), + LIBSSH2_KNOWNHOST_FILE_OPENSSH); if(rc < 0) infof(data, "SSH: failed to read known hosts from %s", - data->set.str[STRING_SSH_KNOWNHOSTS]); + CURL_EASY_STR(data, STRING_SSH_KNOWNHOSTS)); } #ifdef CURL_LIBSSH2_DEBUG @@ -3722,7 +3619,7 @@ static CURLcode scp_done(struct Curl_easy *data, CURLcode status, return ssh_done(data, status); } -static CURLcode scp_send(struct Curl_easy *data, int sockindex, +static CURLcode scp_send(struct Curl_easy *data, int8_t sockindex, const uint8_t *mem, size_t len, bool eos, size_t *pnwritten) { @@ -3754,7 +3651,7 @@ static CURLcode scp_send(struct Curl_easy *data, int sockindex, return result; } -static CURLcode scp_recv(struct Curl_easy *data, int sockindex, +static CURLcode scp_recv(struct Curl_easy *data, int8_t sockindex, char *mem, size_t len, size_t *pnread) { struct connectdata *conn = data->conn; @@ -3880,7 +3777,7 @@ static CURLcode sftp_done(struct Curl_easy *data, CURLcode status, } /* return number of sent bytes */ -static CURLcode sftp_send(struct Curl_easy *data, int sockindex, +static CURLcode sftp_send(struct Curl_easy *data, int8_t sockindex, const uint8_t *mem, size_t len, bool eos, size_t *pnwritten) { @@ -3911,7 +3808,7 @@ static CURLcode sftp_send(struct Curl_easy *data, int sockindex, * Return number of received (decrypted) bytes * or <0 on error */ -static CURLcode sftp_recv(struct Curl_easy *data, int sockindex, +static CURLcode sftp_recv(struct Curl_easy *data, int8_t sockindex, char *mem, size_t len, size_t *pnread) { struct connectdata *conn = data->conn; @@ -4014,10 +3911,10 @@ const struct Curl_protocol Curl_protocol_scp = { ssh_connect, /* connect_it */ ssh_multi_statemach, /* connecting */ scp_doing, /* doing */ - ssh_pollset, /* proto_pollset */ - ssh_pollset, /* doing_pollset */ + Curl_ssh_pollset, /* proto_pollset */ + Curl_ssh_pollset, /* doing_pollset */ ZERO_NULL, /* domore_pollset */ - ssh_pollset, /* perform_pollset */ + Curl_ssh_pollset, /* perform_pollset */ scp_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ ZERO_NULL, /* write_resp_hd */ @@ -4037,10 +3934,10 @@ const struct Curl_protocol Curl_protocol_sftp = { ssh_connect, /* connect_it */ ssh_multi_statemach, /* connecting */ sftp_doing, /* doing */ - ssh_pollset, /* proto_pollset */ - ssh_pollset, /* doing_pollset */ + Curl_ssh_pollset, /* proto_pollset */ + Curl_ssh_pollset, /* doing_pollset */ ZERO_NULL, /* domore_pollset */ - ssh_pollset, /* perform_pollset */ + Curl_ssh_pollset, /* perform_pollset */ sftp_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ ZERO_NULL, /* write_resp_hd */ diff --git a/Utilities/cmcurl/lib/vssh/ssh.h b/Utilities/cmcurl/lib/vssh/ssh.h index de76c8a253..52bc139990 100644 --- a/Utilities/cmcurl/lib/vssh/ssh.h +++ b/Utilities/cmcurl/lib/vssh/ssh.h @@ -144,8 +144,8 @@ struct ssh_conn { /* common */ const char *passphrase; /* pass-phrase to use */ - char *rsa_pub; /* strdup'ed public key file */ - char *rsa; /* strdup'ed private key file */ + char *pub_key; /* strdup'ed public key file */ + char *priv_key; /* strdup'ed private key file */ sshstate state; /* always use ssh.c:state() to change state! */ sshstate nextstate; /* the state to goto after stopping */ struct curl_slist *quote_item; /* for the quote option */ @@ -220,7 +220,7 @@ struct ssh_conn { #ifdef USE_LIBSSH #if LIBSSH_VERSION_INT < SSH_VERSION_INT(0, 9, 0) -#error "SCP/SFTP protocols require libssh 0.9.0 or later" +#error "SCP/SFTP protocols require libssh 0.9.0 or greater" #endif #endif diff --git a/Utilities/cmcurl/lib/vssh/vssh.c b/Utilities/cmcurl/lib/vssh/vssh.c index 0ba4a9e690..9834546987 100644 --- a/Utilities/cmcurl/lib/vssh/vssh.c +++ b/Utilities/cmcurl/lib/vssh/vssh.c @@ -30,6 +30,9 @@ #include "curlx/strparse.h" #include "curl_trc.h" #include "escape.h" +#include "select.h" /* for Curl_pollset_change() */ +#include "url.h" /* for Curl_conn_meta_get() */ +#include "curlx/fopen.h" #ifdef CURLVERBOSE const char *Curl_ssh_statename(sshstate state) @@ -330,4 +333,118 @@ CURLcode Curl_ssh_range(struct Curl_easy *data, return CURLE_OK; } +/* called by the multi interface to figure out what socket(s) to wait for and + for what actions in the DO_DONE, PERFORM and WAITPERFORM states */ +CURLcode Curl_ssh_pollset(struct Curl_easy *data, struct easy_pollset *ps) +{ + struct connectdata *conn = data->conn; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); + curl_socket_t sock = conn->sock[FIRSTSOCKET]; + int waitfor; + + if(!sshc || (sock == CURL_SOCKET_BAD)) + return CURLE_FAILED_INIT; + + waitfor = sshc->waitfor ? sshc->waitfor : data->req.io_flags; + if(waitfor) { + int flags = 0; + if(waitfor & REQ_IO_RECV) + flags |= CURL_POLL_IN; + if(waitfor & REQ_IO_SEND) + flags |= CURL_POLL_OUT; + DEBUGASSERT(flags); + CURL_TRC_SSH(data, "pollset, flags=%x", (unsigned int)flags); + return Curl_pollset_change(data, ps, sock, flags, 0); + } + /* While we still have a session, we listen incoming data. */ + if(sshc->ssh_session) + return Curl_pollset_change(data, ps, sock, CURL_POLL_IN, 0); + return CURLE_OK; +} + +CURLcode Curl_ssh_setup_pkey(struct Curl_easy *data, struct ssh_conn *sshc) +{ + char *home = NULL; + if(data->set.ssh_auth_types & CURLSSH_AUTH_PUBLICKEY) { + const char *str; + + sshc->pub_key = sshc->priv_key = NULL; + + if(CURL_EASY_STR(data, STRING_SSH_PRIVATE_KEY)) { + sshc->priv_key = curlx_strdup( + CURL_EASY_STR(data, STRING_SSH_PRIVATE_KEY)); + if(!sshc->priv_key) + goto fail; + } + else { + /* To ponder about: should really the lib be messing about with the HOME + environment variable etc? */ + curlx_struct_stat sbuf; + home = curl_getenv("HOME"); + + /* If no private key file is specified, try some common paths. */ + if(home) { + /* Try ~/.ssh first. */ + sshc->priv_key = curl_maprintf("%s/.ssh/id_rsa", home); + if(!sshc->priv_key) + goto fail; + else if(curlx_stat(sshc->priv_key, &sbuf)) { + curlx_free(sshc->priv_key); + sshc->priv_key = curl_maprintf("%s/.ssh/id_dsa", home); + if(!sshc->priv_key) + goto fail; + else if(curlx_stat(sshc->priv_key, &sbuf)) { + curlx_safefree(sshc->priv_key); + } + } + curlx_safefree(home); + } + if(!sshc->priv_key) { + /* Nothing found; try the current dir. */ + sshc->priv_key = curlx_strdup("id_rsa"); + if(sshc->priv_key && curlx_stat(sshc->priv_key, &sbuf)) { + curlx_free(sshc->priv_key); + sshc->priv_key = curlx_strdup("id_dsa"); + if(sshc->priv_key && curlx_stat(sshc->priv_key, &sbuf)) { + curlx_free(sshc->priv_key); + /* Out of guesses. Set to the empty string to avoid + * surprising info messages. */ + sshc->priv_key = curlx_strdup(""); + } + } + } + } + + /* + * Unless the user explicitly specifies a public key file, let the SSH + * library extract the public key from the private key file. This is done + * by passing sshc->pub_key = NULL. + */ + str = CURL_EASY_STR(data, STRING_SSH_PUBLIC_KEY); + if(str && *str) { /* treat empty string the same way as NULL */ + sshc->pub_key = curlx_strdup(str); + if(!sshc->pub_key) + goto fail; + } + + sshc->passphrase = data->set.ssl.primary.key_passwd; + if(!sshc->passphrase) + sshc->passphrase = ""; + + if(sshc->pub_key) + infof(data, "SSH: public key file '%s'", sshc->pub_key); + if(sshc->priv_key) + infof(data, "SSH: private key file '%s'", sshc->priv_key); + else + infof(data, "SSH: public key auth without private key set!"); + } + return CURLE_OK; + +fail: + curlx_safefree(home); + curlx_safefree(sshc->priv_key); + curlx_safefree(sshc->pub_key); + return CURLE_OUT_OF_MEMORY; +} + #endif /* USE_SSH */ diff --git a/Utilities/cmcurl/lib/vssh/vssh.h b/Utilities/cmcurl/lib/vssh/vssh.h index 492108fbd7..4c505c453a 100644 --- a/Utilities/cmcurl/lib/vssh/vssh.h +++ b/Utilities/cmcurl/lib/vssh/vssh.h @@ -29,6 +29,8 @@ #include "urldata.h" +struct ssh_conn; + CURLcode Curl_getworkingpath(struct Curl_easy *data, const char *homedir, char **path); @@ -38,5 +40,9 @@ CURLcode Curl_get_pathname(const char **cpp, char **path, const char *homedir); CURLcode Curl_ssh_range(struct Curl_easy *data, const char *range, curl_off_t filesize, curl_off_t *startp, curl_off_t *sizep); + +CURLcode Curl_ssh_pollset(struct Curl_easy *data, struct easy_pollset *ps); +CURLcode Curl_ssh_setup_pkey(struct Curl_easy *data, struct ssh_conn *sshc); + #endif /* USE_SSH */ #endif /* HEADER_CURL_VSSH_VSSH_H */ diff --git a/Utilities/cmcurl/lib/vtls/apple.c b/Utilities/cmcurl/lib/vtls/apple.c index 2e132f2977..ce7f0c5e1c 100644 --- a/Utilities/cmcurl/lib/vtls/apple.c +++ b/Utilities/cmcurl/lib/vtls/apple.c @@ -36,7 +36,7 @@ "SSL/TLS Strong Encryption: An Introduction" https://httpd.apache.org/docs/2.0/ssl/ssl_intro.html -*/ + */ #include "curl_setup.h" @@ -200,8 +200,9 @@ CURLcode Curl_vtls_apple_verify(struct Curl_cfilter *cf, goto out; } -#if defined(HAVE_BUILTIN_AVAILABLE) && defined(SUPPORTS_SecOCSP) if(ocsp_len > 0) { + bool checked = FALSE; +#if defined(HAVE_BUILTIN_AVAILABLE) && defined(SUPPORTS_SecOCSP) if(__builtin_available(macOS 10.9, iOS 7, tvOS 9, watchOS 2, *)) { CFDataRef ocspdata = CFDataCreate(NULL, ocsp_buf, (CFIndex)ocsp_len); @@ -213,12 +214,16 @@ CURLcode Curl_vtls_apple_verify(struct Curl_cfilter *cf, result = CURLE_PEER_FAILED_VERIFICATION; goto out; } + checked = TRUE; + } +#endif + if(!checked) { + (void)ocsp_buf; + failf(data, "Apple SecTrust: OCSP verification not supported"); + result = CURLE_NOT_BUILT_IN; + goto out; } } -#else - (void)ocsp_buf; - (void)ocsp_len; -#endif #ifdef SUPPORTS_SecTrustEvaluateWithError #ifdef HAVE_BUILTIN_AVAILABLE diff --git a/Utilities/cmcurl/lib/vtls/cipher_suite.c b/Utilities/cmcurl/lib/vtls/cipher_suite.c index 197055fc0d..2c6a0c02d0 100644 --- a/Utilities/cmcurl/lib/vtls/cipher_suite.c +++ b/Utilities/cmcurl/lib/vtls/cipher_suite.c @@ -48,7 +48,7 @@ /* Text for cipher suite parts (max 64 entries), keep indexes below in sync with this! */ -static const char *cs_txt = +static const char cs_txt[] = "\0" "TLS" "\0" "WITH" "\0" diff --git a/Utilities/cmcurl/lib/vtls/gtls.c b/Utilities/cmcurl/lib/vtls/gtls.c index 1a7a4a1e5b..9d17573dc5 100644 --- a/Utilities/cmcurl/lib/vtls/gtls.c +++ b/Utilities/cmcurl/lib/vtls/gtls.c @@ -313,10 +313,6 @@ static gnutls_x509_crt_fmt_t gnutls_do_file_type(const char *type) #define GNUTLS_CIPHERS "NORMAL:%PROFILE_MEDIUM:-ARCFOUR-128:" \ "-CTYPE-ALL:+CTYPE-X509" -/* If GnuTLS was compiled without support for SRP it errors out if SRP is - requested in the priority string, so treat it specially - */ -#define GNUTLS_SRP "+SRP" #define QUIC_PRIORITY \ "NORMAL:%PROFILE_MEDIUM:-VERS-ALL:+VERS-TLS1.3:-CIPHER-ALL:+AES-128-GCM:" \ @@ -461,7 +457,7 @@ static CURLcode gtls_populate_creds(struct Curl_cfilter *cf, } infof(data, "SSL Trust Anchors:"); - if(ssl_config->native_ca_store) { + if(config->native_ca_store) { #ifdef USE_APPLE_SECTRUST infof(data, " Native: Apple SecTrust"); creds_are_empty = FALSE; @@ -595,7 +591,7 @@ static struct gtls_shared_creds *gtls_get_cached_creds(struct Curl_cfilter *cf, if(data->multi) { shared_creds = Curl_hash_pick(&data->multi->proto_hash, CURL_UNCONST(MPROTO_GTLS_X509_KEY), - sizeof(MPROTO_GTLS_X509_KEY) - 1); + CURL_CSTRLEN(MPROTO_GTLS_X509_KEY)); if(shared_creds && shared_creds->creds && !gtls_shared_creds_expired(data, shared_creds) && !gtls_shared_creds_different(cf, shared_creds)) { @@ -608,7 +604,7 @@ static struct gtls_shared_creds *gtls_get_cached_creds(struct Curl_cfilter *cf, static void gtls_shared_creds_hash_free(void *key, size_t key_len, void *p) { struct gtls_shared_creds *sc = p; - DEBUGASSERT(key_len == (sizeof(MPROTO_GTLS_X509_KEY) - 1)); + DEBUGASSERT(key_len == CURL_CSTRLEN(MPROTO_GTLS_X509_KEY)); DEBUGASSERT(!memcmp(MPROTO_GTLS_X509_KEY, key, key_len)); (void)key; (void)key_len; @@ -639,7 +635,7 @@ static void gtls_set_cached_creds(struct Curl_cfilter *cf, if(!Curl_hash_add2(&data->multi->proto_hash, CURL_UNCONST(MPROTO_GTLS_X509_KEY), - sizeof(MPROTO_GTLS_X509_KEY) - 1, + CURL_CSTRLEN(MPROTO_GTLS_X509_KEY), sc, gtls_shared_creds_hash_free)) { Curl_gtls_shared_creds_free(&sc); /* down reference again */ return; @@ -665,7 +661,7 @@ CURLcode Curl_gtls_client_trust_setup(struct Curl_cfilter *cf, !conn_config->CApath && !conn_config->ca_info_blob && !ssl_config->primary.CRLfile && - !ssl_config->native_ca_store && + !conn_config->native_ca_store && !conn_config->clientcert; /* GnuTLS adds client cert to its credentials! */ if(cache_criteria_met) @@ -720,14 +716,17 @@ CURLcode Curl_gtls_cache_session(struct Curl_cfilter *cf, curl_off_t valid_until, const char *alpn, unsigned char *quic_tp, - size_t quic_tp_len) + size_t quic_tp_len, + struct Curl_ssl_session **psession) { - struct Curl_ssl_session *sc_session; + struct Curl_ssl_session *sc_session = NULL, *sc_dup = NULL; unsigned char *sdata, *qtp_clone = NULL; size_t sdata_len = 0; size_t earlydata_max = 0; CURLcode result = CURLE_OK; + if(psession) + *psession = NULL; if(!Curl_ssl_scache_use(cf, data)) return CURLE_OK; @@ -766,10 +765,20 @@ CURLcode Curl_gtls_cache_session(struct Curl_cfilter *cf, qtp_clone, quic_tp_len, &sc_session); /* call took ownership of `sdata` and `qtp_clone` */ + if(!result && psession && /* return a duplicate if asked for and FTP */ + (cf->conn->scheme->family == CURLPROTO_FTP)) + result = Curl_ssl_session_dup(sc_session, &sc_dup); if(!result) { result = Curl_ssl_scache_put(cf, data, ssl_peer_key, sc_session); /* took ownership of `sc_session` */ + sc_session = NULL; } + if(!result && psession) { + *psession = sc_dup; + sc_dup = NULL; + } + Curl_ssl_session_destroy(sc_session); + Curl_ssl_session_destroy(sc_dup); return result; } #endif @@ -780,9 +789,17 @@ static CURLcode cf_gtls_update_session_id(struct Curl_cfilter *cf, gnutls_session_t session) { struct ssl_connect_data *connssl = cf->ctx; - return Curl_gtls_cache_session(cf, data, connssl->peer.scache_key, - session, 0, connssl->negotiated.alpn, - NULL, 0); + struct Curl_ssl_session *scs = NULL; + CURLcode result; + + result = Curl_gtls_cache_session(cf, data, connssl->peer.scache_key, + session, 0, connssl->negotiated.alpn, + NULL, 0, &scs); + if(!result) { + Curl_ssl_session_destroy(connssl->session); + connssl->session = scs; + } + return result; } static int gtls_handshake_cb(gnutls_session_t session, unsigned int htype, @@ -825,19 +842,6 @@ static CURLcode gtls_set_priority(struct Curl_cfilter *cf, curlx_dyn_init(&buf, 4096); -#ifdef USE_GNUTLS_SRP - if(conn_config->username) { - /* Only add SRP to the cipher list if SRP is requested. Otherwise - * GnuTLS disables TLS 1.3 support. */ - result = curlx_dyn_add(&buf, priority); - if(!result) - result = curlx_dyn_add(&buf, ":" GNUTLS_SRP); - if(result) - goto out; - priority = curlx_dyn_ptr(&buf); - } -#endif - if(conn_config->cipher_list) { if((conn_config->cipher_list[0] == '+') || (conn_config->cipher_list[0] == '-') || @@ -894,33 +898,6 @@ static CURLcode gtls_client_init(struct Curl_cfilter *cf, if(result) return result; -#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_UNIMPLEMENTED_FEATURE) { - failf(data, "GnuTLS: TLS-SRP support not built in: %s", - gnutls_strerror(rc)); - return CURLE_NOT_BUILT_IN; - } - else 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 */ @@ -1035,25 +1012,11 @@ static CURLcode gtls_client_init(struct Curl_cfilter *cf, } } -#ifdef USE_GNUTLS_SRP - /* put the credentials to the current session */ - if(config->username) { - rc = gnutls_credentials_set(gtls->session, GNUTLS_CRD_SRP, - gtls->srp_client_cred); - if(rc != GNUTLS_E_SUCCESS) { - failf(data, "gnutls_credentials_set() failed: %s", gnutls_strerror(rc)); - return CURLE_SSL_CONNECT_ERROR; - } - } - else -#endif - { - rc = gnutls_credentials_set(gtls->session, GNUTLS_CRD_CERTIFICATE, - gtls->shared_creds->creds); - if(rc != GNUTLS_E_SUCCESS) { - failf(data, "gnutls_credentials_set() failed: %s", gnutls_strerror(rc)); - return CURLE_SSL_CONNECT_ERROR; - } + rc = gnutls_credentials_set(gtls->session, GNUTLS_CRD_CERTIFICATE, + gtls->shared_creds->creds); + if(rc != GNUTLS_E_SUCCESS) { + failf(data, "gnutls_credentials_set() failed: %s", gnutls_strerror(rc)); + return CURLE_SSL_CONNECT_ERROR; } if(config->verifystatus) { @@ -1080,7 +1043,8 @@ static int keylog_callback(gnutls_session_t session, const char *label, return -1; } - Curl_tls_keylog_write(label, crandom.data, secret->data, secret->size); + Curl_tls_keylog_write(label, crandom.data, crandom.size, + secret->data, secret->size); return 0; } @@ -1104,6 +1068,55 @@ static CURLcode gtls_on_session_reuse(struct Curl_cfilter *cf, } #endif +static CURLcode gtls_apply_session( + struct gtls_ctx *gctx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + struct alpn_spec *alpns, + Curl_gtls_init_session_reuse_cb *sess_reuse_cb, + struct Curl_ssl_session *scs, + bool *pwas_setup) +{ + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + CURLcode result = CURLE_OK; + int rc; + + if(scs && scs->sdata && scs->sdata_len && + (!scs->alpn || Curl_alpn_contains_proto(alpns, scs->alpn))) { + /* we got a cached session, use it! */ + + result = gtls_client_init(cf, data, peer, scs->earlydata_max, gctx); + if(result) + goto out; + *pwas_setup = TRUE; + + rc = gnutls_session_set_data(gctx->session, scs->sdata, scs->sdata_len); + if(rc < 0) + infof(data, "SSL session not accepted by GnuTLS, continuing without"); + else { + infof(data, "SSL reusing session with ALPN '%s'", + scs->alpn ? scs->alpn : "-"); + if(ssl_config->earlydata && scs->alpn && + !cf->conn->bits.connect_only) { + bool do_early_data = FALSE; + if(sess_reuse_cb) { + result = sess_reuse_cb(cf, data, alpns, scs, &do_early_data); + if(result) + goto out; + } + if(do_early_data) { + /* We only try the ALPN protocol the session used before, + * otherwise we might send early data for the wrong protocol */ + Curl_alpn_restrict_to(alpns, scs->alpn); + } + } + } + } +out: + return result; +} + CURLcode Curl_gtls_ctx_init(struct gtls_ctx *gctx, struct Curl_cfilter *cf, struct Curl_easy *data, @@ -1115,57 +1128,40 @@ CURLcode Curl_gtls_ctx_init(struct gtls_ctx *gctx, Curl_gtls_init_session_reuse_cb *sess_reuse_cb) { 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 *scs = NULL; gnutls_datum_t gtls_alpns[ALPN_ENTRIES_MAX]; size_t gtls_alpns_count = 0; bool gtls_session_setup = FALSE; struct alpn_spec alpns; CURLcode result = CURLE_OK; - int rc; DEBUGASSERT(gctx); Curl_alpn_copy(&alpns, alpns_requested); + if((cf->sockindex == SECONDARYSOCKET) && !(cf->cft->flags & CF_TYPE_PROXY)) { + /* FTP is a bitch. On TLS secured transfers, it is a common server + * option to require the client to use the SAME TLS session as on + * the control connection or it fails the request. See #22225. */ + scs = Curl_ssl_get_cf_session(data, cf->cft, FIRSTSOCKET); + if(scs) { + result = gtls_apply_session(gctx, cf, data, peer, &alpns, + sess_reuse_cb, scs, >ls_session_setup); + scs = NULL; + if(!result && gtls_session_setup) { + CURL_TRC_CF(data, cf, "applied SSL session from control connection"); + } + } + } /* This might be a reconnect, so we check for a session ID in the cache to speed up things. We need to do this before constructing the GnuTLS session since we need to set flags depending on the kind of reuse. */ - if(conn_config->cache_session && !conn_config->verifystatus) { + if(!gtls_session_setup && conn_config->cache_session && + !conn_config->verifystatus) { result = Curl_ssl_scache_take(cf, data, peer->scache_key, &scs); if(result) goto out; - - if(scs && scs->sdata && scs->sdata_len && - (!scs->alpn || Curl_alpn_contains_proto(&alpns, scs->alpn))) { - /* we got a cached session, use it! */ - - result = gtls_client_init(cf, data, peer, scs->earlydata_max, gctx); - if(result) - goto out; - gtls_session_setup = TRUE; - - rc = gnutls_session_set_data(gctx->session, scs->sdata, scs->sdata_len); - if(rc < 0) - infof(data, "SSL session not accepted by GnuTLS, continuing without"); - else { - infof(data, "SSL reusing session with ALPN '%s'", - scs->alpn ? scs->alpn : "-"); - if(ssl_config->earlydata && scs->alpn && - !cf->conn->bits.connect_only) { - bool do_early_data = FALSE; - if(sess_reuse_cb) { - result = sess_reuse_cb(cf, data, &alpns, scs, &do_early_data); - if(result) - goto out; - } - if(do_early_data) { - /* We only try the ALPN protocol the session used before, - * otherwise we might send early data for the wrong protocol */ - Curl_alpn_restrict_to(&alpns, scs->alpn); - } - } - } - } + result = gtls_apply_session(gctx, cf, data, peer, &alpns, + sess_reuse_cb, scs, >ls_session_setup); } if(!gtls_session_setup) { @@ -1608,7 +1604,7 @@ static CURLcode gtls_verify_cert(struct Curl_easy *data, infof(data, " SSL certificate verified by GnuTLS"); #ifdef USE_APPLE_SECTRUST - if(!verified && ssl_config->native_ca_store) { + if(!verified && config->native_ca_store) { CURLcode result = Curl_vtls_apple_verify(cf, data, peer, chain->num_certs, gtls_chain_get_der, chain, NULL, 0); @@ -1675,21 +1671,10 @@ CURLcode Curl_gtls_verifyserver(struct Curl_cfilter *cf, if(config->verifypeer || config->verifyhost || config->issuercert) { -#ifdef USE_GNUTLS_SRP - if(ssl_config->primary.username && !config->verifypeer && - gnutls_cipher_get(session)) { - /* no peer cert, but auth is ok if we have SRP user and cipher and no - peer verify */ - } - else { -#endif - failf(data, "failed to get server cert"); - *certverifyresult = GNUTLS_E_NO_CERTIFICATE_FOUND; - result = CURLE_PEER_FAILED_VERIFICATION; - goto out; -#ifdef USE_GNUTLS_SRP - } -#endif + failf(data, "failed to get server cert"); + *certverifyresult = GNUTLS_E_NO_CERTIFICATE_FOUND; + result = CURLE_PEER_FAILED_VERIFICATION; + goto out; } infof(data, " common name: WARNING could not obtain"); } @@ -1883,10 +1868,10 @@ static CURLcode gtls_verifyserver(struct Curl_cfilter *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]; + CURL_EASY_STR(data, STRING_SSL_PINNEDPUBLICKEY_PROXY) : + CURL_EASY_STR(data, STRING_SSL_PINNEDPUBLICKEY); #else - const char *pinned_key = data->set.str[STRING_SSL_PINNEDPUBLICKEY]; + const char *pinned_key = CURL_EASY_STR(data, STRING_SSL_PINNEDPUBLICKEY); #endif CURLcode result; @@ -2222,12 +2207,6 @@ static void gtls_close(struct Curl_cfilter *cf, if(backend->gtls.shared_creds) { Curl_gtls_shared_creds_free(&backend->gtls.shared_creds); } -#ifdef USE_GNUTLS_SRP - if(backend->gtls.srp_client_cred) { - gnutls_srp_free_client_credentials(backend->gtls.srp_client_cred); - backend->gtls.srp_client_cred = NULL; - } -#endif } static CURLcode gtls_recv(struct Curl_cfilter *cf, diff --git a/Utilities/cmcurl/lib/vtls/gtls.h b/Utilities/cmcurl/lib/vtls/gtls.h index 4c662003eb..0ad9a7e68c 100644 --- a/Utilities/cmcurl/lib/vtls/gtls.h +++ b/Utilities/cmcurl/lib/vtls/gtls.h @@ -31,14 +31,6 @@ #include "curlx/timeval.h" -#ifdef HAVE_GNUTLS_SRP -/* the function exists */ -#ifdef USE_TLS_SRP -/* the functionality is not disabled */ -#define USE_GNUTLS_SRP -#endif -#endif - struct Curl_easy; struct Curl_cfilter; struct alpn_spec; @@ -64,9 +56,6 @@ void Curl_gtls_shared_creds_free(struct gtls_shared_creds **pcreds); struct gtls_ctx { gnutls_session_t session; struct gtls_shared_creds *shared_creds; -#ifdef USE_GNUTLS_SRP - gnutls_srp_client_credentials_t srp_client_cred; -#endif CURLcode io_result; /* result of last IO cfilter operation */ BIT(sent_shutdown); }; @@ -105,7 +94,8 @@ CURLcode Curl_gtls_verifyserver(struct Curl_cfilter *cf, struct ssl_peer *peer, const char *pinned_key); -/* Extract TLS session and place in cache, if configured. */ +/* Extract TLS session and place in cache, if configured. Return + * a copy of the session if desired. */ CURLcode Curl_gtls_cache_session(struct Curl_cfilter *cf, struct Curl_easy *data, const char *ssl_peer_key, @@ -113,7 +103,8 @@ CURLcode Curl_gtls_cache_session(struct Curl_cfilter *cf, curl_off_t valid_until, const char *alpn, unsigned char *quic_tp, - size_t quic_tp_len); + size_t quic_tp_len, + struct Curl_ssl_session **psession); /* Report properties of a successful handshake */ void Curl_gtls_report_handshake(struct Curl_easy *data, struct gtls_ctx *gctx); diff --git a/Utilities/cmcurl/lib/vtls/hostcheck.c b/Utilities/cmcurl/lib/vtls/hostcheck.c index ad09b83194..0af540993f 100644 --- a/Utilities/cmcurl/lib/vtls/hostcheck.c +++ b/Utilities/cmcurl/lib/vtls/hostcheck.c @@ -34,8 +34,8 @@ #endif #include "curl_memrchr.h" +#include "vdns/hostip.h" #include "vtls/hostcheck.h" -#include "hostip.h" /* check the two input strings with given length, but do not assume they end in nul-bytes */ diff --git a/Utilities/cmcurl/lib/vtls/keylog.c b/Utilities/cmcurl/lib/vtls/keylog.c index 094bf69db7..ac9649a41f 100644 --- a/Utilities/cmcurl/lib/vtls/keylog.c +++ b/Utilities/cmcurl/lib/vtls/keylog.c @@ -102,19 +102,20 @@ bool Curl_tls_keylog_write_line(const char *line) return TRUE; } -bool Curl_tls_keylog_write( - const char *label, - const unsigned char client_random[CLIENT_RANDOM_SIZE], - const unsigned char *secret, size_t secretlen) +bool Curl_tls_keylog_write(const char *label, + const unsigned char *client_random, + size_t random_size, + const unsigned char *secret, size_t secretlen) { size_t pos, i; unsigned char line[KEYLOG_LABEL_MAXLEN + 1 + (2 * CLIENT_RANDOM_SIZE) + 1 + (2 * SECRET_MAXLEN) + 1 + 1]; - - if(!keylog_file_fp) { + DEBUGASSERT(random_size >= CLIENT_RANDOM_SIZE); + if(random_size < CLIENT_RANDOM_SIZE) + return FALSE; + if(!keylog_file_fp) return FALSE; - } pos = strlen(label); if(pos > KEYLOG_LABEL_MAXLEN || !secretlen || secretlen > SECRET_MAXLEN) { diff --git a/Utilities/cmcurl/lib/vtls/keylog.h b/Utilities/cmcurl/lib/vtls/keylog.h index b09fcc6f4d..d489eb6360 100644 --- a/Utilities/cmcurl/lib/vtls/keylog.h +++ b/Utilities/cmcurl/lib/vtls/keylog.h @@ -25,7 +25,7 @@ ***************************************************************************/ #include "curl_setup.h" -#define KEYLOG_LABEL_MAXLEN (sizeof("CLIENT_HANDSHAKE_TRAFFIC_SECRET") - 1) +#define KEYLOG_LABEL_MAXLEN CURL_CSTRLEN("CLIENT_HANDSHAKE_TRAFFIC_SECRET") #define CLIENT_RANDOM_SIZE 32 @@ -61,10 +61,10 @@ const char *Curl_tls_keylog_file_name(void); * Appends a key log file entry. * Returns true iff the key log file is open and a valid entry was provided. */ -bool Curl_tls_keylog_write( - const char *label, - const unsigned char client_random[CLIENT_RANDOM_SIZE], - const unsigned char *secret, size_t secretlen); +bool Curl_tls_keylog_write(const char *label, + const unsigned char *client_random, + size_t random_size, + const unsigned char *secret, size_t secretlen); /* * Appends a line to the key log file, ensure it is terminated by an LF. diff --git a/Utilities/cmcurl/lib/vtls/mbedtls.c b/Utilities/cmcurl/lib/vtls/mbedtls.c index 57727613ae..dbee473d7d 100644 --- a/Utilities/cmcurl/lib/vtls/mbedtls.c +++ b/Utilities/cmcurl/lib/vtls/mbedtls.c @@ -36,7 +36,7 @@ #include #if MBEDTLS_VERSION_NUMBER < 0x03020000 -#error "mbedTLS 3.2.0 or later required" +#error "mbedTLS 3.2.0 or greater required" #endif #include #include @@ -268,7 +268,7 @@ static uint16_t mbed_cipher_suite_walk_str(const char **str, const char **end) static const char ecjpake_suite[] = "TLS_ECJPAKE_WITH_AES_128_CCM_8"; if(!id) { - if((len == sizeof(ecjpake_suite) - 1) && + if((len == CURL_CSTRLEN(ecjpake_suite)) && curl_strnequal(ecjpake_suite, *str, len)) id = MBEDTLS_TLS_ECJPAKE_WITH_AES_128_CCM_8; } @@ -453,9 +453,11 @@ static int mbed_verify_cb(void *ptr, mbedtls_x509_crt *crt, mbed_extract_certinfo(data, crt); } + /* `verifypeer` and `verifyhost` are independent, so clear the flags of a + disabled check only. The name mismatch belongs to `verifyhost`. */ if(!conn_config->verifypeer) - *flags = 0; - else if(!conn_config->verifyhost) + *flags &= MBEDTLS_X509_BADCERT_CN_MISMATCH; + if(!conn_config->verifyhost) *flags &= ~MBEDTLS_X509_BADCERT_CN_MISMATCH; if(*flags) { @@ -786,6 +788,38 @@ static CURLcode mbed_load_crl(struct Curl_cfilter *cf, return CURLE_OK; } +static bool mbed_apply_session(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct Curl_ssl_session *sc_session) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct mbed_ssl_backend_data *backend = + (struct mbed_ssl_backend_data *)connssl->backend; + + if(sc_session && sc_session->sdata && sc_session->sdata_len) { + mbedtls_ssl_session session; + int ret; + + mbedtls_ssl_session_init(&session); + ret = mbedtls_ssl_session_load(&session, sc_session->sdata, + sc_session->sdata_len); + if(ret) { + failf(data, "SSL session error loading: -0x%x", (unsigned int)-ret); + } + else { + ret = mbedtls_ssl_set_session(&backend->ssl, &session); + if(ret) + failf(data, "SSL session error setting: -0x%x", (unsigned int)-ret); + } + mbedtls_ssl_session_free(&session); + if(!ret) { + infof(data, "SSL reusing session ID"); + return TRUE; + } + } + return FALSE; +} + static CURLcode mbed_configure_ssl(struct Curl_cfilter *cf, struct Curl_easy *data) { @@ -795,6 +829,7 @@ static CURLcode mbed_configure_ssl(struct Curl_cfilter *cf, 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); int ret; + bool session_applied = FALSE; CURLcode result; char errorbuf[128]; @@ -899,8 +934,22 @@ static CURLcode mbed_configure_ssl(struct Curl_cfilter *cf, MBEDTLS_SSL_SESSION_TICKETS_DISABLED); #endif + if((cf->sockindex == SECONDARYSOCKET) && !(cf->cft->flags & CF_TYPE_PROXY)) { + /* FTP is a bitch. On TLS secured transfers, it is a common server + * option to require the client to use the SAME TLS session as on + * the control connection or it fails the request. See #22225. */ + struct Curl_ssl_session *scs = + Curl_ssl_get_cf_session(data, cf->cft, FIRSTSOCKET); + if(scs) { + if(mbed_apply_session(cf, data, scs)) { + CURL_TRC_CF(data, cf, "applied SSL session from control connection"); + session_applied = TRUE; + } + } + } + /* Check if there is a cached ID we can/should use here! */ - if(Curl_ssl_scache_use(cf, data)) { + if(!session_applied && Curl_ssl_scache_use(cf, data)) { struct Curl_ssl_session *sc_session = NULL; CURLcode sresult = Curl_ssl_scache_take(cf, data, connssl->peer.scache_key, &sc_session); @@ -1026,10 +1075,11 @@ static CURLcode mbed_connect_step2(struct Curl_cfilter *cf, #ifdef HAVE_PINNED_PUBKEY #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]; + CURL_EASY_STR(data, STRING_SSL_PINNEDPUBLICKEY_PROXY) : + CURL_EASY_STR(data, STRING_SSL_PINNEDPUBLICKEY); #else - const char * const pinnedpubkey = data->set.str[STRING_SSL_PINNEDPUBLICKEY]; + const char * const pinnedpubkey = + CURL_EASY_STR(data, STRING_SSL_PINNEDPUBLICKEY); #endif #endif @@ -1196,13 +1246,22 @@ static CURLcode mbed_new_session(struct Curl_cfilter *cf, connssl->negotiated.alpn, 0, 0, &sc_session); sdata = NULL; /* call took ownership */ - if(!result) + if(!result && /* return a duplicate if asked for and FTP */ + (cf->conn->scheme->family == CURLPROTO_FTP)) { + Curl_ssl_session_destroy(connssl->session); + result = Curl_ssl_session_dup(sc_session, &connssl->session); + } + + if(!result) { result = Curl_ssl_scache_put(cf, data, connssl->peer.scache_key, sc_session); + sc_session = NULL; + } out: if(msession_alloced) mbedtls_ssl_session_free(&session); + Curl_ssl_session_destroy(sc_session); curlx_free(sdata); return result; } @@ -1468,19 +1527,19 @@ static CURLcode mbedtls_connect(struct Curl_cfilter *cf, *done = FALSE; connssl->io_need = CURL_SSL_IO_NEED_NONE; - if(ssl_connect_1 == connssl->connecting_state) { + if(connssl->connecting_state == ssl_connect_1) { result = mbed_connect_step1(cf, data); if(result) return result; } - if(ssl_connect_2 == connssl->connecting_state) { + if(connssl->connecting_state == ssl_connect_2) { result = mbed_connect_step2(cf, data); if(result) return result; } - if(ssl_connect_3 == connssl->connecting_state) { + if(connssl->connecting_state == ssl_connect_3) { /* For tls1.3 we get notified about new sessions */ struct ssl_connect_data *ctx = cf->ctx; struct mbed_ssl_backend_data *backend = @@ -1495,7 +1554,7 @@ static CURLcode mbedtls_connect(struct Curl_cfilter *cf, connssl->connecting_state = ssl_connect_done; } - if(ssl_connect_done == connssl->connecting_state) { + if(connssl->connecting_state == ssl_connect_done) { connssl->state = ssl_connection_complete; *done = TRUE; } diff --git a/Utilities/cmcurl/lib/vtls/openssl.c b/Utilities/cmcurl/lib/vtls/openssl.c index 010bbb9825..dedd8f3e70 100644 --- a/Utilities/cmcurl/lib/vtls/openssl.c +++ b/Utilities/cmcurl/lib/vtls/openssl.c @@ -31,13 +31,11 @@ #include "urldata.h" #include "curl_trc.h" -#include "httpsrr.h" #include "formdata.h" /* for the boundary function */ #include "url.h" /* for the SSL config check function */ #include "curlx/inet_pton.h" #include "vtls/openssl.h" #include "connect.h" -#include "cf-dns.h" #include "progress.h" #include "vtls/vtls.h" #include "vtls/vtls_int.h" @@ -51,6 +49,8 @@ #include "curlx/strparse.h" #include "curlx/strcopy.h" #include "curlx/strdup.h" +#include "vdns/cf-dns.h" +#include "vdns/httpsrr.h" #include "vtls/apple.h" #ifdef USE_ECH #include "curlx/base64.h" @@ -87,11 +87,11 @@ #ifdef LIBRESSL_VERSION_NUMBER /* As of LibreSSL 2.0.0-4.0.0: OPENSSL_VERSION_NUMBER == 0x20000000L */ # if LIBRESSL_VERSION_NUMBER < 0x2090100fL /* 2019-04-13 */ -# error "LibreSSL 2.9.1 or later required" +# error "LibreSSL 2.9.1 or greater required" # endif #elif !defined(HAVE_BORINGSSL_LIKE) # ifndef HAVE_OPENSSL3 /* 2021-09-07 */ -# error "OpenSSL 3.0.0 or later required" +# error "OpenSSL 3.0.0 or greater required" # endif #endif @@ -248,7 +248,7 @@ static CURLcode X509V3_ext(struct Curl_easy *data, if(asn1_object_dump(obj, namebuf, sizeof(namebuf))) /* make sure the name is null-terminated */ - namebuf[sizeof(namebuf) - 1] = 0; + namebuf[CURL_CSTRLEN(namebuf)] = 0; if(!X509V3_EXT_print(bio_out, ext, 0, 0)) ASN1_STRING_print(bio_out, @@ -700,6 +700,7 @@ static void ossl_log_tls12_secret(const SSL *ssl, bool *keylog_done) *keylog_done = TRUE; Curl_tls_keylog_write("CLIENT_RANDOM", client_random, + sizeof(client_random), master_key, master_key_length); } #endif /* !HAVE_KEYLOG_CALLBACK */ @@ -860,7 +861,7 @@ static int ssl_ui_reader(UI *ui, UI_STRING *uis) default: break; } - return (UI_method_get_reader(UI_OpenSSL()))(ui, uis); + return UI_method_get_reader(UI_OpenSSL())(ui, uis); } /* @@ -879,7 +880,7 @@ static int ssl_ui_writer(UI *ui, UI_STRING *uis) default: break; } - return (UI_method_get_writer(UI_OpenSSL()))(ui, uis); + return UI_method_get_writer(UI_OpenSSL())(ui, uis); } /* @@ -904,7 +905,7 @@ static int use_certificate_blob(SSL_CTX *ctx, const struct curl_blob *blob, X509 *x = NULL; /* the typecast of blob->len is fine since it is guaranteed to never be larger than CURL_MAX_INPUT_LENGTH */ - BIO *in = BIO_new_mem_buf(blob->data, (int)(blob->len)); + BIO *in = BIO_new_mem_buf(blob->data, (int)blob->len); if(!in) return CURLE_OUT_OF_MEMORY; @@ -938,7 +939,7 @@ static int use_privatekey_blob(SSL_CTX *ctx, const struct curl_blob *blob, { int ret = 0; EVP_PKEY *pkey = NULL; - BIO *in = BIO_new_mem_buf(blob->data, (int)(blob->len)); + BIO *in = BIO_new_mem_buf(blob->data, (int)blob->len); if(!in) return CURLE_OUT_OF_MEMORY; @@ -966,7 +967,7 @@ static int use_certificate_chain_blob(SSL_CTX *ctx, { int ret = 0; X509 *x = NULL; - BIO *in = BIO_new_mem_buf(blob->data, (int)(blob->len)); + BIO *in = BIO_new_mem_buf(blob->data, (int)blob->len); if(!in) return CURLE_OUT_OF_MEMORY; @@ -1176,7 +1177,7 @@ static int engineload(struct Curl_easy *data, } if(data->state.engine) { - const char *cmd_name = "LOAD_CERT_CTRL"; + static const char cmd_name[] = "LOAD_CERT_CTRL"; struct { const char *cert_id; X509 *cert; @@ -1202,7 +1203,7 @@ static int engineload(struct Curl_easy *data, } if(!params.cert) { - failf(data, "SSL engine did not initialized the certificate properly."); + failf(data, "SSL engine did not initialize the certificate properly."); return 0; } @@ -1318,7 +1319,7 @@ static int pkcs12load(struct Curl_easy *data, int cert_done = 0; STACK_OF(X509) *ca = NULL; if(cert_blob) { - cert_bio = BIO_new_mem_buf(cert_blob->data, (int)(cert_blob->len)); + cert_bio = BIO_new_mem_buf(cert_blob->data, (int)cert_blob->len); if(!cert_bio) { failf(data, "BIO_new_mem_buf NULL, " OSSL_PACKAGE " error %s", ossl_strerror(ERR_get_error(), error_buffer, @@ -1898,7 +1899,7 @@ static CURLcode ossl_shutdown(struct Curl_cfilter *cf, *done = TRUE; goto out; } - if(SSL_ERROR_WANT_WRITE == SSL_get_error(octx->ssl, rc)) { + if(SSL_get_error(octx->ssl, rc) == SSL_ERROR_WANT_WRITE) { CURL_TRC_CF(data, cf, "SSL shutdown still wants to send"); connssl->io_need = CURL_SSL_IO_NEED_SEND; goto out; @@ -1925,7 +1926,7 @@ static CURLcode ossl_shutdown(struct Curl_cfilter *cf, break; case SSL_ERROR_NONE: /* did not get anything */ case SSL_ERROR_WANT_READ: - /* SSL has send its notify and now wants to read the reply + /* SSL has sent its notify and now wants to read the reply * from the server. We are not really interested in that. */ CURL_TRC_CF(data, cf, "SSL shutdown sent, want receive"); connssl->io_need = CURL_SSL_IO_NEED_RECV; @@ -2024,7 +2025,7 @@ static void ossl_close_all(struct Curl_easy *data) in the certificate and must exactly match the IP in the URI. This function is now used from ngtcp2 (QUIC) as well. -*/ + */ static CURLcode ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn, struct ssl_peer *peer, @@ -2103,18 +2104,7 @@ static CURLcode ossl_verifyhost(struct Curl_easy *data, switch(target) { case GEN_DNS: /* name/pattern comparison */ - /* The OpenSSL man page explicitly says: "In general it cannot be - assumed that the data returned by ASN1_STRING_data() is null - terminated or does not contain embedded nulls.", but also that - "The actual format of the data depends on the actual string - type itself: for example for an IA5String the data is ASCII" - - It has been however verified that in 0.9.6 and 0.9.7, IA5String - is always null-terminated. - */ - if((altlen == strlen(altptr)) && - /* if this is not true, there was an embedded zero in the name - string and we cannot match it. */ + if(!memchr(altptr, '\0', altlen) && Curl_cert_hostcheck(altptr, altlen, peer->origin->hostname, hostlen)) { matched = TRUE; @@ -2144,7 +2134,7 @@ static CURLcode ossl_verifyhost(struct Curl_easy *data, else if(dNSName || iPAddress) { const char *tname = (peer->type == CURL_SSL_PEER_DNS) ? "hostname" : (peer->type == CURL_SSL_PEER_IPV4) ? - "ipv4 address" : "ipv6 address"; + "IPv4 address" : "IPv6 address"; infof(data, " subjectAltName does not match %s %s", tname, peer->origin->user_hostname); failf(data, "SSL: no alternative certificate subject name matches " @@ -2630,37 +2620,31 @@ static CURLcode ossl_set_ssl_version_min_max(struct Curl_cfilter *cf, return CURLE_OK; } -#ifdef HAVE_BORINGSSL_LIKE -typedef uint32_t ctx_option_t; -#elif defined(HAVE_OPENSSL3) -typedef uint64_t ctx_option_t; -#elif defined(LIBRESSL_VERSION_NUMBER) -typedef long ctx_option_t; -#else -typedef unsigned long ctx_option_t; -#endif - CURLcode Curl_ossl_add_session(struct Curl_cfilter *cf, struct Curl_easy *data, + struct ossl_ctx *octx, const char *ssl_peer_key, SSL_SESSION *session, - int ietf_tls_id, const char *alpn, unsigned char *quic_tp, - size_t quic_tp_len) + size_t quic_tp_len, + struct Curl_ssl_session **psession) { + struct Curl_ssl_session *sc_session = NULL, *sc_dup = NULL; unsigned char *der_session_buf = NULL; unsigned char *qtp_clone = NULL; CURLcode result = CURLE_OK; + if(psession) + *psession = NULL; if(!cf || !data) goto out; if(Curl_ssl_scache_use(cf, data)) { - struct Curl_ssl_session *sc_session = NULL; size_t der_session_size; unsigned char *der_session_ptr; size_t earlydata_max = 0; + int ietf_tls_id = SSL_version(octx->ssl); der_session_size = i2d_SSL_SESSION(session, NULL); if(der_session_size == 0) { @@ -2698,14 +2682,29 @@ CURLcode Curl_ossl_add_session(struct Curl_cfilter *cf, earlydata_max, qtp_clone, quic_tp_len, &sc_session); der_session_buf = NULL; /* took ownership of sdata */ +#ifdef USE_APPLE_SECTRUST + if(!result) + sc_session->sectrust_verified = octx->sectrust_verified; +#endif + if(!result && psession && /* return a duplicate if asked for and FTP */ + (cf->conn->scheme->family == CURLPROTO_FTP)) { + result = Curl_ssl_session_dup(sc_session, &sc_dup); + } if(!result) { result = Curl_ssl_scache_put(cf, data, ssl_peer_key, sc_session); /* took ownership of `sc_session` */ + sc_session = NULL; } } out: curlx_free(der_session_buf); + if(!result && psession) { + *psession = sc_dup; + sc_dup = NULL; + } + Curl_ssl_session_destroy(sc_session); + Curl_ssl_session_destroy(sc_dup); return result; } @@ -2718,8 +2717,15 @@ static int ossl_new_session_cb(SSL *ssl, SSL_SESSION *ssl_sessionid) if(cf) { struct Curl_easy *data = CF_DATA_CURRENT(cf); struct ssl_connect_data *connssl = cf->ctx; - Curl_ossl_add_session(cf, data, connssl->peer.scache_key, ssl_sessionid, - SSL_version(ssl), connssl->negotiated.alpn, NULL, 0); + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; + struct Curl_ssl_session *session = NULL; + Curl_ossl_add_session(cf, data, octx, connssl->peer.scache_key, + ssl_sessionid, connssl->negotiated.alpn, NULL, + 0, &session); + if(session) { /* remember current TLS session */ + Curl_ssl_session_destroy(connssl->session); + connssl->session = session; + } } return 0; } @@ -2950,7 +2956,7 @@ static CURLcode ossl_windows_load_anchors(struct Curl_cfilter *cf, https://stackoverflow.com/questions/9507184/ https://github.com/d3x0r/SACK/blob/ff15424d3c581b86d40f818532e5a400c516d39d/src/netlib/ssl_layer.c#L1410 https://datatracker.ietf.org/doc/html/rfc5280 */ - const char *win_stores[] = { + static const char * const win_stores[] = { "ROOT", /* Trusted Root Certification Authorities */ "CA" /* Intermediate Certification Authorities */ }; @@ -2983,7 +2989,6 @@ static CURLcode ossl_load_trust_anchors(struct Curl_cfilter *cf, X509_STORE *store) { 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); CURLcode result = CURLE_OK; const char * const ssl_cafile = /* CURLOPT_CAINFO_BLOB overrides CURLOPT_CAINFO */ @@ -2992,7 +2997,7 @@ static CURLcode ossl_load_trust_anchors(struct Curl_cfilter *cf, bool have_native_check = FALSE; octx->store_is_empty = TRUE; - if(ssl_config->native_ca_store) { + if(conn_config->native_ca_store) { #ifdef USE_WIN32_CRYPTO bool added = FALSE; result = ossl_windows_load_anchors(cf, data, store, &added); @@ -3133,7 +3138,7 @@ static CURLcode ossl_populate_x509_store(struct Curl_cfilter *cf, OpenSSL do alternate chain checking by default but we do not know how to determine that in a reliable manner. https://web.archive.org/web/20190422050538/rt.openssl.org/Ticket/Display.html?id=3621 - */ + */ x509flags |= X509_V_FLAG_TRUSTED_FIRST; if(!ssl_config->no_partialchain && !ssl_crlfile) { @@ -3143,8 +3148,7 @@ static CURLcode ossl_populate_x509_store(struct Curl_cfilter *cf, instead of needing the whole chain. Due to OpenSSL bug https://github.com/openssl/openssl/issues/5081 we - cannot do partial chains with a CRL check. - */ + cannot do partial chains with a CRL check. */ x509flags |= X509_V_FLAG_PARTIAL_CHAIN; } (void)X509_STORE_set_flags(store, x509flags); @@ -3166,7 +3170,7 @@ struct ossl_x509_share { static void oss_x509_share_free(void *key, size_t key_len, void *p) { struct ossl_x509_share *share = p; - DEBUGASSERT(key_len == (sizeof(MPROTO_OSSL_X509_KEY) - 1)); + DEBUGASSERT(key_len == CURL_CSTRLEN(MPROTO_OSSL_X509_KEY)); DEBUGASSERT(!memcmp(MPROTO_OSSL_X509_KEY, key, key_len)); (void)key; (void)key_len; @@ -3217,7 +3221,7 @@ static X509_STORE *ossl_get_cached_x509_store(struct Curl_cfilter *cf, *pempty = TRUE; share = multi ? Curl_hash_pick(&multi->proto_hash, CURL_UNCONST(MPROTO_OSSL_X509_KEY), - sizeof(MPROTO_OSSL_X509_KEY) - 1) : NULL; + CURL_CSTRLEN(MPROTO_OSSL_X509_KEY)) : NULL; if(share && share->store && !ossl_cached_x509_store_expired(data, share) && !ossl_cached_x509_store_different(cf, data, share)) { @@ -3242,7 +3246,7 @@ static void ossl_set_cached_x509_store(struct Curl_cfilter *cf, return; share = Curl_hash_pick(&multi->proto_hash, CURL_UNCONST(MPROTO_OSSL_X509_KEY), - sizeof(MPROTO_OSSL_X509_KEY) - 1); + CURL_CSTRLEN(MPROTO_OSSL_X509_KEY)); if(!share) { share = curlx_calloc(1, sizeof(*share)); @@ -3250,7 +3254,7 @@ static void ossl_set_cached_x509_store(struct Curl_cfilter *cf, return; if(!Curl_hash_add2(&multi->proto_hash, CURL_UNCONST(MPROTO_OSSL_X509_KEY), - sizeof(MPROTO_OSSL_X509_KEY) - 1, + CURL_CSTRLEN(MPROTO_OSSL_X509_KEY), share, oss_x509_share_free)) { curlx_free(share); return; @@ -3301,7 +3305,7 @@ CURLcode Curl_ssl_setup_x509_store(struct Curl_cfilter *cf, !conn_config->CApath && !conn_config->ca_info_blob && !ssl_config->primary.CRLfile && - !ssl_config->native_ca_store; + !conn_config->native_ca_store; ERR_set_mark(); @@ -3324,6 +3328,82 @@ CURLcode Curl_ssl_setup_x509_store(struct Curl_cfilter *cf, return result; } +static bool ossl_apply_session( + struct ossl_ctx *octx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct alpn_spec *alpns, + Curl_ossl_init_session_reuse_cb *sess_reuse_cb, + struct Curl_ssl_session *scs) +{ + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + struct ssl_primary_config *conn_cfg = Curl_ssl_cf_get_primary_config(cf); + const unsigned char *der_sessionid = scs->sdata; + size_t der_sessionid_size = scs->sdata_len; + SSL_SESSION *ssl_session = NULL; + + /* If OpenSSL does not accept the session from the cache, this + * is not an error. We continue without it. */ + ssl_session = d2i_SSL_SESSION(NULL, &der_sessionid, + (long)der_sessionid_size); + if(ssl_session) { + if(!SSL_set_session(octx->ssl, ssl_session)) { + VERBOSE(char error_buffer[256]); + infof(data, "SSL: SSL_set_session not accepted, " + "continuing without: %s", + ossl_strerror(ERR_get_error(), error_buffer, + sizeof(error_buffer))); + } + else { + if(conn_cfg->verifypeer && + (SSL_get_verify_result(octx->ssl) != X509_V_OK) +#ifdef USE_APPLE_SECTRUST + /* if sectrust is used and verified the session before */ + && (!conn_cfg->native_ca_store || !scs->sectrust_verified) +#endif + ) { + /* Session was from unverified connection, cannot reuse here */ + SSL_set_session(octx->ssl, NULL); + infof(data, "SSL session not peer verified, not reusing"); + } + else { + infof(data, "SSL reusing session with ALPN '%s'", + scs->alpn ? scs->alpn : "-"); + octx->reused_session = TRUE; +#ifdef USE_APPLE_SECTRUST + octx->sectrust_session = scs->sectrust_verified; +#endif + infof(data, "SSL verify result: %lx", + (unsigned long)SSL_get_verify_result(octx->ssl)); +#ifdef HAVE_OPENSSL_EARLYDATA + if(ssl_config->earlydata && scs->alpn && + SSL_SESSION_get_max_early_data(ssl_session) && + !cf->conn->bits.connect_only && + (SSL_version(octx->ssl) == TLS1_3_VERSION)) { + bool do_early_data = FALSE; + if(sess_reuse_cb) + (void)sess_reuse_cb(cf, data, alpns, scs, &do_early_data); + if(do_early_data) { + /* We only try the ALPN protocol the session used before, + * otherwise we might send early data for the wrong protocol */ + Curl_alpn_restrict_to(alpns, scs->alpn); + } + } +#else + (void)alpns; + (void)ssl_config; + (void)sess_reuse_cb; +#endif + } + } + SSL_SESSION_free(ssl_session); + } + else { + infof(data, "SSL session not accepted by OpenSSL, continuing without"); + } + return (bool)octx->reused_session; +} + static CURLcode ossl_init_session_and_alpns( struct ossl_ctx *octx, struct Curl_cfilter *cf, @@ -3332,7 +3412,6 @@ static CURLcode ossl_init_session_and_alpns( const struct alpn_spec *alpns_requested, Curl_ossl_init_session_reuse_cb *sess_reuse_cb) { - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); struct ssl_primary_config *conn_cfg = Curl_ssl_cf_get_primary_config(cf); struct alpn_spec alpns; CURLcode result; @@ -3340,70 +3419,26 @@ static CURLcode ossl_init_session_and_alpns( Curl_alpn_copy(&alpns, alpns_requested); octx->reused_session = FALSE; - if(Curl_ssl_scache_use(cf, data) && !conn_cfg->verifystatus) { + + if((cf->sockindex == SECONDARYSOCKET) && !(cf->cft->flags & CF_TYPE_PROXY)) { + /* FTP is a bitch. On TLS secured transfers, it is a common server + * option to require the client to use the SAME TLS session as on + * the control connection or it fails the request. See #22225. */ + struct Curl_ssl_session *scs = + Curl_ssl_get_cf_session(data, cf->cft, FIRSTSOCKET); + if(scs) { + if(ossl_apply_session(octx, cf, data, &alpns, sess_reuse_cb, scs)) + CURL_TRC_CF(data, cf, "applied SSL session from control connection"); + } + } + + if(!octx->reused_session && + Curl_ssl_scache_use(cf, data) && !conn_cfg->verifystatus) { struct Curl_ssl_session *scs = NULL; result = Curl_ssl_scache_take(cf, data, peer->scache_key, &scs); if(!result && scs && scs->sdata && scs->sdata_len) { - const unsigned char *der_sessionid = scs->sdata; - size_t der_sessionid_size = scs->sdata_len; - SSL_SESSION *ssl_session = NULL; - - /* If OpenSSL does not accept the session from the cache, this - * is not an error. We continue without it. */ - ssl_session = d2i_SSL_SESSION(NULL, &der_sessionid, - (long)der_sessionid_size); - if(ssl_session) { - if(!SSL_set_session(octx->ssl, ssl_session)) { - VERBOSE(char error_buffer[256]); - infof(data, "SSL: SSL_set_session not accepted, " - "continuing without: %s", - ossl_strerror(ERR_get_error(), error_buffer, - sizeof(error_buffer))); - } - else { - if(conn_cfg->verifypeer && - (SSL_get_verify_result(octx->ssl) != X509_V_OK)) { - /* Session was from unverified connection, cannot reuse here */ - SSL_set_session(octx->ssl, NULL); - infof(data, "SSL session not peer verified, not reusing"); - } - else { - infof(data, "SSL reusing session with ALPN '%s'", - scs->alpn ? scs->alpn : "-"); - octx->reused_session = TRUE; - infof(data, "SSL verify result: %lx", - (unsigned long)SSL_get_verify_result(octx->ssl)); -#ifdef HAVE_OPENSSL_EARLYDATA - if(ssl_config->earlydata && scs->alpn && - SSL_SESSION_get_max_early_data(ssl_session) && - !cf->conn->bits.connect_only && - (SSL_version(octx->ssl) == TLS1_3_VERSION)) { - bool do_early_data = FALSE; - if(sess_reuse_cb) { - result = sess_reuse_cb(cf, data, &alpns, scs, &do_early_data); - if(result) { - SSL_SESSION_free(ssl_session); - return result; - } - } - if(do_early_data) { - /* We only try the ALPN protocol the session used before, - * otherwise we might send early data for the wrong protocol */ - Curl_alpn_restrict_to(&alpns, scs->alpn); - } - } -#else - (void)ssl_config; - (void)sess_reuse_cb; -#endif - } - } - SSL_SESSION_free(ssl_session); - } - else { - infof(data, "SSL session not accepted by OpenSSL, continuing without"); - } + (void)ossl_apply_session(octx, cf, data, &alpns, sess_reuse_cb, scs); } Curl_ssl_scache_return(cf, data, peer->scache_key, scs); } @@ -3431,7 +3466,7 @@ bool Curl_ossl_need_httpsrr(struct Curl_easy *data) if(!CURLECH_ENABLED(data)) return FALSE; if((data->set.tls_ech == CURLECH_GREASE) || - data->set.str[STRING_ECH_CONFIG]) + CURL_EASY_STR(data, STRING_ECH_CONFIG)) return FALSE; return TRUE; } @@ -3441,9 +3476,7 @@ static CURLcode ossl_init_ech(struct ossl_ctx *octx, struct Curl_easy *data, struct ssl_peer *peer) { - unsigned char *ech_config = NULL; - size_t ech_config_len = 0; - char *outername = data->set.str[STRING_ECH_PUBLIC]; + const char *outername = CURL_EASY_STR(data, STRING_ECH_PUBLIC); int trying_ech_now = 0; if(!CURLECH_ENABLED(data)) @@ -3457,10 +3490,12 @@ static CURLcode ossl_init_ech(struct ossl_ctx *octx, SSL_set_options(octx->ssl, SSL_OP_ECH_GREASE); #endif } - else if(data->set.tls_ech && data->set.str[STRING_ECH_CONFIG]) { + else if(data->set.tls_ech && CURL_EASY_STR(data, STRING_ECH_CONFIG)) { #ifdef HAVE_BORINGSSL_LIKE /* have to do base64 decode here for BoringSSL */ - const char *b64 = data->set.str[STRING_ECH_CONFIG]; + const char *b64 = CURL_EASY_STR(data, STRING_ECH_CONFIG); + uint8_t *ech_config; + size_t ech_config_len = 0; CURLcode result; if(!b64) { @@ -3484,13 +3519,16 @@ static CURLcode ossl_init_ech(struct ossl_ctx *octx, curlx_free(ech_config); trying_ech_now = 1; #else - ech_config = (unsigned char *)data->set.str[STRING_ECH_CONFIG]; + const char *ech_config = CURL_EASY_STR(data, STRING_ECH_CONFIG); + size_t ech_config_len = 0; 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_set1_ech_config_list(octx->ssl, ech_config, ech_config_len) != 1) { + ech_config_len = strlen(ech_config); + if(SSL_set1_ech_config_list(octx->ssl, + (const uint8_t *)ech_config, + ech_config_len) != 1) { infof(data, "ECH: SSL_ECH_set1_ech_config_list failed"); if(data->set.tls_ech == CURLECH_HARD) return CURLE_SSL_CONNECT_ERROR; @@ -3707,6 +3745,11 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, ossl_strerror(ERR_peek_error(), error_buffer, sizeof(error_buffer))); return CURLE_OUT_OF_MEMORY; } +#ifdef OPENSSL_HAS_PROVIDERS + if(data->state.libctx) + /* forbid connection reuse with provider/engine use */ + connclose(data->conn); +#endif if(cb_setup) { result = cb_setup(cf, data, cb_user_data); @@ -3724,7 +3767,7 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, } /* OpenSSL contains code to work around lots of bugs and flaws in various - SSL-implementations. SSL_CTX_set_options() is used to enabled those + SSL-implementations. SSL_CTX_set_options() is used to enable those workarounds. The man page for this option states that SSL_OP_ALL enables all the workarounds and that "It is usually safe to use SSL_OP_ALL to enable the bug workaround options if compatibility with somewhat broken @@ -3756,8 +3799,7 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, In 0.9.6e they added a bit to SSL_OP_ALL that _disables_ that workaround despite the fact that SSL_OP_ALL is documented to do "rather harmless" workarounds. In order to keep the secure workaround, the - SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS bit must not be set. - */ + SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS bit must not be set. */ ctx_options = SSL_OP_ALL | SSL_OP_NO_TICKET | SSL_OP_NO_COMPRESSION; @@ -3888,31 +3930,6 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, } #endif -#if defined(HAVE_OPENSSL_SRP) && defined(USE_TLS_SRP) - if(ssl_config->primary.username && Curl_auth_allowed_to_host(data)) { - char * const ssl_username = ssl_config->primary.username; - char * const ssl_password = ssl_config->primary.password; - infof(data, "Using TLS-SRP username: %s", ssl_username); - - if(!SSL_CTX_set_srp_username(octx->ssl_ctx, ssl_username)) { - failf(data, "Unable to set SRP username"); - return CURLE_BAD_FUNCTION_ARGUMENT; - } - 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(octx->ssl_ctx, "SRP")) { - failf(data, "failed setting SRP cipher list"); - return CURLE_SSL_CIPHER; - } - } - } -#endif /* HAVE_OPENSSL_SRP && USE_TLS_SRP */ - /* OpenSSL always tries to verify the peer. By setting the failure mode * to NONE, we allow the connect to complete, regardless of the outcome. * We then explicitly check the result and may try alternatives like @@ -3939,6 +3956,7 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, /* give application a chance to interfere with SSL set up. */ if(data->set.ssl.fsslctx) { + struct Curl_mapi_guard guard; /* When a user callback is installed to modify the SSL_CTX, * we need to do the full initialization before calling it. * See: #11800 */ @@ -3948,10 +3966,10 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, return result; octx->x509_store_setup = TRUE; } - Curl_set_in_callback(data, TRUE); + CURL_CBAPI_START(&guard, data, easy_fsslctx); result = (*data->set.ssl.fsslctx)(data, octx->ssl_ctx, data->set.ssl.fsslctxp); - Curl_set_in_callback(data, FALSE); + CURL_CBAPI_END(&guard); if(result) { failf(data, "error signaled by SSL ctx callback"); return result; @@ -4014,7 +4032,7 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, BIO *bio; CURLcode result; - DEBUGASSERT(ssl_connect_1 == connssl->connecting_state); + DEBUGASSERT(connssl->connecting_state == ssl_connect_1); DEBUGASSERT(octx); DEBUGASSERT(connssl->peer.origin); @@ -4129,7 +4147,7 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, struct ssl_connect_data *connssl = cf->ctx; 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); + DEBUGASSERT(connssl->connecting_state == ssl_connect_2); DEBUGASSERT(octx); connssl->io_need = CURL_SSL_IO_NEED_NONE; @@ -4161,25 +4179,25 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, int detail = SSL_get_error(octx->ssl, err); CURL_TRC_CF(data, cf, "SSL_connect() -> err=%d, detail=%d", err, detail); - if(SSL_ERROR_WANT_READ == detail) { + if(detail == SSL_ERROR_WANT_READ) { CURL_TRC_CF(data, cf, "SSL_connect() -> want recv"); connssl->io_need = CURL_SSL_IO_NEED_RECV; return CURLE_AGAIN; } - if(SSL_ERROR_WANT_WRITE == detail) { + if(detail == SSL_ERROR_WANT_WRITE) { CURL_TRC_CF(data, cf, "SSL_connect() -> want send"); connssl->io_need = CURL_SSL_IO_NEED_SEND; return CURLE_AGAIN; } #ifdef SSL_ERROR_WANT_ASYNC - if(SSL_ERROR_WANT_ASYNC == detail) { + if(detail == SSL_ERROR_WANT_ASYNC) { CURL_TRC_CF(data, cf, "SSL_connect() -> want async"); connssl->io_need = CURL_SSL_IO_NEED_RECV; return CURLE_AGAIN; } #endif #ifdef SSL_ERROR_WANT_RETRY_VERIFY - if(SSL_ERROR_WANT_RETRY_VERIFY == detail) { + if(detail == SSL_ERROR_WANT_RETRY_VERIFY) { CURL_TRC_CF(data, cf, "SSL_connect() -> want retry_verify"); Curl_xfer_pause_recv(data, TRUE); return CURLE_AGAIN; @@ -4568,21 +4586,24 @@ out: return result; } +static const char *pinned(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + (void)cf; + return +#ifndef CURL_DISABLE_PROXY + Curl_ssl_cf_is_proxy(cf) ? + CURL_EASY_STR(data, STRING_SSL_PINNEDPUBLICKEY_PROXY) : +#endif + CURL_EASY_STR(data, STRING_SSL_PINNEDPUBLICKEY); +} + static CURLcode ossl_check_pinned_key(struct Curl_cfilter *cf, struct Curl_easy *data, X509 *server_cert) { - const char *ptr; CURLcode result = CURLE_OK; - - (void)cf; -#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 + const char *ptr = pinned(cf, data); if(ptr) { result = ossl_pkp_pin_peer_pubkey(data, server_cert, ptr); if(result) @@ -4648,6 +4669,7 @@ out: struct ossl_certs_ctx { STACK_OF(X509) *sk; size_t num_certs; + unsigned char *last_der; }; static CURLcode ossl_chain_get_der(struct Curl_cfilter *cf, @@ -4661,6 +4683,9 @@ static CURLcode ossl_chain_get_der(struct Curl_cfilter *cf, X509 *cert; int der_len; + OPENSSL_free(chain->last_der); + chain->last_der = NULL; + (void)cf; (void)data; *pder_len = 0; @@ -4674,6 +4699,7 @@ static CURLcode ossl_chain_get_der(struct Curl_cfilter *cf, der_len = i2d_X509(cert, pder); if(der_len < 0) return CURLE_FAILED_INIT; + chain->last_der = *pder; *pder_len = (size_t)der_len; return CURLE_OK; } @@ -4681,13 +4707,13 @@ static CURLcode ossl_chain_get_der(struct Curl_cfilter *cf, static CURLcode ossl_apple_verify(struct Curl_cfilter *cf, struct Curl_easy *data, struct ossl_ctx *octx, - struct ssl_peer *peer, - bool *pverified) + struct ssl_peer *peer) { struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); struct ossl_certs_ctx chain; CURLcode result; + octx->sectrust_verified = FALSE; memset(&chain, 0, sizeof(chain)); chain.sk = SSL_get_peer_cert_chain(octx->ssl); chain.num_certs = chain.sk ? sk_X509_num(chain.sk) : 0; @@ -4699,8 +4725,11 @@ static CURLcode ossl_apple_verify(struct Curl_cfilter *cf, result = CURLE_PEER_FAILED_VERIFICATION; } else { - /* when session was reused, there is no peer cert chain */ - *pverified = FALSE; + /* When session was reused, there is no peer cert chain. + * We trust it if it came from a SecTrust verified TLS. */ + CURL_TRC_CF(data, cf, "session reused, sectrust_session=%d", + octx->sectrust_session); + octx->sectrust_verified = (bool)octx->sectrust_session; return CURLE_OK; } } @@ -4716,23 +4745,26 @@ static CURLcode ossl_apple_verify(struct Curl_cfilter *cf, ocsp_len = (long)SSL_get_tlsext_status_ocsp_resp(octx->ssl, &ocsp_data); /* SSL_get_tlsext_status_ocsp_resp() returns the length of the OCSP - response data or -1 if there is no OCSP response data. */ - if(ocsp_len < 0) { + response data or -1 if there is no OCSP response data. + AWS-LC breaks the API and returns 0 when there is no data. */ + if(ocsp_len <= 0) { ocsp_len = 0; /* no data available */ ocsp_missing = TRUE; } result = Curl_vtls_apple_verify(cf, data, peer, chain.num_certs, ossl_chain_get_der, &chain, ocsp_data, ocsp_len); + OPENSSL_free(chain.last_der); + chain.last_der = NULL; if(!result && ocsp_missing && conn_config->verifystatus && !octx->reused_session) { /* verified, but OCSP stapling is required and server sent none */ - *pverified = TRUE; + octx->sectrust_verified = TRUE; failf(data, "No OCSP response received"); return CURLE_SSL_INVALIDCERTSTATUS; } } - *pverified = !result; + octx->sectrust_verified = !result; return result; } #endif /* USE_APPLE_SECTRUST */ @@ -4749,9 +4781,6 @@ CURLcode Curl_ossl_check_peer_cert(struct Curl_cfilter *cf, long ossl_verify; X509 *server_cert; bool verified = FALSE; -#if !defined(OPENSSL_NO_OCSP) && defined(USE_APPLE_SECTRUST) - bool sectrust_verified = FALSE; -#endif if(data->set.ssl.certinfo && !octx->reused_session) { /* asked to gather certificate info. Reused sessions do not have cert @@ -4764,7 +4793,8 @@ CURLcode Curl_ossl_check_peer_cert(struct Curl_cfilter *cf, server_cert = SSL_get1_peer_certificate(octx->ssl); if(!server_cert) { /* no verification at all, this maybe acceptable */ - if(!(conn_config->verifypeer || conn_config->verifyhost)) + if(!(conn_config->verifypeer || conn_config->verifyhost) && + !pinned(cf, data)) goto out; failf(data, "SSL: could not get peer certificate"); @@ -4795,19 +4825,17 @@ CURLcode Curl_ossl_check_peer_cert(struct Curl_cfilter *cf, infof(data, "SSL certificate verified via OpenSSL."); #ifdef USE_APPLE_SECTRUST - if(!verified && conn_config->verifypeer && ssl_config->native_ca_store) { + if(!verified && conn_config->verifypeer && conn_config->native_ca_store) { /* we verify using Apple SecTrust *unless* OpenSSL already verified. * This may happen if the application intercepted the OpenSSL callback * and installed its own. */ - result = ossl_apple_verify(cf, data, octx, peer, &verified); + result = ossl_apple_verify(cf, data, octx, peer); if(result && (result != CURLE_PEER_FAILED_VERIFICATION)) goto out; /* unexpected error */ - if(verified) { + if(octx->sectrust_verified) { infof(data, "SSL certificate verified via Apple SecTrust."); ssl_config->certverifyresult = X509_V_OK; -#ifndef OPENSSL_NO_OCSP - sectrust_verified = TRUE; -#endif + verified = TRUE; } } #endif @@ -4826,9 +4854,9 @@ CURLcode Curl_ossl_check_peer_cert(struct Curl_cfilter *cf, #ifndef OPENSSL_NO_OCSP if(conn_config->verifystatus && #ifdef USE_APPLE_SECTRUST - !sectrust_verified && /* already verified via apple sectrust, cannot - * verifystate via OpenSSL in that case as it - * does not have the trust anchors */ + !octx->sectrust_verified && /* already verified via sectrust, cannot + * verifystate via OpenSSL in that case as it + * does not have the trust anchors */ #endif !octx->reused_session) { /* do not do this after Session ID reuse */ @@ -4856,7 +4884,7 @@ static CURLcode ossl_connect_step3(struct Curl_cfilter *cf, struct ssl_connect_data *connssl = cf->ctx; struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; - DEBUGASSERT(ssl_connect_3 == connssl->connecting_state); + DEBUGASSERT(connssl->connecting_state == ssl_connect_3); /* * We check certificates to authenticate the server; otherwise we risk @@ -4968,7 +4996,7 @@ static CURLcode ossl_connect(struct Curl_cfilter *cf, *done = FALSE; connssl->io_need = CURL_SSL_IO_NEED_NONE; - if(ssl_connect_1 == connssl->connecting_state) { + if(connssl->connecting_state == ssl_connect_1) { if(Curl_ossl_need_httpsrr(data) && !Curl_conn_dns_resolved_https(data, cf->sockindex, connssl->peer.peer)) { @@ -4981,7 +5009,7 @@ static CURLcode ossl_connect(struct Curl_cfilter *cf, goto out; } - if(ssl_connect_2 == connssl->connecting_state) { + if(connssl->connecting_state == ssl_connect_2) { CURL_TRC_CF(data, cf, "ossl_connect, step2"); #ifdef HAVE_OPENSSL_EARLYDATA if(connssl->earlydata_state == ssl_earlydata_await) { @@ -5002,7 +5030,7 @@ static CURLcode ossl_connect(struct Curl_cfilter *cf, goto out; } - if(ssl_connect_3 == connssl->connecting_state) { + if(connssl->connecting_state == ssl_connect_3) { CURL_TRC_CF(data, cf, "ossl_connect, step3"); result = ossl_connect_step3(cf, data); if(result) @@ -5020,7 +5048,7 @@ static CURLcode ossl_connect(struct Curl_cfilter *cf, #endif } - if(ssl_connect_done == connssl->connecting_state) { + if(connssl->connecting_state == ssl_connect_done) { CURL_TRC_CF(data, cf, "ossl_connect, done"); connssl->state = ssl_connection_complete; } @@ -5254,7 +5282,7 @@ out: } static CURLcode ossl_get_channel_binding(struct Curl_easy *data, - int sockindex, + int8_t sockindex, struct dynbuf *binding) { X509 *cert; @@ -5265,7 +5293,7 @@ static CURLcode ossl_get_channel_binding(struct Curl_easy *data, unsigned int length; unsigned char buf[EVP_MAX_MD_SIZE]; - const char prefix[] = "tls-server-end-point:"; + static const char prefix[] = "tls-server-end-point:"; struct connectdata *conn = data->conn; struct Curl_cfilter *cf = conn->cfilter[sockindex]; struct ossl_ctx *octx = NULL; @@ -5387,7 +5415,7 @@ static CURLcode ossl_get_channel_binding(struct Curl_easy *data, } /* Append "tls-server-end-point:" */ - result = curlx_dyn_addn(binding, prefix, sizeof(prefix) - 1); + result = curlx_dyn_addn(binding, prefix, CURL_CSTRLEN(prefix)); if(result) goto out; @@ -5405,9 +5433,9 @@ size_t Curl_ossl_version(char *buffer, size_t size) char *p; size_t count; const char *ver = OpenSSL_version(OPENSSL_VERSION); - const char expected[] = OSSL_PACKAGE " "; /* ie "LibreSSL " */ - if(curl_strnequal(ver, expected, sizeof(expected) - 1)) { - ver += sizeof(expected) - 1; + static const char expected[] = OSSL_PACKAGE " "; /* ie "LibreSSL " */ + if(curl_strnequal(ver, expected, CURL_CSTRLEN(expected))) { + ver += CURL_CSTRLEN(expected); } count = curl_msnprintf(buffer, size, "%s/%s", OSSL_PACKAGE, ver); for(p = buffer; *p; ++p) { @@ -5458,10 +5486,10 @@ static CURLcode ossl_sha256sum(const unsigned char *input, EVP_MD_CTX *mdctx; (void)unused; - mdctx = EVP_MD_CTX_create(); + mdctx = EVP_MD_CTX_new(); if(!mdctx) return CURLE_OUT_OF_MEMORY; - if(!EVP_DigestInit(mdctx, EVP_sha256())) { + if(!EVP_DigestInit_ex(mdctx, EVP_sha256(), NULL)) { result = CURLE_FAILED_INIT; goto out; } @@ -5469,7 +5497,7 @@ static CURLcode ossl_sha256sum(const unsigned char *input, !EVP_DigestFinal_ex(mdctx, sha256sum, NULL)) result = CURLE_BAD_FUNCTION_ARGUMENT; out: - EVP_MD_CTX_destroy(mdctx); + EVP_MD_CTX_free(mdctx); return result; } diff --git a/Utilities/cmcurl/lib/vtls/openssl.h b/Utilities/cmcurl/lib/vtls/openssl.h index 4fa466b367..dfac78c21e 100644 --- a/Utilities/cmcurl/lib/vtls/openssl.h +++ b/Utilities/cmcurl/lib/vtls/openssl.h @@ -33,7 +33,7 @@ * , , or something else, does this: * #define X509_NAME ((LPCSTR)7) * - * In AWC-LC/BoringSSL's there is: + * In AWS-LC/BoringSSL's there is: * typedef struct X509_name_st X509_NAME; * etc. * @@ -100,6 +100,14 @@ #define HAVE_OPENSSL_EARLYDATA #endif +#ifdef LIBRESSL_VERSION_NUMBER +typedef long ctx_option_t; +#elif defined(HAVE_BORINGSSL_LIKE) +typedef uint32_t ctx_option_t; +#else +typedef uint64_t ctx_option_t; +#endif + struct alpn_spec; struct ssl_peer; struct Curl_ssl_session; @@ -121,6 +129,10 @@ struct ossl_ctx { BIT(x509_store_setup); /* x509 store has been set up */ BIT(store_is_empty); /* no certs/paths/blobs in x509 store */ BIT(reused_session); /* session-ID was reused for this */ +#ifdef USE_APPLE_SECTRUST + BIT(sectrust_verified); /* peer was verified by sectrust */ + BIT(sectrust_session); /* session from sectrust verified peer */ +#endif }; size_t Curl_ossl_version(char *buffer, size_t size); @@ -174,12 +186,13 @@ CURLcode Curl_ossl_ctx_configure(struct Curl_cfilter *cf, */ CURLcode Curl_ossl_add_session(struct Curl_cfilter *cf, struct Curl_easy *data, + struct ossl_ctx *octx, const char *ssl_peer_key, SSL_SESSION *session, - int ietf_tls_id, const char *alpn, unsigned char *quic_tp, - size_t quic_tp_len); + size_t quic_tp_len, + struct Curl_ssl_session **psession); /* * Get the server cert, verify it and show it, etc., only call failf() if diff --git a/Utilities/cmcurl/lib/vtls/rustls.c b/Utilities/cmcurl/lib/vtls/rustls.c index 950f170212..9c0b0d7be3 100644 --- a/Utilities/cmcurl/lib/vtls/rustls.c +++ b/Utilities/cmcurl/lib/vtls/rustls.c @@ -32,9 +32,9 @@ #include "curlx/fopen.h" #include "curlx/strerr.h" #include "urldata.h" -#include "cf-dns.h" #include "curl_trc.h" -#include "httpsrr.h" +#include "vdns/cf-dns.h" +#include "vdns/httpsrr.h" #include "vtls/vtls.h" #include "vtls/vtls_int.h" #include "vtls/rustls.h" @@ -517,11 +517,11 @@ static void cr_keylog_log_cb(struct rustls_str label, size_t secret_len) { char clabel[KEYLOG_LABEL_MAXLEN]; - (void)client_random_len; DEBUGASSERT(client_random_len == CLIENT_RANDOM_SIZE); /* Turning a "rustls_str" into a null delimited "c" string */ curl_msnprintf(clabel, sizeof(clabel), "%.*s", (int)label.len, label.data); - Curl_tls_keylog_write(clabel, client_random, secret, secret_len); + Curl_tls_keylog_write(clabel, client_random, client_random_len, + secret, secret_len); } static CURLcode @@ -923,7 +923,7 @@ static bool cr_ech_need_httpsrr(struct Curl_easy *data) if(!CURLECH_ENABLED(data)) return FALSE; if((data->set.tls_ech == CURLECH_GREASE) || - data->set.str[STRING_ECH_CONFIG]) + CURL_EASY_STR(data, STRING_ECH_CONFIG)) return FALSE; return TRUE; } @@ -948,7 +948,7 @@ init_config_builder_ech(struct Curl_easy *data, goto cleanup; } - if(data->set.str[STRING_ECH_PUBLIC]) { + if(CURL_EASY_STR(data, STRING_ECH_PUBLIC)) { failf(data, "rustls: ECH outername not supported"); result = CURLE_SSL_CONNECT_ERROR; goto cleanup; @@ -964,8 +964,8 @@ init_config_builder_ech(struct Curl_easy *data, return CURLE_OK; } - if(data->set.tls_ech && data->set.str[STRING_ECH_CONFIG]) { - const char *b64 = data->set.str[STRING_ECH_CONFIG]; + if(data->set.tls_ech && CURL_EASY_STR(data, STRING_ECH_CONFIG)) { + const char *b64 = CURL_EASY_STR(data, STRING_ECH_CONFIG); size_t decode_result; if(!b64) { infof(data, "rustls: ECHConfig from command line empty"); @@ -1005,7 +1005,7 @@ init_config_builder_ech(struct Curl_easy *data, } cleanup: /* if we base64 decoded, we can free now */ - if(data->set.tls_ech && data->set.str[STRING_ECH_CONFIG]) { + if(data->set.tls_ech && CURL_EASY_STR(data, STRING_ECH_CONFIG)) { curlx_free(ech_config); } if(dns) { @@ -1049,7 +1049,7 @@ static CURLcode cr_init_backend(struct Curl_cfilter *cf, rustls_client_config_builder_dangerous_set_certificate_verifier( config_builder, cr_verify_none); } - else if(ssl_config->native_ca_store) { + else if(conn_config->native_ca_store) { if(conn_config->CRLfile) { failf(data, "rustls: CRL file not supported with native CA store; " "the platform verifier has no CRL attachment API"); diff --git a/Utilities/cmcurl/lib/vtls/schannel.c b/Utilities/cmcurl/lib/vtls/schannel.c index 3e871227aa..34c6e79f79 100644 --- a/Utilities/cmcurl/lib/vtls/schannel.c +++ b/Utilities/cmcurl/lib/vtls/schannel.c @@ -48,9 +48,8 @@ #include "curlx/fopen.h" #include "curlx/multibyte.h" #include "vtls/x509asn1.h" -#include "system_win32.h" #include "curlx/version_win32.h" -#include "rand.h" +#include "curlx/winapi.h" #include "curlx/strparse.h" #include "progress.h" #include "curl_sha256.h" @@ -84,7 +83,7 @@ #ifndef SP_PROT_TLS1_3_CLIENT #define SP_PROT_TLS1_3_CLIENT 0x00002000 #endif -/* Offered by mingw-w64 v8+, MS SDK 8.1/~VS2013+ */ +/* Offered by mingw-w64 v8+, MS SDK 8.1/VS2013+ */ #ifndef SCH_USE_STRONG_CRYPTO #define SCH_USE_STRONG_CRYPTO 0x00400000 #endif @@ -119,15 +118,32 @@ /* key to use at `multi->proto_hash` */ #define MPROTO_SCHANNEL_CERT_SHARE_KEY "tls:schannel:cert:share" -/* ALPN requires version 8.1 of the Windows SDK, which was - shipped with Visual Studio 2013, aka _MSC_VER 1800: - https://learn.microsoft.com/previous-versions/windows/it-pro/windows-server-2012-R2-and-2012/hh831771 - Or mingw-w64 9.0 or upper. -*/ -#if (defined(__MINGW64_VERSION_MAJOR) && __MINGW64_VERSION_MAJOR >= 9) || \ - (defined(_MSC_VER) && (_MSC_VER >= 1800) && !defined(_USING_V110_SDK71_)) -#define HAS_ALPN_SCHANNEL static bool s_win_has_alpn; + +/* Offered by mingw-w64 v9+, MS SDK 8.1/VS2013+ */ +#ifndef SECBUFFER_APPLICATION_PROTOCOLS +#define SECBUFFER_APPLICATION_PROTOCOLS 18 +#define SECPKG_ATTR_APPLICATION_PROTOCOL 35 + +typedef enum { + SecApplicationProtocolNegotiationExt_None, + SecApplicationProtocolNegotiationExt_NPN, + SecApplicationProtocolNegotiationExt_ALPN +} SEC_APPLICATION_PROTOCOL_NEGOTIATION_EXT; + +typedef enum { + SecApplicationProtocolNegotiationStatus_None, + SecApplicationProtocolNegotiationStatus_Success, + SecApplicationProtocolNegotiationStatus_SelectedClientOnly +} SEC_APPLICATION_PROTOCOL_NEGOTIATION_STATUS; + +/* !checksrc! disable TYPEDEFSTRUCT 1 */ +typedef struct { + SEC_APPLICATION_PROTOCOL_NEGOTIATION_STATUS ProtoNegoStatus; + SEC_APPLICATION_PROTOCOL_NEGOTIATION_EXT ProtoNegoExt; + unsigned char ProtocolIdSize; + unsigned char ProtocolId[0xff]; +} SecPkgContext_ApplicationProtocol; #endif static void InitSecBuffer(SecBuffer *buffer, unsigned long BufType, @@ -161,8 +177,7 @@ static CURLcode schannel_set_ssl_version_min_max(DWORD *enabled_protocols, /* Windows Server 2022 and newer (including Windows 11) support TLS 1.3 built-in. Previous builds of Windows 10 had broken TLS 1.3 - implementations that could be enabled via registry. - */ + implementations that could be enabled via registry. */ if(curlx_verify_windows_version(10, 0, 20348, PLATFORM_WINNT, VERSION_GREATER_THAN_EQUAL)) { ssl_version_max = CURL_SSLVERSION_MAX_TLSv1_3; @@ -186,7 +201,7 @@ static CURLcode schannel_set_ssl_version_min_max(DWORD *enabled_protocols, break; case CURL_SSLVERSION_TLSv1_3: - /* Windows Server 2022 and newer */ + /* Windows Server 2022 or newer */ if(curlx_verify_windows_version(10, 0, 20348, PLATFORM_WINNT, VERSION_GREATER_THAN_EQUAL)) { *enabled_protocols |= SP_PROT_TLS1_3_CLIENT; @@ -293,9 +308,9 @@ static CURLcode set_ssl_ciphers(SCHANNEL_CRED *schannel_cred, char *ciphers, if(alg) algIds[algCount++] = (ALG_ID)alg; else if(!strncmp(startCur, "USE_STRONG_CRYPTO", - sizeof("USE_STRONG_CRYPTO") - 1) || + CURL_CSTRLEN("USE_STRONG_CRYPTO")) || !strncmp(startCur, "SCH_USE_STRONG_CRYPTO", - sizeof("SCH_USE_STRONG_CRYPTO") - 1)) + CURL_CSTRLEN("SCH_USE_STRONG_CRYPTO"))) schannel_cred->dwFlags |= SCH_USE_STRONG_CRYPTO; else return CURLE_SSL_CIPHER; @@ -316,34 +331,35 @@ static CURLcode get_cert_location(TCHAR *path, DWORD *store_name, TCHAR *store_path_start; size_t store_name_len; - sep = _tcschr(path, TEXT('\\')); + sep = _tcschr(path, _TEXT('\\')); if(!sep) return CURLE_SSL_CERTPROBLEM; store_name_len = sep - path; - if(_tcsncmp(path, TEXT("CurrentUser"), store_name_len) == 0) + if(_tcsncmp(path, _TEXT("CurrentUser"), store_name_len) == 0) *store_name = CERT_SYSTEM_STORE_CURRENT_USER; - else if(_tcsncmp(path, TEXT("LocalMachine"), store_name_len) == 0) + else if(_tcsncmp(path, _TEXT("LocalMachine"), store_name_len) == 0) *store_name = CERT_SYSTEM_STORE_LOCAL_MACHINE; - else if(_tcsncmp(path, TEXT("CurrentService"), store_name_len) == 0) + else if(_tcsncmp(path, _TEXT("CurrentService"), store_name_len) == 0) *store_name = CERT_SYSTEM_STORE_CURRENT_SERVICE; - else if(_tcsncmp(path, TEXT("Services"), store_name_len) == 0) + else if(_tcsncmp(path, _TEXT("Services"), store_name_len) == 0) *store_name = CERT_SYSTEM_STORE_SERVICES; - else if(_tcsncmp(path, TEXT("Users"), store_name_len) == 0) + else if(_tcsncmp(path, _TEXT("Users"), store_name_len) == 0) *store_name = CERT_SYSTEM_STORE_USERS; - else if(_tcsncmp(path, TEXT("CurrentUserGroupPolicy"), store_name_len) == 0) + else if(_tcsncmp(path, _TEXT("CurrentUserGroupPolicy"), store_name_len) == 0) *store_name = CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY; - else if(_tcsncmp(path, TEXT("LocalMachineGroupPolicy"), store_name_len) == 0) + else if(_tcsncmp(path, _TEXT("LocalMachineGroupPolicy"), store_name_len) == + 0) *store_name = CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY; - else if(_tcsncmp(path, TEXT("LocalMachineEnterprise"), store_name_len) == 0) + else if(_tcsncmp(path, _TEXT("LocalMachineEnterprise"), store_name_len) == 0) *store_name = CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE; else return CURLE_SSL_CERTPROBLEM; store_path_start = sep + 1; - sep = _tcschr(store_path_start, TEXT('\\')); + sep = _tcschr(store_path_start, _TEXT('\\')); if(!sep) return CURLE_SSL_CERTPROBLEM; @@ -351,9 +367,9 @@ static CURLcode get_cert_location(TCHAR *path, DWORD *store_name, if(_tcslen(*thumbprint) != CERT_THUMBPRINT_STR_LEN) return CURLE_SSL_CERTPROBLEM; - *sep = TEXT('\0'); + *sep = _TEXT('\0'); *store_path = curlx_tcsdup(store_path_start); - *sep = TEXT('\\'); + *sep = _TEXT('\\'); if(!*store_path) return CURLE_OUT_OF_MEMORY; @@ -431,7 +447,7 @@ static CURLcode get_client_cert(struct Curl_cfilter *cf, if(fInCert || blob) { /* Reading a .p12 or .pfx file, like the example at bottom of https://learn.microsoft.com/archive/msdn-technet-forums/3e7bc95f-b21a-4bcd-bd2c-7f996718cae5 - */ + */ CRYPT_DATA_BLOB datablob; WCHAR *pszPassword; size_t pwd_len = 0; @@ -442,18 +458,18 @@ static CURLcode get_client_cert(struct Curl_cfilter *cf, if(fInCert) { long cert_tell = 0; bool continue_reading = fseek(fInCert, 0, SEEK_END) == 0; - if(continue_reading) + if(continue_reading) { cert_tell = ftell(fInCert); - if(cert_tell < 0) - continue_reading = FALSE; - else - certsize = (size_t)cert_tell; + if(cert_tell < 0) + continue_reading = FALSE; + else + certsize = (size_t)cert_tell; + } if(continue_reading) continue_reading = fseek(fInCert, 0, SEEK_SET) == 0; if(continue_reading && (certsize < CURL_MAX_INPUT_LENGTH)) certdata = curlx_malloc(certsize + 1); - if((!certdata) || - ((int) fread(certdata, certsize, 1, fInCert) != 1)) + if(!certdata || ((int)fread(certdata, certsize, 1, fInCert) != 1)) continue_reading = FALSE; curlx_fclose(fInCert); if(!continue_reading) { @@ -474,10 +490,8 @@ static CURLcode get_client_cert(struct Curl_cfilter *cf, if(pszPassword) { int str_w_len = 0; if(pwd_len > 0) - str_w_len = MultiByteToWideChar(CP_UTF8, - MB_ERR_INVALID_CHARS, - sslc->key_passwd, - (int)pwd_len, + str_w_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, + sslc->key_passwd, (int)pwd_len, pszPassword, (int)(pwd_len + 1)); if((str_w_len >= 0) && (str_w_len <= (int)pwd_len)) @@ -487,6 +501,7 @@ static CURLcode get_client_cert(struct Curl_cfilter *cf, cert_store = PFXImportCertStore(&datablob, pszPassword, PKCS12_NO_PERSIST_KEY); + curlx_memzero(pszPassword, sizeof(WCHAR) * (pwd_len + 1)); curlx_free(pszPassword); } if(!blob) @@ -505,7 +520,7 @@ static CURLcode get_client_cert(struct Curl_cfilter *cf, } /* CERT_FIND_HAS_PRIVATE_KEY is only available in Windows 8 / Server - 2012, (NT v6.2). For earlier versions we use CURL_FIND_ANY. */ + 2012, (NT 6.2). For older versions we use CURL_FIND_ANY. */ if(curlx_verify_windows_version(6, 2, 0, PLATFORM_WINNT, VERSION_GREATER_THAN_EQUAL)) cert_find_flags = CERT_FIND_HAS_PRIVATE_KEY; @@ -594,11 +609,10 @@ static CURLcode acquire_sspi_handle(struct Curl_cfilter *cf, SECURITY_STATUS sspi_status = SEC_E_OK; CURLcode result; - /* We support TLS 1.3 starting in Windows 10 version 1809 (OS build 17763) as - long as the user did not set a legacy algorithm list - (CURLOPT_SSL_CIPHER_LIST). */ + /* We support TLS 1.3 starting in Windows Server 2022 or later + * (OS build 20348) */ if(!conn_config->cipher_list && - curlx_verify_windows_version(10, 0, 17763, PLATFORM_WINNT, + curlx_verify_windows_version(10, 0, 20348, PLATFORM_WINNT, VERSION_GREATER_THAN_EQUAL)) { SCH_CREDENTIALS credentials = { 0 }; @@ -631,8 +645,7 @@ static CURLcode acquire_sspi_handle(struct Curl_cfilter *cf, } sspi_status = - Curl_pSecFn->AcquireCredentialsHandle(NULL, - (TCHAR *)CURL_UNCONST(UNISP_NAME), + Curl_pSecFn->AcquireCredentialsHandle(NULL, CURL_UNCONST(UNISP_NAME), SECPKG_CRED_OUTBOUND, NULL, &credentials, NULL, NULL, &backend->cred->cred_handle, NULL); @@ -681,8 +694,7 @@ static CURLcode acquire_sspi_handle(struct Curl_cfilter *cf, } sspi_status = - Curl_pSecFn->AcquireCredentialsHandle(NULL, - (TCHAR *)CURL_UNCONST(UNISP_NAME), + Curl_pSecFn->AcquireCredentialsHandle(NULL, CURL_UNCONST(UNISP_NAME), SECPKG_CRED_OUTBOUND, NULL, &schannel_cred, NULL, NULL, &backend->cred->cred_handle, NULL); @@ -724,7 +736,7 @@ static CURLcode schannel_acquire_credential_handle(struct Curl_cfilter *cf, DWORD enabled_protocols = 0; struct schannel_ssl_backend_data *backend = - (struct schannel_ssl_backend_data *)(connssl->backend); + (struct schannel_ssl_backend_data *)connssl->backend; DEBUGASSERT(backend); @@ -843,9 +855,7 @@ static CURLcode schannel_connect_step1(struct Curl_cfilter *cf, SecBufferDesc outbuf_desc; SecBuffer inbuf; SecBufferDesc inbuf_desc; -#ifdef HAS_ALPN_SCHANNEL unsigned char alpn_buffer[128]; -#endif SECURITY_STATUS sspi_status = SEC_E_OK; CURLcode result; @@ -853,11 +863,7 @@ static CURLcode schannel_connect_step1(struct Curl_cfilter *cf, DEBUGF(infof(data, "schannel: SSL/TLS connection with %s port %d (step 1/3)", connssl->peer.origin->hostname, connssl->peer.origin->port)); -#ifdef HAS_ALPN_SCHANNEL backend->use_alpn = connssl->alpn && s_win_has_alpn; -#else - backend->use_alpn = FALSE; -#endif if(conn_config->CAfile || conn_config->ca_info_blob) { if(curlx_verify_windows_version(6, 1, 0, PLATFORM_WINNT, @@ -915,7 +921,6 @@ static CURLcode schannel_connect_step1(struct Curl_cfilter *cf, infof(data, "schannel: using IP address, SNI is not supported by OS."); } -#ifdef HAS_ALPN_SCHANNEL if(backend->use_alpn) { int cur = 0; int list_start_index = 0; @@ -925,7 +930,7 @@ static CURLcode schannel_connect_step1(struct Curl_cfilter *cf, /* The first four bytes is an unsigned int indicating number of bytes of data in the rest of the buffer. */ - extension_len = (unsigned int *)(void *)(&alpn_buffer[cur]); + extension_len = (unsigned int *)(void *)&alpn_buffer[cur]; cur += (int)sizeof(unsigned int); /* The next four bytes are an indicator that this buffer contains @@ -936,7 +941,7 @@ static CURLcode schannel_connect_step1(struct Curl_cfilter *cf, /* The next two bytes is an unsigned short indicating the number of bytes used to list the preferred protocols. */ - list_len = (unsigned short *)(void *)(&alpn_buffer[cur]); + list_len = (unsigned short *)(void *)&alpn_buffer[cur]; cur += (int)sizeof(unsigned short); list_start_index = cur; @@ -963,10 +968,6 @@ static CURLcode schannel_connect_step1(struct Curl_cfilter *cf, InitSecBuffer(&inbuf, SECBUFFER_EMPTY, NULL, 0); InitSecBufferDesc(&inbuf_desc, &inbuf, 1); } -#else /* HAS_ALPN_SCHANNEL */ - InitSecBuffer(&inbuf, SECBUFFER_EMPTY, NULL, 0); - InitSecBufferDesc(&inbuf_desc, &inbuf, 1); -#endif /* setup output buffer */ InitSecBuffer(&outbuf, SECBUFFER_EMPTY, NULL, 0); @@ -993,7 +994,7 @@ static CURLcode schannel_connect_step1(struct Curl_cfilter *cf, At the moment we do not pass inbuf unless we are using ALPN since we only use it for that, and WINE (for which we currently disable ALPN) is giving us problems with inbuf regardless. https://github.com/curl/curl/issues/983 - */ + */ sspi_status = Curl_pSecFn->InitializeSecurityContext( &backend->cred->cred_handle, NULL, backend->cred->sni_hostname, backend->req_flags, 0, 0, @@ -1373,7 +1374,7 @@ static CURLcode schannel_connect_step2(struct Curl_cfilter *cf, /* check if the handshake needs to be continued */ result = CURLE_OK; for(i = 0; i < 3; i++) { - /* search for handshake tokens that need to be send */ + /* search for handshake tokens that need to be sent */ if(outbuf[i].BufferType == SECBUFFER_TOKEN && outbuf[i].cbBuffer > 0) { size_t written = 0; DEBUGF(infof(data, "schannel: sending next handshake data: " @@ -1422,16 +1423,15 @@ static CURLcode schannel_connect_step2(struct Curl_cfilter *cf, if(inbuf[1].BufferType == SECBUFFER_EXTRA && inbuf[1].cbBuffer > 0) { SCH_DEV(infof(data, "schannel: encrypted data length: %lu", inbuf[1].cbBuffer)); - /* - There are two cases where we could be getting extra data here: - 1. If we are renegotiating a connection and the handshake is already - complete (from the server perspective), it can encrypted app data - (not handshake data) in an extra buffer at this point. - 2. (sspi_status == SEC_I_CONTINUE_NEEDED) We are negotiating a - connection and this extra data is part of the handshake. - We should process the data immediately; waiting for the socket to - be ready may fail since the server is done sending handshake data. - */ + /* There are two cases where we could be getting extra data here: + 1. If we are renegotiating a connection and the handshake is already + complete (from the server perspective), it can encrypt app data + (not handshake data) in an extra buffer at this point. + 2. (sspi_status == SEC_I_CONTINUE_NEEDED) We are negotiating a + connection and this extra data is part of the handshake. + We should process the data immediately; waiting for the socket to + be ready may fail since the server is done sending handshake data. + */ /* check if the remaining data is less than the total amount and therefore begins after the already processed data */ if(backend->encdata.offset > inbuf[1].cbBuffer) { @@ -1465,10 +1465,10 @@ static CURLcode schannel_connect_step2(struct Curl_cfilter *cf, #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]; + CURL_EASY_STR(data, STRING_SSL_PINNEDPUBLICKEY_PROXY) : + CURL_EASY_STR(data, STRING_SSL_PINNEDPUBLICKEY); #else - pubkey_ptr = data->set.str[STRING_SSL_PINNEDPUBLICKEY]; + pubkey_ptr = CURL_EASY_STR(data, STRING_SSL_PINNEDPUBLICKEY); #endif if(pubkey_ptr) { result = schannel_pkp_pin_peer_pubkey(cf, data, pubkey_ptr); @@ -1592,11 +1592,9 @@ static CURLcode schannel_connect_step3(struct Curl_cfilter *cf, CURLcode result = CURLE_OK; SECURITY_STATUS sspi_status = SEC_E_OK; CERT_CONTEXT *ccert_context = NULL; -#ifdef HAS_ALPN_SCHANNEL SecPkgContext_ApplicationProtocol alpn_result; -#endif - DEBUGASSERT(ssl_connect_3 == connssl->connecting_state); + DEBUGASSERT(connssl->connecting_state == ssl_connect_3); DEBUGASSERT(backend); DEBUGF(infof(data, "schannel: SSL/TLS connection with %s port %d (step 3/3)", @@ -1620,7 +1618,6 @@ static CURLcode schannel_connect_step3(struct Curl_cfilter *cf, return CURLE_SSL_CONNECT_ERROR; } -#ifdef HAS_ALPN_SCHANNEL if(backend->use_alpn) { sspi_status = Curl_pSecFn->QueryContextAttributes(&backend->ctxt->ctxt_handle, @@ -1652,7 +1649,6 @@ static CURLcode schannel_connect_step3(struct Curl_cfilter *cf, Curl_alpn_set_negotiated(cf, data, connssl, NULL, 0); } } -#endif /* save the current session data for possible reuse */ if(Curl_ssl_scache_use(cf, data)) { @@ -1721,40 +1717,45 @@ static CURLcode schannel_connect(struct Curl_cfilter *cf, *done = FALSE; - if(ssl_connect_1 == connssl->connecting_state) { + if(connssl->connecting_state == ssl_connect_1) { result = schannel_connect_step1(cf, data); if(result) return result; } - if(ssl_connect_2 == connssl->connecting_state) { + if(connssl->connecting_state == ssl_connect_2) { result = schannel_connect_step2(cf, data); if(result) return result; } - if(ssl_connect_3 == connssl->connecting_state) { + if(connssl->connecting_state == ssl_connect_3) { result = schannel_connect_step3(cf, data); if(result) return result; } - if(ssl_connect_done == connssl->connecting_state) { + if(connssl->connecting_state == ssl_connect_done) { + struct schannel_ssl_backend_data *backend = + (struct schannel_ssl_backend_data *)connssl->backend; + DEBUGASSERT(backend); + + if(Curl_pSecFn->QueryContextAttributes( + &backend->ctxt->ctxt_handle, + SECPKG_ATTR_STREAM_SIZES, + &backend->stream_sizes)) { + failf(data, "schannel: failed getting stream sizes"); + return CURLE_SSL_CONNECT_ERROR; + } + connssl->state = ssl_connection_complete; -#ifdef SECPKG_ATTR_ENDPOINT_BINDINGS /* mingw-w64 v9+, MS SDK 7.0A/VS2010+ */ /* When SSPI is used in combination with Schannel * we need the Schannel context to create the Schannel * binding to pass the IIS extended protection checks. * Available on Windows 7 or later. */ - { - struct schannel_ssl_backend_data *backend = - (struct schannel_ssl_backend_data *)connssl->backend; - DEBUGASSERT(backend); - cf->conn->sslContext = &backend->ctxt->ctxt_handle; - } -#endif + cf->conn->sslContext = &backend->ctxt->ctxt_handle; *done = TRUE; } @@ -1985,29 +1986,23 @@ static CURLcode schannel_send(struct Curl_cfilter *cf, struct Curl_easy *data, return result; } - /* check if the maximum stream sizes were queried */ - if(backend->stream_sizes.cbMaximumMessage == 0) { - sspi_status = Curl_pSecFn->QueryContextAttributes( - &backend->ctxt->ctxt_handle, - SECPKG_ATTR_STREAM_SIZES, - &backend->stream_sizes); - if(sspi_status != SEC_E_OK) { - return CURLE_SEND_ERROR; - } - } - /* check if the buffer is longer than the maximum message length */ if(len > backend->stream_sizes.cbMaximumMessage) { len = backend->stream_sizes.cbMaximumMessage; } - /* calculate the complete message length and allocate a buffer for it */ + /* calculate the complete message length and prepare the send buffer */ data_len = backend->stream_sizes.cbHeader + len + backend->stream_sizes.cbTrailer; - ptr = curlx_malloc(data_len); - if(!ptr) { - return CURLE_OUT_OF_MEMORY; + if(data_len > backend->send_buffer_len) { + ptr = curlx_realloc(backend->send_buffer, data_len); + if(!ptr) + return CURLE_OUT_OF_MEMORY; + backend->send_buffer = ptr; + backend->send_buffer_len = data_len; } + else + ptr = backend->send_buffer; /* setup output buffers (header, data, trailer, empty) */ InitSecBuffer(&outbuf[0], SECBUFFER_STREAM_HEADER, @@ -2033,21 +2028,19 @@ static CURLcode schannel_send(struct Curl_cfilter *cf, struct Curl_easy *data, /* send the encrypted message including header, data and trailer */ len = outbuf[0].cbBuffer + outbuf[1].cbBuffer + outbuf[2].cbBuffer; - /* - it is important to send the full message which includes the header, - encrypted payload, and trailer. Until the client receives all the - data a coherent message has not been delivered and the client - cannot read any of it. + /* it is important to send the full message which includes the header, + encrypted payload, and trailer. Until the client receives all the + data a coherent message has not been delivered and the client + cannot read any of it. - If we wanted to buffer the unwritten encrypted bytes, we would - tell the client that all data it has requested to be sent has been - sent. The unwritten encrypted bytes would be the first bytes to - send on the next invocation. - Here's the catch with this - if we tell the client that all the - bytes have been sent, does the client call this method again to - send the buffered data? Looking at who calls this function, it - seems the answer is NO. - */ + If we wanted to buffer the unwritten encrypted bytes, we would + tell the client that all data it has requested to be sent has been + sent. The unwritten encrypted bytes would be the first bytes to + send on the next invocation. + Here's the catch with this - if we tell the client that all the + bytes have been sent, does the client call this method again to + send the buffered data? Looking at who calls this function, it + seems the answer is NO. */ /* send entire message or fail */ while(len > *pnwritten) { @@ -2098,8 +2091,6 @@ static CURLcode schannel_send(struct Curl_cfilter *cf, struct Curl_easy *data, result = CURLE_SEND_ERROR; } - curlx_safefree(ptr); - if(len == *pnwritten) /* Encrypted message including header, data and trailer entirely sent. The return value is the number of unencrypted bytes that were sent. */ @@ -2165,8 +2156,7 @@ static CURLcode schannel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, } /* it is debatable what to return when !len. Regardless we cannot return immediately because there may be data to decrypt (in the case we want to - decrypt all encrypted cached data) so handle !len later in cleanup. - */ + decrypt all encrypted cached data) so handle !len later in cleanup. */ else if(len && !backend->recv_connection_closed) { /* the encrypted buffer must be large enough to hold all the bytes requested and some TLS record overhead. 'len' is a buffer size, so this @@ -2350,8 +2340,7 @@ cleanup: The behavior here is a matter of debate. We do not want to be vulnerable to a truncation attack however there is some browser precedent for - ignoring the close_notify for compatibility reasons. - */ + ignoring the close_notify for compatibility reasons. */ if(len && !backend->decdata.offset && backend->recv_connection_closed && !backend->recv_sspi_close_notify) { result = CURLE_RECV_ERROR; @@ -2381,8 +2370,7 @@ cleanup: /* it is debatable what to return when !len. We could return whatever error we got from decryption but instead we override here so the return is - consistent. - */ + consistent. */ if(!len) return CURLE_OK; @@ -2577,6 +2565,10 @@ static void schannel_close(struct Curl_cfilter *cf, struct Curl_easy *data) backend->cred = NULL; } + /* free the buffer used to encrypt outgoing data */ + curlx_safefree(backend->send_buffer); + backend->send_buffer_len = 0; + /* free internal buffer for received encrypted data */ if(backend->encdata.buffer) { curlx_safefree(backend->encdata.buffer); @@ -2595,7 +2587,6 @@ static void schannel_close(struct Curl_cfilter *cf, struct Curl_easy *data) static int schannel_init(void) { -#ifdef HAS_ALPN_SCHANNEL typedef const char *(APIENTRY *WINE_GET_VERSION_FN)(void); #if defined(__clang__) && __clang_major__ >= 16 #pragma clang diagnostic push @@ -2610,17 +2601,16 @@ static int schannel_init(void) if(p_wine_get_version) { /* WINE detected */ curl_off_t ver = 0; const char *wine_version = p_wine_get_version(); /* e.g. "6.0.2" */ - /* Assume ALPN support with WINE 6.0 or upper */ + /* Assume ALPN support with WINE 6.0 or greater */ if(wine_version) curlx_str_number(&wine_version, &ver, 20); s_win_has_alpn = (ver >= 6); } else { - /* ALPN is supported on Windows 8.1 / Server 2012 R2 and above. */ + /* ALPN is supported on Windows 8.1 / Server 2012 R2 or newer. */ s_win_has_alpn = curlx_verify_windows_version(6, 3, 0, PLATFORM_WINNT, VERSION_GREATER_THAN_EQUAL); } -#endif /* HAS_ALPN_SCHANNEL */ return Curl_sspi_global_init() == CURLE_OK ? 1 : 0; } @@ -2640,7 +2630,7 @@ static CURLcode schannel_random(struct Curl_easy *data, { (void)data; - return Curl_win32_random(entropy, length); + return curlx_win32_random(entropy, length); } static CURLcode schannel_checksum(const unsigned char *input, @@ -2747,7 +2737,7 @@ HCERTSTORE Curl_schannel_get_cached_cert_store(struct Curl_cfilter *cf, share = Curl_hash_pick(&multi->proto_hash, CURL_UNCONST(MPROTO_SCHANNEL_CERT_SHARE_KEY), - sizeof(MPROTO_SCHANNEL_CERT_SHARE_KEY) - 1); + CURL_CSTRLEN(MPROTO_SCHANNEL_CERT_SHARE_KEY)); if(!share || !share->cert_store) { return NULL; } @@ -2796,7 +2786,7 @@ HCERTSTORE Curl_schannel_get_cached_cert_store(struct Curl_cfilter *cf, static void schannel_cert_share_free(void *key, size_t key_len, void *p) { struct schannel_cert_share *share = p; - DEBUGASSERT(key_len == (sizeof(MPROTO_SCHANNEL_CERT_SHARE_KEY) - 1)); + DEBUGASSERT(key_len == CURL_CSTRLEN(MPROTO_SCHANNEL_CERT_SHARE_KEY)); DEBUGASSERT(!memcmp(MPROTO_SCHANNEL_CERT_SHARE_KEY, key, key_len)); (void)key; (void)key_len; @@ -2839,7 +2829,7 @@ bool Curl_schannel_set_cached_cert_store(struct Curl_cfilter *cf, share = Curl_hash_pick(&multi->proto_hash, CURL_UNCONST(MPROTO_SCHANNEL_CERT_SHARE_KEY), - sizeof(MPROTO_SCHANNEL_CERT_SHARE_KEY) - 1); + CURL_CSTRLEN(MPROTO_SCHANNEL_CERT_SHARE_KEY)); if(!share) { share = curlx_calloc(1, sizeof(*share)); if(!share) { @@ -2848,7 +2838,7 @@ bool Curl_schannel_set_cached_cert_store(struct Curl_cfilter *cf, } if(!Curl_hash_add2(&multi->proto_hash, CURL_UNCONST(MPROTO_SCHANNEL_CERT_SHARE_KEY), - sizeof(MPROTO_SCHANNEL_CERT_SHARE_KEY) - 1, + CURL_CSTRLEN(MPROTO_SCHANNEL_CERT_SHARE_KEY), share, schannel_cert_share_free)) { curlx_free(share); curlx_free(CAfile); diff --git a/Utilities/cmcurl/lib/vtls/schannel_int.h b/Utilities/cmcurl/lib/vtls/schannel_int.h index b65ff79926..ccbb63d7e2 100644 --- a/Utilities/cmcurl/lib/vtls/schannel_int.h +++ b/Utilities/cmcurl/lib/vtls/schannel_int.h @@ -112,6 +112,8 @@ struct sbuffer { }; struct schannel_ssl_backend_data { + unsigned char *send_buffer; + size_t send_buffer_len; struct sbuffer encdata; struct sbuffer decdata; struct Curl_schannel_cred *cred; @@ -147,9 +149,7 @@ struct schannel_cert_share { struct curltime time; /* when the cached store was created */ }; -/* -* size of the structure: 20 bytes. -*/ +/* size of the structure: 20 bytes */ struct num_ip_data { DWORD size; /* 04 bytes */ union { diff --git a/Utilities/cmcurl/lib/vtls/schannel_verify.c b/Utilities/cmcurl/lib/vtls/schannel_verify.c index e5fe2249a6..6e55c9b839 100644 --- a/Utilities/cmcurl/lib/vtls/schannel_verify.c +++ b/Utilities/cmcurl/lib/vtls/schannel_verify.c @@ -119,8 +119,8 @@ static CURLcode add_certs_data_to_store(HCERTSTORE trust_store, const char *ca_file_text, struct Curl_easy *data) { - const size_t begin_cert_len = strlen(BEGIN_CERT); - const size_t end_cert_len = strlen(END_CERT); + const size_t begin_cert_len = CURL_CSTRLEN(BEGIN_CERT); + const size_t end_cert_len = CURL_CSTRLEN(END_CERT); CURLcode result = CURLE_OK; int num_certs = 0; bool more_certs = 1; diff --git a/Utilities/cmcurl/lib/vtls/vtls.c b/Utilities/cmcurl/lib/vtls/vtls.c index 82ce007b3d..f6cc370890 100644 --- a/Utilities/cmcurl/lib/vtls/vtls.c +++ b/Utilities/cmcurl/lib/vtls/vtls.c @@ -36,14 +36,10 @@ "SSL/TLS Strong Encryption: An Introduction" https://httpd.apache.org/docs/2.0/ssl/ssl_intro.html -*/ + */ #include "curl_setup.h" -#ifdef HAVE_SYS_TYPES_H -#include -#endif - #include "urldata.h" #include "cfilters.h" @@ -71,6 +67,7 @@ #include "connect.h" #include "select.h" #include "setopt.h" +#include "vdns/cf-dns.h" #include "curlx/strdup.h" #include "curlx/strcopy.h" @@ -218,7 +215,7 @@ static void cf_ctx_free(struct ssl_connect_data *ctx) } } -CURLcode Curl_ssl_get_channel_binding(struct Curl_easy *data, int sockindex, +CURLcode Curl_ssl_get_channel_binding(struct Curl_easy *data, int8_t sockindex, struct dynbuf *binding) { if(Curl_ssl->get_channel_binding) @@ -486,8 +483,8 @@ CURLcode Curl_pin_peer_pubkey(struct Curl_easy *data, pinned_hash = pinnedpubkey; while(pinned_hash && - !strncmp(pinned_hash, "sha256//", (sizeof("sha256//") - 1))) { - pinned_hash = pinned_hash + (sizeof("sha256//") - 1); + !strncmp(pinned_hash, "sha256//", CURL_CSTRLEN("sha256//"))) { + pinned_hash = pinned_hash + CURL_CSTRLEN("sha256//"); end_pos = strchr(pinned_hash, ';'); pinned_hash_len = end_pos ? (size_t)(end_pos - pinned_hash) : strlen(pinned_hash); @@ -544,7 +541,7 @@ CURLcode Curl_pin_peer_pubkey(struct Curl_easy *data, do { char buffer[1024]; size_t want = left > sizeof(buffer) ? sizeof(buffer) : left; - if(want != fread(buffer, 1, want, fp)) + if(fread(buffer, 1, want, fp) != want) goto end; if(curlx_dyn_addn(&buf, buffer, want)) goto end; @@ -877,19 +874,6 @@ void Curl_ssl_peer_cleanup(struct ssl_peer *peer) peer->type = CURL_SSL_PEER_DNS; } -static void cf_close(struct Curl_cfilter *cf, struct Curl_easy *data) -{ - struct ssl_connect_data *connssl = cf->ctx; - if(connssl) { - connssl->ssl_impl->close(cf, data); - connssl->state = ssl_connection_none; - connssl->connecting_state = ssl_connect_1; - connssl->prefs_checked = FALSE; - Curl_ssl_peer_cleanup(&connssl->peer); - } - cf->connected = FALSE; -} - static ssl_peer_type get_peer_type(const char *hostname) { if(hostname && hostname[0]) { @@ -957,13 +941,18 @@ out: static void ssl_cf_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) { - struct cf_call_data save; - - CF_DATA_SAVE(save, cf, data); - cf_close(cf, data); - CF_DATA_RESTORE(cf, save); - cf_ctx_free(cf->ctx); - cf->ctx = NULL; + struct ssl_connect_data *connssl = cf->ctx; + if(connssl) { + connssl->ssl_impl->close(cf, data); + connssl->state = ssl_connection_none; + connssl->connecting_state = ssl_connect_1; + connssl->prefs_checked = FALSE; + Curl_ssl_peer_cleanup(&connssl->peer); + Curl_ssl_session_destroy(connssl->session); + cf_ctx_free(connssl); + cf->ctx = NULL; + } + cf->connected = FALSE; } static CURLcode ssl_cf_connect(struct Curl_cfilter *cf, @@ -1075,7 +1064,10 @@ static CURLcode ssl_cf_connect_deferred(struct Curl_cfilter *cf, result = ssl_cf_connect(cf, data, done); if(!result && *done) { - Curl_pgrsTimeWas(data, TIMER_APPCONNECT, connssl->handshake_done); + if(!connssl->stats_reported && (cf->cft == &Curl_cft_ssl)) { + Curl_pgrsTimeWas(data, TIMER_APPCONNECT, connssl->handshake_done); + connssl->stats_reported = TRUE; + } switch(connssl->earlydata_state) { case ssl_earlydata_none: break; @@ -1243,12 +1235,6 @@ static CURLcode ssl_cf_query(struct Curl_cfilter *cf, struct ssl_connect_data *connssl = cf->ctx; switch(query) { - case CF_QUERY_TIMER_APPCONNECT: { - struct curltime *when = pres2; - if(cf->connected && !Curl_ssl_cf_is_proxy(cf)) - *when = connssl->handshake_done; - return CURLE_OK; - } case CF_QUERY_SSL_INFO: case CF_QUERY_SSL_CTX_INFO: if(!Curl_ssl_cf_is_proxy(cf)) { @@ -1298,6 +1284,14 @@ static CURLcode ssl_cf_cntrl(struct Curl_cfilter *cf, cf->conn->httpversion_seen = 30; } break; + case CF_CTRL_REPORT_STATS: + if(cf->connected && !connssl->stats_reported && + (cf->cft == &Curl_cft_ssl) && + (connssl->handshake_done.tv_sec || connssl->handshake_done.tv_usec)) { + Curl_pgrsTimeWas(data, TIMER_APPCONNECT, connssl->handshake_done); + connssl->stats_reported = TRUE; + } + break; } return CURLE_OK; } @@ -1401,7 +1395,7 @@ static CURLcode cf_ssl_peer_init(struct Curl_cfilter *cf, CURLcode Curl_ssl_cfilter_add(struct Curl_easy *data, struct Curl_peer *origin, struct connectdata *conn, - int sockindex) + int8_t sockindex) { struct Curl_cfilter *cf; struct Curl_peer *peer = (sockindex == SECONDARYSOCKET) ? @@ -1429,8 +1423,20 @@ CURLcode Curl_cf_ssl_insert_after(struct Curl_cfilter *cf_at, result = cf_ssl_create(&cf, data, cf_at->conn); if(!result) result = cf_ssl_peer_init(cf, origin, peer, &cf_at->conn->ssl_config); - if(!result) + if(!result) { Curl_conn_cf_insert_after(cf_at, cf); +#if defined(USE_HTTPSRR) && defined(USE_ECH) + /* When using ECH, kick off the HTTPS-RR resolve */ + if((origin->scheme->family == CURLPROTO_HTTP) && + CURLECH_ENABLED(data) && + Curl_ssl_supports(data, SSLSUPP_ECH) && + (data->set.tls_ech != CURLECH_GREASE) && + !CURL_EASY_STR(data, STRING_ECH_CONFIG)) { + result = Curl_conn_dns_add_https_resolve(data, cf->conn, cf->sockindex, + origin); + } +#endif /* USE_HTTPSRR && USE_ECH */ + } else if(cf) Curl_conn_cf_discard_chain(&cf, data); return result; @@ -1551,7 +1557,7 @@ out: } CURLcode Curl_ssl_cfilter_remove(struct Curl_easy *data, - int sockindex, bool send_shutdown) + int8_t sockindex, bool send_shutdown) { struct Curl_cfilter *cf, *head; CURLcode result = CURLE_OK; @@ -1776,4 +1782,30 @@ CURLcode Curl_on_session_reuse(struct Curl_cfilter *cf, return result; } +struct Curl_ssl_session *Curl_ssl_get_cf_session(struct Curl_easy *data, + const struct Curl_cftype *cft, + int8_t sockindex) +{ + if(data->conn && +#ifndef CURL_DISABLE_PROXY + ((cft == &Curl_cft_ssl) || (cft == &Curl_cft_ssl_proxy))) { +#else + (cft == &Curl_cft_ssl)) { +#endif + struct Curl_cfilter *cf1 = data->conn->cfilter[sockindex]; + for(; cf1; cf1 = cf1->next) { + /* A tunneling proxy does not offer end2end encryption, even if + * it does SSL itself (e.g. QUIC H3 proxy) */ + if(cf1->cft == cft) + break; + } + if(cf1) { + struct ssl_connect_data *connssl = cf1->ctx; + if(connssl) + return connssl->session; + } + } + return NULL; +} + #endif /* USE_SSL */ diff --git a/Utilities/cmcurl/lib/vtls/vtls.h b/Utilities/cmcurl/lib/vtls/vtls.h index 96cb7d15ed..8ee22c8d5a 100644 --- a/Utilities/cmcurl/lib/vtls/vtls.h +++ b/Utilities/cmcurl/lib/vtls/vtls.h @@ -171,7 +171,7 @@ bool Curl_ssl_cert_status_request(void); * If channel binding is not supported, binding stays empty and CURLE_OK is * returned. */ -CURLcode Curl_ssl_get_channel_binding(struct Curl_easy *data, int sockindex, +CURLcode Curl_ssl_get_channel_binding(struct Curl_easy *data, int8_t sockindex, struct dynbuf *binding); #define SSL_SHUTDOWN_TIMEOUT 10000 /* ms */ @@ -179,7 +179,7 @@ CURLcode Curl_ssl_get_channel_binding(struct Curl_easy *data, int sockindex, CURLcode Curl_ssl_cfilter_add(struct Curl_easy *data, struct Curl_peer *origin, struct connectdata *conn, - int sockindex); + int8_t sockindex); CURLcode Curl_cf_ssl_insert_after(struct Curl_cfilter *cf_at, struct Curl_easy *data, @@ -187,7 +187,7 @@ CURLcode Curl_cf_ssl_insert_after(struct Curl_cfilter *cf_at, struct Curl_peer *peer); CURLcode Curl_ssl_cfilter_remove(struct Curl_easy *data, - int sockindex, bool send_shutdown); + int8_t sockindex, bool send_shutdown); #ifndef CURL_DISABLE_PROXY CURLcode Curl_cf_ssl_proxy_insert_after(struct Curl_cfilter *cf_at, diff --git a/Utilities/cmcurl/lib/vtls/vtls_config.c b/Utilities/cmcurl/lib/vtls/vtls_config.c index 4a9b69654b..080d513bb5 100644 --- a/Utilities/cmcurl/lib/vtls/vtls_config.c +++ b/Utilities/cmcurl/lib/vtls/vtls_config.c @@ -36,14 +36,10 @@ "SSL/TLS Strong Encryption: An Introduction" https://httpd.apache.org/docs/2.0/ssl/ssl_intro.html -*/ + */ #include "curl_setup.h" -#ifdef HAVE_SYS_TYPES_H -#include -#endif - #include "urldata.h" #include "setopt.h" #include "strcase.h" @@ -133,10 +129,6 @@ void Curl_ssl_config_cleanup(struct ssl_primary_config *sslc) curlx_safefree(sslc->key); curlx_safefree(sslc->key_type); curlx_safefree(sslc->key_passwd); -#ifdef USE_TLS_SRP - curlx_safefree(sslc->username); - curlx_safefree(sslc->password); -#endif sslc->deep_copy = FALSE; } } @@ -149,6 +141,7 @@ static bool match_ssl_primary_config(struct Curl_easy *data, if((c1->version == c2->version) && (c1->version_max == c2->version_max) && (c1->ssl_options == c2->ssl_options) && + (c1->native_ca_store == c2->native_ca_store) && (c1->verifypeer == c2->verifypeer) && (c1->verifyhost == c2->verifyhost) && (c1->verifystatus == c2->verifystatus) && @@ -160,10 +153,6 @@ static bool match_ssl_primary_config(struct Curl_easy *data, Curl_safecmp(c1->CAfile, c2->CAfile) && Curl_safecmp(c1->issuercert, c2->issuercert) && Curl_safecmp(c1->clientcert, c2->clientcert) && -#ifdef USE_TLS_SRP - !Curl_timestrcmp(c1->username, c2->username) && - !Curl_timestrcmp(c1->password, c2->password) && -#endif curl_strequal(c1->cipher_list, c2->cipher_list) && curl_strequal(c1->cipher_list13, c2->cipher_list13) && curl_strequal(c1->curves, c2->curves) && @@ -204,6 +193,7 @@ static bool clone_ssl_primary_config(struct ssl_primary_config *source, dest->verifypeer = source->verifypeer; dest->verifyhost = source->verifyhost; dest->verifystatus = source->verifystatus; + dest->native_ca_store = source->native_ca_store; dest->cache_session = source->cache_session; dest->ssl_options = source->ssl_options; @@ -219,18 +209,13 @@ static bool clone_ssl_primary_config(struct ssl_primary_config *source, CLONE_STRING(curves); CLONE_STRING(signature_algorithms); CLONE_STRING(CRLfile); - /* SSL credentials: client certificate, SRP auth */ + /* SSL credentials: client certificate */ CLONE_STRING(clientcert); CLONE_STRING(cert_type); CLONE_STRING(key); CLONE_STRING(key_type); CLONE_STRING(key_passwd); CLONE_BLOB(key_blob); -#ifdef USE_TLS_SRP - CLONE_STRING(username); - CLONE_STRING(password); -#endif - return TRUE; } @@ -242,7 +227,7 @@ static void ssl_easy_config_compl_options(struct Curl_peer *origin, /* If set via CURLOPT_(PROXY_)SSL_OPTIONS, we definitely use it. * If not, we switch it on for supported backends if no custom * CA settings exist. */ - sslc->native_ca_store = !!(options & CURLSSLOPT_NATIVE_CA); + sslc->primary.native_ca_store = !!(options & CURLSSLOPT_NATIVE_CA); sslc->enable_beast = !!(options & CURLSSLOPT_ALLOW_BEAST); sslc->no_partialchain = !!(options & CURLSSLOPT_NO_PARTIALCHAIN); sslc->no_revoke = !!(options & CURLSSLOPT_NO_REVOKE); @@ -253,12 +238,18 @@ static void ssl_easy_config_compl_options(struct Curl_peer *origin, !!(options & CURLSSLOPT_AUTO_CLIENT_CERT); } +static char *ssl_easy_steal(struct Curl_easy *data, enum dupstring id) +{ + /* For connection matching, we borrow string references from data + * THIS IS NOT REALLY NICE. */ + return CURL_UNCONST(CURL_EASY_STR(data, id)); +} + CURLcode Curl_ssl_easy_config_complete(struct Curl_easy *data, struct Curl_peer *origin) { struct ssl_config_data *sslc = &data->set.ssl; #if defined(CURL_CA_PATH) || defined(CURL_CA_BUNDLE) - struct UserDefined *set = &data->set; CURLcode result; #endif @@ -267,50 +258,47 @@ CURLcode Curl_ssl_easy_config_complete(struct Curl_easy *data, if(Curl_ssl_backend() != CURLSSLBACKEND_SCHANNEL) { #if defined(USE_APPLE_SECTRUST) || defined(CURL_CA_NATIVE) if(!sslc->custom_capath && !sslc->custom_cafile && !sslc->custom_cablob) - sslc->native_ca_store = TRUE; + sslc->primary.native_ca_store = TRUE; #endif #ifdef CURL_CA_PATH - if(!sslc->custom_capath && !set->str[STRING_SSL_CAPATH]) { - result = Curl_setstropt(&set->str[STRING_SSL_CAPATH], CURL_CA_PATH); + if(!sslc->custom_capath && !CURL_EASY_STR(data, STRING_SSL_CAPATH)) { + result = Curl_setstropt(data, STRING_SSL_CAPATH, CURL_CA_PATH); if(result) return result; } #endif #ifdef CURL_CA_BUNDLE - if(!sslc->custom_cafile && !set->str[STRING_SSL_CAFILE]) { - result = Curl_setstropt(&set->str[STRING_SSL_CAFILE], CURL_CA_BUNDLE); + if(!sslc->custom_cafile && !CURL_EASY_STR(data, STRING_SSL_CAFILE)) { + result = Curl_setstropt(data, STRING_SSL_CAFILE, CURL_CA_BUNDLE); if(result) return result; } #endif } - sslc->primary.CAfile = data->set.str[STRING_SSL_CAFILE]; - sslc->primary.CRLfile = data->set.str[STRING_SSL_CRLFILE]; - sslc->primary.CApath = data->set.str[STRING_SSL_CAPATH]; - sslc->primary.cipher_list = data->set.str[STRING_SSL_CIPHER_LIST]; - sslc->primary.cipher_list13 = data->set.str[STRING_SSL_CIPHER13_LIST]; + sslc->primary.CAfile = ssl_easy_steal(data, STRING_SSL_CAFILE); + sslc->primary.CRLfile = ssl_easy_steal(data, STRING_SSL_CRLFILE); + sslc->primary.CApath = ssl_easy_steal(data, STRING_SSL_CAPATH); + sslc->primary.cipher_list = ssl_easy_steal(data, STRING_SSL_CIPHER_LIST); + sslc->primary.cipher_list13 = ssl_easy_steal(data, STRING_SSL_CIPHER13_LIST); sslc->primary.signature_algorithms = - data->set.str[STRING_SSL_SIGNATURE_ALGORITHMS]; + ssl_easy_steal(data, STRING_SSL_SIGNATURE_ALGORITHMS); sslc->primary.ca_info_blob = data->set.blobs[BLOB_CAINFO]; - sslc->primary.curves = data->set.str[STRING_SSL_EC_CURVES]; + sslc->primary.curves = ssl_easy_steal(data, STRING_SSL_EC_CURVES); /* Maybe these should not be used for another origin. But for * backwards compatibility, keep them in. */ - sslc->primary.issuercert = data->set.str[STRING_SSL_ISSUERCERT]; + sslc->primary.issuercert = ssl_easy_steal(data, STRING_SSL_ISSUERCERT); sslc->primary.issuercert_blob = data->set.blobs[BLOB_SSL_ISSUERCERT]; if(Curl_peer_equal(data->state.initial_origin, origin)) { - sslc->primary.pinned_key = data->set.str[STRING_SSL_PINNEDPUBLICKEY]; + sslc->primary.pinned_key = + ssl_easy_steal(data, STRING_SSL_PINNEDPUBLICKEY); sslc->primary.cert_blob = data->set.blobs[BLOB_CERT]; - sslc->primary.cert_type = data->set.str[STRING_CERT_TYPE]; - sslc->primary.key = data->set.str[STRING_KEY]; - sslc->primary.key_type = data->set.str[STRING_KEY_TYPE]; - sslc->primary.key_passwd = data->set.str[STRING_KEY_PASSWD]; - sslc->primary.clientcert = data->set.str[STRING_CERT]; + sslc->primary.cert_type = ssl_easy_steal(data, STRING_CERT_TYPE); + sslc->primary.key = ssl_easy_steal(data, STRING_KEY); + sslc->primary.key_type = ssl_easy_steal(data, STRING_KEY_TYPE); + sslc->primary.key_passwd = ssl_easy_steal(data, STRING_KEY_PASSWD); + sslc->primary.clientcert = ssl_easy_steal(data, STRING_CERT); sslc->primary.key_blob = data->set.blobs[BLOB_KEY]; -#ifdef USE_TLS_SRP - sslc->primary.username = data->set.str[STRING_TLSAUTH_USERNAME]; - sslc->primary.password = data->set.str[STRING_TLSAUTH_PASSWORD]; -#endif } else { sslc->primary.pinned_key = NULL; @@ -321,10 +309,6 @@ CURLcode Curl_ssl_easy_config_complete(struct Curl_easy *data, sslc->primary.key_passwd = NULL; sslc->primary.clientcert = NULL; sslc->primary.key_blob = NULL; -#ifdef USE_TLS_SRP - sslc->primary.username = NULL; - sslc->primary.password = NULL; -#endif } #ifndef CURL_DISABLE_PROXY @@ -335,45 +319,44 @@ CURLcode Curl_ssl_easy_config_complete(struct Curl_easy *data, if(Curl_ssl_backend() != CURLSSLBACKEND_SCHANNEL) { #if defined(USE_APPLE_SECTRUST) || defined(CURL_CA_NATIVE) if(!sslc->custom_capath && !sslc->custom_cafile && !sslc->custom_cablob) - sslc->native_ca_store = TRUE; + sslc->primary.native_ca_store = TRUE; #endif #ifdef CURL_CA_PATH - if(!sslc->custom_capath && !set->str[STRING_SSL_CAPATH_PROXY]) { - result = Curl_setstropt(&set->str[STRING_SSL_CAPATH_PROXY], - CURL_CA_PATH); + if(!sslc->custom_capath && + !CURL_EASY_STR(data, STRING_SSL_CAPATH_PROXY)) { + result = Curl_setstropt(data, STRING_SSL_CAPATH_PROXY, CURL_CA_PATH); if(result) return result; } #endif #ifdef CURL_CA_BUNDLE - if(!sslc->custom_cafile && !set->str[STRING_SSL_CAFILE_PROXY]) { - result = Curl_setstropt(&set->str[STRING_SSL_CAFILE_PROXY], - CURL_CA_BUNDLE); + if(!sslc->custom_cafile && + !CURL_EASY_STR(data, STRING_SSL_CAFILE_PROXY)) { + result = Curl_setstropt(data, STRING_SSL_CAFILE_PROXY, CURL_CA_BUNDLE); if(result) return result; } #endif } - sslc->primary.CAfile = data->set.str[STRING_SSL_CAFILE_PROXY]; - sslc->primary.CApath = data->set.str[STRING_SSL_CAPATH_PROXY]; - sslc->primary.cipher_list = data->set.str[STRING_SSL_CIPHER_LIST_PROXY]; - sslc->primary.cipher_list13 = data->set.str[STRING_SSL_CIPHER13_LIST_PROXY]; - sslc->primary.pinned_key = data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY]; + sslc->primary.CAfile = ssl_easy_steal(data, STRING_SSL_CAFILE_PROXY); + sslc->primary.CApath = ssl_easy_steal(data, STRING_SSL_CAPATH_PROXY); + sslc->primary.cipher_list = + ssl_easy_steal(data, STRING_SSL_CIPHER_LIST_PROXY); + sslc->primary.cipher_list13 = + ssl_easy_steal(data, STRING_SSL_CIPHER13_LIST_PROXY); + sslc->primary.pinned_key = + ssl_easy_steal(data, STRING_SSL_PINNEDPUBLICKEY_PROXY); sslc->primary.cert_blob = data->set.blobs[BLOB_CERT_PROXY]; sslc->primary.ca_info_blob = data->set.blobs[BLOB_CAINFO_PROXY]; - sslc->primary.issuercert = data->set.str[STRING_SSL_ISSUERCERT_PROXY]; + sslc->primary.issuercert = ssl_easy_steal(data, STRING_SSL_ISSUERCERT_PROXY); sslc->primary.issuercert_blob = data->set.blobs[BLOB_SSL_ISSUERCERT_PROXY]; - sslc->primary.CRLfile = data->set.str[STRING_SSL_CRLFILE_PROXY]; - sslc->primary.cert_type = data->set.str[STRING_CERT_TYPE_PROXY]; - sslc->primary.key = data->set.str[STRING_KEY_PROXY]; - sslc->primary.key_type = data->set.str[STRING_KEY_TYPE_PROXY]; - sslc->primary.key_passwd = data->set.str[STRING_KEY_PASSWD_PROXY]; - sslc->primary.clientcert = data->set.str[STRING_CERT_PROXY]; + sslc->primary.CRLfile = ssl_easy_steal(data, STRING_SSL_CRLFILE_PROXY); + sslc->primary.cert_type = ssl_easy_steal(data, STRING_CERT_TYPE_PROXY); + sslc->primary.key = ssl_easy_steal(data, STRING_KEY_PROXY); + sslc->primary.key_type = ssl_easy_steal(data, STRING_KEY_TYPE_PROXY); + sslc->primary.key_passwd = ssl_easy_steal(data, STRING_KEY_PASSWD_PROXY); + sslc->primary.clientcert = ssl_easy_steal(data, STRING_CERT_PROXY); sslc->primary.key_blob = data->set.blobs[BLOB_KEY_PROXY]; -#ifdef USE_TLS_SRP - sslc->primary.username = data->set.str[STRING_TLSAUTH_USERNAME_PROXY]; - sslc->primary.password = data->set.str[STRING_TLSAUTH_PASSWORD_PROXY]; -#endif #endif /* CURL_DISABLE_PROXY */ return CURLE_OK; diff --git a/Utilities/cmcurl/lib/vtls/vtls_config.h b/Utilities/cmcurl/lib/vtls/vtls_config.h index 44e691dd26..8296604259 100644 --- a/Utilities/cmcurl/lib/vtls/vtls_config.h +++ b/Utilities/cmcurl/lib/vtls/vtls_config.h @@ -47,10 +47,6 @@ struct ssl_primary_config { struct curl_blob *ca_info_blob; struct curl_blob *issuercert_blob; struct curl_blob *key_blob; -#ifdef USE_TLS_SRP - char *username; /* TLS username (for, e.g., SRP) */ - char *password; /* TLS password (for, e.g., SRP) */ -#endif char *curves; /* list of curves to use */ uint32_t version_max; /* max supported version the client wants to use */ uint8_t ssl_options; /* the CURLOPT_SSL_OPTIONS bitmask */ @@ -58,6 +54,7 @@ struct ssl_primary_config { BIT(verifypeer); /* set TRUE if this is desired */ BIT(verifyhost); /* set TRUE if CN/SAN must match hostname */ BIT(verifystatus); /* set TRUE if certificate status must be checked */ + BIT(native_ca_store); /* use the native CA store of operating system */ BIT(cache_session); /* cache session or not */ BIT(deep_copy); /* members are deep copies, eg. owned here */ }; @@ -74,7 +71,6 @@ struct ssl_config_data { BIT(no_partialchain); /* do not accept partial certificate chains */ BIT(revoke_best_effort); /* ignore SSL revocation offline/missing revocation list errors */ - BIT(native_ca_store); /* use the native CA store of operating system */ BIT(auto_client_cert); /* automatically locate and use a client certificate for authentication (Schannel) */ BIT(custom_cafile); /* application has set custom CA file */ diff --git a/Utilities/cmcurl/lib/vtls/vtls_int.h b/Utilities/cmcurl/lib/vtls/vtls_int.h index fa5da6e4d5..7c1881b8d8 100644 --- a/Utilities/cmcurl/lib/vtls/vtls_int.h +++ b/Utilities/cmcurl/lib/vtls/vtls_int.h @@ -116,6 +116,7 @@ struct ssl_connect_data { const struct alpn_spec *alpn; /* ALPN to use or NULL for none */ void *backend; /* vtls backend specific props */ struct cf_call_data call_data; /* data handle used in current call */ + struct Curl_ssl_session *session; /* TLS session in use or NULL */ struct curltime handshake_done; /* time when handshake finished */ struct { char *alpn; /* ALPN value or NULL */ @@ -131,6 +132,7 @@ struct ssl_connect_data { BIT(peer_closed); /* peer has closed connection */ BIT(prefs_checked); /* SSL preferences have been checked */ BIT(input_pending); /* data for SSL_read() may be available */ + BIT(stats_reported); /* connect times have been reported */ }; /* Definitions for SSL Implementations */ @@ -183,7 +185,7 @@ struct Curl_ssl { CURLcode (*send_plain)(struct Curl_cfilter *cf, struct Curl_easy *data, const void *mem, size_t len, size_t *pnwritten); - CURLcode (*get_channel_binding)(struct Curl_easy *data, int sockindex, + CURLcode (*get_channel_binding)(struct Curl_easy *data, int8_t sockindex, struct dynbuf *binding); }; @@ -203,6 +205,13 @@ CURLcode Curl_on_session_reuse(struct Curl_cfilter *cf, struct alpn_spec *alpns, struct Curl_ssl_session *scs, bool *do_early_data, bool early_data_allowed); + +/* Retrieve the SSL session held at the filter of type `cft` at + * data's connection at `sockindex` or NULL if not found/available. */ +struct Curl_ssl_session *Curl_ssl_get_cf_session(struct Curl_easy *data, + const struct Curl_cftype *cft, + int8_t sockindex); + #endif /* USE_SSL */ #endif /* HEADER_CURL_VTLS_INT_H */ diff --git a/Utilities/cmcurl/lib/vtls/vtls_scache.c b/Utilities/cmcurl/lib/vtls/vtls_scache.c index 98beeac036..c703302be8 100644 --- a/Utilities/cmcurl/lib/vtls/vtls_scache.c +++ b/Utilities/cmcurl/lib/vtls/vtls_scache.c @@ -25,10 +25,6 @@ #ifdef USE_SSL -#ifdef HAVE_SYS_TYPES_H -#include -#endif - #include "urldata.h" #include "cfilters.h" @@ -41,18 +37,17 @@ #include "url.h" #include "llist.h" #include "curl_share.h" +#include "curl_threads.h" #include "curl_trc.h" #include "curl_sha256.h" #include "rand.h" - +#include "curlx/strdup.h" /* a peer+tls-config we cache sessions for */ struct Curl_ssl_scache_peer { char *ssl_peer_key; /* id for peer + relevant TLS configuration */ char *clientcert; char *key_passwd; - char *srp_username; - char *srp_password; struct Curl_llist sessions; void *sobj; /* object instance or NULL */ Curl_ssl_scache_obj_dtor *sobj_free; /* free `sobj` callback */ @@ -78,9 +73,13 @@ static CURLcode cf_ssl_peer_key_add_path(struct dynbuf *buf, * when used in another process with different CWD. When a path does not * exist, this does not work. Then, we add the path as is. */ #ifdef _WIN32 - char abspath[_MAX_PATH]; - if(_fullpath(abspath, path, _MAX_PATH)) - return curlx_dyn_addf(buf, ":%s-%s", name, abspath); + char *abspath = _fullpath(NULL, path, 0); + if(abspath) { + CURLcode result = curlx_dyn_addf(buf, ":%s-%s", name, abspath); + /* !checksrc! disable BANNEDFUNC 1 */ + free(abspath); /* allocated by CRT, use system free() */ + return result; + } *is_local = TRUE; #elif defined(HAVE_REALPATH) if(path[0] != '/') { @@ -321,13 +320,6 @@ static CURLcode ssl_peer_key_build(struct ssl_primary_config *ssl, result = cf_ssl_peer_key_add_mtls(&buf, ssl, &is_local); if(result) goto out; -#ifdef USE_TLS_SRP - if(ssl->username || ssl->password) { - result = curlx_dyn_add(&buf, ":SRP-AUTH"); - if(result) - goto out; - } -#endif if(!tls_id || !tls_id[0]) { result = CURLE_FAILED_INIT; @@ -365,6 +357,10 @@ struct Curl_ssl_scache { size_t peer_count; int default_lifetime_secs; long age; +#ifdef USE_MUTEX + curl_mutex_t mutex; + curl_thread_id_t locking_thread; +#endif BIT(is_locked); }; @@ -389,9 +385,9 @@ static void cf_ssl_scache_session_ldestroy(void *udata, void *obj) { struct Curl_ssl_session *s = obj; (void)udata; - curlx_free(CURL_UNCONST(s->sdata)); - curlx_free(CURL_UNCONST(s->quic_tp)); - curlx_free((void *)s->alpn); + curlx_free(s->sdata); + curlx_free(s->quic_tp); + curlx_free(s->alpn); curlx_free(s); } @@ -456,6 +452,54 @@ void Curl_ssl_session_destroy(struct Curl_ssl_session *s) } } +CURLcode Curl_ssl_session_dup(struct Curl_ssl_session *src, + struct Curl_ssl_session **pdest) +{ + struct Curl_ssl_session *dest = NULL; + CURLcode result = CURLE_OUT_OF_MEMORY; + + if(!src || !pdest) + return CURLE_BAD_FUNCTION_ARGUMENT; + *pdest = NULL; + + dest = curlx_calloc(1, sizeof(*dest)); + if(!dest) + goto out; + + dest->ietf_tls_id = src->ietf_tls_id; + dest->valid_until = src->valid_until; + dest->earlydata_max = src->earlydata_max; + dest->sectrust_verified = src->sectrust_verified; + if(src->sdata_len) { + dest->sdata = curlx_memdup(src->sdata, src->sdata_len); + if(!dest->sdata) + goto out; + dest->sdata_len = src->sdata_len; + } + if(src->quic_tp_len) { + dest->quic_tp = curlx_memdup(src->quic_tp, src->quic_tp_len); + if(!dest->quic_tp) + goto out; + dest->quic_tp_len = src->quic_tp_len; + } + if(src->alpn) { + dest->alpn = curlx_strdup(src->alpn); + if(!dest->alpn) + goto out; + } + result = CURLE_OK; + +out: + if(!result) + *pdest = dest; + else { + *pdest = NULL; + if(dest) + Curl_ssl_session_destroy(dest); + } + return result; +} + static void cf_ssl_scache_clear_peer(struct Curl_ssl_scache_peer *peer) { Curl_llist_destroy(&peer->sessions, NULL); @@ -468,10 +512,6 @@ static void cf_ssl_scache_clear_peer(struct Curl_ssl_scache_peer *peer) peer->sobj_free = NULL; curlx_safefree(peer->clientcert); curlx_safefree(peer->key_passwd); -#ifdef USE_TLS_SRP - curlx_safefree(peer->srp_username); - curlx_safefree(peer->srp_password); -#endif curlx_safefree(peer->ssl_peer_key); peer->age = 0; peer->hmac_set = FALSE; @@ -497,7 +537,6 @@ static void cf_ssl_cache_peer_update(struct Curl_ssl_scache_peer *peer) * imported using only the salt+hmac * - the peer key is global, e.g. carrying no relative paths */ peer->exportable = (!peer->clientcert && !peer->key_passwd && - !peer->srp_username && !peer->srp_password && (!peer->ssl_peer_key || cf_ssl_peer_key_is_global(peer->ssl_peer_key))); } @@ -506,8 +545,6 @@ static CURLcode cf_ssl_scache_peer_init(struct Curl_ssl_scache_peer *peer, const char *ssl_peer_key, const char *clientcert, const char *key_passwd, - const char *srp_username, - const char *srp_password, const unsigned char *salt, const unsigned char *hmac) { @@ -539,17 +576,6 @@ static CURLcode cf_ssl_scache_peer_init(struct Curl_ssl_scache_peer *peer, if(!peer->key_passwd) goto out; } - if(srp_username) { - peer->srp_username = curlx_strdup(srp_username); - if(!peer->srp_username) - goto out; - } - if(srp_password) { - peer->srp_password = curlx_strdup(srp_password); - if(!peer->srp_password) - goto out; - } - cf_ssl_cache_peer_update(peer); result = CURLE_OK; out: @@ -624,7 +650,9 @@ CURLcode Curl_ssl_scache_create(size_t max_peers, Curl_llist_init(&scache->peers[i].sessions, cf_ssl_scache_session_ldestroy); } - +#ifdef USE_MUTEX + Curl_mutex_init(&scache->mutex); +#endif *pscache = scache; return CURLE_OK; } @@ -638,6 +666,9 @@ void Curl_ssl_scache_destroy(struct Curl_ssl_scache *scache) cf_ssl_scache_clear_peer(&scache->peers[i]); } curlx_free(scache->peers); +#ifdef USE_MUTEX + Curl_mutex_destroy(&scache->mutex); +#endif curlx_free(scache); } } @@ -659,8 +690,16 @@ void Curl_ssl_scache_lock(struct Curl_easy *data) if(CURL_SHARE_ssl_scache(data)) Curl_share_lock(data, CURL_LOCK_DATA_SSL_SESSION, CURL_LOCK_ACCESS_SINGLE); +#ifdef USE_MUTEX + Curl_mutex_acquire(&scache->mutex); + scache->locking_thread = Curl_thread_get_current_id(); DEBUGASSERT(!scache->is_locked); scache->is_locked = TRUE; + Curl_mutex_release(&scache->mutex); +#else + DEBUGASSERT(!scache->is_locked); + scache->is_locked = TRUE; +#endif } } @@ -669,34 +708,49 @@ void Curl_ssl_scache_unlock(struct Curl_easy *data) { struct Curl_ssl_scache *scache = cf_ssl_scache_get(data); if(scache) { +#ifdef USE_MUTEX + Curl_mutex_acquire(&scache->mutex); + scache->locking_thread = 0; DEBUGASSERT(scache->is_locked); scache->is_locked = FALSE; + Curl_mutex_release(&scache->mutex); +#else + DEBUGASSERT(scache->is_locked); + scache->is_locked = FALSE; +#endif if(CURL_SHARE_ssl_scache(data)) Curl_share_unlock(data, CURL_LOCK_DATA_SSL_SESSION); } } +bool Curl_ssl_scache_is_locked_by_current_thread(struct Curl_easy *data) +{ + struct Curl_ssl_scache *scache = cf_ssl_scache_get(data); + bool locked = FALSE; + if(!scache) + return FALSE; +#ifdef USE_MUTEX + Curl_mutex_acquire(&scache->mutex); + locked = scache->is_locked && Curl_thread_is_current(scache->locking_thread); + Curl_mutex_release(&scache->mutex); +#else + locked = (bool)scache->is_locked; +#endif + return locked; +} + static bool cf_ssl_scache_match_auth(struct Curl_ssl_scache_peer *peer, struct ssl_primary_config *conn_config) { if(!conn_config) { if(peer->clientcert || peer->key_passwd) return FALSE; -#ifdef USE_TLS_SRP - if(peer->srp_username || peer->srp_password) - return FALSE; -#endif return TRUE; } else if(!Curl_safecmp(peer->clientcert, conn_config->clientcert)) return FALSE; if(Curl_timestrcmp(peer->key_passwd, conn_config->key_passwd)) return FALSE; -#ifdef USE_TLS_SRP - if(Curl_timestrcmp(peer->srp_username, conn_config->username) || - Curl_timestrcmp(peer->srp_password, conn_config->password)) - return FALSE; -#endif return TRUE; } @@ -720,7 +774,7 @@ static CURLcode cf_ssl_find_peer_by_key(struct Curl_easy *data, /* check for entries with known peer_key */ for(i = 0; scache && i < scache->peer_count; i++) { if(scache->peers[i].ssl_peer_key && - curl_strequal(ssl_peer_key, scache->peers[i].ssl_peer_key) && + !strcmp(ssl_peer_key, scache->peers[i].ssl_peer_key) && cf_ssl_scache_match_auth(&scache->peers[i], conn_config)) { /* yes, we have a cached session for this! */ *ppeer = &scache->peers[i]; @@ -821,11 +875,6 @@ static CURLcode cf_ssl_add_peer(struct Curl_easy *data, char buffer[64]; const char *ccert = conn_config ? conn_config->clientcert : NULL; const char *kpasswd = conn_config ? conn_config->key_passwd : NULL; - const char *username = NULL, *password = NULL; -#ifdef USE_TLS_SRP - username = conn_config ? conn_config->username : NULL; - password = conn_config ? conn_config->password : NULL; -#endif if(!ccert && conn_config && conn_config->cert_blob) { /* when using a client cert blob, create a name for it */ curl_msnprintf(buffer, sizeof(buffer), @@ -833,7 +882,7 @@ static CURLcode cf_ssl_add_peer(struct Curl_easy *data, ccert = buffer; /* data is strduped by cf_ssl_scache_peer_init */ } result = cf_ssl_scache_peer_init(peer, ssl_peer_key, ccert, kpasswd, - username, password, NULL, NULL); + NULL, NULL); if(result) goto out; /* all ready */ @@ -982,7 +1031,7 @@ CURLcode Curl_ssl_scache_take(struct Curl_cfilter *cf, n = Curl_llist_head(&peer->sessions); if(n) { s = Curl_node_take_elem(n); - (scache->age)++; /* increase general age */ + scache->age++; /* increase general age */ peer->age = scache->age; /* set this as used in this age */ } } @@ -1085,12 +1134,6 @@ void Curl_ssl_scache_remove_all(struct Curl_cfilter *cf, #define CURL_SSL_TICKET_MAX (16 * 1024) -bool Curl_ssl_scache_is_locked(struct Curl_easy *data) -{ - struct Curl_ssl_scache *scache = cf_ssl_scache_get(data); - return scache && scache->is_locked; -} - static CURLcode cf_ssl_scache_peer_set_hmac(struct Curl_ssl_scache_peer *peer) { CURLcode result; @@ -1211,7 +1254,7 @@ CURLcode Curl_ssl_session_import(struct Curl_easy *data, peer = cf_ssl_get_free_peer(scache); if(peer) { result = cf_ssl_scache_peer_init(peer, ssl_peer_key, NULL, NULL, - NULL, NULL, salt, hmac); + salt, hmac); if(result) goto out; } diff --git a/Utilities/cmcurl/lib/vtls/vtls_scache.h b/Utilities/cmcurl/lib/vtls/vtls_scache.h index effb1d8f96..c9f35e800f 100644 --- a/Utilities/cmcurl/lib/vtls/vtls_scache.h +++ b/Utilities/cmcurl/lib/vtls/vtls_scache.h @@ -122,15 +122,16 @@ CURLcode Curl_ssl_scache_add_obj(struct Curl_cfilter *cf, /* All about an SSL session ticket */ struct Curl_ssl_session { - const void *sdata; /* session ticket data, plain bytes */ + uint8_t *sdata; /* session ticket data, plain bytes */ size_t sdata_len; /* number of bytes in sdata */ curl_off_t valid_until; /* seconds since EPOCH until ticket expires */ int ietf_tls_id; /* TLS protocol identifier negotiated */ char *alpn; /* APLN TLS negotiated protocol string */ size_t earlydata_max; /* max 0-RTT data supported by peer */ - const unsigned char *quic_tp; /* Optional QUIC transport param bytes */ + uint8_t *quic_tp; /* Optional QUIC transport param bytes */ size_t quic_tp_len; /* number of bytes in quic_tp */ struct Curl_llist_node list; /* internal storage handling */ + BIT(sectrust_verified); /* session comes from sectrust verified TLS */ }; /* Create a `session` instance. Does NOT need locking. @@ -156,6 +157,10 @@ CURLcode Curl_ssl_session_create2(void *sdata, size_t sdata_len, unsigned char *quic_tp, size_t quic_tp_len, struct Curl_ssl_session **psession); +/* Duplicate an ssl session */ +CURLcode Curl_ssl_session_dup(struct Curl_ssl_session *src, + struct Curl_ssl_session **pdest); + /* Destroy a `session` instance. Can be called with NULL. * Does NOT need locking. */ void Curl_ssl_session_destroy(struct Curl_ssl_session *s); @@ -197,9 +202,9 @@ void Curl_ssl_scache_remove_all(struct Curl_cfilter *cf, struct Curl_easy *data, const char *ssl_peer_key); -#ifdef USE_SSLS_EXPORT +bool Curl_ssl_scache_is_locked_by_current_thread(struct Curl_easy *data); -bool Curl_ssl_scache_is_locked(struct Curl_easy *data); +#ifdef USE_SSLS_EXPORT CURLcode Curl_ssl_session_import(struct Curl_easy *data, const char *ssl_peer_key, diff --git a/Utilities/cmcurl/lib/vtls/vtls_spack.c b/Utilities/cmcurl/lib/vtls/vtls_spack.c index 9cbdecc901..d8e51c4fd1 100644 --- a/Utilities/cmcurl/lib/vtls/vtls_spack.c +++ b/Utilities/cmcurl/lib/vtls/vtls_spack.c @@ -38,6 +38,7 @@ #define CURL_SPACK_ALPN 0x05 #define CURL_SPACK_EARLYDATA 0x06 #define CURL_SPACK_QUICTP 0x07 +#define CURL_SPACK_SECTRUST 0x08 static CURLcode spack_enc8(struct dynbuf *buf, uint8_t b) { @@ -218,6 +219,9 @@ CURLcode Curl_ssl_session_pack(struct Curl_easy *data, if(!result) result = spack_enc32(buf, (uint32_t)s->earlydata_max); } + if(!result && s->sectrust_verified) { + result = spack_enc8(buf, CURL_SPACK_SECTRUST); + } if(!result && s->quic_tp && s->quic_tp_len) { result = spack_enc8(buf, CURL_SPACK_QUICTP); if(!result) @@ -240,6 +244,7 @@ CURLcode Curl_ssl_session_unpack(struct Curl_easy *data, uint16_t val16; uint32_t val32; uint64_t val64; + size_t dlen; CURLcode result; DEBUGASSERT(buf); @@ -267,6 +272,7 @@ CURLcode Curl_ssl_session_unpack(struct Curl_easy *data, switch(val8) { case CURL_SPACK_ALPN: + curlx_free(s->alpn); result = spack_decstr16(&s->alpn, &buf, end); if(result) goto out; @@ -284,17 +290,21 @@ CURLcode Curl_ssl_session_unpack(struct Curl_easy *data, s->ietf_tls_id = val16; break; case CURL_SPACK_QUICTP: { - result = spack_decdata16(&pval8, &s->quic_tp_len, &buf, end); + result = spack_decdata16(&pval8, &dlen, &buf, end); if(result) goto out; + curlx_free(s->quic_tp); s->quic_tp = pval8; + s->quic_tp_len = dlen; break; } case CURL_SPACK_TICKET: { - result = spack_decdata16(&pval8, &s->sdata_len, &buf, end); + result = spack_decdata16(&pval8, &dlen, &buf, end); if(result) goto out; + curlx_free(s->sdata); s->sdata = pval8; + s->sdata_len = dlen; break; } case CURL_SPACK_VALID_UNTIL: @@ -303,6 +313,9 @@ CURLcode Curl_ssl_session_unpack(struct Curl_easy *data, goto out; s->valid_until = (curl_off_t)val64; break; + case CURL_SPACK_SECTRUST: + s->sectrust_verified = TRUE; + break; default: /* unknown tag */ result = CURLE_READ_ERROR; goto out; diff --git a/Utilities/cmcurl/lib/vtls/wolfssl.c b/Utilities/cmcurl/lib/vtls/wolfssl.c index 92eaa7a751..4b9facadf0 100644 --- a/Utilities/cmcurl/lib/vtls/wolfssl.c +++ b/Utilities/cmcurl/lib/vtls/wolfssl.c @@ -34,10 +34,10 @@ #include #if LIBWOLFSSL_VERSION_HEX < 0x05000000 /* wolfSSL 5.0.0 (2021-11-01) */ -#error "wolfSSL version should be at least 5.0.0" +#error "wolfSSL 5.0.0 or greater required" #endif #if defined(OPENSSL_COEXIST) && LIBWOLFSSL_VERSION_HEX < 0x05007006 -#error "wolfSSL 5.7.6 or newer is required to coexist with OpenSSL" +#error "wolfSSL 5.7.6 or greater required to coexist with OpenSSL" #endif /* To determine what functions are available we rely on one or both of: @@ -55,8 +55,8 @@ #include "urldata.h" #include "curl_trc.h" -#include "httpsrr.h" -#include "cf-dns.h" +#include "vdns/cf-dns.h" +#include "vdns/httpsrr.h" #include "vtls/vtls.h" #include "vtls/vtls_int.h" #include "vtls/vtls_scache.h" @@ -162,7 +162,8 @@ static int wssl_tls13_secret_callback(SSL *ssl, int id, return 0; } - Curl_tls_keylog_write(label, client_random, secret, secretSz); + Curl_tls_keylog_write(label, client_random, sizeof(client_random), + secret, secretSz); return 0; } #endif /* HAVE_SECRET_CALLBACK && WOLFSSL_TLS13 */ @@ -203,7 +204,7 @@ static void wssl_log_tls12_secret(WOLFSSL *ssl) return; } - Curl_tls_keylog_write("CLIENT_RANDOM", cr, ms, msLen); + Curl_tls_keylog_write("CLIENT_RANDOM", cr, crLen, ms, msLen); } #endif /* OPENSSL_EXTRA */ @@ -397,14 +398,17 @@ CURLcode Curl_wssl_cache_session(struct Curl_cfilter *cf, int ietf_tls_id, const char *alpn, unsigned char *quic_tp, - size_t quic_tp_len) + size_t quic_tp_len, + struct Curl_ssl_session **pscs) { CURLcode result = CURLE_OK; - struct Curl_ssl_session *sc_session = NULL; + struct Curl_ssl_session *sc_session = NULL, *sc_dup = NULL; unsigned char *sdata = NULL, *sdata_ptr, *qtp_clone = NULL; unsigned int sdata_len; unsigned int earlydata_max = 0; + if(pscs) + *pscs = NULL; if(!session) goto out; @@ -446,13 +450,23 @@ CURLcode Curl_wssl_cache_session(struct Curl_cfilter *cf, earlydata_max, qtp_clone, quic_tp_len, &sc_session); sdata = NULL; /* took ownership of sdata */ + if(!result && pscs && /* return a duplicate if asked for and FTP */ + (cf->conn->scheme->family == CURLPROTO_FTP)) + result = Curl_ssl_session_dup(sc_session, &sc_dup); if(!result) { result = Curl_ssl_scache_put(cf, data, ssl_peer_key, sc_session); /* took ownership of `sc_session` */ + sc_session = NULL; } out: curlx_free(sdata); + if(!result && pscs) { + *pscs = sc_dup; + sc_dup = NULL; + } + Curl_ssl_session_destroy(sc_session); + Curl_ssl_session_destroy(sc_dup); return result; } @@ -465,12 +479,17 @@ static int wssl_vtls_new_session_cb(WOLFSSL *ssl, WOLFSSL_SESSION *session) if(cf && session) { struct ssl_connect_data *connssl = cf->ctx; struct Curl_easy *data = CF_DATA_CURRENT(cf); + struct Curl_ssl_session *scs = NULL; DEBUGASSERT(connssl); DEBUGASSERT(data); if(connssl && data) { (void)Curl_wssl_cache_session(cf, data, connssl->peer.scache_key, session, wolfSSL_version(ssl), - connssl->negotiated.alpn, NULL, 0); + connssl->negotiated.alpn, NULL, 0, &scs); + if(scs) { + Curl_ssl_session_destroy(connssl->session); + connssl->session = scs; + } } } return 0; @@ -498,36 +517,35 @@ static CURLcode wssl_on_session_reuse(struct Curl_cfilter *cf, connssl->earlydata_max); } -static CURLcode wssl_setup_session( +static bool wssl_apply_session( struct Curl_cfilter *cf, struct Curl_easy *data, struct wssl_ctx *wss, struct alpn_spec *alpns, - const char *ssl_peer_key, - Curl_wssl_init_session_reuse_cb *sess_reuse_cb) + Curl_wssl_init_session_reuse_cb *sess_reuse_cb, + struct Curl_ssl_session *scs) { struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); - struct Curl_ssl_session *scs = NULL; - CURLcode result; + CURLcode result = CURLE_OK; + WOLFSSL_SESSION *session = NULL; + bool success = FALSE; - result = Curl_ssl_scache_take(cf, data, ssl_peer_key, &scs); - if(!result && scs && scs->sdata && scs->sdata_len && + if(scs && scs->sdata && scs->sdata_len && (!scs->alpn || Curl_alpn_contains_proto(alpns, scs->alpn))) { - WOLFSSL_SESSION *session; /* wolfSSL changes the passed pointer for whatever reasons, yikes */ const unsigned char *sdata = scs->sdata; session = wolfSSL_d2i_SSL_SESSION(NULL, &sdata, (long)scs->sdata_len); if(session) { int ret = wolfSSL_set_session(wss->ssl, session); if(ret != WOLFSSL_SUCCESS) { - Curl_ssl_session_destroy(scs); - scs = NULL; infof(data, "cached session not accepted (%d), " "removing from cache", ret); + goto out; } else { infof(data, "SSL reusing session with ALPN '%s'", scs->alpn ? scs->alpn : "-"); + success = TRUE; if(ssl_config->earlydata && !cf->conn->bits.connect_only && !strcmp("TLSv1.3", wolfSSL_get_version(wss->ssl))) { @@ -535,7 +553,6 @@ static CURLcode wssl_setup_session( if(sess_reuse_cb) { result = sess_reuse_cb(cf, data, alpns, scs, &do_early_data); if(result) { - wolfSSL_SESSION_free(session); goto out; } } @@ -554,15 +571,14 @@ static CURLcode wssl_setup_session( #endif } } - wolfSSL_SESSION_free(session); } else { failf(data, "could not decode previous session"); } } out: - Curl_ssl_scache_return(cf, data, ssl_peer_key, scs); - return result; + wolfSSL_SESSION_free(session); + return success; } static CURLcode wssl_populate_x509_store(struct Curl_cfilter *cf, @@ -576,16 +592,12 @@ static CURLcode wssl_populate_x509_store(struct Curl_cfilter *cf, /* CURLOPT_CAINFO_BLOB overrides CURLOPT_CAINFO */ (ca_info_blob ? NULL : conn_config->CAfile); const char * const ssl_capath = conn_config->CApath; - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); bool imported_native_ca = FALSE; bool imported_ca_info_blob = FALSE; - /* We do not want to do this again, no matter the outcome */ - wssl->x509_store_setup = TRUE; - #ifndef NO_FILESYSTEM /* load native CA certificates */ - if(ssl_config->native_ca_store) { + if(conn_config->native_ca_store) { #ifdef WOLFSSL_SYS_CA_CERTS if(wolfSSL_CTX_load_system_CA_certs(wssl->ssl_ctx) != WOLFSSL_SUCCESS) { infof(data, "error importing native CA store, continuing anyway"); @@ -630,7 +642,7 @@ static CURLcode wssl_populate_x509_store(struct Curl_cfilter *cf, ssl_cafile, ssl_capath, WOLFSSL_LOAD_FLAG_IGNORE_ERR); - if(WOLFSSL_SUCCESS != rc) { + if(rc != WOLFSSL_SUCCESS) { if(conn_config->verifypeer && !imported_native_ca && !imported_ca_info_blob) { /* Fail if we insist on successfully verifying the server. */ @@ -671,7 +683,7 @@ struct wssl_x509_share { static void wssl_x509_share_free(void *key, size_t key_len, void *p) { struct wssl_x509_share *share = p; - DEBUGASSERT(key_len == (sizeof(MPROTO_WSSL_X509_KEY) - 1)); + DEBUGASSERT(key_len == CURL_CSTRLEN(MPROTO_WSSL_X509_KEY)); DEBUGASSERT(!memcmp(MPROTO_WSSL_X509_KEY, key, key_len)); (void)key; (void)key_len; @@ -715,7 +727,7 @@ static WOLFSSL_X509_STORE *wssl_get_cached_x509_store(struct Curl_cfilter *cf, DEBUGASSERT(multi); share = multi ? Curl_hash_pick(&multi->proto_hash, CURL_UNCONST(MPROTO_WSSL_X509_KEY), - sizeof(MPROTO_WSSL_X509_KEY) - 1) : NULL; + CURL_CSTRLEN(MPROTO_WSSL_X509_KEY)) : NULL; if(share && share->store && !wssl_cached_x509_store_expired(data, share) && !wssl_cached_x509_store_different(cf, share)) { @@ -738,7 +750,7 @@ static void wssl_set_cached_x509_store(struct Curl_cfilter *cf, return; share = Curl_hash_pick(&multi->proto_hash, CURL_UNCONST(MPROTO_WSSL_X509_KEY), - sizeof(MPROTO_WSSL_X509_KEY) - 1); + CURL_CSTRLEN(MPROTO_WSSL_X509_KEY)); if(!share) { share = curlx_calloc(1, sizeof(*share)); @@ -746,7 +758,7 @@ static void wssl_set_cached_x509_store(struct Curl_cfilter *cf, return; if(!Curl_hash_add2(&multi->proto_hash, CURL_UNCONST(MPROTO_WSSL_X509_KEY), - sizeof(MPROTO_WSSL_X509_KEY) - 1, + CURL_CSTRLEN(MPROTO_WSSL_X509_KEY), share, wssl_x509_share_free)) { curlx_free(share); return; @@ -785,6 +797,9 @@ CURLcode Curl_wssl_setup_x509_store(struct Curl_cfilter *cf, WOLFSSL_X509_STORE *cached_store; bool cache_criteria_met; + /* We do not want to do this again, no matter the outcome */ + wssl->x509_store_setup = TRUE; + /* Consider the X509 store cacheable if it comes exclusively from a CAfile, or no source is provided and we are falling back to wolfSSL's built-in default. */ @@ -793,7 +808,7 @@ CURLcode Curl_wssl_setup_x509_store(struct Curl_cfilter *cf, !conn_config->CApath && !conn_config->ca_info_blob && !ssl_config->primary.CRLfile && - !ssl_config->native_ca_store; + !conn_config->native_ca_store; cached_store = cache_criteria_met ? wssl_get_cached_x509_store(cf, data) : NULL; @@ -1151,6 +1166,9 @@ static CURLcode wssl_init_ssl_handle( unsigned char transport, Curl_wssl_init_session_reuse_cb *sess_reuse_cb) { + struct Curl_ssl_session *scs = NULL; + bool session_applied = FALSE; + /* Let's make an SSL structure */ wctx->ssl = wolfSSL_new(wctx->ssl_ctx); if(!wctx->ssl) { @@ -1170,11 +1188,30 @@ static CURLcode wssl_init_ssl_handle( (void)transport; #endif + if((cf->sockindex == SECONDARYSOCKET) && !(cf->cft->flags & CF_TYPE_PROXY)) { + /* FTP is a bitch. On TLS secured transfers, it is a common server + * option to require the client to use the SAME TLS session as on + * the control connection or it fails the request. See #22225. */ + scs = Curl_ssl_get_cf_session(data, cf->cft, FIRSTSOCKET); + if(scs) { + session_applied = wssl_apply_session(cf, data, wctx, alpns, + sess_reuse_cb, scs); + if(session_applied) + CURL_TRC_CF(data, cf, "applied SSL session from control connection"); + scs = NULL; + } + } + /* Check if there is a cached ID we can/should use here! */ - if(Curl_ssl_scache_use(cf, data)) { + if(!session_applied && Curl_ssl_scache_use(cf, data)) { /* Set session from cache if there is one */ - (void)wssl_setup_session(cf, data, wctx, alpns, peer->scache_key, - sess_reuse_cb); + CURLcode result = Curl_ssl_scache_take(cf, data, peer->scache_key, &scs); + if(!result && scs) { + if(wssl_apply_session(cf, data, wctx, alpns, sess_reuse_cb, scs)) + Curl_ssl_scache_return(cf, data, peer->scache_key, scs); + else + Curl_ssl_session_destroy(scs); + } } #ifdef HAVE_ALPN @@ -1222,7 +1259,7 @@ static CURLcode wssl_init_ech(struct wssl_ctx *wctx, { int trying_ech_now = 0; - if(data->set.str[STRING_ECH_PUBLIC]) { + if(CURL_EASY_STR(data, STRING_ECH_PUBLIC)) { infof(data, "ECH: outername not (yet) supported" " with wolfSSL"); return CURLE_SSL_CONNECT_ERROR; @@ -1231,12 +1268,12 @@ static CURLcode wssl_init_ech(struct wssl_ctx *wctx, infof(data, "ECH: GREASE is done by default by" " wolfSSL: no need to ask"); } - if(data->set.tls_ech && data->set.str[STRING_ECH_CONFIG]) { - char *b64val = data->set.str[STRING_ECH_CONFIG]; + if(data->set.tls_ech && CURL_EASY_STR(data, STRING_ECH_CONFIG)) { + const char *b64val = CURL_EASY_STR(data, STRING_ECH_CONFIG); word32 b64len = 0; b64len = (word32)strlen(b64val); - if(b64len && wolfSSL_SetEchConfigsBase64(wctx->ssl, b64val, + if(b64len && wolfSSL_SetEchConfigsBase64(wctx->ssl, CURL_UNCONST(b64val), b64len) != WOLFSSL_SUCCESS) { if(data->set.tls_ech == CURLECH_HARD) return CURLE_SSL_CONNECT_ERROR; @@ -1439,7 +1476,7 @@ bool Curl_wssl_need_httpsrr(struct Curl_easy *data) if(!CURLECH_ENABLED(data)) return FALSE; if((data->set.tls_ech == CURLECH_GREASE) || - data->set.str[STRING_ECH_CONFIG]) + CURL_EASY_STR(data, STRING_ECH_CONFIG)) return FALSE; return TRUE; #else @@ -1502,15 +1539,17 @@ static CURLcode wssl_connect_step1(struct Curl_cfilter *cf, wolfSSL_set_bio(wssl->ssl, bio, bio); } #else /* !USE_BIO_CHAIN */ - curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); - if(sockfd > INT_MAX) { - failf(data, "SSL: socket value too large"); - return CURLE_SSL_CONNECT_ERROR; - } - /* pass the raw socket into the SSL layer */ - if(!wolfSSL_set_fd(wssl->ssl, (int)sockfd)) { - failf(data, "SSL: wolfSSL_set_fd failed"); - return CURLE_SSL_CONNECT_ERROR; + { + curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); + if(sockfd > INT_MAX) { + failf(data, "SSL: socket value too large"); + return CURLE_SSL_CONNECT_ERROR; + } + /* pass the raw socket into the SSL layer */ + if(!wolfSSL_set_fd(wssl->ssl, (int)sockfd)) { + failf(data, "SSL: wolfSSL_set_fd failed"); + return CURLE_SSL_CONNECT_ERROR; + } } #endif /* USE_BIO_CHAIN */ @@ -1540,10 +1579,11 @@ CURLcode Curl_wssl_verify_pinned(struct Curl_cfilter *cf, CURLcode result = CURLE_OK; #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]; + CURL_EASY_STR(data, STRING_SSL_PINNEDPUBLICKEY_PROXY) : + CURL_EASY_STR(data, STRING_SSL_PINNEDPUBLICKEY); #else - const char * const pinnedpubkey = data->set.str[STRING_SSL_PINNEDPUBLICKEY]; + const char * const pinnedpubkey = + CURL_EASY_STR(data, STRING_SSL_PINNEDPUBLICKEY); (void)cf; #endif @@ -1723,15 +1763,15 @@ static CURLcode wssl_handshake(struct Curl_cfilter *cf, struct Curl_easy *data) return CURLE_OK; } else { - if(WOLFSSL_ERROR_WANT_READ == detail) { + if(detail == WOLFSSL_ERROR_WANT_READ) { connssl->io_need = CURL_SSL_IO_NEED_RECV; return CURLE_AGAIN; } - else if(WOLFSSL_ERROR_WANT_WRITE == detail) { + else if(detail == WOLFSSL_ERROR_WANT_WRITE) { connssl->io_need = CURL_SSL_IO_NEED_SEND; return CURLE_AGAIN; } - else if(DOMAIN_NAME_MISMATCH == detail) { + else if(detail == DOMAIN_NAME_MISMATCH) { /* There is no easy way to override only the CN matching. * This enables the override of both mismatching SubjectAltNames * as also mismatching CN fields */ @@ -1739,7 +1779,7 @@ static CURLcode wssl_handshake(struct Curl_cfilter *cf, struct Curl_easy *data) connssl->peer.origin->hostname); return CURLE_PEER_FAILED_VERIFICATION; } - else if(ASN_NO_SIGNER_E == detail) { + else if(detail == ASN_NO_SIGNER_E) { if(conn_config->verifypeer) { failf(data, " CA signer not available for verification"); return CURLE_SSL_CACERT_BADFILE; @@ -1750,11 +1790,11 @@ static CURLcode wssl_handshake(struct Curl_cfilter *cf, struct Curl_easy *data) "continuing anyway"); return CURLE_OK; } - else if(ASN_AFTER_DATE_E == detail) { + else if(detail == ASN_AFTER_DATE_E) { failf(data, "server verification failed: certificate has expired."); return CURLE_PEER_FAILED_VERIFICATION; } - else if(ASN_BEFORE_DATE_E == detail) { + else if(detail == ASN_BEFORE_DATE_E) { failf(data, "server verification failed: certificate not valid yet."); return CURLE_PEER_FAILED_VERIFICATION; } @@ -1923,7 +1963,7 @@ static CURLcode wssl_shutdown(struct Curl_cfilter *cf, *done = TRUE; goto out; } - if(WOLFSSL_ERROR_WANT_WRITE == wolfSSL_get_error(wctx->ssl, nread)) { + if(wolfSSL_get_error(wctx->ssl, nread) == WOLFSSL_ERROR_WANT_WRITE) { CURL_TRC_CF(data, cf, "SSL shutdown still wants to send"); connssl->io_need = CURL_SSL_IO_NEED_SEND; goto out; @@ -1946,7 +1986,7 @@ static CURLcode wssl_shutdown(struct Curl_cfilter *cf, break; case WOLFSSL_ERROR_NONE: /* did not get anything */ case WOLFSSL_ERROR_WANT_READ: - /* wolfSSL has send its notify and now wants to read the reply + /* wolfSSL has sent its notify and now wants to read the reply * from the server. We are not really interested in that. */ CURL_TRC_CF(data, cf, "SSL shutdown sent, want receive"); connssl->io_need = CURL_SSL_IO_NEED_RECV; @@ -2116,7 +2156,7 @@ static CURLcode wssl_connect(struct Curl_cfilter *cf, *done = FALSE; connssl->io_need = CURL_SSL_IO_NEED_NONE; - if(ssl_connect_1 == connssl->connecting_state) { + if(connssl->connecting_state == ssl_connect_1) { #ifdef HAVE_WOLFSSL_CTX_GENERATEECHCONFIG /* if we do ECH and need the HTTPS-RR information for it, * we delay the connect until it arrives or DNS resolve fails. */ @@ -2133,7 +2173,7 @@ static CURLcode wssl_connect(struct Curl_cfilter *cf, connssl->connecting_state = ssl_connect_2; } - if(ssl_connect_2 == connssl->connecting_state) { + if(connssl->connecting_state == ssl_connect_2) { if(connssl->earlydata_state == ssl_earlydata_await) { /* We defer the handshake until request data arrives. */ DEBUGASSERT(connssl->state == ssl_connection_deferred); @@ -2146,7 +2186,7 @@ static CURLcode wssl_connect(struct Curl_cfilter *cf, connssl->connecting_state = ssl_connect_3; } - if(ssl_connect_3 == connssl->connecting_state) { + if(connssl->connecting_state == ssl_connect_3) { /* Once the handshake has errored, it stays in that state and * errors again on every call. */ if(wssl->hs_result) { diff --git a/Utilities/cmcurl/lib/vtls/wolfssl.h b/Utilities/cmcurl/lib/vtls/wolfssl.h index 2490bf3e9c..2016a0069f 100644 --- a/Utilities/cmcurl/lib/vtls/wolfssl.h +++ b/Utilities/cmcurl/lib/vtls/wolfssl.h @@ -88,7 +88,8 @@ CURLcode Curl_wssl_cache_session(struct Curl_cfilter *cf, int ietf_tls_id, const char *alpn, unsigned char *quic_tp, - size_t quic_tp_len); + size_t quic_tp_len, + struct Curl_ssl_session **pscs); #endif CURLcode Curl_wssl_verify_pinned(struct Curl_cfilter *cf, diff --git a/Utilities/cmcurl/lib/ws.c b/Utilities/cmcurl/lib/ws.c index 9820c3e4bd..a7653df678 100644 --- a/Utilities/cmcurl/lib/ws.c +++ b/Utilities/cmcurl/lib/ws.c @@ -43,16 +43,15 @@ #include "curlx/strparse.h" #include "curlx/strcopy.h" -/*** - RFC 6455 Section 5.2 +/* RFC 6455 Section 5.2 - 0 1 2 3 4 5 6 7 - +-+-+-+-+-------+ - |F|R|R|R| opcode| - |I|S|S|S| (4) | - |N|V|V|V| | - | |1|2|3| | -*/ + 0 1 2 3 4 5 6 7 + +-+-+-+-+-------+ + |F|R|R|R| opcode| + |I|S|S|S| (4) | + |N|V|V|V| | + | |1|2|3| | + */ #define WSBIT_FIN 0x80 #define WSBIT_RSV1 0x40 #define WSBIT_RSV2 0x20 @@ -477,7 +476,7 @@ static CURLcode ws_dec_read_head(struct ws_decoder *dec, dec->frame_age = 0; dec->payload_offset = 0; - ws_dec_info(dec, data, "decoded"); + ws_dec_info(dec, data, "head"); return CURLE_OK; } return CURLE_AGAIN; @@ -496,7 +495,8 @@ static CURLcode ws_dec_pass_payload(struct ws_decoder *dec, size_t remain = curlx_sotouz_range(dec->payload_len - dec->payload_offset, 0, SIZE_MAX); - while(remain && Curl_bufq_peek(inraw, &inbuf, &inlen)) { + while(remain && Curl_bufq_peek(inraw, &inbuf, &inlen) && + !Curl_cwriter_is_paused(data)) { if(inlen > remain) inlen = remain; result = write_cb(inbuf, inlen, dec->frame_age, dec->frame_flags, @@ -695,6 +695,7 @@ static CURLcode ws_cw_dec_next(const uint8_t *buf, size_t buflen, update_meta(ws, frame_age, frame_flags, payload_offset, payload_len, buflen); + CURL_TRC_WRITE(data, "[WS] pass %zu decoded bytes", buflen); result = Curl_cwriter_write(data, ctx->next_writer, (ctx->cw_type | CLIENTWRITE_0LEN), (const char *)buf, buflen); @@ -713,7 +714,7 @@ static CURLcode ws_cw_write(struct Curl_easy *data, struct websocket *ws; CURLcode result = CURLE_OK; - CURL_TRC_WRITE(data, "ws_cw_write(len=%zu, type=%d)", nbytes, type); + CURL_TRC_WRITE(data, "[WS] write(len=%zu, type=%d)", nbytes, type); if(!(type & CLIENTWRITE_BODY) || data->set.ws_raw_mode) return Curl_cwriter_write(data, writer->next, type, buf, nbytes); @@ -733,7 +734,11 @@ static CURLcode ws_cw_write(struct Curl_easy *data, } } - while(!Curl_bufq_is_empty(&ctx->buf)) { + result = Curl_cwriter_flush(data, writer->next); + if(result) + goto out; + + while(!Curl_bufq_is_empty(&ctx->buf) && !Curl_cwriter_is_paused(data)) { struct ws_cw_dec_ctx pass_ctx; pass_ctx.data = data; pass_ctx.ws = ws; @@ -749,7 +754,8 @@ static CURLcode ws_cw_write(struct Curl_easy *data, } else if(result) { failf(data, "[WS] decode payload error %d", (int)result); - return result; + Curl_bufq_reset(&ctx->buf); + goto out; } } @@ -761,19 +767,72 @@ static CURLcode ws_cw_write(struct Curl_easy *data, out: if(!result) { - result = ws_flush(data, ws, Curl_is_in_callback(data)); + result = ws_flush(data, ws, Curl_api_is_in_callback(data)); if(result == CURLE_AGAIN) result = CURLE_OK; } return result; } +static CURLcode ws_cw_flush(struct Curl_easy *data, + struct Curl_cwriter *writer) +{ + CURLcode result = CURLE_OK; + + CURL_TRC_WRITE(data, "[ws] flush"); + if(!data->set.ws_raw_mode) { + struct ws_cw_ctx *ctx = writer->ctx; + struct websocket *ws; + + /* Frames should be written one by one, else the meta data does + * not fit. Flush the next writer first, so it does not aggregate + * our flushed data with anything it might have buffered. */ + result = Curl_cwriter_flush(data, writer->next); + if(result) + goto out; + + ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN); + if(!ws) { + failf(data, "[WS] not a websocket transfer"); + return CURLE_FAILED_INIT; + } + + while(!Curl_bufq_is_empty(&ctx->buf) && !Curl_cwriter_is_paused(data)) { + struct ws_cw_dec_ctx pass_ctx; + pass_ctx.data = data; + pass_ctx.ws = ws; + pass_ctx.next_writer = writer->next; + pass_ctx.cw_type = CLIENTWRITE_BODY; + result = ws_dec_pass(&ws->dec, data, &ctx->buf, + ws_cw_dec_next, &pass_ctx); + if(result == CURLE_AGAIN) { + /* insufficient amount of data, keep it for later. + * we pretend to have written all since we have a copy */ + result = CURLE_OK; + goto out; + } + else if(result) { + failf(data, "[WS] decode payload error %d", (int)result); + Curl_bufq_reset(&ctx->buf); + goto out; + } + } + } + +out: + if(!result) + result = Curl_cwriter_flush(data, writer->next); + return result; +} + /* WebSocket payload decoding client writer. */ static const struct Curl_cwtype ws_cw_decode = { "ws-decode", NULL, + 0, ws_cw_init, ws_cw_write, + ws_cw_flush, ws_cw_close, sizeof(struct ws_cw_ctx) }; @@ -802,28 +861,27 @@ static void ws_enc_init(struct ws_encoder *enc) ws_enc_reset(enc); } -/*** - RFC 6455 Section 5.2 +/* RFC 6455 Section 5.2 - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-------+-+-------------+-------------------------------+ - |F|R|R|R| opcode|M| Payload len | Extended payload length | - |I|S|S|S| (4) |A| (7) | (16/64) | - |N|V|V|V| |S| | (if payload len==126/127) | - | |1|2|3| |K| | | - +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + - | Extended payload length continued, if payload len == 127 | - + - - - - - - - - - - - - - - - +-------------------------------+ - | |Masking-key, if MASK set to 1 | - +-------------------------------+-------------------------------+ - | Masking-key (continued) | Payload Data | - +-------------------------------- - - - - - - - - - - - - - - - + - : Payload Data continued ... : - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - | Payload Data continued ... | - +---------------------------------------------------------------+ -*/ + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-------+-+-------------+-------------------------------+ + |F|R|R|R| opcode|M| Payload len | Extended payload length | + |I|S|S|S| (4) |A| (7) | (16/64) | + |N|V|V|V| |S| | (if payload len==126/127) | + | |1|2|3| |K| | | + +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + + | Extended payload length continued, if payload len == 127 | + + - - - - - - - - - - - - - - - +-------------------------------+ + | |Masking-key, if MASK set to 1 | + +-------------------------------+-------------------------------+ + | Masking-key (continued) | Payload Data | + +-------------------------------- - - - - - - - - - - - - - - - + + : Payload Data continued ... : + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + | Payload Data continued ... | + +---------------------------------------------------------------+ + */ static CURLcode ws_enc_add_frame(struct Curl_easy *data, struct ws_encoder *enc, @@ -1058,7 +1116,7 @@ static CURLcode ws_enc_send(struct Curl_easy *data, } } else { - result = ws_flush(data, ws, Curl_is_in_callback(data)); + result = ws_flush(data, ws, Curl_api_is_in_callback(data)); if(result) return result; @@ -1091,7 +1149,7 @@ static CURLcode ws_enc_send(struct Curl_easy *data, } /* flush, blocking when in callback */ - result = ws_flush(data, ws, Curl_is_in_callback(data)); + result = ws_flush(data, ws, Curl_api_is_in_callback(data)); if(!result && ws->sendbuf_payload > 0) { *pnsent += ws->sendbuf_payload; buffer += ws->sendbuf_payload; @@ -1363,14 +1421,13 @@ CURLcode Curl_ws_accept(struct Curl_easy *data, The sent value is the base64 encoded version of a SHA-1 hash done on the |Sec-WebSocket-Key| header field concatenated with - the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11". - */ + the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11". */ /* If the response includes a |Sec-WebSocket-Extensions| header field and this header field indicates the use of an extension that was not present in the client's handshake (the server has indicated an extension not requested by the client), the client MUST Fail the WebSocket Connection. - */ + */ /* If the response includes a |Sec-WebSocket-Protocol| header field and this header field indicates the use of a subprotocol that was @@ -1529,104 +1586,116 @@ static CURLcode nw_in_recv(void *reader_ctx, size_t *pnread) { struct Curl_easy *data = reader_ctx; - return curl_easy_recv(data, buf, buflen, pnread); + return Curl_easy_recv(data, buf, buflen, pnread); } CURLcode curl_ws_recv(CURL *curl, void *buffer, size_t buflen, size_t *recv, const struct curl_ws_frame **metap) { - struct Curl_easy *data = curl; - struct connectdata *conn; - struct websocket *ws; - struct ws_collect ctx; + struct Curl_eapi_guard guard; + CURLcode result = CURLE_OK; *recv = 0; *metap = NULL; - if(!GOOD_EASY_HANDLE(data) || (buflen && !buffer)) - return CURLE_BAD_FUNCTION_ARGUMENT; + if(CURL_EAPI_ENTER(&guard, curl, ws_recv, &result)) { + struct Curl_easy *data = curl; + struct connectdata *conn; + struct websocket *ws; + struct ws_collect ctx; - conn = data->conn; - if(!conn) { - /* Unhappy hack with lifetimes of transfers and connection */ - if(!data->set.connect_only) { - failf(data, "[WS] CONNECT_ONLY is required"); - return CURLE_UNSUPPORTED_PROTOCOL; + if(buflen && !buffer) { + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; } - Curl_getconnectinfo(data, &conn); + conn = data->conn; if(!conn) { - failf(data, "[WS] connection not found"); - return CURLE_BAD_FUNCTION_ARGUMENT; - } - } - ws = Curl_conn_meta_get(conn, CURL_META_PROTO_WS_CONN); - if(!ws) { - failf(data, "[WS] connection is not setup for websocket"); - return CURLE_BAD_FUNCTION_ARGUMENT; - } - - memset(&ctx, 0, sizeof(ctx)); - ctx.data = data; - ctx.ws = ws; - ctx.buffer = buffer; - ctx.buflen = buflen; - - while(1) { - CURLcode result; - - /* receive more when our buffer is empty */ - if(Curl_bufq_is_empty(&ws->recvbuf)) { - size_t n; - result = Curl_bufq_slurp(&ws->recvbuf, nw_in_recv, data, &n); - if(result) - return result; - else if(n == 0) { - /* connection closed */ - infof(data, "[WS] connection expectedly closed?"); - return CURLE_GOT_NOTHING; + /* Unhappy hack with lifetimes of transfers and connection */ + if(!data->set.connect_only) { + failf(data, "[WS] CONNECT_ONLY is required"); + result = CURLE_UNSUPPORTED_PROTOCOL; + goto out; } - CURL_TRC_WS(data, "curl_ws_recv, added %zu bytes from network", - Curl_bufq_len(&ws->recvbuf)); - } - result = ws_dec_pass(&ws->dec, data, &ws->recvbuf, - ws_client_collect, &ctx); - if(result == CURLE_AGAIN) { - if(!ctx.written) { - ws_dec_info(&ws->dec, data, "need more input"); - continue; /* nothing written, try more input */ + Curl_getconnectinfo(data, &conn); + if(!conn) { + failf(data, "[WS] connection not found"); + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; } - break; } - else if(result) { - return result; + ws = Curl_conn_meta_get(conn, CURL_META_PROTO_WS_CONN); + if(!ws) { + failf(data, "[WS] connection is not setup for websocket"); + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; } - else if(ctx.written) { - /* The decoded frame is passed back to our caller. - * There are frames like PING were we auto-respond to and - * that we do not return. For these `ctx.written` is not set. */ - break; - } - } - /* update frame information to be passed back */ - update_meta(ws, ctx.frame_age, ctx.frame_flags, ctx.payload_offset, - ctx.payload_len, ctx.bufidx); - *metap = &ws->recvframe; - *recv = ws->recvframe.len; - CURL_TRC_WS(data, "curl_ws_recv(len=%zu) -> %zu bytes (frame at %" - FMT_OFF_T ", %" FMT_OFF_T " left)", - buflen, *recv, ws->recvframe.offset, - ws->recvframe.bytesleft); - /* all's well, try to send any pending control. we do not know - * when the application will call `curl_ws_send()` again. */ - if(!data->set.ws_raw_mode && ws->pending.type) { - CURLcode r2 = ws_enc_add_pending(data, ws); - if(!r2) - (void)ws_flush(data, ws, Curl_is_in_callback(data)); + memset(&ctx, 0, sizeof(ctx)); + ctx.data = data; + ctx.ws = ws; + ctx.buffer = buffer; + ctx.buflen = buflen; + + while(1) { + /* receive more when our buffer is empty */ + if(Curl_bufq_is_empty(&ws->recvbuf)) { + size_t n; + result = Curl_bufq_slurp(&ws->recvbuf, nw_in_recv, data, &n); + if(result) + goto out; + else if(n == 0) { + /* connection closed */ + infof(data, "[WS] connection expectedly closed?"); + result = CURLE_GOT_NOTHING; + goto out; + } + CURL_TRC_WS(data, "curl_ws_recv, added %zu bytes from network", + Curl_bufq_len(&ws->recvbuf)); + } + + result = ws_dec_pass(&ws->dec, data, &ws->recvbuf, + ws_client_collect, &ctx); + if(result == CURLE_AGAIN) { + if(!ctx.written) { + ws_dec_info(&ws->dec, data, "need more input"); + continue; /* nothing written, try more input */ + } + break; + } + else if(result) { + goto out; + } + else if(ctx.written) { + /* The decoded frame is passed back to our caller. + * There are frames like PING were we auto-respond to and + * that we do not return. For these `ctx.written` is not set. */ + break; + } + } + + /* update frame information to be passed back */ + update_meta(ws, ctx.frame_age, ctx.frame_flags, ctx.payload_offset, + ctx.payload_len, ctx.bufidx); + *metap = &ws->recvframe; + *recv = ws->recvframe.len; + CURL_TRC_WS(data, "curl_ws_recv(len=%zu) -> %zu bytes (frame at %" + FMT_OFF_T ", %" FMT_OFF_T " left)", + buflen, *recv, ws->recvframe.offset, + ws->recvframe.bytesleft); + /* all's well, try to send any pending control. we do not know + * when the application will call `curl_ws_send()` again. */ + if(!data->set.ws_raw_mode && ws->pending.type) { + CURLcode r2 = ws_enc_add_pending(data, ws); + if(!r2) + (void)ws_flush(data, ws, Curl_api_is_in_callback(data)); + } + result = CURLE_OK; } - return CURLE_OK; +out: + CURL_EAPI_LEAVE(&guard); + return result; } static CURLcode ws_flush(struct Curl_easy *data, struct websocket *ws, @@ -1669,7 +1738,7 @@ static CURLcode ws_flush(struct Curl_easy *data, struct websocket *ws, result = ws_send_raw_blocking(data, ws, (const char *)out, outlen); n = result ? 0 : outlen; } - else if(data->set.connect_only || Curl_is_in_callback(data)) + else if(data->set.connect_only || Curl_api_is_in_callback(data)) result = Curl_senddata(data, out, outlen, &n); else { result = Curl_xfer_send(data, out, outlen, FALSE, &n); @@ -1753,7 +1822,7 @@ static CURLcode ws_send_raw(struct Curl_easy *data, const void *buffer, if(!buflen) return CURLE_OK; - if(Curl_is_in_callback(data)) { + if(Curl_api_is_in_callback(data)) { /* When invoked from inside callbacks, we do a blocking send as the * callback will probably not implement partial writes that may then * mess up the ws framing subsequently. @@ -1783,75 +1852,80 @@ CURLcode curl_ws_send(CURL *curl, const void *buffer_arg, curl_off_t fragsize, unsigned int flags) { - struct websocket *ws; - const uint8_t *buffer = buffer_arg; + struct Curl_eapi_guard guard; CURLcode result = CURLE_OK; - struct Curl_easy *data = curl; - size_t ndummy; - size_t *pnsent = sent ? sent : &ndummy; - if(!GOOD_EASY_HANDLE(data)) - return CURLE_BAD_FUNCTION_ARGUMENT; - CURL_TRC_WS(data, "curl_ws_send(len=%zu, fragsize=%" FMT_OFF_T - ", flags=%x), raw=%d", - buflen, fragsize, flags, data->set.ws_raw_mode); + if(CURL_EAPI_ENTER(&guard, curl, ws_send, &result)) { + struct websocket *ws; + const uint8_t *buffer = buffer_arg; + struct Curl_easy *data = curl; + size_t ndummy; + size_t *pnsent = sent ? sent : &ndummy; - *pnsent = 0; + CURL_TRC_WS(data, "curl_ws_send(len=%zu, fragsize=%" FMT_OFF_T + ", flags=%x), raw=%d", + buflen, fragsize, flags, data->set.ws_raw_mode); - if(!buffer && buflen) { - failf(data, "[WS] buffer is NULL when buflen is not"); - result = CURLE_BAD_FUNCTION_ARGUMENT; - goto out; - } + *pnsent = 0; - if(!data->conn && data->set.connect_only) { - result = Curl_connect_only_attach(data); - if(result) + if(!buffer && buflen) { + failf(data, "[WS] buffer is NULL when buflen is not"); + result = CURLE_BAD_FUNCTION_ARGUMENT; goto out; - } - if(!data->conn) { - failf(data, "[WS] No associated connection"); - result = CURLE_SEND_ERROR; - goto out; - } - ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN); - if(!ws) { - failf(data, "[WS] Not a websocket transfer"); - result = CURLE_SEND_ERROR; - goto out; - } + } - if(data->set.ws_raw_mode) { - /* In raw mode, we write directly to the connection */ - /* try flushing any content still waiting to be sent. */ - result = ws_flush(data, ws, FALSE); - if(result) + if(!data->conn && data->set.connect_only) { + result = Curl_connect_only_attach(data); + if(result) + goto out; + } + if(!data->conn) { + failf(data, "[WS] No associated connection"); + result = CURLE_SEND_ERROR; goto out; + } + ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN); + if(!ws) { + failf(data, "[WS] Not a websocket transfer"); + result = CURLE_SEND_ERROR; + goto out; + } - if(!buffer) { - failf(data, "[WS] buffer is NULL in raw mode"); - return CURLE_BAD_FUNCTION_ARGUMENT; + if(data->set.ws_raw_mode) { + /* In raw mode, we write directly to the connection */ + /* try flushing any content still waiting to be sent. */ + result = ws_flush(data, ws, FALSE); + if(result) + goto out; + + if(!buffer) { + failf(data, "[WS] buffer is NULL in raw mode"); + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; + } + if(!sent) { + failf(data, "[WS] sent is NULL in raw mode"); + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; + } + if(fragsize || flags) { + failf(data, "[WS] fragsize and flags must be zero in raw mode"); + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; + } + result = ws_send_raw(data, buffer, buflen, pnsent); + goto out; } - if(!sent) { - failf(data, "[WS] sent is NULL in raw mode"); - return CURLE_BAD_FUNCTION_ARGUMENT; - } - if(fragsize || flags) { - failf(data, "[WS] fragsize and flags must be zero in raw mode"); - return CURLE_BAD_FUNCTION_ARGUMENT; - } - result = ws_send_raw(data, buffer, buflen, pnsent); - goto out; + + /* Not RAW mode, we do the frame encoding */ + result = ws_enc_send(data, ws, buffer, buflen, fragsize, flags, pnsent); + CURL_TRC_WS(data, "curl_ws_send(len=%zu, fragsize=%" FMT_OFF_T + ", flags=%x, raw=%d) -> %d, %zu", + buflen, fragsize, flags, data->set.ws_raw_mode, (int)result, + *pnsent); } - - /* Not RAW mode, we do the frame encoding */ - result = ws_enc_send(data, ws, buffer, buflen, fragsize, flags, pnsent); - out: - CURL_TRC_WS(data, "curl_ws_send(len=%zu, fragsize=%" FMT_OFF_T - ", flags=%x, raw=%d) -> %d, %zu", - buflen, fragsize, flags, data->set.ws_raw_mode, (int)result, - *pnsent); + CURL_EAPI_LEAVE(&guard); return result; } @@ -1871,7 +1945,7 @@ const struct curl_ws_frame *curl_ws_meta(CURL *curl) /* we only return something for websocket, called from within the callback when not using raw mode */ struct Curl_easy *data = curl; - if(GOOD_EASY_HANDLE(data) && Curl_is_in_callback(data) && + if(GOOD_EASY_HANDLE(data) && Curl_api_is_in_callback(data) && data->conn && !data->set.ws_raw_mode) { struct websocket *ws; ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN); @@ -1885,46 +1959,48 @@ CURL_EXTERN CURLcode curl_ws_start_frame(CURL *curl, unsigned int flags, curl_off_t frame_len) { - struct websocket *ws; + struct Curl_eapi_guard guard; CURLcode result = CURLE_OK; - struct Curl_easy *data = curl; - if(!GOOD_EASY_HANDLE(data)) - return CURLE_BAD_FUNCTION_ARGUMENT; + if(CURL_EAPI_ENTER(&guard, curl, ws_start_frame, &result)) { + struct Curl_easy *data = curl; + struct websocket *ws; - if(data->set.ws_raw_mode) { - failf(data, "cannot curl_ws_start_frame() with CURLWS_RAW_MODE enabled"); - return CURLE_FAILED_INIT; + if(data->set.ws_raw_mode) { + failf(data, "cannot curl_ws_start_frame() with CURLWS_RAW_MODE enabled"); + result = CURLE_FAILED_INIT; + goto out; + } + + CURL_TRC_WS(data, "curl_ws_start_frame(flags=%x, frame_len=%" FMT_OFF_T, + flags, frame_len); + + if(!data->conn) { + failf(data, "[WS] No associated connection"); + result = CURLE_SEND_ERROR; + goto out; + } + ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN); + if(!ws) { + failf(data, "[WS] Not a websocket transfer"); + result = CURLE_SEND_ERROR; + goto out; + } + + if(ws->enc.payload_remain) { + failf(data, "[WS] previous frame not finished"); + result = CURLE_SEND_ERROR; + goto out; + } + + result = ws_enc_write_head(data, ws, &ws->enc, flags, frame_len, + &ws->sendbuf); + if(result) + CURL_TRC_WS(data, "curl_start_frame(), error adding frame head %d", + (int)result); } - - CURL_TRC_WS(data, "curl_ws_start_frame(flags=%x, frame_len=%" FMT_OFF_T, - flags, frame_len); - - if(!data->conn) { - failf(data, "[WS] No associated connection"); - result = CURLE_SEND_ERROR; - goto out; - } - ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN); - if(!ws) { - failf(data, "[WS] Not a websocket transfer"); - result = CURLE_SEND_ERROR; - goto out; - } - - if(ws->enc.payload_remain) { - failf(data, "[WS] previous frame not finished"); - result = CURLE_SEND_ERROR; - goto out; - } - - result = ws_enc_write_head(data, ws, &ws->enc, flags, frame_len, - &ws->sendbuf); - if(result) - CURL_TRC_WS(data, "curl_start_frame(), error adding frame head %d", - (int)result); - out: + CURL_EAPI_LEAVE(&guard); return result; }